#include <rfb/VNCServer.h>
#include <rfb/InputHandler.h>
#include <rfb/Exception.h>
+#include <rfb/screenTypes.h>
namespace rfb {
virtual Point getFbSize() = 0;
+ // setScreenLayout() requests to reconfigure the framebuffer and/or
+ // the layout of screens.
+ virtual unsigned int setScreenLayout(int fb_width, int fb_height,
+ const ScreenSet& layout) {
+ return resultProhibited;
+ }
+
// InputHandler interface
// pointerEvent(), keyEvent() and clientCutText() are called in response to
// the relevant RFB protocol messages from clients.
#include <rfb/screenTypes.h>
#include <rfb/Encoder.h>
#include <rfb/PixelBuffer.h>
+#include <rfb/ScreenSet.h>
namespace rdr { class OutStream; }
// writeSetDesktopSize() on a V3 writer won't actually write immediately,
// but will write the relevant pseudo-rectangle as part of the next update.
virtual bool writeSetDesktopSize()=0;
- // Same thing for the extended version
- virtual bool writeExtendedDesktopSize(rdr::U16 error = resultUnsolicited)=0;
+ // Same thing for the extended version. The first version queues up a
+ // generic update of the current server state, but the second queues a
+ // specific message.
+ virtual bool writeExtendedDesktopSize()=0;
+ virtual bool writeExtendedDesktopSize(rdr::U16 reason, rdr::U16 result,
+ int fb_width, int fb_height,
+ const ScreenSet& layout)=0;
virtual bool writeSetDesktopName()=0;
return true;
}
-bool SMsgWriterV3::writeExtendedDesktopSize(rdr::U16 error) {
+bool SMsgWriterV3::writeExtendedDesktopSize() {
if (!cp->supportsExtendedDesktopSize) return false;
- if (error == resultUnsolicited)
- needExtendedDesktopSize = true;
- else
- edsErrors.push_back(error);
+ needExtendedDesktopSize = true;
+ return true;
+}
+
+bool SMsgWriterV3::writeExtendedDesktopSize(rdr::U16 reason, rdr::U16 result,
+ int fb_width, int fb_height,
+ const ScreenSet& layout) {
+ ExtendedDesktopSizeMsg msg;
+
+ if (!cp->supportsExtendedDesktopSize) return false;
+
+ msg.reason = reason;
+ msg.result = result;
+ msg.fb_width = fb_width;
+ msg.fb_height = fb_height;
+ msg.layout = layout;
+
+ extendedDesktopSizeMsgs.push_back(msg);
+
return true;
}
if (wsccb) nRects++;
if (needSetDesktopSize) nRects++;
if (needExtendedDesktopSize) nRects++;
- if (!edsErrors.empty()) nRects += edsErrors.size();
+ if (!extendedDesktopSizeMsgs.empty()) nRects += extendedDesktopSizeMsgs.size();
if (needSetDesktopName) nRects++;
os->writeU16(nRects);
nRectsInUpdate = 0;
void SMsgWriterV3::writeFramebufferUpdateEnd()
{
- /* Start with responses to SetDesktopSize messages */
- if (!edsErrors.empty()) {
- std::list<rdr::U16>::const_iterator iter;
+ /* Start with specific ExtendedDesktopSize messages */
+ if (!extendedDesktopSizeMsgs.empty()) {
+ std::list<ExtendedDesktopSizeMsg>::const_iterator ri;
+ ScreenSet::const_iterator si;
if (!cp->supportsExtendedDesktopSize)
throw Exception("Client does not support extended desktop resize");
- if ((nRectsInUpdate += edsErrors.size()) > nRectsInHeader && nRectsInHeader)
- throw Exception("SMsgWriterV3 setExtendedDesktopSize: nRects out of sync");
-
- for (iter = edsErrors.begin();iter != edsErrors.end();iter++) {
- os->writeU16(1);
- os->writeU16(*iter);
- os->writeU16(0);
- os->writeU16(0);
+ if ((nRectsInUpdate += extendedDesktopSizeMsgs.size()) > nRectsInHeader && nRectsInHeader)
+ throw Exception("SMsgWriterV3 SetDesktopSize reply: nRects out of sync");
+
+ for (ri = extendedDesktopSizeMsgs.begin();ri != extendedDesktopSizeMsgs.end();++ri) {
+ os->writeU16(ri->reason);
+ os->writeU16(ri->result);
+ os->writeU16(ri->fb_width);
+ os->writeU16(ri->fb_height);
os->writeU32(pseudoEncodingExtendedDesktopSize);
- os->writeU8(0); // # screens
+
+ os->writeU8(ri->layout.num_screens());
os->pad(3);
+
+ for (si = ri->layout.begin();si != ri->layout.end();++si) {
+ os->writeU32(si->id);
+ os->writeU16(si->dimensions.tl.x);
+ os->writeU16(si->dimensions.tl.y);
+ os->writeU16(si->dimensions.width());
+ os->writeU16(si->dimensions.height());
+ os->writeU32(si->flags);
+ }
}
- edsErrors.clear();
+ extendedDesktopSizeMsgs.clear();
}
/* Send this before SetDesktopSize to make life easier on the clients */
bool SMsgWriterV3::needFakeUpdate()
{
- return wsccb || needSetDesktopSize || needSetDesktopName;
+ return wsccb || needSetDesktopSize || needExtendedDesktopSize ||
+ !extendedDesktopSizeMsgs.empty() || needSetDesktopName;
}
void SMsgWriterV3::startRect(const Rect& r, unsigned int encoding)
#define __RFB_SMSGWRITERV3_H__
#include <list>
+#include <utility>
#include <rfb/SMsgWriter.h>
virtual void startMsg(int type);
virtual void endMsg();
virtual bool writeSetDesktopSize();
- virtual bool writeExtendedDesktopSize(rdr::U16 error);
+ virtual bool writeExtendedDesktopSize();
+ virtual bool writeExtendedDesktopSize(rdr::U16 reason, rdr::U16 result,
+ int fb_width, int fb_height,
+ const ScreenSet& layout);
virtual bool writeSetDesktopName();
virtual void cursorChange(WriteSetCursorCallback* cb);
virtual void writeSetCursor(int width, int height, const Point& hotspot,
WriteSetCursorCallback* wsccb;
bool needSetDesktopSize;
bool needExtendedDesktopSize;
- std::list<rdr::U16> edsErrors;
bool needSetDesktopName;
bool needLastRect;
+
+ typedef struct {
+ rdr::U16 reason, result;
+ int fb_width, fb_height;
+ ScreenSet layout;
+ } ExtendedDesktopSizeMsg;
+ std::list<ExtendedDesktopSizeMsg> extendedDesktopSizeMsgs;
+
};
}
#endif
#ifndef __RFB_SCREENSET_INCLUDED__
#define __RFB_SCREENSET_INCLUDED__
+#include <stdio.h>
+
#include <rfb/Rect.h>
#include <list>
#include <set>
Screen(void) : id(0), flags(0) {};
Screen(rdr::U32 id_, int x_, int y_, int w_, int h_, rdr::U32 flags_) :
id(id_), dimensions(x_, y_, x_+w_, y_+h_), flags(flags_) {};
+
+ inline bool operator==(const Screen& r) const {
+ if (id != r.id)
+ return false;
+ if (!dimensions.equals(r.dimensions))
+ return false;
+ if (flags != r.flags)
+ return false;
+ return true;
+ }
+
rdr::U32 id;
Rect dimensions;
rdr::U32 flags;
return true;
};
+ inline void debug_print(void) const {
+ std::list<Screen>::const_iterator iter;
+ fprintf(stderr, "%d screens\n", num_screens());
+ for (iter = screens.begin();iter != screens.end();++iter) {
+ fprintf(stderr, " %10d (0x%08x): %dx%d+%d+%d (flags 0x%08x)\n",
+ (int)iter->id, (unsigned)iter->id,
+ iter->dimensions.width(), iter->dimensions.height(),
+ iter->dimensions.tl.x, iter->dimensions.tl.y,
+ (unsigned)iter->flags);
+ }
+ };
+
+ // FIXME: List order shouldn't matter
+ inline bool operator==(const ScreenSet& r) const { return screens == r.screens; }
+ inline bool operator!=(const ScreenSet& r) const { return screens != r.screens; }
+
std::list<Screen> screens;
};
}
}
+void VNCSConnectionST::screenLayoutChange(rdr::U16 reason)
+{
+ try {
+ if (!authenticated())
+ return;
+
+ cp.screenLayout = server->screenLayout;
+ if (state() == RFBSTATE_NORMAL) {
+ writer()->writeExtendedDesktopSize(reason, 0, cp.width, cp.height,
+ cp.screenLayout);
+ }
+
+ if (writer()->needFakeUpdate())
+ writeFramebufferUpdate();
+ } catch(rdr::Exception &e) {
+ close(e.str());
+ }
+}
+
void VNCSConnectionST::setColourMapEntriesOrClose(int firstColour,int nColours)
{
try {
void VNCSConnectionST::setDesktopSize(int fb_width, int fb_height,
const ScreenSet& layout)
{
- vlog.info("Rejecting client request to change desktop size");
- writer()->writeExtendedDesktopSize(resultProhibited);
+ unsigned int result;
+
+ // Don't bother the desktop with an invalid configuration
+ if (!layout.validate(fb_width, fb_height)) {
+ writer()->writeExtendedDesktopSize(reasonClient, resultInvalid,
+ fb_width, fb_height, layout);
+ if (writer()->needFakeUpdate())
+ writeFramebufferUpdate();
+ return;
+ }
+
+ // FIXME: the desktop will call back to VNCServerST and an extra set
+ // of ExtendedDesktopSize messages will be sent. This is okay
+ // protocol-wise, but unnecessary.
+ result = server->desktop->setScreenLayout(fb_width, fb_height, layout);
+
+ // Always send back a reply to the requesting client
+ writer()->writeExtendedDesktopSize(reasonClient, result,
+ fb_width, fb_height, layout);
+ if (writer()->needFakeUpdate())
+ writeFramebufferUpdate();
+
+ // But only notify other clients on success
+ if (result == resultSuccess) {
+ if (server->screenLayout != layout)
+ throw Exception("Desktop configured a different screen layout than requested");
+ server->notifyScreenLayoutChange(this);
+ }
}
void VNCSConnectionST::setInitialColourMap()
void writeFramebufferUpdateOrClose();
void pixelBufferChange();
+ void screenLayoutChange(rdr::U16 reason);
void setColourMapEntriesOrClose(int firstColour, int nColours);
void bell();
void serverCutText(const char *str, int len);
#include <rfb/UpdateTracker.h>
#include <rfb/SSecurity.h>
+#include <rfb/ScreenSet.h>
namespace rfb {
class VNCServer : public UpdateTracker {
public:
- // setPixelBuffer() tells the server to use the given pixel buffer. If
- // this differs in size from the previous pixel buffer, this may result in
- // protocol messages being sent, or clients being disconnected.
+ // setPixelBuffer() tells the server to use the given pixel buffer (and
+ // optionally a modified screen layout). If this differs in size from
+ // the previous pixel buffer, this may result in protocol messages being
+ // sent, or clients being disconnected.
+ virtual void setPixelBuffer(PixelBuffer* pb, const ScreenSet& layout) = 0;
virtual void setPixelBuffer(PixelBuffer* pb) = 0;
+ // setScreenLayout() modifies the current screen layout without changing
+ // the pixelbuffer. Clients will be notified of the new layout.
+ virtual void setScreenLayout(const ScreenSet& layout) = 0;
+
// getPixelBuffer() returns a pointer to the PixelBuffer object.
virtual PixelBuffer* getPixelBuffer() const = 0;
// VNCServer methods
-void VNCServerST::setPixelBuffer(PixelBuffer* pb_)
+void VNCServerST::setPixelBuffer(PixelBuffer* pb_, const ScreenSet& layout)
{
pb = pb_;
delete comparer;
comparer = 0;
- if (pb) {
- comparer = new ComparingUpdateTracker(pb);
- cursor.setPF(pb->getPF());
- renderedCursor.setPF(pb->getPF());
-
- // Check that the screen layout is still valid
- if (!screenLayout.validate(pb->width(), pb->height())) {
- Rect fbRect;
- ScreenSet::iterator iter, iter_next;
-
- fbRect.setXYWH(0, 0, pb->width(), pb->height());
-
- for (iter = screenLayout.begin();iter != screenLayout.end();iter = iter_next) {
- iter_next = iter; ++iter_next;
- if (iter->dimensions.enclosed_by(fbRect))
- continue;
- iter->dimensions = iter->dimensions.intersect(fbRect);
- if (iter->dimensions.is_empty()) {
- slog.info("Removing screen %d (%x) as it is completely outside the new framebuffer",
- (int)iter->id, (unsigned)iter->id);
- screenLayout.remove_screen(iter->id);
- }
- }
- }
+ screenLayout = layout;
- if (screenLayout.num_screens() == 0) {
- // Boot strap the screen layout
- screenLayout.add_screen(Screen(0, 0, 0, pb->width(), pb->height(), 0));
- }
+ if (!pb) {
+ if (desktopStarted)
+ throw Exception("setPixelBuffer: null PixelBuffer when desktopStarted?");
+ return;
+ }
- std::list<VNCSConnectionST*>::iterator ci, ci_next;
- for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
- ci_next = ci; ci_next++;
- (*ci)->pixelBufferChange();
- }
- } else {
+ comparer = new ComparingUpdateTracker(pb);
+ cursor.setPF(pb->getPF());
+ renderedCursor.setPF(pb->getPF());
+
+ // Make sure that we have at least one screen
+ if (screenLayout.num_screens() == 0)
+ screenLayout.add_screen(Screen(0, 0, 0, pb->width(), pb->height(), 0));
+
+ std::list<VNCSConnectionST*>::iterator ci, ci_next;
+ for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
+ ci_next = ci; ci_next++;
+ (*ci)->pixelBufferChange();
+ // Since the new pixel buffer means an ExtendedDesktopSize needs to
+ // be sent anyway, we don't need to call screenLayoutChange.
+ }
+}
+
+void VNCServerST::setPixelBuffer(PixelBuffer* pb_)
+{
+ ScreenSet layout;
+
+ if (!pb_) {
if (desktopStarted)
throw Exception("setPixelBuffer: null PixelBuffer when desktopStarted?");
+ return;
+ }
+
+ layout = screenLayout;
+
+ // Check that the screen layout is still valid
+ if (!layout.validate(pb_->width(), pb_->height())) {
+ Rect fbRect;
+ ScreenSet::iterator iter, iter_next;
+
+ fbRect.setXYWH(0, 0, pb_->width(), pb_->height());
+
+ for (iter = layout.begin();iter != layout.end();iter = iter_next) {
+ iter_next = iter; ++iter_next;
+ if (iter->dimensions.enclosed_by(fbRect))
+ continue;
+ iter->dimensions = iter->dimensions.intersect(fbRect);
+ if (iter->dimensions.is_empty()) {
+ slog.info("Removing screen %d (%x) as it is completely outside the new framebuffer",
+ (int)iter->id, (unsigned)iter->id);
+ layout.remove_screen(iter->id);
+ }
+ }
+ }
+
+ setPixelBuffer(pb_, layout);
+}
+
+void VNCServerST::setScreenLayout(const ScreenSet& layout)
+{
+ if (!pb)
+ throw Exception("setScreenLayout: new screen layout without a PixelBuffer");
+ if (!layout.validate(pb->width(), pb->height()))
+ throw Exception("setScreenLayout: invalid screen layout");
+
+ std::list<VNCSConnectionST*>::iterator ci, ci_next;
+ for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
+ ci_next = ci; ci_next++;
+ (*ci)->screenLayoutChange(reasonServer);
}
}
}
}
+void VNCServerST::notifyScreenLayoutChange(VNCSConnectionST* requester)
+{
+ std::list<VNCSConnectionST*>::iterator ci, ci_next;
+ for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
+ ci_next = ci; ci_next++;
+ if ((*ci) == requester)
+ continue;
+ (*ci)->screenLayoutChange(reasonOtherClient);
+ }
+}
// Methods overridden from VNCServer
+ virtual void setPixelBuffer(PixelBuffer* pb, const ScreenSet& layout);
virtual void setPixelBuffer(PixelBuffer* pb);
+ virtual void setScreenLayout(const ScreenSet& layout);
virtual PixelBuffer* getPixelBuffer() const { return pb; }
virtual void setColourMapEntries(int firstColour=0, int nColours=0);
virtual void serverCutText(const char* str, int len);
bool needRenderedCursor();
void checkUpdate();
+ void notifyScreenLayoutChange(VNCSConnectionST *requester);
+
SSecurityFactory* securityFactory;
QueryConnectionHandler* queryConnectionHandler;
KeyRemapper* keyRemapper;