1 /*
2 * synergy -- mouse and keyboard sharing utility
3 * Copyright (C) 2012-2016 Symless Ltd.
4 * Copyright (C) 2002 Chris Schoeneman
5 *
6 * This package is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * found in the file LICENSE that should have accompanied this file.
9 *
10 * This package is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "server/Server.h"
20
21 #include "server/ClientProxy.h"
22 #include "server/ClientProxyUnknown.h"
23 #include "server/PrimaryClient.h"
24 #include "server/ClientListener.h"
25 #include "synergy/FileChunk.h"
26 #include "synergy/IPlatformScreen.h"
27 #include "synergy/DropHelper.h"
28 #include "synergy/option_types.h"
29 #include "synergy/protocol_types.h"
30 #include "synergy/XScreen.h"
31 #include "synergy/XSynergy.h"
32 #include "synergy/StreamChunker.h"
33 #include "synergy/KeyState.h"
34 #include "synergy/Screen.h"
35 #include "synergy/PacketStreamFilter.h"
36 #include "net/TCPSocket.h"
37 #include "net/IDataSocket.h"
38 #include "net/IListenSocket.h"
39 #include "net/XSocket.h"
40 #include "mt/Thread.h"
41 #include "arch/Arch.h"
42 #include "base/TMethodJob.h"
43 #include "base/IEventQueue.h"
44 #include "base/Log.h"
45 #include "base/TMethodEventJob.h"
46 #include "common/stdexcept.h"
47 #include "shared/SerialKey.h"
48
49 #include <cstring>
50 #include <cstdlib>
51 #include <sstream>
52 #include <fstream>
53 #include <ctime>
54 #include <climits>
55
56 //
57 // Server
58 //
59
Server(Config & config,PrimaryClient * primaryClient,synergy::Screen * screen,IEventQueue * events,lib::synergy::ServerArgs const & args)60 Server::Server(
61 Config& config,
62 PrimaryClient* primaryClient,
63 synergy::Screen* screen,
64 IEventQueue* events,
65 lib::synergy::ServerArgs const& args) :
66 m_mock(false),
67 m_primaryClient(primaryClient),
68 m_active(primaryClient),
69 m_seqNum(0),
70 m_xDelta(0),
71 m_yDelta(0),
72 m_xDelta2(0),
73 m_yDelta2(0),
74 m_config(&config),
75 m_inputFilter(config.getInputFilter()),
76 m_activeSaver(NULL),
77 m_switchDir(kNoDirection),
78 m_switchScreen(NULL),
79 m_switchWaitDelay(0.0),
80 m_switchWaitTimer(NULL),
81 m_switchTwoTapDelay(0.0),
82 m_switchTwoTapEngaged(false),
83 m_switchTwoTapArmed(false),
84 m_switchTwoTapZone(3),
85 m_switchNeedsShift(false),
86 m_switchNeedsControl(false),
87 m_switchNeedsAlt(false),
88 m_relativeMoves(false),
89 m_keyboardBroadcasting(false),
90 m_lockedToScreen(false),
91 m_screen(screen),
92 m_events(events),
93 m_sendFileThread(NULL),
94 m_writeToDropDirThread(NULL),
95 m_ignoreFileTransfer(false),
96 m_disableLockToScreen(false),
97 m_enableClipboard(true),
98 m_maximumClipboardSize(INT_MAX),
99 m_sendDragInfoThread(NULL),
100 m_waitDragInfoThread(true),
101 m_args(args)
102 {
103 // must have a primary client and it must have a canonical name
104 assert(m_primaryClient != NULL);
105 assert(config.isScreen(primaryClient->getName()));
106 assert(m_screen != NULL);
107
108 String primaryName = getName(primaryClient);
109
110 // clear clipboards
111 for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
112 ClipboardInfo& clipboard = m_clipboards[id];
113 clipboard.m_clipboardOwner = primaryName;
114 clipboard.m_clipboardSeqNum = m_seqNum;
115 if (clipboard.m_clipboard.open(0)) {
116 clipboard.m_clipboard.empty();
117 clipboard.m_clipboard.close();
118 }
119 clipboard.m_clipboardData = clipboard.m_clipboard.marshall();
120 }
121
122 // install event handlers
123 m_events->adoptHandler(Event::kTimer, this,
124 new TMethodEventJob<Server>(this,
125 &Server::handleSwitchWaitTimeout));
126 m_events->adoptHandler(m_events->forIKeyState().keyDown(),
127 m_inputFilter,
128 new TMethodEventJob<Server>(this,
129 &Server::handleKeyDownEvent));
130 m_events->adoptHandler(m_events->forIKeyState().keyUp(),
131 m_inputFilter,
132 new TMethodEventJob<Server>(this,
133 &Server::handleKeyUpEvent));
134 m_events->adoptHandler(m_events->forIKeyState().keyRepeat(),
135 m_inputFilter,
136 new TMethodEventJob<Server>(this,
137 &Server::handleKeyRepeatEvent));
138 m_events->adoptHandler(m_events->forIPrimaryScreen().buttonDown(),
139 m_inputFilter,
140 new TMethodEventJob<Server>(this,
141 &Server::handleButtonDownEvent));
142 m_events->adoptHandler(m_events->forIPrimaryScreen().buttonUp(),
143 m_inputFilter,
144 new TMethodEventJob<Server>(this,
145 &Server::handleButtonUpEvent));
146 m_events->adoptHandler(m_events->forIPrimaryScreen().motionOnPrimary(),
147 m_primaryClient->getEventTarget(),
148 new TMethodEventJob<Server>(this,
149 &Server::handleMotionPrimaryEvent));
150 m_events->adoptHandler(m_events->forIPrimaryScreen().motionOnSecondary(),
151 m_primaryClient->getEventTarget(),
152 new TMethodEventJob<Server>(this,
153 &Server::handleMotionSecondaryEvent));
154 m_events->adoptHandler(m_events->forIPrimaryScreen().wheel(),
155 m_primaryClient->getEventTarget(),
156 new TMethodEventJob<Server>(this,
157 &Server::handleWheelEvent));
158 m_events->adoptHandler(m_events->forIPrimaryScreen().screensaverActivated(),
159 m_primaryClient->getEventTarget(),
160 new TMethodEventJob<Server>(this,
161 &Server::handleScreensaverActivatedEvent));
162 m_events->adoptHandler(m_events->forIPrimaryScreen().screensaverDeactivated(),
163 m_primaryClient->getEventTarget(),
164 new TMethodEventJob<Server>(this,
165 &Server::handleScreensaverDeactivatedEvent));
166 m_events->adoptHandler(m_events->forServer().switchToScreen(),
167 m_inputFilter,
168 new TMethodEventJob<Server>(this,
169 &Server::handleSwitchToScreenEvent));
170 m_events->adoptHandler(m_events->forServer().switchInDirection(),
171 m_inputFilter,
172 new TMethodEventJob<Server>(this,
173 &Server::handleSwitchInDirectionEvent));
174 m_events->adoptHandler(m_events->forServer().keyboardBroadcast(),
175 m_inputFilter,
176 new TMethodEventJob<Server>(this,
177 &Server::handleKeyboardBroadcastEvent));
178 m_events->adoptHandler(m_events->forServer().lockCursorToScreen(),
179 m_inputFilter,
180 new TMethodEventJob<Server>(this,
181 &Server::handleLockCursorToScreenEvent));
182 m_events->adoptHandler(m_events->forIPrimaryScreen().fakeInputBegin(),
183 m_inputFilter,
184 new TMethodEventJob<Server>(this,
185 &Server::handleFakeInputBeginEvent));
186 m_events->adoptHandler(m_events->forIPrimaryScreen().fakeInputEnd(),
187 m_inputFilter,
188 new TMethodEventJob<Server>(this,
189 &Server::handleFakeInputEndEvent));
190
191 if (m_args.m_enableDragDrop) {
192 m_events->adoptHandler(m_events->forFile().fileChunkSending(),
193 this,
194 new TMethodEventJob<Server>(this,
195 &Server::handleFileChunkSendingEvent));
196 m_events->adoptHandler(m_events->forFile().fileRecieveCompleted(),
197 this,
198 new TMethodEventJob<Server>(this,
199 &Server::handleFileRecieveCompletedEvent));
200 }
201
202 // add connection
203 addClient(m_primaryClient);
204
205 // set initial configuration
206 setConfig(config);
207
208 // enable primary client
209 m_primaryClient->enable();
210 m_inputFilter->setPrimaryClient(m_primaryClient);
211
212 // Determine if scroll lock is already set. If so, lock the cursor to the primary screen
213 if (m_primaryClient->getToggleMask() & KeyModifierScrollLock) {
214 LOG((CLOG_NOTE "Scroll Lock is on, locking cursor to screen"));
215 m_lockedToScreen = true;
216 }
217
218 }
219
~Server()220 Server::~Server()
221 {
222 if (m_mock) {
223 return;
224 }
225
226 // remove event handlers and timers
227 m_events->removeHandler(m_events->forIKeyState().keyDown(),
228 m_inputFilter);
229 m_events->removeHandler(m_events->forIKeyState().keyUp(),
230 m_inputFilter);
231 m_events->removeHandler(m_events->forIKeyState().keyRepeat(),
232 m_inputFilter);
233 m_events->removeHandler(m_events->forIPrimaryScreen().buttonDown(),
234 m_inputFilter);
235 m_events->removeHandler(m_events->forIPrimaryScreen().buttonUp(),
236 m_inputFilter);
237 m_events->removeHandler(m_events->forIPrimaryScreen().motionOnPrimary(),
238 m_primaryClient->getEventTarget());
239 m_events->removeHandler(m_events->forIPrimaryScreen().motionOnSecondary(),
240 m_primaryClient->getEventTarget());
241 m_events->removeHandler(m_events->forIPrimaryScreen().wheel(),
242 m_primaryClient->getEventTarget());
243 m_events->removeHandler(m_events->forIPrimaryScreen().screensaverActivated(),
244 m_primaryClient->getEventTarget());
245 m_events->removeHandler(m_events->forIPrimaryScreen().screensaverDeactivated(),
246 m_primaryClient->getEventTarget());
247 m_events->removeHandler(m_events->forIPrimaryScreen().fakeInputBegin(),
248 m_inputFilter);
249 m_events->removeHandler(m_events->forIPrimaryScreen().fakeInputEnd(),
250 m_inputFilter);
251 m_events->removeHandler(Event::kTimer, this);
252 stopSwitch();
253
254 // force immediate disconnection of secondary clients
255 disconnect();
256 for (OldClients::iterator index = m_oldClients.begin();
257 index != m_oldClients.end(); ++index) {
258 BaseClientProxy* client = index->first;
259 m_events->deleteTimer(index->second);
260 m_events->removeHandler(Event::kTimer, client);
261 m_events->removeHandler(m_events->forClientProxy().disconnected(), client);
262 delete client;
263 }
264
265 // remove input filter
266 m_inputFilter->setPrimaryClient(NULL);
267
268 // disable and disconnect primary client
269 m_primaryClient->disable();
270 removeClient(m_primaryClient);
271 }
272
273 bool
setConfig(const Config & config)274 Server::setConfig(const Config& config)
275 {
276 // refuse configuration if it doesn't include the primary screen
277 if (!config.isScreen(m_primaryClient->getName())) {
278 return false;
279 }
280
281 // close clients that are connected but being dropped from the
282 // configuration.
283 closeClients(config);
284
285 // cut over
286 processOptions();
287
288 // add ScrollLock as a hotkey to lock to the screen. this was a
289 // built-in feature in earlier releases and is now supported via
290 // the user configurable hotkey mechanism. if the user has already
291 // registered ScrollLock for something else then that will win but
292 // we will unfortunately generate a warning. if the user has
293 // configured a LockCursorToScreenAction then we don't add
294 // ScrollLock as a hotkey.
295 if (!m_disableLockToScreen && !m_config->hasLockToScreenAction()) {
296 IPlatformScreen::KeyInfo* key =
297 IPlatformScreen::KeyInfo::alloc(kKeyScrollLock, 0, 0, 0);
298 InputFilter::Rule rule(new InputFilter::KeystrokeCondition(m_events, key));
299 rule.adoptAction(new InputFilter::LockCursorToScreenAction(m_events), true);
300 m_inputFilter->addFilterRule(rule);
301 }
302
303 // tell primary screen about reconfiguration
304 m_primaryClient->reconfigure(getActivePrimarySides());
305
306 // tell all (connected) clients about current options
307 for (ClientList::const_iterator index = m_clients.begin();
308 index != m_clients.end(); ++index) {
309 BaseClientProxy* client = index->second;
310 sendOptions(client);
311 }
312
313 return true;
314 }
315
316 void
adoptClient(BaseClientProxy * client)317 Server::adoptClient(BaseClientProxy* client)
318 {
319 assert(client != NULL);
320
321 // watch for client disconnection
322 m_events->adoptHandler(m_events->forClientProxy().disconnected(), client,
323 new TMethodEventJob<Server>(this,
324 &Server::handleClientDisconnected, client));
325
326 // name must be in our configuration
327 if (!m_config->isScreen(client->getName())) {
328 LOG((CLOG_WARN "unrecognised client name \"%s\", check server config", client->getName().c_str()));
329 closeClient(client, kMsgEUnknown);
330 return;
331 }
332
333 // add client to client list
334 if (!addClient(client)) {
335 // can only have one screen with a given name at any given time
336 LOG((CLOG_WARN "a client with name \"%s\" is already connected", getName(client).c_str()));
337 closeClient(client, kMsgEBusy);
338 return;
339 }
340 LOG((CLOG_NOTE "client \"%s\" has connected", getName(client).c_str()));
341
342 // send configuration options to client
343 sendOptions(client);
344
345 // activate screen saver on new client if active on the primary screen
346 if (m_activeSaver != NULL) {
347 client->screensaver(true);
348 }
349
350 // send notification
351 Server::ScreenConnectedInfo* info =
352 new Server::ScreenConnectedInfo(getName(client));
353 m_events->addEvent(Event(m_events->forServer().connected(),
354 m_primaryClient->getEventTarget(), info));
355 }
356
357 void
disconnect()358 Server::disconnect()
359 {
360 // close all secondary clients
361 if (m_clients.size() > 1 || !m_oldClients.empty()) {
362 Config emptyConfig(m_events);
363 closeClients(emptyConfig);
364 }
365 else {
366 m_events->addEvent(Event(m_events->forServer().disconnected(), this));
367 }
368 }
369
370 UInt32
getNumClients() const371 Server::getNumClients() const
372 {
373 return (SInt32)m_clients.size();
374 }
375
376 void
getClients(std::vector<String> & list) const377 Server::getClients(std::vector<String>& list) const
378 {
379 list.clear();
380 for (ClientList::const_iterator index = m_clients.begin();
381 index != m_clients.end(); ++index) {
382 list.push_back(index->first);
383 }
384 }
385
386 String
getName(const BaseClientProxy * client) const387 Server::getName(const BaseClientProxy* client) const
388 {
389 String name = m_config->getCanonicalName(client->getName());
390 if (name.empty()) {
391 name = client->getName();
392 }
393 return name;
394 }
395
396 UInt32
getActivePrimarySides() const397 Server::getActivePrimarySides() const
398 {
399 UInt32 sides = 0;
400 if (!isLockedToScreenServer()) {
401 if (hasAnyNeighbor(m_primaryClient, kLeft)) {
402 sides |= kLeftMask;
403 }
404 if (hasAnyNeighbor(m_primaryClient, kRight)) {
405 sides |= kRightMask;
406 }
407 if (hasAnyNeighbor(m_primaryClient, kTop)) {
408 sides |= kTopMask;
409 }
410 if (hasAnyNeighbor(m_primaryClient, kBottom)) {
411 sides |= kBottomMask;
412 }
413 }
414 return sides;
415 }
416
417 bool
isLockedToScreenServer() const418 Server::isLockedToScreenServer() const
419 {
420 // locked if scroll-lock is toggled on
421 return m_lockedToScreen;
422 }
423
424 bool
isLockedToScreen() const425 Server::isLockedToScreen() const
426 {
427 if (m_disableLockToScreen) {
428 return false;
429 }
430
431 // locked if we say we're locked
432 if (isLockedToScreenServer()) {
433 LOG((CLOG_NOTE "Cursor is locked to screen, check Scroll Lock key"));
434 return true;
435 }
436
437 // locked if primary says we're locked
438 if (m_primaryClient->isLockedToScreen()) {
439 return true;
440 }
441
442 // not locked
443 return false;
444 }
445
446 SInt32
getJumpZoneSize(BaseClientProxy * client) const447 Server::getJumpZoneSize(BaseClientProxy* client) const
448 {
449 if (client == m_primaryClient) {
450 return m_primaryClient->getJumpZoneSize();
451 }
452 else {
453 return 0;
454 }
455 }
456
457 void
switchScreen(BaseClientProxy * dst,SInt32 x,SInt32 y,bool forScreensaver)458 Server::switchScreen(BaseClientProxy* dst,
459 SInt32 x, SInt32 y, bool forScreensaver)
460 {
461 assert(dst != NULL);
462
463 // if trial is expired, exit the process
464 if (m_args.m_serial.isExpired(std::time(0))) {
465 LOG((CLOG_ERR "trial has expired, aborting server"));
466 exit(kExitSuccess);
467 }
468
469 #ifndef NDEBUG
470 {
471 SInt32 dx, dy, dw, dh;
472 dst->getShape(dx, dy, dw, dh);
473 assert(x >= dx && y >= dy && x < dx + dw && y < dy + dh);
474 }
475 #endif
476 assert(m_active != NULL);
477
478 LOG((CLOG_INFO "switch from \"%s\" to \"%s\" at %d,%d", getName(m_active).c_str(), getName(dst).c_str(), x, y));
479
480 // stop waiting to switch
481 stopSwitch();
482
483 // record new position
484 m_x = x;
485 m_y = y;
486 m_xDelta = 0;
487 m_yDelta = 0;
488 m_xDelta2 = 0;
489 m_yDelta2 = 0;
490
491 // wrapping means leaving the active screen and entering it again.
492 // since that's a waste of time we skip that and just warp the
493 // mouse.
494 if (m_active != dst) {
495 // leave active screen
496 if (!m_active->leave()) {
497 // cannot leave screen
498 LOG((CLOG_WARN "can't leave screen"));
499 return;
500 }
501
502 // update the primary client's clipboards if we're leaving the
503 // primary screen.
504 if (m_active == m_primaryClient && m_enableClipboard) {
505 for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
506 ClipboardInfo& clipboard = m_clipboards[id];
507 if (clipboard.m_clipboardOwner == getName(m_primaryClient)) {
508 onClipboardChanged(m_primaryClient,
509 id, clipboard.m_clipboardSeqNum);
510 }
511 }
512 }
513
514 // cut over
515 m_active = dst;
516
517 // increment enter sequence number
518 ++m_seqNum;
519
520 // enter new screen
521 m_active->enter(x, y, m_seqNum,
522 m_primaryClient->getToggleMask(),
523 forScreensaver);
524
525 if (m_enableClipboard) {
526 // send the clipboard data to new active screen
527 for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
528 // Hackity hackity hack
529 if (m_clipboards[id].m_clipboard.marshall().size() > (m_maximumClipboardSize * 1024)) {
530 continue;
531 }
532 m_active->setClipboard(id, &m_clipboards[id].m_clipboard);
533 }
534 }
535
536 Server::SwitchToScreenInfo* info =
537 Server::SwitchToScreenInfo::alloc(m_active->getName());
538 m_events->addEvent(Event(m_events->forServer().screenSwitched(), this, info));
539 }
540 else {
541 m_active->mouseMove(x, y);
542 }
543 }
544
545 void
jumpToScreen(BaseClientProxy * newScreen)546 Server::jumpToScreen(BaseClientProxy* newScreen)
547 {
548 assert(newScreen != NULL);
549
550 // record the current cursor position on the active screen
551 m_active->setJumpCursorPos(m_x, m_y);
552
553 // get the last cursor position on the target screen
554 SInt32 x, y;
555 newScreen->getJumpCursorPos(x, y);
556
557 switchScreen(newScreen, x, y, false);
558 }
559
560 float
mapToFraction(BaseClientProxy * client,EDirection dir,SInt32 x,SInt32 y) const561 Server::mapToFraction(BaseClientProxy* client,
562 EDirection dir, SInt32 x, SInt32 y) const
563 {
564 SInt32 sx, sy, sw, sh;
565 client->getShape(sx, sy, sw, sh);
566 switch (dir) {
567 case kLeft:
568 case kRight:
569 return static_cast<float>(y - sy + 0.5f) / static_cast<float>(sh);
570
571 case kTop:
572 case kBottom:
573 return static_cast<float>(x - sx + 0.5f) / static_cast<float>(sw);
574
575 case kNoDirection:
576 assert(0 && "bad direction");
577 break;
578 }
579 return 0.0f;
580 }
581
582 void
mapToPixel(BaseClientProxy * client,EDirection dir,float f,SInt32 & x,SInt32 & y) const583 Server::mapToPixel(BaseClientProxy* client,
584 EDirection dir, float f, SInt32& x, SInt32& y) const
585 {
586 SInt32 sx, sy, sw, sh;
587 client->getShape(sx, sy, sw, sh);
588 switch (dir) {
589 case kLeft:
590 case kRight:
591 y = static_cast<SInt32>(f * sh) + sy;
592 break;
593
594 case kTop:
595 case kBottom:
596 x = static_cast<SInt32>(f * sw) + sx;
597 break;
598
599 case kNoDirection:
600 assert(0 && "bad direction");
601 break;
602 }
603 }
604
605 bool
hasAnyNeighbor(BaseClientProxy * client,EDirection dir) const606 Server::hasAnyNeighbor(BaseClientProxy* client, EDirection dir) const
607 {
608 assert(client != NULL);
609
610 return m_config->hasNeighbor(getName(client), dir);
611 }
612
613 BaseClientProxy*
getNeighbor(BaseClientProxy * src,EDirection dir,SInt32 & x,SInt32 & y) const614 Server::getNeighbor(BaseClientProxy* src,
615 EDirection dir, SInt32& x, SInt32& y) const
616 {
617 // note -- must be locked on entry
618
619 assert(src != NULL);
620
621 // get source screen name
622 String srcName = getName(src);
623 assert(!srcName.empty());
624 LOG((CLOG_DEBUG2 "find neighbor on %s of \"%s\"", Config::dirName(dir), srcName.c_str()));
625
626 // convert position to fraction
627 float t = mapToFraction(src, dir, x, y);
628
629 // search for the closest neighbor that exists in direction dir
630 float tTmp;
631 for (;;) {
632 String dstName(m_config->getNeighbor(srcName, dir, t, &tTmp));
633
634 // if nothing in that direction then return NULL. if the
635 // destination is the source then we can make no more
636 // progress in this direction. since we haven't found a
637 // connected neighbor we return NULL.
638 if (dstName.empty()) {
639 LOG((CLOG_DEBUG2 "no neighbor on %s of \"%s\"", Config::dirName(dir), srcName.c_str()));
640 return NULL;
641 }
642
643 // look up neighbor cell. if the screen is connected and
644 // ready then we can stop.
645 ClientList::const_iterator index = m_clients.find(dstName);
646 if (index != m_clients.end()) {
647 LOG((CLOG_DEBUG2 "\"%s\" is on %s of \"%s\" at %f", dstName.c_str(), Config::dirName(dir), srcName.c_str(), t));
648 mapToPixel(index->second, dir, tTmp, x, y);
649 return index->second;
650 }
651
652 // skip over unconnected screen
653 LOG((CLOG_DEBUG2 "ignored \"%s\" on %s of \"%s\"", dstName.c_str(), Config::dirName(dir), srcName.c_str()));
654 srcName = dstName;
655
656 // use position on skipped screen
657 t = tTmp;
658 }
659 }
660
661 BaseClientProxy*
mapToNeighbor(BaseClientProxy * src,EDirection srcSide,SInt32 & x,SInt32 & y) const662 Server::mapToNeighbor(BaseClientProxy* src,
663 EDirection srcSide, SInt32& x, SInt32& y) const
664 {
665 // note -- must be locked on entry
666
667 assert(src != NULL);
668
669 // get the first neighbor
670 BaseClientProxy* dst = getNeighbor(src, srcSide, x, y);
671 if (dst == NULL) {
672 return NULL;
673 }
674
675 // get the source screen's size
676 SInt32 dx, dy, dw, dh;
677 BaseClientProxy* lastGoodScreen = src;
678 lastGoodScreen->getShape(dx, dy, dw, dh);
679
680 // find destination screen, adjusting x or y (but not both). the
681 // searches are done in a sort of canonical screen space where
682 // the upper-left corner is 0,0 for each screen. we adjust from
683 // actual to canonical position on entry to and from canonical to
684 // actual on exit from the search.
685 switch (srcSide) {
686 case kLeft:
687 x -= dx;
688 while (dst != NULL) {
689 lastGoodScreen = dst;
690 lastGoodScreen->getShape(dx, dy, dw, dh);
691 x += dw;
692 if (x >= 0) {
693 break;
694 }
695 LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
696 dst = getNeighbor(lastGoodScreen, srcSide, x, y);
697 }
698 assert(lastGoodScreen != NULL);
699 x += dx;
700 break;
701
702 case kRight:
703 x -= dx;
704 while (dst != NULL) {
705 x -= dw;
706 lastGoodScreen = dst;
707 lastGoodScreen->getShape(dx, dy, dw, dh);
708 if (x < dw) {
709 break;
710 }
711 LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
712 dst = getNeighbor(lastGoodScreen, srcSide, x, y);
713 }
714 assert(lastGoodScreen != NULL);
715 x += dx;
716 break;
717
718 case kTop:
719 y -= dy;
720 while (dst != NULL) {
721 lastGoodScreen = dst;
722 lastGoodScreen->getShape(dx, dy, dw, dh);
723 y += dh;
724 if (y >= 0) {
725 break;
726 }
727 LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
728 dst = getNeighbor(lastGoodScreen, srcSide, x, y);
729 }
730 assert(lastGoodScreen != NULL);
731 y += dy;
732 break;
733
734 case kBottom:
735 y -= dy;
736 while (dst != NULL) {
737 y -= dh;
738 lastGoodScreen = dst;
739 lastGoodScreen->getShape(dx, dy, dw, dh);
740 if (y < dh) {
741 break;
742 }
743 LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str()));
744 dst = getNeighbor(lastGoodScreen, srcSide, x, y);
745 }
746 assert(lastGoodScreen != NULL);
747 y += dy;
748 break;
749
750 case kNoDirection:
751 assert(0 && "bad direction");
752 return NULL;
753 }
754
755 // save destination screen
756 assert(lastGoodScreen != NULL);
757 dst = lastGoodScreen;
758
759 // if entering primary screen then be sure to move in far enough
760 // to avoid the jump zone. if entering a side that doesn't have
761 // a neighbor (i.e. an asymmetrical side) then we don't need to
762 // move inwards because that side can't provoke a jump.
763 avoidJumpZone(dst, srcSide, x, y);
764
765 return dst;
766 }
767
768 void
avoidJumpZone(BaseClientProxy * dst,EDirection dir,SInt32 & x,SInt32 & y) const769 Server::avoidJumpZone(BaseClientProxy* dst,
770 EDirection dir, SInt32& x, SInt32& y) const
771 {
772 // we only need to avoid jump zones on the primary screen
773 if (dst != m_primaryClient) {
774 return;
775 }
776
777 const String dstName(getName(dst));
778 SInt32 dx, dy, dw, dh;
779 dst->getShape(dx, dy, dw, dh);
780 float t = mapToFraction(dst, dir, x, y);
781 SInt32 z = getJumpZoneSize(dst);
782
783 // move in far enough to avoid the jump zone. if entering a side
784 // that doesn't have a neighbor (i.e. an asymmetrical side) then we
785 // don't need to move inwards because that side can't provoke a jump.
786 switch (dir) {
787 case kLeft:
788 if (!m_config->getNeighbor(dstName, kRight, t, NULL).empty() &&
789 x > dx + dw - 1 - z)
790 x = dx + dw - 1 - z;
791 break;
792
793 case kRight:
794 if (!m_config->getNeighbor(dstName, kLeft, t, NULL).empty() &&
795 x < dx + z)
796 x = dx + z;
797 break;
798
799 case kTop:
800 if (!m_config->getNeighbor(dstName, kBottom, t, NULL).empty() &&
801 y > dy + dh - 1 - z)
802 y = dy + dh - 1 - z;
803 break;
804
805 case kBottom:
806 if (!m_config->getNeighbor(dstName, kTop, t, NULL).empty() &&
807 y < dy + z)
808 y = dy + z;
809 break;
810
811 case kNoDirection:
812 assert(0 && "bad direction");
813 }
814 }
815
816 bool
isSwitchOkay(BaseClientProxy * newScreen,EDirection dir,SInt32 x,SInt32 y,SInt32 xActive,SInt32 yActive)817 Server::isSwitchOkay(BaseClientProxy* newScreen,
818 EDirection dir, SInt32 x, SInt32 y,
819 SInt32 xActive, SInt32 yActive)
820 {
821 LOG((CLOG_DEBUG1 "try to leave \"%s\" on %s", getName(m_active).c_str(), Config::dirName(dir)));
822
823 // is there a neighbor?
824 if (newScreen == NULL) {
825 // there's no neighbor. we don't want to switch and we don't
826 // want to try to switch later.
827 LOG((CLOG_DEBUG1 "no neighbor %s", Config::dirName(dir)));
828 stopSwitch();
829 return false;
830 }
831
832 // should we switch or not?
833 bool preventSwitch = false;
834 bool allowSwitch = false;
835
836 // note if the switch direction has changed. save the new
837 // direction and screen if so.
838 bool isNewDirection = (dir != m_switchDir);
839 if (isNewDirection || m_switchScreen == NULL) {
840 m_switchDir = dir;
841 m_switchScreen = newScreen;
842 }
843
844 // is this a double tap and do we care?
845 if (!allowSwitch && m_switchTwoTapDelay > 0.0) {
846 if (isNewDirection ||
847 !isSwitchTwoTapStarted() || !shouldSwitchTwoTap()) {
848 // tapping a different or new edge or second tap not
849 // fast enough. prepare for second tap.
850 preventSwitch = true;
851 startSwitchTwoTap();
852 }
853 else {
854 // got second tap
855 allowSwitch = true;
856 }
857 }
858
859 // if waiting before a switch then prepare to switch later
860 if (!allowSwitch && m_switchWaitDelay > 0.0) {
861 if (isNewDirection || !isSwitchWaitStarted()) {
862 startSwitchWait(x, y);
863 }
864 preventSwitch = true;
865 }
866
867 // are we in a locked corner? first check if screen has the option set
868 // and, if not, check the global options.
869 const Config::ScreenOptions* options =
870 m_config->getOptions(getName(m_active));
871 if (options == NULL || options->count(kOptionScreenSwitchCorners) == 0) {
872 options = m_config->getOptions("");
873 }
874 if (options != NULL && options->count(kOptionScreenSwitchCorners) > 0) {
875 // get corner mask and size
876 Config::ScreenOptions::const_iterator i =
877 options->find(kOptionScreenSwitchCorners);
878 UInt32 corners = static_cast<UInt32>(i->second);
879 i = options->find(kOptionScreenSwitchCornerSize);
880 SInt32 size = 0;
881 if (i != options->end()) {
882 size = i->second;
883 }
884
885 // see if we're in a locked corner
886 if ((getCorner(m_active, xActive, yActive, size) & corners) != 0) {
887 // yep, no switching
888 LOG((CLOG_DEBUG1 "locked in corner"));
889 preventSwitch = true;
890 stopSwitch();
891 }
892 }
893
894 // ignore if mouse is locked to screen and don't try to switch later
895 if (!preventSwitch && isLockedToScreen()) {
896 LOG((CLOG_DEBUG1 "locked to screen"));
897 preventSwitch = true;
898 stopSwitch();
899 }
900
901 // check for optional needed modifiers
902 KeyModifierMask mods = this->m_primaryClient->getToggleMask();
903
904 if (!preventSwitch && (
905 (this->m_switchNeedsShift && ((mods & KeyModifierShift) != KeyModifierShift)) ||
906 (this->m_switchNeedsControl && ((mods & KeyModifierControl) != KeyModifierControl)) ||
907 (this->m_switchNeedsAlt && ((mods & KeyModifierAlt) != KeyModifierAlt))
908 )) {
909 LOG((CLOG_DEBUG1 "need modifiers to switch"));
910 preventSwitch = true;
911 stopSwitch();
912 }
913
914 return !preventSwitch;
915 }
916
917 void
noSwitch(SInt32 x,SInt32 y)918 Server::noSwitch(SInt32 x, SInt32 y)
919 {
920 armSwitchTwoTap(x, y);
921 stopSwitchWait();
922 }
923
924 void
stopSwitch()925 Server::stopSwitch()
926 {
927 if (m_switchScreen != NULL) {
928 m_switchScreen = NULL;
929 m_switchDir = kNoDirection;
930 stopSwitchTwoTap();
931 stopSwitchWait();
932 }
933 }
934
935 void
startSwitchTwoTap()936 Server::startSwitchTwoTap()
937 {
938 m_switchTwoTapEngaged = true;
939 m_switchTwoTapArmed = false;
940 m_switchTwoTapTimer.reset();
941 LOG((CLOG_DEBUG1 "waiting for second tap"));
942 }
943
944 void
armSwitchTwoTap(SInt32 x,SInt32 y)945 Server::armSwitchTwoTap(SInt32 x, SInt32 y)
946 {
947 if (m_switchTwoTapEngaged) {
948 if (m_switchTwoTapTimer.getTime() > m_switchTwoTapDelay) {
949 // second tap took too long. disengage.
950 stopSwitchTwoTap();
951 }
952 else if (!m_switchTwoTapArmed) {
953 // still time for a double tap. see if we left the tap
954 // zone and, if so, arm the two tap.
955 SInt32 ax, ay, aw, ah;
956 m_active->getShape(ax, ay, aw, ah);
957 SInt32 tapZone = m_primaryClient->getJumpZoneSize();
958 if (tapZone < m_switchTwoTapZone) {
959 tapZone = m_switchTwoTapZone;
960 }
961 if (x >= ax + tapZone && x < ax + aw - tapZone &&
962 y >= ay + tapZone && y < ay + ah - tapZone) {
963 // win32 can generate bogus mouse events that appear to
964 // move in the opposite direction that the mouse actually
965 // moved. try to ignore that crap here.
966 switch (m_switchDir) {
967 case kLeft:
968 m_switchTwoTapArmed = (m_xDelta > 0 && m_xDelta2 > 0);
969 break;
970
971 case kRight:
972 m_switchTwoTapArmed = (m_xDelta < 0 && m_xDelta2 < 0);
973 break;
974
975 case kTop:
976 m_switchTwoTapArmed = (m_yDelta > 0 && m_yDelta2 > 0);
977 break;
978
979 case kBottom:
980 m_switchTwoTapArmed = (m_yDelta < 0 && m_yDelta2 < 0);
981 break;
982
983 default:
984 break;
985 }
986 }
987 }
988 }
989 }
990
991 void
stopSwitchTwoTap()992 Server::stopSwitchTwoTap()
993 {
994 m_switchTwoTapEngaged = false;
995 m_switchTwoTapArmed = false;
996 }
997
998 bool
isSwitchTwoTapStarted() const999 Server::isSwitchTwoTapStarted() const
1000 {
1001 return m_switchTwoTapEngaged;
1002 }
1003
1004 bool
shouldSwitchTwoTap() const1005 Server::shouldSwitchTwoTap() const
1006 {
1007 // this is the second tap if two-tap is armed and this tap
1008 // came fast enough
1009 return (m_switchTwoTapArmed &&
1010 m_switchTwoTapTimer.getTime() <= m_switchTwoTapDelay);
1011 }
1012
1013 void
startSwitchWait(SInt32 x,SInt32 y)1014 Server::startSwitchWait(SInt32 x, SInt32 y)
1015 {
1016 stopSwitchWait();
1017 m_switchWaitX = x;
1018 m_switchWaitY = y;
1019 m_switchWaitTimer = m_events->newOneShotTimer(m_switchWaitDelay, this);
1020 LOG((CLOG_DEBUG1 "waiting to switch"));
1021 }
1022
1023 void
stopSwitchWait()1024 Server::stopSwitchWait()
1025 {
1026 if (m_switchWaitTimer != NULL) {
1027 m_events->deleteTimer(m_switchWaitTimer);
1028 m_switchWaitTimer = NULL;
1029 }
1030 }
1031
1032 bool
isSwitchWaitStarted() const1033 Server::isSwitchWaitStarted() const
1034 {
1035 return (m_switchWaitTimer != NULL);
1036 }
1037
1038 UInt32
getCorner(BaseClientProxy * client,SInt32 x,SInt32 y,SInt32 size) const1039 Server::getCorner(BaseClientProxy* client,
1040 SInt32 x, SInt32 y, SInt32 size) const
1041 {
1042 assert(client != NULL);
1043
1044 // get client screen shape
1045 SInt32 ax, ay, aw, ah;
1046 client->getShape(ax, ay, aw, ah);
1047
1048 // check for x,y on the left or right
1049 SInt32 xSide;
1050 if (x <= ax) {
1051 xSide = -1;
1052 }
1053 else if (x >= ax + aw - 1) {
1054 xSide = 1;
1055 }
1056 else {
1057 xSide = 0;
1058 }
1059
1060 // check for x,y on the top or bottom
1061 SInt32 ySide;
1062 if (y <= ay) {
1063 ySide = -1;
1064 }
1065 else if (y >= ay + ah - 1) {
1066 ySide = 1;
1067 }
1068 else {
1069 ySide = 0;
1070 }
1071
1072 // if against the left or right then check if y is within size
1073 if (xSide != 0) {
1074 if (y < ay + size) {
1075 return (xSide < 0) ? kTopLeftMask : kTopRightMask;
1076 }
1077 else if (y >= ay + ah - size) {
1078 return (xSide < 0) ? kBottomLeftMask : kBottomRightMask;
1079 }
1080 }
1081
1082 // if against the left or right then check if y is within size
1083 if (ySide != 0) {
1084 if (x < ax + size) {
1085 return (ySide < 0) ? kTopLeftMask : kBottomLeftMask;
1086 }
1087 else if (x >= ax + aw - size) {
1088 return (ySide < 0) ? kTopRightMask : kBottomRightMask;
1089 }
1090 }
1091
1092 return kNoCornerMask;
1093 }
1094
1095 void
stopRelativeMoves()1096 Server::stopRelativeMoves()
1097 {
1098 if (m_relativeMoves && m_active != m_primaryClient) {
1099 // warp to the center of the active client so we know where we are
1100 SInt32 ax, ay, aw, ah;
1101 m_active->getShape(ax, ay, aw, ah);
1102 m_x = ax + (aw >> 1);
1103 m_y = ay + (ah >> 1);
1104 m_xDelta = 0;
1105 m_yDelta = 0;
1106 m_xDelta2 = 0;
1107 m_yDelta2 = 0;
1108 LOG((CLOG_DEBUG2 "synchronize move on %s by %d,%d", getName(m_active).c_str(), m_x, m_y));
1109 m_active->mouseMove(m_x, m_y);
1110 }
1111 }
1112
1113 void
sendOptions(BaseClientProxy * client) const1114 Server::sendOptions(BaseClientProxy* client) const
1115 {
1116 OptionsList optionsList;
1117
1118 // look up options for client
1119 const Config::ScreenOptions* options =
1120 m_config->getOptions(getName(client));
1121 if (options != NULL) {
1122 // convert options to a more convenient form for sending
1123 optionsList.reserve(2 * options->size());
1124 for (Config::ScreenOptions::const_iterator index = options->begin();
1125 index != options->end(); ++index) {
1126 optionsList.push_back(index->first);
1127 optionsList.push_back(static_cast<UInt32>(index->second));
1128 }
1129 }
1130
1131 // look up global options
1132 options = m_config->getOptions("");
1133 if (options != NULL) {
1134 // convert options to a more convenient form for sending
1135 optionsList.reserve(optionsList.size() + 2 * options->size());
1136 for (Config::ScreenOptions::const_iterator index = options->begin();
1137 index != options->end(); ++index) {
1138 optionsList.push_back(index->first);
1139 optionsList.push_back(static_cast<UInt32>(index->second));
1140 }
1141 }
1142
1143 // send the options
1144 client->resetOptions();
1145 client->setOptions(optionsList);
1146 }
1147
1148 void
processOptions()1149 Server::processOptions()
1150 {
1151 const Config::ScreenOptions* options = m_config->getOptions("");
1152 if (options == NULL) {
1153 return;
1154 }
1155
1156 m_switchNeedsShift = false; // it seems if i don't add these
1157 m_switchNeedsControl = false; // lines, the 'reload config' option
1158 m_switchNeedsAlt = false; // doesnt' work correct.
1159
1160 bool newRelativeMoves = m_relativeMoves;
1161 for (Config::ScreenOptions::const_iterator index = options->begin();
1162 index != options->end(); ++index) {
1163 const OptionID id = index->first;
1164 const OptionValue value = index->second;
1165 if (id == kOptionScreenSwitchDelay) {
1166 m_switchWaitDelay = 1.0e-3 * static_cast<double>(value);
1167 if (m_switchWaitDelay < 0.0) {
1168 m_switchWaitDelay = 0.0;
1169 }
1170 stopSwitchWait();
1171 }
1172 else if (id == kOptionScreenSwitchTwoTap) {
1173 m_switchTwoTapDelay = 1.0e-3 * static_cast<double>(value);
1174 if (m_switchTwoTapDelay < 0.0) {
1175 m_switchTwoTapDelay = 0.0;
1176 }
1177 stopSwitchTwoTap();
1178 }
1179 else if (id == kOptionScreenSwitchNeedsControl) {
1180 m_switchNeedsControl = (value != 0);
1181 }
1182 else if (id == kOptionScreenSwitchNeedsShift) {
1183 m_switchNeedsShift = (value != 0);
1184 }
1185 else if (id == kOptionScreenSwitchNeedsAlt) {
1186 m_switchNeedsAlt = (value != 0);
1187 }
1188 else if (id == kOptionRelativeMouseMoves) {
1189 newRelativeMoves = (value != 0);
1190 }
1191 else if (id == kOptionDisableLockToScreen) {
1192 m_disableLockToScreen = (value != 0);
1193 }
1194 else if (id == kOptionClipboardSharing) {
1195 m_enableClipboard = value;
1196 if (!m_enableClipboard) {
1197 LOG((CLOG_NOTE "clipboard sharing is disabled"));
1198 }
1199 }
1200 else if (id == kOptionClipboardSharingSize) {
1201 if (value <= 0) {
1202 m_maximumClipboardSize = 0;
1203 LOG((CLOG_NOTE "clipboard sharing is disabled because the "
1204 "maximum shared clipboard size is set to 0"));
1205 } else {
1206 m_maximumClipboardSize = static_cast<size_t>(value);
1207 }
1208 }
1209 }
1210 if (m_relativeMoves && !newRelativeMoves) {
1211 stopRelativeMoves();
1212 }
1213 m_relativeMoves = newRelativeMoves;
1214 }
1215
1216 void
handleShapeChanged(const Event &,void * vclient)1217 Server::handleShapeChanged(const Event&, void* vclient)
1218 {
1219 // ignore events from unknown clients
1220 BaseClientProxy* client = static_cast<BaseClientProxy*>(vclient);
1221 if (m_clientSet.count(client) == 0) {
1222 return;
1223 }
1224
1225 LOG((CLOG_DEBUG "screen \"%s\" shape changed", getName(client).c_str()));
1226
1227 // update jump coordinate
1228 SInt32 x, y;
1229 client->getCursorPos(x, y);
1230 client->setJumpCursorPos(x, y);
1231
1232 // update the mouse coordinates
1233 if (client == m_active) {
1234 m_x = x;
1235 m_y = y;
1236 }
1237
1238 // handle resolution change to primary screen
1239 if (client == m_primaryClient) {
1240 if (client == m_active) {
1241 onMouseMovePrimary(m_x, m_y);
1242 }
1243 else {
1244 onMouseMoveSecondary(0, 0);
1245 }
1246 }
1247 }
1248
1249 void
handleClipboardGrabbed(const Event & event,void * vclient)1250 Server::handleClipboardGrabbed(const Event& event, void* vclient)
1251 {
1252 if (!m_enableClipboard || (m_maximumClipboardSize == 0)) {
1253 return;
1254 }
1255
1256 // ignore events from unknown clients
1257 BaseClientProxy* grabber = static_cast<BaseClientProxy*>(vclient);
1258 if (m_clientSet.count(grabber) == 0) {
1259 return;
1260 }
1261 const IScreen::ClipboardInfo* info =
1262 static_cast<const IScreen::ClipboardInfo*>(event.getData());
1263
1264 // ignore grab if sequence number is old. always allow primary
1265 // screen to grab.
1266 ClipboardInfo& clipboard = m_clipboards[info->m_id];
1267 if (grabber != m_primaryClient &&
1268 info->m_sequenceNumber < clipboard.m_clipboardSeqNum) {
1269 LOG((CLOG_INFO "ignored screen \"%s\" grab of clipboard %d", getName(grabber).c_str(), info->m_id));
1270 return;
1271 }
1272
1273 // mark screen as owning clipboard
1274 LOG((CLOG_INFO "screen \"%s\" grabbed clipboard %d from \"%s\"", getName(grabber).c_str(), info->m_id, clipboard.m_clipboardOwner.c_str()));
1275 clipboard.m_clipboardOwner = getName(grabber);
1276 clipboard.m_clipboardSeqNum = info->m_sequenceNumber;
1277
1278 // clear the clipboard data (since it's not known at this point)
1279 if (clipboard.m_clipboard.open(0)) {
1280 clipboard.m_clipboard.empty();
1281 clipboard.m_clipboard.close();
1282 }
1283 clipboard.m_clipboardData = clipboard.m_clipboard.marshall();
1284
1285 // tell all other screens to take ownership of clipboard. tell the
1286 // grabber that it's clipboard isn't dirty.
1287 for (ClientList::iterator index = m_clients.begin();
1288 index != m_clients.end(); ++index) {
1289 BaseClientProxy* client = index->second;
1290 if (client == grabber) {
1291 client->setClipboardDirty(info->m_id, false);
1292 }
1293 else {
1294 client->grabClipboard(info->m_id);
1295 }
1296 }
1297 }
1298
1299 void
handleClipboardChanged(const Event & event,void * vclient)1300 Server::handleClipboardChanged(const Event& event, void* vclient)
1301 {
1302 // ignore events from unknown clients
1303 BaseClientProxy* sender = static_cast<BaseClientProxy*>(vclient);
1304 if (m_clientSet.count(sender) == 0) {
1305 return;
1306 }
1307 const IScreen::ClipboardInfo* info =
1308 static_cast<const IScreen::ClipboardInfo*>(event.getData());
1309 onClipboardChanged(sender, info->m_id, info->m_sequenceNumber);
1310 }
1311
1312 void
handleKeyDownEvent(const Event & event,void *)1313 Server::handleKeyDownEvent(const Event& event, void*)
1314 {
1315 IPlatformScreen::KeyInfo* info =
1316 static_cast<IPlatformScreen::KeyInfo*>(event.getData());
1317 onKeyDown(info->m_key, info->m_mask, info->m_button, info->m_screens);
1318 }
1319
1320 void
handleKeyUpEvent(const Event & event,void *)1321 Server::handleKeyUpEvent(const Event& event, void*)
1322 {
1323 IPlatformScreen::KeyInfo* info =
1324 static_cast<IPlatformScreen::KeyInfo*>(event.getData());
1325 onKeyUp(info->m_key, info->m_mask, info->m_button, info->m_screens);
1326 }
1327
1328 void
handleKeyRepeatEvent(const Event & event,void *)1329 Server::handleKeyRepeatEvent(const Event& event, void*)
1330 {
1331 IPlatformScreen::KeyInfo* info =
1332 static_cast<IPlatformScreen::KeyInfo*>(event.getData());
1333 onKeyRepeat(info->m_key, info->m_mask, info->m_count, info->m_button);
1334 }
1335
1336 void
handleButtonDownEvent(const Event & event,void *)1337 Server::handleButtonDownEvent(const Event& event, void*)
1338 {
1339 IPlatformScreen::ButtonInfo* info =
1340 static_cast<IPlatformScreen::ButtonInfo*>(event.getData());
1341 onMouseDown(info->m_button);
1342 }
1343
1344 void
handleButtonUpEvent(const Event & event,void *)1345 Server::handleButtonUpEvent(const Event& event, void*)
1346 {
1347 IPlatformScreen::ButtonInfo* info =
1348 static_cast<IPlatformScreen::ButtonInfo*>(event.getData());
1349 onMouseUp(info->m_button);
1350 }
1351
1352 void
handleMotionPrimaryEvent(const Event & event,void *)1353 Server::handleMotionPrimaryEvent(const Event& event, void*)
1354 {
1355 IPlatformScreen::MotionInfo* info =
1356 static_cast<IPlatformScreen::MotionInfo*>(event.getData());
1357 onMouseMovePrimary(info->m_x, info->m_y);
1358 }
1359
1360 void
handleMotionSecondaryEvent(const Event & event,void *)1361 Server::handleMotionSecondaryEvent(const Event& event, void*)
1362 {
1363 IPlatformScreen::MotionInfo* info =
1364 static_cast<IPlatformScreen::MotionInfo*>(event.getData());
1365 onMouseMoveSecondary(info->m_x, info->m_y);
1366 }
1367
1368 void
handleWheelEvent(const Event & event,void *)1369 Server::handleWheelEvent(const Event& event, void*)
1370 {
1371 IPlatformScreen::WheelInfo* info =
1372 static_cast<IPlatformScreen::WheelInfo*>(event.getData());
1373 onMouseWheel(info->m_xDelta, info->m_yDelta);
1374 }
1375
1376 void
handleScreensaverActivatedEvent(const Event &,void *)1377 Server::handleScreensaverActivatedEvent(const Event&, void*)
1378 {
1379 onScreensaver(true);
1380 }
1381
1382 void
handleScreensaverDeactivatedEvent(const Event &,void *)1383 Server::handleScreensaverDeactivatedEvent(const Event&, void*)
1384 {
1385 onScreensaver(false);
1386 }
1387
1388 void
handleSwitchWaitTimeout(const Event &,void *)1389 Server::handleSwitchWaitTimeout(const Event&, void*)
1390 {
1391 // ignore if mouse is locked to screen
1392 if (isLockedToScreen()) {
1393 LOG((CLOG_DEBUG1 "locked to screen"));
1394 stopSwitch();
1395 return;
1396 }
1397
1398 // switch screen
1399 switchScreen(m_switchScreen, m_switchWaitX, m_switchWaitY, false);
1400 }
1401
1402 void
handleClientDisconnected(const Event &,void * vclient)1403 Server::handleClientDisconnected(const Event&, void* vclient)
1404 {
1405 // client has disconnected. it might be an old client or an
1406 // active client. we don't care so just handle it both ways.
1407 BaseClientProxy* client = static_cast<BaseClientProxy*>(vclient);
1408 removeActiveClient(client);
1409 removeOldClient(client);
1410
1411 delete client;
1412 }
1413
1414 void
handleClientCloseTimeout(const Event &,void * vclient)1415 Server::handleClientCloseTimeout(const Event&, void* vclient)
1416 {
1417 // client took too long to disconnect. just dump it.
1418 BaseClientProxy* client = static_cast<BaseClientProxy*>(vclient);
1419 LOG((CLOG_NOTE "forced disconnection of client \"%s\"", getName(client).c_str()));
1420 removeOldClient(client);
1421
1422 delete client;
1423 }
1424
1425 void
handleSwitchToScreenEvent(const Event & event,void *)1426 Server::handleSwitchToScreenEvent(const Event& event, void*)
1427 {
1428 SwitchToScreenInfo* info =
1429 static_cast<SwitchToScreenInfo*>(event.getData());
1430
1431 ClientList::const_iterator index = m_clients.find(info->m_screen);
1432 if (index == m_clients.end()) {
1433 LOG((CLOG_DEBUG1 "screen \"%s\" not active", info->m_screen));
1434 }
1435 else {
1436 jumpToScreen(index->second);
1437 }
1438 }
1439
1440 void
handleSwitchInDirectionEvent(const Event & event,void *)1441 Server::handleSwitchInDirectionEvent(const Event& event, void*)
1442 {
1443 SwitchInDirectionInfo* info =
1444 static_cast<SwitchInDirectionInfo*>(event.getData());
1445
1446 // jump to screen in chosen direction from center of this screen
1447 SInt32 x = m_x, y = m_y;
1448 BaseClientProxy* newScreen =
1449 getNeighbor(m_active, info->m_direction, x, y);
1450 if (newScreen == NULL) {
1451 LOG((CLOG_DEBUG1 "no neighbor %s", Config::dirName(info->m_direction)));
1452 }
1453 else {
1454 jumpToScreen(newScreen);
1455 }
1456 }
1457
1458 void
handleKeyboardBroadcastEvent(const Event & event,void *)1459 Server::handleKeyboardBroadcastEvent(const Event& event, void*)
1460 {
1461 KeyboardBroadcastInfo* info = (KeyboardBroadcastInfo*)event.getData();
1462
1463 // choose new state
1464 bool newState;
1465 switch (info->m_state) {
1466 case KeyboardBroadcastInfo::kOff:
1467 newState = false;
1468 break;
1469
1470 default:
1471 case KeyboardBroadcastInfo::kOn:
1472 newState = true;
1473 break;
1474
1475 case KeyboardBroadcastInfo::kToggle:
1476 newState = !m_keyboardBroadcasting;
1477 break;
1478 }
1479
1480 // enter new state
1481 if (newState != m_keyboardBroadcasting ||
1482 info->m_screens != m_keyboardBroadcastingScreens) {
1483 m_keyboardBroadcasting = newState;
1484 m_keyboardBroadcastingScreens = info->m_screens;
1485 LOG((CLOG_DEBUG "keyboard broadcasting %s: %s", m_keyboardBroadcasting ? "on" : "off", m_keyboardBroadcastingScreens.c_str()));
1486 }
1487 }
1488
1489 void
handleLockCursorToScreenEvent(const Event & event,void *)1490 Server::handleLockCursorToScreenEvent(const Event& event, void*)
1491 {
1492 LockCursorToScreenInfo* info = (LockCursorToScreenInfo*)event.getData();
1493
1494 // choose new state
1495 bool newState;
1496 switch (info->m_state) {
1497 case LockCursorToScreenInfo::kOff:
1498 newState = false;
1499 break;
1500
1501 default:
1502 case LockCursorToScreenInfo::kOn:
1503 newState = true;
1504 break;
1505
1506 case LockCursorToScreenInfo::kToggle:
1507 newState = !m_lockedToScreen;
1508 break;
1509 }
1510
1511 // enter new state
1512 if (newState != m_lockedToScreen) {
1513 m_lockedToScreen = newState;
1514 LOG((CLOG_NOTE "cursor %s current screen", m_lockedToScreen ? "locked to" : "unlocked from"));
1515
1516 m_primaryClient->reconfigure(getActivePrimarySides());
1517 if (!isLockedToScreenServer()) {
1518 stopRelativeMoves();
1519 }
1520 }
1521 }
1522
1523 void
handleFakeInputBeginEvent(const Event &,void *)1524 Server::handleFakeInputBeginEvent(const Event&, void*)
1525 {
1526 m_primaryClient->fakeInputBegin();
1527 }
1528
1529 void
handleFakeInputEndEvent(const Event &,void *)1530 Server::handleFakeInputEndEvent(const Event&, void*)
1531 {
1532 m_primaryClient->fakeInputEnd();
1533 }
1534
1535 void
handleFileChunkSendingEvent(const Event & event,void *)1536 Server::handleFileChunkSendingEvent(const Event& event, void*)
1537 {
1538 onFileChunkSending(event.getData());
1539 }
1540
1541 void
handleFileRecieveCompletedEvent(const Event & event,void *)1542 Server::handleFileRecieveCompletedEvent(const Event& event, void*)
1543 {
1544 onFileRecieveCompleted();
1545 }
1546
1547 void
onClipboardChanged(BaseClientProxy * sender,ClipboardID id,UInt32 seqNum)1548 Server::onClipboardChanged(BaseClientProxy* sender,
1549 ClipboardID id, UInt32 seqNum)
1550 {
1551 ClipboardInfo& clipboard = m_clipboards[id];
1552
1553 // ignore update if sequence number is old
1554 if (seqNum < clipboard.m_clipboardSeqNum) {
1555 LOG((CLOG_INFO "ignored screen \"%s\" update of clipboard %d (missequenced)", getName(sender).c_str(), id));
1556 return;
1557 }
1558
1559 // should be the expected client
1560 assert(sender == m_clients.find(clipboard.m_clipboardOwner)->second);
1561
1562 // get data
1563 sender->getClipboard(id, &clipboard.m_clipboard);
1564
1565 // ignore if data hasn't changed
1566 String data = clipboard.m_clipboard.marshall();
1567 if (data.size() > m_maximumClipboardSize * 1024) {
1568 LOG((CLOG_NOTE "not updating clipboard because it's over the size limit (%i KB) configured by the server",
1569 m_maximumClipboardSize));
1570 return;
1571 }
1572
1573 if (data == clipboard.m_clipboardData) {
1574 LOG((CLOG_DEBUG "ignored screen \"%s\" update of clipboard %d (unchanged)", clipboard.m_clipboardOwner.c_str(), id));
1575 return;
1576 }
1577
1578 // got new data
1579 LOG((CLOG_INFO "screen \"%s\" updated clipboard %d", clipboard.m_clipboardOwner.c_str(), id));
1580 clipboard.m_clipboardData = data;
1581
1582 // tell all clients except the sender that the clipboard is dirty
1583 for (ClientList::const_iterator index = m_clients.begin();
1584 index != m_clients.end(); ++index) {
1585 BaseClientProxy* client = index->second;
1586 client->setClipboardDirty(id, client != sender);
1587 }
1588
1589 // send the new clipboard to the active screen
1590 m_active->setClipboard(id, &clipboard.m_clipboard);
1591 }
1592
1593 void
onScreensaver(bool activated)1594 Server::onScreensaver(bool activated)
1595 {
1596 LOG((CLOG_DEBUG "onScreenSaver %s", activated ? "activated" : "deactivated"));
1597
1598 if (activated) {
1599 // save current screen and position
1600 m_activeSaver = m_active;
1601 m_xSaver = m_x;
1602 m_ySaver = m_y;
1603
1604 // jump to primary screen
1605 if (m_active != m_primaryClient) {
1606 switchScreen(m_primaryClient, 0, 0, true);
1607 }
1608 }
1609 else {
1610 // jump back to previous screen and position. we must check
1611 // that the position is still valid since the screen may have
1612 // changed resolutions while the screen saver was running.
1613 if (m_activeSaver != NULL && m_activeSaver != m_primaryClient) {
1614 // check position
1615 BaseClientProxy* screen = m_activeSaver;
1616 SInt32 x, y, w, h;
1617 screen->getShape(x, y, w, h);
1618 SInt32 zoneSize = getJumpZoneSize(screen);
1619 if (m_xSaver < x + zoneSize) {
1620 m_xSaver = x + zoneSize;
1621 }
1622 else if (m_xSaver >= x + w - zoneSize) {
1623 m_xSaver = x + w - zoneSize - 1;
1624 }
1625 if (m_ySaver < y + zoneSize) {
1626 m_ySaver = y + zoneSize;
1627 }
1628 else if (m_ySaver >= y + h - zoneSize) {
1629 m_ySaver = y + h - zoneSize - 1;
1630 }
1631
1632 // jump
1633 switchScreen(screen, m_xSaver, m_ySaver, false);
1634 }
1635
1636 // reset state
1637 m_activeSaver = NULL;
1638 }
1639
1640 // send message to all clients
1641 for (ClientList::const_iterator index = m_clients.begin();
1642 index != m_clients.end(); ++index) {
1643 BaseClientProxy* client = index->second;
1644 client->screensaver(activated);
1645 }
1646 }
1647
1648 void
onKeyDown(KeyID id,KeyModifierMask mask,KeyButton button,const char * screens)1649 Server::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button,
1650 const char* screens)
1651 {
1652 LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x", id, mask, button));
1653 assert(m_active != NULL);
1654
1655 // relay
1656 if (!m_keyboardBroadcasting && IKeyState::KeyInfo::isDefault(screens)) {
1657 m_active->keyDown(id, mask, button);
1658 }
1659 else {
1660 if (!screens && m_keyboardBroadcasting) {
1661 screens = m_keyboardBroadcastingScreens.c_str();
1662 if (IKeyState::KeyInfo::isDefault(screens)) {
1663 screens = "*";
1664 }
1665 }
1666 for (ClientList::const_iterator index = m_clients.begin();
1667 index != m_clients.end(); ++index) {
1668 if (IKeyState::KeyInfo::contains(screens, index->first)) {
1669 index->second->keyDown(id, mask, button);
1670 }
1671 }
1672 }
1673 }
1674
1675 void
onKeyUp(KeyID id,KeyModifierMask mask,KeyButton button,const char * screens)1676 Server::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button,
1677 const char* screens)
1678 {
1679 LOG((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button));
1680 assert(m_active != NULL);
1681
1682 // relay
1683 if (!m_keyboardBroadcasting && IKeyState::KeyInfo::isDefault(screens)) {
1684 m_active->keyUp(id, mask, button);
1685 }
1686 else {
1687 if (!screens && m_keyboardBroadcasting) {
1688 screens = m_keyboardBroadcastingScreens.c_str();
1689 if (IKeyState::KeyInfo::isDefault(screens)) {
1690 screens = "*";
1691 }
1692 }
1693 for (ClientList::const_iterator index = m_clients.begin();
1694 index != m_clients.end(); ++index) {
1695 if (IKeyState::KeyInfo::contains(screens, index->first)) {
1696 index->second->keyUp(id, mask, button);
1697 }
1698 }
1699 }
1700 }
1701
1702 void
onKeyRepeat(KeyID id,KeyModifierMask mask,SInt32 count,KeyButton button)1703 Server::onKeyRepeat(KeyID id, KeyModifierMask mask,
1704 SInt32 count, KeyButton button)
1705 {
1706 LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button));
1707 assert(m_active != NULL);
1708
1709 // relay
1710 m_active->keyRepeat(id, mask, count, button);
1711 }
1712
1713 void
onMouseDown(ButtonID id)1714 Server::onMouseDown(ButtonID id)
1715 {
1716 LOG((CLOG_DEBUG1 "onMouseDown id=%d", id));
1717 assert(m_active != NULL);
1718
1719 // relay
1720 m_active->mouseDown(id);
1721
1722 // reset this variable back to default value true
1723 m_waitDragInfoThread = true;
1724 }
1725
1726 void
onMouseUp(ButtonID id)1727 Server::onMouseUp(ButtonID id)
1728 {
1729 LOG((CLOG_DEBUG1 "onMouseUp id=%d", id));
1730 assert(m_active != NULL);
1731
1732 // relay
1733 m_active->mouseUp(id);
1734
1735 if (m_ignoreFileTransfer) {
1736 m_ignoreFileTransfer = false;
1737 return;
1738 }
1739
1740 if (m_args.m_enableDragDrop) {
1741 if (!m_screen->isOnScreen()) {
1742 String& file = m_screen->getDraggingFilename();
1743 if (!file.empty()) {
1744 sendFileToClient(file.c_str());
1745 }
1746 }
1747
1748 // always clear dragging filename
1749 m_screen->clearDraggingFilename();
1750 }
1751 }
1752
1753 bool
onMouseMovePrimary(SInt32 x,SInt32 y)1754 Server::onMouseMovePrimary(SInt32 x, SInt32 y)
1755 {
1756 LOG((CLOG_DEBUG4 "onMouseMovePrimary %d,%d", x, y));
1757
1758 // mouse move on primary (server's) screen
1759 if (m_active != m_primaryClient) {
1760 // stale event -- we're actually on a secondary screen
1761 return false;
1762 }
1763
1764 // save last delta
1765 m_xDelta2 = m_xDelta;
1766 m_yDelta2 = m_yDelta;
1767
1768 // save current delta
1769 m_xDelta = x - m_x;
1770 m_yDelta = y - m_y;
1771
1772 // save position
1773 m_x = x;
1774 m_y = y;
1775
1776 // get screen shape
1777 SInt32 ax, ay, aw, ah;
1778 m_active->getShape(ax, ay, aw, ah);
1779 SInt32 zoneSize = getJumpZoneSize(m_active);
1780
1781 // clamp position to screen
1782 SInt32 xc = x, yc = y;
1783 if (xc < ax + zoneSize) {
1784 xc = ax;
1785 }
1786 else if (xc >= ax + aw - zoneSize) {
1787 xc = ax + aw - 1;
1788 }
1789 if (yc < ay + zoneSize) {
1790 yc = ay;
1791 }
1792 else if (yc >= ay + ah - zoneSize) {
1793 yc = ay + ah - 1;
1794 }
1795
1796 // see if we should change screens
1797 // when the cursor is in a corner, there may be a screen either
1798 // horizontally or vertically. check both directions.
1799 EDirection dirh = kNoDirection, dirv = kNoDirection;
1800 SInt32 xh = x, yv = y;
1801 if (x < ax + zoneSize) {
1802 xh -= zoneSize;
1803 dirh = kLeft;
1804 }
1805 else if (x >= ax + aw - zoneSize) {
1806 xh += zoneSize;
1807 dirh = kRight;
1808 }
1809 if (y < ay + zoneSize) {
1810 yv -= zoneSize;
1811 dirv = kTop;
1812 }
1813 else if (y >= ay + ah - zoneSize) {
1814 yv += zoneSize;
1815 dirv = kBottom;
1816 }
1817 if (dirh == kNoDirection && dirv == kNoDirection) {
1818 // still on local screen
1819 noSwitch(x, y);
1820 return false;
1821 }
1822
1823 // check both horizontally and vertically
1824 EDirection dirs[] = {dirh, dirv};
1825 SInt32 xs[] = {xh, x}, ys[] = {y, yv};
1826 for (int i = 0; i < 2; ++i) {
1827 EDirection dir = dirs[i];
1828 if (dir == kNoDirection) {
1829 continue;
1830 }
1831 x = xs[i], y = ys[i];
1832
1833 // get jump destination
1834 BaseClientProxy* newScreen = mapToNeighbor(m_active, dir, x, y);
1835
1836 // should we switch or not?
1837 if (isSwitchOkay(newScreen, dir, x, y, xc, yc)) {
1838 if (m_args.m_enableDragDrop
1839 && m_screen->isDraggingStarted()
1840 && m_active != newScreen
1841 && m_waitDragInfoThread) {
1842 if (m_sendDragInfoThread == NULL) {
1843 m_sendDragInfoThread = new Thread(
1844 new TMethodJob<Server>(
1845 this,
1846 &Server::sendDragInfoThread, newScreen));
1847 }
1848
1849 return false;
1850 }
1851
1852 // switch screen
1853 switchScreen(newScreen, x, y, false);
1854 m_waitDragInfoThread = true;
1855 return true;
1856 }
1857 }
1858
1859 return false;
1860 }
1861
1862 void
sendDragInfoThread(void * arg)1863 Server::sendDragInfoThread(void* arg)
1864 {
1865 BaseClientProxy* newScreen = static_cast<BaseClientProxy*>(arg);
1866
1867 m_dragFileList.clear();
1868 String& dragFileList = m_screen->getDraggingFilename();
1869 if (!dragFileList.empty()) {
1870 DragInformation di;
1871 di.setFilename(dragFileList);
1872 m_dragFileList.push_back(di);
1873 }
1874
1875 #if defined(__APPLE__)
1876 // on mac it seems that after faking a LMB up, system would signal back
1877 // to synergy a mouse up event, which doesn't happen on windows. as a
1878 // result, synergy would send dragging file to client twice. This variable
1879 // is used to ignore the first file sending.
1880 m_ignoreFileTransfer = true;
1881 #endif
1882
1883 // send drag file info to client if there is any
1884 if (m_dragFileList.size() > 0) {
1885 sendDragInfo(newScreen);
1886 m_dragFileList.clear();
1887 }
1888 m_waitDragInfoThread = false;
1889 m_sendDragInfoThread = NULL;
1890 }
1891
1892 void
sendDragInfo(BaseClientProxy * newScreen)1893 Server::sendDragInfo(BaseClientProxy* newScreen)
1894 {
1895 String infoString;
1896 UInt32 fileCount = DragInformation::setupDragInfo(m_dragFileList, infoString);
1897
1898 if (fileCount > 0) {
1899 char* info = NULL;
1900 size_t size = infoString.size();
1901 info = new char[size];
1902 memcpy(info, infoString.c_str(), size);
1903
1904 LOG((CLOG_DEBUG2 "sending drag information to client"));
1905 LOG((CLOG_DEBUG3 "dragging file list: %s", info));
1906 LOG((CLOG_DEBUG3 "dragging file list string size: %i", size));
1907 newScreen->sendDragInfo(fileCount, info, size);
1908 }
1909 }
1910
1911 void
onMouseMoveSecondary(SInt32 dx,SInt32 dy)1912 Server::onMouseMoveSecondary(SInt32 dx, SInt32 dy)
1913 {
1914 LOG((CLOG_DEBUG2 "onMouseMoveSecondary %+d,%+d", dx, dy));
1915
1916 // mouse move on secondary (client's) screen
1917 assert(m_active != NULL);
1918 if (m_active == m_primaryClient) {
1919 // stale event -- we're actually on the primary screen
1920 return;
1921 }
1922
1923 // if doing relative motion on secondary screens and we're locked
1924 // to the screen (which activates relative moves) then send a
1925 // relative mouse motion. when we're doing this we pretend as if
1926 // the mouse isn't actually moving because we're expecting some
1927 // program on the secondary screen to warp the mouse on us, so we
1928 // have no idea where it really is.
1929 if (m_relativeMoves && isLockedToScreenServer()) {
1930 LOG((CLOG_DEBUG2 "relative move on %s by %d,%d", getName(m_active).c_str(), dx, dy));
1931 m_active->mouseRelativeMove(dx, dy);
1932 return;
1933 }
1934
1935 // save old position
1936 const SInt32 xOld = m_x;
1937 const SInt32 yOld = m_y;
1938
1939 // save last delta
1940 m_xDelta2 = m_xDelta;
1941 m_yDelta2 = m_yDelta;
1942
1943 // save current delta
1944 m_xDelta = dx;
1945 m_yDelta = dy;
1946
1947 // accumulate motion
1948 m_x += dx;
1949 m_y += dy;
1950
1951 // get screen shape
1952 SInt32 ax, ay, aw, ah;
1953 m_active->getShape(ax, ay, aw, ah);
1954
1955 // find direction of neighbor and get the neighbor
1956 bool jump = true;
1957 BaseClientProxy* newScreen;
1958 do {
1959 // clamp position to screen
1960 SInt32 xc = m_x, yc = m_y;
1961 if (xc < ax) {
1962 xc = ax;
1963 }
1964 else if (xc >= ax + aw) {
1965 xc = ax + aw - 1;
1966 }
1967 if (yc < ay) {
1968 yc = ay;
1969 }
1970 else if (yc >= ay + ah) {
1971 yc = ay + ah - 1;
1972 }
1973
1974 EDirection dir;
1975 if (m_x < ax) {
1976 dir = kLeft;
1977 }
1978 else if (m_x > ax + aw - 1) {
1979 dir = kRight;
1980 }
1981 else if (m_y < ay) {
1982 dir = kTop;
1983 }
1984 else if (m_y > ay + ah - 1) {
1985 dir = kBottom;
1986 }
1987 else {
1988 // we haven't left the screen
1989 newScreen = m_active;
1990 jump = false;
1991
1992 // if waiting and mouse is not on the border we're waiting
1993 // on then stop waiting. also if it's not on the border
1994 // then arm the double tap.
1995 if (m_switchScreen != NULL) {
1996 bool clearWait;
1997 SInt32 zoneSize = m_primaryClient->getJumpZoneSize();
1998 switch (m_switchDir) {
1999 case kLeft:
2000 clearWait = (m_x >= ax + zoneSize);
2001 break;
2002
2003 case kRight:
2004 clearWait = (m_x <= ax + aw - 1 - zoneSize);
2005 break;
2006
2007 case kTop:
2008 clearWait = (m_y >= ay + zoneSize);
2009 break;
2010
2011 case kBottom:
2012 clearWait = (m_y <= ay + ah - 1 + zoneSize);
2013 break;
2014
2015 default:
2016 clearWait = false;
2017 break;
2018 }
2019 if (clearWait) {
2020 // still on local screen
2021 noSwitch(m_x, m_y);
2022 }
2023 }
2024
2025 // skip rest of block
2026 break;
2027 }
2028
2029 // try to switch screen. get the neighbor.
2030 newScreen = mapToNeighbor(m_active, dir, m_x, m_y);
2031
2032 // see if we should switch
2033 if (!isSwitchOkay(newScreen, dir, m_x, m_y, xc, yc)) {
2034 newScreen = m_active;
2035 jump = false;
2036 }
2037 } while (false);
2038
2039 if (jump) {
2040 if (m_sendFileThread != NULL) {
2041 StreamChunker::interruptFile();
2042 m_sendFileThread = NULL;
2043 }
2044
2045 SInt32 newX = m_x;
2046 SInt32 newY = m_y;
2047
2048 // switch screens
2049 switchScreen(newScreen, newX, newY, false);
2050 }
2051 else {
2052 // same screen. clamp mouse to edge.
2053 m_x = xOld + dx;
2054 m_y = yOld + dy;
2055 if (m_x < ax) {
2056 m_x = ax;
2057 LOG((CLOG_DEBUG2 "clamp to left of \"%s\"", getName(m_active).c_str()));
2058 }
2059 else if (m_x > ax + aw - 1) {
2060 m_x = ax + aw - 1;
2061 LOG((CLOG_DEBUG2 "clamp to right of \"%s\"", getName(m_active).c_str()));
2062 }
2063 if (m_y < ay) {
2064 m_y = ay;
2065 LOG((CLOG_DEBUG2 "clamp to top of \"%s\"", getName(m_active).c_str()));
2066 }
2067 else if (m_y > ay + ah - 1) {
2068 m_y = ay + ah - 1;
2069 LOG((CLOG_DEBUG2 "clamp to bottom of \"%s\"", getName(m_active).c_str()));
2070 }
2071
2072 // warp cursor if it moved.
2073 if (m_x != xOld || m_y != yOld) {
2074 LOG((CLOG_DEBUG2 "move on %s to %d,%d", getName(m_active).c_str(), m_x, m_y));
2075 m_active->mouseMove(m_x, m_y);
2076 }
2077 }
2078 }
2079
2080 void
onMouseWheel(SInt32 xDelta,SInt32 yDelta)2081 Server::onMouseWheel(SInt32 xDelta, SInt32 yDelta)
2082 {
2083 LOG((CLOG_DEBUG1 "onMouseWheel %+d,%+d", xDelta, yDelta));
2084 assert(m_active != NULL);
2085
2086 // relay
2087 m_active->mouseWheel(xDelta, yDelta);
2088 }
2089
2090 void
onFileChunkSending(const void * data)2091 Server::onFileChunkSending(const void* data)
2092 {
2093 FileChunk* chunk = static_cast<FileChunk*>(const_cast<void*>(data));
2094
2095 LOG((CLOG_DEBUG1 "sending file chunk"));
2096 assert(m_active != NULL);
2097
2098 // relay
2099 m_active->fileChunkSending(chunk->m_chunk[0], &chunk->m_chunk[1], chunk->m_dataSize);
2100 }
2101
2102 void
onFileRecieveCompleted()2103 Server::onFileRecieveCompleted()
2104 {
2105 if (isReceivedFileSizeValid()) {
2106 m_writeToDropDirThread = new Thread(
2107 new TMethodJob<Server>(
2108 this, &Server::writeToDropDirThread));
2109 }
2110 }
2111
2112 void
writeToDropDirThread(void *)2113 Server::writeToDropDirThread(void*)
2114 {
2115 LOG((CLOG_DEBUG "starting write to drop dir thread"));
2116
2117 while (m_screen->isFakeDraggingStarted()) {
2118 ARCH->sleep(.1f);
2119 }
2120
2121 DropHelper::writeToDir(m_screen->getDropTarget(), m_fakeDragFileList,
2122 m_receivedFileData);
2123 }
2124
2125 bool
addClient(BaseClientProxy * client)2126 Server::addClient(BaseClientProxy* client)
2127 {
2128 String name = getName(client);
2129 if (m_clients.count(name) != 0) {
2130 return false;
2131 }
2132
2133 // add event handlers
2134 m_events->adoptHandler(m_events->forIScreen().shapeChanged(),
2135 client->getEventTarget(),
2136 new TMethodEventJob<Server>(this,
2137 &Server::handleShapeChanged, client));
2138 m_events->adoptHandler(m_events->forClipboard().clipboardGrabbed(),
2139 client->getEventTarget(),
2140 new TMethodEventJob<Server>(this,
2141 &Server::handleClipboardGrabbed, client));
2142 m_events->adoptHandler(m_events->forClipboard().clipboardChanged(),
2143 client->getEventTarget(),
2144 new TMethodEventJob<Server>(this,
2145 &Server::handleClipboardChanged, client));
2146
2147 // add to list
2148 m_clientSet.insert(client);
2149 m_clients.insert(std::make_pair(name, client));
2150
2151 // initialize client data
2152 SInt32 x, y;
2153 client->getCursorPos(x, y);
2154 client->setJumpCursorPos(x, y);
2155
2156 // tell primary client about the active sides
2157 m_primaryClient->reconfigure(getActivePrimarySides());
2158
2159 return true;
2160 }
2161
2162 bool
removeClient(BaseClientProxy * client)2163 Server::removeClient(BaseClientProxy* client)
2164 {
2165 // return false if not in list
2166 ClientSet::iterator i = m_clientSet.find(client);
2167 if (i == m_clientSet.end()) {
2168 return false;
2169 }
2170
2171 // remove event handlers
2172 m_events->removeHandler(m_events->forIScreen().shapeChanged(),
2173 client->getEventTarget());
2174 m_events->removeHandler(m_events->forClipboard().clipboardGrabbed(),
2175 client->getEventTarget());
2176 m_events->removeHandler(m_events->forClipboard().clipboardChanged(),
2177 client->getEventTarget());
2178
2179 // remove from list
2180 m_clients.erase(getName(client));
2181 m_clientSet.erase(i);
2182
2183 return true;
2184 }
2185
2186 void
closeClient(BaseClientProxy * client,const char * msg)2187 Server::closeClient(BaseClientProxy* client, const char* msg)
2188 {
2189 assert(client != m_primaryClient);
2190 assert(msg != NULL);
2191
2192 // send message to client. this message should cause the client
2193 // to disconnect. we add this client to the closed client list
2194 // and install a timer to remove the client if it doesn't respond
2195 // quickly enough. we also remove the client from the active
2196 // client list since we're not going to listen to it anymore.
2197 // note that this method also works on clients that are not in
2198 // the m_clients list. adoptClient() may call us with such a
2199 // client.
2200 LOG((CLOG_NOTE "disconnecting client \"%s\"", getName(client).c_str()));
2201
2202 // send message
2203 // FIXME -- avoid type cast (kinda hard, though)
2204 ((ClientProxy*)client)->close(msg);
2205
2206 // install timer. wait timeout seconds for client to close.
2207 double timeout = 5.0;
2208 EventQueueTimer* timer = m_events->newOneShotTimer(timeout, NULL);
2209 m_events->adoptHandler(Event::kTimer, timer,
2210 new TMethodEventJob<Server>(this,
2211 &Server::handleClientCloseTimeout, client));
2212
2213 // move client to closing list
2214 removeClient(client);
2215 m_oldClients.insert(std::make_pair(client, timer));
2216
2217 // if this client is the active screen then we have to
2218 // jump off of it
2219 forceLeaveClient(client);
2220 }
2221
2222 void
closeClients(const Config & config)2223 Server::closeClients(const Config& config)
2224 {
2225 // collect the clients that are connected but are being dropped
2226 // from the configuration (or who's canonical name is changing).
2227 typedef std::set<BaseClientProxy*> RemovedClients;
2228 RemovedClients removed;
2229 for (ClientList::iterator index = m_clients.begin();
2230 index != m_clients.end(); ++index) {
2231 if (!config.isCanonicalName(index->first)) {
2232 removed.insert(index->second);
2233 }
2234 }
2235
2236 // don't close the primary client
2237 removed.erase(m_primaryClient);
2238
2239 // now close them. we collect the list then close in two steps
2240 // because closeClient() modifies the collection we iterate over.
2241 for (RemovedClients::iterator index = removed.begin();
2242 index != removed.end(); ++index) {
2243 closeClient(*index, kMsgCClose);
2244 }
2245 }
2246
2247 void
removeActiveClient(BaseClientProxy * client)2248 Server::removeActiveClient(BaseClientProxy* client)
2249 {
2250 if (removeClient(client)) {
2251 forceLeaveClient(client);
2252 m_events->removeHandler(m_events->forClientProxy().disconnected(), client);
2253 if (m_clients.size() == 1 && m_oldClients.empty()) {
2254 m_events->addEvent(Event(m_events->forServer().disconnected(), this));
2255 }
2256 }
2257 }
2258
2259 void
removeOldClient(BaseClientProxy * client)2260 Server::removeOldClient(BaseClientProxy* client)
2261 {
2262 OldClients::iterator i = m_oldClients.find(client);
2263 if (i != m_oldClients.end()) {
2264 m_events->removeHandler(m_events->forClientProxy().disconnected(), client);
2265 m_events->removeHandler(Event::kTimer, i->second);
2266 m_events->deleteTimer(i->second);
2267 m_oldClients.erase(i);
2268 if (m_clients.size() == 1 && m_oldClients.empty()) {
2269 m_events->addEvent(Event(m_events->forServer().disconnected(), this));
2270 }
2271 }
2272 }
2273
2274 void
forceLeaveClient(BaseClientProxy * client)2275 Server::forceLeaveClient(BaseClientProxy* client)
2276 {
2277 BaseClientProxy* active =
2278 (m_activeSaver != NULL) ? m_activeSaver : m_active;
2279 if (active == client) {
2280 // record new position (center of primary screen)
2281 m_primaryClient->getCursorCenter(m_x, m_y);
2282
2283 // stop waiting to switch to this client
2284 if (active == m_switchScreen) {
2285 stopSwitch();
2286 }
2287
2288 // don't notify active screen since it has probably already
2289 // disconnected.
2290 LOG((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", getName(active).c_str(), getName(m_primaryClient).c_str(), m_x, m_y));
2291
2292 // cut over
2293 m_active = m_primaryClient;
2294
2295 // enter new screen (unless we already have because of the
2296 // screen saver)
2297 if (m_activeSaver == NULL) {
2298 m_primaryClient->enter(m_x, m_y, m_seqNum,
2299 m_primaryClient->getToggleMask(), false);
2300 }
2301 }
2302
2303 // if this screen had the cursor when the screen saver activated
2304 // then we can't switch back to it when the screen saver
2305 // deactivates.
2306 if (m_activeSaver == client) {
2307 m_activeSaver = NULL;
2308 }
2309
2310 // tell primary client about the active sides
2311 m_primaryClient->reconfigure(getActivePrimarySides());
2312 }
2313
2314
2315 //
2316 // Server::ClipboardInfo
2317 //
2318
ClipboardInfo()2319 Server::ClipboardInfo::ClipboardInfo() :
2320 m_clipboard(),
2321 m_clipboardData(),
2322 m_clipboardOwner(),
2323 m_clipboardSeqNum(0)
2324 {
2325 // do nothing
2326 }
2327
2328
2329 //
2330 // Server::LockCursorToScreenInfo
2331 //
2332
2333 Server::LockCursorToScreenInfo*
alloc(State state)2334 Server::LockCursorToScreenInfo::alloc(State state)
2335 {
2336 LockCursorToScreenInfo* info =
2337 (LockCursorToScreenInfo*)malloc(sizeof(LockCursorToScreenInfo));
2338 info->m_state = state;
2339 return info;
2340 }
2341
2342
2343 //
2344 // Server::SwitchToScreenInfo
2345 //
2346
2347 Server::SwitchToScreenInfo*
alloc(const String & screen)2348 Server::SwitchToScreenInfo::alloc(const String& screen)
2349 {
2350 SwitchToScreenInfo* info =
2351 (SwitchToScreenInfo*)malloc(sizeof(SwitchToScreenInfo) +
2352 screen.size());
2353 strcpy(info->m_screen, screen.c_str()); // Compliant: we made sure the buffer is large enough
2354 return info;
2355 }
2356
2357
2358 //
2359 // Server::SwitchInDirectionInfo
2360 //
2361
2362 Server::SwitchInDirectionInfo*
alloc(EDirection direction)2363 Server::SwitchInDirectionInfo::alloc(EDirection direction)
2364 {
2365 SwitchInDirectionInfo* info =
2366 (SwitchInDirectionInfo*)malloc(sizeof(SwitchInDirectionInfo));
2367 info->m_direction = direction;
2368 return info;
2369 }
2370
2371 //
2372 // Server::KeyboardBroadcastInfo
2373 //
2374
2375 Server::KeyboardBroadcastInfo*
alloc(State state)2376 Server::KeyboardBroadcastInfo::alloc(State state)
2377 {
2378 KeyboardBroadcastInfo* info =
2379 (KeyboardBroadcastInfo*)malloc(sizeof(KeyboardBroadcastInfo));
2380 info->m_state = state;
2381 info->m_screens[0] = '\0';
2382 return info;
2383 }
2384
2385 Server::KeyboardBroadcastInfo*
alloc(State state,const String & screens)2386 Server::KeyboardBroadcastInfo::alloc(State state, const String& screens)
2387 {
2388 KeyboardBroadcastInfo* info =
2389 (KeyboardBroadcastInfo*)malloc(sizeof(KeyboardBroadcastInfo) +
2390 screens.size());
2391 info->m_state = state;
2392 strcpy(info->m_screens, screens.c_str()); // Compliant: we made sure that screens variable ended with null
2393 return info;
2394 }
2395
2396 bool
isReceivedFileSizeValid()2397 Server::isReceivedFileSizeValid()
2398 {
2399 return m_expectedFileSize == m_receivedFileData.size();
2400 }
2401
2402 void
sendFileToClient(const char * filename)2403 Server::sendFileToClient(const char* filename)
2404 {
2405 if (m_sendFileThread != NULL) {
2406 StreamChunker::interruptFile();
2407 }
2408
2409 m_sendFileThread = new Thread(
2410 new TMethodJob<Server>(
2411 this, &Server::sendFileThread,
2412 static_cast<void*>(const_cast<char*>(filename))));
2413 }
2414
2415 void
sendFileThread(void * data)2416 Server::sendFileThread(void* data)
2417 {
2418 try {
2419 char* filename = static_cast<char*>(data);
2420 LOG((CLOG_DEBUG "sending file to client, filename=%s", filename));
2421 StreamChunker::sendFile(filename, m_events, this);
2422 }
2423 catch (std::runtime_error &error) {
2424 LOG((CLOG_ERR "failed sending file chunks, error: %s", error.what()));
2425 }
2426
2427 m_sendFileThread = NULL;
2428 }
2429
2430 void
dragInfoReceived(UInt32 fileNum,String content)2431 Server::dragInfoReceived(UInt32 fileNum, String content)
2432 {
2433 if (!m_args.m_enableDragDrop) {
2434 LOG((CLOG_DEBUG "drag drop not enabled, ignoring drag info."));
2435 return;
2436 }
2437
2438 DragInformation::parseDragInfo(m_fakeDragFileList, fileNum, content);
2439
2440 m_screen->startDraggingFiles(m_fakeDragFileList);
2441 }
2442