source: trunk/synergy/lib/server/CServer.cpp@ 3441

Last change on this file since 3441 was 2749, checked in by bird, 19 years ago

synergy v1.3.1 sources (zip).

File size: 53.0 KB
Line 
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 "CClientProxy.h"
17#include "CClientProxyUnknown.h"
18#include "CPrimaryClient.h"
19#include "IPlatformScreen.h"
20#include "OptionTypes.h"
21#include "ProtocolTypes.h"
22#include "XScreen.h"
23#include "XSynergy.h"
24#include "IDataSocket.h"
25#include "IListenSocket.h"
26#include "XSocket.h"
27#include "IEventQueue.h"
28#include "CLog.h"
29#include "TMethodEventJob.h"
30#include "CArch.h"
31#include <string.h>
32
33//
34// CServer
35//
36
37CEvent::Type CServer::s_errorEvent = CEvent::kUnknown;
38CEvent::Type CServer::s_connectedEvent = CEvent::kUnknown;
39CEvent::Type CServer::s_disconnectedEvent = CEvent::kUnknown;
40CEvent::Type CServer::s_switchToScreen = CEvent::kUnknown;
41CEvent::Type CServer::s_switchInDirection = CEvent::kUnknown;
42CEvent::Type CServer::s_lockCursorToScreen = CEvent::kUnknown;
43
44CServer::CServer(const CConfig& config, CPrimaryClient* primaryClient) :
45 m_primaryClient(primaryClient),
46 m_active(primaryClient),
47 m_seqNum(0),
48 m_xDelta(0),
49 m_yDelta(0),
50 m_xDelta2(0),
51 m_yDelta2(0),
52 m_config(),
53 m_inputFilter(m_config.getInputFilter()),
54 m_activeSaver(NULL),
55 m_switchDir(kNoDirection),
56 m_switchScreen(NULL),
57 m_switchWaitDelay(0.0),
58 m_switchWaitTimer(NULL),
59 m_switchTwoTapDelay(0.0),
60 m_switchTwoTapEngaged(false),
61 m_switchTwoTapArmed(false),
62 m_switchTwoTapZone(3),
63 m_relativeMoves(false),
64 m_lockedToScreen(false)
65{
66 // must have a primary client and it must have a canonical name
67 assert(m_primaryClient != NULL);
68 assert(config.isScreen(primaryClient->getName()));
69
70 CString primaryName = getName(primaryClient);
71
72 // clear clipboards
73 for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
74 CClipboardInfo& clipboard = m_clipboards[id];
75 clipboard.m_clipboardOwner = primaryName;
76 clipboard.m_clipboardSeqNum = m_seqNum;
77 if (clipboard.m_clipboard.open(0)) {
78 clipboard.m_clipboard.empty();
79 clipboard.m_clipboard.close();
80 }
81 clipboard.m_clipboardData = clipboard.m_clipboard.marshall();
82 }
83
84 // install event handlers
85 EVENTQUEUE->adoptHandler(CEvent::kTimer, this,
86 new TMethodEventJob<CServer>(this,
87 &CServer::handleSwitchWaitTimeout));
88 EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyDownEvent(),
89 m_inputFilter,
90 new TMethodEventJob<CServer>(this,
91 &CServer::handleKeyDownEvent));
92 EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyUpEvent(),
93 m_inputFilter,
94 new TMethodEventJob<CServer>(this,
95 &CServer::handleKeyUpEvent));
96 EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyRepeatEvent(),
97 m_inputFilter,
98 new TMethodEventJob<CServer>(this,
99 &CServer::handleKeyRepeatEvent));
100 EVENTQUEUE->adoptHandler(IPlatformScreen::getButtonDownEvent(),
101 m_inputFilter,
102 new TMethodEventJob<CServer>(this,
103 &CServer::handleButtonDownEvent));
104 EVENTQUEUE->adoptHandler(IPlatformScreen::getButtonUpEvent(),
105 m_inputFilter,
106 new TMethodEventJob<CServer>(this,
107 &CServer::handleButtonUpEvent));
108 EVENTQUEUE->adoptHandler(IPlatformScreen::getMotionOnPrimaryEvent(),
109 m_primaryClient->getEventTarget(),
110 new TMethodEventJob<CServer>(this,
111 &CServer::handleMotionPrimaryEvent));
112 EVENTQUEUE->adoptHandler(IPlatformScreen::getMotionOnSecondaryEvent(),
113 m_primaryClient->getEventTarget(),
114 new TMethodEventJob<CServer>(this,
115 &CServer::handleMotionSecondaryEvent));
116 EVENTQUEUE->adoptHandler(IPlatformScreen::getWheelEvent(),
117 m_primaryClient->getEventTarget(),
118 new TMethodEventJob<CServer>(this,
119 &CServer::handleWheelEvent));
120 EVENTQUEUE->adoptHandler(IPlatformScreen::getScreensaverActivatedEvent(),
121 m_primaryClient->getEventTarget(),
122 new TMethodEventJob<CServer>(this,
123 &CServer::handleScreensaverActivatedEvent));
124 EVENTQUEUE->adoptHandler(IPlatformScreen::getScreensaverDeactivatedEvent(),
125 m_primaryClient->getEventTarget(),
126 new TMethodEventJob<CServer>(this,
127 &CServer::handleScreensaverDeactivatedEvent));
128 EVENTQUEUE->adoptHandler(getSwitchToScreenEvent(),
129 m_inputFilter,
130 new TMethodEventJob<CServer>(this,
131 &CServer::handleSwitchToScreenEvent));
132 EVENTQUEUE->adoptHandler(getSwitchInDirectionEvent(),
133 m_inputFilter,
134 new TMethodEventJob<CServer>(this,
135 &CServer::handleSwitchInDirectionEvent));
136 EVENTQUEUE->adoptHandler(getLockCursorToScreenEvent(),
137 m_inputFilter,
138 new TMethodEventJob<CServer>(this,
139 &CServer::handleLockCursorToScreenEvent));
140 EVENTQUEUE->adoptHandler(IPlatformScreen::getFakeInputBeginEvent(),
141 m_inputFilter,
142 new TMethodEventJob<CServer>(this,
143 &CServer::handleFakeInputBeginEvent));
144 EVENTQUEUE->adoptHandler(IPlatformScreen::getFakeInputEndEvent(),
145 m_inputFilter,
146 new TMethodEventJob<CServer>(this,
147 &CServer::handleFakeInputEndEvent));
148
149 // add connection
150 addClient(m_primaryClient);
151
152 // set initial configuration
153 setConfig(config);
154
155 // enable primary client
156 m_primaryClient->enable();
157 m_inputFilter->setPrimaryClient(m_primaryClient);
158}
159
160CServer::~CServer()
161{
162 // remove event handlers and timers
163 EVENTQUEUE->removeHandler(IPlatformScreen::getKeyDownEvent(),
164 m_inputFilter);
165 EVENTQUEUE->removeHandler(IPlatformScreen::getKeyUpEvent(),
166 m_inputFilter);
167 EVENTQUEUE->removeHandler(IPlatformScreen::getKeyRepeatEvent(),
168 m_inputFilter);
169 EVENTQUEUE->removeHandler(IPlatformScreen::getButtonDownEvent(),
170 m_inputFilter);
171 EVENTQUEUE->removeHandler(IPlatformScreen::getButtonUpEvent(),
172 m_inputFilter);
173 EVENTQUEUE->removeHandler(IPlatformScreen::getMotionOnPrimaryEvent(),
174 m_primaryClient->getEventTarget());
175 EVENTQUEUE->removeHandler(IPlatformScreen::getMotionOnSecondaryEvent(),
176 m_primaryClient->getEventTarget());
177 EVENTQUEUE->removeHandler(IPlatformScreen::getWheelEvent(),
178 m_primaryClient->getEventTarget());
179 EVENTQUEUE->removeHandler(IPlatformScreen::getScreensaverActivatedEvent(),
180 m_primaryClient->getEventTarget());
181 EVENTQUEUE->removeHandler(IPlatformScreen::getScreensaverDeactivatedEvent(),
182 m_primaryClient->getEventTarget());
183 EVENTQUEUE->removeHandler(IPlatformScreen::getFakeInputBeginEvent(),
184 m_inputFilter);
185 EVENTQUEUE->removeHandler(IPlatformScreen::getFakeInputEndEvent(),
186 m_inputFilter);
187 EVENTQUEUE->removeHandler(CEvent::kTimer, this);
188 stopSwitch();
189
190 // force immediate disconnection of secondary clients
191 disconnect();
192 for (COldClients::iterator index = m_oldClients.begin();
193 index != m_oldClients.begin(); ++index) {
194 CBaseClientProxy* client = index->first;
195 EVENTQUEUE->deleteTimer(index->second);
196 EVENTQUEUE->removeHandler(CEvent::kTimer, client);
197 EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client);
198 delete client;
199 }
200
201 // remove input filter
202 m_inputFilter->setPrimaryClient(NULL);
203
204 // disable and disconnect primary client
205 m_primaryClient->disable();
206 removeClient(m_primaryClient);
207}
208
209bool
210CServer::setConfig(const CConfig& config)
211{
212 // refuse configuration if it doesn't include the primary screen
213 if (!config.isScreen(m_primaryClient->getName())) {
214 return false;
215 }
216
217 // close clients that are connected but being dropped from the
218 // configuration.
219 closeClients(config);
220
221 // cut over
222 m_config = config;
223 processOptions();
224
225 // add ScrollLock as a hotkey to lock to the screen. this was a
226 // built-in feature in earlier releases and is now supported via
227 // the user configurable hotkey mechanism. if the user has already
228 // registered ScrollLock for something else then that will win but
229 // we will unfortunately generate a warning. if the user has
230 // configured a CLockCursorToScreenAction then we don't add
231 // ScrollLock as a hotkey.
232 if (!m_config.hasLockToScreenAction()) {
233 IPlatformScreen::CKeyInfo* key =
234 IPlatformScreen::CKeyInfo::alloc(kKeyScrollLock, 0, 0, 0);
235 CInputFilter::CRule rule(new CInputFilter::CKeystrokeCondition(key));
236 rule.adoptAction(new CInputFilter::CLockCursorToScreenAction, true);
237 m_inputFilter->addFilterRule(rule);
238 }
239
240 // tell primary screen about reconfiguration
241 m_primaryClient->reconfigure(getActivePrimarySides());
242
243 // tell all (connected) clients about current options
244 for (CClientList::const_iterator index = m_clients.begin();
245 index != m_clients.end(); ++index) {
246 CBaseClientProxy* client = index->second;
247 sendOptions(client);
248 }
249
250 return true;
251}
252
253void
254CServer::adoptClient(CBaseClientProxy* client)
255{
256 assert(client != NULL);
257
258 // watch for client disconnection
259 EVENTQUEUE->adoptHandler(CClientProxy::getDisconnectedEvent(), client,
260 new TMethodEventJob<CServer>(this,
261 &CServer::handleClientDisconnected, client));
262
263 // name must be in our configuration
264 if (!m_config.isScreen(client->getName())) {
265 LOG((CLOG_WARN "a client with name \"%s\" is not in the map", client->getName().c_str()));
266 closeClient(client, kMsgEUnknown);
267 return;
268 }
269
270 // add client to client list
271 if (!addClient(client)) {
272 // can only have one screen with a given name at any given time
273 LOG((CLOG_WARN "a client with name \"%s\" is already connected", getName(client).c_str()));
274 closeClient(client, kMsgEBusy);
275 return;
276 }
277 LOG((CLOG_NOTE "client \"%s\" has connected", getName(client).c_str()));
278
279 // send configuration options to client
280 sendOptions(client);
281
282 // activate screen saver on new client if active on the primary screen
283 if (m_activeSaver != NULL) {
284 client->screensaver(true);
285 }
286
287 // send notification
288 CServer::CScreenConnectedInfo* info =
289 CServer::CScreenConnectedInfo::alloc(getName(client));
290 EVENTQUEUE->addEvent(CEvent(CServer::getConnectedEvent(),
291 m_primaryClient->getEventTarget(), info));
292}
293
294void
295CServer::disconnect()
296{
297 // close all secondary clients
298 if (m_clients.size() > 1 || !m_oldClients.empty()) {
299 CConfig emptyConfig;
300 closeClients(emptyConfig);
301 }
302 else {
303 EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this));
304 }
305}
306
307UInt32
308CServer::getNumClients() const
309{
310 return m_clients.size();
311}
312
313void
314CServer::getClients(std::vector<CString>& list) const
315{
316 list.clear();
317 for (CClientList::const_iterator index = m_clients.begin();
318 index != m_clients.end(); ++index) {
319 list.push_back(index->first);
320 }
321}
322
323CEvent::Type
324CServer::getErrorEvent()
325{
326 return CEvent::registerTypeOnce(s_errorEvent,
327 "CServer::error");
328}
329
330CEvent::Type
331CServer::getConnectedEvent()
332{
333 return CEvent::registerTypeOnce(s_connectedEvent,
334 "CServer::connected");
335}
336
337CEvent::Type
338CServer::getDisconnectedEvent()
339{
340 return CEvent::registerTypeOnce(s_disconnectedEvent,
341 "CServer::disconnected");
342}
343
344CEvent::Type
345CServer::getSwitchToScreenEvent()
346{
347 return CEvent::registerTypeOnce(s_switchToScreen,
348 "CServer::switchToScreen");
349}
350
351CEvent::Type
352CServer::getSwitchInDirectionEvent()
353{
354 return CEvent::registerTypeOnce(s_switchInDirection,
355 "CServer::switchInDirection");
356}
357
358CEvent::Type
359CServer::getLockCursorToScreenEvent()
360{
361 return CEvent::registerTypeOnce(s_lockCursorToScreen,
362 "CServer::lockCursorToScreen");
363}
364
365CString
366CServer::getName(const CBaseClientProxy* client) const
367{
368 CString name = m_config.getCanonicalName(client->getName());
369 if (name.empty()) {
370 name = client->getName();
371 }
372 return name;
373}
374
375UInt32
376CServer::getActivePrimarySides() const
377{
378 UInt32 sides = 0;
379 if (!isLockedToScreenServer()) {
380 if (hasAnyNeighbor(m_primaryClient, kLeft)) {
381 sides |= kLeftMask;
382 }
383 if (hasAnyNeighbor(m_primaryClient, kRight)) {
384 sides |= kRightMask;
385 }
386 if (hasAnyNeighbor(m_primaryClient, kTop)) {
387 sides |= kTopMask;
388 }
389 if (hasAnyNeighbor(m_primaryClient, kBottom)) {
390 sides |= kBottomMask;
391 }
392 }
393 return sides;
394}
395
396bool
397CServer::isLockedToScreenServer() const
398{
399 // locked if scroll-lock is toggled on
400 return m_lockedToScreen;
401}
402
403bool
404CServer::isLockedToScreen() const
405{
406 // locked if we say we're locked
407 if (isLockedToScreenServer()) {
408 LOG((CLOG_DEBUG "locked to screen"));
409 return true;
410 }
411
412 // locked if primary says we're locked
413 if (m_primaryClient->isLockedToScreen()) {
414 return true;
415 }
416
417 // not locked
418 return false;
419}
420
421SInt32
422CServer::getJumpZoneSize(CBaseClientProxy* client) const
423{
424 if (client == m_primaryClient) {
425 return m_primaryClient->getJumpZoneSize();
426 }
427 else {
428 return 0;
429 }
430}
431
432void
433CServer::switchScreen(CBaseClientProxy* dst,
434 SInt32 x, SInt32 y, bool forScreensaver)
435{
436 assert(dst != NULL);
437#ifndef NDEBUG
438 {
439 SInt32 dx, dy, dw, dh;
440 dst->getShape(dx, dy, dw, dh);
441 assert(x >= dx && y >= dy && x < dx + dw && y < dy + dh);
442 }
443#endif
444 assert(m_active != NULL);
445
446 LOG((CLOG_INFO "switch from \"%s\" to \"%s\" at %d,%d", getName(m_active).c_str(), getName(dst).c_str(), x, y));
447
448 // stop waiting to switch
449 stopSwitch();
450
451 // record new position
452 m_x = x;
453 m_y = y;
454 m_xDelta = 0;
455 m_yDelta = 0;
456 m_xDelta2 = 0;
457 m_yDelta2 = 0;
458
459 // wrapping means leaving the active screen and entering it again.
460 // since that's a waste of time we skip that and just warp the
461 // mouse.
462 if (m_active != dst) {
463 // leave active screen
464 if (!m_active->leave()) {
465 // cannot leave screen
466 LOG((CLOG_WARN "can't leave screen"));
467 return;
468 }
469
470 // update the primary client's clipboards if we're leaving the
471 // primary screen.
472 if (m_active == m_primaryClient) {
473 for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
474 CClipboardInfo& clipboard = m_clipboards[id];
475 if (clipboard.m_clipboardOwner == getName(m_primaryClient)) {
476 onClipboardChanged(m_primaryClient,
477 id, clipboard.m_clipboardSeqNum);
478 }
479 }
480 }
481
482 // cut over
483 m_active = dst;
484
485 // increment enter sequence number
486 ++m_seqNum;
487
488 // enter new screen
489 m_active->enter(x, y, m_seqNum,
490 m_primaryClient->getToggleMask(),
491 forScreensaver);
492
493 // send the clipboard data to new active screen
494 for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
495 m_active->setClipboard(id, &m_clipboards[id].m_clipboard);
496 }
497 }
498 else {
499 m_active->mouseMove(x, y);
500 }
501}
502
503void
504CServer::jumpToScreen(CBaseClientProxy* newScreen)
505{
506 assert(newScreen != NULL);
507
508 // record the current cursor position on the active screen
509 m_active->setJumpCursorPos(m_x, m_y);
510
511 // get the last cursor position on the target screen
512 SInt32 x, y;
513 newScreen->getJumpCursorPos(x, y);
514
515 switchScreen(newScreen, x, y, false);
516}
517
518float
519CServer::mapToFraction(CBaseClientProxy* client,
520 EDirection dir, SInt32 x, SInt32 y) const
521{
522 SInt32 sx, sy, sw, sh;
523 client->getShape(sx, sy, sw, sh);
524 switch (dir) {
525 case kLeft:
526 case kRight:
527 return static_cast<float>(y - sy + 0.5f) / static_cast<float>(sh);
528
529 case kTop:
530 case kBottom:
531 return static_cast<float>(x - sx + 0.5f) / static_cast<float>(sw);
532
533 case kNoDirection:
534 assert(0 && "bad direction");
535 break;
536 }
537 return 0.0f;
538}
539
540void
541CServer::mapToPixel(CBaseClientProxy* client,
542 EDirection dir, float f, SInt32& x, SInt32& y) const
543{
544 SInt32 sx, sy, sw, sh;
545 client->getShape(sx, sy, sw, sh);
546 switch (dir) {
547 case kLeft:
548 case kRight:
549 y = static_cast<SInt32>(f * sh) + sy;
550 break;
551
552 case kTop:
553 case kBottom:
554 x = static_cast<SInt32>(f * sw) + sx;
555 break;
556
557 case kNoDirection:
558 assert(0 && "bad direction");
559 break;
560 }
561}
562
563bool
564CServer::hasAnyNeighbor(CBaseClientProxy* client, EDirection dir) const
565{
566 assert(client != NULL);
567
568 return m_config.hasNeighbor(getName(client), dir);
569}
570
571CBaseClientProxy*
572CServer::getNeighbor(CBaseClientProxy* src,
573 EDirection dir, SInt32& x, SInt32& y) const
574{
575 // note -- must be locked on entry
576
577 assert(src != NULL);
578
579 // get source screen name
580 CString srcName = getName(src);
581 assert(!srcName.empty());
582 LOG((CLOG_DEBUG2 "find neighbor on %s of \"%s\"", CConfig::dirName(dir), srcName.c_str()));
583
584 // convert position to fraction
585 float t = mapToFraction(src, dir, x, y);
586
587 // search for the closest neighbor that exists in direction dir
588 float tTmp;
589 for (;;) {
590 CString dstName(m_config.getNeighbor(srcName, dir, t, &tTmp));
591
592 // if nothing in that direction then return NULL. if the
593 // destination is the source then we can make no more
594 // progress in this direction. since we haven't found a
595 // connected neighbor we return NULL.
596 if (dstName.empty()) {
597 LOG((CLOG_DEBUG2 "no neighbor on %s of \"%s\"", CConfig::dirName(dir), srcName.c_str()));
598 return NULL;
599 }
600
601 // look up neighbor cell. if the screen is connected and
602 // ready then we can stop.
603 CClientList::const_iterator index = m_clients.find(dstName);
604 if (index != m_clients.end()) {
605 LOG((CLOG_DEBUG2 "\"%s\" is on %s of \"%s\" at %f", dstName.c_str(), CConfig::dirName(dir), srcName.c_str(), t));
606 mapToPixel(index->second, dir, tTmp, x, y);
607 return index->second;
608 }
609
610 // skip over unconnected screen
611 LOG((CLOG_DEBUG2 "ignored \"%s\" on %s of \"%s\"", dstName.c_str(), CConfig::dirName(dir), srcName.c_str()));
612 srcName = dstName;
613
614 // use position on skipped screen
615 t = tTmp;
616 }
617}
618
619CBaseClientProxy*
620CServer::mapToNeighbor(CBaseClientProxy* src,
621 EDirection srcSide, SInt32& x, SInt32& y) const
622{
623 // note -- must be locked on entry
624
625 assert(src != NULL);
626
627 // get the first neighbor
628 CBaseClientProxy* dst = getNeighbor(src, srcSide, x, y);
629 if (dst == NULL) {
630 return NULL;
631 }
632
633 // get the source screen's size
634 SInt32 dx, dy, dw, dh;
635 CBaseClientProxy* lastGoodScreen = src;
636 lastGoodScreen->getShape(dx, dy, dw, dh);
637
638 // find destination screen, adjusting x or y (but not both). the
639 // searches are done in a sort of canonical screen space where
640 // the upper-left corner is 0,0 for each screen. we adjust from
641 // actual to canonical position on entry to and from canonical to
642 // actual on exit from the search.
643 switch (srcSide) {
644 case kLeft:
645 x -= dx;
646 while (dst != NULL) {
647 lastGoodScreen = dst;
648 lastGoodScreen->getShape(dx, dy, dw, dh);
649 x += dw;
650 if (x >= 0) {
651 break;
652 }
653 LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
654 dst = getNeighbor(lastGoodScreen, srcSide, x, y);
655 }
656 assert(lastGoodScreen != NULL);
657 x += dx;
658 break;
659
660 case kRight:
661 x -= dx;
662 while (dst != NULL) {
663 x -= dw;
664 lastGoodScreen = dst;
665 lastGoodScreen->getShape(dx, dy, dw, dh);
666 if (x < dw) {
667 break;
668 }
669 LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
670 dst = getNeighbor(lastGoodScreen, srcSide, x, y);
671 }
672 assert(lastGoodScreen != NULL);
673 x += dx;
674 break;
675
676 case kTop:
677 y -= dy;
678 while (dst != NULL) {
679 lastGoodScreen = dst;
680 lastGoodScreen->getShape(dx, dy, dw, dh);
681 y += dh;
682 if (y >= 0) {
683 break;
684 }
685 LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
686 dst = getNeighbor(lastGoodScreen, srcSide, x, y);
687 }
688 assert(lastGoodScreen != NULL);
689 y += dy;
690 break;
691
692 case kBottom:
693 y -= dy;
694 while (dst != NULL) {
695 y -= dh;
696 lastGoodScreen = dst;
697 lastGoodScreen->getShape(dx, dy, dw, dh);
698 if (y < dh) {
699 break;
700 }
701 LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
702 dst = getNeighbor(lastGoodScreen, srcSide, x, y);
703 }
704 assert(lastGoodScreen != NULL);
705 y += dy;
706 break;
707
708 case kNoDirection:
709 assert(0 && "bad direction");
710 return NULL;
711 }
712
713 // save destination screen
714 assert(lastGoodScreen != NULL);
715 dst = lastGoodScreen;
716
717 // if entering primary screen then be sure to move in far enough
718 // to avoid the jump zone. if entering a side that doesn't have
719 // a neighbor (i.e. an asymmetrical side) then we don't need to
720 // move inwards because that side can't provoke a jump.
721 avoidJumpZone(dst, srcSide, x, y);
722
723 return dst;
724}
725
726void
727CServer::avoidJumpZone(CBaseClientProxy* dst,
728 EDirection dir, SInt32& x, SInt32& y) const
729{
730 // we only need to avoid jump zones on the primary screen
731 if (dst != m_primaryClient) {
732 return;
733 }
734
735 const CString dstName(getName(dst));
736 SInt32 dx, dy, dw, dh;
737 dst->getShape(dx, dy, dw, dh);
738 float t = mapToFraction(dst, dir, x, y);
739 SInt32 z = getJumpZoneSize(dst);
740
741 // move in far enough to avoid the jump zone. if entering a side
742 // that doesn't have a neighbor (i.e. an asymmetrical side) then we
743 // don't need to move inwards because that side can't provoke a jump.
744 switch (dir) {
745 case kLeft:
746 if (!m_config.getNeighbor(dstName, kRight, t, NULL).empty() &&
747 x > dx + dw - 1 - z)
748 x = dx + dw - 1 - z;
749 break;
750
751 case kRight:
752 if (!m_config.getNeighbor(dstName, kLeft, t, NULL).empty() &&
753 x < dx + z)
754 x = dx + z;
755 break;
756
757 case kTop:
758 if (!m_config.getNeighbor(dstName, kBottom, t, NULL).empty() &&
759 y > dy + dh - 1 - z)
760 y = dy + dh - 1 - z;
761 break;
762
763 case kBottom:
764 if (!m_config.getNeighbor(dstName, kTop, t, NULL).empty() &&
765 y < dy + z)
766 y = dy + z;
767 break;
768
769 case kNoDirection:
770 assert(0 && "bad direction");
771 }
772}
773
774bool
775CServer::isSwitchOkay(CBaseClientProxy* newScreen,
776 EDirection dir, SInt32 x, SInt32 y,
777 SInt32 xActive, SInt32 yActive)
778{
779 LOG((CLOG_DEBUG1 "try to leave \"%s\" on %s", getName(m_active).c_str(), CConfig::dirName(dir)));
780
781 // is there a neighbor?
782 if (newScreen == NULL) {
783 // there's no neighbor. we don't want to switch and we don't
784 // want to try to switch later.
785 LOG((CLOG_DEBUG1 "no neighbor %s", CConfig::dirName(dir)));
786 stopSwitch();
787 return false;
788 }
789
790 // should we switch or not?
791 bool preventSwitch = false;
792 bool allowSwitch = false;
793
794 // note if the switch direction has changed. save the new
795 // direction and screen if so.
796 bool isNewDirection = (dir != m_switchDir);
797 if (isNewDirection || m_switchScreen == NULL) {
798 m_switchDir = dir;
799 m_switchScreen = newScreen;
800 }
801
802 // is this a double tap and do we care?
803 if (!allowSwitch && m_switchTwoTapDelay > 0.0) {
804 if (isNewDirection ||
805 !isSwitchTwoTapStarted() || !shouldSwitchTwoTap()) {
806 // tapping a different or new edge or second tap not
807 // fast enough. prepare for second tap.
808 preventSwitch = true;
809 startSwitchTwoTap();
810 }
811 else {
812 // got second tap
813 allowSwitch = true;
814 }
815 }
816
817 // if waiting before a switch then prepare to switch later
818 if (!allowSwitch && m_switchWaitDelay > 0.0) {
819 if (isNewDirection || !isSwitchWaitStarted()) {
820 startSwitchWait(x, y);
821 }
822 preventSwitch = true;
823 }
824
825 // are we in a locked corner? first check if screen has the option set
826 // and, if not, check the global options.
827 const CConfig::CScreenOptions* options =
828 m_config.getOptions(getName(m_active));
829 if (options == NULL || options->count(kOptionScreenSwitchCorners) == 0) {
830 options = m_config.getOptions("");
831 }
832 if (options != NULL && options->count(kOptionScreenSwitchCorners) > 0) {
833 // get corner mask and size
834 CConfig::CScreenOptions::const_iterator i =
835 options->find(kOptionScreenSwitchCorners);
836 UInt32 corners = static_cast<UInt32>(i->second);
837 i = options->find(kOptionScreenSwitchCornerSize);
838 SInt32 size = 0;
839 if (i != options->end()) {
840 size = i->second;
841 }
842
843 // see if we're in a locked corner
844 if ((getCorner(m_active, xActive, yActive, size) & corners) != 0) {
845 // yep, no switching
846 LOG((CLOG_DEBUG1 "locked in corner"));
847 preventSwitch = true;
848 stopSwitch();
849 }
850 }
851
852 // ignore if mouse is locked to screen and don't try to switch later
853 if (!preventSwitch && isLockedToScreen()) {
854 LOG((CLOG_DEBUG1 "locked to screen"));
855 preventSwitch = true;
856 stopSwitch();
857 }
858
859 return !preventSwitch;
860}
861
862void
863CServer::noSwitch(SInt32 x, SInt32 y)
864{
865 armSwitchTwoTap(x, y);
866 stopSwitchWait();
867}
868
869void
870CServer::stopSwitch()
871{
872 if (m_switchScreen != NULL) {
873 m_switchScreen = NULL;
874 m_switchDir = kNoDirection;
875 stopSwitchTwoTap();
876 stopSwitchWait();
877 }
878}
879
880void
881CServer::startSwitchTwoTap()
882{
883 m_switchTwoTapEngaged = true;
884 m_switchTwoTapArmed = false;
885 m_switchTwoTapTimer.reset();
886 LOG((CLOG_DEBUG1 "waiting for second tap"));
887}
888
889void
890CServer::armSwitchTwoTap(SInt32 x, SInt32 y)
891{
892 if (m_switchTwoTapEngaged) {
893 if (m_switchTwoTapTimer.getTime() > m_switchTwoTapDelay) {
894 // second tap took too long. disengage.
895 stopSwitchTwoTap();
896 }
897 else if (!m_switchTwoTapArmed) {
898 // still time for a double tap. see if we left the tap
899 // zone and, if so, arm the two tap.
900 SInt32 ax, ay, aw, ah;
901 m_active->getShape(ax, ay, aw, ah);
902 SInt32 tapZone = m_primaryClient->getJumpZoneSize();
903 if (tapZone < m_switchTwoTapZone) {
904 tapZone = m_switchTwoTapZone;
905 }
906 if (x >= ax + tapZone && x < ax + aw - tapZone &&
907 y >= ay + tapZone && y < ay + ah - tapZone) {
908 // win32 can generate bogus mouse events that appear to
909 // move in the opposite direction that the mouse actually
910 // moved. try to ignore that crap here.
911 switch (m_switchDir) {
912 case kLeft:
913 m_switchTwoTapArmed = (m_xDelta > 0 && m_xDelta2 > 0);
914 break;
915
916 case kRight:
917 m_switchTwoTapArmed = (m_xDelta < 0 && m_xDelta2 < 0);
918 break;
919
920 case kTop:
921 m_switchTwoTapArmed = (m_yDelta > 0 && m_yDelta2 > 0);
922 break;
923
924 case kBottom:
925 m_switchTwoTapArmed = (m_yDelta < 0 && m_yDelta2 < 0);
926 break;
927
928 default:
929 break;
930 }
931 }
932 }
933 }
934}
935
936void
937CServer::stopSwitchTwoTap()
938{
939 m_switchTwoTapEngaged = false;
940 m_switchTwoTapArmed = false;
941}
942
943bool
944CServer::isSwitchTwoTapStarted() const
945{
946 return m_switchTwoTapEngaged;
947}
948
949bool
950CServer::shouldSwitchTwoTap() const
951{
952 // this is the second tap if two-tap is armed and this tap
953 // came fast enough
954 return (m_switchTwoTapArmed &&
955 m_switchTwoTapTimer.getTime() <= m_switchTwoTapDelay);
956}
957
958void
959CServer::startSwitchWait(SInt32 x, SInt32 y)
960{
961 stopSwitchWait();
962 m_switchWaitX = x;
963 m_switchWaitY = y;
964 m_switchWaitTimer = EVENTQUEUE->newOneShotTimer(m_switchWaitDelay, this);
965 LOG((CLOG_DEBUG1 "waiting to switch"));
966}
967
968void
969CServer::stopSwitchWait()
970{
971 if (m_switchWaitTimer != NULL) {
972 EVENTQUEUE->deleteTimer(m_switchWaitTimer);
973 m_switchWaitTimer = NULL;
974 }
975}
976
977bool
978CServer::isSwitchWaitStarted() const
979{
980 return (m_switchWaitTimer != NULL);
981}
982
983UInt32
984CServer::getCorner(CBaseClientProxy* client,
985 SInt32 x, SInt32 y, SInt32 size) const
986{
987 assert(client != NULL);
988
989 // get client screen shape
990 SInt32 ax, ay, aw, ah;
991 client->getShape(ax, ay, aw, ah);
992
993 // check for x,y on the left or right
994 SInt32 xSide;
995 if (x <= ax) {
996 xSide = -1;
997 }
998 else if (x >= ax + aw - 1) {
999 xSide = 1;
1000 }
1001 else {
1002 xSide = 0;
1003 }
1004
1005 // check for x,y on the top or bottom
1006 SInt32 ySide;
1007 if (y <= ay) {
1008 ySide = -1;
1009 }
1010 else if (y >= ay + ah - 1) {
1011 ySide = 1;
1012 }
1013 else {
1014 ySide = 0;
1015 }
1016
1017 // if against the left or right then check if y is within size
1018 if (xSide != 0) {
1019 if (y < ay + size) {
1020 return (xSide < 0) ? kTopLeftMask : kTopRightMask;
1021 }
1022 else if (y >= ay + ah - size) {
1023 return (xSide < 0) ? kBottomLeftMask : kBottomRightMask;
1024 }
1025 }
1026
1027 // if against the left or right then check if y is within size
1028 if (ySide != 0) {
1029 if (x < ax + size) {
1030 return (ySide < 0) ? kTopLeftMask : kBottomLeftMask;
1031 }
1032 else if (x >= ax + aw - size) {
1033 return (ySide < 0) ? kTopRightMask : kBottomRightMask;
1034 }
1035 }
1036
1037 return kNoCornerMask;
1038}
1039
1040void
1041CServer::stopRelativeMoves()
1042{
1043 if (m_relativeMoves && m_active != m_primaryClient) {
1044 // warp to the center of the active client so we know where we are
1045 SInt32 ax, ay, aw, ah;
1046 m_active->getShape(ax, ay, aw, ah);
1047 m_x = ax + (aw >> 1);
1048 m_y = ay + (ah >> 1);
1049 m_xDelta = 0;
1050 m_yDelta = 0;
1051 m_xDelta2 = 0;
1052 m_yDelta2 = 0;
1053 LOG((CLOG_DEBUG2 "synchronize move on %s by %d,%d", getName(m_active).c_str(), m_x, m_y));
1054 m_active->mouseMove(m_x, m_y);
1055 }
1056}
1057
1058void
1059CServer::sendOptions(CBaseClientProxy* client) const
1060{
1061 COptionsList optionsList;
1062
1063 // look up options for client
1064 const CConfig::CScreenOptions* options =
1065 m_config.getOptions(getName(client));
1066 if (options != NULL) {
1067 // convert options to a more convenient form for sending
1068 optionsList.reserve(2 * options->size());
1069 for (CConfig::CScreenOptions::const_iterator index = options->begin();
1070 index != options->end(); ++index) {
1071 optionsList.push_back(index->first);
1072 optionsList.push_back(static_cast<UInt32>(index->second));
1073 }
1074 }
1075
1076 // look up global options
1077 options = m_config.getOptions("");
1078 if (options != NULL) {
1079 // convert options to a more convenient form for sending
1080 optionsList.reserve(optionsList.size() + 2 * options->size());
1081 for (CConfig::CScreenOptions::const_iterator index = options->begin();
1082 index != options->end(); ++index) {
1083 optionsList.push_back(index->first);
1084 optionsList.push_back(static_cast<UInt32>(index->second));
1085 }
1086 }
1087
1088 // send the options
1089 client->resetOptions();
1090 client->setOptions(optionsList);
1091}
1092
1093void
1094CServer::processOptions()
1095{
1096 const CConfig::CScreenOptions* options = m_config.getOptions("");
1097 if (options == NULL) {
1098 return;
1099 }
1100
1101 bool newRelativeMoves = m_relativeMoves;
1102 for (CConfig::CScreenOptions::const_iterator index = options->begin();
1103 index != options->end(); ++index) {
1104 const OptionID id = index->first;
1105 const OptionValue value = index->second;
1106 if (id == kOptionScreenSwitchDelay) {
1107 m_switchWaitDelay = 1.0e-3 * static_cast<double>(value);
1108 if (m_switchWaitDelay < 0.0) {
1109 m_switchWaitDelay = 0.0;
1110 }
1111 stopSwitchWait();
1112 }
1113 else if (id == kOptionScreenSwitchTwoTap) {
1114 m_switchTwoTapDelay = 1.0e-3 * static_cast<double>(value);
1115 if (m_switchTwoTapDelay < 0.0) {
1116 m_switchTwoTapDelay = 0.0;
1117 }
1118 stopSwitchTwoTap();
1119 }
1120 else if (id == kOptionRelativeMouseMoves) {
1121 newRelativeMoves = (value != 0);
1122 }
1123 }
1124
1125 if (m_relativeMoves && !newRelativeMoves) {
1126 stopRelativeMoves();
1127 }
1128 m_relativeMoves = newRelativeMoves;
1129}
1130
1131void
1132CServer::handleShapeChanged(const CEvent&, void* vclient)
1133{
1134 // ignore events from unknown clients
1135 CBaseClientProxy* client = reinterpret_cast<CBaseClientProxy*>(vclient);
1136 if (m_clientSet.count(client) == 0) {
1137 return;
1138 }
1139
1140 LOG((CLOG_INFO "screen \"%s\" shape changed", getName(client).c_str()));
1141
1142 // update jump coordinate
1143 SInt32 x, y;
1144 client->getCursorPos(x, y);
1145 client->setJumpCursorPos(x, y);
1146
1147 // update the mouse coordinates
1148 if (client == m_active) {
1149 m_x = x;
1150 m_y = y;
1151 }
1152
1153 // handle resolution change to primary screen
1154 if (client == m_primaryClient) {
1155 if (client == m_active) {
1156 onMouseMovePrimary(m_x, m_y);
1157 }
1158 else {
1159 onMouseMoveSecondary(0, 0);
1160 }
1161 }
1162}
1163
1164void
1165CServer::handleClipboardGrabbed(const CEvent& event, void* vclient)
1166{
1167 // ignore events from unknown clients
1168 CBaseClientProxy* grabber = reinterpret_cast<CBaseClientProxy*>(vclient);
1169 if (m_clientSet.count(grabber) == 0) {
1170 return;
1171 }
1172 const IScreen::CClipboardInfo* info =
1173 reinterpret_cast<const IScreen::CClipboardInfo*>(event.getData());
1174
1175 // ignore grab if sequence number is old. always allow primary
1176 // screen to grab.
1177 CClipboardInfo& clipboard = m_clipboards[info->m_id];
1178 if (grabber != m_primaryClient &&
1179 info->m_sequenceNumber < clipboard.m_clipboardSeqNum) {
1180 LOG((CLOG_INFO "ignored screen \"%s\" grab of clipboard %d", getName(grabber).c_str(), info->m_id));
1181 return;
1182 }
1183
1184 // mark screen as owning clipboard
1185 LOG((CLOG_INFO "screen \"%s\" grabbed clipboard %d from \"%s\"", getName(grabber).c_str(), info->m_id, clipboard.m_clipboardOwner.c_str()));
1186 clipboard.m_clipboardOwner = getName(grabber);
1187 clipboard.m_clipboardSeqNum = info->m_sequenceNumber;
1188
1189 // clear the clipboard data (since it's not known at this point)
1190 if (clipboard.m_clipboard.open(0)) {
1191 clipboard.m_clipboard.empty();
1192 clipboard.m_clipboard.close();
1193 }
1194 clipboard.m_clipboardData = clipboard.m_clipboard.marshall();
1195
1196 // tell all other screens to take ownership of clipboard. tell the
1197 // grabber that it's clipboard isn't dirty.
1198 for (CClientList::iterator index = m_clients.begin();
1199 index != m_clients.end(); ++index) {
1200 CBaseClientProxy* client = index->second;
1201 if (client == grabber) {
1202 client->setClipboardDirty(info->m_id, false);
1203 }
1204 else {
1205 client->grabClipboard(info->m_id);
1206 }
1207 }
1208}
1209
1210void
1211CServer::handleClipboardChanged(const CEvent& event, void* vclient)
1212{
1213 // ignore events from unknown clients
1214 CBaseClientProxy* sender = reinterpret_cast<CBaseClientProxy*>(vclient);
1215 if (m_clientSet.count(sender) == 0) {
1216 return;
1217 }
1218 const IScreen::CClipboardInfo* info =
1219 reinterpret_cast<const IScreen::CClipboardInfo*>(event.getData());
1220 onClipboardChanged(sender, info->m_id, info->m_sequenceNumber);
1221}
1222
1223void
1224CServer::handleKeyDownEvent(const CEvent& event, void*)
1225{
1226 IPlatformScreen::CKeyInfo* info =
1227 reinterpret_cast<IPlatformScreen::CKeyInfo*>(event.getData());
1228 onKeyDown(info->m_key, info->m_mask, info->m_button, info->m_screens);
1229}
1230
1231void
1232CServer::handleKeyUpEvent(const CEvent& event, void*)
1233{
1234 IPlatformScreen::CKeyInfo* info =
1235 reinterpret_cast<IPlatformScreen::CKeyInfo*>(event.getData());
1236 onKeyUp(info->m_key, info->m_mask, info->m_button, info->m_screens);
1237}
1238
1239void
1240CServer::handleKeyRepeatEvent(const CEvent& event, void*)
1241{
1242 IPlatformScreen::CKeyInfo* info =
1243 reinterpret_cast<IPlatformScreen::CKeyInfo*>(event.getData());
1244 onKeyRepeat(info->m_key, info->m_mask, info->m_count, info->m_button);
1245}
1246
1247void
1248CServer::handleButtonDownEvent(const CEvent& event, void*)
1249{
1250 IPlatformScreen::CButtonInfo* info =
1251 reinterpret_cast<IPlatformScreen::CButtonInfo*>(event.getData());
1252 onMouseDown(info->m_button);
1253}
1254
1255void
1256CServer::handleButtonUpEvent(const CEvent& event, void*)
1257{
1258 IPlatformScreen::CButtonInfo* info =
1259 reinterpret_cast<IPlatformScreen::CButtonInfo*>(event.getData());
1260 onMouseUp(info->m_button);
1261}
1262
1263void
1264CServer::handleMotionPrimaryEvent(const CEvent& event, void*)
1265{
1266 IPlatformScreen::CMotionInfo* info =
1267 reinterpret_cast<IPlatformScreen::CMotionInfo*>(event.getData());
1268 onMouseMovePrimary(info->m_x, info->m_y);
1269}
1270
1271void
1272CServer::handleMotionSecondaryEvent(const CEvent& event, void*)
1273{
1274 IPlatformScreen::CMotionInfo* info =
1275 reinterpret_cast<IPlatformScreen::CMotionInfo*>(event.getData());
1276 onMouseMoveSecondary(info->m_x, info->m_y);
1277}
1278
1279void
1280CServer::handleWheelEvent(const CEvent& event, void*)
1281{
1282 IPlatformScreen::CWheelInfo* info =
1283 reinterpret_cast<IPlatformScreen::CWheelInfo*>(event.getData());
1284 onMouseWheel(info->m_xDelta, info->m_yDelta);
1285}
1286
1287void
1288CServer::handleScreensaverActivatedEvent(const CEvent&, void*)
1289{
1290 onScreensaver(true);
1291}
1292
1293void
1294CServer::handleScreensaverDeactivatedEvent(const CEvent&, void*)
1295{
1296 onScreensaver(false);
1297}
1298
1299void
1300CServer::handleSwitchWaitTimeout(const CEvent&, void*)
1301{
1302 // ignore if mouse is locked to screen
1303 if (isLockedToScreen()) {
1304 LOG((CLOG_DEBUG1 "locked to screen"));
1305 stopSwitch();
1306 return;
1307 }
1308
1309 // switch screen
1310 switchScreen(m_switchScreen, m_switchWaitX, m_switchWaitY, false);
1311}
1312
1313void
1314CServer::handleClientDisconnected(const CEvent&, void* vclient)
1315{
1316 // client has disconnected. it might be an old client or an
1317 // active client. we don't care so just handle it both ways.
1318 CBaseClientProxy* client = reinterpret_cast<CBaseClientProxy*>(vclient);
1319 removeActiveClient(client);
1320 removeOldClient(client);
1321 delete client;
1322}
1323
1324void
1325CServer::handleClientCloseTimeout(const CEvent&, void* vclient)
1326{
1327 // client took too long to disconnect. just dump it.
1328 CBaseClientProxy* client = reinterpret_cast<CBaseClientProxy*>(vclient);
1329 LOG((CLOG_NOTE "forced disconnection of client \"%s\"", getName(client).c_str()));
1330 removeOldClient(client);
1331 delete client;
1332}
1333
1334void
1335CServer::handleSwitchToScreenEvent(const CEvent& event, void*)
1336{
1337 CSwitchToScreenInfo* info =
1338 reinterpret_cast<CSwitchToScreenInfo*>(event.getData());
1339
1340 CClientList::const_iterator index = m_clients.find(info->m_screen);
1341 if (index == m_clients.end()) {
1342 LOG((CLOG_DEBUG1 "screen \"%s\" not active", info->m_screen));
1343 }
1344 else {
1345 jumpToScreen(index->second);
1346 }
1347}
1348
1349void
1350CServer::handleSwitchInDirectionEvent(const CEvent& event, void*)
1351{
1352 CSwitchInDirectionInfo* info =
1353 reinterpret_cast<CSwitchInDirectionInfo*>(event.getData());
1354
1355 // jump to screen in chosen direction from center of this screen
1356 SInt32 x = m_x, y = m_y;
1357 CBaseClientProxy* newScreen =
1358 getNeighbor(m_active, info->m_direction, x, y);
1359 if (newScreen == NULL) {
1360 LOG((CLOG_DEBUG1 "no neighbor %s", CConfig::dirName(info->m_direction)));
1361 }
1362 else {
1363 jumpToScreen(newScreen);
1364 }
1365}
1366
1367void
1368CServer::handleLockCursorToScreenEvent(const CEvent& event, void*)
1369{
1370 CLockCursorToScreenInfo* info = (CLockCursorToScreenInfo*)event.getData();
1371
1372 // choose new state
1373 bool newState;
1374 switch (info->m_state) {
1375 case CLockCursorToScreenInfo::kOff:
1376 newState = false;
1377 break;
1378
1379 default:
1380 case CLockCursorToScreenInfo::kOn:
1381 newState = true;
1382 break;
1383
1384 case CLockCursorToScreenInfo::kToggle:
1385 newState = !m_lockedToScreen;
1386 break;
1387 }
1388
1389 // enter new state
1390 if (newState != m_lockedToScreen) {
1391 m_lockedToScreen = newState;
1392 LOG((CLOG_DEBUG "cursor %s current screen", m_lockedToScreen ? "locked to" : "unlocked from"));
1393
1394 m_primaryClient->reconfigure(getActivePrimarySides());
1395 if (!isLockedToScreenServer()) {
1396 stopRelativeMoves();
1397 }
1398 }
1399}
1400
1401void
1402CServer::handleFakeInputBeginEvent(const CEvent&, void*)
1403{
1404 m_primaryClient->fakeInputBegin();
1405}
1406
1407void
1408CServer::handleFakeInputEndEvent(const CEvent&, void*)
1409{
1410 m_primaryClient->fakeInputEnd();
1411}
1412
1413void
1414CServer::onClipboardChanged(CBaseClientProxy* sender,
1415 ClipboardID id, UInt32 seqNum)
1416{
1417 CClipboardInfo& clipboard = m_clipboards[id];
1418
1419 // ignore update if sequence number is old
1420 if (seqNum < clipboard.m_clipboardSeqNum) {
1421 LOG((CLOG_INFO "ignored screen \"%s\" update of clipboard %d (missequenced)", getName(sender).c_str(), id));
1422 return;
1423 }
1424
1425 // should be the expected client
1426 assert(sender == m_clients.find(clipboard.m_clipboardOwner)->second);
1427
1428 // get data
1429 sender->getClipboard(id, &clipboard.m_clipboard);
1430
1431 // ignore if data hasn't changed
1432 CString data = clipboard.m_clipboard.marshall();
1433 if (data == clipboard.m_clipboardData) {
1434 LOG((CLOG_DEBUG "ignored screen \"%s\" update of clipboard %d (unchanged)", clipboard.m_clipboardOwner.c_str(), id));
1435 return;
1436 }
1437
1438 // got new data
1439 LOG((CLOG_INFO "screen \"%s\" updated clipboard %d", clipboard.m_clipboardOwner.c_str(), id));
1440 clipboard.m_clipboardData = data;
1441
1442 // tell all clients except the sender that the clipboard is dirty
1443 for (CClientList::const_iterator index = m_clients.begin();
1444 index != m_clients.end(); ++index) {
1445 CBaseClientProxy* client = index->second;
1446 client->setClipboardDirty(id, client != sender);
1447 }
1448
1449 // send the new clipboard to the active screen
1450 m_active->setClipboard(id, &clipboard.m_clipboard);
1451}
1452
1453void
1454CServer::onScreensaver(bool activated)
1455{
1456 LOG((CLOG_DEBUG "onScreenSaver %s", activated ? "activated" : "deactivated"));
1457
1458 if (activated) {
1459 // save current screen and position
1460 m_activeSaver = m_active;
1461 m_xSaver = m_x;
1462 m_ySaver = m_y;
1463
1464 // jump to primary screen
1465 if (m_active != m_primaryClient) {
1466 switchScreen(m_primaryClient, 0, 0, true);
1467 }
1468 }
1469 else {
1470 // jump back to previous screen and position. we must check
1471 // that the position is still valid since the screen may have
1472 // changed resolutions while the screen saver was running.
1473 if (m_activeSaver != NULL && m_activeSaver != m_primaryClient) {
1474 // check position
1475 CBaseClientProxy* screen = m_activeSaver;
1476 SInt32 x, y, w, h;
1477 screen->getShape(x, y, w, h);
1478 SInt32 zoneSize = getJumpZoneSize(screen);
1479 if (m_xSaver < x + zoneSize) {
1480 m_xSaver = x + zoneSize;
1481 }
1482 else if (m_xSaver >= x + w - zoneSize) {
1483 m_xSaver = x + w - zoneSize - 1;
1484 }
1485 if (m_ySaver < y + zoneSize) {
1486 m_ySaver = y + zoneSize;
1487 }
1488 else if (m_ySaver >= y + h - zoneSize) {
1489 m_ySaver = y + h - zoneSize - 1;
1490 }
1491
1492 // jump
1493 switchScreen(screen, m_xSaver, m_ySaver, false);
1494 }
1495
1496 // reset state
1497 m_activeSaver = NULL;
1498 }
1499
1500 // send message to all clients
1501 for (CClientList::const_iterator index = m_clients.begin();
1502 index != m_clients.end(); ++index) {
1503 CBaseClientProxy* client = index->second;
1504 client->screensaver(activated);
1505 }
1506}
1507
1508void
1509CServer::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button,
1510 const char* screens)
1511{
1512 LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x", id, mask, button));
1513 assert(m_active != NULL);
1514
1515 // relay
1516 if (IKeyState::CKeyInfo::isDefault(screens)) {
1517 m_active->keyDown(id, mask, button);
1518 }
1519 else {
1520 for (CClientList::const_iterator index = m_clients.begin();
1521 index != m_clients.end(); ++index) {
1522 if (IKeyState::CKeyInfo::contains(screens, index->first)) {
1523 index->second->keyDown(id, mask, button);
1524 }
1525 }
1526 }
1527}
1528
1529void
1530CServer::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button,
1531 const char* screens)
1532{
1533 LOG((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button));
1534 assert(m_active != NULL);
1535
1536 // relay
1537 if (IKeyState::CKeyInfo::isDefault(screens)) {
1538 m_active->keyUp(id, mask, button);
1539 }
1540 else {
1541 for (CClientList::const_iterator index = m_clients.begin();
1542 index != m_clients.end(); ++index) {
1543 if (IKeyState::CKeyInfo::contains(screens, index->first)) {
1544 index->second->keyUp(id, mask, button);
1545 }
1546 }
1547 }
1548}
1549
1550void
1551CServer::onKeyRepeat(KeyID id, KeyModifierMask mask,
1552 SInt32 count, KeyButton button)
1553{
1554 LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button));
1555 assert(m_active != NULL);
1556
1557 // relay
1558 m_active->keyRepeat(id, mask, count, button);
1559}
1560
1561void
1562CServer::onMouseDown(ButtonID id)
1563{
1564 LOG((CLOG_DEBUG1 "onMouseDown id=%d", id));
1565 assert(m_active != NULL);
1566
1567 // relay
1568 m_active->mouseDown(id);
1569}
1570
1571void
1572CServer::onMouseUp(ButtonID id)
1573{
1574 LOG((CLOG_DEBUG1 "onMouseUp id=%d", id));
1575 assert(m_active != NULL);
1576
1577 // relay
1578 m_active->mouseUp(id);
1579}
1580
1581bool
1582CServer::onMouseMovePrimary(SInt32 x, SInt32 y)
1583{
1584 LOG((CLOG_DEBUG2 "onMouseMovePrimary %d,%d", x, y));
1585
1586 // mouse move on primary (server's) screen
1587 if (m_active != m_primaryClient) {
1588 // stale event -- we're actually on a secondary screen
1589 return false;
1590 }
1591
1592 // save last delta
1593 m_xDelta2 = m_xDelta;
1594 m_yDelta2 = m_yDelta;
1595
1596 // save current delta
1597 m_xDelta = x - m_x;
1598 m_yDelta = y - m_y;
1599
1600 // save position
1601 m_x = x;
1602 m_y = y;
1603
1604 // get screen shape
1605 SInt32 ax, ay, aw, ah;
1606 m_active->getShape(ax, ay, aw, ah);
1607 SInt32 zoneSize = getJumpZoneSize(m_active);
1608
1609 // clamp position to screen
1610 SInt32 xc = x, yc = y;
1611 if (xc < ax + zoneSize) {
1612 xc = ax;
1613 }
1614 else if (xc >= ax + aw - zoneSize) {
1615 xc = ax + aw - 1;
1616 }
1617 if (yc < ay + zoneSize) {
1618 yc = ay;
1619 }
1620 else if (yc >= ay + ah - zoneSize) {
1621 yc = ay + ah - 1;
1622 }
1623
1624 // see if we should change screens
1625 EDirection dir;
1626 if (x < ax + zoneSize) {
1627 x -= zoneSize;
1628 dir = kLeft;
1629 }
1630 else if (x >= ax + aw - zoneSize) {
1631 x += zoneSize;
1632 dir = kRight;
1633 }
1634 else if (y < ay + zoneSize) {
1635 y -= zoneSize;
1636 dir = kTop;
1637 }
1638 else if (y >= ay + ah - zoneSize) {
1639 y += zoneSize;
1640 dir = kBottom;
1641 }
1642 else {
1643 // still on local screen
1644 noSwitch(x, y);
1645 return false;
1646 }
1647
1648 // get jump destination
1649 CBaseClientProxy* newScreen = mapToNeighbor(m_active, dir, x, y);
1650
1651 // should we switch or not?
1652 if (isSwitchOkay(newScreen, dir, x, y, xc, yc)) {
1653 // switch screen
1654 switchScreen(newScreen, x, y, false);
1655 return true;
1656 }
1657 else {
1658 return false;
1659 }
1660}
1661
1662void
1663CServer::onMouseMoveSecondary(SInt32 dx, SInt32 dy)
1664{
1665 LOG((CLOG_DEBUG2 "onMouseMoveSecondary %+d,%+d", dx, dy));
1666
1667 // mouse move on secondary (client's) screen
1668 assert(m_active != NULL);
1669 if (m_active == m_primaryClient) {
1670 // stale event -- we're actually on the primary screen
1671 return;
1672 }
1673
1674 // if doing relative motion on secondary screens and we're locked
1675 // to the screen (which activates relative moves) then send a
1676 // relative mouse motion. when we're doing this we pretend as if
1677 // the mouse isn't actually moving because we're expecting some
1678 // program on the secondary screen to warp the mouse on us, so we
1679 // have no idea where it really is.
1680 if (m_relativeMoves && isLockedToScreenServer()) {
1681 LOG((CLOG_DEBUG2 "relative move on %s by %d,%d", getName(m_active).c_str(), dx, dy));
1682 m_active->mouseRelativeMove(dx, dy);
1683 return;
1684 }
1685
1686 // save old position
1687 const SInt32 xOld = m_x;
1688 const SInt32 yOld = m_y;
1689
1690 // save last delta
1691 m_xDelta2 = m_xDelta;
1692 m_yDelta2 = m_yDelta;
1693
1694 // save current delta
1695 m_xDelta = dx;
1696 m_yDelta = dy;
1697
1698 // accumulate motion
1699 m_x += dx;
1700 m_y += dy;
1701
1702 // get screen shape
1703 SInt32 ax, ay, aw, ah;
1704 m_active->getShape(ax, ay, aw, ah);
1705
1706 // find direction of neighbor and get the neighbor
1707 bool jump = true;
1708 CBaseClientProxy* newScreen;
1709 do {
1710 // clamp position to screen
1711 SInt32 xc = m_x, yc = m_y;
1712 if (xc < ax) {
1713 xc = ax;
1714 }
1715 else if (xc >= ax + aw) {
1716 xc = ax + aw - 1;
1717 }
1718 if (yc < ay) {
1719 yc = ay;
1720 }
1721 else if (yc >= ay + ah) {
1722 yc = ay + ah - 1;
1723 }
1724
1725 EDirection dir;
1726 if (m_x < ax) {
1727 dir = kLeft;
1728 }
1729 else if (m_x > ax + aw - 1) {
1730 dir = kRight;
1731 }
1732 else if (m_y < ay) {
1733 dir = kTop;
1734 }
1735 else if (m_y > ay + ah - 1) {
1736 dir = kBottom;
1737 }
1738 else {
1739 // we haven't left the screen
1740 newScreen = m_active;
1741 jump = false;
1742
1743 // if waiting and mouse is not on the border we're waiting
1744 // on then stop waiting. also if it's not on the border
1745 // then arm the double tap.
1746 if (m_switchScreen != NULL) {
1747 bool clearWait;
1748 SInt32 zoneSize = m_primaryClient->getJumpZoneSize();
1749 switch (m_switchDir) {
1750 case kLeft:
1751 clearWait = (m_x >= ax + zoneSize);
1752 break;
1753
1754 case kRight:
1755 clearWait = (m_x <= ax + aw - 1 - zoneSize);
1756 break;
1757
1758 case kTop:
1759 clearWait = (m_y >= ay + zoneSize);
1760 break;
1761
1762 case kBottom:
1763 clearWait = (m_y <= ay + ah - 1 + zoneSize);
1764 break;
1765
1766 default:
1767 clearWait = false;
1768 break;
1769 }
1770 if (clearWait) {
1771 // still on local screen
1772 noSwitch(m_x, m_y);
1773 }
1774 }
1775
1776 // skip rest of block
1777 break;
1778 }
1779
1780 // try to switch screen. get the neighbor.
1781 newScreen = mapToNeighbor(m_active, dir, m_x, m_y);
1782
1783 // see if we should switch
1784 if (!isSwitchOkay(newScreen, dir, m_x, m_y, xc, yc)) {
1785 newScreen = m_active;
1786 jump = false;
1787 }
1788 } while (false);
1789
1790 if (jump) {
1791 // switch screens
1792 switchScreen(newScreen, m_x, m_y, false);
1793 }
1794 else {
1795 // same screen. clamp mouse to edge.
1796 m_x = xOld + dx;
1797 m_y = yOld + dy;
1798 if (m_x < ax) {
1799 m_x = ax;
1800 LOG((CLOG_DEBUG2 "clamp to left of \"%s\"", getName(m_active).c_str()));
1801 }
1802 else if (m_x > ax + aw - 1) {
1803 m_x = ax + aw - 1;
1804 LOG((CLOG_DEBUG2 "clamp to right of \"%s\"", getName(m_active).c_str()));
1805 }
1806 if (m_y < ay) {
1807 m_y = ay;
1808 LOG((CLOG_DEBUG2 "clamp to top of \"%s\"", getName(m_active).c_str()));
1809 }
1810 else if (m_y > ay + ah - 1) {
1811 m_y = ay + ah - 1;
1812 LOG((CLOG_DEBUG2 "clamp to bottom of \"%s\"", getName(m_active).c_str()));
1813 }
1814
1815 // warp cursor if it moved.
1816 if (m_x != xOld || m_y != yOld) {
1817 LOG((CLOG_DEBUG2 "move on %s to %d,%d", getName(m_active).c_str(), m_x, m_y));
1818 m_active->mouseMove(m_x, m_y);
1819 }
1820 }
1821}
1822
1823void
1824CServer::onMouseWheel(SInt32 xDelta, SInt32 yDelta)
1825{
1826 LOG((CLOG_DEBUG1 "onMouseWheel %+d,%+d", xDelta, yDelta));
1827 assert(m_active != NULL);
1828
1829 // relay
1830 m_active->mouseWheel(xDelta, yDelta);
1831}
1832
1833bool
1834CServer::addClient(CBaseClientProxy* client)
1835{
1836 CString name = getName(client);
1837 if (m_clients.count(name) != 0) {
1838 return false;
1839 }
1840
1841 // add event handlers
1842 EVENTQUEUE->adoptHandler(IScreen::getShapeChangedEvent(),
1843 client->getEventTarget(),
1844 new TMethodEventJob<CServer>(this,
1845 &CServer::handleShapeChanged, client));
1846 EVENTQUEUE->adoptHandler(IScreen::getClipboardGrabbedEvent(),
1847 client->getEventTarget(),
1848 new TMethodEventJob<CServer>(this,
1849 &CServer::handleClipboardGrabbed, client));
1850 EVENTQUEUE->adoptHandler(CClientProxy::getClipboardChangedEvent(),
1851 client->getEventTarget(),
1852 new TMethodEventJob<CServer>(this,
1853 &CServer::handleClipboardChanged, client));
1854
1855 // add to list
1856 m_clientSet.insert(client);
1857 m_clients.insert(std::make_pair(name, client));
1858
1859 // initialize client data
1860 SInt32 x, y;
1861 client->getCursorPos(x, y);
1862 client->setJumpCursorPos(x, y);
1863
1864 // tell primary client about the active sides
1865 m_primaryClient->reconfigure(getActivePrimarySides());
1866
1867 return true;
1868}
1869
1870bool
1871CServer::removeClient(CBaseClientProxy* client)
1872{
1873 // return false if not in list
1874 CClientSet::iterator i = m_clientSet.find(client);
1875 if (i == m_clientSet.end()) {
1876 return false;
1877 }
1878
1879 // remove event handlers
1880 EVENTQUEUE->removeHandler(IScreen::getShapeChangedEvent(),
1881 client->getEventTarget());
1882 EVENTQUEUE->removeHandler(IScreen::getClipboardGrabbedEvent(),
1883 client->getEventTarget());
1884 EVENTQUEUE->removeHandler(CClientProxy::getClipboardChangedEvent(),
1885 client->getEventTarget());
1886
1887 // remove from list
1888 m_clients.erase(getName(client));
1889 m_clientSet.erase(i);
1890
1891 return true;
1892}
1893
1894void
1895CServer::closeClient(CBaseClientProxy* client, const char* msg)
1896{
1897 assert(client != m_primaryClient);
1898 assert(msg != NULL);
1899
1900 // send message to client. this message should cause the client
1901 // to disconnect. we add this client to the closed client list
1902 // and install a timer to remove the client if it doesn't respond
1903 // quickly enough. we also remove the client from the active
1904 // client list since we're not going to listen to it anymore.
1905 // note that this method also works on clients that are not in
1906 // the m_clients list. adoptClient() may call us with such a
1907 // client.
1908 LOG((CLOG_NOTE "disconnecting client \"%s\"", getName(client).c_str()));
1909
1910 // send message
1911 // FIXME -- avoid type cast (kinda hard, though)
1912 ((CClientProxy*)client)->close(msg);
1913
1914 // install timer. wait timeout seconds for client to close.
1915 double timeout = 5.0;
1916 CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(timeout, NULL);
1917 EVENTQUEUE->adoptHandler(CEvent::kTimer, timer,
1918 new TMethodEventJob<CServer>(this,
1919 &CServer::handleClientCloseTimeout, client));
1920
1921 // move client to closing list
1922 removeClient(client);
1923 m_oldClients.insert(std::make_pair(client, timer));
1924
1925 // if this client is the active screen then we have to
1926 // jump off of it
1927 forceLeaveClient(client);
1928}
1929
1930void
1931CServer::closeClients(const CConfig& config)
1932{
1933 // collect the clients that are connected but are being dropped
1934 // from the configuration (or who's canonical name is changing).
1935 typedef std::set<CBaseClientProxy*> CRemovedClients;
1936 CRemovedClients removed;
1937 for (CClientList::iterator index = m_clients.begin();
1938 index != m_clients.end(); ++index) {
1939 if (!config.isCanonicalName(index->first)) {
1940 removed.insert(index->second);
1941 }
1942 }
1943
1944 // don't close the primary client
1945 removed.erase(m_primaryClient);
1946
1947 // now close them. we collect the list then close in two steps
1948 // because closeClient() modifies the collection we iterate over.
1949 for (CRemovedClients::iterator index = removed.begin();
1950 index != removed.end(); ++index) {
1951 closeClient(*index, kMsgCClose);
1952 }
1953}
1954
1955void
1956CServer::removeActiveClient(CBaseClientProxy* client)
1957{
1958 if (removeClient(client)) {
1959 forceLeaveClient(client);
1960 EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client);
1961 if (m_clients.size() == 1 && m_oldClients.empty()) {
1962 EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this));
1963 }
1964 }
1965}
1966
1967void
1968CServer::removeOldClient(CBaseClientProxy* client)
1969{
1970 COldClients::iterator i = m_oldClients.find(client);
1971 if (i != m_oldClients.end()) {
1972 EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client);
1973 EVENTQUEUE->removeHandler(CEvent::kTimer, i->second);
1974 EVENTQUEUE->deleteTimer(i->second);
1975 m_oldClients.erase(i);
1976 if (m_clients.size() == 1 && m_oldClients.empty()) {
1977 EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this));
1978 }
1979 }
1980}
1981
1982void
1983CServer::forceLeaveClient(CBaseClientProxy* client)
1984{
1985 CBaseClientProxy* active =
1986 (m_activeSaver != NULL) ? m_activeSaver : m_active;
1987 if (active == client) {
1988 // record new position (center of primary screen)
1989 m_primaryClient->getCursorCenter(m_x, m_y);
1990
1991 // stop waiting to switch to this client
1992 if (active == m_switchScreen) {
1993 stopSwitch();
1994 }
1995
1996 // don't notify active screen since it has probably already
1997 // disconnected.
1998 LOG((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", getName(active).c_str(), getName(m_primaryClient).c_str(), m_x, m_y));
1999
2000 // cut over
2001 m_active = m_primaryClient;
2002
2003 // enter new screen (unless we already have because of the
2004 // screen saver)
2005 if (m_activeSaver == NULL) {
2006 m_primaryClient->enter(m_x, m_y, m_seqNum,
2007 m_primaryClient->getToggleMask(), false);
2008 }
2009 }
2010
2011 // if this screen had the cursor when the screen saver activated
2012 // then we can't switch back to it when the screen saver
2013 // deactivates.
2014 if (m_activeSaver == client) {
2015 m_activeSaver = NULL;
2016 }
2017
2018 // tell primary client about the active sides
2019 m_primaryClient->reconfigure(getActivePrimarySides());
2020}
2021
2022
2023//
2024// CServer::CClipboardInfo
2025//
2026
2027CServer::CClipboardInfo::CClipboardInfo() :
2028 m_clipboard(),
2029 m_clipboardData(),
2030 m_clipboardOwner(),
2031 m_clipboardSeqNum(0)
2032{
2033 // do nothing
2034}
2035
2036
2037//
2038// CServer::CLockCursorToScreenInfo
2039//
2040
2041CServer::CLockCursorToScreenInfo*
2042CServer::CLockCursorToScreenInfo::alloc(State state)
2043{
2044 CLockCursorToScreenInfo* info =
2045 (CLockCursorToScreenInfo*)malloc(sizeof(CLockCursorToScreenInfo));
2046 info->m_state = state;
2047 return info;
2048}
2049
2050
2051//
2052// CServer::CSwitchToScreenInfo
2053//
2054
2055CServer::CSwitchToScreenInfo*
2056CServer::CSwitchToScreenInfo::alloc(const CString& screen)
2057{
2058 CSwitchToScreenInfo* info =
2059 (CSwitchToScreenInfo*)malloc(sizeof(CSwitchToScreenInfo) +
2060 screen.size());
2061 strcpy(info->m_screen, screen.c_str());
2062 return info;
2063}
2064
2065
2066//
2067// CServer::CSwitchInDirectionInfo
2068//
2069
2070CServer::CSwitchInDirectionInfo*
2071CServer::CSwitchInDirectionInfo::alloc(EDirection direction)
2072{
2073 CSwitchInDirectionInfo* info =
2074 (CSwitchInDirectionInfo*)malloc(sizeof(CSwitchInDirectionInfo));
2075 info->m_direction = direction;
2076 return info;
2077}
2078
2079
2080//
2081// CServer::CScreenConnectedInfo
2082//
2083
2084CServer::CScreenConnectedInfo*
2085CServer::CScreenConnectedInfo::alloc(const CString& screen)
2086{
2087 CScreenConnectedInfo* info =
2088 (CScreenConnectedInfo*)malloc(sizeof(CScreenConnectedInfo) +
2089 screen.size());
2090 strcpy(info->m_screen, screen.c_str());
2091 return info;
2092}
Note: See TracBrowser for help on using the repository browser.