blob: 1685b33ccd82fe8180b0d5108a346d8dc49d2ab3 [file] [log] [blame]
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 *
3 * This is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This software is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this software; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
16 * USA.
17 */
18
19// -=- Single-Threaded VNC Server implementation
20
21
22// Note about how sockets get closed:
23//
24// Closing sockets to clients is non-trivial because the code which calls
25// VNCServerST must explicitly know about all the sockets (so that it can block
26// on them appropriately). However, VNCServerST may want to close clients for
27// a number of reasons, and from a variety of entry points. The simplest is
28// when processSocketEvent() is called for a client, and the remote end has
29// closed its socket. A more complex reason is when processSocketEvent() is
30// called for a client which has just sent a ClientInit with the shared flag
31// set to false - in this case we want to close all other clients. Yet another
32// reason for disconnecting clients is when the desktop size has changed as a
33// result of a call to setPixelBuffer().
34//
35// The responsibility for creating and deleting sockets is entirely with the
36// calling code. When VNCServerST wants to close a connection to a client it
37// calls the VNCSConnectionST's close() method which calls shutdown() on the
38// socket. Eventually the calling code will notice that the socket has been
39// shut down and call removeSocket() so that we can delete the
40// VNCSConnectionST. Note that the socket must not be deleted by the calling
41// code until after removeSocket() has been called.
42//
43// One minor complication is that we don't allocate a VNCSConnectionST object
44// for a blacklisted host (since we want to minimise the resources used for
45// dealing with such a connection). In order to properly implement the
46// getSockets function, we must maintain a separate closingSockets list,
47// otherwise blacklisted connections might be "forgotten".
48
49
Pierre Ossmanf99c5712009-03-13 14:41:27 +000050#include <stdlib.h>
51
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000052#include <rfb/ServerCore.h>
53#include <rfb/VNCServerST.h>
54#include <rfb/VNCSConnectionST.h>
55#include <rfb/ComparingUpdateTracker.h>
Adam Tkaca6578bf2010-04-23 14:07:41 +000056#include <rfb/Security.h>
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000057#include <rfb/KeyRemapper.h>
58#include <rfb/util.h>
59
60#include <rdr/types.h>
61
62using namespace rfb;
63
64static LogWriter slog("VNCServerST");
65LogWriter VNCServerST::connectionsLog("Connections");
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000066
67//
68// -=- VNCServerST Implementation
69//
70
71// -=- Constructors/Destructor
72
Adam Tkaca6578bf2010-04-23 14:07:41 +000073VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000074 : blHosts(&blacklist), desktop(desktop_), desktopStarted(false), pb(0),
Adam Tkacd36b6262009-09-04 10:57:20 +000075 name(strDup(name_)), pointerClient(0), comparer(0),
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000076 renderedCursorInvalid(false),
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000077 queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance),
78 useEconomicTranslate(false),
Pierre Ossman02e43d72009-03-05 11:57:11 +000079 lastConnectionTime(0), disableclients(false)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000080{
81 lastUserInputTime = lastDisconnectTime = time(0);
82 slog.debug("creating single-threaded server %s", name.buf);
83}
84
85VNCServerST::~VNCServerST()
86{
87 slog.debug("shutting down server %s", name.buf);
88
89 // Close any active clients, with appropriate logging & cleanup
90 closeClients("Server shutdown");
91
92 // Delete all the clients, and their sockets, and any closing sockets
93 // NB: Deleting a client implicitly removes it from the clients list
94 while (!clients.empty()) {
95 delete clients.front();
96 }
97
98 // Stop the desktop object if active, *only* after deleting all clients!
99 if (desktopStarted) {
100 desktopStarted = false;
101 desktop->stop();
102 }
103
104 delete comparer;
105}
106
107
108// SocketServer methods
109
110void VNCServerST::addSocket(network::Socket* sock, bool outgoing)
111{
112 // - Check the connection isn't black-marked
113 // *** do this in getSecurity instead?
114 CharArray address(sock->getPeerAddress());
115 if (blHosts->isBlackmarked(address.buf)) {
116 connectionsLog.error("blacklisted: %s", address.buf);
117 try {
118 SConnection::writeConnFailedFromScratch("Too many security failures",
119 &sock->outStream());
120 } catch (rdr::Exception&) {
121 }
122 sock->shutdown();
123 closingSockets.push_back(sock);
124 return;
125 }
126
127 if (clients.empty()) {
128 lastConnectionTime = time(0);
129 }
130
131 VNCSConnectionST* client = new VNCSConnectionST(this, sock, outgoing);
132 client->init();
133}
134
135void VNCServerST::removeSocket(network::Socket* sock) {
136 // - If the socket has resources allocated to it, delete them
137 std::list<VNCSConnectionST*>::iterator ci;
138 for (ci = clients.begin(); ci != clients.end(); ci++) {
139 if ((*ci)->getSock() == sock) {
140 // - Delete the per-Socket resources
141 delete *ci;
142
143 // - Check that the desktop object is still required
144 if (authClientCount() == 0 && desktopStarted) {
145 slog.debug("no authenticated clients - stopping desktop");
146 desktopStarted = false;
147 desktop->stop();
148 }
149 return;
150 }
151 }
152
153 // - If the Socket has no resources, it may have been a closingSocket
154 closingSockets.remove(sock);
155}
156
157void VNCServerST::processSocketEvent(network::Socket* sock)
158{
159 // - Find the appropriate VNCSConnectionST and process the event
160 std::list<VNCSConnectionST*>::iterator ci;
161 for (ci = clients.begin(); ci != clients.end(); ci++) {
162 if ((*ci)->getSock() == sock) {
163 (*ci)->processMessages();
164 return;
165 }
166 }
167 throw rdr::Exception("invalid Socket in VNCServerST");
168}
169
170int VNCServerST::checkTimeouts()
171{
172 int timeout = 0;
173 std::list<VNCSConnectionST*>::iterator ci, ci_next;
174 for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
175 ci_next = ci; ci_next++;
176 soonestTimeout(&timeout, (*ci)->checkIdleTimeout());
177 }
178
179 int timeLeft;
Constantin Kaplinsky8499d0c2008-08-21 05:51:29 +0000180 time_t now = time(0);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000181
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000182 // Check MaxDisconnectionTime
183 if (rfb::Server::maxDisconnectionTime && clients.empty()) {
184 if (now < lastDisconnectTime) {
185 // Someone must have set the time backwards.
186 slog.info("Time has gone backwards - resetting lastDisconnectTime");
187 lastDisconnectTime = now;
188 }
189 timeLeft = lastDisconnectTime + rfb::Server::maxDisconnectionTime - now;
190 if (timeLeft < -60) {
191 // Someone must have set the time forwards.
192 slog.info("Time has gone forwards - resetting lastDisconnectTime");
193 lastDisconnectTime = now;
194 timeLeft = rfb::Server::maxDisconnectionTime;
195 }
196 if (timeLeft <= 0) {
197 slog.info("MaxDisconnectionTime reached, exiting");
198 exit(0);
199 }
200 soonestTimeout(&timeout, timeLeft * 1000);
201 }
202
203 // Check MaxConnectionTime
204 if (rfb::Server::maxConnectionTime && lastConnectionTime && !clients.empty()) {
205 if (now < lastConnectionTime) {
206 // Someone must have set the time backwards.
207 slog.info("Time has gone backwards - resetting lastConnectionTime");
208 lastConnectionTime = now;
209 }
210 timeLeft = lastConnectionTime + rfb::Server::maxConnectionTime - now;
211 if (timeLeft < -60) {
212 // Someone must have set the time forwards.
213 slog.info("Time has gone forwards - resetting lastConnectionTime");
214 lastConnectionTime = now;
215 timeLeft = rfb::Server::maxConnectionTime;
216 }
217 if (timeLeft <= 0) {
218 slog.info("MaxConnectionTime reached, exiting");
219 exit(0);
220 }
221 soonestTimeout(&timeout, timeLeft * 1000);
222 }
223
224
225 // Check MaxIdleTime
226 if (rfb::Server::maxIdleTime) {
227 if (now < lastUserInputTime) {
228 // Someone must have set the time backwards.
229 slog.info("Time has gone backwards - resetting lastUserInputTime");
230 lastUserInputTime = now;
231 }
232 timeLeft = lastUserInputTime + rfb::Server::maxIdleTime - now;
233 if (timeLeft < -60) {
234 // Someone must have set the time forwards.
235 slog.info("Time has gone forwards - resetting lastUserInputTime");
236 lastUserInputTime = now;
237 timeLeft = rfb::Server::maxIdleTime;
238 }
239 if (timeLeft <= 0) {
240 slog.info("MaxIdleTime reached, exiting");
241 exit(0);
242 }
243 soonestTimeout(&timeout, timeLeft * 1000);
244 }
245
246 return timeout;
247}
248
249
250// VNCServer methods
251
Pierre Ossman04e62db2009-03-23 16:57:07 +0000252void VNCServerST::setPixelBuffer(PixelBuffer* pb_, const ScreenSet& layout)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000253{
254 pb = pb_;
255 delete comparer;
256 comparer = 0;
257
Pierre Ossman04e62db2009-03-23 16:57:07 +0000258 screenLayout = layout;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000259
Pierre Ossman04e62db2009-03-23 16:57:07 +0000260 if (!pb) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000261 if (desktopStarted)
262 throw Exception("setPixelBuffer: null PixelBuffer when desktopStarted?");
Pierre Ossman04e62db2009-03-23 16:57:07 +0000263 return;
264 }
265
266 comparer = new ComparingUpdateTracker(pb);
267 cursor.setPF(pb->getPF());
268 renderedCursor.setPF(pb->getPF());
269
270 // Make sure that we have at least one screen
271 if (screenLayout.num_screens() == 0)
272 screenLayout.add_screen(Screen(0, 0, 0, pb->width(), pb->height(), 0));
273
274 std::list<VNCSConnectionST*>::iterator ci, ci_next;
275 for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
276 ci_next = ci; ci_next++;
277 (*ci)->pixelBufferChange();
278 // Since the new pixel buffer means an ExtendedDesktopSize needs to
279 // be sent anyway, we don't need to call screenLayoutChange.
280 }
281}
282
283void VNCServerST::setPixelBuffer(PixelBuffer* pb_)
284{
285 ScreenSet layout;
286
287 if (!pb_) {
288 if (desktopStarted)
289 throw Exception("setPixelBuffer: null PixelBuffer when desktopStarted?");
290 return;
291 }
292
293 layout = screenLayout;
294
295 // Check that the screen layout is still valid
296 if (!layout.validate(pb_->width(), pb_->height())) {
297 Rect fbRect;
298 ScreenSet::iterator iter, iter_next;
299
300 fbRect.setXYWH(0, 0, pb_->width(), pb_->height());
301
302 for (iter = layout.begin();iter != layout.end();iter = iter_next) {
303 iter_next = iter; ++iter_next;
304 if (iter->dimensions.enclosed_by(fbRect))
305 continue;
306 iter->dimensions = iter->dimensions.intersect(fbRect);
307 if (iter->dimensions.is_empty()) {
308 slog.info("Removing screen %d (%x) as it is completely outside the new framebuffer",
309 (int)iter->id, (unsigned)iter->id);
310 layout.remove_screen(iter->id);
311 }
312 }
313 }
314
315 setPixelBuffer(pb_, layout);
316}
317
318void VNCServerST::setScreenLayout(const ScreenSet& layout)
319{
320 if (!pb)
321 throw Exception("setScreenLayout: new screen layout without a PixelBuffer");
322 if (!layout.validate(pb->width(), pb->height()))
323 throw Exception("setScreenLayout: invalid screen layout");
324
Pierre Ossmandf453202009-04-02 14:26:45 +0000325 screenLayout = layout;
326
Pierre Ossman04e62db2009-03-23 16:57:07 +0000327 std::list<VNCSConnectionST*>::iterator ci, ci_next;
328 for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
329 ci_next = ci; ci_next++;
330 (*ci)->screenLayoutChange(reasonServer);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000331 }
332}
333
334void VNCServerST::setColourMapEntries(int firstColour, int nColours)
335{
336 std::list<VNCSConnectionST*>::iterator ci, ci_next;
337 for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
338 ci_next = ci; ci_next++;
339 (*ci)->setColourMapEntriesOrClose(firstColour, nColours);
340 }
341}
342
343void VNCServerST::bell()
344{
345 std::list<VNCSConnectionST*>::iterator ci, ci_next;
346 for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
347 ci_next = ci; ci_next++;
348 (*ci)->bell();
349 }
350}
351
352void VNCServerST::serverCutText(const char* str, int len)
353{
354 std::list<VNCSConnectionST*>::iterator ci, ci_next;
355 for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
356 ci_next = ci; ci_next++;
357 (*ci)->serverCutText(str, len);
358 }
359}
360
Peter Ã…strandc39e0782009-01-15 12:21:42 +0000361void VNCServerST::setName(const char* name_)
362{
Adam Tkacd36b6262009-09-04 10:57:20 +0000363 name.replaceBuf(strDup(name_));
Peter Ã…strandc39e0782009-01-15 12:21:42 +0000364 std::list<VNCSConnectionST*>::iterator ci, ci_next;
365 for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
366 ci_next = ci; ci_next++;
367 (*ci)->setDesktopName(name_);
368 }
369}
370
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000371void VNCServerST::add_changed(const Region& region)
372{
Constantin Kaplinsky3ce6a722008-09-05 02:26:35 +0000373 if (comparer != 0) {
374 comparer->add_changed(region);
375 }
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000376}
377
378void VNCServerST::add_copied(const Region& dest, const Point& delta)
379{
Constantin Kaplinsky3ce6a722008-09-05 02:26:35 +0000380 if (comparer != 0) {
381 comparer->add_copied(dest, delta);
382 }
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000383}
384
385bool VNCServerST::clientsReadyForUpdate()
386{
387 std::list<VNCSConnectionST*>::iterator ci;
388 for (ci = clients.begin(); ci != clients.end(); ci++) {
389 if ((*ci)->readyForUpdate())
390 return true;
391 }
392 return false;
393}
394
395void VNCServerST::tryUpdate()
396{
397 std::list<VNCSConnectionST*>::iterator ci, ci_next;
398 for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
399 ci_next = ci; ci_next++;
400 (*ci)->writeFramebufferUpdateOrClose();
401 }
402}
403
404void VNCServerST::setCursor(int width, int height, const Point& newHotspot,
405 void* data, void* mask)
406{
407 cursor.hotspot = newHotspot;
408 cursor.setSize(width, height);
409 memcpy(cursor.data, data, cursor.dataLen());
410 memcpy(cursor.mask.buf, mask, cursor.maskLen());
411
412 cursor.crop();
413
414 renderedCursorInvalid = true;
415
416 std::list<VNCSConnectionST*>::iterator ci, ci_next;
417 for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
418 ci_next = ci; ci_next++;
419 (*ci)->renderedCursorChange();
420 (*ci)->setCursorOrClose();
421 }
422}
423
424void VNCServerST::setCursorPos(const Point& pos)
425{
426 if (!cursorPos.equals(pos)) {
427 cursorPos = pos;
428 renderedCursorInvalid = true;
429 std::list<VNCSConnectionST*>::iterator ci;
430 for (ci = clients.begin(); ci != clients.end(); ci++)
431 (*ci)->renderedCursorChange();
432 }
433}
434
435// Other public methods
436
437void VNCServerST::approveConnection(network::Socket* sock, bool accept,
438 const char* reason)
439{
440 std::list<VNCSConnectionST*>::iterator ci;
441 for (ci = clients.begin(); ci != clients.end(); ci++) {
442 if ((*ci)->getSock() == sock) {
443 (*ci)->approveConnectionOrClose(accept, reason);
444 return;
445 }
446 }
447}
448
449void VNCServerST::closeClients(const char* reason, network::Socket* except)
450{
451 std::list<VNCSConnectionST*>::iterator i, next_i;
452 for (i=clients.begin(); i!=clients.end(); i=next_i) {
453 next_i = i; next_i++;
454 if ((*i)->getSock() != except)
455 (*i)->close(reason);
456 }
457}
458
459void VNCServerST::getSockets(std::list<network::Socket*>* sockets)
460{
461 sockets->clear();
462 std::list<VNCSConnectionST*>::iterator ci;
463 for (ci = clients.begin(); ci != clients.end(); ci++) {
464 sockets->push_back((*ci)->getSock());
465 }
466 std::list<network::Socket*>::iterator si;
467 for (si = closingSockets.begin(); si != closingSockets.end(); si++) {
468 sockets->push_back(*si);
469 }
470}
471
472SConnection* VNCServerST::getSConnection(network::Socket* sock) {
473 std::list<VNCSConnectionST*>::iterator ci;
474 for (ci = clients.begin(); ci != clients.end(); ci++) {
475 if ((*ci)->getSock() == sock)
476 return *ci;
477 }
478 return 0;
479}
480
481
482// -=- Internal methods
483
484void VNCServerST::startDesktop()
485{
486 if (!desktopStarted) {
487 slog.debug("starting desktop");
488 desktop->start(this);
489 desktopStarted = true;
490 if (!pb)
491 throw Exception("SDesktop::start() did not set a valid PixelBuffer");
492 }
493}
494
495int VNCServerST::authClientCount() {
496 int count = 0;
497 std::list<VNCSConnectionST*>::iterator ci;
498 for (ci = clients.begin(); ci != clients.end(); ci++) {
499 if ((*ci)->authenticated())
500 count++;
501 }
502 return count;
503}
504
505inline bool VNCServerST::needRenderedCursor()
506{
507 std::list<VNCSConnectionST*>::iterator ci;
508 for (ci = clients.begin(); ci != clients.end(); ci++)
509 if ((*ci)->needRenderedCursor()) return true;
510 return false;
511}
512
513// checkUpdate() is called just before sending an update. It checks to see
514// what updates are pending and propagates them to the update tracker for each
515// client. It uses the ComparingUpdateTracker's compare() method to filter out
516// areas of the screen which haven't actually changed. It also checks the
517// state of the (server-side) rendered cursor, if necessary rendering it again
518// with the correct background.
519
520void VNCServerST::checkUpdate()
521{
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000522 UpdateInfo ui;
523 comparer->getUpdateInfo(&ui, pb->getRect());
524
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000525 bool renderCursor = needRenderedCursor();
526
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000527 if (ui.is_empty() && !(renderCursor && renderedCursorInvalid))
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000528 return;
529
Pierre Ossman02e43d72009-03-05 11:57:11 +0000530 Region toCheck = ui.changed.union_(ui.copied);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000531
532 if (renderCursor) {
533 Rect clippedCursorRect
534 = cursor.getRect(cursorTL()).intersect(pb->getRect());
535
536 if (!renderedCursorInvalid && (toCheck.intersect(clippedCursorRect)
537 .is_empty())) {
538 renderCursor = false;
539 } else {
540 renderedCursorTL = clippedCursorRect.tl;
541 renderedCursor.setSize(clippedCursorRect.width(),
542 clippedCursorRect.height());
543 toCheck.assign_union(clippedCursorRect);
544 }
545 }
546
547 pb->grabRegion(toCheck);
548
Constantin Kaplinskyf0b3be72008-08-21 05:22:04 +0000549 if (rfb::Server::compareFB) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000550 comparer->compare();
Constantin Kaplinskyf0b3be72008-08-21 05:22:04 +0000551 comparer->getUpdateInfo(&ui, pb->getRect());
552 }
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000553
554 if (renderCursor) {
555 pb->getImage(renderedCursor.data,
556 renderedCursor.getRect(renderedCursorTL));
557 renderedCursor.maskRect(cursor.getRect(cursorTL()
558 .subtract(renderedCursorTL)),
559 cursor.data, cursor.mask.buf);
560 renderedCursorInvalid = false;
561 }
562
563 std::list<VNCSConnectionST*>::iterator ci, ci_next;
564 for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
565 ci_next = ci; ci_next++;
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000566 (*ci)->add_copied(ui.copied, ui.copy_delta);
567 (*ci)->add_changed(ui.changed);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000568 }
569
570 comparer->clear();
571}
572
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000573void VNCServerST::getConnInfo(ListConnInfo * listConn)
574{
575 listConn->Clear();
576 listConn->setDisable(getDisable());
577 if (clients.empty())
578 return;
579 std::list<VNCSConnectionST*>::iterator i;
580 for (i = clients.begin(); i != clients.end(); i++)
581 listConn->addInfo((void*)(*i), (*i)->getSock()->getPeerAddress(),
582 (*i)->getStartTime(), (*i)->getStatus());
583}
584
585void VNCServerST::setConnStatus(ListConnInfo* listConn)
586{
587 setDisable(listConn->getDisable());
588 if (listConn->Empty() || clients.empty()) return;
589 for (listConn->iBegin(); !listConn->iEnd(); listConn->iNext()) {
590 VNCSConnectionST* conn = (VNCSConnectionST*)listConn->iGetConn();
591 std::list<VNCSConnectionST*>::iterator i;
592 for (i = clients.begin(); i != clients.end(); i++) {
593 if ((*i) == conn) {
594 int status = listConn->iGetStatus();
595 if (status == 3) {
596 (*i)->close(0);
597 } else {
598 (*i)->setStatus(status);
599 }
600 break;
601 }
602 }
603 }
604}
Constantin Kaplinsky9d1fc6c2008-06-14 05:23:10 +0000605
Pierre Ossman04e62db2009-03-23 16:57:07 +0000606void VNCServerST::notifyScreenLayoutChange(VNCSConnectionST* requester)
607{
608 std::list<VNCSConnectionST*>::iterator ci, ci_next;
609 for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
610 ci_next = ci; ci_next++;
611 if ((*ci) == requester)
612 continue;
613 (*ci)->screenLayoutChange(reasonOtherClient);
614 }
615}