~nobuto/ubuntu/natty/synergy/merge-from-experimental

« back to all changes in this revision

Viewing changes to lib/server/CServer.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Daniel Lutz
  • Date: 2003-10-31 19:36:30 UTC
  • Revision ID: james.westby@ubuntu.com-20031031193630-knbv79x5az7qh49y
Tags: upstream-1.0.14
ImportĀ upstreamĀ versionĀ 1.0.14

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * synergy -- mouse and keyboard sharing utility
 
3
 * Copyright (C) 2002 Chris Schoeneman
 
4
 * 
 
5
 * This package is free software; you can redistribute it and/or
 
6
 * modify it under the terms of the GNU General Public License
 
7
 * found in the file COPYING that should have accompanied this file.
 
8
 * 
 
9
 * This package is distributed in the hope that it will be useful,
 
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
 * GNU General Public License for more details.
 
13
 */
 
14
 
 
15
#include "CServer.h"
 
16
#include "CHTTPServer.h"
 
17
#include "CPrimaryClient.h"
 
18
#include "IPrimaryScreenFactory.h"
 
19
#include "CInputPacketStream.h"
 
20
#include "COutputPacketStream.h"
 
21
#include "CProtocolUtil.h"
 
22
#include "CClientProxy1_0.h"
 
23
#include "CClientProxy1_1.h"
 
24
#include "OptionTypes.h"
 
25
#include "ProtocolTypes.h"
 
26
#include "XScreen.h"
 
27
#include "XSynergy.h"
 
28
#include "CTCPListenSocket.h"
 
29
#include "IDataSocket.h"
 
30
#include "ISocketFactory.h"
 
31
#include "XSocket.h"
 
32
#include "IStreamFilterFactory.h"
 
33
#include "CLock.h"
 
34
#include "CThread.h"
 
35
#include "CTimerThread.h"
 
36
#include "XMT.h"
 
37
#include "XThread.h"
 
38
#include "CFunctionJob.h"
 
39
#include "CLog.h"
 
40
#include "CStopwatch.h"
 
41
#include "TMethodJob.h"
 
42
#include "CArch.h"
 
43
 
 
44
//
 
45
// CServer
 
46
//
 
47
 
 
48
const SInt32                    CServer::s_httpMaxSimultaneousRequests = 3;
 
49
 
 
50
CServer::CServer(const CString& serverName) :
 
51
        m_name(serverName),
 
52
        m_error(false),
 
53
        m_bindTimeout(5.0 * 60.0),
 
54
        m_screenFactory(NULL),
 
55
        m_socketFactory(NULL),
 
56
        m_streamFilterFactory(NULL),
 
57
        m_acceptClientThread(NULL),
 
58
        m_active(NULL),
 
59
        m_primaryClient(NULL),
 
60
        m_seqNum(0),
 
61
        m_activeSaver(NULL),
 
62
        m_httpServer(NULL),
 
63
        m_httpAvailable(&m_mutex, s_httpMaxSimultaneousRequests),
 
64
        m_switchDir(kNoDirection),
 
65
        m_switchScreen(NULL),
 
66
        m_switchWaitDelay(0.0),
 
67
        m_switchWaitEngaged(false),
 
68
        m_switchTwoTapDelay(0.0),
 
69
        m_switchTwoTapEngaged(false),
 
70
        m_switchTwoTapArmed(false),
 
71
        m_switchTwoTapZone(3),
 
72
        m_status(kNotRunning)
 
73
{
 
74
        // do nothing
 
75
}
 
76
 
 
77
CServer::~CServer()
 
78
{
 
79
        delete m_screenFactory;
 
80
        delete m_socketFactory;
 
81
        delete m_streamFilterFactory;
 
82
}
 
83
 
 
84
void
 
85
CServer::open()
 
86
{
 
87
        // open the screen
 
88
        try {
 
89
                LOG((CLOG_INFO "opening screen"));
 
90
                openPrimaryScreen();
 
91
                setStatus(kNotRunning);
 
92
        }
 
93
        catch (XScreen& e) {
 
94
                // can't open screen
 
95
                setStatus(kError, e.what());
 
96
                LOG((CLOG_INFO "failed to open screen"));
 
97
                throw;
 
98
        }
 
99
        catch (XUnknownClient& e) {
 
100
                // can't open screen
 
101
                setStatus(kServerNameUnknown);
 
102
                LOG((CLOG_CRIT "unknown screen name `%s'", e.getName().c_str()));
 
103
                throw;
 
104
        }
 
105
}
 
106
 
 
107
void
 
108
CServer::mainLoop()
 
109
{
 
110
        // check preconditions
 
111
        {
 
112
                CLock lock(&m_mutex);
 
113
                assert(m_primaryClient != NULL);
 
114
        }
 
115
 
 
116
        try {
 
117
                setStatus(kNotRunning);
 
118
                LOG((CLOG_NOTE "starting server"));
 
119
 
 
120
                // start listening for new clients
 
121
                m_acceptClientThread = new CThread(startThread(
 
122
                                                                new TMethodJob<CServer>(this,
 
123
                                                                        &CServer::acceptClients)));
 
124
 
 
125
                // start listening for HTTP requests
 
126
                if (m_config.getHTTPAddress().isValid()) {
 
127
                        m_httpServer = new CHTTPServer(this);
 
128
                        startThread(new TMethodJob<CServer>(this,
 
129
                                                                &CServer::acceptHTTPClients));
 
130
                }
 
131
 
 
132
                // handle events
 
133
                m_primaryClient->mainLoop();
 
134
 
 
135
                // clean up
 
136
                LOG((CLOG_NOTE "stopping server"));
 
137
 
 
138
                // use a macro to write the stuff that should go into a finally
 
139
                // block so we can repeat it easily.  stroustrup's view that
 
140
                // "resource acquistion is initialization" is a better solution
 
141
                // than a finally block is parochial.  they both have their
 
142
                // place.  adding finally to C++ would've been a drop in a big
 
143
                // bucket.
 
144
#define FINALLY do {                                    \
 
145
                stopThreads();                                  \
 
146
                delete m_httpServer;                    \
 
147
                m_httpServer = NULL;                    \
 
148
                runStatusJobs();                                \
 
149
                } while (false)
 
150
                FINALLY;
 
151
        }
 
152
        catch (XMT& e) {
 
153
                LOG((CLOG_ERR "server error: %s", e.what()));
 
154
                setStatus(kError, e.what());
 
155
 
 
156
                // clean up
 
157
                LOG((CLOG_NOTE "stopping server"));
 
158
                FINALLY;
 
159
                throw;
 
160
        }
 
161
        catch (XBase& e) {
 
162
                LOG((CLOG_ERR "server error: %s", e.what()));
 
163
                setStatus(kError, e.what());
 
164
 
 
165
                // clean up
 
166
                LOG((CLOG_NOTE "stopping server"));
 
167
                FINALLY;
 
168
        }
 
169
        catch (XThread&) {
 
170
                setStatus(kNotRunning);
 
171
 
 
172
                // clean up
 
173
                LOG((CLOG_NOTE "stopping server"));
 
174
                FINALLY;
 
175
                throw;
 
176
        }
 
177
        catch (...) {
 
178
                LOG((CLOG_DEBUG "unknown server error"));
 
179
                setStatus(kError);
 
180
 
 
181
                // clean up
 
182
                LOG((CLOG_NOTE "stopping server"));
 
183
                FINALLY;
 
184
                throw;
 
185
        }
 
186
#undef FINALLY
 
187
 
 
188
        // throw if there was an error
 
189
        if (m_error) {
 
190
                LOG((CLOG_DEBUG "forwarding child thread exception"));
 
191
                throw XServerRethrow();
 
192
        }
 
193
}
 
194
 
 
195
void
 
196
CServer::exitMainLoop()
 
197
{
 
198
        m_primaryClient->exitMainLoop();
 
199
}
 
200
 
 
201
void
 
202
CServer::exitMainLoopWithError()
 
203
{
 
204
        {
 
205
                CLock lock(&m_mutex);
 
206
                m_error = true;
 
207
        }
 
208
        exitMainLoop();
 
209
}
 
210
 
 
211
void
 
212
CServer::close()
 
213
{
 
214
        if (m_primaryClient != NULL) {
 
215
                closePrimaryScreen();
 
216
        }
 
217
        LOG((CLOG_INFO "closed screen"));
 
218
}
 
219
 
 
220
bool
 
221
CServer::setConfig(const CConfig& config)
 
222
{
 
223
        // refuse configuration if it doesn't include the primary screen
 
224
        {
 
225
                CLock lock(&m_mutex);
 
226
                if (m_primaryClient != NULL &&
 
227
                        !config.isScreen(m_primaryClient->getName())) {
 
228
                        return false;
 
229
                }
 
230
        }
 
231
 
 
232
        // close clients that are connected but being dropped from the
 
233
        // configuration.
 
234
        closeClients(config);
 
235
 
 
236
        // cut over
 
237
        CLock lock(&m_mutex);
 
238
        m_config = config;
 
239
 
 
240
        // process global options
 
241
        const CConfig::CScreenOptions* options = m_config.getOptions("");
 
242
        if (options != NULL && options->size() > 0) {
 
243
                for (CConfig::CScreenOptions::const_iterator index = options->begin();
 
244
                                                                        index != options->end(); ++index) {
 
245
                        const OptionID id       = index->first;
 
246
                        const OptionValue value = index->second;
 
247
                        if (id == kOptionScreenSwitchDelay) {
 
248
                                m_switchWaitDelay = 1.0e-3 * static_cast<double>(value);
 
249
                                if (m_switchWaitDelay < 0.0) {
 
250
                                        m_switchWaitDelay = 0.0;
 
251
                                }
 
252
                                m_switchWaitEngaged = false;
 
253
                        }
 
254
                        else if (id == kOptionScreenSwitchTwoTap) {
 
255
                                m_switchTwoTapDelay = 1.0e-3 * static_cast<double>(value);
 
256
                                if (m_switchTwoTapDelay < 0.0) {
 
257
                                        m_switchTwoTapDelay = 0.0;
 
258
                                }
 
259
                                m_switchTwoTapEngaged = false;
 
260
                        }
 
261
                }
 
262
        }
 
263
 
 
264
        // tell primary screen about reconfiguration
 
265
        if (m_primaryClient != NULL) {
 
266
                m_primaryClient->reconfigure(getActivePrimarySides());
 
267
        }
 
268
 
 
269
        // tell all (connected) clients about current options
 
270
        for (CClientList::const_iterator index = m_clients.begin();
 
271
                                                                index != m_clients.end(); ++index) {
 
272
                IClient* client = index->second;
 
273
                sendOptions(client);
 
274
        }
 
275
 
 
276
        // notify of status
 
277
        runStatusJobs();
 
278
 
 
279
        return true;
 
280
}
 
281
 
 
282
void
 
283
CServer::setScreenFactory(IPrimaryScreenFactory* adopted)
 
284
{
 
285
        CLock lock(&m_mutex);
 
286
        delete m_screenFactory;
 
287
        m_screenFactory = adopted;
 
288
}
 
289
 
 
290
void
 
291
CServer::setSocketFactory(ISocketFactory* adopted)
 
292
{
 
293
        CLock lock(&m_mutex);
 
294
        delete m_socketFactory;
 
295
        m_socketFactory = adopted;
 
296
}
 
297
 
 
298
void
 
299
CServer::setStreamFilterFactory(IStreamFilterFactory* adopted)
 
300
{
 
301
        CLock lock(&m_mutex);
 
302
        delete m_streamFilterFactory;
 
303
        m_streamFilterFactory = adopted;
 
304
}
 
305
 
 
306
void
 
307
CServer::addStatusJob(IJob* job)
 
308
{
 
309
        m_statusJobs.addJob(job);
 
310
}
 
311
 
 
312
void
 
313
CServer::removeStatusJob(IJob* job)
 
314
{
 
315
        m_statusJobs.removeJob(job);
 
316
}
 
317
 
 
318
CString
 
319
CServer::getPrimaryScreenName() const
 
320
{
 
321
        return m_name;
 
322
}
 
323
 
 
324
UInt32
 
325
CServer::getNumClients() const
 
326
{
 
327
        CLock lock(&m_mutex);
 
328
        return m_clients.size();
 
329
}
 
330
 
 
331
void
 
332
CServer::getClients(std::vector<CString>& list) const
 
333
{
 
334
        CLock lock(&m_mutex);
 
335
        list.clear();
 
336
        for (CClientList::const_iterator index = m_clients.begin();
 
337
                                                        index != m_clients.end(); ++index) {
 
338
                list.push_back(index->first);
 
339
        }
 
340
}
 
341
 
 
342
CServer::EStatus
 
343
CServer::getStatus(CString* msg) const
 
344
{
 
345
        CLock lock(&m_mutex);
 
346
        if (msg != NULL) {
 
347
                *msg = m_statusMessage;
 
348
        }
 
349
        return m_status;
 
350
}
 
351
 
 
352
void
 
353
CServer::getConfig(CConfig* config) const
 
354
{
 
355
        assert(config != NULL);
 
356
 
 
357
        CLock lock(&m_mutex);
 
358
        *config = m_config;
 
359
}
 
360
 
 
361
void
 
362
CServer::runStatusJobs() const
 
363
{
 
364
        m_statusJobs.runJobs();
 
365
}
 
366
 
 
367
void
 
368
CServer::setStatus(EStatus status, const char* msg)
 
369
{
 
370
        {
 
371
                CLock lock(&m_mutex);
 
372
                m_status = status;
 
373
                if (m_status == kError) {
 
374
                        m_statusMessage = (msg == NULL) ? "Error" : msg;
 
375
                }
 
376
                else {
 
377
                        m_statusMessage = (msg == NULL) ? "" : msg;
 
378
                }
 
379
        }
 
380
        runStatusJobs();
 
381
}
 
382
 
 
383
UInt32
 
384
CServer::getActivePrimarySides() const
 
385
{
 
386
        // note -- m_mutex must be locked on entry
 
387
        UInt32 sides = 0;
 
388
        if (!m_config.getNeighbor(getPrimaryScreenName(), kLeft).empty()) {
 
389
                sides |= kLeftMask;
 
390
        }
 
391
        if (!m_config.getNeighbor(getPrimaryScreenName(), kRight).empty()) {
 
392
                sides |= kRightMask;
 
393
        }
 
394
        if (!m_config.getNeighbor(getPrimaryScreenName(), kTop).empty()) {
 
395
                sides |= kTopMask;
 
396
        }
 
397
        if (!m_config.getNeighbor(getPrimaryScreenName(), kBottom).empty()) {
 
398
                sides |= kBottomMask;
 
399
        }
 
400
        return sides;
 
401
}
 
402
 
 
403
void
 
404
CServer::onError()
 
405
{
 
406
        setStatus(kError);
 
407
 
 
408
        // stop all running threads but don't wait too long since some
 
409
        // threads may be unable to proceed until this thread returns.
 
410
        stopThreads(3.0);
 
411
 
 
412
        // done with the HTTP server
 
413
        CLock lock(&m_mutex);
 
414
        delete m_httpServer;
 
415
        m_httpServer = NULL;
 
416
 
 
417
        // note -- we do not attempt to close down the primary screen
 
418
}
 
419
 
 
420
void
 
421
CServer::onInfoChanged(const CString& name, const CClientInfo& info)
 
422
{
 
423
        CLock lock(&m_mutex);
 
424
 
 
425
        // look up client
 
426
        CClientList::iterator index = m_clients.find(name);
 
427
        if (index == m_clients.end()) {
 
428
                throw XBadClient();
 
429
        }
 
430
        IClient* client = index->second;
 
431
        assert(client != NULL);
 
432
 
 
433
        // update the remote mouse coordinates
 
434
        if (client == m_active) {
 
435
                m_x = info.m_mx;
 
436
                m_y = info.m_my;
 
437
        }
 
438
        LOG((CLOG_INFO "screen \"%s\" shape=%d,%d %dx%d zone=%d pos=%d,%d", name.c_str(), info.m_x, info.m_y, info.m_w, info.m_h, info.m_zoneSize, info.m_mx, info.m_my));
 
439
 
 
440
        // handle resolution change to primary screen
 
441
        if (client == m_primaryClient) {
 
442
                if (client == m_active) {
 
443
                        onMouseMovePrimaryNoLock(m_x, m_y);
 
444
                }
 
445
                else {
 
446
                        onMouseMoveSecondaryNoLock(0, 0);
 
447
                }
 
448
        }
 
449
}
 
450
 
 
451
bool
 
452
CServer::onGrabClipboard(const CString& name, ClipboardID id, UInt32 seqNum)
 
453
{
 
454
        CLock lock(&m_mutex);
 
455
 
 
456
        // screen must be connected
 
457
        CClientList::iterator grabber = m_clients.find(name);
 
458
        if (grabber == m_clients.end()) {
 
459
                throw XBadClient();
 
460
        }
 
461
 
 
462
        // ignore grab if sequence number is old.  always allow primary
 
463
        // screen to grab.
 
464
        CClipboardInfo& clipboard = m_clipboards[id];
 
465
        if (name != m_primaryClient->getName() &&
 
466
                seqNum < clipboard.m_clipboardSeqNum) {
 
467
                LOG((CLOG_INFO "ignored screen \"%s\" grab of clipboard %d", name.c_str(), id));
 
468
                return false;
 
469
        }
 
470
 
 
471
        // mark screen as owning clipboard
 
472
        LOG((CLOG_INFO "screen \"%s\" grabbed clipboard %d from \"%s\"", name.c_str(), id, clipboard.m_clipboardOwner.c_str()));
 
473
        clipboard.m_clipboardOwner  = name;
 
474
        clipboard.m_clipboardSeqNum = seqNum;
 
475
 
 
476
        // clear the clipboard data (since it's not known at this point)
 
477
        if (clipboard.m_clipboard.open(0)) {
 
478
                clipboard.m_clipboard.empty();
 
479
                clipboard.m_clipboard.close();
 
480
        }
 
481
        clipboard.m_clipboardData = clipboard.m_clipboard.marshall();
 
482
 
 
483
        // tell all other screens to take ownership of clipboard.  tell the
 
484
        // grabber that it's clipboard isn't dirty.
 
485
        for (CClientList::iterator index = m_clients.begin();
 
486
                                                                index != m_clients.end(); ++index) {
 
487
                IClient* client = index->second;
 
488
                if (index == grabber) {
 
489
                        client->setClipboardDirty(id, false);
 
490
                }
 
491
                else {
 
492
                        client->grabClipboard(id);
 
493
                }
 
494
        }
 
495
 
 
496
        return true;
 
497
}
 
498
 
 
499
void
 
500
CServer::onClipboardChanged(ClipboardID id, UInt32 seqNum, const CString& data)
 
501
{
 
502
        CLock lock(&m_mutex);
 
503
        onClipboardChangedNoLock(id, seqNum, data);
 
504
}
 
505
 
 
506
void
 
507
CServer::onClipboardChangedNoLock(ClipboardID id,
 
508
                                UInt32 seqNum, const CString& data)
 
509
{
 
510
        CClipboardInfo& clipboard = m_clipboards[id];
 
511
 
 
512
        // ignore update if sequence number is old
 
513
        if (seqNum < clipboard.m_clipboardSeqNum) {
 
514
                LOG((CLOG_INFO "ignored screen \"%s\" update of clipboard %d (missequenced)", clipboard.m_clipboardOwner.c_str(), id));
 
515
                return;
 
516
        }
 
517
 
 
518
        // ignore if data hasn't changed
 
519
        if (data == clipboard.m_clipboardData) {
 
520
                LOG((CLOG_DEBUG "ignored screen \"%s\" update of clipboard %d (unchanged)", clipboard.m_clipboardOwner.c_str(), id));
 
521
                return;
 
522
        }
 
523
 
 
524
        // unmarshall into our clipboard buffer
 
525
        LOG((CLOG_INFO "screen \"%s\" updated clipboard %d", clipboard.m_clipboardOwner.c_str(), id));
 
526
        clipboard.m_clipboardData = data;
 
527
        clipboard.m_clipboard.unmarshall(clipboard.m_clipboardData, 0);
 
528
 
 
529
        // tell all clients except the sender that the clipboard is dirty
 
530
        CClientList::const_iterator sender =
 
531
                                                                m_clients.find(clipboard.m_clipboardOwner);
 
532
        for (CClientList::const_iterator index = m_clients.begin();
 
533
                                                                index != m_clients.end(); ++index) {
 
534
                IClient* client = index->second;
 
535
                client->setClipboardDirty(id, index != sender);
 
536
        }
 
537
 
 
538
        // send the new clipboard to the active screen
 
539
        m_active->setClipboard(id, m_clipboards[id].m_clipboardData);
 
540
}
 
541
 
 
542
void
 
543
CServer::onScreensaver(bool activated)
 
544
{
 
545
        LOG((CLOG_DEBUG "onScreenSaver %s", activated ? "activated" : "deactivated"));
 
546
        CLock lock(&m_mutex);
 
547
 
 
548
        if (activated) {
 
549
                // save current screen and position
 
550
                m_activeSaver = m_active;
 
551
                m_xSaver      = m_x;
 
552
                m_ySaver      = m_y;
 
553
 
 
554
                // jump to primary screen
 
555
                if (m_active != m_primaryClient) {
 
556
                        switchScreen(m_primaryClient, 0, 0, true);
 
557
                }
 
558
        }
 
559
        else {
 
560
                // jump back to previous screen and position.  we must check
 
561
                // that the position is still valid since the screen may have
 
562
                // changed resolutions while the screen saver was running.
 
563
                if (m_activeSaver != NULL && m_activeSaver != m_primaryClient) {
 
564
                        // check position
 
565
                        IClient* screen = m_activeSaver;
 
566
                        SInt32 x, y, w, h;
 
567
                        screen->getShape(x, y, w, h);
 
568
                        SInt32 zoneSize = screen->getJumpZoneSize();
 
569
                        if (m_xSaver < x + zoneSize) {
 
570
                                m_xSaver = x + zoneSize;
 
571
                        }
 
572
                        else if (m_xSaver >= x + w - zoneSize) {
 
573
                                m_xSaver = x + w - zoneSize - 1;
 
574
                        }
 
575
                        if (m_ySaver < y + zoneSize) {
 
576
                                m_ySaver = y + zoneSize;
 
577
                        }
 
578
                        else if (m_ySaver >= y + h - zoneSize) {
 
579
                                m_ySaver = y + h - zoneSize - 1;
 
580
                        }
 
581
 
 
582
                        // jump
 
583
                        switchScreen(screen, m_xSaver, m_ySaver, false);
 
584
                }
 
585
 
 
586
                // reset state
 
587
                m_activeSaver = NULL;
 
588
        }
 
589
 
 
590
        // send message to all clients
 
591
        for (CClientList::const_iterator index = m_clients.begin();
 
592
                                                                index != m_clients.end(); ++index) {
 
593
                IClient* client = index->second;
 
594
                client->screensaver(activated);
 
595
        }
 
596
}
 
597
 
 
598
void
 
599
CServer::onOneShotTimerExpired(UInt32 id)
 
600
{
 
601
        CLock lock(&m_mutex);
 
602
 
 
603
        // ignore if it's an old timer or if switch wait isn't engaged anymore
 
604
        if (!m_switchWaitEngaged || id != m_switchWaitTimer) {
 
605
                return;
 
606
        }
 
607
 
 
608
        // ignore if mouse is locked to screen
 
609
        if (isLockedToScreenNoLock()) {
 
610
                LOG((CLOG_DEBUG1 "locked to screen"));
 
611
                clearSwitchState();
 
612
                return;
 
613
        }
 
614
 
 
615
        // switch screen
 
616
        switchScreen(m_switchScreen, m_switchWaitX, m_switchWaitY, false);
 
617
}
 
618
 
 
619
void
 
620
CServer::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button)
 
621
{
 
622
        LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x", id, mask, button));
 
623
        CLock lock(&m_mutex);
 
624
        assert(m_active != NULL);
 
625
 
 
626
        // handle command keys
 
627
        if (onCommandKey(id, mask, true)) {
 
628
                return;
 
629
        }
 
630
 
 
631
        // relay
 
632
        m_active->keyDown(id, mask, button);
 
633
}
 
634
 
 
635
void
 
636
CServer::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button)
 
637
{
 
638
        LOG((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button));
 
639
        CLock lock(&m_mutex);
 
640
        assert(m_active != NULL);
 
641
 
 
642
        // handle command keys
 
643
        if (onCommandKey(id, mask, false)) {
 
644
                return;
 
645
        }
 
646
 
 
647
        // relay
 
648
        m_active->keyUp(id, mask, button);
 
649
}
 
650
 
 
651
void
 
652
CServer::onKeyRepeat(KeyID id, KeyModifierMask mask,
 
653
                                SInt32 count, KeyButton button)
 
654
{
 
655
        LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button));
 
656
        CLock lock(&m_mutex);
 
657
        assert(m_active != NULL);
 
658
 
 
659
        // handle command keys
 
660
        if (onCommandKey(id, mask, false)) {
 
661
                onCommandKey(id, mask, true);
 
662
                return;
 
663
        }
 
664
 
 
665
        // relay
 
666
        m_active->keyRepeat(id, mask, count, button);
 
667
}
 
668
 
 
669
void
 
670
CServer::onMouseDown(ButtonID id)
 
671
{
 
672
        LOG((CLOG_DEBUG1 "onMouseDown id=%d", id));
 
673
        CLock lock(&m_mutex);
 
674
        assert(m_active != NULL);
 
675
 
 
676
        // relay
 
677
        m_active->mouseDown(id);
 
678
}
 
679
 
 
680
void
 
681
CServer::onMouseUp(ButtonID id)
 
682
{
 
683
        LOG((CLOG_DEBUG1 "onMouseUp id=%d", id));
 
684
        CLock lock(&m_mutex);
 
685
        assert(m_active != NULL);
 
686
 
 
687
        // relay
 
688
        m_active->mouseUp(id);
 
689
}
 
690
 
 
691
bool
 
692
CServer::onMouseMovePrimary(SInt32 x, SInt32 y)
 
693
{
 
694
        LOG((CLOG_DEBUG2 "onMouseMovePrimary %d,%d", x, y));
 
695
        CLock lock(&m_mutex);
 
696
        return onMouseMovePrimaryNoLock(x, y);
 
697
}
 
698
 
 
699
bool
 
700
CServer::onMouseMovePrimaryNoLock(SInt32 x, SInt32 y)
 
701
{
 
702
        // mouse move on primary (server's) screen
 
703
        assert(m_primaryClient != NULL);
 
704
        assert(m_active == m_primaryClient);
 
705
 
 
706
        // get screen shape
 
707
        SInt32 ax, ay, aw, ah;
 
708
        m_active->getShape(ax, ay, aw, ah);
 
709
        SInt32 zoneSize = m_active->getJumpZoneSize();
 
710
 
 
711
        // see if we should change screens
 
712
        EDirection dir;
 
713
        if (x < ax + zoneSize) {
 
714
                x  -= zoneSize;
 
715
                dir = kLeft;
 
716
        }
 
717
        else if (x >= ax + aw - zoneSize) {
 
718
                x  += zoneSize;
 
719
                dir = kRight;
 
720
        }
 
721
        else if (y < ay + zoneSize) {
 
722
                y  -= zoneSize;
 
723
                dir = kTop;
 
724
        }
 
725
        else if (y >= ay + ah - zoneSize) {
 
726
                y  += zoneSize;
 
727
                dir = kBottom;
 
728
        }
 
729
        else {
 
730
                // still on local screen.  check if we're inside the tap region.
 
731
                SInt32 tapZone = (zoneSize < m_switchTwoTapZone) ?
 
732
                                                        m_switchTwoTapZone : zoneSize;
 
733
                bool inTapZone = (x <  ax + tapZone ||
 
734
                                                  x >= ax + aw - tapZone ||
 
735
                                                  y <  ay + tapZone ||
 
736
                                                  y >= ay + ah - tapZone);
 
737
 
 
738
                // failed to switch
 
739
                onNoSwitch(inTapZone);
 
740
                return false;
 
741
        }
 
742
 
 
743
        // get jump destination
 
744
        IClient* newScreen = getNeighbor(m_active, dir, x, y);
 
745
 
 
746
        // should we switch or not?
 
747
        if (isSwitchOkay(newScreen, dir, x, y)) {
 
748
                // switch screen
 
749
                switchScreen(newScreen, x, y, false);
 
750
                return true;
 
751
        }
 
752
        else {
 
753
                return false;
 
754
        }
 
755
}
 
756
 
 
757
void
 
758
CServer::onMouseMoveSecondary(SInt32 dx, SInt32 dy)
 
759
{
 
760
        LOG((CLOG_DEBUG2 "onMouseMoveSecondary %+d,%+d", dx, dy));
 
761
        CLock lock(&m_mutex);
 
762
        onMouseMoveSecondaryNoLock(dx, dy);
 
763
}
 
764
 
 
765
void
 
766
CServer::onMouseMoveSecondaryNoLock(SInt32 dx, SInt32 dy)
 
767
{
 
768
        // mouse move on secondary (client's) screen
 
769
        assert(m_active != NULL);
 
770
        if (m_active == m_primaryClient) {
 
771
                // we're actually on the primary screen.  this can happen
 
772
                // when the primary screen begins processing a mouse move
 
773
                // for a secondary screen, then the active (secondary)
 
774
                // screen disconnects causing us to jump to the primary
 
775
                // screen, and finally the primary screen finishes
 
776
                // processing the mouse move, still thinking it's for
 
777
                // a secondary screen.  we just ignore the motion.
 
778
                return;
 
779
        }
 
780
 
 
781
        // save old position
 
782
        const SInt32 xOld = m_x;
 
783
        const SInt32 yOld = m_y;
 
784
 
 
785
        // accumulate motion
 
786
        m_x += dx;
 
787
        m_y += dy;
 
788
 
 
789
        // get screen shape
 
790
        SInt32 ax, ay, aw, ah;
 
791
        m_active->getShape(ax, ay, aw, ah);
 
792
 
 
793
        // find direction of neighbor and get the neighbor
 
794
        bool jump = true;
 
795
        IClient* newScreen;
 
796
        do {
 
797
                EDirection dir;
 
798
                if (m_x < ax) {
 
799
                        dir = kLeft;
 
800
                }
 
801
                else if (m_x > ax + aw - 1) {
 
802
                        dir = kRight;
 
803
                }
 
804
                else if (m_y < ay) {
 
805
                        dir = kTop;
 
806
                }
 
807
                else if (m_y > ay + ah - 1) {
 
808
                        dir = kBottom;
 
809
                }
 
810
                else {
 
811
                        // we haven't left the screen
 
812
                        newScreen = m_active;
 
813
                        jump      = false;
 
814
 
 
815
                        // if waiting and mouse is not on the border we're waiting
 
816
                        // on then stop waiting.  also if it's not on the border
 
817
                        // then arm the double tap.
 
818
                        if (m_switchScreen != NULL) {
 
819
                                bool clearWait;
 
820
                                SInt32 zoneSize = m_primaryClient->getJumpZoneSize();
 
821
                                switch (m_switchDir) {
 
822
                                case kLeft:
 
823
                                        clearWait = (m_x >= ax + zoneSize);
 
824
                                        break;
 
825
 
 
826
                                case kRight:
 
827
                                        clearWait = (m_x <= ax + aw - 1 - zoneSize);
 
828
                                        break;
 
829
 
 
830
                                case kTop:
 
831
                                        clearWait = (m_y >= ay + zoneSize);
 
832
                                        break;
 
833
 
 
834
                                case kBottom:
 
835
                                        clearWait = (m_y <= ay + ah - 1 + zoneSize);
 
836
                                        break;
 
837
 
 
838
                                default:
 
839
                                        clearWait = false;
 
840
                                        break;
 
841
                                }
 
842
                                if (clearWait) {
 
843
                                        // still on local screen.  check if we're inside the
 
844
                                        // tap region.
 
845
                                        SInt32 tapZone = (zoneSize < m_switchTwoTapZone) ?
 
846
                                                                                m_switchTwoTapZone : zoneSize;
 
847
                                        bool inTapZone = (m_x <  ax + tapZone ||
 
848
                                                                          m_x >= ax + aw - tapZone ||
 
849
                                                                          m_y <  ay + tapZone ||
 
850
                                                                          m_y >= ay + ah - tapZone);
 
851
 
 
852
                                        // failed to switch
 
853
                                        onNoSwitch(inTapZone);
 
854
                                }
 
855
                        }
 
856
 
 
857
                        // skip rest of block
 
858
                        break;
 
859
                }
 
860
 
 
861
                // try to switch screen.  get the neighbor.
 
862
                newScreen = getNeighbor(m_active, dir, m_x, m_y);
 
863
 
 
864
                // see if we should switch
 
865
                if (!isSwitchOkay(newScreen, dir, m_x, m_y)) {
 
866
                        newScreen = m_active;
 
867
                        jump      = false;
 
868
                }
 
869
        } while (false);
 
870
 
 
871
        if (jump) {
 
872
                // switch screens
 
873
                switchScreen(newScreen, m_x, m_y, false);
 
874
        }
 
875
        else {
 
876
                // same screen.  clamp mouse to edge.
 
877
                m_x = xOld + dx;
 
878
                m_y = yOld + dy;
 
879
                if (m_x < ax) {
 
880
                        m_x = ax;
 
881
                        LOG((CLOG_DEBUG2 "clamp to left of \"%s\"", m_active->getName().c_str()));
 
882
                }
 
883
                else if (m_x > ax + aw - 1) {
 
884
                        m_x = ax + aw - 1;
 
885
                        LOG((CLOG_DEBUG2 "clamp to right of \"%s\"", m_active->getName().c_str()));
 
886
                }
 
887
                if (m_y < ay) {
 
888
                        m_y = ay;
 
889
                        LOG((CLOG_DEBUG2 "clamp to top of \"%s\"", m_active->getName().c_str()));
 
890
                }
 
891
                else if (m_y > ay + ah - 1) {
 
892
                        m_y = ay + ah - 1;
 
893
                        LOG((CLOG_DEBUG2 "clamp to bottom of \"%s\"", m_active->getName().c_str()));
 
894
                }
 
895
 
 
896
                // warp cursor if it moved.
 
897
                if (m_x != xOld || m_y != yOld) {
 
898
                        LOG((CLOG_DEBUG2 "move on %s to %d,%d", m_active->getName().c_str(), m_x, m_y));
 
899
                        m_active->mouseMove(m_x, m_y);
 
900
                }
 
901
        }
 
902
}
 
903
 
 
904
void
 
905
CServer::onMouseWheel(SInt32 delta)
 
906
{
 
907
        LOG((CLOG_DEBUG1 "onMouseWheel %+d", delta));
 
908
        CLock lock(&m_mutex);
 
909
        assert(m_active != NULL);
 
910
 
 
911
        // relay
 
912
        m_active->mouseWheel(delta);
 
913
}
 
914
 
 
915
bool
 
916
CServer::onCommandKey(KeyID /*id*/, KeyModifierMask /*mask*/, bool /*down*/)
 
917
{
 
918
        return false;
 
919
}
 
920
 
 
921
bool
 
922
CServer::isLockedToScreenNoLock() const
 
923
{
 
924
        // locked if scroll-lock is toggled on
 
925
        if ((m_primaryClient->getToggleMask() & KeyModifierScrollLock) != 0) {
 
926
                LOG((CLOG_DEBUG "locked by ScrollLock"));
 
927
                return true;
 
928
        }
 
929
 
 
930
        // locked if primary says we're locked
 
931
        if (m_primaryClient->isLockedToScreen()) {
 
932
                return true;
 
933
        }
 
934
 
 
935
        // not locked
 
936
        return false;
 
937
}
 
938
 
 
939
void
 
940
CServer::switchScreen(IClient* dst, SInt32 x, SInt32 y, bool forScreensaver)
 
941
{
 
942
        // note -- must be locked on entry
 
943
 
 
944
        assert(dst != NULL);
 
945
#ifndef NDEBUG
 
946
        {
 
947
                SInt32 dx, dy, dw, dh;
 
948
                dst->getShape(dx, dy, dw, dh);
 
949
                assert(x >= dx && y >= dy && x < dx + dw && y < dy + dh);
 
950
        }
 
951
#endif
 
952
        assert(m_active != NULL);
 
953
 
 
954
        LOG((CLOG_INFO "switch from \"%s\" to \"%s\" at %d,%d", m_active->getName().c_str(), dst->getName().c_str(), x, y));
 
955
 
 
956
        // stop waiting to switch
 
957
        clearSwitchState();
 
958
 
 
959
        // record new position
 
960
        m_x = x;
 
961
        m_y = y;
 
962
 
 
963
        // wrapping means leaving the active screen and entering it again.
 
964
        // since that's a waste of time we skip that and just warp the
 
965
        // mouse.
 
966
        if (m_active != dst) {
 
967
                // leave active screen
 
968
                if (!m_active->leave()) {
 
969
                        // cannot leave screen
 
970
                        LOG((CLOG_WARN "can't leave screen"));
 
971
                        return;
 
972
                }
 
973
 
 
974
                // update the primary client's clipboards if we're leaving the
 
975
                // primary screen.
 
976
                if (m_active == m_primaryClient) {
 
977
                        for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
 
978
                                CClipboardInfo& clipboard = m_clipboards[id];
 
979
                                if (clipboard.m_clipboardOwner == m_primaryClient->getName()) {
 
980
                                        CString clipboardData;
 
981
                                        m_primaryClient->getClipboard(id, clipboardData);
 
982
                                        onClipboardChangedNoLock(id,
 
983
                                                                clipboard.m_clipboardSeqNum, clipboardData);
 
984
                                }
 
985
                        }
 
986
                }
 
987
 
 
988
                // cut over
 
989
                m_active = dst;
 
990
 
 
991
                // increment enter sequence number
 
992
                ++m_seqNum;
 
993
 
 
994
                // enter new screen
 
995
                m_active->enter(x, y, m_seqNum,
 
996
                                                                m_primaryClient->getToggleMask(),
 
997
                                                                forScreensaver);
 
998
 
 
999
                // send the clipboard data to new active screen
 
1000
                for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
 
1001
                        m_active->setClipboard(id, m_clipboards[id].m_clipboardData);
 
1002
                }
 
1003
        }
 
1004
        else {
 
1005
                m_active->mouseMove(x, y);
 
1006
        }
 
1007
}
 
1008
 
 
1009
IClient*
 
1010
CServer::getNeighbor(IClient* src, EDirection dir) const
 
1011
{
 
1012
        // note -- must be locked on entry
 
1013
 
 
1014
        assert(src != NULL);
 
1015
 
 
1016
        // get source screen name
 
1017
        CString srcName = src->getName();
 
1018
        assert(!srcName.empty());
 
1019
        LOG((CLOG_DEBUG2 "find neighbor on %s of \"%s\"", CConfig::dirName(dir), srcName.c_str()));
 
1020
 
 
1021
        // get first neighbor.  if it's the source then the source jumps
 
1022
        // to itself and we return the source.
 
1023
        CString dstName(m_config.getNeighbor(srcName, dir));
 
1024
        if (dstName == srcName) {
 
1025
                LOG((CLOG_DEBUG2 "\"%s\" is on %s of \"%s\"", dstName.c_str(), CConfig::dirName(dir), srcName.c_str()));
 
1026
                return src;
 
1027
        }
 
1028
 
 
1029
        // keep checking
 
1030
        for (;;) {
 
1031
                // if nothing in that direction then return NULL. if the
 
1032
                // destination is the source then we can make no more
 
1033
                // progress in this direction.  since we haven't found a
 
1034
                // connected neighbor we return NULL.
 
1035
                if (dstName.empty() || dstName == srcName) {
 
1036
                        LOG((CLOG_DEBUG2 "no neighbor on %s of \"%s\"", CConfig::dirName(dir), srcName.c_str()));
 
1037
                        return NULL;
 
1038
                }
 
1039
 
 
1040
                // look up neighbor cell.  if the screen is connected and
 
1041
                // ready then we can stop.
 
1042
                CClientList::const_iterator index = m_clients.find(dstName);
 
1043
                if (index != m_clients.end()) {
 
1044
                        LOG((CLOG_DEBUG2 "\"%s\" is on %s of \"%s\"", dstName.c_str(), CConfig::dirName(dir), srcName.c_str()));
 
1045
                        return index->second;
 
1046
                }
 
1047
 
 
1048
                // skip over unconnected screen
 
1049
                LOG((CLOG_DEBUG2 "ignored \"%s\" on %s of \"%s\"", dstName.c_str(), CConfig::dirName(dir), srcName.c_str()));
 
1050
                srcName = dstName;
 
1051
 
 
1052
                // look up name of neighbor of skipped screen
 
1053
                dstName = m_config.getNeighbor(srcName, dir);
 
1054
        }
 
1055
}
 
1056
 
 
1057
IClient*
 
1058
CServer::getNeighbor(IClient* src,
 
1059
                                EDirection srcSide, SInt32& x, SInt32& y) const
 
1060
{
 
1061
        // note -- must be locked on entry
 
1062
 
 
1063
        assert(src != NULL);
 
1064
 
 
1065
        // get the first neighbor
 
1066
        IClient* dst = getNeighbor(src, srcSide);
 
1067
        if (dst == NULL) {
 
1068
                return NULL;
 
1069
        }
 
1070
 
 
1071
        // get the source screen's size (needed for kRight and kBottom)
 
1072
        SInt32 sx, sy, sw, sh;
 
1073
        SInt32 dx, dy, dw, dh;
 
1074
        IClient* lastGoodScreen = src;
 
1075
        lastGoodScreen->getShape(sx, sy, sw, sh);
 
1076
        lastGoodScreen->getShape(dx, dy, dw, dh);
 
1077
 
 
1078
        // find destination screen, adjusting x or y (but not both).  the
 
1079
        // searches are done in a sort of canonical screen space where
 
1080
        // the upper-left corner is 0,0 for each screen.  we adjust from
 
1081
        // actual to canonical position on entry to and from canonical to
 
1082
        // actual on exit from the search.
 
1083
        switch (srcSide) {
 
1084
        case kLeft:
 
1085
                x -= dx;
 
1086
                while (dst != NULL) {
 
1087
                        lastGoodScreen = dst;
 
1088
                        lastGoodScreen->getShape(dx, dy, dw, dh);
 
1089
                        x += dw;
 
1090
                        if (x >= 0) {
 
1091
                                break;
 
1092
                        }
 
1093
                        LOG((CLOG_DEBUG2 "skipping over screen %s", dst->getName().c_str()));
 
1094
                        dst = getNeighbor(lastGoodScreen, srcSide);
 
1095
                }
 
1096
                assert(lastGoodScreen != NULL);
 
1097
                x += dx;
 
1098
                break;
 
1099
 
 
1100
        case kRight:
 
1101
                x -= dx;
 
1102
                while (dst != NULL) {
 
1103
                        x -= dw;
 
1104
                        lastGoodScreen = dst;
 
1105
                        lastGoodScreen->getShape(dx, dy, dw, dh);
 
1106
                        if (x < dw) {
 
1107
                                break;
 
1108
                        }
 
1109
                        LOG((CLOG_DEBUG2 "skipping over screen %s", dst->getName().c_str()));
 
1110
                        dst = getNeighbor(lastGoodScreen, srcSide);
 
1111
                }
 
1112
                assert(lastGoodScreen != NULL);
 
1113
                x += dx;
 
1114
                break;
 
1115
 
 
1116
        case kTop:
 
1117
                y -= dy;
 
1118
                while (dst != NULL) {
 
1119
                        lastGoodScreen = dst;
 
1120
                        lastGoodScreen->getShape(dx, dy, dw, dh);
 
1121
                        y += dh;
 
1122
                        if (y >= 0) {
 
1123
                                break;
 
1124
                        }
 
1125
                        LOG((CLOG_DEBUG2 "skipping over screen %s", dst->getName().c_str()));
 
1126
                        dst = getNeighbor(lastGoodScreen, srcSide);
 
1127
                }
 
1128
                assert(lastGoodScreen != NULL);
 
1129
                y += dy;
 
1130
                break;
 
1131
 
 
1132
        case kBottom:
 
1133
                y -= dy;
 
1134
                while (dst != NULL) {
 
1135
                        y -= dh;
 
1136
                        lastGoodScreen = dst;
 
1137
                        lastGoodScreen->getShape(dx, dy, dw, dh);
 
1138
                        if (y < sh) {
 
1139
                                break;
 
1140
                        }
 
1141
                        LOG((CLOG_DEBUG2 "skipping over screen %s", dst->getName().c_str()));
 
1142
                        dst = getNeighbor(lastGoodScreen, srcSide);
 
1143
                }
 
1144
                assert(lastGoodScreen != NULL);
 
1145
                y += dy;
 
1146
                break;
 
1147
        }
 
1148
 
 
1149
        // save destination screen
 
1150
        assert(lastGoodScreen != NULL);
 
1151
        dst = lastGoodScreen;
 
1152
 
 
1153
        // if entering primary screen then be sure to move in far enough
 
1154
        // to avoid the jump zone.  if entering a side that doesn't have
 
1155
        // a neighbor (i.e. an asymmetrical side) then we don't need to
 
1156
        // move inwards because that side can't provoke a jump.
 
1157
        if (dst == m_primaryClient) {
 
1158
                const CString dstName(dst->getName());
 
1159
                switch (srcSide) {
 
1160
                case kLeft:
 
1161
                        if (!m_config.getNeighbor(dstName, kRight).empty() &&
 
1162
                                x > dx + dw - 1 - dst->getJumpZoneSize())
 
1163
                                x = dx + dw - 1 - dst->getJumpZoneSize();
 
1164
                        break;
 
1165
 
 
1166
                case kRight:
 
1167
                        if (!m_config.getNeighbor(dstName, kLeft).empty() &&
 
1168
                                x < dx + dst->getJumpZoneSize())
 
1169
                                x = dx + dst->getJumpZoneSize();
 
1170
                        break;
 
1171
 
 
1172
                case kTop:
 
1173
                        if (!m_config.getNeighbor(dstName, kBottom).empty() &&
 
1174
                                y > dy + dh - 1 - dst->getJumpZoneSize())
 
1175
                                y = dy + dh - 1 - dst->getJumpZoneSize();
 
1176
                        break;
 
1177
 
 
1178
                case kBottom:
 
1179
                        if (!m_config.getNeighbor(dstName, kTop).empty() &&
 
1180
                                y < dy + dst->getJumpZoneSize())
 
1181
                                y = dy + dst->getJumpZoneSize();
 
1182
                        break;
 
1183
                }
 
1184
        }
 
1185
 
 
1186
        // adjust the coordinate orthogonal to srcSide to account for
 
1187
        // resolution differences.  for example, if y is 200 pixels from
 
1188
        // the top on a screen 1000 pixels high (20% from the top) when
 
1189
        // we cross the left edge onto a screen 600 pixels high then y
 
1190
        // should be set 120 pixels from the top (again 20% from the
 
1191
        // top).
 
1192
        switch (srcSide) {
 
1193
        case kLeft:
 
1194
        case kRight:
 
1195
                y -= sy;
 
1196
                if (y < 0) {
 
1197
                        y = 0;
 
1198
                }
 
1199
                else if (y >= sh) {
 
1200
                        y = dh - 1;
 
1201
                }
 
1202
                else {
 
1203
                        y = static_cast<SInt32>(0.5 + y *
 
1204
                                                                static_cast<double>(dh - 1) / (sh - 1));
 
1205
                }
 
1206
                y += dy;
 
1207
                break;
 
1208
 
 
1209
        case kTop:
 
1210
        case kBottom:
 
1211
                x -= sx;
 
1212
                if (x < 0) {
 
1213
                        x = 0;
 
1214
                }
 
1215
                else if (x >= sw) {
 
1216
                        x = dw - 1;
 
1217
                }
 
1218
                else {
 
1219
                        x = static_cast<SInt32>(0.5 + x *
 
1220
                                                                static_cast<double>(dw - 1) / (sw - 1));
 
1221
                }
 
1222
                x += dx;
 
1223
                break;
 
1224
        }
 
1225
 
 
1226
        return dst;
 
1227
}
 
1228
 
 
1229
bool
 
1230
CServer::isSwitchOkay(IClient* newScreen, EDirection dir, SInt32 x, SInt32 y)
 
1231
{
 
1232
        LOG((CLOG_DEBUG1 "try to leave \"%s\" on %s", m_active->getName().c_str(), CConfig::dirName(dir)));
 
1233
 
 
1234
        // is there a neighbor?
 
1235
        if (newScreen == NULL) {
 
1236
                // there's no neighbor.  we don't want to switch and we don't
 
1237
                // want to try to switch later.
 
1238
                LOG((CLOG_DEBUG1 "no neighbor %s", CConfig::dirName(dir)));
 
1239
                clearSwitchState();
 
1240
                return false;
 
1241
        }
 
1242
 
 
1243
        // should we switch or not?
 
1244
        bool preventSwitch = false;
 
1245
        bool allowSwitch   = false;
 
1246
 
 
1247
        // note if the switch direction has changed.  save the new
 
1248
        // direction and screen if so.
 
1249
        bool isNewDirection  = (dir != m_switchDir);
 
1250
        if (isNewDirection || m_switchScreen == NULL) {
 
1251
                m_switchDir    = dir;
 
1252
                m_switchScreen = newScreen;
 
1253
        }
 
1254
 
 
1255
        // is this a double tap and do we care?
 
1256
        if (!allowSwitch && m_switchTwoTapDelay > 0.0) {
 
1257
                if (isNewDirection || !m_switchTwoTapEngaged) {
 
1258
                        // tapping a different or new edge.  prepare for second tap.
 
1259
                        preventSwitch         = true;
 
1260
                        m_switchTwoTapEngaged = true;
 
1261
                        m_switchTwoTapArmed   = false;
 
1262
                        m_switchTwoTapTimer.reset();
 
1263
                        LOG((CLOG_DEBUG1 "waiting for second tap"));
 
1264
                }
 
1265
                else {
 
1266
                        // second tap if we were armed.  if soon enough then switch.
 
1267
                        if (m_switchTwoTapArmed &&
 
1268
                                m_switchTwoTapTimer.getTime() <= m_switchTwoTapDelay) {
 
1269
                                allowSwitch = true;
 
1270
                        }
 
1271
                        else {
 
1272
                                // not fast enough.  reset the clock.
 
1273
                                preventSwitch         = true;
 
1274
                                m_switchTwoTapEngaged = true;
 
1275
                                m_switchTwoTapArmed   = false;
 
1276
                                m_switchTwoTapTimer.reset();
 
1277
                                LOG((CLOG_DEBUG1 "waiting for second tap"));
 
1278
                        }
 
1279
                }
 
1280
        }
 
1281
 
 
1282
        // if waiting before a switch then prepare to switch later
 
1283
        if (!allowSwitch && m_switchWaitDelay > 0.0) {
 
1284
                if (isNewDirection || !m_switchWaitEngaged) {
 
1285
                        m_switchWaitEngaged = true;
 
1286
                        m_switchWaitX       = x;
 
1287
                        m_switchWaitY       = y;
 
1288
                        m_switchWaitTimer   = m_primaryClient->addOneShotTimer(
 
1289
                                                                                                        m_switchWaitDelay);
 
1290
                        LOG((CLOG_DEBUG1 "waiting to switch"));
 
1291
                }
 
1292
                preventSwitch = true;
 
1293
        }
 
1294
 
 
1295
        // ignore if mouse is locked to screen
 
1296
        if (!preventSwitch && isLockedToScreenNoLock()) {
 
1297
                LOG((CLOG_DEBUG1 "locked to screen"));
 
1298
                preventSwitch = true;
 
1299
 
 
1300
                // don't try to switch later.  it's possible that we might
 
1301
                // not be locked to the screen when the wait delay expires
 
1302
                // and could switch then but we'll base the decision on
 
1303
                // when the user first attempts the switch.  this also
 
1304
                // ensures that all switch tests are using the same
 
1305
                clearSwitchState();
 
1306
        }
 
1307
 
 
1308
        return !preventSwitch;
 
1309
}
 
1310
 
 
1311
void
 
1312
CServer::onNoSwitch(bool inTapZone)
 
1313
{
 
1314
        if (m_switchTwoTapEngaged) {
 
1315
                if (m_switchTwoTapTimer.getTime() > m_switchTwoTapDelay) {
 
1316
                        // second tap took too long.  disengage.
 
1317
                        m_switchTwoTapEngaged = false;
 
1318
                        m_switchTwoTapArmed   = false;
 
1319
                }
 
1320
                else if (!inTapZone) {
 
1321
                        // we've moved away from the edge and there's still
 
1322
                        // time to get back for a double tap.
 
1323
                        m_switchTwoTapArmed = true;
 
1324
                }
 
1325
        }
 
1326
 
 
1327
        // once the mouse moves away from the edge we no longer want to
 
1328
        // switch after a delay.
 
1329
        m_switchWaitEngaged = false;
 
1330
}
 
1331
 
 
1332
void
 
1333
CServer::clearSwitchState()
 
1334
{
 
1335
        if (m_switchScreen != NULL) {
 
1336
                m_switchDir           = kNoDirection;
 
1337
                m_switchScreen        = NULL;
 
1338
                m_switchWaitEngaged   = false;
 
1339
                m_switchTwoTapEngaged = false;
 
1340
        }
 
1341
}
 
1342
 
 
1343
void
 
1344
CServer::closeClients(const CConfig& config)
 
1345
{
 
1346
        CThreadList threads;
 
1347
        {
 
1348
                CLock lock(&m_mutex);
 
1349
 
 
1350
                // get the set of clients that are connected but are being
 
1351
                // dropped from the configuration (or who's canonical name
 
1352
                // is changing) and tell them to disconnect.  note that
 
1353
                // since m_clientThreads doesn't include a thread for the
 
1354
                // primary client we will not close it.
 
1355
                for (CClientThreadList::iterator
 
1356
                                                                index  = m_clientThreads.begin();
 
1357
                                                                index != m_clientThreads.end(); ) {
 
1358
                        const CString& name = index->first;
 
1359
                        if (!config.isCanonicalName(name)) {
 
1360
                                // lookup IClient with name
 
1361
                                CClientList::const_iterator index2 = m_clients.find(name);
 
1362
                                assert(index2 != m_clients.end());
 
1363
 
 
1364
                                // save the thread and remove it from m_clientThreads
 
1365
                                threads.push_back(index->second);
 
1366
                                m_clientThreads.erase(index++);
 
1367
 
 
1368
                                // close that client
 
1369
                                assert(index2->second != m_primaryClient);
 
1370
                                index2->second->close();
 
1371
 
 
1372
                                // don't switch to it if we planned to
 
1373
                                if (index2->second == m_switchScreen) {
 
1374
                                        clearSwitchState();
 
1375
                                }
 
1376
                        }
 
1377
                        else {
 
1378
                                ++index;
 
1379
                        }
 
1380
                }
 
1381
        }
 
1382
 
 
1383
        // wait a moment to allow each client to close its connection
 
1384
        // before we close it (to avoid having our socket enter TIME_WAIT).
 
1385
        if (threads.size() > 0) {
 
1386
                ARCH->sleep(1.0);
 
1387
        }
 
1388
 
 
1389
        // cancel the old client threads
 
1390
        for (CThreadList::iterator index = threads.begin();
 
1391
                                                                index != threads.end(); ++index) {
 
1392
                index->cancel();
 
1393
        }
 
1394
 
 
1395
        // wait for old client threads to terminate.  we must not hold
 
1396
        // the lock while we do this so those threads can finish any
 
1397
        // calls to this object.
 
1398
        for (CThreadList::iterator index = threads.begin();
 
1399
                                                                index != threads.end(); ++index) {
 
1400
                index->wait();
 
1401
        }
 
1402
 
 
1403
        // clean up thread list
 
1404
        reapThreads();
 
1405
}
 
1406
 
 
1407
CThread
 
1408
CServer::startThread(IJob* job)
 
1409
{
 
1410
        CLock lock(&m_mutex);
 
1411
 
 
1412
        // reap completed threads
 
1413
        doReapThreads(m_threads);
 
1414
 
 
1415
        // add new thread to list
 
1416
        CThread thread(job);
 
1417
        m_threads.push_back(thread);
 
1418
        LOG((CLOG_DEBUG1 "started thread 0x%08x", thread.getID()));
 
1419
        return thread;
 
1420
}
 
1421
 
 
1422
void
 
1423
CServer::stopThreads(double timeout)
 
1424
{
 
1425
        LOG((CLOG_DEBUG1 "stopping threads"));
 
1426
 
 
1427
        // cancel the accept client thread to prevent more clients from
 
1428
        // connecting while we're shutting down.
 
1429
        CThread* acceptClientThread;
 
1430
        {
 
1431
                CLock lock(&m_mutex);
 
1432
                acceptClientThread   = m_acceptClientThread;
 
1433
                m_acceptClientThread = NULL;
 
1434
        }
 
1435
        if (acceptClientThread != NULL) {
 
1436
                acceptClientThread->cancel();
 
1437
                acceptClientThread->wait(timeout);
 
1438
                delete acceptClientThread;
 
1439
        }
 
1440
 
 
1441
        // close all clients (except the primary)
 
1442
        {
 
1443
                CConfig emptyConfig;
 
1444
                closeClients(emptyConfig);
 
1445
        }
 
1446
 
 
1447
        // swap thread list so nobody can mess with it
 
1448
        CThreadList threads;
 
1449
        {
 
1450
                CLock lock(&m_mutex);
 
1451
                threads.swap(m_threads);
 
1452
        }
 
1453
 
 
1454
        // cancel every thread
 
1455
        for (CThreadList::iterator index = threads.begin();
 
1456
                                                                index != threads.end(); ++index) {
 
1457
                index->cancel();
 
1458
        }
 
1459
 
 
1460
        // now wait for the threads
 
1461
        CStopwatch timer(true);
 
1462
        while (threads.size() > 0 && (timeout < 0.0 || timer.getTime() < timeout)) {
 
1463
                doReapThreads(threads);
 
1464
                ARCH->sleep(0.01);
 
1465
        }
 
1466
 
 
1467
        // delete remaining threads
 
1468
        for (CThreadList::iterator index = threads.begin();
 
1469
                                                                index != threads.end(); ++index) {
 
1470
                LOG((CLOG_DEBUG1 "reaped running thread 0x%08x", index->getID()));
 
1471
        }
 
1472
 
 
1473
        LOG((CLOG_DEBUG1 "stopped threads"));
 
1474
}
 
1475
 
 
1476
void
 
1477
CServer::reapThreads()
 
1478
{
 
1479
        CLock lock(&m_mutex);
 
1480
        doReapThreads(m_threads);
 
1481
}
 
1482
 
 
1483
void
 
1484
CServer::doReapThreads(CThreadList& threads)
 
1485
{
 
1486
        for (CThreadList::iterator index = threads.begin();
 
1487
                                                                index != threads.end(); ) {
 
1488
                if (index->wait(0.0)) {
 
1489
                        // thread terminated
 
1490
                        LOG((CLOG_DEBUG1 "reaped thread 0x%08x", index->getID()));
 
1491
                        index = threads.erase(index);
 
1492
                }
 
1493
                else {
 
1494
                        // thread is running
 
1495
                        ++index;
 
1496
                }
 
1497
        }
 
1498
}
 
1499
 
 
1500
void
 
1501
CServer::acceptClients(void*)
 
1502
{
 
1503
        LOG((CLOG_DEBUG1 "starting to wait for clients"));
 
1504
 
 
1505
        IListenSocket* listen = NULL;
 
1506
        try {
 
1507
                // create socket listener
 
1508
                if (m_socketFactory != NULL) {
 
1509
                        listen = m_socketFactory->createListen();
 
1510
                }
 
1511
                assert(listen != NULL);
 
1512
 
 
1513
                // bind to the desired port.  keep retrying if we can't bind
 
1514
                // the address immediately.
 
1515
                CStopwatch timer;
 
1516
                for (;;) {
 
1517
                        try {
 
1518
                                LOG((CLOG_DEBUG1 "binding listen socket"));
 
1519
                                listen->bind(m_config.getSynergyAddress());
 
1520
                                break;
 
1521
                        }
 
1522
                        catch (XSocketAddressInUse& e) {
 
1523
                                setStatus(kError, e.what());
 
1524
                                LOG((CLOG_WARN "bind failed: %s", e.what()));
 
1525
 
 
1526
                                // give up if we've waited too long
 
1527
                                if (timer.getTime() >= m_bindTimeout) {
 
1528
                                        LOG((CLOG_ERR "waited too long to bind, giving up"));
 
1529
                                        throw;
 
1530
                                }
 
1531
 
 
1532
                                // wait a bit before retrying
 
1533
                                ARCH->sleep(5.0);
 
1534
                        }
 
1535
                }
 
1536
 
 
1537
                // accept connections and begin processing them
 
1538
                setStatus(kRunning);
 
1539
                LOG((CLOG_DEBUG1 "waiting for client connections"));
 
1540
                for (;;) {
 
1541
                        // accept connection
 
1542
                        CThread::testCancel();
 
1543
                        IDataSocket* socket = listen->accept();
 
1544
                        LOG((CLOG_NOTE "accepted client connection"));
 
1545
                        CThread::testCancel();
 
1546
 
 
1547
                        // start handshake thread
 
1548
                        startThread(new TMethodJob<CServer>(
 
1549
                                                                this, &CServer::runClient, socket));
 
1550
                }
 
1551
        }
 
1552
        catch (XBase& e) {
 
1553
                setStatus(kError, e.what());
 
1554
                LOG((CLOG_ERR "cannot listen for clients: %s", e.what()));
 
1555
                delete listen;
 
1556
                exitMainLoopWithError();
 
1557
        }
 
1558
        catch (...) {
 
1559
                setStatus(kNotRunning);
 
1560
                delete listen;
 
1561
                throw;
 
1562
        }
 
1563
}
 
1564
 
 
1565
void
 
1566
CServer::runClient(void* vsocket)
 
1567
{
 
1568
        // get the socket pointer from the argument
 
1569
        assert(vsocket != NULL);
 
1570
        IDataSocket* socket = reinterpret_cast<IDataSocket*>(vsocket);
 
1571
 
 
1572
        // create proxy
 
1573
        CClientProxy* proxy = NULL;
 
1574
        try {
 
1575
                proxy = handshakeClient(socket);
 
1576
                if (proxy == NULL) {
 
1577
                        delete socket;
 
1578
                        return;
 
1579
                }
 
1580
        }
 
1581
        catch (...) {
 
1582
                delete socket;
 
1583
                throw;
 
1584
        }
 
1585
 
 
1586
        // add the connection
 
1587
        try {
 
1588
                addConnection(proxy);
 
1589
 
 
1590
                // save this client's thread
 
1591
                CLock lock(&m_mutex);
 
1592
                m_clientThreads.insert(std::make_pair(proxy->getName(),
 
1593
                                                                CThread::getCurrentThread()));
 
1594
 
 
1595
                // send configuration options
 
1596
                sendOptions(proxy);
 
1597
        }
 
1598
        catch (XDuplicateClient& e) {
 
1599
                // client has duplicate name
 
1600
                LOG((CLOG_WARN "a client with name \"%s\" is already connected", e.getName().c_str()));
 
1601
                try {
 
1602
                        CProtocolUtil::writef(proxy->getOutputStream(), kMsgEBusy);
 
1603
                }
 
1604
                catch (XSocket&) {
 
1605
                        // ignore
 
1606
                }
 
1607
                delete proxy;
 
1608
                delete socket;
 
1609
                return;
 
1610
        }
 
1611
        catch (XUnknownClient& e) {
 
1612
                // client has unknown name
 
1613
                LOG((CLOG_WARN "a client with name \"%s\" is not in the map", e.getName().c_str()));
 
1614
                try {
 
1615
                        CProtocolUtil::writef(proxy->getOutputStream(), kMsgEUnknown);
 
1616
                }
 
1617
                catch (XSocket&) {
 
1618
                        // ignore
 
1619
                }
 
1620
                delete proxy;
 
1621
                delete socket;
 
1622
                return;
 
1623
        }
 
1624
        catch (...) {
 
1625
                delete proxy;
 
1626
                delete socket;
 
1627
                throw;
 
1628
        }
 
1629
 
 
1630
        // activate screen saver on new client if active on the primary screen
 
1631
        {
 
1632
                CLock lock(&m_mutex);
 
1633
                if (m_activeSaver != NULL) {
 
1634
                        proxy->screensaver(true);
 
1635
                }
 
1636
        }
 
1637
 
 
1638
        // handle client messages
 
1639
        try {
 
1640
                LOG((CLOG_NOTE "client \"%s\" has connected", proxy->getName().c_str()));
 
1641
                proxy->mainLoop();
 
1642
        }
 
1643
        catch (XBadClient&) {
 
1644
                // client not behaving
 
1645
                LOG((CLOG_WARN "protocol error from client \"%s\"", proxy->getName().c_str()));
 
1646
                try {
 
1647
                        CProtocolUtil::writef(proxy->getOutputStream(), kMsgEBad);
 
1648
                }
 
1649
                catch (XSocket&) {
 
1650
                        // ignore.  client probably aborted the connection.
 
1651
                }
 
1652
        }
 
1653
        catch (XBase& e) {
 
1654
                // misc error
 
1655
                LOG((CLOG_WARN "error communicating with client \"%s\": %s", proxy->getName().c_str(), e.what()));
 
1656
        }
 
1657
        catch (...) {
 
1658
                // mainLoop() was probably cancelled
 
1659
                removeConnection(proxy->getName());
 
1660
                delete socket;
 
1661
                throw;
 
1662
        }
 
1663
 
 
1664
        // clean up
 
1665
        removeConnection(proxy->getName());
 
1666
        delete socket;
 
1667
}
 
1668
 
 
1669
CClientProxy*
 
1670
CServer::handshakeClient(IDataSocket* socket)
 
1671
{
 
1672
        LOG((CLOG_DEBUG1 "negotiating with new client"));
 
1673
 
 
1674
        // get the input and output streams
 
1675
        IInputStream*  input  = socket->getInputStream();
 
1676
        IOutputStream* output = socket->getOutputStream();
 
1677
        bool own              = false;
 
1678
 
 
1679
        // attach filters
 
1680
        if (m_streamFilterFactory != NULL) {
 
1681
                input  = m_streamFilterFactory->createInput(input, own);
 
1682
                output = m_streamFilterFactory->createOutput(output, own);
 
1683
                own    = true;
 
1684
        }
 
1685
 
 
1686
        // attach the packetizing filters
 
1687
        input  = new CInputPacketStream(input, own);
 
1688
        output = new COutputPacketStream(output, own);
 
1689
        own    = true;
 
1690
 
 
1691
        CClientProxy* proxy = NULL;
 
1692
        CString name("<unknown>");
 
1693
        try {
 
1694
                // give the client a limited time to complete the handshake
 
1695
                CTimerThread timer(30.0);
 
1696
 
 
1697
                // say hello
 
1698
                LOG((CLOG_DEBUG1 "saying hello"));
 
1699
                CProtocolUtil::writef(output, kMsgHello,
 
1700
                                                                                kProtocolMajorVersion,
 
1701
                                                                                kProtocolMinorVersion);
 
1702
                output->flush();
 
1703
 
 
1704
                // wait for the reply
 
1705
                LOG((CLOG_DEBUG1 "waiting for hello reply"));
 
1706
                UInt32 n = input->getSize();
 
1707
 
 
1708
                // limit the maximum length of the hello
 
1709
                if (n > kMaxHelloLength) {
 
1710
                        throw XBadClient();
 
1711
                }
 
1712
 
 
1713
                // get and parse the reply to hello
 
1714
                SInt16 major, minor;
 
1715
                try {
 
1716
                        LOG((CLOG_DEBUG1 "parsing hello reply"));
 
1717
                        CProtocolUtil::readf(input, kMsgHelloBack,
 
1718
                                                                                &major, &minor, &name);
 
1719
                }
 
1720
                catch (XIO&) {
 
1721
                        throw XBadClient();
 
1722
                }
 
1723
 
 
1724
                // disallow invalid version numbers
 
1725
                if (major <= 0 || minor < 0) {
 
1726
                        throw XIncompatibleClient(major, minor);
 
1727
                }
 
1728
 
 
1729
                // convert name to canonical form (if any)
 
1730
                if (m_config.isScreen(name)) {
 
1731
                        name = m_config.getCanonicalName(name);
 
1732
                }
 
1733
 
 
1734
                // create client proxy for highest version supported by the client
 
1735
                LOG((CLOG_DEBUG1 "creating proxy for client \"%s\" version %d.%d", name.c_str(), major, minor));
 
1736
                if (major == 1) {
 
1737
                        switch (minor) {
 
1738
                        case 0:
 
1739
                                proxy = new CClientProxy1_0(this, name, input, output);
 
1740
                                break;
 
1741
 
 
1742
                        case 1:
 
1743
                                proxy = new CClientProxy1_1(this, name, input, output);
 
1744
                                break;
 
1745
                        }
 
1746
                }
 
1747
 
 
1748
                // hangup (with error) if version isn't supported
 
1749
                if (proxy == NULL) {
 
1750
                        throw XIncompatibleClient(major, minor);
 
1751
                }
 
1752
 
 
1753
                // negotiate
 
1754
                // FIXME
 
1755
 
 
1756
                // ask and wait for the client's info
 
1757
                LOG((CLOG_DEBUG1 "waiting for info for client \"%s\"", name.c_str()));
 
1758
                proxy->open();
 
1759
 
 
1760
                return proxy;
 
1761
        }
 
1762
        catch (XIncompatibleClient& e) {
 
1763
                // client is incompatible
 
1764
                LOG((CLOG_WARN "client \"%s\" has incompatible version %d.%d)", name.c_str(), e.getMajor(), e.getMinor()));
 
1765
                try {
 
1766
                        CProtocolUtil::writef(output, kMsgEIncompatible,
 
1767
                                                        kProtocolMajorVersion, kProtocolMinorVersion);
 
1768
                }
 
1769
                catch (XSocket&) {
 
1770
                        // ignore
 
1771
                }
 
1772
        }
 
1773
        catch (XBadClient&) {
 
1774
                // client not behaving
 
1775
                LOG((CLOG_WARN "protocol error from client \"%s\"", name.c_str()));
 
1776
                try {
 
1777
                        CProtocolUtil::writef(output, kMsgEBad);
 
1778
                }
 
1779
                catch (XSocket&) {
 
1780
                        // ignore.  client probably aborted the connection.
 
1781
                }
 
1782
        }
 
1783
        catch (XBase& e) {
 
1784
                // misc error
 
1785
                LOG((CLOG_WARN "error communicating with client \"%s\": %s", name.c_str(), e.what()));
 
1786
        }
 
1787
        catch (...) {
 
1788
                // probably timed out
 
1789
                if (proxy != NULL) {
 
1790
                        delete proxy;
 
1791
                }
 
1792
                else if (own) {
 
1793
                        delete input;
 
1794
                        delete output;
 
1795
                }
 
1796
                throw;
 
1797
        }
 
1798
 
 
1799
        // failed
 
1800
        if (proxy != NULL) {
 
1801
                delete proxy;
 
1802
        }
 
1803
        else if (own) {
 
1804
                delete input;
 
1805
                delete output;
 
1806
        }
 
1807
 
 
1808
        return NULL;
 
1809
}
 
1810
 
 
1811
void
 
1812
CServer::acceptHTTPClients(void*)
 
1813
{
 
1814
        LOG((CLOG_DEBUG1 "starting to wait for HTTP clients"));
 
1815
 
 
1816
        IListenSocket* listen = NULL;
 
1817
        try {
 
1818
                // create socket listener
 
1819
                listen = new CTCPListenSocket;
 
1820
 
 
1821
                // bind to the desired port.  keep retrying if we can't bind
 
1822
                // the address immediately.
 
1823
                CStopwatch timer;
 
1824
                for (;;) {
 
1825
                        try {
 
1826
                                LOG((CLOG_DEBUG1 "binding HTTP listen socket"));
 
1827
                                listen->bind(m_config.getHTTPAddress());
 
1828
                                break;
 
1829
                        }
 
1830
                        catch (XSocketBind& e) {
 
1831
                                LOG((CLOG_DEBUG1 "bind HTTP failed: %s", e.what()));
 
1832
 
 
1833
                                // give up if we've waited too long
 
1834
                                if (timer.getTime() >= m_bindTimeout) {
 
1835
                                        LOG((CLOG_DEBUG1 "waited too long to bind HTTP, giving up"));
 
1836
                                        throw;
 
1837
                                }
 
1838
 
 
1839
                                // wait a bit before retrying
 
1840
                                ARCH->sleep(5.0);
 
1841
                        }
 
1842
                }
 
1843
 
 
1844
                // accept connections and begin processing them
 
1845
                LOG((CLOG_DEBUG1 "waiting for HTTP connections"));
 
1846
                for (;;) {
 
1847
                        // limit the number of HTTP requests being handled at once
 
1848
                        {
 
1849
                                CLock lock(&m_httpAvailable);
 
1850
                                while (m_httpAvailable == 0) {
 
1851
                                        m_httpAvailable.wait();
 
1852
                                }
 
1853
                                assert(m_httpAvailable > 0);
 
1854
                                m_httpAvailable = m_httpAvailable - 1;
 
1855
                        }
 
1856
 
 
1857
                        // accept connection
 
1858
                        CThread::testCancel();
 
1859
                        IDataSocket* socket = listen->accept();
 
1860
                        LOG((CLOG_NOTE "accepted HTTP connection"));
 
1861
                        CThread::testCancel();
 
1862
 
 
1863
                        // handle HTTP request
 
1864
                        startThread(new TMethodJob<CServer>(
 
1865
                                                                this, &CServer::processHTTPRequest, socket));
 
1866
                }
 
1867
 
 
1868
                // clean up
 
1869
                delete listen;
 
1870
        }
 
1871
        catch (XBase& e) {
 
1872
                LOG((CLOG_ERR "cannot listen for HTTP clients: %s", e.what()));
 
1873
                delete listen;
 
1874
                exitMainLoopWithError();
 
1875
        }
 
1876
        catch (...) {
 
1877
                delete listen;
 
1878
                throw;
 
1879
        }
 
1880
}
 
1881
 
 
1882
void
 
1883
CServer::processHTTPRequest(void* vsocket)
 
1884
{
 
1885
        IDataSocket* socket = reinterpret_cast<IDataSocket*>(vsocket);
 
1886
        try {
 
1887
                // process the request and force delivery
 
1888
                m_httpServer->processRequest(socket);
 
1889
                socket->getOutputStream()->flush();
 
1890
 
 
1891
                // wait a moment to give the client a chance to hangup first
 
1892
                ARCH->sleep(3.0);
 
1893
 
 
1894
                // clean up
 
1895
                socket->close();
 
1896
                delete socket;
 
1897
 
 
1898
                // increment available HTTP handlers
 
1899
                {
 
1900
                        CLock lock(&m_httpAvailable);
 
1901
                        m_httpAvailable = m_httpAvailable + 1;
 
1902
                        m_httpAvailable.signal();
 
1903
                }
 
1904
        }
 
1905
        catch (...) {
 
1906
                delete socket;
 
1907
                {
 
1908
                        CLock lock(&m_httpAvailable);
 
1909
                        m_httpAvailable = m_httpAvailable + 1;
 
1910
                        m_httpAvailable.signal();
 
1911
                }
 
1912
                throw;
 
1913
        }
 
1914
}
 
1915
 
 
1916
void
 
1917
CServer::sendOptions(IClient* client) const
 
1918
{
 
1919
        // note -- must be locked on entry
 
1920
 
 
1921
        COptionsList optionsList;
 
1922
 
 
1923
        // look up options for client
 
1924
        const CConfig::CScreenOptions* options =
 
1925
                                                m_config.getOptions(client->getName());
 
1926
        if (options != NULL && options->size() > 0) {
 
1927
                // convert options to a more convenient form for sending
 
1928
                optionsList.reserve(2 * options->size());
 
1929
                for (CConfig::CScreenOptions::const_iterator index = options->begin();
 
1930
                                                                        index != options->end(); ++index) {
 
1931
                        optionsList.push_back(index->first);
 
1932
                        optionsList.push_back(static_cast<UInt32>(index->second));
 
1933
                }
 
1934
        }
 
1935
 
 
1936
        // look up global options
 
1937
        options = m_config.getOptions("");
 
1938
        if (options != NULL && options->size() > 0) {
 
1939
                // convert options to a more convenient form for sending
 
1940
                optionsList.reserve(optionsList.size() + 2 * options->size());
 
1941
                for (CConfig::CScreenOptions::const_iterator index = options->begin();
 
1942
                                                                        index != options->end(); ++index) {
 
1943
                        optionsList.push_back(index->first);
 
1944
                        optionsList.push_back(static_cast<UInt32>(index->second));
 
1945
                }
 
1946
        }
 
1947
 
 
1948
        // send the options
 
1949
        client->setOptions(optionsList);
 
1950
}
 
1951
 
 
1952
void
 
1953
CServer::openPrimaryScreen()
 
1954
{
 
1955
        assert(m_primaryClient == NULL);
 
1956
 
 
1957
        // reset sequence number
 
1958
        m_seqNum = 0;
 
1959
 
 
1960
        // canonicalize the primary screen name
 
1961
        CString primaryName = m_config.getCanonicalName(getPrimaryScreenName());
 
1962
        if (primaryName.empty()) {
 
1963
                throw XUnknownClient(getPrimaryScreenName());
 
1964
        }
 
1965
 
 
1966
        // clear clipboards
 
1967
        for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
 
1968
                CClipboardInfo& clipboard   = m_clipboards[id];
 
1969
                clipboard.m_clipboardOwner  = primaryName;
 
1970
                clipboard.m_clipboardSeqNum = m_seqNum;
 
1971
                if (clipboard.m_clipboard.open(0)) {
 
1972
                        clipboard.m_clipboard.empty();
 
1973
                        clipboard.m_clipboard.close();
 
1974
                }
 
1975
                clipboard.m_clipboardData   = clipboard.m_clipboard.marshall();
 
1976
        }
 
1977
 
 
1978
        try {
 
1979
                // create the primary client
 
1980
                m_primaryClient = new CPrimaryClient(m_screenFactory,
 
1981
                                                                this, this, primaryName);
 
1982
 
 
1983
                // add connection
 
1984
                addConnection(m_primaryClient);
 
1985
                m_active = m_primaryClient;
 
1986
 
 
1987
                // open the screen
 
1988
                LOG((CLOG_DEBUG1 "opening primary screen"));
 
1989
                m_primaryClient->open();
 
1990
 
 
1991
                // tell it about the active sides
 
1992
                m_primaryClient->reconfigure(getActivePrimarySides());
 
1993
 
 
1994
                // tell primary client about its options
 
1995
                sendOptions(m_primaryClient);
 
1996
        }
 
1997
        catch (...) {
 
1998
                // if m_active is NULL then we haven't added the connection
 
1999
                // for the primary client so we don't try to remove it.
 
2000
                if (m_active != NULL) {
 
2001
                        removeConnection(primaryName);
 
2002
                }
 
2003
                else {
 
2004
                        delete m_primaryClient;
 
2005
                }
 
2006
                m_active        = NULL;
 
2007
                m_primaryClient = NULL;
 
2008
                throw;
 
2009
        }
 
2010
}
 
2011
 
 
2012
void
 
2013
CServer::closePrimaryScreen()
 
2014
{
 
2015
        assert(m_primaryClient != NULL);
 
2016
 
 
2017
        // close the primary screen
 
2018
        try {
 
2019
                LOG((CLOG_DEBUG1 "closing primary screen"));
 
2020
                m_primaryClient->close();
 
2021
        }
 
2022
        catch (...) {
 
2023
                // ignore
 
2024
        }
 
2025
 
 
2026
        // remove connection
 
2027
        removeConnection(m_primaryClient->getName());
 
2028
        m_primaryClient = NULL;
 
2029
}
 
2030
 
 
2031
void
 
2032
CServer::addConnection(IClient* client)
 
2033
{
 
2034
        assert(client != NULL);
 
2035
 
 
2036
        LOG((CLOG_DEBUG "adding connection \"%s\"", client->getName().c_str()));
 
2037
 
 
2038
        {
 
2039
                CLock lock(&m_mutex);
 
2040
 
 
2041
                // name must be in our configuration
 
2042
                if (!m_config.isScreen(client->getName())) {
 
2043
                        throw XUnknownClient(client->getName());
 
2044
                }
 
2045
 
 
2046
                // can only have one screen with a given name at any given time
 
2047
                if (m_clients.count(client->getName()) != 0) {
 
2048
                        throw XDuplicateClient(client->getName());
 
2049
                }
 
2050
 
 
2051
                // save screen info
 
2052
                m_clients.insert(std::make_pair(client->getName(), client));
 
2053
                LOG((CLOG_DEBUG "added connection \"%s\"", client->getName().c_str()));
 
2054
        }
 
2055
        runStatusJobs();
 
2056
}
 
2057
 
 
2058
void
 
2059
CServer::removeConnection(const CString& name)
 
2060
{
 
2061
        LOG((CLOG_DEBUG "removing connection \"%s\"", name.c_str()));
 
2062
        bool updateStatus;
 
2063
        {
 
2064
                CLock lock(&m_mutex);
 
2065
 
 
2066
                // find client
 
2067
                CClientList::iterator index = m_clients.find(name);
 
2068
                assert(index != m_clients.end());
 
2069
 
 
2070
                // if this is active screen then we have to jump off of it
 
2071
                IClient* active = (m_activeSaver != NULL) ? m_activeSaver : m_active;
 
2072
                if (active == index->second && active != m_primaryClient) {
 
2073
                        // record new position (center of primary screen)
 
2074
                        m_primaryClient->getCursorCenter(m_x, m_y);
 
2075
 
 
2076
                        // stop waiting to switch if we were
 
2077
                        if (active == m_switchScreen) {
 
2078
                                clearSwitchState();
 
2079
                        }
 
2080
 
 
2081
                        // don't notify active screen since it probably already
 
2082
                        // disconnected.
 
2083
                        LOG((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", active->getName().c_str(), m_primaryClient->getName().c_str(), m_x, m_y));
 
2084
 
 
2085
                        // cut over
 
2086
                        m_active = m_primaryClient;
 
2087
 
 
2088
                        // enter new screen (unless we already have because of the
 
2089
                        // screen saver)
 
2090
                        if (m_activeSaver == NULL) {
 
2091
                                m_primaryClient->enter(m_x, m_y, m_seqNum,
 
2092
                                                                        m_primaryClient->getToggleMask(), false);
 
2093
                        }
 
2094
                }
 
2095
 
 
2096
                // if this screen had the cursor when the screen saver activated
 
2097
                // then we can't switch back to it when the screen saver
 
2098
                // deactivates.
 
2099
                if (m_activeSaver == index->second) {
 
2100
                        m_activeSaver = NULL;
 
2101
                }
 
2102
 
 
2103
                // done with client
 
2104
                delete index->second;
 
2105
                m_clients.erase(index);
 
2106
 
 
2107
                // remove any thread for this client
 
2108
                m_clientThreads.erase(name);
 
2109
 
 
2110
                updateStatus = (m_clients.size() <= 1);
 
2111
        }
 
2112
 
 
2113
        if (updateStatus) {
 
2114
                runStatusJobs();
 
2115
        }
 
2116
}
 
2117
 
 
2118
 
 
2119
//
 
2120
// CServer::CClipboardInfo
 
2121
//
 
2122
 
 
2123
CString
 
2124
CServer::XServerRethrow::getWhat() const throw()
 
2125
{
 
2126
        return format("XServerRethrow", "child thread failed");
 
2127
}
 
2128
 
 
2129
 
 
2130
//
 
2131
// CServer::CClipboardInfo
 
2132
//
 
2133
 
 
2134
CServer::CClipboardInfo::CClipboardInfo() :
 
2135
        m_clipboard(),
 
2136
        m_clipboardData(),
 
2137
        m_clipboardOwner(),
 
2138
        m_clipboardSeqNum(0)
 
2139
{
 
2140
        // do nothing
 
2141
}