Merge branch 'fps' of https://github.com/CendioOssman/tigervnc
diff --git a/BUILDING.txt b/BUILDING.txt
index 9987bd7..9449810 100644
--- a/BUILDING.txt
+++ b/BUILDING.txt
@@ -503,7 +503,7 @@
and releases can be found in the "contrib/rpm/el{5,6}" directories
of the TigerVNC subversion trunk. All external source tarballs
must be fetched manually and placed into the 'SOURCES' directory
-under the rpmbuild root. Additonally, the following macros need
+under the rpmbuild root. Additionally, the following macros need
to be defined:
EL6:
diff --git a/cmake/Modules/CMakeMacroLibtoolFile.cmake b/cmake/Modules/CMakeMacroLibtoolFile.cmake
index a54d994..c882c4a 100644
--- a/cmake/Modules/CMakeMacroLibtoolFile.cmake
+++ b/cmake/Modules/CMakeMacroLibtoolFile.cmake
@@ -89,7 +89,7 @@
endif()
else()
# Detected a static library. Check whether the library pathname is
- # absolute and, if not, use find_library() to get the abolute path.
+ # absolute and, if not, use find_library() to get the absolute path.
get_filename_component(_name ${library} NAME)
string(REPLACE "${_name}" "" _path ${library})
if(NOT "${_path}" STREQUAL "")
@@ -132,6 +132,10 @@
file(APPEND ${_laname} "dlpreopen=''\n\n")
file(APPEND ${_laname} "libdir='/usr/lib'\n\n")
+ # Make sure the timestamp is updated to trigger other make invocations
+ add_custom_command(TARGET ${_target} POST_BUILD COMMAND
+ "${CMAKE_COMMAND}" -E touch "${_laname}")
+
# Add custom command to symlink the static library so that autotools finds
# the library in .libs. These are executed after the specified target build.
diff --git a/common/Xregion/Region.c b/common/Xregion/Region.c
index 5209443..1acf581 100644
--- a/common/Xregion/Region.c
+++ b/common/Xregion/Region.c
@@ -1310,7 +1310,7 @@
else if (r2->x1 <= x1)
{
/*
- * Subtrahend preceeds minuend: nuke left edge of minuend.
+ * Subtrahend preceds minuend: nuke left edge of minuend.
*/
x1 = r2->x2;
if (x1 >= r1->x2)
diff --git a/common/rfb/CConnection.cxx b/common/rfb/CConnection.cxx
index 35be946..2020418 100644
--- a/common/rfb/CConnection.cxx
+++ b/common/rfb/CConnection.cxx
@@ -1,4 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright 2011-2017 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -320,6 +321,13 @@
CMsgHandler::setExtendedDesktopSize(reason, result, w, h, layout);
}
+void CConnection::readAndDecodeRect(const Rect& r, int encoding,
+ ModifiablePixelBuffer* pb)
+{
+ decoder.decodeRect(r, encoding, pb);
+ decoder.flush();
+}
+
void CConnection::framebufferUpdateStart()
{
CMsgHandler::framebufferUpdateStart();
diff --git a/common/rfb/CConnection.h b/common/rfb/CConnection.h
index 6bc7a38..799a9c2 100644
--- a/common/rfb/CConnection.h
+++ b/common/rfb/CConnection.h
@@ -1,4 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright 2011-2017 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -99,6 +100,9 @@
int w, int h,
const ScreenSet& layout);
+ virtual void readAndDecodeRect(const Rect& r, int encoding,
+ ModifiablePixelBuffer* pb);
+
virtual void framebufferUpdateStart();
virtual void framebufferUpdateEnd();
virtual void dataRect(const Rect& r, int encoding);
diff --git a/common/rfb/CMsgHandler.h b/common/rfb/CMsgHandler.h
index 7d2cdc2..993276e 100644
--- a/common/rfb/CMsgHandler.h
+++ b/common/rfb/CMsgHandler.h
@@ -50,13 +50,16 @@
int w, int h,
const ScreenSet& layout);
virtual void setCursor(int width, int height, const Point& hotspot,
- void* data, void* mask) = 0;
+ const rdr::U8* data) = 0;
virtual void setPixelFormat(const PixelFormat& pf);
virtual void setName(const char* name);
virtual void fence(rdr::U32 flags, unsigned len, const char data[]);
virtual void endOfContinuousUpdates();
virtual void serverInit() = 0;
+ virtual void readAndDecodeRect(const Rect& r, int encoding,
+ ModifiablePixelBuffer* pb) = 0;
+
virtual void framebufferUpdateStart();
virtual void framebufferUpdateEnd();
virtual void dataRect(const Rect& r, int encoding) = 0;
diff --git a/common/rfb/CMsgReader.cxx b/common/rfb/CMsgReader.cxx
index 96ddf44..7233fbd 100644
--- a/common/rfb/CMsgReader.cxx
+++ b/common/rfb/CMsgReader.cxx
@@ -1,5 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright 2009-2014 Pierre Ossman for Cendio AB
+ * Copyright 2009-2017 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,7 +16,10 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
+
+#include <assert.h>
#include <stdio.h>
+
#include <rfb/msgTypes.h>
#include <rdr/InStream.h>
#include <rfb/Exception.h>
@@ -88,9 +91,15 @@
case pseudoEncodingLastRect:
nUpdateRectsLeft = 1; // this rectangle is the last one
break;
+ case pseudoEncodingXCursor:
+ readSetXCursor(w, h, Point(x,y));
+ break;
case pseudoEncodingCursor:
readSetCursor(w, h, Point(x,y));
break;
+ case pseudoEncodingCursorWithAlpha:
+ readSetCursorWithAlpha(w, h, Point(x,y));
+ break;
case pseudoEncodingDesktopName:
readSetDesktopName(x, y, w, h);
break;
@@ -191,6 +200,61 @@
handler->dataRect(r, encoding);
}
+void CMsgReader::readSetXCursor(int width, int height, const Point& hotspot)
+{
+ rdr::U8 pr, pg, pb;
+ rdr::U8 sr, sg, sb;
+ int data_len = ((width+7)/8) * height;
+ int mask_len = ((width+7)/8) * height;
+ rdr::U8Array data(data_len);
+ rdr::U8Array mask(mask_len);
+
+ int x, y;
+ rdr::U8 buf[width*height*4];
+ rdr::U8* out;
+
+ if (width * height) {
+ pr = is->readU8();
+ pg = is->readU8();
+ pb = is->readU8();
+
+ sr = is->readU8();
+ sg = is->readU8();
+ sb = is->readU8();
+
+ is->readBytes(data.buf, data_len);
+ is->readBytes(mask.buf, mask_len);
+ }
+
+ int maskBytesPerRow = (width+7)/8;
+ out = buf;
+ for (y = 0;y < height;y++) {
+ for (x = 0;x < width;x++) {
+ int byte = y * maskBytesPerRow + x / 8;
+ int bit = 7 - x % 8;
+
+ if (data.buf[byte] & (1 << bit)) {
+ out[0] = pr;
+ out[1] = pg;
+ out[2] = pb;
+ } else {
+ out[0] = sr;
+ out[1] = sg;
+ out[2] = sb;
+ }
+
+ if (mask.buf[byte] & (1 << bit))
+ out[3] = 255;
+ else
+ out[3] = 0;
+
+ out += 4;
+ }
+ }
+
+ handler->setCursor(width, height, hotspot, buf);
+}
+
void CMsgReader::readSetCursor(int width, int height, const Point& hotspot)
{
int data_len = width * height * (handler->cp.pf().bpp/8);
@@ -198,10 +262,79 @@
rdr::U8Array data(data_len);
rdr::U8Array mask(mask_len);
+ int x, y;
+ rdr::U8 buf[width*height*4];
+ rdr::U8* in;
+ rdr::U8* out;
+
is->readBytes(data.buf, data_len);
is->readBytes(mask.buf, mask_len);
- handler->setCursor(width, height, hotspot, data.buf, mask.buf);
+ int maskBytesPerRow = (width+7)/8;
+ in = data.buf;
+ out = buf;
+ for (y = 0;y < height;y++) {
+ for (x = 0;x < width;x++) {
+ int byte = y * maskBytesPerRow + x / 8;
+ int bit = 7 - x % 8;
+
+ handler->cp.pf().rgbFromBuffer(out, in, 1);
+
+ if (mask.buf[byte] & (1 << bit))
+ out[3] = 255;
+ else
+ out[3] = 0;
+
+ in += handler->cp.pf().bpp/8;
+ out += 4;
+ }
+ }
+
+ handler->setCursor(width, height, hotspot, buf);
+}
+
+void CMsgReader::readSetCursorWithAlpha(int width, int height, const Point& hotspot)
+{
+ int encoding;
+
+ const PixelFormat rgbaPF(32, 32, false, true, 255, 255, 255, 16, 8, 0);
+ ManagedPixelBuffer pb(rgbaPF, width, height);
+ PixelFormat origPF;
+
+ rdr::U8* buf;
+ int stride;
+
+ encoding = is->readS32();
+
+ origPF = handler->cp.pf();
+ handler->cp.setPF(rgbaPF);
+ handler->readAndDecodeRect(pb.getRect(), encoding, &pb);
+ handler->cp.setPF(origPF);
+
+ // On-wire data has pre-multiplied alpha, but we store it
+ // non-pre-multiplied
+ buf = pb.getBufferRW(pb.getRect(), &stride);
+ assert(stride == width);
+
+ for (int i = 0;i < pb.area();i++) {
+ rdr::U8 alpha;
+
+ alpha = buf[3];
+ if (alpha == 0)
+ alpha = 1; // Avoid division by zero
+
+ buf[0] = (unsigned)buf[0] * 255/alpha;
+ buf[1] = (unsigned)buf[1] * 255/alpha;
+ buf[2] = (unsigned)buf[2] * 255/alpha;
+ buf[3] = alpha;
+
+ buf += 4;
+ }
+
+ pb.commitBufferRW(pb.getRect());
+
+ handler->setCursor(width, height, hotspot,
+ pb.getBuffer(pb.getRect(), &stride));
}
void CMsgReader::readSetDesktopName(int x, int y, int w, int h)
diff --git a/common/rfb/CMsgReader.h b/common/rfb/CMsgReader.h
index 42c6496..ff73414 100644
--- a/common/rfb/CMsgReader.h
+++ b/common/rfb/CMsgReader.h
@@ -60,7 +60,9 @@
void readRect(const Rect& r, int encoding);
+ void readSetXCursor(int width, int height, const Point& hotspot);
void readSetCursor(int width, int height, const Point& hotspot);
+ void readSetCursorWithAlpha(int width, int height, const Point& hotspot);
void readSetDesktopName(int x, int y, int w, int h);
void readExtendedDesktopSize(int x, int y, int w, int h);
diff --git a/common/rfb/CMsgWriter.cxx b/common/rfb/CMsgWriter.cxx
index 6536eaf..8576d8f 100644
--- a/common/rfb/CMsgWriter.cxx
+++ b/common/rfb/CMsgWriter.cxx
@@ -71,8 +71,11 @@
int nEncodings = 0;
rdr::U32 encodings[encodingMax+3];
- if (cp->supportsLocalCursor)
+ if (cp->supportsLocalCursor) {
+ encodings[nEncodings++] = pseudoEncodingXCursor;
encodings[nEncodings++] = pseudoEncodingCursor;
+ encodings[nEncodings++] = pseudoEncodingCursorWithAlpha;
+ }
if (cp->supportsDesktopResize)
encodings[nEncodings++] = pseudoEncodingDesktopSize;
if (cp->supportsExtendedDesktopSize)
diff --git a/common/rfb/ConnParams.cxx b/common/rfb/ConnParams.cxx
index ab3b884..9ee1d9c 100644
--- a/common/rfb/ConnParams.cxx
+++ b/common/rfb/ConnParams.cxx
@@ -31,6 +31,7 @@
: majorVersion(0), minorVersion(0),
width(0), height(0), useCopyRect(false),
supportsLocalCursor(false), supportsLocalXCursor(false),
+ supportsLocalCursorWithAlpha(false),
supportsDesktopResize(false), supportsExtendedDesktopSize(false),
supportsDesktopRename(false), supportsLastRect(false),
supportsSetDesktopSize(false), supportsFence(false),
@@ -39,11 +40,13 @@
subsampling(subsampleUndefined), name_(0), verStrPos(0)
{
setName("");
+ cursor_ = new Cursor(0, 0, Point(), NULL);
}
ConnParams::~ConnParams()
{
delete [] name_;
+ delete cursor_;
}
bool ConnParams::readVersion(rdr::InStream* is, bool* done)
@@ -86,17 +89,8 @@
void ConnParams::setCursor(const Cursor& other)
{
- const rdr::U8* data;
- int stride;
-
- cursor_.hotspot = other.hotspot;
- cursor_.setPF(other.getPF());
- cursor_.setSize(other.width(), other.height());
-
- data = other.getBuffer(other.getRect(), &stride);
- cursor_.imageRect(cursor_.getRect(), data, stride);
-
- memcpy(cursor_.mask.buf, other.mask.buf, cursor_.maskLen());
+ delete cursor_;
+ cursor_ = new Cursor(other);
}
bool ConnParams::supportsEncoding(rdr::S32 encoding) const
@@ -108,6 +102,7 @@
{
useCopyRect = false;
supportsLocalCursor = false;
+ supportsLocalCursorWithAlpha = false;
supportsDesktopResize = false;
supportsExtendedDesktopSize = false;
supportsLocalXCursor = false;
@@ -131,6 +126,9 @@
case pseudoEncodingXCursor:
supportsLocalXCursor = true;
break;
+ case pseudoEncodingCursorWithAlpha:
+ supportsLocalCursorWithAlpha = true;
+ break;
case pseudoEncodingDesktopSize:
supportsDesktopResize = true;
break;
diff --git a/common/rfb/ConnParams.h b/common/rfb/ConnParams.h
index 9e647ba..5e53893 100644
--- a/common/rfb/ConnParams.h
+++ b/common/rfb/ConnParams.h
@@ -77,7 +77,7 @@
const char* name() const { return name_; }
void setName(const char* name);
- const Cursor& cursor() const { return cursor_; }
+ const Cursor& cursor() const { return *cursor_; }
void setCursor(const Cursor& cursor);
bool supportsEncoding(rdr::S32 encoding) const;
@@ -88,6 +88,7 @@
bool supportsLocalCursor;
bool supportsLocalXCursor;
+ bool supportsLocalCursorWithAlpha;
bool supportsDesktopResize;
bool supportsExtendedDesktopSize;
bool supportsDesktopRename;
@@ -106,7 +107,7 @@
PixelFormat pf_;
char* name_;
- Cursor cursor_;
+ Cursor* cursor_;
std::set<rdr::S32> encodings_;
char verStr[13];
int verStrPos;
diff --git a/common/rfb/Cursor.cxx b/common/rfb/Cursor.cxx
index 3bb8e0f..99df82d 100644
--- a/common/rfb/Cursor.cxx
+++ b/common/rfb/Cursor.cxx
@@ -1,5 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright 2014 Pierre Ossman for Cendio AB
+ * Copyright 2014-2017 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,98 +26,179 @@
static LogWriter vlog("Cursor");
-void Cursor::setSize(int w, int h) {
- int oldMaskLen = maskLen();
- ManagedPixelBuffer::setSize(w, h);
- if (maskLen() > oldMaskLen) {
- delete [] mask.buf;
- mask.buf = new rdr::U8[maskLen()];
+Cursor::Cursor(int width, int height, const Point& hotspot,
+ const rdr::U8* data) :
+ width_(width), height_(height), hotspot_(hotspot)
+{
+ this->data = new rdr::U8[width_*height_*4];
+ memcpy(this->data, data, width_*height_*4);
+}
+
+Cursor::Cursor(const Cursor& other) :
+ width_(other.width_), height_(other.height_),
+ hotspot_(other.hotspot_)
+{
+ data = new rdr::U8[width_*height_*4];
+ memcpy(data, other.data, width_*height_*4);
+}
+
+Cursor::~Cursor()
+{
+ delete [] data;
+}
+
+static unsigned short pow223[] = { 0, 30, 143, 355, 676, 1113, 1673,
+ 2361, 3181, 4139, 5237, 6479, 7869,
+ 9409, 11103, 12952, 14961, 17130,
+ 19462, 21960, 24626, 27461, 30467,
+ 33647, 37003, 40535, 44245, 48136,
+ 52209, 56466, 60907, 65535 };
+
+static unsigned short ipow(unsigned short val, unsigned short lut[])
+{
+ int idx = val >> (16-5);
+ int a, b;
+
+ if (val < 0x8000) {
+ a = lut[idx];
+ b = lut[idx+1];
+ } else {
+ a = lut[idx-1];
+ b = lut[idx];
+ }
+
+ return (val & 0x7ff) * (b-a) / 0x7ff + a;
+}
+
+static unsigned short srgb_to_lin(unsigned char srgb)
+{
+ return ipow((unsigned)srgb * 65535 / 255, pow223);
+}
+
+// Floyd-Steinberg dithering
+static void dither(int width, int height, int* data)
+{
+ for (int y = 0; y < height; y++) {
+ for (int x_ = 0; x_ < width; x_++) {
+ int x = (y & 1) ? (width - x_ - 1) : x_;
+ int error;
+
+ if (data[x] > 32767) {
+ error = data[x] - 65535;
+ data[x] = 65535;
+ } else {
+ error = data[x] - 0;
+ data[x] = 0;
+ }
+
+ if (y & 1) {
+ if (x > 0) {
+ data[x - 1] += error * 7 / 16;
+ }
+ if ((y + 1) < height) {
+ if (x > 0)
+ data[x - 1 + width] += error * 3 / 16;
+ data[x + width] += error * 5 / 16;
+ if ((x + 1) < width)
+ data[x + 1] += error * 1 / 16;
+ }
+ } else {
+ if ((x + 1) < width) {
+ data[x + 1] += error * 7 / 16;
+ }
+ if ((y + 1) < height) {
+ if ((x + 1) < width)
+ data[x + 1 + width] += error * 3 / 16;
+ data[x + width] += error * 5 / 16;
+ if (x > 0)
+ data[x - 1] += error * 1 / 16;
+ }
+ }
+ }
+ data += width;
}
}
-void Cursor::drawOutline(const Pixel& c)
+rdr::U8* Cursor::getBitmap() const
{
- Cursor outlined;
- rdr::U8 cbuf[4];
-
- // Create a mirror of the existing cursor
- outlined.setPF(getPF());
- outlined.setSize(width(), height());
- outlined.hotspot = hotspot;
-
- // Clear the mirror's background to the outline colour
- outlined.getPF().bufferFromPixel(cbuf, c);
- outlined.fillRect(getRect(), cbuf);
-
- // Blit the existing cursor, using its mask
- outlined.maskRect(getRect(), data, mask.buf);
-
- // Now just adjust the mask to add the outline. The outline pixels
- // will already be the right colour. :)
- int maskBytesPerRow = (width() + 7) / 8;
+ // First step is converting to luminance
+ int luminance[width()*height()];
+ int *lum_ptr = luminance;
+ const rdr::U8 *data_ptr = data;
for (int y = 0; y < height(); y++) {
- for (int byte=0; byte<maskBytesPerRow; byte++) {
- rdr::U8 m8 = mask.buf[y*maskBytesPerRow + byte];
+ for (int x = 0; x < width(); x++) {
+ // Use BT.709 coefficients for grayscale
+ *lum_ptr = 0;
+ *lum_ptr += (int)srgb_to_lin(data_ptr[0]) * 6947; // 0.2126
+ *lum_ptr += (int)srgb_to_lin(data_ptr[1]) * 23436; // 0.7152
+ *lum_ptr += (int)srgb_to_lin(data_ptr[2]) * 2366; // 0.0722
+ *lum_ptr /= 32768;
- // Handle above & below outline
- if (y > 0) m8 |= mask.buf[(y-1)*maskBytesPerRow + byte];
- if (y < height()-1) m8 |= mask.buf[(y+1)*maskBytesPerRow + byte];
-
- // Left outline
- m8 |= mask.buf[y*maskBytesPerRow + byte] << 1;
- if (byte < maskBytesPerRow-1)
- m8 |= (mask.buf[y*maskBytesPerRow + byte + 1] >> 7) & 1;
-
- // Right outline
- m8 |= mask.buf[y*maskBytesPerRow + byte] >> 1;
- if (byte > 0)
- m8 |= (mask.buf[y*maskBytesPerRow + byte - 1] << 7) & 128;
-
- outlined.mask.buf[y*maskBytesPerRow + byte] = m8;
+ lum_ptr++;
+ data_ptr += 4;
}
}
- // Replace the existing cursor & mask with the new one
- delete [] data;
- delete [] mask.buf;
- data = outlined.data; outlined.data = 0;
- mask.buf = outlined.mask.buf; outlined.mask.buf = 0;
-}
+ // Then diterhing
+ dither(width(), height(), luminance);
-rdr::U8* Cursor::getBitmap(Pixel* pix0, Pixel* pix1) const
-{
- bool gotPix0 = false;
- bool gotPix1 = false;
- *pix0 = *pix1 = 0;
- rdr::U8Array source(maskLen());
- memset(source.buf, 0, maskLen());
-
+ // Then conversion to a bit mask
+ rdr::U8Array source((width()+7)/8*height());
+ memset(source.buf, 0, (width()+7)/8*height());
int maskBytesPerRow = (width() + 7) / 8;
- const rdr::U8 *data_ptr = data;
+ lum_ptr = luminance;
+ data_ptr = data;
for (int y = 0; y < height(); y++) {
for (int x = 0; x < width(); x++) {
int byte = y * maskBytesPerRow + x / 8;
int bit = 7 - x % 8;
- if (mask.buf[byte] & (1 << bit)) {
- Pixel pix = getPF().pixelFromBuffer(data_ptr);
- if (!gotPix0 || pix == *pix0) {
- gotPix0 = true;
- *pix0 = pix;
- } else if (!gotPix1 || pix == *pix1) {
- gotPix1 = true;
- *pix1 = pix;
- source.buf[byte] |= (1 << bit);
- } else {
- // not a bitmap
- return 0;
- }
- }
- data_ptr += getPF().bpp/8;
+ if (*lum_ptr > 32767)
+ source.buf[byte] |= (1 << bit);
+ lum_ptr++;
+ data_ptr += 4;
}
}
+
return source.takeBuf();
}
+rdr::U8* Cursor::getMask() const
+{
+ // First step is converting to integer array
+ int alpha[width()*height()];
+ int *alpha_ptr = alpha;
+ const rdr::U8 *data_ptr = data;
+ for (int y = 0; y < height(); y++) {
+ for (int x = 0; x < width(); x++) {
+ *alpha_ptr = (int)data_ptr[3] * 65535 / 255;
+ alpha_ptr++;
+ data_ptr += 4;
+ }
+ }
+
+ // Then diterhing
+ dither(width(), height(), alpha);
+
+ // Then conversion to a bit mask
+ rdr::U8Array mask((width()+7)/8*height());
+ memset(mask.buf, 0, (width()+7)/8*height());
+ int maskBytesPerRow = (width() + 7) / 8;
+ alpha_ptr = alpha;
+ data_ptr = data;
+ for (int y = 0; y < height(); y++) {
+ for (int x = 0; x < width(); x++) {
+ int byte = y * maskBytesPerRow + x / 8;
+ int bit = 7 - x % 8;
+ if (*alpha_ptr > 32767)
+ mask.buf[byte] |= (1 << bit);
+ alpha_ptr++;
+ data_ptr += 4;
+ }
+ }
+
+ return mask.takeBuf();
+}
+
// crop() determines the "busy" rectangle for the cursor - the minimum bounding
// rectangle containing actual pixels. This isn't the most efficient algorithm
// but it's short. For sanity, we make sure that the busy rectangle always
@@ -126,58 +207,40 @@
void Cursor::crop()
{
- Rect busy = getRect().intersect(Rect(hotspot.x, hotspot.y,
- hotspot.x+1, hotspot.y+1));
- int maskBytesPerRow = (width() + 7) / 8;
+ Rect busy = Rect(0, 0, width_, height_);
+ busy = busy.intersect(Rect(hotspot_.x, hotspot_.y,
+ hotspot_.x+1, hotspot_.y+1));
int x, y;
+ rdr::U8 *data_ptr = data;
for (y = 0; y < height(); y++) {
for (x = 0; x < width(); x++) {
- int byte = y * maskBytesPerRow + x / 8;
- int bit = 7 - x % 8;
- if (mask.buf[byte] & (1 << bit)) {
+ if (data_ptr[3] > 0) {
if (x < busy.tl.x) busy.tl.x = x;
if (x+1 > busy.br.x) busy.br.x = x+1;
if (y < busy.tl.y) busy.tl.y = y;
if (y+1 > busy.br.y) busy.br.y = y+1;
}
+ data_ptr += 4;
}
}
if (width() == busy.width() && height() == busy.height()) return;
- vlog.debug("cropping %dx%d to %dx%d", width(), height(),
- busy.width(), busy.height());
-
// Copy the pixel data
- int newDataLen = busy.area() * (getPF().bpp/8);
+ int newDataLen = busy.area() * 4;
rdr::U8* newData = new rdr::U8[newDataLen];
- getImage(newData, busy);
-
- // Copy the mask
- int newMaskBytesPerRow = (busy.width()+7)/8;
- int newMaskLen = newMaskBytesPerRow * busy.height();
- rdr::U8* newMask = new rdr::U8[newMaskLen];
- memset(newMask, 0, newMaskLen);
- for (y = 0; y < busy.height(); y++) {
- int newByte, newBit;
- for (x = 0; x < busy.width(); x++) {
- int oldByte = (y+busy.tl.y) * maskBytesPerRow + (x+busy.tl.x) / 8;
- int oldBit = 7 - (x+busy.tl.x) % 8;
- newByte = y * newMaskBytesPerRow + x / 8;
- newBit = 7 - x % 8;
- if (mask.buf[oldByte] & (1 << oldBit))
- newMask[newByte] |= (1 << newBit);
- }
+ data_ptr = newData;
+ for (y = busy.tl.y; y < busy.br.y; y++) {
+ memcpy(data_ptr, data + y*width()*4 + busy.tl.x*4, busy.width()*4);
+ data_ptr += busy.width()*4;
}
// Set the size and data to the new, cropped cursor.
- setSize(busy.width(), busy.height());
- hotspot = hotspot.subtract(busy.tl);
+ width_ = busy.width();
+ height_ = busy.height();
+ hotspot_ = hotspot_.subtract(busy.tl);
delete [] data;
- delete [] mask.buf;
- datasize = newDataLen;
data = newData;
- mask.buf = newMask;
}
RenderedCursor::RenderedCursor()
@@ -198,7 +261,7 @@
void RenderedCursor::update(PixelBuffer* framebuffer,
Cursor* cursor, const Point& pos)
{
- Point rawOffset;
+ Point rawOffset, diff;
Rect clippedRect;
const rdr::U8* data;
@@ -207,24 +270,53 @@
assert(framebuffer);
assert(cursor);
- if (!framebuffer->getPF().equal(cursor->getPF()))
- throw Exception("RenderedCursor: Trying to render cursor on incompatible frame buffer");
-
format = framebuffer->getPF();
width_ = framebuffer->width();
height_ = framebuffer->height();
- rawOffset = pos.subtract(cursor->hotspot);
- clippedRect = cursor->getRect(rawOffset).intersect(framebuffer->getRect());
+ rawOffset = pos.subtract(cursor->hotspot());
+ clippedRect = Rect(0, 0, cursor->width(), cursor->height())
+ .translate(rawOffset)
+ .intersect(framebuffer->getRect());
offset = clippedRect.tl;
- buffer.setPF(cursor->getPF());
+ buffer.setPF(format);
buffer.setSize(clippedRect.width(), clippedRect.height());
+ // Bail out early to avoid pestering the framebuffer with
+ // bogus coordinates
+ if (clippedRect.area() == 0)
+ return;
+
data = framebuffer->getBuffer(buffer.getRect(offset), &stride);
buffer.imageRect(buffer.getRect(), data, stride);
- data = cursor->getBuffer(cursor->getRect(), &stride);
- buffer.maskRect(cursor->getRect(rawOffset.subtract(offset)),
- data, cursor->mask.buf);
+ diff = offset.subtract(rawOffset);
+ for (int y = 0;y < buffer.height();y++) {
+ for (int x = 0;x < buffer.width();x++) {
+ size_t idx;
+ rdr::U8 bg[4], fg[4];
+ rdr::U8 rgb[3];
+
+ idx = (y+diff.y)*cursor->width() + (x+diff.x);
+ memcpy(fg, cursor->getBuffer() + idx*4, 4);
+
+ if (fg[3] == 0x00)
+ continue;
+ else if (fg[3] == 0xff) {
+ memcpy(rgb, fg, 3);
+ } else {
+ buffer.getImage(bg, Rect(x, y, x+1, y+1));
+ format.rgbFromBuffer(rgb, bg, 1);
+ // FIXME: Gamma aware blending
+ for (int i = 0;i < 3;i++) {
+ rgb[i] = (unsigned)rgb[i]*(255-fg[3])/255 +
+ (unsigned)fg[i]*fg[3]/255;
+ }
+ }
+
+ format.bufferFromRGB(bg, rgb, 1);
+ buffer.imageRect(Rect(x, y, x+1, y+1), bg);
+ }
+ }
}
diff --git a/common/rfb/Cursor.h b/common/rfb/Cursor.h
index 560e4d8..6c6db7e 100644
--- a/common/rfb/Cursor.h
+++ b/common/rfb/Cursor.h
@@ -1,5 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright 2014 Pierre Ossman for Cendio AB
+ * Copyright 2014-2017 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -28,29 +28,30 @@
namespace rfb {
- class Cursor : public ManagedPixelBuffer {
+ class Cursor {
public:
- Cursor() {}
- rdr::U8Array mask;
- Point hotspot;
+ Cursor(int width, int height, const Point& hotspot, const rdr::U8* data);
+ Cursor(const Cursor& other);
+ ~Cursor();
- int maskLen() const { return (width() + 7) / 8 * height(); }
+ int width() const { return width_; };
+ int height() const { return height_; };
+ const Point& hotspot() const { return hotspot_; };
+ const rdr::U8* getBuffer() const { return data; };
- // setSize() resizes the cursor. The contents of the data and mask are
- // undefined after this call.
- virtual void setSize(int w, int h);
-
- // drawOutline() adds an outline to the cursor in the given colour.
- void drawOutline(const Pixel& c);
-
- // getBitmap() tests whether the cursor is monochrome, and if so returns a
- // bitmap together with background and foreground colours. The size and
- // layout of the bitmap are the same as the mask.
- rdr::U8* getBitmap(Pixel* pix0, Pixel* pix1) const;
+ // getBitmap() returns a monochrome version of the cursor
+ rdr::U8* getBitmap() const;
+ // getMask() returns a simple mask version of the alpha channel
+ rdr::U8* getMask() const;
// crop() crops the cursor down to the smallest possible size, based on the
// mask.
void crop();
+
+ protected:
+ int width_, height_;
+ Point hotspot_;
+ rdr::U8* data;
};
class RenderedCursor : public PixelBuffer {
diff --git a/common/rfb/DecodeManager.cxx b/common/rfb/DecodeManager.cxx
index a655c53..ccf084f 100644
--- a/common/rfb/DecodeManager.cxx
+++ b/common/rfb/DecodeManager.cxx
@@ -197,7 +197,7 @@
{
os::AutoMutex a(queueMutex);
- if (threadException == NULL)
+ if (threadException != NULL)
return;
threadException = new rdr::Exception("Exception on worker thread: %s", e.str());
diff --git a/common/rfb/Encoder.h b/common/rfb/Encoder.h
index 62cb6eb..a8a447e 100644
--- a/common/rfb/Encoder.h
+++ b/common/rfb/Encoder.h
@@ -43,7 +43,7 @@
enum EncoderFlags flags, unsigned int maxPaletteSize);
virtual ~Encoder();
- // isSupported() should return a boolean indiciating if this encoder
+ // isSupported() should return a boolean indicating if this encoder
// is okay to use with the current connection. This usually involves
// checking the list of encodings in the connection parameters.
virtual bool isSupported()=0;
diff --git a/common/rfb/JpegCompressor.cxx b/common/rfb/JpegCompressor.cxx
index c8bf841..27cb9de 100644
--- a/common/rfb/JpegCompressor.cxx
+++ b/common/rfb/JpegCompressor.cxx
@@ -42,7 +42,7 @@
static const PixelFormat pfXBGR(32, 24, false, true, 255, 255, 255, 24, 16, 8);
//
-// Error manager implmentation for the JPEG library
+// Error manager implementation for the JPEG library
//
struct JPEG_ERROR_MGR {
diff --git a/common/rfb/JpegDecompressor.cxx b/common/rfb/JpegDecompressor.cxx
index 70a4276..4f94faa 100644
--- a/common/rfb/JpegDecompressor.cxx
+++ b/common/rfb/JpegDecompressor.cxx
@@ -43,7 +43,7 @@
static const PixelFormat pfXBGR(32, 24, false, true, 255, 255, 255, 24, 16, 8);
//
-// Error manager implmentation for the JPEG library
+// Error manager implementation for the JPEG library
//
struct JPEG_ERROR_MGR {
diff --git a/common/rfb/PixelBuffer.cxx b/common/rfb/PixelBuffer.cxx
index 89addab..007b6c8 100644
--- a/common/rfb/PixelBuffer.cxx
+++ b/common/rfb/PixelBuffer.cxx
@@ -42,17 +42,32 @@
void
-PixelBuffer::getImage(void* imageBuf, const Rect& r, int outStride) const {
+PixelBuffer::getImage(void* imageBuf, const Rect& r, int outStride) const
+{
int inStride;
- const U8* data = getBuffer(r, &inStride);
- // We assume that the specified rectangle is pre-clipped to the buffer
- int bytesPerPixel = format.bpp/8;
- int inBytesPerRow = inStride * bytesPerPixel;
- if (!outStride) outStride = r.width();
- int outBytesPerRow = outStride * bytesPerPixel;
- int bytesPerMemCpy = r.width() * bytesPerPixel;
- U8* imageBufPos = (U8*)imageBuf;
- const U8* end = data + (inBytesPerRow * r.height());
+ const U8* data;
+ int bytesPerPixel, inBytesPerRow, outBytesPerRow, bytesPerMemCpy;
+ U8* imageBufPos;
+ const U8* end;
+
+ if (!r.enclosed_by(getRect()))
+ throw rfb::Exception("Source rect %dx%d at %d,%d exceeds framebuffer %dx%d",
+ r.width(), r.height(),
+ r.tl.x, r.tl.y, width_, height_);
+
+ data = getBuffer(r, &inStride);
+
+ bytesPerPixel = format.bpp/8;
+ inBytesPerRow = inStride * bytesPerPixel;
+
+ if (!outStride)
+ outStride = r.width();
+ outBytesPerRow = outStride * bytesPerPixel;
+ bytesPerMemCpy = r.width() * bytesPerPixel;
+
+ imageBufPos = (U8*)imageBuf;
+ end = data + (inBytesPerRow * r.height());
+
while (data < end) {
memcpy(imageBufPos, data, bytesPerMemCpy);
imageBufPos += outBytesPerRow;
@@ -71,6 +86,11 @@
return;
}
+ if (!r.enclosed_by(getRect()))
+ throw rfb::Exception("Source rect %dx%d at %d,%d exceeds framebuffer %dx%d",
+ r.width(), r.height(),
+ r.tl.x, r.tl.y, width_, height_);
+
if (stride == 0)
stride = r.width();
@@ -102,6 +122,10 @@
U8 *buf;
int w, h, b;
+ if (!r.enclosed_by(getRect()))
+ throw rfb::Exception("Destination rect %dx%d at %d,%d exceeds framebuffer %dx%d",
+ r.width(), r.height(), r.tl.x, r.tl.y, width_, height_);
+
w = r.width();
h = r.height();
b = format.bpp/8;
@@ -142,105 +166,40 @@
void ModifiablePixelBuffer::imageRect(const Rect& r,
const void* pixels, int srcStride)
{
- int bytesPerPixel = getPF().bpp/8;
+ U8* dest;
int destStride;
- U8* dest = getBufferRW(r, &destStride);
- int bytesPerDestRow = bytesPerPixel * destStride;
- if (!srcStride) srcStride = r.width();
- int bytesPerSrcRow = bytesPerPixel * srcStride;
- int bytesPerFill = bytesPerPixel * r.width();
- const U8* src = (const U8*)pixels;
- U8* end = dest + (bytesPerDestRow * r.height());
+ int bytesPerPixel, bytesPerDestRow, bytesPerSrcRow, bytesPerFill;
+ const U8* src;
+ U8* end;
+
+ if (!r.enclosed_by(getRect()))
+ throw rfb::Exception("Destination rect %dx%d at %d,%d exceeds framebuffer %dx%d",
+ r.width(), r.height(),
+ r.tl.x, r.tl.y, width_, height_);
+
+ bytesPerPixel = getPF().bpp/8;
+
+ dest = getBufferRW(r, &destStride);
+
+ bytesPerDestRow = bytesPerPixel * destStride;
+
+ if (!srcStride)
+ srcStride = r.width();
+ bytesPerSrcRow = bytesPerPixel * srcStride;
+ bytesPerFill = bytesPerPixel * r.width();
+
+ src = (const U8*)pixels;
+ end = dest + (bytesPerDestRow * r.height());
+
while (dest < end) {
memcpy(dest, src, bytesPerFill);
dest += bytesPerDestRow;
src += bytesPerSrcRow;
}
+
commitBufferRW(r);
}
-void ModifiablePixelBuffer::maskRect(const Rect& r,
- const void* pixels, const void* mask_)
-{
- Rect cr = getRect().intersect(r);
- if (cr.is_empty()) return;
- int stride;
- U8* data = getBufferRW(cr, &stride);
- U8* mask = (U8*) mask_;
- int w = cr.width();
- int h = cr.height();
- int bpp = getPF().bpp;
- int pixelStride = r.width();
- int maskStride = (r.width() + 7) / 8;
-
- Point offset = Point(cr.tl.x-r.tl.x, cr.tl.y-r.tl.y);
- mask += offset.y * maskStride;
- for (int y = 0; y < h; y++) {
- int cy = offset.y + y;
- for (int x = 0; x < w; x++) {
- int cx = offset.x + x;
- U8* byte = mask + (cx / 8);
- int bit = 7 - cx % 8;
- if ((*byte) & (1 << bit)) {
- switch (bpp) {
- case 8:
- ((U8*)data)[y * stride + x] = ((U8*)pixels)[cy * pixelStride + cx];
- break;
- case 16:
- ((U16*)data)[y * stride + x] = ((U16*)pixels)[cy * pixelStride + cx];
- break;
- case 32:
- ((U32*)data)[y * stride + x] = ((U32*)pixels)[cy * pixelStride + cx];
- break;
- }
- }
- }
- mask += maskStride;
- }
-
- commitBufferRW(cr);
-}
-
-void ModifiablePixelBuffer::maskRect(const Rect& r,
- Pixel pixel, const void* mask_)
-{
- Rect cr = getRect().intersect(r);
- if (cr.is_empty()) return;
- int stride;
- U8* data = getBufferRW(cr, &stride);
- U8* mask = (U8*) mask_;
- int w = cr.width();
- int h = cr.height();
- int bpp = getPF().bpp;
- int maskStride = (r.width() + 7) / 8;
-
- Point offset = Point(cr.tl.x-r.tl.x, cr.tl.y-r.tl.y);
- mask += offset.y * maskStride;
- for (int y = 0; y < h; y++) {
- for (int x = 0; x < w; x++) {
- int cx = offset.x + x;
- U8* byte = mask + (cx / 8);
- int bit = 7 - cx % 8;
- if ((*byte) & (1 << bit)) {
- switch (bpp) {
- case 8:
- ((U8*)data)[y * stride + x] = pixel;
- break;
- case 16:
- ((U16*)data)[y * stride + x] = pixel;
- break;
- case 32:
- ((U32*)data)[y * stride + x] = pixel;
- break;
- }
- }
- }
- mask += maskStride;
- }
-
- commitBufferRW(cr);
-}
-
void ModifiablePixelBuffer::copyRect(const Rect &rect,
const Point &move_by_delta)
{
@@ -251,26 +210,16 @@
Rect drect, srect;
drect = rect;
- if (!drect.enclosed_by(getRect())) {
- vlog.error("Destination rect %dx%d at %d,%d exceeds framebuffer %dx%d",
- drect.width(), drect.height(), drect.tl.x, drect.tl.y, width_, height_);
- drect = drect.intersect(getRect());
- }
-
- if (drect.is_empty())
- return;
+ if (!drect.enclosed_by(getRect()))
+ throw rfb::Exception("Destination rect %dx%d at %d,%d exceeds framebuffer %dx%d",
+ drect.width(), drect.height(),
+ drect.tl.x, drect.tl.y, width_, height_);
srect = drect.translate(move_by_delta.negate());
- if (!srect.enclosed_by(getRect())) {
- vlog.error("Source rect %dx%d at %d,%d exceeds framebuffer %dx%d",
- srect.width(), srect.height(), srect.tl.x, srect.tl.y, width_, height_);
- srect = srect.intersect(getRect());
- // Need to readjust the destination now that the area has changed
- drect = srect.translate(move_by_delta);
- }
-
- if (srect.is_empty())
- return;
+ if (!srect.enclosed_by(getRect()))
+ throw rfb::Exception("Source rect %dx%d at %d,%d exceeds framebuffer %dx%d",
+ srect.width(), srect.height(),
+ srect.tl.x, srect.tl.y, width_, height_);
srcData = getBuffer(srect, &srcStride);
dstData = getBufferRW(drect, &dstStride);
@@ -320,6 +269,11 @@
rdr::U8* dstBuffer;
int dstStride;
+ if (!dest.enclosed_by(getRect()))
+ throw rfb::Exception("Destination rect %dx%d at %d,%d exceeds framebuffer %dx%d",
+ dest.width(), dest.height(),
+ dest.tl.x, dest.tl.y, width_, height_);
+
if (stride == 0)
stride = dest.width();
@@ -344,6 +298,11 @@
rdr::U8* FullFramePixelBuffer::getBufferRW(const Rect& r, int* stride_)
{
+ if (!r.enclosed_by(getRect()))
+ throw rfb::Exception("Pixel buffer request %dx%d at %d,%d exceeds framebuffer %dx%d",
+ r.width(), r.height(),
+ r.tl.x, r.tl.y, width_, height_);
+
*stride_ = stride;
return &data[(r.tl.x + (r.tl.y * stride)) * format.bpp/8];
}
@@ -354,6 +313,11 @@
const rdr::U8* FullFramePixelBuffer::getBuffer(const Rect& r, int* stride_) const
{
+ if (!r.enclosed_by(getRect()))
+ throw rfb::Exception("Pixel buffer request %dx%d at %d,%d exceeds framebuffer %dx%d",
+ r.width(), r.height(),
+ r.tl.x, r.tl.y, width_, height_);
+
*stride_ = stride;
return &data[(r.tl.x + (r.tl.y * stride)) * format.bpp/8];
}
@@ -392,7 +356,6 @@
ManagedPixelBuffer::checkDataSize() {
unsigned long new_datasize = width_ * height_ * (format.bpp/8);
if (datasize < new_datasize) {
- vlog.debug("reallocating managed buffer (%dx%d)", width_, height_);
if (data) {
delete [] data;
datasize = 0; data = 0;
diff --git a/common/rfb/PixelBuffer.h b/common/rfb/PixelBuffer.h
index ba586b0..75caa63 100644
--- a/common/rfb/PixelBuffer.h
+++ b/common/rfb/PixelBuffer.h
@@ -127,16 +127,6 @@
// Copy pixel data from one PixelBuffer location to another
void copyRect(const Rect &dest, const Point& move_by_delta);
- // Copy pixel data to the buffer through a mask
- // pixels is a pointer to the pixel to be copied to r.tl.
- // maskPos specifies the pixel offset in the mask to start from.
- // mask_ is a pointer to the mask bits at (0,0).
- // pStride and mStride are the strides of the pixel and mask buffers.
- void maskRect(const Rect& r, const void* pixels, const void* mask_);
-
- // pixel is the Pixel value to be used where mask_ is set
- void maskRect(const Rect& r, Pixel pixel, const void* mask_);
-
// Render in a specific format
// Does the exact same thing as the above methods, but the given
// pixel values are defined by the given PixelFormat.
diff --git a/common/rfb/SMsgWriter.cxx b/common/rfb/SMsgWriter.cxx
index 5040b65..cf3264e 100644
--- a/common/rfb/SMsgWriter.cxx
+++ b/common/rfb/SMsgWriter.cxx
@@ -1,6 +1,6 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright (C) 2011 D. R. Commander. All Rights Reserved.
- * Copyright 2009-2014 Pierre Ossman for Cendio AB
+ * Copyright 2009-2017 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -36,7 +36,8 @@
: cp(cp_), os(os_),
nRectsInUpdate(0), nRectsInHeader(0),
needSetDesktopSize(false), needExtendedDesktopSize(false),
- needSetDesktopName(false), needSetCursor(false), needSetXCursor(false)
+ needSetDesktopName(false), needSetCursor(false),
+ needSetXCursor(false), needSetCursorWithAlpha(false)
{
}
@@ -180,11 +181,21 @@
return true;
}
+bool SMsgWriter::writeSetCursorWithAlpha()
+{
+ if (!cp->supportsLocalCursorWithAlpha)
+ return false;
+
+ needSetCursorWithAlpha = true;
+
+ return true;
+}
+
bool SMsgWriter::needFakeUpdate()
{
if (needSetDesktopName)
return true;
- if (needSetCursor || needSetXCursor)
+ if (needSetCursor || needSetXCursor || needSetCursorWithAlpha)
return true;
if (needNoDataUpdate())
return true;
@@ -232,6 +243,8 @@
nRects++;
if (needSetXCursor)
nRects++;
+ if (needSetCursorWithAlpha)
+ nRects++;
}
os->writeU16(nRects);
@@ -301,41 +314,48 @@
void SMsgWriter::writePseudoRects()
{
if (needSetCursor) {
- rdr::U8* data;
-
const Cursor& cursor = cp->cursor();
- data = new rdr::U8[cursor.area() * cp->pf().bpp/8];
- cursor.getImage(cp->pf(), data, cursor.getRect());
+ rdr::U8Array data(cursor.width()*cursor.height() * cp->pf().bpp/8);
+ rdr::U8Array mask(cursor.getMask());
+
+ const rdr::U8* in;
+ rdr::U8* out;
+
+ in = cursor.getBuffer();
+ out = data.buf;
+ for (int i = 0;i < cursor.width()*cursor.height();i++) {
+ cp->pf().bufferFromRGB(out, in, 1);
+ in += 4;
+ out += cp->pf().bpp/8;
+ }
writeSetCursorRect(cursor.width(), cursor.height(),
- cursor.hotspot.x, cursor.hotspot.y,
- data, cursor.mask.buf);
+ cursor.hotspot().x, cursor.hotspot().y,
+ data.buf, mask.buf);
needSetCursor = false;
-
- delete [] data;
}
if (needSetXCursor) {
const Cursor& cursor = cp->cursor();
- Pixel pix0, pix1;
- rdr::U8 rgb0[3], rgb1[3];
- rdr::U8Array bitmap(cursor.getBitmap(&pix0, &pix1));
-
- if (!bitmap.buf) {
- // FIXME: We could reduce to two colors.
- throw Exception("SMsgWriter::writePseudoRects: Unable to send multicolor cursor: RichCursor not supported by client");
- }
-
- cp->pf().rgbFromPixel(pix0, &rgb0[0], &rgb0[1], &rgb0[2]);
- cp->pf().rgbFromPixel(pix1, &rgb1[0], &rgb1[1], &rgb1[2]);
+ rdr::U8Array bitmap(cursor.getBitmap());
+ rdr::U8Array mask(cursor.getMask());
writeSetXCursorRect(cursor.width(), cursor.height(),
- cursor.hotspot.x, cursor.hotspot.y,
- rgb0, rgb1, bitmap.buf, cursor.mask.buf);
+ cursor.hotspot().x, cursor.hotspot().y,
+ bitmap.buf, mask.buf);
needSetXCursor = false;
}
+ if (needSetCursorWithAlpha) {
+ const Cursor& cursor = cp->cursor();
+
+ writeSetCursorWithAlphaRect(cursor.width(), cursor.height(),
+ cursor.hotspot().x, cursor.hotspot().y,
+ cursor.getBuffer());
+ needSetCursorWithAlpha = false;
+ }
+
if (needSetDesktopName) {
writeSetDesktopNameRect(cp->name());
needSetDesktopName = false;
@@ -452,8 +472,6 @@
void SMsgWriter::writeSetXCursorRect(int width, int height,
int hotspotX, int hotspotY,
- const rdr::U8 pix0[],
- const rdr::U8 pix1[],
const void* data, const void* mask)
{
if (!cp->supportsLocalXCursor)
@@ -467,13 +485,41 @@
os->writeU16(height);
os->writeU32(pseudoEncodingXCursor);
if (width * height) {
- os->writeU8(pix0[0]);
- os->writeU8(pix0[1]);
- os->writeU8(pix0[2]);
- os->writeU8(pix1[0]);
- os->writeU8(pix1[1]);
- os->writeU8(pix1[2]);
+ os->writeU8(255);
+ os->writeU8(255);
+ os->writeU8(255);
+ os->writeU8(0);
+ os->writeU8(0);
+ os->writeU8(0);
os->writeBytes(data, (width+7)/8 * height);
os->writeBytes(mask, (width+7)/8 * height);
}
}
+
+void SMsgWriter::writeSetCursorWithAlphaRect(int width, int height,
+ int hotspotX, int hotspotY,
+ const rdr::U8* data)
+{
+ if (!cp->supportsLocalCursorWithAlpha)
+ throw Exception("Client does not support local cursors");
+ if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
+ throw Exception("SMsgWriter::writeSetCursorWithAlphaRect: nRects out of sync");
+
+ os->writeS16(hotspotX);
+ os->writeS16(hotspotY);
+ os->writeU16(width);
+ os->writeU16(height);
+ os->writeU32(pseudoEncodingCursorWithAlpha);
+
+ // FIXME: Use an encoder with compression?
+ os->writeU32(encodingRaw);
+
+ // Alpha needs to be pre-multiplied
+ for (int i = 0;i < width*height;i++) {
+ os->writeU8((unsigned)data[0] * data[3] / 255);
+ os->writeU8((unsigned)data[1] * data[3] / 255);
+ os->writeU8((unsigned)data[2] * data[3] / 255);
+ os->writeU8(data[3]);
+ data += 4;
+ }
+}
diff --git a/common/rfb/SMsgWriter.h b/common/rfb/SMsgWriter.h
index 917b933..548b8e8 100644
--- a/common/rfb/SMsgWriter.h
+++ b/common/rfb/SMsgWriter.h
@@ -80,6 +80,7 @@
// immediately.
bool writeSetCursor();
bool writeSetXCursor();
+ bool writeSetCursorWithAlpha();
// needFakeUpdate() returns true when an immediate update is needed in
// order to flush out pseudo-rectangles to the client.
@@ -126,8 +127,10 @@
const void* data, const void* mask);
void writeSetXCursorRect(int width, int height,
int hotspotX, int hotspotY,
- const rdr::U8 pix0[], const rdr::U8 pix1[],
const void* data, const void* mask);
+ void writeSetCursorWithAlphaRect(int width, int height,
+ int hotspotX, int hotspotY,
+ const rdr::U8* data);
ConnParams* cp;
rdr::OutStream* os;
@@ -141,6 +144,7 @@
bool needLastRect;
bool needSetCursor;
bool needSetXCursor;
+ bool needSetCursorWithAlpha;
typedef struct {
rdr::U16 reason, result;
diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx
index 9b5eaf8..0a2ca33 100644
--- a/common/rfb/VNCSConnectionST.cxx
+++ b/common/rfb/VNCSConnectionST.cxx
@@ -51,7 +51,7 @@
static const unsigned INITIAL_WINDOW = 16384;
// TCP's minimal window is 3*MSS. But since we don't know the MSS, we
-// make a guess at 4 KiB (it's probaly a bit higher).
+// make a guess at 4 KiB (it's probably a bit higher).
static const unsigned MINIMUM_WINDOW = 4096;
// The current default maximum window for Linux (4 MiB). Should be a good
@@ -827,7 +827,7 @@
}
// We only keep track of the minimum latency seen (for a given interval)
- // on the basis that we want to avoid continous buffer issue, but don't
+ // on the basis that we want to avoid continuous buffer issue, but don't
// mind (or even approve of) bursts.
if (rtt < minRTT)
minRTT = rtt;
@@ -1129,12 +1129,14 @@
if (state() != RFBSTATE_NORMAL)
return;
- cp.setCursor(server->cursor);
+ cp.setCursor(*server->cursor);
- if (!writer()->writeSetCursor()) {
- if (!writer()->writeSetXCursor()) {
- // No client support
- return;
+ if (!writer()->writeSetCursorWithAlpha()) {
+ if (!writer()->writeSetCursor()) {
+ if (!writer()->writeSetXCursor()) {
+ // No client support
+ return;
+ }
}
}
diff --git a/common/rfb/VNCServer.h b/common/rfb/VNCServer.h
index c76e5c9..982a4ff 100644
--- a/common/rfb/VNCServer.h
+++ b/common/rfb/VNCServer.h
@@ -64,16 +64,10 @@
virtual void closeClients(const char* reason) = 0;
// setCursor() tells the server that the cursor has changed. The
- // cursorData argument contains width*height pixel values in the pixel
- // buffer's format. The mask argument is a bitmask with a 1-bit meaning
- // the corresponding pixel in cursorData is valid. The mask consists of
- // left-to-right, top-to-bottom scanlines, where each scanline is padded to
- // a whole number of bytes [(width+7)/8]. Within each byte the most
- // significant bit represents the leftmost pixel, and the bytes are simply
- // in left-to-right order. The server takes its own copy of the data in
- // cursorData and mask.
+ // cursorData argument contains width*height rgba quadruplets with
+ // non-premultiplied alpha.
virtual void setCursor(int width, int height, const Point& hotspot,
- const void* cursorData, const void* mask) = 0;
+ const rdr::U8* cursorData) = 0;
// setCursorPos() tells the server the current position of the cursor.
virtual void setCursorPos(const Point& p) = 0;
diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx
index 80c992f..ec5e962 100644
--- a/common/rfb/VNCServerST.cxx
+++ b/common/rfb/VNCServerST.cxx
@@ -1,5 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright 2009-2016 Pierre Ossman for Cendio AB
+ * Copyright 2009-2017 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -76,6 +76,7 @@
: blHosts(&blacklist), desktop(desktop_), desktopStarted(false),
blockCounter(0), pb(0),
name(strDup(name_)), pointerClient(0), comparer(0),
+ cursor(new Cursor(0, 0, Point(), NULL)),
renderedCursorInvalid(false),
queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance),
lastConnectionTime(0), disableclients(false),
@@ -110,6 +111,8 @@
if (comparer)
comparer->logStats();
delete comparer;
+
+ delete cursor;
}
@@ -315,7 +318,6 @@
}
comparer = new ComparingUpdateTracker(pb);
- cursor.setPF(pb->getPF());
renderedCursorInvalid = true;
// Make sure that we have at least one screen
@@ -429,14 +431,11 @@
}
void VNCServerST::setCursor(int width, int height, const Point& newHotspot,
- const void* data, const void* mask)
+ const rdr::U8* data)
{
- cursor.hotspot = newHotspot;
- cursor.setSize(width, height);
- cursor.imageRect(cursor.getRect(), data);
- memcpy(cursor.mask.buf, mask, cursor.maskLen());
-
- cursor.crop();
+ delete cursor;
+ cursor = new Cursor(width, height, newHotspot, data);
+ cursor->crop();
renderedCursorInvalid = true;
@@ -585,8 +584,9 @@
toCheck = ui.changed.union_(ui.copied);
if (needRenderedCursor()) {
- Rect clippedCursorRect
- = cursor.getRect(cursorPos.subtract(cursor.hotspot)).intersect(pb->getRect());
+ Rect clippedCursorRect = Rect(0, 0, cursor->width(), cursor->height())
+ .translate(cursorPos.subtract(cursor->hotspot()))
+ .intersect(pb->getRect());
if (!toCheck.intersect(clippedCursorRect).is_empty())
renderedCursorInvalid = true;
@@ -631,7 +631,7 @@
const RenderedCursor* VNCServerST::getRenderedCursor()
{
if (renderedCursorInvalid) {
- renderedCursor.update(pb, &cursor, cursorPos);
+ renderedCursor.update(pb, cursor, cursorPos);
renderedCursorInvalid = false;
}
diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h
index b5eb415..00f77c7 100644
--- a/common/rfb/VNCServerST.h
+++ b/common/rfb/VNCServerST.h
@@ -99,7 +99,7 @@
virtual void add_changed(const Region ®ion);
virtual void add_copied(const Region &dest, const Point &delta);
virtual void setCursor(int width, int height, const Point& hotspot,
- const void* cursorData, const void* mask);
+ const rdr::U8* data);
virtual void setCursorPos(const Point& p);
virtual void bell();
@@ -219,7 +219,7 @@
ComparingUpdateTracker* comparer;
Point cursorPos;
- Cursor cursor;
+ Cursor* cursor;
RenderedCursor renderedCursor;
bool renderedCursorInvalid;
diff --git a/common/rfb/XF86keysym.h b/common/rfb/XF86keysym.h
index fd3af4f..ff3dd4f 100644
--- a/common/rfb/XF86keysym.h
+++ b/common/rfb/XF86keysym.h
@@ -136,7 +136,7 @@
#define XF86XK_LogOff 0x1008FF61 /* Log off system */
#define XF86XK_Market 0x1008FF62 /* ?? */
#define XF86XK_Meeting 0x1008FF63 /* enter meeting in calendar */
-#define XF86XK_MenuKB 0x1008FF65 /* distingush keyboard from PB */
+#define XF86XK_MenuKB 0x1008FF65 /* distinguish keyboard from PB */
#define XF86XK_MenuPB 0x1008FF66 /* distinuish PB from keyboard */
#define XF86XK_MySites 0x1008FF67 /* Favourites */
#define XF86XK_New 0x1008FF68 /* New (folder, document... */
diff --git a/common/rfb/ZRLEDecoder.cxx b/common/rfb/ZRLEDecoder.cxx
index b891ba5..1dfb72a 100644
--- a/common/rfb/ZRLEDecoder.cxx
+++ b/common/rfb/ZRLEDecoder.cxx
@@ -1,4 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright 2009-2017 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -91,24 +92,27 @@
case 16: zrleDecode16(r, &is, &zis, pf, pb); break;
case 32:
{
- Pixel maxPixel = pf.pixelFromRGB((rdr::U16)-1, (rdr::U16)-1, (rdr::U16)-1);
- bool fitsInLS3Bytes = maxPixel < (1<<24);
- bool fitsInMS3Bytes = (maxPixel & 0xff) == 0;
+ if (pf.depth <= 24) {
+ Pixel maxPixel = pf.pixelFromRGB((rdr::U16)-1, (rdr::U16)-1, (rdr::U16)-1);
+ bool fitsInLS3Bytes = maxPixel < (1<<24);
+ bool fitsInMS3Bytes = (maxPixel & 0xff) == 0;
- if ((fitsInLS3Bytes && pf.isLittleEndian()) ||
- (fitsInMS3Bytes && pf.isBigEndian()))
- {
- zrleDecode24A(r, &is, &zis, pf, pb);
+ if ((fitsInLS3Bytes && pf.isLittleEndian()) ||
+ (fitsInMS3Bytes && pf.isBigEndian()))
+ {
+ zrleDecode24A(r, &is, &zis, pf, pb);
+ break;
+ }
+
+ if ((fitsInLS3Bytes && pf.isBigEndian()) ||
+ (fitsInMS3Bytes && pf.isLittleEndian()))
+ {
+ zrleDecode24B(r, &is, &zis, pf, pb);
+ break;
+ }
}
- else if ((fitsInLS3Bytes && pf.isBigEndian()) ||
- (fitsInMS3Bytes && pf.isLittleEndian()))
- {
- zrleDecode24B(r, &is, &zis, pf, pb);
- }
- else
- {
- zrleDecode32(r, &is, &zis, pf, pb);
- }
+
+ zrleDecode32(r, &is, &zis, pf, pb);
break;
}
}
diff --git a/common/rfb/encodings.h b/common/rfb/encodings.h
index 5c6c5ea..a65d863 100644
--- a/common/rfb/encodings.h
+++ b/common/rfb/encodings.h
@@ -38,6 +38,7 @@
const int pseudoEncodingDesktopName = -307;
const int pseudoEncodingFence = -312;
const int pseudoEncodingContinuousUpdates = -313;
+ const int pseudoEncodingCursorWithAlpha = -314;
// TightVNC-specific
const int pseudoEncodingLastRect = -224;
diff --git a/common/rfb/keysymdef.h b/common/rfb/keysymdef.h
index 979ebdd..a80f3f5 100644
--- a/common/rfb/keysymdef.h
+++ b/common/rfb/keysymdef.h
@@ -175,7 +175,7 @@
/*
- * Auxilliary Functions; note the duplicate definitions for left and right
+ * Auxiliary Functions; note the duplicate definitions for left and right
* function keys; Sun keyboards and a few other manufactures have such
* function key groups on the left and/or right sides of the keyboard.
* We've not found a keyboard with more than 35 function keys total.
diff --git a/common/rfb/rreDecode.h b/common/rfb/rreDecode.h
index 56defbd..f9fdcfc 100644
--- a/common/rfb/rreDecode.h
+++ b/common/rfb/rreDecode.h
@@ -22,6 +22,7 @@
// BPP - 8, 16 or 32
#include <rdr/InStream.h>
+#include <rfb/Exception.h>
namespace rfb {
@@ -49,6 +50,10 @@
int y = is->readU16();
int w = is->readU16();
int h = is->readU16();
+
+ if (((x+w) > r.width()) || ((y+h) > r.height()))
+ throw Exception ("RRE decode error");
+
pb->fillRect(pf, Rect(r.tl.x+x, r.tl.y+y, r.tl.x+x+w, r.tl.y+y+h), &pix);
}
}
diff --git a/common/rfb/util.cxx b/common/rfb/util.cxx
index aec45f6..22e00ff 100644
--- a/common/rfb/util.cxx
+++ b/common/rfb/util.cxx
@@ -139,7 +139,7 @@
static size_t doPrefix(long long value, const char *unit,
char *buffer, size_t maxlen,
unsigned divisor, const char **prefixes,
- size_t prefixCount) {
+ size_t prefixCount, int precision) {
double newValue;
size_t prefix, len;
@@ -152,7 +152,7 @@
prefix++;
}
- len = snprintf(buffer, maxlen, "%g %s%s", newValue,
+ len = snprintf(buffer, maxlen, "%.*g %s%s", precision, newValue,
(prefix == 0) ? "" : prefixes[prefix-1], unit);
buffer[maxlen-1] = '\0';
@@ -165,14 +165,16 @@
{ "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi" };
size_t siPrefix(long long value, const char *unit,
- char *buffer, size_t maxlen) {
+ char *buffer, size_t maxlen, int precision) {
return doPrefix(value, unit, buffer, maxlen, 1000, siPrefixes,
- sizeof(siPrefixes)/sizeof(*siPrefixes));
+ sizeof(siPrefixes)/sizeof(*siPrefixes),
+ precision);
}
size_t iecPrefix(long long value, const char *unit,
- char *buffer, size_t maxlen) {
+ char *buffer, size_t maxlen, int precision) {
return doPrefix(value, unit, buffer, maxlen, 1024, iecPrefixes,
- sizeof(iecPrefixes)/sizeof(*iecPrefixes));
+ sizeof(iecPrefixes)/sizeof(*iecPrefixes),
+ precision);
}
};
diff --git a/common/rfb/util.h b/common/rfb/util.h
index 9ad1772..e9114c3 100644
--- a/common/rfb/util.h
+++ b/common/rfb/util.h
@@ -99,9 +99,9 @@
unsigned msSince(const struct timeval *then);
size_t siPrefix(long long value, const char *unit,
- char *buffer, size_t maxlen);
+ char *buffer, size_t maxlen, int precision=6);
size_t iecPrefix(long long value, const char *unit,
- char *buffer, size_t maxlen);
+ char *buffer, size_t maxlen, int precision=6);
}
// Some platforms (e.g. Windows) include max() and min() macros in their
diff --git a/contrib/packages/deb/ubuntu-xenial/debian/control b/contrib/packages/deb/ubuntu-xenial/debian/control
index 626efee..c063496 100644
--- a/contrib/packages/deb/ubuntu-xenial/debian/control
+++ b/contrib/packages/deb/ubuntu-xenial/debian/control
@@ -30,7 +30,7 @@
Package: xtigervncviewer
Architecture: any
Provides: vncviewer, vnc-viewer
-Depends: libc6, libexpat1, libfontconfig1, libfreetype6, libgcc1, libgnutls30, libjpeg-turbo8, libp11-kit0, libpng12-0, libstdc++6, libtasn1-3-bin, libx11-6, libxau6, libxcb1, libxdmcp6, libxext6, libxft2, libxrender1, zlib1g, libglu1-mesa, libxcursor1, libxinerama1, libxfixes3, libfltk1.3
+Depends: libc6, libexpat1, libfontconfig1, libfreetype6, libgcc1, libgnutls30, libjpeg-turbo8, libp11-kit0, libpng12-0, libstdc++6, libtasn1-3-bin, libx11-6, libxau6, libxcb1, libxdmcp6, libxext6, libxft2, libxrender1, zlib1g, libglu1-mesa, libxcursor1, libxinerama1, libxfixes3, libfltk1.3, libfltk-images1.3
Recommends: xfonts-base
Suggests: tigervncserver, ssh
Description: virtual network computing client software for X
diff --git a/java/com/tigervnc/network/SSLEngineManager.java b/java/com/tigervnc/network/SSLEngineManager.java
index c011099..e6abe3e 100644
--- a/java/com/tigervnc/network/SSLEngineManager.java
+++ b/java/com/tigervnc/network/SSLEngineManager.java
@@ -74,7 +74,7 @@
hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
switch (hs) {
-
+
case NEED_UNWRAP:
// Receive handshaking data from peer
peerNetData.flip();
@@ -92,26 +92,26 @@
peerNetData.flip();
peerNetData.compact();
break;
-
+
case OK:
// Process incoming handshaking data
break;
-
+
case CLOSED:
engine.closeInbound();
break;
-
+
}
break;
-
+
case NEED_WRAP:
// Empty the local network packet buffer.
myNetData.clear();
-
+
// Generate handshaking data
res = engine.wrap(myAppData, myNetData);
hs = res.getHandshakeStatus();
-
+
// Check status
switch (res.getStatus()) {
case OK:
@@ -121,15 +121,15 @@
os.flush();
myNetData.clear();
break;
-
+
case BUFFER_OVERFLOW:
// FIXME: How much larger should the buffer be?
break;
-
+
case CLOSED:
engine.closeOutbound();
break;
-
+
}
break;
diff --git a/java/com/tigervnc/network/TcpSocket.java b/java/com/tigervnc/network/TcpSocket.java
index 43787e4..95f16d3 100644
--- a/java/com/tigervnc/network/TcpSocket.java
+++ b/java/com/tigervnc/network/TcpSocket.java
@@ -32,6 +32,9 @@
import java.nio.*;
import java.nio.channels.*;
+import java.util.Set;
+import java.util.Iterator;
+
public class TcpSocket extends Socket {
// -=- Socket initialisation
@@ -77,23 +80,37 @@
/* Attempt to connect to the remote host */
try {
result = sock.connect(new InetSocketAddress(addr, port));
+ Selector selector = Selector.open();
+ SelectionKey connect_key =
+ sock.socket().getChannel().register(selector, SelectionKey.OP_CONNECT);
+ // Try for the connection for 250ms
+ while (selector.select(250) > 0) {
+ while (!result) {
+
+ Set keys = selector.selectedKeys();
+ Iterator i = keys.iterator();
+
+ while (i.hasNext()) {
+ SelectionKey key = (SelectionKey)i.next();
+
+ // Remove the current key
+ i.remove();
+
+ // Attempt a connection
+ if (key.isConnectable()) {
+ if (sock.isConnectionPending())
+ sock.finishConnect();
+ result = true;
+ }
+ }
+ }
+ }
+ if (!result)
+ throw new SocketException("unable to connect to socket: Host is down");
} catch(java.io.IOException e) {
throw new SocketException("unable to connect:"+e.getMessage());
}
- if (!result && sock.isConnectionPending()) {
- while (!result) {
- try {
- result = sock.finishConnect();
- } catch(java.io.IOException e) {
- throw new Exception(e.getMessage());
- }
- }
- }
-
- if (!result)
- throw new SocketException("unable connect to socket");
-
// Disable Nagle's algorithm, to reduce latency
enableNagles(sock, false);
diff --git a/java/com/tigervnc/rdr/InStream.java b/java/com/tigervnc/rdr/InStream.java
index 3938a32..0906d32 100644
--- a/java/com/tigervnc/rdr/InStream.java
+++ b/java/com/tigervnc/rdr/InStream.java
@@ -23,6 +23,8 @@
package com.tigervnc.rdr;
+import java.nio.*;
+
import com.tigervnc.network.*;
abstract public class InStream {
@@ -98,6 +100,16 @@
// readBytes() reads an exact number of bytes into an array at an offset.
+ public void readBytes(ByteBuffer data, int length) {
+ int dataEnd = data.mark().position() + length;
+ while (data.position() < dataEnd) {
+ int n = check(1, dataEnd - data.position());
+ data.put(b, ptr, n);
+ ptr += n;
+ }
+ data.reset();
+ }
+
public void readBytes(byte[] data, int dataPtr, int length) {
int dataEnd = dataPtr + length;
while (dataPtr < dataEnd) {
@@ -147,20 +159,6 @@
}
}
- public final int readCompactLength() {
- int b = readU8();
- int result = b & 0x7F;
- if ((b & 0x80) != 0) {
- b = readU8();
- result |= (b & 0x7F) << 7;
- if ((b & 0x80) != 0) {
- b = readU8();
- result |= (b & 0xFF) << 14;
- }
- }
- return result;
- }
-
// pos() returns the position in the stream.
abstract public int pos();
diff --git a/java/com/tigervnc/rdr/OutStream.java b/java/com/tigervnc/rdr/OutStream.java
index 46fe734..a3b1a6c 100644
--- a/java/com/tigervnc/rdr/OutStream.java
+++ b/java/com/tigervnc/rdr/OutStream.java
@@ -116,6 +116,17 @@
}
}
+ // copyBytes() efficiently transfers data between streams
+
+ public void copyBytes(InStream is, int length) {
+ while (length > 0) {
+ int n = check(1, length);
+ is.readBytes(b, ptr, n);
+ ptr += n;
+ length -= n;
+ }
+ }
+
// writeOpaqueN() writes a quantity without byte-swapping. Because java has
// no byte-ordering, we just use big-endian.
diff --git a/java/com/tigervnc/rdr/ZlibInStream.java b/java/com/tigervnc/rdr/ZlibInStream.java
index 103bb64..151633e 100644
--- a/java/com/tigervnc/rdr/ZlibInStream.java
+++ b/java/com/tigervnc/rdr/ZlibInStream.java
@@ -62,6 +62,18 @@
ptr = end = start;
}
+ public void removeUnderlying()
+ {
+ ptr = end = start;
+ if (underlying == null) return;
+
+ while (bytesIn > 0) {
+ decompress(true);
+ end = start; // throw away any data
+ }
+ underlying = null;
+ }
+
public int pos()
{
return offset + ptr - start;
diff --git a/java/com/tigervnc/rfb/AliasParameter.java b/java/com/tigervnc/rfb/AliasParameter.java
index a1ae838..3f20ae4 100644
--- a/java/com/tigervnc/rfb/AliasParameter.java
+++ b/java/com/tigervnc/rfb/AliasParameter.java
@@ -44,5 +44,13 @@
param.setImmutable();
}
+ public void setHasBeenSet() {
+ param.setHasBeenSet();
+ }
+
+ public boolean hasBeenSet() {
+ return param.hasBeenSet();
+ }
+
protected VoidParameter param;
}
diff --git a/java/com/tigervnc/rfb/CConnection.java b/java/com/tigervnc/rfb/CConnection.java
index c354868..0b38aea 100644
--- a/java/com/tigervnc/rfb/CConnection.java
+++ b/java/com/tigervnc/rfb/CConnection.java
@@ -19,7 +19,11 @@
package com.tigervnc.rfb;
+import java.awt.color.*;
+import java.awt.image.*;
+import java.nio.*;
import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
import com.tigervnc.network.*;
import com.tigervnc.rdr.*;
@@ -28,21 +32,86 @@
public CConnection()
{
- csecurity = null; is = null; os = null; reader_ = null;
- writer_ = null; shared = false;
+ super();
+ csecurity = null; is = null; os = null; reader_ = null; writer_ = null;
+ shared = false;
state_ = RFBSTATE_UNINITIALISED; useProtocol3_3 = false;
+ framebuffer = null; decoder = new DecodeManager(this);
security = new SecurityClient();
}
- // deleteReaderAndWriter() deletes the reader and writer associated with
- // this connection. This may be useful if you want to delete the streams
- // before deleting the SConnection to make sure that no attempt by the
- // SConnection is made to read or write.
- // XXX Do we really need this at all???
- public void deleteReaderAndWriter()
+ // Methods to initialise the connection
+
+ // setServerName() is used to provide a unique(ish) name for the server to
+ // which we are connected. This might be the result of getPeerEndpoint on
+ // a TcpSocket, for example, or a host specified by DNS name & port.
+ // The serverName is used when verifying the Identity of a host (see RA2).
+ public final void setServerName(String name) {
+ serverName = name;
+ }
+
+ // setShared sets the value of the shared flag which will be sent to the
+ // server upon initialisation.
+ public final void setShared(boolean s) { shared = s; }
+
+ // setProtocol3_3 configures whether or not the CConnection should
+ // only ever support protocol version 3.3
+ public final void setProtocol3_3(boolean s) { useProtocol3_3 = s; }
+
+ // setStreams() sets the streams to be used for the connection. These must
+ // be set before initialiseProtocol() and processMsg() are called. The
+ // CSecurity object may call setStreams() again to provide alternative
+ // streams over which the RFB protocol is sent (i.e. encrypting/decrypting
+ // streams). Ownership of the streams remains with the caller
+ // (i.e. SConnection will not delete them).
+ public final void setStreams(InStream is_, OutStream os_)
{
- reader_ = null;
- writer_ = null;
+ is = is_;
+ os = os_;
+ }
+
+ // setFramebuffer configures the PixelBuffer that the CConnection
+ // should render all pixel data in to. Note that the CConnection
+ // takes ownership of the PixelBuffer and it must not be deleted by
+ // anyone else. Call setFramebuffer again with NULL or a different
+ // PixelBuffer to delete the previous one.
+ public void setFramebuffer(ModifiablePixelBuffer fb)
+ {
+ decoder.flush();
+
+ if ((framebuffer != null) && (fb != null)) {
+ Rect rect = new Rect();
+
+ Raster data;
+
+ byte[] black = new byte[4];
+
+ // Copy still valid area
+
+ rect.setXYWH(0, 0,
+ Math.min(fb.width(), framebuffer.width()),
+ Math.min(fb.height(), framebuffer.height()));
+ data = framebuffer.getBuffer(rect);
+ fb.imageRect(framebuffer.getPF(), rect, data);
+
+ // Black out any new areas
+
+ if (fb.width() > framebuffer.width()) {
+ rect.setXYWH(framebuffer.width(), 0,
+ fb.width() - framebuffer.width(),
+ fb.height());
+ fb.fillRect(rect, black);
+ }
+
+ if (fb.height() > framebuffer.height()) {
+ rect.setXYWH(0, framebuffer.height(),
+ fb.width(),
+ fb.height() - framebuffer.height());
+ fb.fillRect(rect, black);
+ }
+ }
+
+ framebuffer = fb;
}
// initialiseProtocol() should be called once the streams and security
@@ -75,11 +144,12 @@
private void processVersionMsg()
{
vlog.debug("reading protocol version");
- if (!cp.readVersion(is)) {
+ AtomicBoolean done = new AtomicBoolean();
+ if (!cp.readVersion(is, done)) {
state_ = RFBSTATE_INVALID;
throw new Exception("reading version failed: not an RFB server?");
}
- if (!cp.done) return;
+ if (!done.get()) return;
vlog.info("Server supports RFB protocol version "
+cp.majorVersion+"."+ cp.minorVersion);
@@ -111,7 +181,7 @@
int secType = Security.secTypeInvalid;
List<Integer> secTypes = new ArrayList<Integer>();
- secTypes = Security.GetEnabledSecTypes();
+ secTypes = security.GetEnabledSecTypes();
if (cp.isVersion(3,3)) {
@@ -241,59 +311,56 @@
private void securityCompleted() {
state_ = RFBSTATE_INITIALISATION;
- reader_ = new CMsgReaderV3(this, is);
- writer_ = new CMsgWriterV3(cp, os);
+ reader_ = new CMsgReader(this, is);
+ writer_ = new CMsgWriter(cp, os);
vlog.debug("Authentication success!");
authSuccess();
writer_.writeClientInit(shared);
}
- // Methods to initialise the connection
-
- // setServerName() is used to provide a unique(ish) name for the server to
- // which we are connected. This might be the result of getPeerEndpoint on
- // a TcpSocket, for example, or a host specified by DNS name & port.
- // The serverName is used when verifying the Identity of a host (see RA2).
- public final void setServerName(String name) {
- serverName = name;
- }
-
- // setStreams() sets the streams to be used for the connection. These must
- // be set before initialiseProtocol() and processMsg() are called. The
- // CSecurity object may call setStreams() again to provide alternative
- // streams over which the RFB protocol is sent (i.e. encrypting/decrypting
- // streams). Ownership of the streams remains with the caller
- // (i.e. SConnection will not delete them).
- public final void setStreams(InStream is_, OutStream os_)
- {
- is = is_;
- os = os_;
- }
-
- // setShared sets the value of the shared flag which will be sent to the
- // server upon initialisation.
- public final void setShared(boolean s) { shared = s; }
-
- // setProtocol3_3 configures whether or not the CConnection should
- // only ever support protocol version 3.3
- public final void setProtocol3_3(boolean s) { useProtocol3_3 = s; }
-
- public void setServerPort(int port) {
- serverPort = port;
- }
-
- public void initSecTypes() {
- nSecTypes = 0;
- }
-
// Methods to be overridden in a derived class
+ // Note: These must be called by any deriving classes
+
+ public void setDesktopSize(int w, int h) {
+ decoder.flush();
+
+ super.setDesktopSize(w,h);
+ }
+
+ public void setExtendedDesktopSize(int reason,
+ int result, int w, int h,
+ ScreenSet layout) {
+ decoder.flush();
+
+ super.setExtendedDesktopSize(reason, result, w, h, layout);
+ }
+
// getIdVerifier() returns the identity verifier associated with the connection.
// Ownership of the IdentityVerifier is retained by the CConnection instance.
//public IdentityVerifier getIdentityVerifier() { return 0; }
+ public void framebufferUpdateStart()
+ {
+ super.framebufferUpdateStart();
+ }
+
+ public void framebufferUpdateEnd()
+ {
+ decoder.flush();
+
+ super.framebufferUpdateEnd();
+ }
+
+ public void dataRect(Rect r, int encoding)
+ {
+ decoder.decodeRect(r, encoding, framebuffer);
+ }
+
// authSuccess() is called when authentication has succeeded.
- public void authSuccess() {}
+ public void authSuccess()
+ {
+ }
// serverInit() is called when the ServerInit message is received. The
// derived class must call on to CConnection::serverInit().
@@ -303,34 +370,17 @@
vlog.debug("initialisation done");
}
- // getCSecurity() gets the CSecurity object for the given type. The type
- // is guaranteed to be one of the secTypes passed in to addSecType(). The
- // CSecurity object's destroy() method will be called by the CConnection
- // from its destructor.
- //abstract public CSecurity getCSecurity(int secType);
-
- // getCurrentCSecurity() gets the CSecurity instance used for this
- // connection.
- //public CSecurity getCurrentCSecurity() { return security; }
-
- // setClientSecTypeOrder() determines whether the client should obey the
- // server's security type preference, by picking the first server security
- // type that the client supports, or whether it should pick the first type
- // that the server supports, from the client-supported list of types.
- public void setClientSecTypeOrder( boolean csto ) {
- clientSecTypeOrder = csto;
- }
-
// Other methods
- public CMsgReaderV3 reader() { return reader_; }
- public CMsgWriterV3 writer() { return writer_; }
+ public CMsgReader reader() { return reader_; }
+ public CMsgWriter writer() { return writer_; }
public InStream getInStream() { return is; }
public OutStream getOutStream() { return os; }
+ // Access method used by SSecurity implementations that can verify servers'
+ // Identities, to determine the unique(ish) name of the server.
public String getServerName() { return serverName; }
- public int getServerPort() { return serverPort; }
public static final int RFBSTATE_UNINITIALISED = 0;
public static final int RFBSTATE_PROTOCOL_VERSION = 1;
@@ -343,7 +393,17 @@
public int state() { return state_; }
- protected final void setState(int s) { state_ = s; }
+ public int getServerPort() { return serverPort; }
+ public void setServerPort(int port) {
+ serverPort = port;
+ }
+
+ protected void setState(int s) { state_ = s; }
+
+ protected void setReader(CMsgReader r) { reader_ = r; }
+ protected void setWriter(CMsgWriter w) { writer_ = w; }
+
+ protected ModifiablePixelBuffer getFramebuffer() { return framebuffer; }
public void fence(int flags, int len, byte[] data)
{
@@ -373,20 +433,27 @@
throw new AuthFailureException(reason);
}
- InStream is;
- OutStream os;
- CMsgReaderV3 reader_;
- CMsgWriterV3 writer_;
- boolean shared;
+ private InStream is;
+ private OutStream os;
+ private CMsgReader reader_;
+ private CMsgWriter writer_;
+ private boolean deleteStreamsWhenDone;
+ private boolean shared;
+ private int state_ = RFBSTATE_UNINITIALISED;
+
+ private String serverName;
+
+ private boolean useProtocol3_3;
+
+ protected ModifiablePixelBuffer framebuffer;
+ private DecodeManager decoder;
+
public CSecurity csecurity;
public SecurityClient security;
public static final int maxSecTypes = 8;
int nSecTypes;
int[] secTypes;
- int state_ = RFBSTATE_UNINITIALISED;
- String serverName;
int serverPort;
- boolean useProtocol3_3;
boolean clientSecTypeOrder;
static LogWriter vlog = new LogWriter("CConnection");
diff --git a/java/com/tigervnc/rfb/CMsgHandler.java b/java/com/tigervnc/rfb/CMsgHandler.java
index dd9767e..9940598 100644
--- a/java/com/tigervnc/rfb/CMsgHandler.java
+++ b/java/com/tigervnc/rfb/CMsgHandler.java
@@ -78,25 +78,18 @@
String x509subject) {}
public void setCursor(int width, int height, Point hotspot,
- int[] data, byte[] mask) {}
+ byte[] data, byte[] mask) {}
public void serverInit() {}
public void framebufferUpdateStart() {}
public void framebufferUpdateEnd() {}
- public void beginRect(Rect r, int encoding) {}
- public void endRect(Rect r, int encoding) {}
+ public void dataRect(Rect r, int encoding) {}
public void setColourMapEntries(int firstColour, int nColours,
int[] rgbs) { }
public void bell() {}
public void serverCutText(String str, int len) {}
- public void fillRect(Rect r, int pix) {}
- public void imageRect(Rect r, Object pixels) {}
- public void copyRect(Rect r, int srcX, int srcY) {}
-
- abstract public PixelFormat getPreferredPF();
-
public ConnParams cp;
static LogWriter vlog = new LogWriter("CMsgHandler");
diff --git a/java/com/tigervnc/rfb/CMsgReader.java b/java/com/tigervnc/rfb/CMsgReader.java
index a93324c..e9cad89 100644
--- a/java/com/tigervnc/rfb/CMsgReader.java
+++ b/java/com/tigervnc/rfb/CMsgReader.java
@@ -28,7 +28,7 @@
import java.nio.charset.Charset;
import com.tigervnc.rdr.*;
-abstract public class CMsgReader {
+public class CMsgReader {
protected CMsgReader(CMsgHandler handler_, InStream is_)
{
@@ -37,7 +37,86 @@
is = is_;
imageBuf = null;
imageBufSize = 0;
- decoders = new Decoder[Encodings.encodingMax+1];
+ nUpdateRectsLeft = 0;
+ }
+
+ public void readServerInit()
+ {
+ int width = is.readU16();
+ int height = is.readU16();
+ handler.setDesktopSize(width, height);
+ PixelFormat pf = new PixelFormat();
+ pf.read(is);
+ handler.setPixelFormat(pf);
+ String name = is.readString();
+ handler.setName(name);
+ handler.serverInit();
+ }
+
+ public void readMsg()
+ {
+ if (nUpdateRectsLeft == 0) {
+ int type = is.readU8();
+
+ switch (type) {
+ case MsgTypes.msgTypeSetColourMapEntries:
+ readSetColourMapEntries();
+ break;
+ case MsgTypes.msgTypeBell:
+ readBell();
+ break;
+ case MsgTypes.msgTypeServerCutText:
+ readServerCutText();
+ break;
+ case MsgTypes.msgTypeFramebufferUpdate:
+ readFramebufferUpdate();
+ break;
+ case MsgTypes.msgTypeServerFence:
+ readFence();
+ break;
+ case MsgTypes.msgTypeEndOfContinuousUpdates:
+ readEndOfContinuousUpdates();
+ break;
+ default:
+ //fprintf(stderr, "unknown message type %d\n", type);
+ throw new Exception("unknown message type");
+ }
+ } else {
+ int x = is.readU16();
+ int y = is.readU16();
+ int w = is.readU16();
+ int h = is.readU16();
+ int encoding = is.readS32();
+
+ switch (encoding) {
+ case Encodings.pseudoEncodingLastRect:
+ nUpdateRectsLeft = 1; // this rectangle is the last one
+ break;
+ case Encodings.pseudoEncodingCursor:
+ readSetCursor(w, h, new Point(x,y));
+ break;
+ case Encodings.pseudoEncodingDesktopName:
+ readSetDesktopName(x, y, w, h);
+ break;
+ case Encodings.pseudoEncodingDesktopSize:
+ handler.setDesktopSize(w, h);
+ break;
+ case Encodings.pseudoEncodingExtendedDesktopSize:
+ readExtendedDesktopSize(x, y, w, h);
+ break;
+ case Encodings.pseudoEncodingClientRedirect:
+ nUpdateRectsLeft = 0;
+ readClientRedirect(x, y, w, h);
+ return;
+ default:
+ readRect(new Rect(x, y, x+w, y+h), encoding);
+ break;
+ };
+
+ nUpdateRectsLeft--;
+ if (nUpdateRectsLeft == 0)
+ handler.framebufferUpdateEnd();
+ }
}
protected void readSetColourMapEntries()
@@ -72,6 +151,43 @@
handler.serverCutText(chars.toString(), len);
}
+ protected void readFence()
+ {
+ int flags;
+ int len;
+ byte[] data = new byte[64];
+
+ is.skip(3);
+
+ flags = is.readU32();
+
+ len = is.readU8();
+ if (len > data.length) {
+ System.out.println("Ignoring fence with too large payload\n");
+ is.skip(len);
+ return;
+ }
+
+ is.readBytes(data, 0, len);
+
+ handler.fence(flags, len, data);
+ }
+
+ protected void readEndOfContinuousUpdates()
+ {
+ handler.endOfContinuousUpdates();
+ }
+
+ protected void readFramebufferUpdate()
+ {
+ is.skip(1);
+ nUpdateRectsLeft = is.readU16();
+ handler.framebufferUpdateStart();
+ }
+
+
+
+ /*
protected void readFramebufferUpdateStart()
{
handler.framebufferUpdateStart();
@@ -81,6 +197,7 @@
{
handler.framebufferUpdateEnd();
}
+ */
protected void readRect(Rect r, int encoding)
{
@@ -94,45 +211,70 @@
if (r.is_empty())
vlog.error("Ignoring zero size rect");
- handler.beginRect(r, encoding);
-
- if (encoding == Encodings.encodingCopyRect) {
- readCopyRect(r);
- } else {
-
- if (decoders[encoding] == null) {
- decoders[encoding] = Decoder.createDecoder(encoding, this);
- if (decoders[encoding] == null) {
- vlog.error("Unknown rect encoding "+encoding);
- throw new Exception("Unknown rect encoding");
- }
- }
- decoders[encoding].readRect(r, handler);
- }
-
- handler.endRect(r, encoding);
- }
-
- protected void readCopyRect(Rect r)
- {
- int srcX = is.readU16();
- int srcY = is.readU16();
- handler.copyRect(r, srcX, srcY);
+ handler.dataRect(r, encoding);
}
protected void readSetCursor(int width, int height, Point hotspot)
{
- int data_len = width * height;
+ int data_len = width * height * (handler.cp.pf().bpp/8);
int mask_len = ((width+7)/8) * height;
- int[] data = new int[data_len];
+ byte[] data = new byte[data_len];
byte[] mask = new byte[mask_len];
- is.readPixels(data, data_len, (handler.cp.pf().bpp/8), handler.cp.pf().bigEndian);
+ is.readBytes(data, 0, data_len);
is.readBytes(mask, 0, mask_len);
handler.setCursor(width, height, hotspot, data, mask);
}
+ protected void readSetDesktopName(int x, int y, int w, int h)
+ {
+ String name = is.readString();
+
+ if (x != 0 || y != 0 || w != 0 || h != 0) {
+ vlog.error("Ignoring DesktopName rect with non-zero position/size");
+ } else {
+ handler.setName(name);
+ }
+
+ }
+
+ protected void readExtendedDesktopSize(int x, int y, int w, int h)
+ {
+ int screens, i;
+ int id, flags;
+ int sx, sy, sw, sh;
+ ScreenSet layout = new ScreenSet();
+
+ screens = is.readU8();
+ is.skip(3);
+
+ for (i = 0;i < screens;i++) {
+ id = is.readU32();
+ sx = is.readU16();
+ sy = is.readU16();
+ sw = is.readU16();
+ sh = is.readU16();
+ flags = is.readU32();
+
+ layout.add_screen(new Screen(id, sx, sy, sw, sh, flags));
+ }
+
+ handler.setExtendedDesktopSize(x, y, w, h, layout);
+ }
+
+ protected void readClientRedirect(int x, int y, int w, int h)
+ {
+ int port = is.readU16();
+ String host = is.readString();
+ String x509subject = is.readString();
+
+ if (x != 0 || y != 0 || w != 0 || h != 0)
+ vlog.error("Ignoring ClientRedirect rect with non-zero position/size");
+ else
+ handler.clientRedirect(port, host, x509subject);
+ }
+
public int[] getImageBuf(int required) { return getImageBuf(required, 0, 0); }
public int[] getImageBuf(int required, int requested, int nPixels)
@@ -154,23 +296,13 @@
return imageBuf;
}
- public final int bpp()
- {
- return handler.cp.pf().bpp;
- }
-
- abstract public void readServerInit();
-
- // readMsg() reads a message, calling the handler as appropriate.
- abstract public void readMsg();
-
public InStream getInStream() { return is; }
public int imageBufIdealSize;
protected CMsgHandler handler;
protected InStream is;
- protected Decoder[] decoders;
+ protected int nUpdateRectsLeft;
protected int[] imageBuf;
protected int imageBufSize;
diff --git a/java/com/tigervnc/rfb/CMsgReaderV3.java b/java/com/tigervnc/rfb/CMsgReaderV3.java
deleted file mode 100644
index e09d3bb..0000000
--- a/java/com/tigervnc/rfb/CMsgReaderV3.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright 2009-2011 Pierre Ossman for Cendio AB
- * Copyright (C) 2011 Brian P. Hinz
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this software; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
- * USA.
- */
-
-package com.tigervnc.rfb;
-
-import com.tigervnc.rdr.*;
-
-public class CMsgReaderV3 extends CMsgReader {
-
- public CMsgReaderV3(CMsgHandler handler_, InStream is_)
- {
- super(handler_, is_);
- nUpdateRectsLeft = 0;
- }
-
- public void readServerInit()
- {
- int width = is.readU16();
- int height = is.readU16();
- handler.setDesktopSize(width, height);
- PixelFormat pf = new PixelFormat();
- pf.read(is);
- handler.setPixelFormat(pf);
- String name = is.readString();
- handler.setName(name);
- handler.serverInit();
- }
-
- public void readMsg()
- {
- if (nUpdateRectsLeft == 0) {
-
- int type = is.readU8();
- switch (type) {
- case MsgTypes.msgTypeFramebufferUpdate: readFramebufferUpdate(); break;
- case MsgTypes.msgTypeSetColourMapEntries: readSetColourMapEntries(); break;
- case MsgTypes.msgTypeBell: readBell(); break;
- case MsgTypes.msgTypeServerCutText: readServerCutText(); break;
- case MsgTypes.msgTypeServerFence: readFence(); break;
- case MsgTypes.msgTypeEndOfContinuousUpdates: readEndOfContinuousUpdates(); break;
- default:
- vlog.error("unknown message type "+type);
- throw new Exception("unknown message type");
- }
-
- } else {
-
- int x = is.readU16();
- int y = is.readU16();
- int w = is.readU16();
- int h = is.readU16();
- int encoding = is.readS32();
-
- switch (encoding) {
- case Encodings.pseudoEncodingDesktopSize:
- handler.setDesktopSize(w, h);
- break;
- case Encodings.pseudoEncodingExtendedDesktopSize:
- readExtendedDesktopSize(x, y, w, h);
- break;
- case Encodings.pseudoEncodingDesktopName:
- readSetDesktopName(x, y, w, h);
- break;
- case Encodings.pseudoEncodingCursor:
- readSetCursor(w, h, new Point(x,y));
- break;
- case Encodings.pseudoEncodingLastRect:
- nUpdateRectsLeft = 1; // this rectangle is the last one
- break;
- case Encodings.pseudoEncodingClientRedirect:
- readClientRedirect(x, y, w, h);
- break;
- default:
- readRect(new Rect(x, y, x+w, y+h), encoding);
- break;
- }
-
- nUpdateRectsLeft--;
- if (nUpdateRectsLeft == 0) handler.framebufferUpdateEnd();
- }
- }
-
- void readFramebufferUpdate()
- {
- is.skip(1);
- nUpdateRectsLeft = is.readU16();
- handler.framebufferUpdateStart();
- }
-
- void readSetDesktopName(int x, int y, int w, int h)
- {
- String name = is.readString();
-
- if (x != 0 || y != 0 || w != 0 || h != 0) {
- vlog.error("Ignoring DesktopName rect with non-zero position/size");
- } else {
- handler.setName(name);
- }
-
- }
-
- void readExtendedDesktopSize(int x, int y, int w, int h)
- {
- int screens, i;
- int id, flags;
- int sx, sy, sw, sh;
- ScreenSet layout = new ScreenSet();
-
- screens = is.readU8();
- is.skip(3);
-
- for (i = 0;i < screens;i++) {
- id = is.readU32();
- sx = is.readU16();
- sy = is.readU16();
- sw = is.readU16();
- sh = is.readU16();
- flags = is.readU32();
-
- layout.add_screen(new Screen(id, sx, sy, sw, sh, flags));
- }
-
- handler.setExtendedDesktopSize(x, y, w, h, layout);
- }
-
- void readFence()
- {
- int flags;
- int len;
- byte[] data = new byte[64];
-
- is.skip(3);
-
- flags = is.readU32();
-
- len = is.readU8();
- if (len > data.length) {
- System.out.println("Ignoring fence with too large payload\n");
- is.skip(len);
- return;
- }
-
- is.readBytes(data, 0, len);
-
- handler.fence(flags, len, data);
- }
-
- void readEndOfContinuousUpdates()
- {
- handler.endOfContinuousUpdates();
- }
-
- void readClientRedirect(int x, int y, int w, int h)
- {
- int port = is.readU16();
- String host = is.readString();
- String x509subject = is.readString();
-
- if (x != 0 || y != 0 || w != 0 || h != 0) {
- vlog.error("Ignoring ClientRedirect rect with non-zero position/size");
- } else {
- handler.clientRedirect(port, host, x509subject);
- }
- }
-
- int nUpdateRectsLeft;
-
- static LogWriter vlog = new LogWriter("CMsgReaderV3");
-}
diff --git a/java/com/tigervnc/rfb/CMsgWriter.java b/java/com/tigervnc/rfb/CMsgWriter.java
index 0d3dccf..bdf50c2 100644
--- a/java/com/tigervnc/rfb/CMsgWriter.java
+++ b/java/com/tigervnc/rfb/CMsgWriter.java
@@ -20,13 +20,23 @@
package com.tigervnc.rfb;
+import com.tigervnc.rdr.*;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
-import com.tigervnc.rdr.*;
+import java.util.Iterator;
-abstract public class CMsgWriter {
+public class CMsgWriter {
- abstract public void writeClientInit(boolean shared);
+ protected CMsgWriter(ConnParams cp_, OutStream os_)
+ {
+ cp = cp_;
+ os = os_;
+ }
+
+ synchronized public void writeClientInit(boolean shared) {
+ os.writeU8(shared?1:0);
+ endMsg();
+ }
synchronized public void writeSetPixelFormat(PixelFormat pf)
{
@@ -53,6 +63,7 @@
{
int nEncodings = 0;
int[] encodings = new int[Encodings.encodingMax+3];
+
if (cp.supportsLocalCursor)
encodings[nEncodings++] = Encodings.pseudoEncodingCursor;
if (cp.supportsDesktopResize)
@@ -68,7 +79,6 @@
encodings[nEncodings++] = Encodings.pseudoEncodingContinuousUpdates;
encodings[nEncodings++] = Encodings.pseudoEncodingFence;
-
if (Decoder.supported(preferredEncoding)) {
encodings[nEncodings++] = preferredEncoding;
}
@@ -98,9 +108,11 @@
// Remaining encodings
for (int i = Encodings.encodingMax; i >= 0; i--) {
switch (i) {
+ case Encodings.encodingCopyRect:
case Encodings.encodingTight:
case Encodings.encodingZRLE:
case Encodings.encodingHextile:
+ /* These have already been sent earlier */
break;
default:
if ((i != preferredEncoding) && Decoder.supported(i))
@@ -108,15 +120,44 @@
}
}
- encodings[nEncodings++] = Encodings.pseudoEncodingLastRect;
- if (cp.customCompressLevel && cp.compressLevel >= 0 && cp.compressLevel <= 9)
- encodings[nEncodings++] = Encodings.pseudoEncodingCompressLevel0 + cp.compressLevel;
- if (!cp.noJpeg && cp.qualityLevel >= 0 && cp.qualityLevel <= 9)
- encodings[nEncodings++] = Encodings.pseudoEncodingQualityLevel0 + cp.qualityLevel;
+ if (cp.compressLevel >= 0 && cp.compressLevel <= 9)
+ encodings[nEncodings++] =
+ Encodings.pseudoEncodingCompressLevel0 + cp.compressLevel;
+ if (cp.qualityLevel >= 0 && cp.qualityLevel <= 9)
+ encodings[nEncodings++] =
+ Encodings.pseudoEncodingQualityLevel0 + cp.qualityLevel;
writeSetEncodings(nEncodings, encodings);
}
+ synchronized public void writeSetDesktopSize(int width, int height,
+ ScreenSet layout)
+ {
+ if (!cp.supportsSetDesktopSize)
+ throw new Exception("Server does not support SetDesktopSize");
+
+ startMsg(MsgTypes.msgTypeSetDesktopSize);
+ os.pad(1);
+
+ os.writeU16(width);
+ os.writeU16(height);
+
+ os.writeU8(layout.num_screens());
+ os.pad(1);
+
+ for (Iterator<Screen> iter = layout.screens.iterator(); iter.hasNext(); ) {
+ Screen refScreen = (Screen)iter.next();
+ os.writeU32(refScreen.id);
+ os.writeU16(refScreen.dimensions.tl.x);
+ os.writeU16(refScreen.dimensions.tl.y);
+ os.writeU16(refScreen.dimensions.width());
+ os.writeU16(refScreen.dimensions.height());
+ os.writeU32(refScreen.flags);
+ }
+
+ endMsg();
+ }
+
synchronized public void writeFramebufferUpdateRequest(Rect r, boolean incremental)
{
startMsg(MsgTypes.msgTypeFramebufferUpdateRequest);
@@ -128,6 +169,44 @@
endMsg();
}
+ synchronized public void writeEnableContinuousUpdates(boolean enable,
+ int x, int y, int w, int h)
+ {
+ if (!cp.supportsContinuousUpdates)
+ throw new Exception("Server does not support continuous updates");
+
+ startMsg(MsgTypes.msgTypeEnableContinuousUpdates);
+
+ os.writeU8((enable?1:0));
+
+ os.writeU16(x);
+ os.writeU16(y);
+ os.writeU16(w);
+ os.writeU16(h);
+
+ endMsg();
+ }
+
+ synchronized public void writeFence(int flags, int len, byte[] data)
+ {
+ if (!cp.supportsFence)
+ throw new Exception("Server does not support fences");
+ if (len > 64)
+ throw new Exception("Too large fence payload");
+ if ((flags & ~fenceTypes.fenceFlagsSupported) != 0)
+ throw new Exception("Unknown fence flags");
+
+ startMsg(MsgTypes.msgTypeClientFence);
+ os.pad(3);
+
+ os.writeU32(flags);
+
+ os.writeU8(len);
+ os.writeBytes(data, 0, len);
+
+ endMsg();
+ }
+
synchronized public void writeKeyEvent(int key, boolean down)
{
startMsg(MsgTypes.msgTypeKeyEvent);
@@ -163,17 +242,14 @@
endMsg();
}
- abstract public void startMsg(int type);
- abstract public void endMsg();
+ synchronized public void startMsg(int type) {
+ os.writeU8(type);
+ }
- synchronized public void setOutStream(OutStream os_) { os = os_; }
-
- ConnParams getConnParams() { return cp; }
- OutStream getOutStream() { return os; }
-
- protected CMsgWriter(ConnParams cp_, OutStream os_) {cp = cp_; os = os_;}
+ synchronized public void endMsg() {
+ os.flush();
+ }
ConnParams cp;
OutStream os;
- static LogWriter vlog = new LogWriter("CMsgWriter");
}
diff --git a/java/com/tigervnc/rfb/CMsgWriterV3.java b/java/com/tigervnc/rfb/CMsgWriterV3.java
deleted file mode 100644
index 10c377a..0000000
--- a/java/com/tigervnc/rfb/CMsgWriterV3.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright 2009-2011 Pierre Ossman for Cendio AB
- * Copyright (C) 2011 Brian P. Hinz
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this software; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
- * USA.
- */
-
-package com.tigervnc.rfb;
-
-import com.tigervnc.rdr.*;
-import java.util.*;
-
-public class CMsgWriterV3 extends CMsgWriter {
-
- public CMsgWriterV3(ConnParams cp_, OutStream os_) { super(cp_, os_); }
-
- synchronized public void writeClientInit(boolean shared) {
- os.writeU8(shared?1:0);
- endMsg();
- }
-
- synchronized public void startMsg(int type) {
- os.writeU8(type);
- }
-
- synchronized public void endMsg() {
- os.flush();
- }
-
- synchronized public void writeSetDesktopSize(int width, int height,
- ScreenSet layout)
- {
- if (!cp.supportsSetDesktopSize)
- throw new Exception("Server does not support SetDesktopSize");
-
- startMsg(MsgTypes.msgTypeSetDesktopSize);
- os.pad(1);
-
- os.writeU16(width);
- os.writeU16(height);
-
- os.writeU8(layout.num_screens());
- os.pad(1);
-
- for (Iterator<Screen> iter = layout.screens.iterator(); iter.hasNext(); ) {
- Screen refScreen = (Screen)iter.next();
- os.writeU32(refScreen.id);
- os.writeU16(refScreen.dimensions.tl.x);
- os.writeU16(refScreen.dimensions.tl.y);
- os.writeU16(refScreen.dimensions.width());
- os.writeU16(refScreen.dimensions.height());
- os.writeU32(refScreen.flags);
- }
-
- endMsg();
- }
-
- synchronized public void writeFence(int flags, int len, byte[] data)
- {
- if (!cp.supportsFence)
- throw new Exception("Server does not support fences");
- if (len > 64)
- throw new Exception("Too large fence payload");
- if ((flags & ~fenceTypes.fenceFlagsSupported) != 0)
- throw new Exception("Unknown fence flags");
-
- startMsg(MsgTypes.msgTypeClientFence);
- os.pad(3);
-
- os.writeU32(flags);
-
- os.writeU8(len);
- os.writeBytes(data, 0, len);
-
- endMsg();
- }
-
- synchronized public void writeEnableContinuousUpdates(boolean enable,
- int x, int y, int w, int h)
- {
- if (!cp.supportsContinuousUpdates)
- throw new Exception("Server does not support continuous updates");
-
- startMsg(MsgTypes.msgTypeEnableContinuousUpdates);
-
- os.writeU8((enable?1:0));
-
- os.writeU16(x);
- os.writeU16(y);
- os.writeU16(w);
- os.writeU16(h);
-
- endMsg();
- }
-}
diff --git a/java/com/tigervnc/rfb/CSecurityTLS.java b/java/com/tigervnc/rfb/CSecurityTLS.java
index a8f6df3..4b20e0b 100644
--- a/java/com/tigervnc/rfb/CSecurityTLS.java
+++ b/java/com/tigervnc/rfb/CSecurityTLS.java
@@ -58,11 +58,11 @@
public class CSecurityTLS extends CSecurity {
- public static StringParameter x509ca
- = new StringParameter("x509ca",
+ public static StringParameter X509CA
+ = new StringParameter("X509CA",
"X509 CA certificate", "", Configuration.ConfigurationObject.ConfViewer);
- public static StringParameter x509crl
- = new StringParameter("x509crl",
+ public static StringParameter X509CRL
+ = new StringParameter("X509CRL",
"X509 CRL file", "", Configuration.ConfigurationObject.ConfViewer);
private void initGlobal()
@@ -80,8 +80,8 @@
manager = null;
setDefaults();
- cafile = x509ca.getData();
- crlfile = x509crl.getData();
+ cafile = X509CA.getData();
+ crlfile = X509CRL.getData();
}
public static String getDefaultCA() {
@@ -99,9 +99,9 @@
public static void setDefaults()
{
if (new File(getDefaultCA()).exists())
- x509ca.setDefaultStr(getDefaultCA());
+ X509CA.setDefaultStr(getDefaultCA());
if (new File(getDefaultCRL()).exists())
- x509crl.setDefaultStr(getDefaultCRL());
+ X509CRL.setDefaultStr(getDefaultCRL());
}
// FIXME:
diff --git a/java/com/tigervnc/rfb/CSecurityVeNCrypt.java b/java/com/tigervnc/rfb/CSecurityVeNCrypt.java
index f353874..179900a 100644
--- a/java/com/tigervnc/rfb/CSecurityVeNCrypt.java
+++ b/java/com/tigervnc/rfb/CSecurityVeNCrypt.java
@@ -134,7 +134,7 @@
Iterator<Integer> j;
List<Integer> secTypes = new ArrayList<Integer>();
- secTypes = Security.GetEnabledExtSecTypes();
+ secTypes = security.GetEnabledExtSecTypes();
/* Honor server's security type order */
for (i = 0; i < nAvailableTypes; i++) {
diff --git a/java/com/tigervnc/rfb/Configuration.java b/java/com/tigervnc/rfb/Configuration.java
index 5d140d9..11fc89a 100644
--- a/java/com/tigervnc/rfb/Configuration.java
+++ b/java/com/tigervnc/rfb/Configuration.java
@@ -26,14 +26,19 @@
import java.io.FileInputStream;
import java.io.PrintWriter;
+import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
+import com.tigervnc.vncviewer.VncViewer;
+
public class Configuration {
static LogWriter vlog = new LogWriter("Configuration");
+ private static final String IDENTIFIER_STRING = "TigerVNC Configuration file Version 1.0";
+
public enum ConfigurationObject { ConfGlobal, ConfServer, ConfViewer };
// -=- The Global/server/viewer Configuration objects
@@ -259,77 +264,6 @@
list(79, 10);
}
- public void readAppletParams(java.applet.Applet applet) {
- VoidParameter current = head;
- while (current != null) {
- String str = applet.getParameter(current.getName());
- if (str != null)
- current.setParam(str);
- current = current._next;
- }
- }
-
- public static void load(String filename) {
- if (filename == null)
- return;
-
- /* Read parameters from file */
- Properties props = new Properties();
- try {
- props.load(new FileInputStream(filename));
- } catch(java.security.AccessControlException e) {
- vlog.error("Cannot access system properties:"+e.getMessage());
- return;
- } catch (java.lang.Exception e) {
- vlog.error("Error opening config file:"+e.getMessage());
- return;
- }
-
- for (Iterator<String> i = props.stringPropertyNames().iterator(); i.hasNext();) {
- String name = (String)i.next();
- if (name.startsWith("[")) {
- // skip the section delimiters
- continue;
- } else if (name.equals("host")) {
- setParam("Server", props.getProperty(name));
- } else if (name.equals("disableclipboard")) {
- setParam("RecvClipboard", props.getProperty(name));
- setParam("SendClipboard", props.getProperty(name));
- } else if (name.equals("localcursor")) {
- setParam("UseLocalCursor", props.getProperty(name));
- } else {
- if (!setParam(name, props.getProperty(name)))
- vlog.debug("Cannot set parameter: "+name);
- }
- }
- }
-
- public static void save(String filename) {
- PrintWriter pw = null;
- try {
- pw = new PrintWriter(filename, "UTF-8");
- } catch (java.lang.Exception e) {
- vlog.error("Error opening config file:"+e.getMessage());
- return;
- }
-
- pw.println("# TigerVNC viewer configuration");
- DateFormat dateFormat = new SimpleDateFormat("E MMM d k:m:s z yyyy");
- Date date = new Date();
- pw.println("# "+dateFormat.format(date));
- VoidParameter current = Configuration.global().head;
- while (current != null) {
- String name = current.getName();
- String value = current.getValueStr();
- if (!name.equals("Server") && !name.equals("Port") &&
- value != null && value != current.getDefaultStr())
- pw.println(name+"="+current.getValueStr());
- current = current._next;
- }
- pw.flush();
- pw.close();
- }
-
// Name for this Configuration
private String name;
diff --git a/java/com/tigervnc/rfb/ConnParams.java b/java/com/tigervnc/rfb/ConnParams.java
index f1f5395..fe52770 100644
--- a/java/com/tigervnc/rfb/ConnParams.java
+++ b/java/com/tigervnc/rfb/ConnParams.java
@@ -1,6 +1,6 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright (C) 2011 D. R. Commander. All Rights Reserved.
- * Copyright (C) 2012 Brian P. Hinz
+ * Copyright (C) 2012-2016 Brian P. Hinz
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,11 +21,21 @@
package com.tigervnc.rfb;
import com.tigervnc.rdr.*;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
public class ConnParams {
- static LogWriter vlog = new LogWriter("ConnParams");
- public ConnParams() {
+ private static final int subsampleUndefined = -1;
+ private static final int subsampleNone = 0;
+ private static final int subsampleGray = 1;
+ private static final int subsample2X = 2;
+ private static final int subsample4X = 3;
+ private static final int subsample8X = 4;
+ private static final int subsample16X = 5;
+
+ public ConnParams()
+ {
majorVersion = 0; minorVersion = 0;
width = 0; height = 0; useCopyRect = false;
supportsLocalCursor = false; supportsLocalXCursor = false;
@@ -34,19 +44,17 @@
supportsSetDesktopSize = false; supportsFence = false;
supportsContinuousUpdates = false;
supportsClientRedirect = false;
- customCompressLevel = false; compressLevel = 6;
- noJpeg = false; qualityLevel = -1; fineQualityLevel = -1;
- subsampling = "SUBSAMP_UNDEFINED";
- name_ = null; nEncodings_ = 0; encodings_ = null;
- currentEncoding_ = Encodings.encodingRaw; verStrPos = 0;
+ compressLevel = 6; qualityLevel = -1; fineQualityLevel = -1;
+ subsampling = subsampleUndefined; name_ = null; verStrPos = 0;
+
+ encodings_ = new ArrayList();
screenLayout = new ScreenSet();
setName("");
}
- public boolean readVersion(InStream is)
+ public boolean readVersion(InStream is, AtomicBoolean done)
{
- done = false;
if (verStrPos >= 12) return false;
verStr = new StringBuilder(13);
while (is.checkNoWait(1) && verStrPos < 12) {
@@ -54,10 +62,10 @@
}
if (verStrPos < 12) {
- done = false;
+ done.set(false);
return true;
}
- done = true;
+ done.set(true);
verStr.insert(12,'0');
verStrPos = 0;
if (verStr.toString().matches("RFB \\d{3}\\.\\d{3}\\n0")) {
@@ -68,7 +76,8 @@
return false;
}
- public void writeVersion(OutStream os) {
+ public void writeVersion(OutStream os)
+ {
String str = String.format("RFB %03d.%03d\n", majorVersion, minorVersion);
os.writeBytes(str.getBytes(), 0, 12);
os.flush();
@@ -97,10 +106,11 @@
public PixelFormat pf() { return pf_; }
public void setPF(PixelFormat pf) {
+
pf_ = pf;
- if (pf.bpp != 8 && pf.bpp != 16 && pf.bpp != 32) {
+
+ if (pf.bpp != 8 && pf.bpp != 16 && pf.bpp != 32)
throw new Exception("setPF: not 8, 16 or 32 bpp?");
- }
}
public String name() { return name_; }
@@ -109,80 +119,96 @@
name_ = name;
}
- public int currentEncoding() { return currentEncoding_; }
- public int nEncodings() { return nEncodings_; }
- public int[] encodings() { return encodings_; }
+ public boolean supportsEncoding(int encoding)
+ {
+ return encodings_.indexOf(encoding) != -1;
+ }
+
public void setEncodings(int nEncodings, int[] encodings)
{
- if (nEncodings > nEncodings_) {
- encodings_ = new int[nEncodings];
- }
- nEncodings_ = nEncodings;
useCopyRect = false;
supportsLocalCursor = false;
supportsDesktopResize = false;
supportsExtendedDesktopSize = false;
supportsLocalXCursor = false;
supportsLastRect = false;
- customCompressLevel = false;
compressLevel = -1;
- noJpeg = true;
qualityLevel = -1;
fineQualityLevel = -1;
- subsampling = "SUBSAMP_UNDEFINED";
- currentEncoding_ = Encodings.encodingRaw;
+ subsampling = subsampleUndefined;
+
+ encodings_.clear();
+ encodings_.add(Encodings.encodingRaw);
for (int i = nEncodings-1; i >= 0; i--) {
- encodings_[i] = encodings[i];
-
- if (encodings[i] == Encodings.encodingCopyRect)
+ switch (encodings[i]) {
+ case Encodings.encodingCopyRect:
useCopyRect = true;
- else if (encodings[i] == Encodings.pseudoEncodingCursor)
+ break;
+ case Encodings.pseudoEncodingCursor:
supportsLocalCursor = true;
- else if (encodings[i] == Encodings.pseudoEncodingXCursor)
+ break;
+ case Encodings.pseudoEncodingXCursor:
supportsLocalXCursor = true;
- else if (encodings[i] == Encodings.pseudoEncodingDesktopSize)
+ break;
+ case Encodings.pseudoEncodingDesktopSize:
supportsDesktopResize = true;
- else if (encodings[i] == Encodings.pseudoEncodingExtendedDesktopSize)
+ break;
+ case Encodings.pseudoEncodingExtendedDesktopSize:
supportsExtendedDesktopSize = true;
- else if (encodings[i] == Encodings.pseudoEncodingDesktopName)
+ break;
+ case Encodings.pseudoEncodingDesktopName:
supportsDesktopRename = true;
- else if (encodings[i] == Encodings.pseudoEncodingLastRect)
+ break;
+ case Encodings.pseudoEncodingLastRect:
supportsLastRect = true;
- else if (encodings[i] == Encodings.pseudoEncodingFence)
+ break;
+ case Encodings.pseudoEncodingFence:
supportsFence = true;
- else if (encodings[i] == Encodings.pseudoEncodingContinuousUpdates)
+ break;
+ case Encodings.pseudoEncodingContinuousUpdates:
supportsContinuousUpdates = true;
- else if (encodings[i] == Encodings.pseudoEncodingClientRedirect)
+ break;
+ case Encodings.pseudoEncodingClientRedirect:
supportsClientRedirect = true;
- else if (encodings[i] >= Encodings.pseudoEncodingCompressLevel0 &&
- encodings[i] <= Encodings.pseudoEncodingCompressLevel9) {
- customCompressLevel = true;
- compressLevel = encodings[i] - Encodings.pseudoEncodingCompressLevel0;
- } else if (encodings[i] >= Encodings.pseudoEncodingQualityLevel0 &&
- encodings[i] <= Encodings.pseudoEncodingQualityLevel9) {
- noJpeg = false;
- qualityLevel = encodings[i] - Encodings.pseudoEncodingQualityLevel0;
- } else if (encodings[i] <= Encodings.encodingMax &&
- Encoder.supported(encodings[i]))
- currentEncoding_ = encodings[i];
- }
-
- // If the TurboVNC fine quality/subsampling encodings exist, let them
- // override the coarse TightVNC quality level
- for (int i = nEncodings-1; i >= 0; i--) {
- if (encodings[i] >= Encodings.pseudoEncodingFineQualityLevel0 + 1 &&
- encodings[i] <= Encodings.pseudoEncodingFineQualityLevel100) {
- noJpeg = false;
- fineQualityLevel = encodings[i] - Encodings.pseudoEncodingFineQualityLevel0;
- } else if (encodings[i] >= Encodings.pseudoEncodingSubsamp1X &&
- encodings[i] <= Encodings.pseudoEncodingSubsampGray) {
- noJpeg = false;
- subsampling = JpegCompressor.subsamplingName(encodings[i] - Encodings.pseudoEncodingSubsamp1X);
+ break;
+ case Encodings.pseudoEncodingSubsamp1X:
+ subsampling = subsampleNone;
+ break;
+ case Encodings.pseudoEncodingSubsampGray:
+ subsampling = subsampleGray;
+ break;
+ case Encodings.pseudoEncodingSubsamp2X:
+ subsampling = subsample2X;
+ break;
+ case Encodings.pseudoEncodingSubsamp4X:
+ subsampling = subsample4X;
+ break;
+ case Encodings.pseudoEncodingSubsamp8X:
+ subsampling = subsample8X;
+ break;
+ case Encodings.pseudoEncodingSubsamp16X:
+ subsampling = subsample16X;
+ break;
}
+
+ if (encodings[i] >= Encodings.pseudoEncodingCompressLevel0 &&
+ encodings[i] <= Encodings.pseudoEncodingCompressLevel9)
+ compressLevel = encodings[i] - Encodings.pseudoEncodingCompressLevel0;
+
+ if (encodings[i] >= Encodings.pseudoEncodingQualityLevel0 &&
+ encodings[i] <= Encodings.pseudoEncodingQualityLevel9)
+ qualityLevel = encodings[i] - Encodings.pseudoEncodingQualityLevel0;
+
+ if (encodings[i] >= Encodings.pseudoEncodingFineQualityLevel0 &&
+ encodings[i] <= Encodings.pseudoEncodingFineQualityLevel100)
+ fineQualityLevel = encodings[i] - Encodings.pseudoEncodingFineQualityLevel0;
+
+ if (encodings[i] > 0)
+ encodings_.add(encodings[i]);
}
}
- public boolean done;
+
public boolean useCopyRect;
public boolean supportsLocalCursor;
@@ -190,25 +216,22 @@
public boolean supportsDesktopResize;
public boolean supportsExtendedDesktopSize;
public boolean supportsDesktopRename;
- public boolean supportsClientRedirect;
- public boolean supportsFence;
- public boolean supportsContinuousUpdates;
public boolean supportsLastRect;
+ public boolean supportsClientRedirect;
public boolean supportsSetDesktopSize;
+ public boolean supportsFence;
+ public boolean supportsContinuousUpdates;
- public boolean customCompressLevel;
public int compressLevel;
- public boolean noJpeg;
public int qualityLevel;
public int fineQualityLevel;
- public String subsampling;
+ public int subsampling;
private PixelFormat pf_;
private String name_;
- private int nEncodings_;
- private int[] encodings_;
- private int currentEncoding_;
+ private Cursor cursor_;
+ private ArrayList encodings_;
private StringBuilder verStr;
private int verStrPos;
}
diff --git a/java/com/tigervnc/rfb/CopyRectDecoder.java b/java/com/tigervnc/rfb/CopyRectDecoder.java
new file mode 100644
index 0000000..a4298fd
--- /dev/null
+++ b/java/com/tigervnc/rfb/CopyRectDecoder.java
@@ -0,0 +1,44 @@
+/* Copyright 2014 Pierre Ossman <ossman@cendio.se> for Cendio AB
+ * Copyright 2016 Brian P. Hinz
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+package com.tigervnc.rfb;
+
+import com.tigervnc.rdr.*;
+
+public class CopyRectDecoder extends Decoder {
+
+ public CopyRectDecoder() { super(DecoderFlags.DecoderPlain); }
+
+ public void readRect(Rect r, InStream is,
+ ConnParams cp, OutStream os)
+ {
+ os.copyBytes(is, 4);
+ }
+
+ public void decodeRect(Rect r, Object buffer,
+ int buflen, ConnParams cp,
+ ModifiablePixelBuffer pb)
+ {
+ MemInStream is = new MemInStream((byte[])buffer, 0, buflen);
+ int srcX = is.readU16();
+ int srcY = is.readU16();
+ pb.copyRect(r, new Point(r.tl.x-srcX, r.tl.y-srcY));
+ }
+
+}
diff --git a/java/com/tigervnc/rfb/Cursor.java b/java/com/tigervnc/rfb/Cursor.java
index 78aa0fb..05122ae 100644
--- a/java/com/tigervnc/rfb/Cursor.java
+++ b/java/com/tigervnc/rfb/Cursor.java
@@ -20,11 +20,18 @@
public class Cursor extends ManagedPixelBuffer {
+ public Cursor(PixelFormat pf, int w, int h) {
+ super(pf, w, h);
+ hotspot = new Point(0, 0);
+ }
+
public void setSize(int w, int h) {
+ int oldMaskLen = maskLen();
super.setSize(w, h);
- if (mask == null || mask.length < maskLen())
+ if (mask == null || maskLen() > oldMaskLen)
mask = new byte[maskLen()];
}
+
public int maskLen() { return (width() + 7) / 8 * height(); }
public Point hotspot;
diff --git a/java/com/tigervnc/rfb/DecodeManager.java b/java/com/tigervnc/rfb/DecodeManager.java
new file mode 100644
index 0000000..9e254ad
--- /dev/null
+++ b/java/com/tigervnc/rfb/DecodeManager.java
@@ -0,0 +1,386 @@
+/* Copyright 2015 Pierre Ossman for Cendio AB
+ * Copyright 2016 Brian P. Hinz
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+package com.tigervnc.rfb;
+
+import java.lang.Runtime;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.*;
+
+import com.tigervnc.rdr.*;
+import com.tigervnc.rdr.Exception;
+
+import static com.tigervnc.rfb.Decoder.DecoderFlags.*;
+
+public class DecodeManager {
+
+ static LogWriter vlog = new LogWriter("DecodeManager");
+
+ public DecodeManager(CConnection conn) {
+ int cpuCount;
+
+ this.conn = conn; threadException = null;
+ decoders = new Decoder[Encodings.encodingMax+1];
+
+ queueMutex = new ReentrantLock();
+ producerCond = queueMutex.newCondition();
+ consumerCond = queueMutex.newCondition();
+
+ //cpuCount = 1;
+ cpuCount = Runtime.getRuntime().availableProcessors();
+ if (cpuCount == 0) {
+ vlog.error("Unable to determine the number of CPU cores on this system");
+ cpuCount = 1;
+ } else {
+ vlog.info("Detected "+cpuCount+" CPU core(s)");
+ // No point creating more threads than this, they'll just end up
+ // wasting CPU fighting for locks
+ if (cpuCount > 4)
+ cpuCount = 4;
+ // The overhead of threading is small, but not small enough to
+ // ignore on single CPU systems
+ if (cpuCount == 1)
+ vlog.info("Decoding data on main thread");
+ else
+ vlog.info("Creating "+cpuCount+" decoder thread(s)");
+ }
+
+ freeBuffers = new ArrayDeque<MemOutStream>(cpuCount*2);
+ workQueue = new ArrayDeque<QueueEntry>(cpuCount);
+ threads = new ArrayList<DecodeThread>(cpuCount);
+ while (cpuCount-- > 0) {
+ // Twice as many possible entries in the queue as there
+ // are worker threads to make sure they don't stall
+ try {
+ freeBuffers.addLast(new MemOutStream());
+ freeBuffers.addLast(new MemOutStream());
+
+ threads.add(new DecodeThread(this));
+ } catch (IllegalStateException e) { }
+ }
+
+ }
+
+ public void decodeRect(Rect r, int encoding,
+ ModifiablePixelBuffer pb)
+ {
+ Decoder decoder;
+ MemOutStream bufferStream;
+
+ QueueEntry entry;
+
+ assert(pb != null);
+
+ if (!Decoder.supported(encoding)) {
+ vlog.error("Unknown encoding " + encoding);
+ throw new Exception("Unknown encoding");
+ }
+
+ if (decoders[encoding] == null) {
+ decoders[encoding] = Decoder.createDecoder(encoding);
+ if (decoders[encoding] == null) {
+ vlog.error("Unknown encoding " + encoding);
+ throw new Exception("Unknown encoding");
+ }
+ }
+
+ decoder = decoders[encoding];
+
+ // Fast path for single CPU machines to avoid the context
+ // switching overhead
+ if (threads.size() == 1) {
+ bufferStream = freeBuffers.getFirst();
+ bufferStream.clear();
+ decoder.readRect(r, conn.getInStream(), conn.cp, bufferStream);
+ decoder.decodeRect(r, (Object)bufferStream.data(), bufferStream.length(),
+ conn.cp, pb);
+ return;
+ }
+
+ // Wait for an available memory buffer
+ queueMutex.lock();
+
+ while (freeBuffers.isEmpty())
+ try {
+ producerCond.await();
+ } catch (InterruptedException e) { }
+
+ // Don't pop the buffer in case we throw an exception
+ // whilst reading
+ bufferStream = freeBuffers.getFirst();
+
+ queueMutex.unlock();
+
+ // First check if any thread has encountered a problem
+ throwThreadException();
+
+ // Read the rect
+ bufferStream.clear();
+ decoder.readRect(r, conn.getInStream(), conn.cp, bufferStream);
+
+ // Then try to put it on the queue
+ entry = new QueueEntry();
+
+ entry.active = false;
+ entry.rect = r;
+ entry.encoding = encoding;
+ entry.decoder = decoder;
+ entry.cp = conn.cp;
+ entry.pb = pb;
+ entry.bufferStream = bufferStream;
+ entry.affectedRegion = new Region(r);
+
+ decoder.getAffectedRegion(r, bufferStream.data(),
+ bufferStream.length(), conn.cp,
+ entry.affectedRegion);
+
+ // The workers add buffers to the end so it's safe to assume
+ // the front is still the same buffer
+ freeBuffers.removeFirst();
+
+ queueMutex.lock();
+
+ workQueue.addLast(entry);
+
+ // We only put a single entry on the queue so waking a single
+ // thread is sufficient
+ consumerCond.signal();
+
+ queueMutex.unlock();
+ }
+
+ public void flush()
+ {
+ queueMutex.lock();
+
+ while (!workQueue.isEmpty())
+ try {
+ producerCond.await();
+ } catch (InterruptedException e) { }
+
+ queueMutex.unlock();
+
+ throwThreadException();
+ }
+
+ private void setThreadException(Exception e)
+ {
+ //os::AutoMutex a(queueMutex);
+ queueMutex.lock();
+
+ if (threadException == null)
+ return;
+
+ threadException =
+ new Exception("Exception on worker thread: "+e.getMessage());
+ }
+
+ private void throwThreadException()
+ {
+ //os::AutoMutex a(queueMutex);
+ queueMutex.lock();
+
+ if (threadException == null)
+ return;
+
+ Exception e = new Exception(threadException.getMessage());
+
+ threadException = null;
+
+ throw e;
+ }
+
+ private class QueueEntry {
+
+ public QueueEntry() {
+ }
+ public boolean active;
+ public Rect rect;
+ public int encoding;
+ public Decoder decoder;
+ public ConnParams cp;
+ public ModifiablePixelBuffer pb;
+ public MemOutStream bufferStream;
+ public Region affectedRegion;
+ }
+
+ private class DecodeThread implements Runnable {
+
+ public DecodeThread(DecodeManager manager)
+ {
+ this.manager = manager;
+
+ stopRequested = false;
+
+ (thread = new Thread(this)).start();
+ }
+
+ public void stop()
+ {
+ //os::AutoMutex a(manager.queueMutex);
+ manager.queueMutex.lock();
+
+ if (!thread.isAlive())
+ return;
+
+ stopRequested = true;
+
+ // We can't wake just this thread, so wake everyone
+ manager.consumerCond.signalAll();
+ }
+
+ public void run()
+ {
+ manager.queueMutex.lock();
+ while (!stopRequested) {
+ QueueEntry entry;
+
+ // Look for an available entry in the work queue
+ entry = findEntry();
+ if (entry == null) {
+ // Wait and try again
+ try {
+ manager.consumerCond.await();
+ } catch (InterruptedException e) { }
+ continue;
+ }
+
+ // This is ours now
+ entry.active = true;
+
+ manager.queueMutex.unlock();
+
+ // Do the actual decoding
+ try {
+ entry.decoder.decodeRect(entry.rect, entry.bufferStream.data(),
+ entry.bufferStream.length(),
+ entry.cp, entry.pb);
+ } catch (com.tigervnc.rdr.Exception e) {
+ manager.setThreadException(e);
+ } catch(java.lang.Exception e) {
+ assert(false);
+ }
+
+ manager.queueMutex.lock();
+
+ // Remove the entry from the queue and give back the memory buffer
+ manager.freeBuffers.add(entry.bufferStream);
+ manager.workQueue.remove(entry);
+ entry = null;
+
+ // Wake the main thread in case it is waiting for a memory buffer
+ manager.producerCond.signal();
+ // This rect might have been blocking multiple other rects, so
+ // wake up every worker thread
+ if (manager.workQueue.size() > 1)
+ manager.consumerCond.signalAll();
+ }
+
+ manager.queueMutex.unlock();
+ }
+
+ protected QueueEntry findEntry()
+ {
+ Iterator<QueueEntry> iter;
+ Region lockedRegion = new Region();
+
+ if (manager.workQueue.isEmpty())
+ return null;
+
+ if (!manager.workQueue.peek().active)
+ return manager.workQueue.peek();
+
+ for (iter = manager.workQueue.iterator(); iter.hasNext();) {
+ QueueEntry entry;
+
+ Iterator<QueueEntry> iter2;
+
+ entry = iter.next();
+
+ // Another thread working on this?
+ if (entry.active) {
+ lockedRegion.assign_union(entry.affectedRegion);
+ continue;
+ }
+
+ // If this is an ordered decoder then make sure this is the first
+ // rectangle in the queue for that decoder
+ if ((entry.decoder.flags & DecoderOrdered) != 0) {
+ for (iter2 = manager.workQueue.iterator(); iter2.hasNext() && iter2 != iter;) {
+ if (entry.encoding == (iter2.next()).encoding) {
+ lockedRegion.assign_union(entry.affectedRegion);
+ continue;
+ }
+ }
+ }
+
+ // For a partially ordered decoder we must ask the decoder for each
+ // pair of rectangles.
+ if ((entry.decoder.flags & DecoderPartiallyOrdered) != 0) {
+ for (iter2 = manager.workQueue.iterator(); iter2.hasNext() && iter2 != iter;) {
+ QueueEntry entry2 = iter2.next();
+ if (entry.encoding != entry2.encoding)
+ continue;
+ if (entry.decoder.doRectsConflict(entry.rect,
+ entry.bufferStream.data(),
+ entry.bufferStream.length(),
+ entry2.rect,
+ entry2.bufferStream.data(),
+ entry2.bufferStream.length(),
+ entry.cp))
+ lockedRegion.assign_union(entry.affectedRegion);
+ continue;
+ }
+ }
+
+ // Check overlap with earlier rectangles
+ if (!lockedRegion.intersect(entry.affectedRegion).is_empty()) {
+ lockedRegion.assign_union(entry.affectedRegion);
+ continue;
+ }
+
+ return entry;
+
+ }
+
+ return null;
+ }
+
+ private DecodeManager manager;
+ private boolean stopRequested;
+
+ private Thread thread;
+
+ }
+
+ private CConnection conn;
+ private Decoder[] decoders;
+
+ private ArrayDeque<MemOutStream> freeBuffers;
+ private ArrayDeque<QueueEntry> workQueue;
+
+ private ReentrantLock queueMutex;
+ private Condition producerCond;
+ private Condition consumerCond;
+
+ private List<DecodeThread> threads;
+ private com.tigervnc.rdr.Exception threadException;
+
+}
diff --git a/java/com/tigervnc/rfb/Decoder.java b/java/com/tigervnc/rfb/Decoder.java
index f0ece0a..6bbed85 100644
--- a/java/com/tigervnc/rfb/Decoder.java
+++ b/java/com/tigervnc/rfb/Decoder.java
@@ -18,34 +18,80 @@
package com.tigervnc.rfb;
+import com.tigervnc.rdr.*;
+
abstract public class Decoder {
- abstract public void readRect(Rect r, CMsgHandler handler);
+ public static class DecoderFlags {
+ // A constant for decoders that don't need anything special
+ public static int DecoderPlain = 0;
+ // All rects for this decoder must be handled in order
+ public static int DecoderOrdered = 1 << 0;
+ // Only some of the rects must be handled in order,
+ // see doesRectsConflict()
+ public static int DecoderPartiallyOrdered = 1 << 1;
+ };
+
+ public Decoder(int flags)
+ {
+ this.flags = flags;
+ }
+
+ abstract public void readRect(Rect r, InStream is,
+ ConnParams cp, OutStream os);
+
+ abstract public void decodeRect(Rect r, Object buffer,
+ int buflen, ConnParams cp,
+ ModifiablePixelBuffer pb);
+
+ public void getAffectedRegion(Rect rect, Object buffer,
+ int buflen, ConnParams cp,
+ Region region)
+ {
+ region.reset(rect);
+ }
+
+ public boolean doRectsConflict(Rect rectA, Object bufferA,
+ int buflenA, Rect rectB,
+ Object bufferB, int buflenB,
+ ConnParams cp)
+ {
+ return false;
+ }
static public boolean supported(int encoding)
{
-/*
- return encoding <= Encodings.encodingMax && createFns[encoding];
-*/
- return (encoding == Encodings.encodingRaw ||
- encoding == Encodings.encodingRRE ||
- encoding == Encodings.encodingHextile ||
- encoding == Encodings.encodingTight ||
- encoding == Encodings.encodingZRLE);
- }
- static public Decoder createDecoder(int encoding, CMsgReader reader) {
-/*
- if (encoding <= Encodings.encodingMax && createFns[encoding])
- return (createFns[encoding])(reader);
- return 0;
-*/
switch(encoding) {
- case Encodings.encodingRaw: return new RawDecoder(reader);
- case Encodings.encodingRRE: return new RREDecoder(reader);
- case Encodings.encodingHextile: return new HextileDecoder(reader);
- case Encodings.encodingTight: return new TightDecoder(reader);
- case Encodings.encodingZRLE: return new ZRLEDecoder(reader);
+ case Encodings.encodingRaw:
+ case Encodings.encodingCopyRect:
+ case Encodings.encodingRRE:
+ case Encodings.encodingHextile:
+ case Encodings.encodingZRLE:
+ case Encodings.encodingTight:
+ return true;
+ default:
+ return false;
}
- return null;
}
+
+ static public Decoder createDecoder(int encoding) {
+ switch(encoding) {
+ case Encodings.encodingRaw:
+ return new RawDecoder();
+ case Encodings.encodingCopyRect:
+ return new CopyRectDecoder();
+ case Encodings.encodingRRE:
+ return new RREDecoder();
+ case Encodings.encodingHextile:
+ return new HextileDecoder();
+ case Encodings.encodingZRLE:
+ return new ZRLEDecoder();
+ case Encodings.encodingTight:
+ return new TightDecoder();
+ default:
+ return null;
+ }
+ }
+
+ public final int flags;
}
diff --git a/java/com/tigervnc/rfb/FullFramePixelBuffer.java b/java/com/tigervnc/rfb/FullFramePixelBuffer.java
new file mode 100644
index 0000000..1c3b095
--- /dev/null
+++ b/java/com/tigervnc/rfb/FullFramePixelBuffer.java
@@ -0,0 +1,54 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+package com.tigervnc.rfb;
+
+import java.awt.image.*;
+
+public class FullFramePixelBuffer extends ModifiablePixelBuffer {
+
+ public FullFramePixelBuffer(PixelFormat pf, int w, int h,
+ WritableRaster data_) {
+ super(pf, w, h);
+ data = data_;
+ }
+
+ protected FullFramePixelBuffer() {}
+
+ public WritableRaster getBufferRW(Rect r)
+ {
+ return data.createWritableChild(r.tl.x, r.tl.y, r.width(), r.height(),
+ 0, 0, null);
+ }
+
+ public void commitBufferRW(Rect r)
+ {
+ }
+
+ public Raster getBuffer(Rect r)
+ {
+ Raster src =
+ data.createChild(r.tl.x, r.tl.y, r.width(), r.height(), 0, 0, null);
+ WritableRaster dst =
+ data.createCompatibleWritableRaster(r.width(), r.height());
+ dst.setDataElements(0, 0, src);
+ return dst;
+ }
+
+ protected WritableRaster data;
+}
diff --git a/java/com/tigervnc/rfb/HextileDecoder.java b/java/com/tigervnc/rfb/HextileDecoder.java
index 94e91f7..b0744ca 100644
--- a/java/com/tigervnc/rfb/HextileDecoder.java
+++ b/java/com/tigervnc/rfb/HextileDecoder.java
@@ -18,22 +18,126 @@
package com.tigervnc.rfb;
+import java.awt.image.*;
+import java.nio.*;
+import java.util.Arrays;
+
import com.tigervnc.rdr.*;
public class HextileDecoder extends Decoder {
- public HextileDecoder(CMsgReader reader_) { reader = reader_; }
+ public static final int hextileRaw = (1 << 0);
+ public static final int hextileBgSpecified = (1 << 1);
+ public static final int hextileFgSpecified = (1 << 2);
+ public static final int hextileAnySubrects = (1 << 3);
+ public static final int hextileSubrectsColoured = (1 << 4);
- public void readRect(Rect r, CMsgHandler handler) {
- InStream is = reader.getInStream();
- int bytesPerPixel = handler.cp.pf().bpp / 8;
- boolean bigEndian = handler.cp.pf().bigEndian;
+ public HextileDecoder() { super(DecoderFlags.DecoderPlain); }
- int[] buf = reader.getImageBuf(16 * 16 * 4);
-
+ public void readRect(Rect r, InStream is,
+ ConnParams cp, OutStream os)
+ {
Rect t = new Rect();
- int bg = 0;
- int fg = 0;
+ int bytesPerPixel;
+
+ bytesPerPixel = cp.pf().bpp/8;
+
+ for (t.tl.y = r.tl.y; t.tl.y < r.br.y; t.tl.y += 16) {
+
+ t.br.y = Math.min(r.br.y, t.tl.y + 16);
+
+ for (t.tl.x = r.tl.x; t.tl.x < r.br.x; t.tl.x += 16) {
+ int tileType;
+
+ t.br.x = Math.min(r.br.x, t.tl.x + 16);
+
+ tileType = is.readU8() & 0xff;
+ os.writeU32(tileType);
+
+ if ((tileType & hextileRaw) != 0) {
+ os.copyBytes(is, t.area() * bytesPerPixel);
+ continue;
+ }
+
+ if ((tileType & hextileBgSpecified) != 0)
+ os.copyBytes(is, bytesPerPixel);
+
+ if ((tileType & hextileFgSpecified) != 0)
+ os.copyBytes(is, bytesPerPixel);
+
+ if ((tileType & hextileAnySubrects) != 0) {
+ int nSubrects;
+
+ nSubrects = is.readU8() & 0xff;
+ os.writeU32(nSubrects);
+
+ if ((tileType & hextileSubrectsColoured) != 0)
+ os.copyBytes(is, nSubrects * (bytesPerPixel + 2));
+ else
+ os.copyBytes(is, nSubrects * 2);
+ }
+ }
+ }
+ }
+
+ public void decodeRect(Rect r, Object buffer,
+ int buflen, ConnParams cp,
+ ModifiablePixelBuffer pb)
+ {
+ MemInStream is = new MemInStream((byte[])buffer, 0, buflen);
+ PixelFormat pf = cp.pf();
+ switch (pf.bpp) {
+ case 8: hextileDecode8(r, is, pf, pb); break;
+ case 16: hextileDecode16(r, is, pf, pb); break;
+ case 32: hextileDecode32(r, is, pf, pb); break;
+ }
+ }
+
+ private void hextileDecode8(Rect r, InStream is,
+ PixelFormat pf,
+ ModifiablePixelBuffer pb)
+ {
+ HEXTILE_DECODE(r, is, pf, pb);
+ }
+
+ private void hextileDecode16(Rect r, InStream is,
+ PixelFormat pf,
+ ModifiablePixelBuffer pb)
+ {
+ HEXTILE_DECODE(r, is, pf, pb);
+ }
+
+ private void hextileDecode32(Rect r, InStream is,
+ PixelFormat pf,
+ ModifiablePixelBuffer pb)
+ {
+ HEXTILE_DECODE(r, is, pf, pb);
+ }
+
+ private static ByteBuffer READ_PIXEL(InStream is, PixelFormat pf) {
+ ByteBuffer b = ByteBuffer.allocate(4);
+ switch (pf.bpp) {
+ case 8:
+ b.putInt(is.readOpaque8());
+ return ByteBuffer.allocate(1).put(b.get(3));
+ case 16:
+ b.putInt(is.readOpaque16());
+ return ByteBuffer.allocate(2).put(b.array(), 2, 2);
+ case 32:
+ default:
+ b.putInt(is.readOpaque32());
+ return b;
+ }
+ }
+
+ private void HEXTILE_DECODE(Rect r, InStream is,
+ PixelFormat pf,
+ ModifiablePixelBuffer pb)
+ {
+ Rect t = new Rect();
+ ByteBuffer bg = ByteBuffer.allocate(pf.bpp/8);
+ ByteBuffer fg = ByteBuffer.allocate(pf.bpp/8);
+ ByteBuffer buf = ByteBuffer.allocate(16 * 16 * 4);
for (t.tl.y = r.tl.y; t.tl.y < r.br.y; t.tl.y += 16) {
@@ -43,59 +147,51 @@
t.br.x = Math.min(r.br.x, t.tl.x + 16);
- int tileType = is.readU8();
+ int tileType = is.readU32();
- if ((tileType & Hextile.raw) != 0) {
- is.readPixels(buf, t.area(), bytesPerPixel, bigEndian);
- handler.imageRect(t, buf);
+ if ((tileType & hextileRaw) != 0) {
+ is.readBytes(buf, t.area() * (pf.bpp/8));
+ pb.imageRect(pf, t, buf.array());
continue;
}
- if ((tileType & Hextile.bgSpecified) != 0)
- bg = is.readPixel(bytesPerPixel, bigEndian);
+ if ((tileType & hextileBgSpecified) != 0)
+ bg = READ_PIXEL(is, pf);
int len = t.area();
- int ptr = 0;
- while (len-- > 0) buf[ptr++] = bg;
+ ByteBuffer ptr = buf.duplicate();
+ while (len-- > 0) ptr.put(bg.array());
- if ((tileType & Hextile.fgSpecified) != 0)
- fg = is.readPixel(bytesPerPixel, bigEndian);
+ if ((tileType & hextileFgSpecified) != 0)
+ fg = READ_PIXEL(is, pf);
- if ((tileType & Hextile.anySubrects) != 0) {
- int nSubrects = is.readU8();
+ if ((tileType & hextileAnySubrects) != 0) {
+ int nSubrects = is.readU32();
for (int i = 0; i < nSubrects; i++) {
- if ((tileType & Hextile.subrectsColoured) != 0)
- fg = is.readPixel(bytesPerPixel, bigEndian);
+ if ((tileType & hextileSubrectsColoured) != 0)
+ fg = READ_PIXEL(is, pf);
int xy = is.readU8();
int wh = is.readU8();
-/*
- Rect s = new Rect();
- s.tl.x = t.tl.x + ((xy >> 4) & 15);
- s.tl.y = t.tl.y + (xy & 15);
- s.br.x = s.tl.x + ((wh >> 4) & 15) + 1;
- s.br.y = s.tl.y + (wh & 15) + 1;
-*/
int x = ((xy >> 4) & 15);
int y = (xy & 15);
int w = ((wh >> 4) & 15) + 1;
int h = (wh & 15) + 1;
- ptr = y * t.width() + x;
- int rowAdd = t.width() - w;
+ ptr = buf.duplicate();
+ ptr.position((y * t.width() + x)*pf.bpp/8);
+ int rowAdd = (t.width() - w)*pf.bpp/8;
while (h-- > 0) {
len = w;
- while (len-- > 0) buf[ptr++] = fg;
- ptr += rowAdd;
+ while (len-- > 0) ptr.put(fg.array());
+ ptr.position(ptr.position()+Math.min(rowAdd,ptr.remaining()));
}
}
}
- handler.imageRect(t, buf);
+ pb.imageRect(pf, t, buf.array());
}
}
}
-
- CMsgReader reader;
}
diff --git a/java/com/tigervnc/rfb/JpegDecompressor.java b/java/com/tigervnc/rfb/JpegDecompressor.java
new file mode 100644
index 0000000..9137847
--- /dev/null
+++ b/java/com/tigervnc/rfb/JpegDecompressor.java
@@ -0,0 +1,53 @@
+/* Copyright (C) 2016 Brian P. Hinz
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+package com.tigervnc.rfb;
+
+import java.awt.image.*;
+import java.io.*;
+import java.nio.ByteBuffer;
+import javax.imageio.*;
+import javax.imageio.stream.*;
+
+public class JpegDecompressor {
+
+ public JpegDecompressor() {}
+
+ public void decompress(ByteBuffer jpegBuf, int jpegBufLen,
+ WritableRaster buf, Rect r, PixelFormat pf)
+ {
+
+ byte[] src = new byte[jpegBufLen];
+
+ jpegBuf.get(src);
+ try {
+ BufferedImage image =
+ ImageIO.read(new MemoryCacheImageInputStream(new ByteArrayInputStream(src)));
+ ColorModel cm = pf.getColorModel();
+ if (cm.isCompatibleRaster(image.getRaster()) &&
+ cm.isCompatibleSampleModel(image.getRaster().getSampleModel())) {
+ buf.setDataElements(0, 0, image.getRaster());
+ } else {
+ ColorConvertOp converter = pf.getColorConvertOp(cm.getColorSpace());
+ converter.filter(image.getRaster(), buf);
+ }
+ image.flush();
+ } catch (IOException e) {
+ throw new Exception(e.getMessage());
+ }
+ }
+}
diff --git a/java/com/tigervnc/rfb/ManagedPixelBuffer.java b/java/com/tigervnc/rfb/ManagedPixelBuffer.java
index f947af7..6e14b92 100644
--- a/java/com/tigervnc/rfb/ManagedPixelBuffer.java
+++ b/java/com/tigervnc/rfb/ManagedPixelBuffer.java
@@ -18,21 +18,37 @@
package com.tigervnc.rfb;
-public class ManagedPixelBuffer extends PixelBuffer {
- public void setSize(int w, int h) {
- width_ = w;
- height_ = h;
- checkDataSize();
- }
- public void setPF(PixelFormat pf) {
- super.setPF(pf);
+public class ManagedPixelBuffer extends FullFramePixelBuffer {
+
+ public ManagedPixelBuffer() {
+ datasize = 0;
checkDataSize();
}
- public int dataLen() { return area(); }
+ public ManagedPixelBuffer(PixelFormat pf, int w, int h)
+ {
+ super(pf, w, h, null);
+ datasize = 0;
+ checkDataSize();
+ }
+
+ public void setPF(PixelFormat pf) {
+ format = pf; checkDataSize();
+ }
+
+ public void setSize(int w, int h) {
+ width_ = w; height_ = h; checkDataSize();
+ }
final void checkDataSize() {
- if (data == null || data.length < dataLen())
- data = new int[dataLen()];
+ int new_datasize = width_ * height_;
+ if (datasize < new_datasize) {
+ vlog.debug("reallocating managed buffer ("+width_+"x"+height_+")");
+ if (format != null)
+ data = PixelFormat.getColorModel(format).createCompatibleWritableRaster(width_, height_);
+ }
}
+
+ protected int datasize;
+ static LogWriter vlog = new LogWriter("ManagedPixelBuffer");
}
diff --git a/java/com/tigervnc/rfb/ModifiablePixelBuffer.java b/java/com/tigervnc/rfb/ModifiablePixelBuffer.java
new file mode 100644
index 0000000..bcc559d
--- /dev/null
+++ b/java/com/tigervnc/rfb/ModifiablePixelBuffer.java
@@ -0,0 +1,267 @@
+/* Copyright 2016 Brian P. Hinz
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+// -=- Modifiable generic pixel buffer class
+
+package com.tigervnc.rfb;
+
+import java.awt.image.*;
+import java.awt.Color;
+import java.awt.color.ColorSpace;
+import java.lang.*;
+import java.nio.*;
+import java.util.*;
+
+import static java.awt.image.DataBuffer.*;
+
+public abstract class ModifiablePixelBuffer extends PixelBuffer
+{
+
+ public ModifiablePixelBuffer(PixelFormat pf, int w, int h)
+ {
+ super(pf, w, h);
+ }
+
+ protected ModifiablePixelBuffer()
+ {
+ }
+
+ ///////////////////////////////////////////////
+ // Access to pixel data
+ //
+
+ // Get a writeable pointer into the buffer
+ // Like getBuffer(), the pointer is to the top-left pixel of the
+ // specified Rect.
+ public abstract WritableRaster getBufferRW(Rect r);
+ // Commit the modified contents
+ // Ensures that the changes to the specified Rect is properly
+ // stored away and any temporary buffers are freed. The Rect given
+ // here needs to match the Rect given to the earlier call to
+ // getBufferRW().
+ public abstract void commitBufferRW(Rect r);
+
+ static LogWriter vlog = new LogWriter("ModifiablePixelBuffer");
+ ///////////////////////////////////////////////
+ // Basic rendering operations
+ // These operations DO NOT clip to the pixelbuffer area, or trap overruns.
+
+ // Fill a rectangle
+ public synchronized void fillRect(Rect r, byte[] pix)
+ {
+ WritableRaster buf;
+ int w, h;
+
+ w = r.width();
+ h = r.height();
+
+ if (h == 0 || w ==0)
+ return;
+
+ buf = getBufferRW(r);
+
+ ByteBuffer src =
+ ByteBuffer.allocate(r.area()*format.bpp/8).order(format.getByteOrder());
+ for (int i=0; i < r.area(); i++)
+ src.put(pix);
+ Raster raster = format.rasterFromBuffer(r, (ByteBuffer)src.rewind());
+ buf.setDataElements(0, 0, raster);
+
+ commitBufferRW(r);
+ }
+
+ // Copy pixel data to the buffer
+ public synchronized void imageRect(Rect r, byte[] pixels)
+ {
+ WritableRaster dest = getBufferRW(r);
+
+ ByteBuffer src = ByteBuffer.wrap(pixels).order(format.getByteOrder());
+ Raster raster = format.rasterFromBuffer(r, src);
+ dest.setDataElements(0, 0, raster);
+
+ commitBufferRW(r);
+ }
+
+ // Copy pixel data from one PixelBuffer location to another
+ public synchronized void copyRect(Rect rect,
+ Point move_by_delta)
+ {
+ Raster srcData;
+ WritableRaster dstData;
+
+ Rect drect, srect;
+
+ drect = new Rect(rect.tl, rect.br);
+ if (!drect.enclosed_by(getRect())) {
+ String msg = "Destination rect %dx%d at %d,%d exceeds framebuffer %dx%d";
+ vlog.error(String.format(msg, drect.width(), drect.height(),
+ drect.tl.x, drect.tl.y, width_, height_));
+ drect = drect.intersect(getRect());
+ }
+
+ if (drect.is_empty())
+ return;
+
+ srect = drect.translate(move_by_delta.negate());
+ if (!srect.enclosed_by(getRect())) {
+ String msg = "Source rect %dx%d at %d,%d exceeds framebuffer %dx%d";
+ vlog.error(String.format(msg, srect.width(), srect.height(),
+ srect.tl.x, srect.tl.y, width_, height_));
+ srect = srect.intersect(getRect());
+ // Need to readjust the destination now that the area has changed
+ drect = srect.translate(move_by_delta);
+ }
+
+ if (srect.is_empty())
+ return;
+
+ srcData = getBuffer(srect);
+ dstData = getBufferRW(drect);
+
+ dstData.setDataElements(0, 0, srcData);
+
+ commitBufferRW(rect);
+ }
+
+ // Copy pixel data to the buffer through a mask
+ // pixels is a pointer to the pixel to be copied to r.tl.
+ // maskPos specifies the pixel offset in the mask to start from.
+ // mask_ is a pointer to the mask bits at (0,0).
+ // pStride and mStride are the strides of the pixel and mask buffers.
+ public synchronized void maskRect(Rect r,
+ Object pixels, byte[] mask_)
+ {
+ Rect cr = getRect().intersect(r);
+ if (cr.is_empty()) return;
+ WritableRaster data = getBufferRW(cr);
+
+ // FIXME
+ ColorModel cm = format.getColorModel();
+ SampleModel sm =
+ cm.createCompatibleSampleModel(r.width(), r.height());
+ DataBuffer db = null;
+ ByteBuffer src =
+ ByteBuffer.wrap((byte[])pixels).order(format.getByteOrder());
+ Buffer dst;
+ switch (sm.getTransferType()) {
+ case TYPE_INT:
+ dst = IntBuffer.allocate(src.remaining()).put(src.asIntBuffer());
+ db = new DataBufferInt(((IntBuffer)dst).array(), r.area());
+ break;
+ case TYPE_BYTE:
+ db = new DataBufferByte(src.array(), r.area());
+ break;
+ case TYPE_SHORT:
+ dst = ShortBuffer.allocate(src.remaining()).put(src.asShortBuffer());
+ db = new DataBufferShort(((ShortBuffer)dst).array(), r.area());
+ break;
+ }
+ assert(db != null);
+ Raster raster =
+ Raster.createRaster(sm, db, new java.awt.Point(0, 0));
+ ColorConvertOp converter = format.getColorConvertOp(cm.getColorSpace());
+ WritableRaster t = data.createCompatibleWritableRaster();
+ converter.filter(raster, t);
+
+ int w = cr.width();
+ int h = cr.height();
+
+ Point offset = new Point(cr.tl.x-r.tl.x, cr.tl.y-r.tl.y);
+
+ int maskBytesPerRow = (w + 7) / 8;
+
+ for (int y = 0; y < h; y++) {
+ int cy = offset.y + y;
+ for (int x = 0; x < w; x++) {
+ int cx = offset.x + x;
+ int byte_ = cy * maskBytesPerRow + y / 8;
+ int bit = 7 - cx % 8;
+
+ if ((mask_[byte_] & (1 << bit)) != 0)
+ data.setDataElements(x+cx, y+cy, t.getDataElements(x+cx, y+cy, null));
+ }
+ }
+
+ commitBufferRW(r);
+ }
+
+ // pixel is the Pixel value to be used where mask_ is set
+ public synchronized void maskRect(Rect r, int pixel, byte[] mask)
+ {
+ // FIXME
+ }
+
+ // Render in a specific format
+ // Does the exact same thing as the above methods, but the given
+ // pixel values are defined by the given PixelFormat.
+ public synchronized void fillRect(PixelFormat pf, Rect dest, byte[] pix)
+ {
+ WritableRaster dstBuffer = getBufferRW(dest);
+
+ ColorModel cm = pf.getColorModel();
+ if (cm.isCompatibleRaster(dstBuffer) &&
+ cm.isCompatibleSampleModel(dstBuffer.getSampleModel())) {
+ fillRect(dest, pix);
+ } else {
+ ByteBuffer src =
+ ByteBuffer.allocate(dest.area()*pf.bpp/8).order(pf.getByteOrder());
+ for (int i=0; i < dest.area(); i++)
+ src.put(pix);
+ Raster raster = pf.rasterFromBuffer(dest, (ByteBuffer)src.rewind());
+ ColorConvertOp converter = format.getColorConvertOp(cm.getColorSpace());
+ converter.filter(raster, dstBuffer);
+ }
+
+ commitBufferRW(dest);
+ }
+
+ public synchronized void imageRect(PixelFormat pf, Rect dest, byte[] pixels)
+ {
+ WritableRaster dstBuffer = getBufferRW(dest);
+
+ ColorModel cm = pf.getColorModel();
+ if (cm.isCompatibleRaster(dstBuffer) &&
+ cm.isCompatibleSampleModel(dstBuffer.getSampleModel())) {
+ imageRect(dest, pixels);
+ } else {
+ ByteBuffer src = ByteBuffer.wrap(pixels).order(pf.getByteOrder());
+ Raster raster = pf.rasterFromBuffer(dest, src);
+ ColorConvertOp converter = format.getColorConvertOp(cm.getColorSpace());
+ converter.filter(raster, dstBuffer);
+ }
+
+ commitBufferRW(dest);
+ }
+
+ public synchronized void imageRect(PixelFormat pf, Rect dest, Raster pixels)
+ {
+ WritableRaster dstBuffer = getBufferRW(dest);
+
+ ColorModel cm = pf.getColorModel();
+ if (cm.isCompatibleRaster(dstBuffer) &&
+ cm.isCompatibleSampleModel(dstBuffer.getSampleModel())) {
+ dstBuffer.setDataElements(0, 0, pixels);
+ } else {
+ ColorConvertOp converter = format.getColorConvertOp(cm.getColorSpace());
+ converter.filter(pixels, dstBuffer);
+ }
+
+ commitBufferRW(dest);
+ }
+
+}
diff --git a/java/com/tigervnc/rfb/PixelBuffer.java b/java/com/tigervnc/rfb/PixelBuffer.java
index a46667d..1b7d2c1 100644
--- a/java/com/tigervnc/rfb/PixelBuffer.java
+++ b/java/com/tigervnc/rfb/PixelBuffer.java
@@ -1,4 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright 2016 Brian P. Hinz
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,117 +17,67 @@
* USA.
*/
-//
-// PixelBuffer - note that this code is only written for the 8, 16, and 32 bpp cases at the
-// moment.
-//
+// -=- Generic pixel buffer class
package com.tigervnc.rfb;
import java.awt.image.*;
+import java.awt.Color;
+import java.nio.*;
+import java.util.concurrent.atomic.*;
-public class PixelBuffer {
+public abstract class PixelBuffer {
- public PixelBuffer() {
- setPF(new PixelFormat());
- }
-
- public void setPF(PixelFormat pf) {
- if (!(pf.bpp == 32) && !(pf.bpp == 16) && !(pf.bpp == 8))
- throw new Exception("Internal error: bpp must be 8, 16, or 32 in PixelBuffer ("+pf.bpp+")");
+ public PixelBuffer(PixelFormat pf, int w, int h) {
format = pf;
- switch (pf.depth) {
- case 3:
- // Fall-through to depth 8
- case 6:
- // Fall-through to depth 8
- case 8:
- if (!pf.trueColour) {
- if (cm == null)
- cm = new IndexColorModel(8, 256, new byte[256], new byte[256], new byte[256]);
- break;
- }
- int rmask = pf.redMax << pf.redShift;
- int gmask = pf.greenMax << pf.greenShift;
- int bmask = pf.blueMax << pf.blueShift;
- cm = new DirectColorModel(8, rmask, gmask, bmask);
- break;
- case 16:
- cm = new DirectColorModel(32, 0xF800, 0x07C0, 0x003E);
- break;
- case 24:
- cm = new DirectColorModel(32, (0xff << 16), (0xff << 8), 0xff);
- break;
- case 32:
- cm = new DirectColorModel(32, (0xff << pf.redShift),
- (0xff << pf.greenShift), (0xff << pf.blueShift));
- break;
- default:
- throw new Exception("Unsupported color depth ("+pf.depth+")");
- }
+ width_ = w;
+ height_= h;
}
- public PixelFormat getPF() { return format; }
+ protected PixelBuffer() { width_ = 0; height_ = 0; }
+
+ // Get pixel format
+ public final PixelFormat getPF() { return format; }
+
+ // Get width, height and number of pixels
public final int width() { return width_; }
public final int height() { return height_; }
public final int area() { return width_ * height_; }
- public void fillRect(int x, int y, int w, int h, int pix) {
- for (int ry = y; ry < y + h; ry++)
- for (int rx = x; rx < x + w; rx++)
- data[ry * width_ + rx] = pix;
+ // Get rectangle encompassing this buffer
+ // Top-left of rectangle is either at (0,0), or the specified point.
+ public final Rect getRect() { return new Rect(0, 0, width_, height_); }
+ public final Rect getRect(Point pos) {
+ return new Rect(pos, pos.translate(new Point(width_, height_)));
}
- public void imageRect(int x, int y, int w, int h, int[] pix) {
- for (int j = 0; j < h; j++)
- System.arraycopy(pix, (w * j), data, width_ * (y + j) + x, w);
- }
+ ///////////////////////////////////////////////
+ // Access to pixel data
+ //
- public void copyRect(int x, int y, int w, int h, int srcX, int srcY) {
- int dest = (width_ * y) + x;
- int src = (width_ * srcY) + srcX;
- int inc = width_;
+ // Get a pointer into the buffer
+ // The pointer is to the top-left pixel of the specified Rect.
+ public abstract Raster getBuffer(Rect r);
- if (y > srcY) {
- src += (h-1) * inc;
- dest += (h-1) * inc;
- inc = -inc;
- }
- int destEnd = dest + h * inc;
+ // Get pixel data for a given part of the buffer
+ // Data is copied into the supplied buffer, with the specified
+ // stride. Try to avoid using this though as getBuffer() will in
+ // most cases avoid the extra memory copy.
+ //void getImage(void* imageBuf, const Rect& r, int stride=0) const;
+ // Get pixel data in a given format
+ // Works just the same as getImage(), but guaranteed to be in a
+ // specific format.
+ //void getImage(const PixelFormat& pf, void* imageBuf,
+ // const Rect& r, int stride=0) const;
- while (dest != destEnd) {
- System.arraycopy(data, src, data, dest, w);
- src += inc;
- dest += inc;
- }
- }
+ ///////////////////////////////////////////////
+ // Framebuffer update methods
+ //
- public void maskRect(int x, int y, int w, int h, int[] pix, byte[] mask) {
- int maskBytesPerRow = (w + 7) / 8;
-
- for (int j = 0; j < h; j++) {
- int cy = y + j;
-
- if (cy < 0 || cy >= height_)
- continue;
-
- for (int i = 0; i < w; i++) {
- int cx = x + i;
-
- if (cx < 0 || cx >= width_)
- continue;
-
- int byte_ = j * maskBytesPerRow + i / 8;
- int bit = 7 - i % 8;
-
- if ((mask[byte_] & (1 << bit)) != 0)
- data[cy * width_ + cx] = pix[j * w + i];
- }
- }
- }
-
- public int[] data;
- public ColorModel cm;
+ // Ensure that the specified rectangle of buffer is up to date.
+ // Overridden by derived classes implementing framebuffer access
+ // to copy the required display data into place.
+ //public abstract void grabRegion(Region& region) {}
protected PixelFormat format;
protected int width_, height_;
diff --git a/java/com/tigervnc/rfb/PixelFormat.java b/java/com/tigervnc/rfb/PixelFormat.java
index c4d6870..9a26999 100644
--- a/java/com/tigervnc/rfb/PixelFormat.java
+++ b/java/com/tigervnc/rfb/PixelFormat.java
@@ -25,40 +25,80 @@
package com.tigervnc.rfb;
+import java.awt.color.*;
+import java.awt.image.*;
+import java.nio.*;
+import java.util.*;
+
import com.tigervnc.rdr.*;
-import java.awt.image.ColorModel;
public class PixelFormat {
- public PixelFormat(int b, int d, boolean e, boolean t) {
- bpp = b;
- depth = d;
- bigEndian = e;
- trueColour = t;
- }
public PixelFormat(int b, int d, boolean e, boolean t,
- int rm, int gm, int bm, int rs, int gs, int bs) {
- this(b, d, e, t);
- redMax = rm;
- greenMax = gm;
- blueMax = bm;
- redShift = rs;
- greenShift = gs;
- blueShift = bs;
- }
- public PixelFormat() { this(8,8,false,true,7,7,3,0,3,6); }
+ int rm, int gm, int bm, int rs, int gs, int bs)
+ {
+ bpp = b; depth = d; trueColour = t; bigEndian = e;
+ redMax = rm; greenMax = gm; blueMax = bm;
+ redShift = rs; greenShift = gs; blueShift = bs;
+ converters = new HashMap<Integer, ColorConvertOp>();
+ assert(isSane());
- public boolean equal(PixelFormat x) {
- return (bpp == x.bpp &&
- depth == x.depth &&
- (bigEndian == x.bigEndian || bpp == 8) &&
- trueColour == x.trueColour &&
- (!trueColour || (redMax == x.redMax &&
- greenMax == x.greenMax &&
- blueMax == x.blueMax &&
- redShift == x.redShift &&
- greenShift == x.greenShift &&
- blueShift == x.blueShift)));
+ updateState();
+ }
+
+ public PixelFormat()
+ {
+ this(8, 8, false, true, 7, 7, 3, 0, 3, 6);
+ updateState();
+ }
+
+ public boolean equal(PixelFormat other)
+ {
+ if (bpp != other.bpp || depth != other.depth)
+ return false;
+
+ if (redMax != other.redMax)
+ return false;
+ if (greenMax != other.greenMax)
+ return false;
+ if (blueMax != other.blueMax)
+ return false;
+
+ // Endianness requires more care to determine compatibility
+ if (bigEndian == other.bigEndian || bpp == 8) {
+ if (redShift != other.redShift)
+ return false;
+ if (greenShift != other.greenShift)
+ return false;
+ if (blueShift != other.blueShift)
+ return false;
+ } else {
+ // Has to be the same byte for each channel
+ if (redShift/8 != (3 - other.redShift/8))
+ return false;
+ if (greenShift/8 != (3 - other.greenShift/8))
+ return false;
+ if (blueShift/8 != (3 - other.blueShift/8))
+ return false;
+
+ // And the same bit offset within the byte
+ if (redShift%8 != other.redShift%8)
+ return false;
+ if (greenShift%8 != other.greenShift%8)
+ return false;
+ if (blueShift%8 != other.blueShift%8)
+ return false;
+
+ // And not cross a byte boundary
+ if (redShift/8 != (redShift + redBits - 1)/8)
+ return false;
+ if (greenShift/8 != (greenShift + greenBits - 1)/8)
+ return false;
+ if (blueShift/8 != (blueShift + blueBits - 1)/8)
+ return false;
+ }
+
+ return true;
}
public void read(InStream is) {
@@ -73,6 +113,23 @@
greenShift = is.readU8();
blueShift = is.readU8();
is.skip(3);
+
+ // We have no real support for colour maps. If the client
+ // wants one, then we force a 8-bit true colour format and
+ // pretend it's a colour map.
+ if (!trueColour) {
+ redMax = 7;
+ greenMax = 7;
+ blueMax = 3;
+ redShift = 0;
+ greenShift = 3;
+ blueShift = 6;
+ }
+
+ if (!isSane())
+ throw new Exception("invalid pixel format: "+print());
+
+ updateState();
}
public void write(OutStream os) {
@@ -89,6 +146,14 @@
os.pad(3);
}
+ public final boolean isBigEndian() {
+ return bigEndian;
+ }
+
+ public final boolean isLittleEndian() {
+ return ! bigEndian;
+ }
+
public final boolean is888() {
if(!trueColour)
return false;
@@ -139,53 +204,140 @@
return 0;
}
- public void bufferFromRGB(int[] dst, int dstPtr, byte[] src,
- int srcPtr, int pixels) {
+ public void bufferFromRGB(ByteBuffer dst, ByteBuffer src, int pixels)
+ {
+ bufferFromRGB(dst, src, pixels, pixels, 1);
+ }
+
+ public void bufferFromRGB(ByteBuffer dst, ByteBuffer src,
+ int w, int stride, int h)
+ {
if (is888()) {
// Optimised common case
- int r, g, b;
+ int r, g, b, x;
- for (int i=srcPtr; i < pixels; i++) {
- if (bigEndian) {
- r = (src[3*i+0] & 0xff) << (24 - redShift);
- g = (src[3*i+1] & 0xff) << (24 - greenShift);
- b = (src[3*i+2] & 0xff) << (24 - blueShift);
- dst[dstPtr+i] = r | g | b | 0xff;
- } else {
- r = (src[3*i+0] & 0xff) << redShift;
- g = (src[3*i+1] & 0xff) << greenShift;
- b = (src[3*i+2] & 0xff) << blueShift;
- dst[dstPtr+i] = (0xff << 24) | r | g | b;
+ if (bigEndian) {
+ r = dst.position() + (24 - redShift)/8;
+ g = dst.position() + (24 - greenShift)/8;
+ b = dst.position() + (24 - blueShift)/8;
+ x = dst.position() + (24 - (48 - redShift - greenShift - blueShift))/8;
+ } else {
+ r = dst.position() + redShift/8;
+ g = dst.position() + greenShift/8;
+ b = dst.position() + blueShift/8;
+ x = dst.position() + (48 - redShift - greenShift - blueShift)/8;
+ }
+
+ int dstPad = (stride - w) * 4;
+ while (h-- > 0) {
+ int w_ = w;
+ while (w_-- > 0) {
+ dst.put(r, src.get());
+ dst.put(g, src.get());
+ dst.put(b, src.get());
+ dst.put(x, (byte)0);
+ r += 4;
+ g += 4;
+ b += 4;
+ x += 4;
}
+ r += dstPad;
+ g += dstPad;
+ b += dstPad;
+ x += dstPad;
}
} else {
// Generic code
- int p, r, g, b;
- int[] rgb = new int[4];
+ int dstPad = (stride - w) * bpp/8;
+ while (h-- > 0) {
+ int w_ = w;
+ while (w_-- > 0) {
+ int p;
+ int r, g, b;
- int i = srcPtr; int j = dstPtr;
- while (i < pixels) {
- r = src[i++] & 0xff;
- g = src[i++] & 0xff;
- b = src[i++] & 0xff;
+ r = src.get();
+ g = src.get();
+ b = src.get();
- //p = pixelFromRGB(r, g, b, cm);
- p = ColorModel.getRGBdefault().getDataElement(new int[] {0xff, r, g, b}, 0);
+ p = pixelFromRGB(r, g, b, model);
- bufferFromPixel(dst, j, p);
- j += bpp/8;
+ bufferFromPixel(dst, p);
+ dst.position(dst.position() + bpp/8);
+ }
+ dst.position(dst.position() + dstPad);
}
}
}
- public void rgbFromBuffer(byte[] dst, int dstPtr, byte[] src, int srcPtr, int pixels, ColorModel cm)
+ public void rgbFromBuffer(ByteBuffer dst, ByteBuffer src, int pixels)
+ {
+ rgbFromBuffer(dst, src, pixels, pixels, 1);
+ }
+
+ public void rgbFromBuffer(ByteBuffer dst, ByteBuffer src,
+ int w, int stride, int h)
+ {
+ if (is888()) {
+ // Optimised common case
+ int r, g, b;
+
+ if (bigEndian) {
+ r = src.position() + (24 - redShift)/8;
+ g = src.position() + (24 - greenShift)/8;
+ b = src.position() + (24 - blueShift)/8;
+ } else {
+ r = src.position() + redShift/8;
+ g = src.position() + greenShift/8;
+ b = src.position() + blueShift/8;
+ }
+
+ int srcPad = (stride - w) * 4;
+ while (h-- > 0) {
+ int w_ = w;
+ while (w_-- > 0) {
+ dst.put(src.get(r));
+ dst.put(src.get(g));
+ dst.put(src.get(b));
+ r += 4;
+ g += 4;
+ b += 4;
+ }
+ r += srcPad;
+ g += srcPad;
+ b += srcPad;
+ }
+ } else {
+ // Generic code
+ int srcPad = (stride - w) * bpp/8;
+ while (h-- > 0) {
+ int w_ = w;
+ while (w_-- > 0) {
+ int p;
+ byte r, g, b;
+
+ p = pixelFromBuffer(src.duplicate());
+
+ r = (byte)getColorModel().getRed(p);
+ g = (byte)getColorModel().getGreen(p);
+ b = (byte)getColorModel().getBlue(p);
+
+ dst.put(r);
+ dst.put(g);
+ dst.put(b);
+ src.position(src.position() + bpp/8);
+ }
+ src.reset().position(src.position() + srcPad).mark();
+ }
+ }
+ }
+
+ public void rgbFromPixels(byte[] dst, int dstPtr, int[] src, int srcPtr, int pixels, ColorModel cm)
{
int p;
byte r, g, b;
for (int i=0; i < pixels; i++) {
- p = pixelFromBuffer(src, srcPtr);
- srcPtr += bpp/8;
+ p = src[i];
dst[dstPtr++] = (byte)cm.getRed(p);
dst[dstPtr++] = (byte)cm.getGreen(p);
@@ -193,31 +345,29 @@
}
}
- public int pixelFromBuffer(byte[] buffer, int bufferPtr)
+ public int pixelFromBuffer(ByteBuffer buffer)
{
int p;
- p = 0;
+ p = 0xff000000;
- if (bigEndian) {
+ if (!bigEndian) {
switch (bpp) {
case 32:
- p = (buffer[0] & 0xff) << 24 | (buffer[1] & 0xff) << 16 | (buffer[2] & 0xff) << 8 | 0xff;
- break;
+ p |= buffer.get() << 24;
+ p |= buffer.get() << 16;
case 16:
- p = (buffer[0] & 0xff) << 8 | (buffer[1] & 0xff);
- break;
+ p |= buffer.get() << 8;
case 8:
- p = (buffer[0] & 0xff);
- break;
+ p |= buffer.get();
}
} else {
- p = (buffer[0] & 0xff);
+ p |= buffer.get(0);
if (bpp >= 16) {
- p |= (buffer[1] & 0xff) << 8;
+ p |= buffer.get(1) << 8;
if (bpp == 32) {
- p |= (buffer[2] & 0xff) << 16;
- p |= (buffer[3] & 0xff) << 24;
+ p |= buffer.get(2) << 16;
+ p |= buffer.get(3) << 24;
}
}
}
@@ -263,33 +413,212 @@
return s.toString();
}
- public void bufferFromPixel(int[] buffer, int bufPtr, int p)
+ private static int bits(int value)
+ {
+ int bits;
+
+ bits = 16;
+
+ if ((value & 0xff00) == 0) {
+ bits -= 8;
+ value <<= 8;
+ }
+ if ((value & 0xf000) == 0) {
+ bits -= 4;
+ value <<= 4;
+ }
+ if ((value & 0xc000) == 0) {
+ bits -= 2;
+ value <<= 2;
+ }
+ if ((value & 0x8000) == 0) {
+ bits -= 1;
+ value <<= 1;
+ }
+
+ return bits;
+ }
+
+ private void updateState()
+ {
+ int endianTest = 1;
+
+ redBits = bits(redMax);
+ greenBits = bits(greenMax);
+ blueBits = bits(blueMax);
+
+ maxBits = redBits;
+ if (greenBits > maxBits)
+ maxBits = greenBits;
+ if (blueBits > maxBits)
+ maxBits = blueBits;
+
+ minBits = redBits;
+ if (greenBits < minBits)
+ minBits = greenBits;
+ if (blueBits < minBits)
+ minBits = blueBits;
+
+ if ((((char)endianTest) == 0) != bigEndian)
+ endianMismatch = true;
+ else
+ endianMismatch = false;
+
+ model = getColorModel(this);
+ }
+
+ private boolean isSane()
+ {
+ int totalBits;
+
+ if ((bpp != 8) && (bpp != 16) && (bpp != 32))
+ return false;
+ if (depth > bpp)
+ return false;
+
+ if (!trueColour && (depth != 8))
+ return false;
+
+ if ((redMax & (redMax + 1)) != 0)
+ return false;
+ if ((greenMax & (greenMax + 1)) != 0)
+ return false;
+ if ((blueMax & (blueMax + 1)) != 0)
+ return false;
+
+ /*
+ * We don't allow individual channels > 8 bits in order to keep our
+ * conversions simple.
+ */
+ if (redMax >= (1 << 8))
+ return false;
+ if (greenMax >= (1 << 8))
+ return false;
+ if (blueMax >= (1 << 8))
+ return false;
+
+ totalBits = bits(redMax) + bits(greenMax) + bits(blueMax);
+ if (totalBits > bpp)
+ return false;
+
+ if (((redMax << redShift) & (greenMax << greenShift)) != 0)
+ return false;
+ if (((redMax << redShift) & (blueMax << blueShift)) != 0)
+ return false;
+ if (((greenMax << greenShift) & (blueMax << blueShift)) != 0)
+ return false;
+
+ return true;
+ }
+
+ public void bufferFromPixel(ByteBuffer buffer, int p)
{
if (bigEndian) {
switch (bpp) {
case 32:
- buffer[bufPtr++] = (p >> 24) & 0xff;
- buffer[bufPtr++] = (p >> 16) & 0xff;
+ buffer.put((byte)((p >> 24) & 0xff));
+ buffer.put((byte)((p >> 16) & 0xff));
break;
case 16:
- buffer[bufPtr++] = (p >> 8) & 0xff;
+ buffer.put((byte)((p >> 8) & 0xff));
break;
case 8:
- buffer[bufPtr++] = (p >> 0) & 0xff;
+ buffer.put((byte)((p >> 0) & 0xff));
break;
}
} else {
- buffer[0] = (p >> 0) & 0xff;
+ buffer.put(0, (byte)((p >> 0) & 0xff));
if (bpp >= 16) {
- buffer[1] = (p >> 8) & 0xff;
+ buffer.put(1, (byte)((p >> 8) & 0xff));
if (bpp == 32) {
- buffer[2] = (p >> 16) & 0xff;
- buffer[3] = (p >> 24) & 0xff;
+ buffer.put(2, (byte)((p >> 16) & 0xff));
+ buffer.put(3, (byte)((p >> 24) & 0xff));
}
}
}
}
+ public ColorModel getColorModel()
+ {
+ return model;
+ }
+
+ public static ColorModel getColorModel(PixelFormat pf) {
+ if (!(pf.bpp == 32) && !(pf.bpp == 16) && !(pf.bpp == 8))
+ throw new Exception("Internal error: bpp must be 8, 16, or 32 in PixelBuffer ("+pf.bpp+")");
+ ColorModel cm;
+ switch (pf.depth) {
+ case 3:
+ // Fall-through to depth 8
+ case 6:
+ // Fall-through to depth 8
+ case 8:
+ int rmask = pf.redMax << pf.redShift;
+ int gmask = pf.greenMax << pf.greenShift;
+ int bmask = pf.blueMax << pf.blueShift;
+ cm = new DirectColorModel(8, rmask, gmask, bmask);
+ break;
+ case 16:
+ cm = new DirectColorModel(32, 0xF800, 0x07C0, 0x003E);
+ break;
+ case 24:
+ cm = new DirectColorModel(32, (0xff << 16), (0xff << 8), 0xff);
+ break;
+ case 32:
+ cm = new DirectColorModel(32, (0xff << pf.redShift),
+ (0xff << pf.greenShift), (0xff << pf.blueShift));
+ break;
+ default:
+ throw new Exception("Unsupported color depth ("+pf.depth+")");
+ }
+ assert(cm != null);
+ return cm;
+ }
+
+ public ColorConvertOp getColorConvertOp(ColorSpace src)
+ {
+ // The overhead associated with initializing ColorConvertOps is
+ // enough to justify maintaining a static lookup table.
+ if (converters.containsKey(src.getType()))
+ return converters.get(src.getType());
+ ColorSpace dst = model.getColorSpace();
+ converters.put(src.getType(), new ColorConvertOp(src, dst, null));
+ return converters.get(src.getType());
+ }
+
+ public ByteOrder getByteOrder()
+ {
+ if (isBigEndian())
+ return ByteOrder.BIG_ENDIAN;
+ else
+ return ByteOrder.LITTLE_ENDIAN;
+ }
+
+ public Raster rasterFromBuffer(Rect r, ByteBuffer buf)
+ {
+ Buffer dst;
+ DataBuffer db = null;
+
+ SampleModel sm =
+ model.createCompatibleSampleModel(r.width(), r.height());
+ switch (sm.getTransferType()) {
+ case DataBuffer.TYPE_INT:
+ dst = IntBuffer.allocate(r.area()).put(buf.asIntBuffer());
+ db = new DataBufferInt(((IntBuffer)dst).array(), r.area());
+ break;
+ case DataBuffer.TYPE_BYTE:
+ db = new DataBufferByte(buf.array(), r.area());
+ break;
+ case DataBuffer.TYPE_SHORT:
+ dst = ShortBuffer.allocate(r.area()).put(buf.asShortBuffer());
+ db = new DataBufferShort(((ShortBuffer)dst).array(), r.area());
+ break;
+ }
+ assert(db != null);
+ return Raster.createRaster(sm, db, new java.awt.Point(0, 0));
+ }
+
+ private static HashMap<Integer, ColorConvertOp> converters;
public int bpp;
public int depth;
@@ -301,4 +630,10 @@
public int redShift;
public int greenShift;
public int blueShift;
+
+ protected int redBits, greenBits, blueBits;
+ protected int maxBits, minBits;
+ protected boolean endianMismatch;
+
+ private ColorModel model;
}
diff --git a/java/com/tigervnc/rfb/RREDecoder.java b/java/com/tigervnc/rfb/RREDecoder.java
index 487aa3d..c73c7a9 100644
--- a/java/com/tigervnc/rfb/RREDecoder.java
+++ b/java/com/tigervnc/rfb/RREDecoder.java
@@ -1,4 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright 2016 Brian P. Hinz
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,30 +19,90 @@
package com.tigervnc.rfb;
+import java.nio.*;
+
import com.tigervnc.rdr.*;
public class RREDecoder extends Decoder {
- public RREDecoder(CMsgReader reader_) { reader = reader_; }
+ public RREDecoder() { super(DecoderFlags.DecoderPlain); }
- public void readRect(Rect r, CMsgHandler handler) {
- InStream is = reader.getInStream();
- int bytesPerPixel = handler.cp.pf().bpp / 8;
- boolean bigEndian = handler.cp.pf().bigEndian;
+ public void readRect(Rect r, InStream is,
+ ConnParams cp, OutStream os)
+ {
+ int numRects;
+
+ numRects = is.readU32();
+ os.writeU32(numRects);
+
+ os.copyBytes(is, cp.pf().bpp/8 + numRects * (cp.pf().bpp/8 + 8));
+ }
+
+ public void decodeRect(Rect r, Object buffer,
+ int buflen, ConnParams cp,
+ ModifiablePixelBuffer pb)
+ {
+ MemInStream is = new MemInStream((byte[])buffer, 0, buflen);
+ PixelFormat pf = cp.pf();
+ switch (pf.bpp) {
+ case 8: rreDecode8 (r, is, pf, pb); break;
+ case 16: rreDecode16(r, is, pf, pb); break;
+ case 32: rreDecode32(r, is, pf, pb); break;
+ }
+ }
+
+ private static ByteBuffer READ_PIXEL(InStream is, PixelFormat pf) {
+ ByteBuffer b = ByteBuffer.allocate(4);
+ switch (pf.bpp) {
+ case 8:
+ b.putInt(is.readOpaque8());
+ return ByteBuffer.allocate(1).put(b.get(3));
+ case 16:
+ b.putInt(is.readOpaque16());
+ return ByteBuffer.allocate(2).put(b.array(), 2, 2);
+ case 32:
+ default:
+ b.putInt(is.readOpaque32());
+ return b;
+ }
+ }
+
+ private void RRE_DECODE(Rect r, InStream is,
+ PixelFormat pf, ModifiablePixelBuffer pb)
+ {
int nSubrects = is.readU32();
- int bg = is.readPixel(bytesPerPixel, bigEndian);
- handler.fillRect(r, bg);
+ byte[] bg = READ_PIXEL(is, pf).array();
+ pb.fillRect(pf, r, bg);
for (int i = 0; i < nSubrects; i++) {
- int pix = is.readPixel(bytesPerPixel, bigEndian);
+ byte[] pix = READ_PIXEL(is, pf).array();
int x = is.readU16();
int y = is.readU16();
int w = is.readU16();
int h = is.readU16();
- handler.fillRect(new Rect(r.tl.x + x, r.tl.y + y,
- r.tl.x + x + w, r.tl.y + y + h), pix);
+ pb.fillRect(pf, new Rect(r.tl.x+x, r.tl.y+y, r.tl.x+x+w, r.tl.y+y+h), pix);
}
}
- CMsgReader reader;
+ private void rreDecode8(Rect r, InStream is,
+ PixelFormat pf,
+ ModifiablePixelBuffer pb)
+ {
+ RRE_DECODE(r, is, pf, pb);
+ }
+
+ private void rreDecode16(Rect r, InStream is,
+ PixelFormat pf,
+ ModifiablePixelBuffer pb)
+ {
+ RRE_DECODE(r, is, pf, pb);
+ }
+
+ private void rreDecode32(Rect r, InStream is,
+ PixelFormat pf,
+ ModifiablePixelBuffer pb)
+ {
+ RRE_DECODE(r, is, pf, pb);
+ }
+
}
diff --git a/java/com/tigervnc/rfb/RawDecoder.java b/java/com/tigervnc/rfb/RawDecoder.java
index b2219a2..71b7960 100644
--- a/java/com/tigervnc/rfb/RawDecoder.java
+++ b/java/com/tigervnc/rfb/RawDecoder.java
@@ -18,28 +18,25 @@
package com.tigervnc.rfb;
+import com.tigervnc.rdr.*;
+
public class RawDecoder extends Decoder {
- public RawDecoder(CMsgReader reader_) { reader = reader_; }
+ public RawDecoder() { super(DecoderFlags.DecoderPlain); }
- public void readRect(Rect r, CMsgHandler handler) {
- int x = r.tl.x;
- int y = r.tl.y;
- int w = r.width();
- int h = r.height();
- int[] imageBuf = new int[w*h];
- int nPixels = imageBuf.length;
- int bytesPerRow = w * (reader.bpp() / 8);
- while (h > 0) {
- int nRows = nPixels / w;
- if (nRows > h) nRows = h;
- reader.getInStream().readPixels(imageBuf, nPixels, (reader.bpp() / 8), handler.cp.pf().bigEndian);
- handler.imageRect(new Rect(x, y, x+w, y+nRows), imageBuf);
- h -= nRows;
- y += nRows;
- }
+ public void readRect(Rect r, InStream is,
+ ConnParams cp, OutStream os)
+ {
+ os.copyBytes(is, r.area() * cp.pf().bpp/8);
}
- CMsgReader reader;
static LogWriter vlog = new LogWriter("RawDecoder");
+ public void decodeRect(Rect r, Object buffer,
+ int buflen, ConnParams cp,
+ ModifiablePixelBuffer pb)
+ {
+ assert(buflen >= r.area() * cp.pf().bpp/8);
+ pb.imageRect(cp.pf(), r, (byte[])buffer);
+ }
+
}
diff --git a/java/com/tigervnc/rfb/Region.java b/java/com/tigervnc/rfb/Region.java
new file mode 100644
index 0000000..f7da91d
--- /dev/null
+++ b/java/com/tigervnc/rfb/Region.java
@@ -0,0 +1,102 @@
+/* Copyright (C) 2016 Brian P. Hinz. All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+package com.tigervnc.rfb;
+
+import java.awt.*;
+import java.awt.geom.*;
+
+public class Region extends Area {
+
+ // Create an empty region
+ public Region() {
+ super();
+ }
+
+ // Create a rectangular region
+ public Region(Rect r) {
+ super(new Rectangle(r.tl.x, r.tl.y, r.width(), r.height()));
+ }
+
+ public Region(Region r) {
+ super(r);
+ //intersect(r);
+ }
+
+ public void clear() { reset(); }
+
+ public void reset(Rect r) {
+ if (r.is_empty()) {
+ clear();
+ } else {
+ clear();
+ assign_union(new Region(r));
+ /*
+ xrgn.numRects = 1;
+ xrgn.rects[0].x1 = xrgn.extents.x1 = r.tl.x;
+ xrgn.rects[0].y1 = xrgn.extents.y1 = r.tl.y;
+ xrgn.rects[0].x2 = xrgn.extents.x2 = r.br.x;
+ xrgn.rects[0].y2 = xrgn.extents.y2 = r.br.y;
+ */
+ }
+ }
+
+ public void translate(Point delta) {
+ AffineTransform t =
+ AffineTransform.getTranslateInstance((double)delta.x, (double)delta.y);
+ transform(t);
+ }
+
+ public void assign_intersect(Region r) {
+ intersect(r);
+ }
+
+ public void assign_union(Region r) {
+ add(r);
+ }
+
+ public void assign_subtract(Region r) {
+ subtract(r);
+ }
+
+ public Region intersect(Region r) {
+ Region ret = new Region(this);
+ ((Area)ret).intersect(this);
+ return ret;
+ }
+
+ public Region union(Region r) {
+ Region ret = new Region(r);
+ ((Area)ret).add(this);
+ return ret;
+ }
+
+ public Region subtract(Region r) {
+ Region ret = new Region(this);
+ ((Area)ret).subtract(r);
+ return ret;
+ }
+
+ public boolean is_empty() { return isEmpty(); }
+
+ public Rect get_bounding_rect() {
+ Rectangle b = getBounds();
+ return new Rect((int)b.getX(), (int)b.getY(),
+ (int)b.getWidth(), (int)b.getHeight());
+ }
+}
diff --git a/java/com/tigervnc/rfb/ScreenSet.java b/java/com/tigervnc/rfb/ScreenSet.java
index a14f561..173dd10 100644
--- a/java/com/tigervnc/rfb/ScreenSet.java
+++ b/java/com/tigervnc/rfb/ScreenSet.java
@@ -31,13 +31,18 @@
screens = new ArrayList<Screen>();
}
+ public final ListIterator<Screen> begin() { return screens.listIterator(0); }
+ public final ListIterator<Screen> end() {
+ return screens.listIterator(screens.size());
+ }
public final int num_screens() { return screens.size(); }
public final void add_screen(Screen screen) { screens.add(screen); }
public final void remove_screen(int id) {
- for (Iterator<Screen> iter = screens.iterator(); iter.hasNext(); ) {
- Screen refScreen = (Screen)iter.next();
- if (refScreen.id == id)
+ ListIterator iter, nextiter;
+ for (iter = begin(); iter != end(); iter = nextiter) {
+ nextiter = iter; nextiter.next();
+ if (((Screen)iter.next()).id == id)
iter.remove();
}
}
@@ -68,9 +73,10 @@
}
public final void debug_print() {
+ vlog.debug(num_screens()+" screen(s)");
for (Iterator<Screen> iter = screens.iterator(); iter.hasNext(); ) {
Screen refScreen = (Screen)iter.next();
- vlog.error(" "+refScreen.id+" (0x"+refScreen.id+"): "+
+ vlog.debug(" "+refScreen.id+" (0x"+refScreen.id+"): "+
refScreen.dimensions.width()+"x"+refScreen.dimensions.height()+
"+"+refScreen.dimensions.tl.x+"+"+refScreen.dimensions.tl.y+
" (flags 0x"+refScreen.flags+")");
diff --git a/java/com/tigervnc/rfb/Security.java b/java/com/tigervnc/rfb/Security.java
index a68ae3e..e256e6e 100644
--- a/java/com/tigervnc/rfb/Security.java
+++ b/java/com/tigervnc/rfb/Security.java
@@ -60,6 +60,8 @@
public static final int secResultFailed = 1;
public static final int secResultTooMany = 2; // deprecated
+ public Security() { }
+
public Security(StringParameter secTypes)
{
String secTypesStr;
@@ -70,9 +72,9 @@
secTypesStr = null;
}
- public static List<Integer> enabledSecTypes = new ArrayList<Integer>();
+ private List<Integer> enabledSecTypes = new ArrayList<Integer>();
- public static final List<Integer> GetEnabledSecTypes()
+ public final List<Integer> GetEnabledSecTypes()
{
List<Integer> result = new ArrayList<Integer>();
@@ -98,7 +100,7 @@
return (result);
}
- public static final List<Integer> GetEnabledExtSecTypes()
+ public final List<Integer> GetEnabledExtSecTypes()
{
List<Integer> result = new ArrayList<Integer>();
@@ -111,7 +113,7 @@
return (result);
}
- public static final void EnableSecType(int secType)
+ public final void EnableSecType(int secType)
{
for (Iterator<Integer> i = enabledSecTypes.iterator(); i.hasNext(); )
@@ -134,7 +136,29 @@
return false;
}
- public static void DisableSecType(int secType) { enabledSecTypes.remove((Object)secType); }
+ public String ToString()
+ {
+ Iterator<Integer> i;
+ String out = new String("");
+ boolean firstpass = true;
+ String name;
+
+ for (i = enabledSecTypes.iterator(); i.hasNext(); ) {
+ name = secTypeName((Integer)i.next());
+ if (name.startsWith("[")) /* Unknown security type */
+ continue;
+
+ if (!firstpass)
+ out = out.concat(",");
+ else
+ firstpass = false;
+ out = out.concat(name);
+ }
+
+ return out;
+ }
+
+ public void DisableSecType(int secType) { enabledSecTypes.remove((Object)secType); }
public static int secTypeNum(String name) {
if (name.equalsIgnoreCase("None")) return secTypeNone;
@@ -203,7 +227,9 @@
return (result);
}
- public final void SetSecTypes(List<Integer> secTypes) { enabledSecTypes = secTypes; }
+ public final void SetSecTypes(List<Integer> secTypes) {
+ enabledSecTypes = secTypes;
+ }
static LogWriter vlog = new LogWriter("Security");
}
diff --git a/java/com/tigervnc/rfb/SecurityClient.java b/java/com/tigervnc/rfb/SecurityClient.java
index 59499b1..ff2433c 100644
--- a/java/com/tigervnc/rfb/SecurityClient.java
+++ b/java/com/tigervnc/rfb/SecurityClient.java
@@ -78,9 +78,9 @@
//UserPasswdGetter upg = null;
String msg = null;
- static StringParameter secTypes
+ public static StringParameter secTypes
= new StringParameter("SecurityTypes",
- "Specify which security scheme to use (None, VncAuth)",
- "Ident,TLSIdent,X509Ident,X509Plain,TLSPlain,X509Vnc,TLSVnc,X509None,TLSNone,VncAuth,None", Configuration.ConfigurationObject.ConfViewer);
+ "Specify which security scheme to use (None, VncAuth, Plain, Ident, TLSNone, TLSVnc, TLSPlain, TLSIdent, X509None, X509Vnc, X509Plain, X509Ident)",
+ "X509Ident,X509Plain,TLSIdent,TLSPlain,X509Vnc,TLSVnc,X509None,TLSNone,Ident,VncAuth,None", Configuration.ConfigurationObject.ConfViewer);
}
diff --git a/java/com/tigervnc/rfb/TightDecoder.java b/java/com/tigervnc/rfb/TightDecoder.java
index b644cdb..aa468eb 100644
--- a/java/com/tigervnc/rfb/TightDecoder.java
+++ b/java/com/tigervnc/rfb/TightDecoder.java
@@ -22,56 +22,181 @@
package com.tigervnc.rfb;
import com.tigervnc.rdr.InStream;
+import com.tigervnc.rdr.MemInStream;
+import com.tigervnc.rdr.OutStream;
import com.tigervnc.rdr.ZlibInStream;
import java.util.ArrayList;
import java.io.InputStream;
import java.awt.image.*;
import java.awt.*;
+import java.math.BigInteger;
+import java.io.*;
+import java.nio.*;
+import javax.imageio.*;
+import javax.imageio.stream.*;
public class TightDecoder extends Decoder {
final static int TIGHT_MAX_WIDTH = 2048;
+ final static int TIGHT_MIN_TO_COMPRESS = 12;
// Compression control
- final static int rfbTightExplicitFilter = 0x04;
- final static int rfbTightFill = 0x08;
- final static int rfbTightJpeg = 0x09;
- final static int rfbTightMaxSubencoding = 0x09;
+ final static int tightExplicitFilter = 0x04;
+ final static int tightFill = 0x08;
+ final static int tightJpeg = 0x09;
+ final static int tightMaxSubencoding = 0x09;
// Filters to improve compression efficiency
- final static int rfbTightFilterCopy = 0x00;
- final static int rfbTightFilterPalette = 0x01;
- final static int rfbTightFilterGradient = 0x02;
- final static int rfbTightMinToCompress = 12;
+ final static int tightFilterCopy = 0x00;
+ final static int tightFilterPalette = 0x01;
+ final static int tightFilterGradient = 0x02;
- final static Toolkit tk = Toolkit.getDefaultToolkit();
-
- public TightDecoder(CMsgReader reader_) {
- reader = reader_;
+ public TightDecoder() {
+ super(DecoderFlags.DecoderPartiallyOrdered);
zis = new ZlibInStream[4];
for (int i = 0; i < 4; i++)
zis[i] = new ZlibInStream();
}
- public void readRect(Rect r, CMsgHandler handler)
+ public void readRect(Rect r, InStream is,
+ ConnParams cp, OutStream os)
{
- InStream is = reader.getInStream();
- boolean cutZeros = false;
- clientpf = handler.getPreferredPF();
- serverpf = handler.cp.pf();
- int bpp = serverpf.bpp;
- cutZeros = false;
- if (bpp == 32) {
- if (serverpf.is888()) {
- cutZeros = true;
+ int comp_ctl;
+
+ comp_ctl = is.readU8();
+ os.writeU8(comp_ctl);
+
+ comp_ctl >>= 4;
+
+ // "Fill" compression type.
+ if (comp_ctl == tightFill) {
+ if (cp.pf().is888())
+ os.copyBytes(is, 3);
+ else
+ os.copyBytes(is, cp.pf().bpp/8);
+ return;
+ }
+
+ // "JPEG" compression type.
+ if (comp_ctl == tightJpeg) {
+ int len;
+
+ len = readCompact(is);
+ os.writeOpaque32(len);
+ os.copyBytes(is, len);
+ return;
+ }
+
+ // Quit on unsupported compression type.
+ if (comp_ctl > tightMaxSubencoding)
+ throw new Exception("TightDecoder: bad subencoding value received");
+
+ // "Basic" compression type.
+
+ int palSize = 0;
+
+ if (r.width() > TIGHT_MAX_WIDTH)
+ throw new Exception("TightDecoder: too large rectangle ("+r.width()+" pixels)");
+
+ // Possible palette
+ if ((comp_ctl & tightExplicitFilter) != 0) {
+ int filterId;
+
+ filterId = is.readU8() & 0xff;
+ os.writeU8(filterId);
+
+ switch (filterId) {
+ case tightFilterPalette:
+ palSize = is.readU8() + 1;
+ os.writeU32(palSize - 1);
+
+ if (cp.pf().is888())
+ os.copyBytes(is, palSize * 3);
+ else
+ os.copyBytes(is, palSize * cp.pf().bpp/8);
+ break;
+ case tightFilterGradient:
+ if (cp.pf().bpp == 8)
+ throw new Exception("TightDecoder: invalid BPP for gradient filter");
+ break;
+ case tightFilterCopy:
+ break;
+ default:
+ throw new Exception("TightDecoder: unknown filter code received");
}
}
- int comp_ctl = is.readU8();
+ int rowSize, dataSize;
- boolean bigEndian = handler.cp.pf().bigEndian;
+ if (palSize != 0) {
+ if (palSize <= 2)
+ rowSize = (r.width() + 7) / 8;
+ else
+ rowSize = r.width();
+ } else if (cp.pf().is888()) {
+ rowSize = r.width() * 3;
+ } else {
+ rowSize = r.width() * cp.pf().bpp/8;
+ }
- // Flush zlib streams if we are told by the server to do so.
+ dataSize = r.height() * rowSize;
+
+ if (dataSize < TIGHT_MIN_TO_COMPRESS) {
+ os.copyBytes(is, dataSize);
+ } else {
+ int len;
+
+ len = readCompact(is);
+ os.writeOpaque32(len);
+ os.copyBytes(is, len);
+ }
+ }
+
+ public boolean doRectsConflict(Rect rectA,
+ Object bufferA,
+ int buflenA,
+ Rect rectB,
+ Object bufferB,
+ int buflenB,
+ ConnParams cp)
+ {
+ byte comp_ctl_a, comp_ctl_b;
+
+ assert(buflenA >= 1);
+ assert(buflenB >= 1);
+
+ comp_ctl_a = ((byte[])bufferA)[0];
+ comp_ctl_b = ((byte[])bufferB)[0];
+
+ // Resets or use of zlib pose the same problem, so merge them
+ if ((comp_ctl_a & 0x80) == 0x00)
+ comp_ctl_a |= 1 << ((comp_ctl_a >> 4) & 0x03);
+ if ((comp_ctl_b & 0x80) == 0x00)
+ comp_ctl_b |= 1 << ((comp_ctl_b >> 4) & 0x03);
+
+ if (((comp_ctl_a & 0x0f) & (comp_ctl_b & 0x0f)) != 0)
+ return true;
+
+ return false;
+ }
+
+ public void decodeRect(Rect r, Object buffer,
+ int buflen, ConnParams cp,
+ ModifiablePixelBuffer pb)
+ {
+ ByteBuffer bufptr;
+ PixelFormat pf = cp.pf();
+
+ int comp_ctl;
+
+ bufptr = ByteBuffer.wrap((byte[])buffer);
+
+ assert(buflen >= 1);
+
+ comp_ctl = bufptr.get() & 0xff;
+ buflen -= 1;
+
+ // Reset zlib streams if we are told by the server to do so.
for (int i = 0; i < 4; i++) {
if ((comp_ctl & 1) != 0) {
zis[i].reset();
@@ -80,190 +205,216 @@
}
// "Fill" compression type.
- if (comp_ctl == rfbTightFill) {
- int[] pix = new int[1];
- if (cutZeros) {
- byte[] bytebuf = new byte[3];
- is.readBytes(bytebuf, 0, 3);
- serverpf.bufferFromRGB(pix, 0, bytebuf, 0, 1);
+ if (comp_ctl == tightFill) {
+ if (pf.is888()) {
+ ByteBuffer pix = ByteBuffer.allocate(4);
+
+ assert(buflen >= 3);
+
+ pf.bufferFromRGB(pix, bufptr, 1);
+ pb.fillRect(pf, r, pix.array());
} else {
- pix[0] = is.readPixel(serverpf.bpp/8, serverpf.bigEndian);
+ assert(buflen >= pf.bpp/8);
+ byte[] pix = new byte[pf.bpp/8];
+ bufptr.get(pix);
+ pb.fillRect(pf, r, pix);
}
- handler.fillRect(r, pix[0]);
return;
}
// "JPEG" compression type.
- if (comp_ctl == rfbTightJpeg) {
- DECOMPRESS_JPEG_RECT(r, is, handler);
+ if (comp_ctl == tightJpeg) {
+ int len;
+
+ WritableRaster buf;
+
+ JpegDecompressor jd = new JpegDecompressor();
+
+ assert(buflen >= 4);
+
+ len = bufptr.getInt();
+ buflen -= 4;
+
+ // We always use direct decoding with JPEG images
+ buf = pb.getBufferRW(r);
+ jd.decompress(bufptr, len, buf, r, pb.getPF());
+ pb.commitBufferRW(r);
return;
}
// Quit on unsupported compression type.
- if (comp_ctl > rfbTightMaxSubencoding) {
+ if (comp_ctl > tightMaxSubencoding)
throw new Exception("TightDecoder: bad subencoding value received");
- }
// "Basic" compression type.
int palSize = 0;
- int[] palette = new int[256];
+ ByteBuffer palette = ByteBuffer.allocate(256 * 4);
boolean useGradient = false;
- if ((comp_ctl & rfbTightExplicitFilter) != 0) {
- int filterId = is.readU8();
+ if ((comp_ctl & tightExplicitFilter) != 0) {
+ int filterId;
+
+ assert(buflen >= 1);
+
+ filterId = bufptr.get();
switch (filterId) {
- case rfbTightFilterPalette:
- palSize = is.readU8() + 1;
- byte[] tightPalette;
- if (cutZeros) {
- tightPalette = new byte[256 * 3];
- is.readBytes(tightPalette, 0, palSize * 3);
- serverpf.bufferFromRGB(palette, 0, tightPalette, 0, palSize);
+ case tightFilterPalette:
+ assert(buflen >= 1);
+
+ palSize = bufptr.getInt() + 1;
+ buflen -= 4;
+
+ if (pf.is888()) {
+ ByteBuffer tightPalette = ByteBuffer.allocate(palSize * 3);
+
+ assert(buflen >= tightPalette.capacity());
+
+ bufptr.get(tightPalette.array(), 0, tightPalette.capacity());
+ buflen -= tightPalette.capacity();
+
+ pf.bufferFromRGB(palette.duplicate(), tightPalette, palSize);
} else {
- is.readPixels(palette, palSize, serverpf.bpp/8, serverpf.bigEndian);
+ int len;
+
+ len = palSize * pf.bpp/8;
+
+ assert(buflen >= len);
+
+ bufptr.get(palette.array(), 0, len);
+ buflen -= len;
}
break;
- case rfbTightFilterGradient:
+ case tightFilterGradient:
useGradient = true;
break;
- case rfbTightFilterCopy:
+ case tightFilterCopy:
break;
default:
- throw new Exception("TightDecoder: unknown filter code recieved");
+ assert(false);
}
}
- int bppp = bpp;
- if (palSize != 0) {
- bppp = (palSize <= 2) ? 1 : 8;
- } else if (cutZeros) {
- bppp = 24;
- }
-
// Determine if the data should be decompressed or just copied.
- int rowSize = (r.width() * bppp + 7) / 8;
- int dataSize = r.height() * rowSize;
- int streamId = -1;
- InStream input;
- if (dataSize < rfbTightMinToCompress) {
- input = is;
+ int rowSize, dataSize;
+ byte[] netbuf;
+
+ if (palSize != 0) {
+ if (palSize <= 2)
+ rowSize = (r.width() + 7) / 8;
+ else
+ rowSize = r.width();
+ } else if (pf.is888()) {
+ rowSize = r.width() * 3;
} else {
- int length = is.readCompactLength();
- streamId = comp_ctl & 0x03;
- zis[streamId].setUnderlying(is, length);
- input = (ZlibInStream)zis[streamId];
+ rowSize = r.width() * pf.bpp/8;
}
- // Allocate netbuf and read in data
- byte[] netbuf = new byte[dataSize];
- input.readBytes(netbuf, 0, dataSize);
+ dataSize = r.height() * rowSize;
+ if (dataSize < TIGHT_MIN_TO_COMPRESS) {
+ assert(buflen >= dataSize);
+ } else {
+ int len;
+ int streamId;
+ MemInStream ms;
+
+ assert(buflen >= 4);
+
+ len = bufptr.getInt();
+ buflen -= 4;
+
+ assert(buflen >= len);
+
+ streamId = comp_ctl & 0x03;
+ ms = new MemInStream(bufptr.array(), bufptr.position(), len);
+ zis[streamId].setUnderlying(ms, len);
+
+ // Allocate netbuf and read in data
+ netbuf = new byte[dataSize];
+
+ zis[streamId].readBytes(netbuf, 0, dataSize);
+
+ zis[streamId].removeUnderlying();
+ ms = null;
+
+ bufptr = ByteBuffer.wrap(netbuf);
+ buflen = dataSize;
+ }
+
+ ByteBuffer outbuf = ByteBuffer.allocate(r.area() * pf.bpp/8);
int stride = r.width();
- int[] buf = reader.getImageBuf(r.area());
if (palSize == 0) {
// Truecolor data.
if (useGradient) {
- if (bpp == 32 && cutZeros) {
- FilterGradient24(netbuf, buf, stride, r);
+ if (pf.is888()) {
+ FilterGradient24(bufptr, pf, outbuf, stride, r);
} else {
- FilterGradient(netbuf, buf, stride, r);
+ switch (pf.bpp) {
+ case 8:
+ assert(false);
+ break;
+ case 16:
+ FilterGradient(bufptr, pf, outbuf, stride, r);
+ break;
+ case 32:
+ FilterGradient(bufptr, pf, outbuf, stride, r);
+ break;
+ }
}
} else {
// Copy
- int h = r.height();
- int ptr = 0;
- int srcPtr = 0;
+ ByteBuffer ptr = (ByteBuffer)outbuf.duplicate().mark();
+ ByteBuffer srcPtr = bufptr.duplicate();
int w = r.width();
- if (cutZeros) {
- serverpf.bufferFromRGB(buf, ptr, netbuf, srcPtr, w*h);
- } else {
- int pixelSize = (bpp >= 24) ? 3 : bpp/8;
+ int h = r.height();
+ if (pf.is888()) {
while (h > 0) {
- for (int i = 0; i < w; i++) {
- if (bpp == 8) {
- buf[ptr+i] = netbuf[srcPtr+i] & 0xff;
- } else {
- for (int j = pixelSize-1; j >= 0; j--)
- buf[ptr+i] |= ((netbuf[srcPtr+i+j] & 0xff) << j*8);
- }
- }
- ptr += stride;
- srcPtr += w * pixelSize;
+ pf.bufferFromRGB(ptr.duplicate(), srcPtr.duplicate(), w);
+ ptr.position(ptr.position() + stride * pf.bpp/8);
+ srcPtr.position(srcPtr.position() + w * 3);
+ h--;
+ }
+ } else {
+ while (h > 0) {
+ ptr.put(srcPtr.array(), srcPtr.position(), w * pf.bpp/8);
+ ptr.reset().position(ptr.position() + stride * pf.bpp/8).mark();
+ srcPtr.position(srcPtr.position() + w * pf.bpp/8);
h--;
}
}
}
} else {
// Indexed color
- int x, h = r.height(), w = r.width(), b, pad = stride - w;
- int ptr = 0;
- int srcPtr = 0, bits;
- if (palSize <= 2) {
- // 2-color palette
- while (h > 0) {
- for (x = 0; x < w / 8; x++) {
- bits = netbuf[srcPtr++];
- for(b = 7; b >= 0; b--) {
- buf[ptr++] = palette[bits >> b & 1];
- }
- }
- if (w % 8 != 0) {
- bits = netbuf[srcPtr++];
- for (b = 7; b >= 8 - w % 8; b--) {
- buf[ptr++] = palette[bits >> b & 1];
- }
- }
- ptr += pad;
- h--;
- }
- } else {
- // 256-color palette
- while (h > 0) {
- int endOfRow = ptr + w;
- while (ptr < endOfRow) {
- buf[ptr++] = palette[netbuf[srcPtr++] & 0xff];
- }
- ptr += pad;
- h--;
- }
+ switch (pf.bpp) {
+ case 8:
+ FilterPalette8(palette, palSize,
+ bufptr, outbuf, stride, r);
+ break;
+ case 16:
+ FilterPalette16(palette.asShortBuffer(), palSize,
+ bufptr, outbuf.asShortBuffer(), stride, r);
+ break;
+ case 32:
+ FilterPalette32(palette.asIntBuffer(), palSize,
+ bufptr, outbuf.asIntBuffer(), stride, r);
+ break;
}
}
- handler.imageRect(r, buf);
+ pb.imageRect(pf, r, outbuf.array());
- if (streamId != -1) {
- zis[streamId].reset();
- }
}
- final private void DECOMPRESS_JPEG_RECT(Rect r, InStream is, CMsgHandler handler)
+ final private void FilterGradient24(ByteBuffer inbuf,
+ PixelFormat pf, ByteBuffer outbuf,
+ int stride, Rect r)
{
- // Read length
- int compressedLen = is.readCompactLength();
- if (compressedLen <= 0)
- vlog.info("Incorrect data received from the server.");
-
- // Allocate netbuf and read in data
- byte[] netbuf = new byte[compressedLen];
- is.readBytes(netbuf, 0, compressedLen);
-
- // Create an Image object from the JPEG data.
- Image jpeg = tk.createImage(netbuf);
- jpeg.setAccelerationPriority(1);
- handler.imageRect(r, jpeg);
- jpeg.flush();
- }
-
- final private void FilterGradient24(byte[] netbuf, int[] buf, int stride,
- Rect r)
- {
-
int x, y, c;
byte[] prevRow = new byte[TIGHT_MAX_WIDTH*3];
byte[] thisRow = new byte[TIGHT_MAX_WIDTH*3];
- byte[] pix = new byte[3];
+ ByteBuffer pix = ByteBuffer.allocate(3);
int[] est = new int[3];
// Set up shortcut variables
@@ -273,38 +424,38 @@
for (y = 0; y < rectHeight; y++) {
/* First pixel in a row */
for (c = 0; c < 3; c++) {
- pix[c] = (byte)(netbuf[y*rectWidth*3+c] + prevRow[c]);
- thisRow[c] = pix[c];
+ pix.put(c, (byte)(inbuf.get(y*rectWidth*3+c) + prevRow[c]));
+ thisRow[c] = pix.get(c);
}
- serverpf.bufferFromRGB(buf, y*stride, pix, 0, 1);
+ pf.bufferFromRGB((ByteBuffer)outbuf.position(y*stride), pix, 1);
/* Remaining pixels of a row */
for (x = 1; x < rectWidth; x++) {
for (c = 0; c < 3; c++) {
- est[c] = (int)(prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c]);
- if (est[c] > 0xFF) {
- est[c] = 0xFF;
+ est[c] = prevRow[x*3+c] + pix.get(c) - prevRow[(x-1)*3+c];
+ if (est[c] > 0xff) {
+ est[c] = 0xff;
} else if (est[c] < 0) {
est[c] = 0;
}
- pix[c] = (byte)(netbuf[(y*rectWidth+x)*3+c] + est[c]);
- thisRow[x*3+c] = pix[c];
+ pix.put(c, (byte)(inbuf.get((y*rectWidth+x)*3+c) + est[c]));
+ thisRow[x*3+c] = pix.get(c);
}
- serverpf.bufferFromRGB(buf, y*stride+x, pix, 0, 1);
+ pf.bufferFromRGB((ByteBuffer)outbuf.position(y*stride+x), pix, 1);
}
System.arraycopy(thisRow, 0, prevRow, 0, prevRow.length);
}
}
- final private void FilterGradient(byte[] netbuf, int[] buf, int stride,
- Rect r)
+ final private void FilterGradient(ByteBuffer inbuf,
+ PixelFormat pf, ByteBuffer outbuf,
+ int stride, Rect r)
{
-
int x, y, c;
byte[] prevRow = new byte[TIGHT_MAX_WIDTH];
byte[] thisRow = new byte[TIGHT_MAX_WIDTH];
- byte[] pix = new byte[3];
+ ByteBuffer pix = ByteBuffer.allocate(3);
int[] est = new int[3];
// Set up shortcut variables
@@ -313,19 +464,18 @@
for (y = 0; y < rectHeight; y++) {
/* First pixel in a row */
- // FIXME
- //serverpf.rgbFromBuffer(pix, 0, netbuf, y*rectWidth, 1, cm);
+ pf.rgbFromBuffer(pix, (ByteBuffer)inbuf.position(y*rectWidth), 1);
for (c = 0; c < 3; c++)
- pix[c] += prevRow[c];
+ pix.put(c, (byte)(pix.get(c) + prevRow[c]));
- System.arraycopy(pix, 0, thisRow, 0, pix.length);
+ System.arraycopy(pix.array(), 0, thisRow, 0, pix.capacity());
- serverpf.bufferFromRGB(buf, y*stride, pix, 0, 1);
+ pf.bufferFromRGB((ByteBuffer)outbuf.position(y*stride), pix, 1);
/* Remaining pixels of a row */
for (x = 1; x < rectWidth; x++) {
for (c = 0; c < 3; c++) {
- est[c] = (int)(prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c]);
+ est[c] = prevRow[x*3+c] + pix.get(c) - prevRow[(x-1)*3+c];
if (est[c] > 0xff) {
est[c] = 0xff;
} else if (est[c] < 0) {
@@ -333,24 +483,156 @@
}
}
- // FIXME
- //serverpf.rgbFromBuffer(pix, 0, netbuf, y*rectWidth+x, 1, cm);
+ pf.rgbFromBuffer(pix, (ByteBuffer)inbuf.position(y*rectWidth+x), 1);
for (c = 0; c < 3; c++)
- pix[c] += est[c];
+ pix.put(c, (byte)(pix.get(c) + est[c]));
- System.arraycopy(pix, 0, thisRow, x*3, pix.length);
+ System.arraycopy(pix.array(), 0, thisRow, x*3, pix.capacity());
- serverpf.bufferFromRGB(buf, y*stride+x, pix, 0, 1);
+ pf.bufferFromRGB((ByteBuffer)outbuf.position(y*stride+x), pix, 1);
}
System.arraycopy(thisRow, 0, prevRow, 0, prevRow.length);
}
}
- private CMsgReader reader;
+ private void FilterPalette8(ByteBuffer palette, int palSize,
+ ByteBuffer inbuf, ByteBuffer outbuf,
+ int stride, Rect r)
+ {
+ // Indexed color
+ int x, h = r.height(), w = r.width(), b, pad = stride - w;
+ ByteBuffer ptr = outbuf.duplicate();
+ byte bits;
+ ByteBuffer srcPtr = inbuf.duplicate();
+ if (palSize <= 2) {
+ // 2-color palette
+ while (h > 0) {
+ for (x = 0; x < w / 8; x++) {
+ bits = srcPtr.get();
+ for (b = 7; b >= 0; b--) {
+ ptr.put(palette.get(bits >> b & 1));
+ }
+ }
+ if (w % 8 != 0) {
+ bits = srcPtr.get();
+ for (b = 7; b >= 8 - w % 8; b--) {
+ ptr.put(palette.get(bits >> b & 1));
+ }
+ }
+ ptr.position(ptr.position() + pad);
+ h--;
+ }
+ } else {
+ // 256-color palette
+ while (h > 0) {
+ int endOfRow = ptr.position() + w;
+ while (ptr.position() < endOfRow) {
+ ptr.put(palette.get(srcPtr.get()));
+ }
+ ptr.position(ptr.position() + pad);
+ h--;
+ }
+ }
+ }
+
+ private void FilterPalette16(ShortBuffer palette, int palSize,
+ ByteBuffer inbuf, ShortBuffer outbuf,
+ int stride, Rect r)
+ {
+ // Indexed color
+ int x, h = r.height(), w = r.width(), b, pad = stride - w;
+ ShortBuffer ptr = outbuf.duplicate();
+ byte bits;
+ ByteBuffer srcPtr = inbuf.duplicate();
+ if (palSize <= 2) {
+ // 2-color palette
+ while (h > 0) {
+ for (x = 0; x < w / 8; x++) {
+ bits = srcPtr.get();
+ for (b = 7; b >= 0; b--) {
+ ptr.put(palette.get(bits >> b & 1));
+ }
+ }
+ if (w % 8 != 0) {
+ bits = srcPtr.get();
+ for (b = 7; b >= 8 - w % 8; b--) {
+ ptr.put(palette.get(bits >> b & 1));
+ }
+ }
+ ptr.position(ptr.position() + pad);
+ h--;
+ }
+ } else {
+ // 256-color palette
+ while (h > 0) {
+ int endOfRow = ptr.position() + w;
+ while (ptr.position() < endOfRow) {
+ ptr.put(palette.get(srcPtr.get()));
+ }
+ ptr.position(ptr.position() + pad);
+ h--;
+ }
+ }
+ }
+
+ private void FilterPalette32(IntBuffer palette, int palSize,
+ ByteBuffer inbuf, IntBuffer outbuf,
+ int stride, Rect r)
+ {
+ // Indexed color
+ int x, h = r.height(), w = r.width(), b, pad = stride - w;
+ IntBuffer ptr = outbuf.duplicate();
+ byte bits;
+ ByteBuffer srcPtr = inbuf.duplicate();
+ if (palSize <= 2) {
+ // 2-color palette
+ while (h > 0) {
+ for (x = 0; x < w / 8; x++) {
+ bits = srcPtr.get();
+ for (b = 7; b >= 0; b--) {
+ ptr.put(palette.get(bits >> b & 1));
+ }
+ }
+ if (w % 8 != 0) {
+ bits = srcPtr.get();
+ for (b = 7; b >= 8 - w % 8; b--) {
+ ptr.put(palette.get(bits >> b & 1));
+ }
+ }
+ ptr.position(ptr.position() + pad);
+ h--;
+ }
+ } else {
+ // 256-color palette
+ while (h > 0) {
+ int endOfRow = ptr.position() + w;
+ while (ptr.position() < endOfRow) {
+ ptr.put(palette.get(srcPtr.get() & 0xff));
+ }
+ ptr.position(ptr.position() + pad);
+ h--;
+ }
+ }
+ }
+
+ public final int readCompact(InStream is) {
+ byte b;
+ int result;
+
+ b = (byte)is.readU8();
+ result = (int)b & 0x7F;
+ if ((b & 0x80) != 0) {
+ b = (byte)is.readU8();
+ result |= ((int)b & 0x7F) << 7;
+ if ((b & 0x80) != 0) {
+ b = (byte)is.readU8();
+ result |= ((int)b & 0xFF) << 14;
+ }
+ }
+ return result;
+ }
+
private ZlibInStream[] zis;
- private PixelFormat serverpf;
- private PixelFormat clientpf;
- static LogWriter vlog = new LogWriter("TightDecoder");
}
diff --git a/java/com/tigervnc/rfb/ZRLEDecoder.java b/java/com/tigervnc/rfb/ZRLEDecoder.java
index e706510..c1f908a 100644
--- a/java/com/tigervnc/rfb/ZRLEDecoder.java
+++ b/java/com/tigervnc/rfb/ZRLEDecoder.java
@@ -18,22 +18,143 @@
package com.tigervnc.rfb;
+import java.awt.image.*;
+import java.nio.*;
+import java.util.*;
+
import com.tigervnc.rdr.*;
public class ZRLEDecoder extends Decoder {
- public ZRLEDecoder(CMsgReader reader_) {
- reader = reader_;
+ private static int readOpaque24A(InStream is)
+ {
+ is.check(3);
+ ByteBuffer r = ByteBuffer.allocate(4);
+ r.put(0, (byte)is.readU8());
+ r.put(1, (byte)is.readU8());
+ r.put(2, (byte)is.readU8());
+ return ((ByteBuffer)r.rewind()).getInt();
+ }
+
+ private static int readOpaque24B(InStream is)
+ {
+ is.check(3);
+ ByteBuffer r = ByteBuffer.allocate(4);
+ r.put(2, (byte)is.readU8());
+ r.put(1, (byte)is.readU8());
+ r.put(0, (byte)is.readU8());
+ return ((ByteBuffer)r.rewind()).getInt();
+ }
+
+ public ZRLEDecoder() {
+ super(DecoderFlags.DecoderOrdered);
zis = new ZlibInStream();
}
- public void readRect(Rect r, CMsgHandler handler) {
- InStream is = reader.getInStream();
- int[] buf = reader.getImageBuf(64 * 64 * 4);
- int bpp = handler.cp.pf().bpp;
- int bytesPerPixel = (bpp > 24 ? 3 : bpp / 8);
- boolean bigEndian = handler.cp.pf().bigEndian;
+ public void readRect(Rect r, InStream is,
+ ConnParams cp, OutStream os)
+ {
+ int len;
+ len = is.readU32();
+ os.writeU32(len);
+ os.copyBytes(is, len);
+ }
+
+ public void decodeRect(Rect r, Object buffer,
+ int buflen, ConnParams cp,
+ ModifiablePixelBuffer pb)
+ {
+ MemInStream is = new MemInStream((byte[])buffer, 0, buflen);
+ PixelFormat pf = cp.pf();
+ ByteBuffer buf = ByteBuffer.allocate(64 * 64 * 4);
+ switch (pf.bpp) {
+ case 8: zrleDecode8(r, is, zis, buf, pf, pb); break;
+ case 16: zrleDecode16(r, is, zis, buf, pf, pb); break;
+ case 32:
+ int maxPixel = pf.pixelFromRGB(-1, -1, -1, pf.getColorModel());
+ boolean fitsInLS3Bytes = maxPixel < (1<<24);
+ boolean fitsInMS3Bytes = (maxPixel & 0xff) == 0;
+
+ if ((fitsInLS3Bytes && pf.isLittleEndian()) ||
+ (fitsInMS3Bytes && pf.isBigEndian()))
+ {
+ zrleDecode24A(r, is, zis, buf, pf, pb);
+ }
+ else if ((fitsInLS3Bytes && pf.isBigEndian()) ||
+ (fitsInMS3Bytes && pf.isLittleEndian()))
+ {
+ zrleDecode24B(r, is, zis, buf, pf, pb);
+ }
+ else
+ {
+ zrleDecode32(r, is, zis, buf, pf, pb);
+ }
+ break;
+ }
+ }
+
+ private static enum PIXEL_T { U8, U16, U24A, U24B, U32 };
+
+ private static ByteBuffer READ_PIXEL(InStream is, PIXEL_T type) {
+ ByteBuffer b = ByteBuffer.allocate(4);
+ switch (type) {
+ case U8:
+ b.putInt(is.readOpaque8());
+ return (ByteBuffer)ByteBuffer.allocate(1).put(b.get(3)).rewind();
+ case U16:
+ b.putInt(is.readOpaque16());
+ return (ByteBuffer)ByteBuffer.allocate(2).put(b.array(), 2, 2).rewind();
+ case U24A:
+ return (ByteBuffer)b.putInt(readOpaque24A(is)).rewind();
+ case U24B:
+ return (ByteBuffer)b.putInt(readOpaque24B(is)).rewind();
+ case U32:
+ default:
+ return (ByteBuffer)b.putInt(is.readOpaque32()).rewind();
+ }
+ }
+
+ private void zrleDecode8(Rect r, InStream is,
+ ZlibInStream zis, ByteBuffer buf,
+ PixelFormat pf, ModifiablePixelBuffer pb)
+ {
+ ZRLE_DECODE(r, is, zis, buf, pf, pb, PIXEL_T.U8);
+ }
+
+ private void zrleDecode16(Rect r, InStream is,
+ ZlibInStream zis, ByteBuffer buf,
+ PixelFormat pf, ModifiablePixelBuffer pb)
+ {
+ ZRLE_DECODE(r, is, zis, buf, pf, pb, PIXEL_T.U16);
+ }
+
+ private void zrleDecode24A(Rect r, InStream is,
+ ZlibInStream zis, ByteBuffer buf,
+ PixelFormat pf, ModifiablePixelBuffer pb)
+ {
+ ZRLE_DECODE(r, is, zis, buf, pf, pb, PIXEL_T.U24A);
+ }
+
+ private void zrleDecode24B(Rect r, InStream is,
+ ZlibInStream zis, ByteBuffer buf,
+ PixelFormat pf, ModifiablePixelBuffer pb)
+ {
+ ZRLE_DECODE(r, is, zis, buf, pf, pb, PIXEL_T.U24B);
+ }
+
+ private void zrleDecode32(Rect r, InStream is,
+ ZlibInStream zis, ByteBuffer buf,
+ PixelFormat pf, ModifiablePixelBuffer pb)
+ {
+ ZRLE_DECODE(r, is, zis, buf, pf, pb, PIXEL_T.U32);
+ }
+
+ private void ZRLE_DECODE(Rect r, InStream is,
+ ZlibInStream zis, ByteBuffer buf,
+ PixelFormat pf, ModifiablePixelBuffer pb,
+ PIXEL_T pix_t)
+ {
int length = is.readU32();
zis.setUnderlying(is, length);
Rect t = new Rect();
@@ -49,13 +170,16 @@
int mode = zis.readU8();
boolean rle = (mode & 128) != 0;
int palSize = mode & 127;
- int[] palette = new int[128];
+ ByteBuffer palette = ByteBuffer.allocate(128 * pf.bpp/8);
- zis.readPixels(palette, palSize, bytesPerPixel, bigEndian);
+ for (int i = 0; i < palSize; i++) {
+ palette.put(READ_PIXEL(zis, pix_t));
+ }
if (palSize == 1) {
- int pix = palette[0];
- handler.fillRect(t, pix);
+ ByteBuffer pix =
+ ByteBuffer.allocate(pf.bpp/8).put(palette.array(), 0, pf.bpp/8);
+ pb.fillRect(pf, t, pix.array());
continue;
}
@@ -63,8 +187,17 @@
if (palSize == 0) {
// raw
-
- zis.readPixels(buf, t.area(), bytesPerPixel, bigEndian);
+ switch (pix_t) {
+ case U24A:
+ case U24B:
+ ByteBuffer ptr = buf.duplicate();
+ for (int iptr=0; iptr < t.area(); iptr++) {
+ ptr.put(READ_PIXEL(zis, pix_t));
+ }
+ break;
+ default:
+ zis.readBytes(buf, t.area() * (pf.bpp/8));
+ }
} else {
@@ -72,21 +205,21 @@
int bppp = ((palSize > 16) ? 8 :
((palSize > 4) ? 4 : ((palSize > 2) ? 2 : 1)));
- int ptr = 0;
+ ByteBuffer ptr = buf.duplicate();
for (int i = 0; i < t.height(); i++) {
- int eol = ptr + t.width();
+ int eol = ptr.position() + t.width()*pf.bpp/8;
int b = 0;
int nbits = 0;
- while (ptr < eol) {
+ while (ptr.position() < eol) {
if (nbits == 0) {
b = zis.readU8();
nbits = 8;
}
nbits -= bppp;
int index = (b >> nbits) & ((1 << bppp) - 1) & 127;
- buf[ptr++] = palette[index];
+ ptr.put(palette.array(), index*pf.bpp/8, pf.bpp/8);
}
}
}
@@ -97,10 +230,10 @@
// plain RLE
- int ptr = 0;
- int end = ptr + t.area();
- while (ptr < end) {
- int pix = zis.readPixel(bytesPerPixel, bigEndian);
+ ByteBuffer ptr = buf.duplicate();
+ int end = ptr.position() + t.area()*pf.bpp/8;
+ while (ptr.position() < end) {
+ ByteBuffer pix = READ_PIXEL(zis, pix_t);
int len = 1;
int b;
do {
@@ -108,19 +241,21 @@
len += b;
} while (b == 255);
- if (!(len <= end - ptr))
- throw new Exception("ZRLEDecoder: assertion (len <= end - ptr)"
- +" failed");
+ if (end - ptr.position() < len*(pf.bpp/8)) {
+ System.err.println("ZRLE decode error\n");
+ throw new Exception("ZRLE decode error");
+ }
- while (len-- > 0) buf[ptr++] = pix;
+ while (len-- > 0) ptr.put(pix);
+
}
} else {
// palette RLE
- int ptr = 0;
- int end = ptr + t.area();
- while (ptr < end) {
+ ByteBuffer ptr = buf.duplicate();
+ int end = ptr.position() + t.area()*pf.bpp/8;
+ while (ptr.position() < end) {
int index = zis.readU8();
int len = 1;
if ((index & 128) != 0) {
@@ -130,27 +265,26 @@
len += b;
} while (b == 255);
- if (!(len <= end - ptr))
- throw new Exception("ZRLEDecoder: assertion "
- +"(len <= end - ptr) failed");
+ if (end - ptr.position() < len*(pf.bpp/8)) {
+ System.err.println("ZRLE decode error\n");
+ throw new Exception("ZRLE decode error");
+ }
}
index &= 127;
- int pix = palette[index];
+ while (len-- > 0) ptr.put(palette.array(), index*pf.bpp/8, pf.bpp/8);
- while (len-- > 0) buf[ptr++] = pix;
}
}
}
- handler.imageRect(t, buf);
+ pb.imageRect(pf, t, buf.array());
}
}
- zis.reset();
+ zis.removeUnderlying();
}
- CMsgReader reader;
- ZlibInStream zis;
+ private ZlibInStream zis;
}
diff --git a/java/com/tigervnc/vncviewer/BIPixelBuffer.java b/java/com/tigervnc/vncviewer/BIPixelBuffer.java
deleted file mode 100644
index 9612b36..0000000
--- a/java/com/tigervnc/vncviewer/BIPixelBuffer.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/* Copyright (C) 2012 Brian P. Hinz
- * Copyright (C) 2012 D. R. Commander. All Rights Reserved.
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this software; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
- * USA.
- */
-
-package com.tigervnc.vncviewer;
-
-import java.awt.*;
-import java.awt.image.*;
-
-import com.tigervnc.rfb.*;
-import com.tigervnc.rfb.Exception;
-
-public class BIPixelBuffer extends PlatformPixelBuffer implements ImageObserver
-{
- public BIPixelBuffer(int w, int h, CConn cc_, DesktopWindow desktop_) {
- super(w, h, cc_, desktop_);
- clip = new Rectangle();
- }
-
- public void setPF(PixelFormat pf) {
- super.setPF(pf);
- createImage(width(), height());
- }
-
- public void updateColourMap() {
- super.updateColourMap();
- createImage(width_, height_);
- }
-
- // resize() resizes the image, preserving the image data where possible.
- public void resize(int w, int h) {
- if (w == width() && h == height())
- return;
-
- width_ = w;
- height_ = h;
- createImage(w, h);
- }
-
- private void createImage(int w, int h) {
- if (w == 0 || h == 0) return;
- WritableRaster wr;
- if (cm instanceof IndexColorModel)
- wr = ((IndexColorModel)cm).createCompatibleWritableRaster(w, h);
- else
- wr = ((DirectColorModel)cm).createCompatibleWritableRaster(w, h);
- image = new BufferedImage(cm, wr, true, null);
- db = wr.getDataBuffer();
- }
-
- public void fillRect(int x, int y, int w, int h, int pix) {
- Graphics2D graphics = (Graphics2D)image.getGraphics();
- switch (format.depth) {
- case 24:
- graphics.setColor(new Color(pix));
- graphics.fillRect(x, y, w, h);
- break;
- default:
- Color color = new Color((0xff << 24) | (cm.getRed(pix) << 16) |
- (cm.getGreen(pix) << 8) | (cm.getBlue(pix)));
- graphics.setColor(color);
- graphics.fillRect(x, y, w, h);
- break;
- }
- graphics.dispose();
- }
-
- public void imageRect(int x, int y, int w, int h, Object pix) {
- if (pix instanceof Image) {
- Image img = (Image)pix;
- clip = new Rectangle(x, y, w, h);
- synchronized(clip) {
- tk.prepareImage(img, -1, -1, this);
- try {
- clip.wait(1000);
- } catch (InterruptedException e) {
- throw new Exception("Error decoding JPEG data");
- }
- }
- clip = null;
- img.flush();
- } else {
- if (image.getSampleModel().getTransferType() == DataBuffer.TYPE_BYTE) {
- byte[] bytes = new byte[((int[])pix).length];
- for (int i = 0; i < bytes.length; i++)
- bytes[i] = (byte)((int[])pix)[i];
- pix = bytes;
- }
- image.getSampleModel().setDataElements(x, y, w, h, pix, db);
- }
- }
-
- public void copyRect(int x, int y, int w, int h, int srcX, int srcY) {
- Graphics2D graphics = (Graphics2D)image.getGraphics();
- graphics.copyArea(srcX, srcY, w, h, x - srcX, y - srcY);
- graphics.dispose();
- }
-
- public Image getImage() {
- return (Image)image;
- }
-
- public boolean imageUpdate(Image img, int infoflags, int x, int y, int w, int h) {
- if ((infoflags & (ALLBITS | ABORT)) == 0) {
- return true;
- } else {
- if ((infoflags & ALLBITS) != 0) {
- if (clip != null) {
- synchronized(clip) {
- Graphics2D graphics = (Graphics2D)image.getGraphics();
- graphics.drawImage(img, clip.x, clip.y, clip.width, clip.height, null);
- graphics.dispose();
- clip.notify();
- }
- }
- }
- return false;
- }
- }
-
- BufferedImage image;
- DataBuffer db;
- Rectangle clip;
-
- static LogWriter vlog = new LogWriter("BIPixelBuffer");
-}
diff --git a/java/com/tigervnc/vncviewer/CConn.java b/java/com/tigervnc/vncviewer/CConn.java
index b9680ef..128867b 100644
--- a/java/com/tigervnc/vncviewer/CConn.java
+++ b/java/com/tigervnc/vncviewer/CConn.java
@@ -35,7 +35,9 @@
package com.tigervnc.vncviewer;
import java.awt.*;
+import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
+import java.awt.Toolkit;
import java.io.IOException;
import java.io.InputStream;
@@ -49,6 +51,7 @@
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.*;
+import java.util.prefs.*;
import com.tigervnc.rdr.*;
import com.tigervnc.rfb.*;
@@ -57,17 +60,23 @@
import com.tigervnc.network.Socket;
import com.tigervnc.network.TcpSocket;
-public class CConn extends CConnection implements
- UserPasswdGetter, UserMsgBox, OptionsDialogCallback,
- FdInStreamBlockCallback, ActionListener {
+import static com.tigervnc.vncviewer.Parameters.*;
- public final PixelFormat getPreferredPF() { return fullColourPF; }
- static final PixelFormat verylowColourPF =
+public class CConn extends CConnection implements
+ UserPasswdGetter, FdInStreamBlockCallback, ActionListener {
+
+ // 8 colours (1 bit per component)
+ static final PixelFormat verylowColorPF =
new PixelFormat(8, 3, false, true, 1, 1, 1, 2, 1, 0);
- static final PixelFormat lowColourPF =
+
+ // 64 colours (2 bits per component)
+ static final PixelFormat lowColorPF =
new PixelFormat(8, 6, false, true, 3, 3, 3, 4, 2, 0);
- static final PixelFormat mediumColourPF =
- new PixelFormat(8, 8, false, false, 7, 7, 3, 0, 3, 6);
+
+ // 256 colours (2-3 bits per component)
+ static final PixelFormat mediumColorPF =
+ new PixelFormat(8, 8, false, true, 7, 7, 3, 5, 2, 0);
+
static final int KEY_LOC_SHIFT_R = 0;
static final int KEY_LOC_SHIFT_L = 16;
static final int SUPER_MASK = 1<<15;
@@ -75,67 +84,49 @@
////////////////////////////////////////////////////////////////////
// The following methods are all called from the RFB thread
- public CConn(VncViewer viewer_, Socket sock_,
- String vncServerName)
+ public CConn(String vncServerName, Socket socket)
{
- sock = sock_; viewer = viewer_;
+ serverHost = null; serverPort = 0; desktop = null;
pendingPFChange = false;
currentEncoding = Encodings.encodingTight; lastServerEncoding = -1;
- fullColour = viewer.fullColour.getValue();
- lowColourLevel = viewer.lowColourLevel.getValue();
- autoSelect = viewer.autoSelect.getValue();
formatChange = false; encodingChange = false;
- fullScreen = viewer.fullScreen.getValue();
- menuKeyCode = MenuKey.getMenuKeyCode();
- options = new OptionsDialog(this);
- options.initDialog();
- clipboardDialog = new ClipboardDialog(this);
firstUpdate = true; pendingUpdate = false; continuousUpdates = false;
forceNonincremental = true; supportsSyncFence = false;
+
+ setShared(shared.getValue());
+ sock = socket;
downKeySym = new HashMap<Integer, Integer>();
- setShared(viewer.shared.getValue());
upg = this;
- msg = this;
- String encStr = viewer.preferredEncoding.getValue();
- int encNum = Encodings.encodingNum(encStr);
- if (encNum != -1) {
+ int encNum = Encodings.encodingNum(preferredEncoding.getValue());
+ if (encNum != -1)
currentEncoding = encNum;
- }
+
+ cp.supportsLocalCursor = true;
+
cp.supportsDesktopResize = true;
cp.supportsExtendedDesktopSize = true;
+ cp.supportsDesktopRename = true;
+
cp.supportsSetDesktopSize = false;
cp.supportsClientRedirect = true;
- cp.supportsDesktopRename = true;
- cp.supportsLocalCursor = viewer.useLocalCursor.getValue();
- cp.customCompressLevel = viewer.customCompressLevel.getValue();
- cp.compressLevel = viewer.compressLevel.getValue();
- cp.noJpeg = viewer.noJpeg.getValue();
- cp.qualityLevel = viewer.qualityLevel.getValue();
- initMenu();
- if (sock != null) {
- String name = sock.getPeerEndpoint();
- vlog.info("Accepted connection from " + name);
- } else {
- if (vncServerName != null &&
- !viewer.alwaysShowServerDialog.getValue()) {
- setServerName(Hostname.getHost(vncServerName));
- setServerPort(Hostname.getPort(vncServerName));
- } else {
- ServerDialog dlg = new ServerDialog(options, vncServerName, this);
- boolean ret = dlg.showDialog();
- if (!ret) {
- close();
- return;
- }
- setServerName(viewer.vncServerName.getValueStr());
- setServerPort(viewer.vncServerPort.getValue());
- }
+ if (customCompressLevel.getValue())
+ cp.compressLevel = compressLevel.getValue();
+ else
+ cp.compressLevel = -1;
+ if (!noJpeg.getValue())
+ cp.qualityLevel = qualityLevel.getValue();
+ else
+ cp.qualityLevel = -1;
+
+ if (sock == null) {
+ setServerName(Hostname.getHost(vncServerName));
+ setServerPort(Hostname.getPort(vncServerName));
try {
- if (viewer.tunnel.getValue() || (viewer.via.getValue() != null)) {
+ if (tunnel.getValue() || !via.getValue().isEmpty()) {
int localPort = TcpSocket.findFreeTcpPort();
if (localPort == 0)
throw new Exception("Could not obtain free TCP port");
@@ -148,11 +139,22 @@
throw new Exception(e.getMessage());
}
vlog.info("connected to host "+getServerName()+" port "+getServerPort());
+ } else {
+ String name = sock.getPeerEndpoint();
+ if (listenMode.getValue())
+ vlog.info("Accepted connection from " + name);
+ else
+ vlog.info("connected to host "+Hostname.getHost(name)+" port "+Hostname.getPort(name));
}
+ // See callback below
sock.inStream().setBlockCallback(this);
+
setStreams(sock.inStream(), sock.outStream());
+
initialiseProtocol();
+
+ OptionsDialog.addCallback("handleOptions", this);
}
public void refreshFramebuffer()
@@ -165,21 +167,37 @@
requestNewUpdate();
}
- public boolean showMsgBox(int flags, String title, String text)
- {
- //StringBuffer titleText = new StringBuffer("VNC Viewer: "+title);
- return true;
+ public String connectionInfo() {
+ String info = new String("Desktop name: %s%n"+
+ "Host: %s:%d%n"+
+ "Size: %dx%d%n"+
+ "Pixel format: %s%n"+
+ " (server default: %s)%n"+
+ "Requested encoding: %s%n"+
+ "Last used encoding: %s%n"+
+ "Line speed estimate: %d kbit/s%n"+
+ "Protocol version: %d.%d%n"+
+ "Security method: %s [%s]%n");
+ String infoText =
+ String.format(info, cp.name(),
+ sock.getPeerName(), sock.getPeerPort(),
+ cp.width, cp.height,
+ cp.pf().print(),
+ serverPF.print(),
+ Encodings.encodingName(currentEncoding),
+ Encodings.encodingName(lastServerEncoding),
+ sock.inStream().kbitsPerSecond(),
+ cp.majorVersion, cp.minorVersion,
+ Security.secTypeName(csecurity.getType()),
+ csecurity.description());
+
+ return infoText;
}
- // deleteWindow() is called when the user closes the desktop or menu windows.
+ // The RFB core is not properly asynchronous, so it calls this callback
+ // whenever it needs to block to wait for more data. Since FLTK is
+ // monitoring the socket, we just make sure FLTK gets to run.
- void deleteWindow() {
- if (viewport != null)
- viewport.dispose();
- viewport = null;
- }
-
- // blockCallback() is called when reading from the socket would block.
public void blockCallback() {
try {
synchronized(this) {
@@ -196,7 +214,7 @@
public final boolean getUserPasswd(StringBuffer user, StringBuffer passwd) {
String title = ("VNC Authentication ["
+csecurity.description() + "]");
- String passwordFileStr = viewer.passwordFile.getValue();
+ String passwordFileStr = passwordFile.getValue();
PasswdDialog dlg;
if (user == null && !passwordFileStr.equals("")) {
@@ -222,16 +240,16 @@
if (user == null) {
dlg = new PasswdDialog(title, (user == null), (passwd == null));
} else {
- if ((passwd == null) && viewer.sendLocalUsername.getValue()) {
+ if ((passwd == null) && sendLocalUsername.getValue()) {
user.append((String)System.getProperties().get("user.name"));
return true;
}
- dlg = new PasswdDialog(title, viewer.sendLocalUsername.getValue(),
+ dlg = new PasswdDialog(title, sendLocalUsername.getValue(),
(passwd == null));
}
- if (!dlg.showDialog()) return false;
+ dlg.showDialog();
if (user != null) {
- if (viewer.sendLocalUsername.getValue()) {
+ if (sendLocalUsername.getValue()) {
user.append((String)System.getProperties().get("user.name"));
} else {
user.append(dlg.userEntry.getText());
@@ -242,23 +260,24 @@
return true;
}
- // CConnection callback methods
+ ////////////////////// CConnection callback methods //////////////////////
// serverInit() is called when the serverInit message has been received. At
// this point we create the desktop window and display it. We also tell the
// server the pixel format and encodings to use and request the first update.
- public void serverInit() {
+ public void serverInit()
+ {
super.serverInit();
// If using AutoSelect with old servers, start in FullColor
// mode. See comment in autoSelectFormatAndEncoding.
- if (cp.beforeVersion(3, 8) && autoSelect)
- fullColour = true;
+ if (cp.beforeVersion(3, 8) && autoSelect.getValue())
+ fullColor.setParam(true);
serverPF = cp.pf();
- desktop = new DesktopWindow(cp.width, cp.height, serverPF, this);
- fullColourPF = desktop.getPreferredPF();
+ desktop = new DesktopWindow(cp.width, cp.height, cp.name(), serverPF, this);
+ fullColorPF = desktop.getPreferredPF();
// Force a switch to the format and encoding we'd like
formatChange = true; encodingChange = true;
@@ -269,71 +288,22 @@
// This initial update request is a bit of a corner case, so we need
// to help out setting the correct format here.
assert(pendingPFChange);
- desktop.setServerPF(pendingPF);
cp.setPF(pendingPF);
pendingPFChange = false;
-
- if (viewer.embed.getValue()) {
- setupEmbeddedFrame();
- } else {
- recreateViewport();
- }
- }
-
- void setupEmbeddedFrame() {
- UIManager.getDefaults().put("ScrollPane.ancestorInputMap",
- new UIDefaults.LazyInputMap(new Object[]{}));
- JScrollPane sp = new JScrollPane();
- sp.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
- sp.getViewport().setBackground(Color.BLACK);
- InputMap im = sp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
- int ctrlAltShiftMask = Event.SHIFT_MASK | Event.CTRL_MASK | Event.ALT_MASK;
- if (im != null) {
- im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, ctrlAltShiftMask),
- "unitScrollUp");
- im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, ctrlAltShiftMask),
- "unitScrollDown");
- im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, ctrlAltShiftMask),
- "unitScrollLeft");
- im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, ctrlAltShiftMask),
- "unitScrollRight");
- im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, ctrlAltShiftMask),
- "scrollUp");
- im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, ctrlAltShiftMask),
- "scrollDown");
- im.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, ctrlAltShiftMask),
- "scrollLeft");
- im.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, ctrlAltShiftMask),
- "scrollRight");
- }
- sp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
- sp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
- desktop.setViewport(sp.getViewport());
- viewer.getContentPane().removeAll();
- viewer.add(sp);
- viewer.addFocusListener(new FocusAdapter() {
- public void focusGained(FocusEvent e) {
- if (desktop.isAncestorOf(viewer))
- desktop.requestFocus();
- }
- public void focusLost(FocusEvent e) {
- releaseDownKeys();
- }
- });
- viewer.validate();
- desktop.requestFocus();
}
// setDesktopSize() is called when the desktop size changes (including when
// it is set initially).
- public void setDesktopSize(int w, int h) {
+ public void setDesktopSize(int w, int h)
+ {
super.setDesktopSize(w, h);
resizeFramebuffer();
}
// setExtendedDesktopSize() is a more advanced version of setDesktopSize()
public void setExtendedDesktopSize(int reason, int result, int w, int h,
- ScreenSet layout) {
+ ScreenSet layout)
+ {
super.setExtendedDesktopSize(reason, result, w, h, layout);
if ((reason == screenTypes.reasonClient) &&
@@ -346,26 +316,30 @@
}
// clientRedirect() migrates the client to another host/port
- public void clientRedirect(int port, String host,
- String x509subject) {
+ public void clientRedirect(int port, String host, String x509subject)
+ {
try {
sock.close();
- setServerPort(port);
sock = new TcpSocket(host, port);
vlog.info("Redirected to "+host+":"+port);
- VncViewer.newViewer(viewer, sock, true);
+ setServerName(host);
+ setServerPort(port);
+ sock.inStream().setBlockCallback(this);
+ setStreams(sock.inStream(), sock.outStream());
+ if (desktop != null)
+ desktop.dispose();
+ initialiseProtocol();
} catch (java.lang.Exception e) {
throw new Exception(e.getMessage());
}
}
// setName() is called when the desktop name changes
- public void setName(String name) {
+ public void setName(String name)
+ {
super.setName(name);
-
- if (viewport != null) {
- viewport.setTitle(name+" - TigerVNC");
- }
+ if (desktop != null)
+ desktop.setName(name);
}
// framebufferUpdateStart() is called at the beginning of an update.
@@ -374,10 +348,23 @@
// one.
public void framebufferUpdateStart()
{
+ ModifiablePixelBuffer pb;
+ PlatformPixelBuffer ppb;
+
+ super.framebufferUpdateStart();
+
// Note: This might not be true if sync fences are supported
pendingUpdate = false;
requestNewUpdate();
+
+ // We might still be rendering the previous update
+ pb = getFramebuffer();
+ assert(pb != null);
+ ppb = (PlatformPixelBuffer)pb;
+ assert(ppb != null);
+
+ //FIXME
}
// framebufferUpdateEnd() is called at the end of an update.
@@ -386,110 +373,69 @@
// appropriately, and then request another incremental update.
public void framebufferUpdateEnd()
{
+ super.framebufferUpdateEnd();
desktop.updateWindow();
if (firstUpdate) {
- int width, height;
-
// We need fences to make extra update requests and continuous
// updates "safe". See fence() for the next step.
if (cp.supportsFence)
writer().writeFence(fenceTypes.fenceFlagRequest | fenceTypes.fenceFlagSyncNext, 0, null);
- if (cp.supportsSetDesktopSize &&
- viewer.desktopSize.getValue() != null &&
- viewer.desktopSize.getValue().split("x").length == 2) {
- width = Integer.parseInt(viewer.desktopSize.getValue().split("x")[0]);
- height = Integer.parseInt(viewer.desktopSize.getValue().split("x")[1]);
- ScreenSet layout;
-
- layout = cp.screenLayout;
-
- if (layout.num_screens() == 0)
- layout.add_screen(new Screen());
- else if (layout.num_screens() != 1) {
-
- while (true) {
- Iterator<Screen> iter = layout.screens.iterator();
- Screen screen = (Screen)iter.next();
-
- if (!iter.hasNext())
- break;
-
- layout.remove_screen(screen.id);
- }
- }
-
- Screen screen0 = (Screen)layout.screens.iterator().next();
- screen0.dimensions.tl.x = 0;
- screen0.dimensions.tl.y = 0;
- screen0.dimensions.br.x = width;
- screen0.dimensions.br.y = height;
-
- writer().writeSetDesktopSize(width, height, layout);
- }
-
firstUpdate = false;
}
// A format change has been scheduled and we are now past the update
// with the old format. Time to active the new one.
if (pendingPFChange) {
- desktop.setServerPF(pendingPF);
cp.setPF(pendingPF);
pendingPFChange = false;
}
// Compute new settings based on updated bandwidth values
- if (autoSelect)
+ if (autoSelect.getValue())
autoSelectFormatAndEncoding();
}
// The rest of the callbacks are fairly self-explanatory...
- public void setColourMapEntries(int firstColour, int nColours, int[] rgbs) {
- desktop.setColourMapEntries(firstColour, nColours, rgbs);
+ public void setColourMapEntries(int firstColor, int nColors, int[] rgbs)
+ {
+ vlog.error("Invalid SetColourMapEntries from server!");
}
- public void bell() {
- if (viewer.acceptBell.getValue())
+ public void bell()
+ {
+ if (acceptBell.getValue())
desktop.getToolkit().beep();
}
- public void serverCutText(String str, int len) {
- if (viewer.acceptClipboard.getValue())
- clipboardDialog.serverCutText(str, len);
+ public void serverCutText(String str, int len)
+ {
+ StringSelection buffer;
+
+ if (!acceptClipboard.getValue())
+ return;
+
+ ClipboardDialog.serverCutText(str);
}
- // We start timing on beginRect and stop timing on endRect, to
- // avoid skewing the bandwidth estimation as a result of the server
- // being slow or the network having high latency
- public void beginRect(Rect r, int encoding) {
+ public void dataRect(Rect r, int encoding)
+ {
sock.inStream().startTiming();
- if (encoding != Encodings.encodingCopyRect) {
- lastServerEncoding = encoding;
- }
- }
- public void endRect(Rect r, int encoding) {
+ if (encoding != Encodings.encodingCopyRect)
+ lastServerEncoding = encoding;
+
+ super.dataRect(r, encoding);
+
sock.inStream().stopTiming();
}
- public void fillRect(Rect r, int p) {
- desktop.fillRect(r.tl.x, r.tl.y, r.width(), r.height(), p);
- }
-
- public void imageRect(Rect r, Object p) {
- desktop.imageRect(r.tl.x, r.tl.y, r.width(), r.height(), p);
- }
-
- public void copyRect(Rect r, int sx, int sy) {
- desktop.copyRect(r.tl.x, r.tl.y, r.width(), r.height(), sx, sy);
- }
-
public void setCursor(int width, int height, Point hotspot,
- int[] data, byte[] mask) {
+ byte[] data, byte[] mask)
+ {
desktop.setCursor(width, height, hotspot, data, mask);
}
@@ -524,11 +470,11 @@
pf.read(memStream);
- desktop.setServerPF(pf);
cp.setPF(pf);
}
}
+ ////////////////////// Internal methods //////////////////////
private void resizeFramebuffer()
{
if (desktop == null)
@@ -537,78 +483,7 @@
if (continuousUpdates)
writer().writeEnableContinuousUpdates(true, 0, 0, cp.width, cp.height);
- if ((cp.width == 0) && (cp.height == 0))
- return;
- if ((desktop.width() == cp.width) && (desktop.height() == cp.height))
- return;
-
- desktop.resize();
- if (viewer.embed.getValue()) {
- setupEmbeddedFrame();
- } else {
- recreateViewport();
- }
- }
-
- public void setEmbeddedFeatures(boolean s) {
- menu.restore.setEnabled(s);
- menu.minimize.setEnabled(s);
- menu.maximize.setEnabled(s);
- menu.fullScreen.setEnabled(s);
- menu.newConn.setEnabled(s);
- options.fullScreen.setEnabled(s);
- options.fullScreenAllMonitors.setEnabled(s);
- options.scalingFactor.setEnabled(s);
- }
-
- // recreateViewport() recreates our top-level window. This seems to be
- // better than attempting to resize the existing window, at least with
- // various X window managers.
-
- public void recreateViewport() {
- if (viewer.embed.getValue())
- return;
- if (viewport != null) viewport.dispose();
- viewport = new Viewport(cp.name(), this);
- viewport.setUndecorated(fullScreen);
- desktop.setViewport(viewport.getViewport());
- reconfigureViewport();
- if ((cp.width > 0) && (cp.height > 0))
- viewport.setVisible(true);
- desktop.requestFocusInWindow();
- }
-
- private void reconfigureViewport() {
- Dimension dpySize = viewport.getScreenSize();
- int w = desktop.scaledWidth;
- int h = desktop.scaledHeight;
- if (fullScreen) {
- if (!viewer.fullScreenAllMonitors.getValue())
- viewport.setExtendedState(JFrame.MAXIMIZED_BOTH);
- viewport.setBounds(viewport.getScreenBounds());
- if (!viewer.fullScreenAllMonitors.getValue())
- Viewport.setFullScreenWindow(viewport);
- } else {
- int wmDecorationWidth = viewport.getInsets().left + viewport.getInsets().right;
- int wmDecorationHeight = viewport.getInsets().top + viewport.getInsets().bottom;
- if (w + wmDecorationWidth >= dpySize.width)
- w = dpySize.width - wmDecorationWidth;
- if (h + wmDecorationHeight >= dpySize.height)
- h = dpySize.height - wmDecorationHeight;
- if (viewport.getExtendedState() == JFrame.MAXIMIZED_BOTH) {
- w = viewport.getSize().width;
- h = viewport.getSize().height;
- int x = viewport.getLocation().x;
- int y = viewport.getLocation().y;
- viewport.setGeometry(x, y, w, h);
- } else {
- int x = (dpySize.width - w - wmDecorationWidth) / 2;
- int y = (dpySize.height - h - wmDecorationHeight)/2;
- viewport.setExtendedState(JFrame.NORMAL);
- viewport.setGeometry(x, y, w, h);
- }
- Viewport.setFullScreenWindow(null);
- }
+ desktop.resizeFramebuffer(cp.width, cp.height);
}
// autoSelectFormatAndEncoding() chooses the format and encoding appropriate
@@ -626,11 +501,12 @@
// Note: The system here is fairly arbitrary and should be replaced
// with something more intelligent at the server end.
//
- private void autoSelectFormatAndEncoding() {
+ private void autoSelectFormatAndEncoding()
+ {
long kbitsPerSecond = sock.inStream().kbitsPerSecond();
long timeWaited = sock.inStream().timeWaited();
- boolean newFullColour = fullColour;
- int newQualityLevel = cp.qualityLevel;
+ boolean newFullColor = fullColor.getValue();
+ int newQualityLevel = qualityLevel.getValue();
// Always use Tight
if (currentEncoding != Encodings.encodingTight) {
@@ -643,17 +519,17 @@
return;
// Select appropriate quality level
- if (!cp.noJpeg) {
+ if (!noJpeg.getValue()) {
if (kbitsPerSecond > 16000)
newQualityLevel = 8;
else
newQualityLevel = 6;
- if (newQualityLevel != cp.qualityLevel) {
+ if (newQualityLevel != qualityLevel.getValue()) {
vlog.info("Throughput "+kbitsPerSecond+
" kbit/s - changing to quality "+newQualityLevel);
cp.qualityLevel = newQualityLevel;
- viewer.qualityLevel.setParam(Integer.toString(newQualityLevel));
+ qualityLevel.setParam(newQualityLevel);
encodingChange = true;
}
}
@@ -670,14 +546,24 @@
}
// Select best color level
- newFullColour = (kbitsPerSecond > 256);
- if (newFullColour != fullColour) {
+ newFullColor = (kbitsPerSecond > 256);
+ if (newFullColor != fullColor.getValue()) {
vlog.info("Throughput "+kbitsPerSecond+
" kbit/s - full color is now "+
- (newFullColour ? "enabled" : "disabled"));
- fullColour = newFullColour;
+ (newFullColor ? "enabled" : "disabled"));
+ fullColor.setParam(newFullColor);
formatChange = true;
- forceNonincremental = true;
+ }
+ }
+
+ // checkEncodings() sends a setEncodings message if one is needed.
+ private void checkEncodings()
+ {
+ if (encodingChange && (writer() != null)) {
+ vlog.info("Using " + Encodings.encodingName(currentEncoding) +
+ " encoding");
+ writer().writeSetEncodings(currentEncoding, true);
+ encodingChange = false;
}
}
@@ -691,15 +577,15 @@
/* Catch incorrect requestNewUpdate calls */
assert(!pendingUpdate || supportsSyncFence);
- if (fullColour) {
- pf = fullColourPF;
+ if (fullColor.getValue()) {
+ pf = fullColorPF;
} else {
- if (lowColourLevel == 0) {
- pf = verylowColourPF;
- } else if (lowColourLevel == 1) {
- pf = lowColourPF;
+ if (lowColorLevel.getValue() == 0) {
+ pf = verylowColorPF;
+ } else if (lowColorLevel.getValue() == 1) {
+ pf = lowColorPF;
} else {
- pf = mediumColourPF;
+ pf = mediumColorPF;
}
}
@@ -739,6 +625,60 @@
forceNonincremental = false;
}
+ public void handleOptions()
+ {
+
+ // Checking all the details of the current set of encodings is just
+ // a pain. Assume something has changed, as resending the encoding
+ // list is cheap. Avoid overriding what the auto logic has selected
+ // though.
+ if (!autoSelect.getValue()) {
+ int encNum = Encodings.encodingNum(preferredEncoding.getValue());
+
+ if (encNum != -1)
+ this.currentEncoding = encNum;
+ }
+
+ this.cp.supportsLocalCursor = true;
+
+ if (customCompressLevel.getValue())
+ this.cp.compressLevel = compressLevel.getValue();
+ else
+ this.cp.compressLevel = -1;
+
+ if (!noJpeg.getValue() && !autoSelect.getValue())
+ this.cp.qualityLevel = qualityLevel.getValue();
+ else
+ this.cp.qualityLevel = -1;
+
+ this.encodingChange = true;
+
+ // Format changes refreshes the entire screen though and are therefore
+ // very costly. It's probably worth the effort to see if it is necessary
+ // here.
+ PixelFormat pf;
+
+ if (fullColor.getValue()) {
+ pf = fullColorPF;
+ } else {
+ if (lowColorLevel.getValue() == 0)
+ pf = verylowColorPF;
+ else if (lowColorLevel.getValue() == 1)
+ pf = lowColorPF;
+ else
+ pf = mediumColorPF;
+ }
+
+ if (!pf.equal(this.cp.pf())) {
+ this.formatChange = true;
+
+ // Without fences, we cannot safely trigger an update request directly
+ // but must wait for the next update to arrive.
+ if (this.supportsSyncFence)
+ this.requestNewUpdate();
+ }
+
+ }
////////////////////////////////////////////////////////////////////
// The following methods are all called from the GUI thread
@@ -746,14 +686,12 @@
// close() shuts down the socket, thus waking up the RFB thread.
public void close() {
if (closeListener != null) {
- viewer.embed.setParam(true);
- if (VncViewer.nViewers == 1) {
- JFrame f = (JFrame)JOptionPane.getFrameForComponent(viewer);
- if (f != null)
- f.dispatchEvent(new WindowEvent(f, WindowEvent.WINDOW_CLOSING));
- }
+ embed.setParam(true);
+ JFrame f =
+ (JFrame)SwingUtilities.getAncestorOfClass(JFrame.class, desktop);
+ if (f != null)
+ f.dispatchEvent(new WindowEvent(f, WindowEvent.WINDOW_CLOSING));
}
- deleteWindow();
shuttingDown = true;
try {
if (sock != null)
@@ -763,51 +701,10 @@
}
}
- // Menu callbacks. These are guaranteed only to be called after serverInit()
- // has been called, since the menu is only accessible from the DesktopWindow
-
- private void initMenu() {
- menu = new F8Menu(this);
- }
-
- void showMenu(int x, int y) {
- String os = System.getProperty("os.name");
- if (os.startsWith("Windows"))
- com.sun.java.swing.plaf.windows.WindowsLookAndFeel.setMnemonicHidden(false);
- menu.show(desktop, x, y);
- }
-
- void showAbout() {
- String pkgDate = "";
- String pkgTime = "";
- try {
- Manifest manifest = new Manifest(VncViewer.timestamp);
- Attributes attributes = manifest.getMainAttributes();
- pkgDate = attributes.getValue("Package-Date");
- pkgTime = attributes.getValue("Package-Time");
- } catch (java.lang.Exception e) { }
-
- Window fullScreenWindow = Viewport.getFullScreenWindow();
- if (fullScreenWindow != null)
- Viewport.setFullScreenWindow(null);
- String msg =
- String.format(VncViewer.aboutText, VncViewer.version, VncViewer.build,
- VncViewer.buildDate, VncViewer.buildTime);
- JOptionPane op =
- new JOptionPane(msg, JOptionPane.INFORMATION_MESSAGE,
- JOptionPane.DEFAULT_OPTION, VncViewer.logoIcon);
- JDialog dlg = op.createDialog(desktop, "About TigerVNC Viewer for Java");
- dlg.setIconImage(VncViewer.frameIcon);
- dlg.setAlwaysOnTop(true);
- dlg.setVisible(true);
- if (fullScreenWindow != null)
- Viewport.setFullScreenWindow(fullScreenWindow);
- }
-
void showInfo() {
- Window fullScreenWindow = Viewport.getFullScreenWindow();
+ Window fullScreenWindow = DesktopWindow.getFullScreenWindow();
if (fullScreenWindow != null)
- Viewport.setFullScreenWindow(null);
+ DesktopWindow.setFullScreenWindow(null);
String info = new String("Desktop name: %s%n"+
"Host: %s:%d%n"+
"Size: %dx%d%n"+
@@ -822,7 +719,7 @@
String.format(info, cp.name(),
sock.getPeerName(), sock.getPeerPort(),
cp.width, cp.height,
- desktop.getPF().print(),
+ cp.pf().print(),
serverPF.print(),
Encodings.encodingName(currentEncoding),
Encodings.encodingName(lastServerEncoding),
@@ -837,7 +734,7 @@
dlg.setAlwaysOnTop(true);
dlg.setVisible(true);
if (fullScreenWindow != null)
- Viewport.setFullScreenWindow(fullScreenWindow);
+ DesktopWindow.setFullScreenWindow(fullScreenWindow);
}
public void refresh() {
@@ -845,516 +742,6 @@
pendingUpdate = true;
}
-
- // OptionsDialogCallback. setOptions() sets the options dialog's checkboxes
- // etc to reflect our flags. getOptions() sets our flags according to the
- // options dialog's checkboxes. They are both called from the GUI thread.
- // Some of the flags are also accessed by the RFB thread. I believe that
- // reading and writing boolean and int values in java is atomic, so there is
- // no need for synchronization.
-
- public void setOptions() {
- int digit;
- options.autoSelect.setSelected(autoSelect);
- options.fullColour.setSelected(fullColour);
- options.veryLowColour.setSelected(!fullColour && lowColourLevel == 0);
- options.lowColour.setSelected(!fullColour && lowColourLevel == 1);
- options.mediumColour.setSelected(!fullColour && lowColourLevel == 2);
- options.tight.setSelected(currentEncoding == Encodings.encodingTight);
- options.zrle.setSelected(currentEncoding == Encodings.encodingZRLE);
- options.hextile.setSelected(currentEncoding == Encodings.encodingHextile);
- options.raw.setSelected(currentEncoding == Encodings.encodingRaw);
-
- options.customCompressLevel.setSelected(viewer.customCompressLevel.getValue());
- digit = 0 + viewer.compressLevel.getValue();
- if (digit >= 0 && digit <= 9) {
- options.compressLevel.setSelectedItem(digit);
- } else {
- options.compressLevel.setSelectedItem(Integer.parseInt(viewer.compressLevel.getDefaultStr()));
- }
- options.noJpeg.setSelected(!viewer.noJpeg.getValue());
- digit = 0 + viewer.qualityLevel.getValue();
- if (digit >= 0 && digit <= 9) {
- options.qualityLevel.setSelectedItem(digit);
- } else {
- options.qualityLevel.setSelectedItem(Integer.parseInt(viewer.qualityLevel.getDefaultStr()));
- }
-
- options.viewOnly.setSelected(viewer.viewOnly.getValue());
- options.acceptClipboard.setSelected(viewer.acceptClipboard.getValue());
- options.sendClipboard.setSelected(viewer.sendClipboard.getValue());
- options.menuKey.setSelectedItem(KeyEvent.getKeyText(MenuKey.getMenuKeyCode()));
- options.sendLocalUsername.setSelected(viewer.sendLocalUsername.getValue());
-
- if (state() == RFBSTATE_NORMAL) {
- options.shared.setEnabled(false);
- options.secVeNCrypt.setEnabled(false);
- options.encNone.setEnabled(false);
- options.encTLS.setEnabled(false);
- options.encX509.setEnabled(false);
- options.x509ca.setEnabled(false);
- options.caButton.setEnabled(false);
- options.x509crl.setEnabled(false);
- options.crlButton.setEnabled(false);
- options.secIdent.setEnabled(false);
- options.secNone.setEnabled(false);
- options.secVnc.setEnabled(false);
- options.secPlain.setEnabled(false);
- options.sendLocalUsername.setEnabled(false);
- options.cfLoadButton.setEnabled(false);
- options.cfSaveAsButton.setEnabled(true);
- options.sshTunnel.setEnabled(false);
- options.sshUseGateway.setEnabled(false);
- options.sshUser.setEnabled(false);
- options.sshHost.setEnabled(false);
- options.sshPort.setEnabled(false);
- options.sshUseExt.setEnabled(false);
- options.sshClient.setEnabled(false);
- options.sshClientBrowser.setEnabled(false);
- options.sshArgsDefault.setEnabled(false);
- options.sshArgsCustom.setEnabled(false);
- options.sshArguments.setEnabled(false);
- options.sshConfig.setEnabled(false);
- options.sshConfigBrowser.setEnabled(false);
- options.sshKeyFile.setEnabled(false);
- options.sshKeyFileBrowser.setEnabled(false);
- } else {
- options.shared.setSelected(viewer.shared.getValue());
- options.sendLocalUsername.setSelected(viewer.sendLocalUsername.getValue());
- options.cfSaveAsButton.setEnabled(false);
- if (viewer.tunnel.getValue() || viewer.via.getValue() != null)
- options.sshTunnel.setSelected(true);
- if (viewer.via.getValue() != null)
- options.sshUseGateway.setSelected(true);
- options.sshUser.setText(Tunnel.getSshUser(this));
- options.sshHost.setText(Tunnel.getSshHost(this));
- options.sshPort.setText(Integer.toString(Tunnel.getSshPort(this)));
- options.sshUseExt.setSelected(viewer.extSSH.getValue());
- File client = new File(viewer.extSSHClient.getValue());
- if (client.exists() && client.canRead())
- options.sshClient.setText(client.getAbsolutePath());
- if (viewer.extSSHArgs.getValue() == null) {
- options.sshArgsDefault.setSelected(true);
- options.sshArguments.setText("");
- } else {
- options.sshArgsCustom.setSelected(true);
- options.sshArguments.setText(viewer.extSSHArgs.getValue());
- }
- File config = new File(viewer.sshConfig.getValue());
- if (config.exists() && config.canRead())
- options.sshConfig.setText(config.getAbsolutePath());
- options.sshKeyFile.setText(Tunnel.getSshKeyFile(this));
-
- /* Process non-VeNCrypt sectypes */
- java.util.List<Integer> secTypes = new ArrayList<Integer>();
- secTypes = Security.GetEnabledSecTypes();
- for (Iterator<Integer> i = secTypes.iterator(); i.hasNext();) {
- switch ((Integer)i.next()) {
- case Security.secTypeVeNCrypt:
- options.secVeNCrypt.setSelected(UserPreferences.getBool("viewer", "secVeNCrypt", true));
- break;
- case Security.secTypeNone:
- options.encNone.setSelected(true);
- options.secNone.setSelected(UserPreferences.getBool("viewer", "secTypeNone", true));
- break;
- case Security.secTypeVncAuth:
- options.encNone.setSelected(true);
- options.secVnc.setSelected(UserPreferences.getBool("viewer", "secTypeVncAuth", true));
- break;
- }
- }
-
- /* Process VeNCrypt subtypes */
- if (options.secVeNCrypt.isSelected()) {
- java.util.List<Integer> secTypesExt = new ArrayList<Integer>();
- secTypesExt = Security.GetEnabledExtSecTypes();
- for (Iterator<Integer> iext = secTypesExt.iterator(); iext.hasNext();) {
- switch ((Integer)iext.next()) {
- case Security.secTypePlain:
- options.encNone.setSelected(UserPreferences.getBool("viewer", "encNone", true));
- options.secPlain.setSelected(UserPreferences.getBool("viewer", "secPlain", true));
- break;
- case Security.secTypeIdent:
- options.encNone.setSelected(UserPreferences.getBool("viewer", "encNone", true));
- options.secIdent.setSelected(UserPreferences.getBool("viewer", "secIdent", true));
- break;
- case Security.secTypeTLSNone:
- options.encTLS.setSelected(UserPreferences.getBool("viewer", "encTLS", true));
- options.secNone.setSelected(UserPreferences.getBool("viewer", "secNone", true));
- break;
- case Security.secTypeTLSVnc:
- options.encTLS.setSelected(UserPreferences.getBool("viewer", "encTLS", true));
- options.secVnc.setSelected(UserPreferences.getBool("viewer", "secVnc", true));
- break;
- case Security.secTypeTLSPlain:
- options.encTLS.setSelected(UserPreferences.getBool("viewer", "encTLS", true));
- options.secPlain.setSelected(UserPreferences.getBool("viewer", "secPlain", true));
- break;
- case Security.secTypeTLSIdent:
- options.encTLS.setSelected(UserPreferences.getBool("viewer", "encTLS", true));
- options.secIdent.setSelected(UserPreferences.getBool("viewer", "secIdent", true));
- break;
- case Security.secTypeX509None:
- options.encX509.setSelected(UserPreferences.getBool("viewer", "encX509", true));
- options.secNone.setSelected(UserPreferences.getBool("viewer", "secNone", true));
- break;
- case Security.secTypeX509Vnc:
- options.encX509.setSelected(UserPreferences.getBool("viewer", "encX509", true));
- options.secVnc.setSelected(UserPreferences.getBool("viewer", "secVnc", true));
- break;
- case Security.secTypeX509Plain:
- options.encX509.setSelected(UserPreferences.getBool("viewer", "encX509", true));
- options.secPlain.setSelected(UserPreferences.getBool("viewer", "secPlain", true));
- break;
- case Security.secTypeX509Ident:
- options.encX509.setSelected(UserPreferences.getBool("viewer", "encX509", true));
- options.secIdent.setSelected(UserPreferences.getBool("viewer", "secIdent", true));
- break;
- }
- }
- }
- File caFile = new File(viewer.x509ca.getValue());
- if (caFile.exists() && caFile.canRead())
- options.x509ca.setText(caFile.getAbsolutePath());
- File crlFile = new File(viewer.x509crl.getValue());
- if (crlFile.exists() && crlFile.canRead())
- options.x509crl.setText(crlFile.getAbsolutePath());
- options.encNone.setEnabled(options.secVeNCrypt.isSelected());
- options.encTLS.setEnabled(options.secVeNCrypt.isSelected());
- options.encX509.setEnabled(options.secVeNCrypt.isSelected());
- options.x509ca.setEnabled(options.secVeNCrypt.isSelected() &&
- options.encX509.isSelected());
- options.caButton.setEnabled(options.secVeNCrypt.isSelected() &&
- options.encX509.isSelected());
- options.x509crl.setEnabled(options.secVeNCrypt.isSelected() &&
- options.encX509.isSelected());
- options.crlButton.setEnabled(options.secVeNCrypt.isSelected() &&
- options.encX509.isSelected());
- options.secIdent.setEnabled(options.secVeNCrypt.isSelected());
- options.secPlain.setEnabled(options.secVeNCrypt.isSelected());
- options.sendLocalUsername.setEnabled(options.secPlain.isSelected()||
- options.secIdent.isSelected());
- options.sshTunnel.setEnabled(true);
- options.sshUseGateway.setEnabled(options.sshTunnel.isSelected());
- options.sshUser.setEnabled(options.sshTunnel.isSelected() &&
- options.sshUseGateway.isEnabled() &&
- options.sshUseGateway.isSelected());
- options.sshHost.setEnabled(options.sshTunnel.isSelected() &&
- options.sshUseGateway.isEnabled() &&
- options.sshUseGateway.isSelected());
- options.sshPort.setEnabled(options.sshTunnel.isSelected() &&
- options.sshUseGateway.isEnabled() &&
- options.sshUseGateway.isSelected());
- options.sshUseExt.setEnabled(options.sshTunnel.isSelected());
- options.sshClient.setEnabled(options.sshTunnel.isSelected() &&
- options.sshUseExt.isEnabled() &&
- options.sshUseExt.isSelected());
- options.sshClientBrowser.setEnabled(options.sshTunnel.isSelected() &&
- options.sshUseExt.isEnabled() &&
- options.sshUseExt.isSelected());
- options.sshArgsDefault.setEnabled(options.sshTunnel.isSelected() &&
- options.sshUseExt.isEnabled() &&
- options.sshUseExt.isSelected());
- options.sshArgsCustom.setEnabled(options.sshTunnel.isSelected() &&
- options.sshUseExt.isEnabled() &&
- options.sshUseExt.isSelected());
- options.sshArguments.setEnabled(options.sshTunnel.isSelected() &&
- options.sshUseExt.isEnabled() &&
- options.sshUseExt.isSelected() &&
- options.sshArgsCustom.isSelected());
- options.sshConfig.setEnabled(options.sshTunnel.isSelected() &&
- options.sshUseExt.isEnabled() &&
- !options.sshUseExt.isSelected());
- options.sshConfigBrowser.setEnabled(options.sshTunnel.isSelected() &&
- options.sshUseExt.isEnabled() &&
- !options.sshUseExt.isSelected());
- options.sshKeyFile.setEnabled(options.sshTunnel.isSelected() &&
- options.sshUseExt.isEnabled() &&
- !options.sshUseExt.isSelected());
- options.sshKeyFileBrowser.setEnabled(options.sshTunnel.isSelected() &&
- options.sshUseExt.isEnabled() &&
- !options.sshUseExt.isSelected());
- }
-
- options.fullScreen.setSelected(fullScreen);
- options.fullScreenAllMonitors.setSelected(viewer.fullScreenAllMonitors.getValue());
- options.useLocalCursor.setSelected(viewer.useLocalCursor.getValue());
- options.acceptBell.setSelected(viewer.acceptBell.getValue());
- String scaleString = viewer.scalingFactor.getValue();
- if (scaleString.equalsIgnoreCase("Auto")) {
- options.scalingFactor.setSelectedItem("Auto");
- } else if(scaleString.equalsIgnoreCase("FixedRatio")) {
- options.scalingFactor.setSelectedItem("Fixed Aspect Ratio");
- } else {
- digit = Integer.parseInt(scaleString);
- if (digit >= 1 && digit <= 1000) {
- options.scalingFactor.setSelectedItem(digit+"%");
- } else {
- digit = Integer.parseInt(viewer.scalingFactor.getDefaultStr());
- options.scalingFactor.setSelectedItem(digit+"%");
- }
- int scaleFactor =
- Integer.parseInt(scaleString.substring(0, scaleString.length()));
- }
- if (viewer.desktopSize.getValue() != null &&
- viewer.desktopSize.getValue().split("x").length == 2) {
- options.desktopSize.setSelected(true);
- String desktopWidth = viewer.desktopSize.getValue().split("x")[0];
- options.desktopWidth.setText(desktopWidth);
- String desktopHeight = viewer.desktopSize.getValue().split("x")[1];
- options.desktopHeight.setText(desktopHeight);
- }
- }
-
- public void getOptions() {
- autoSelect = options.autoSelect.isSelected();
- if (fullColour != options.fullColour.isSelected()) {
- formatChange = true;
- forceNonincremental = true;
- }
- fullColour = options.fullColour.isSelected();
- if (!fullColour) {
- int newLowColourLevel = (options.veryLowColour.isSelected() ? 0 :
- options.lowColour.isSelected() ? 1 : 2);
- if (newLowColourLevel != lowColourLevel) {
- lowColourLevel = newLowColourLevel;
- formatChange = true;
- forceNonincremental = true;
- }
- }
- int newEncoding = (options.zrle.isSelected() ? Encodings.encodingZRLE :
- options.hextile.isSelected() ? Encodings.encodingHextile :
- options.tight.isSelected() ? Encodings.encodingTight :
- Encodings.encodingRaw);
- if (newEncoding != currentEncoding) {
- currentEncoding = newEncoding;
- encodingChange = true;
- }
-
- viewer.customCompressLevel.setParam(options.customCompressLevel.isSelected());
- if (cp.customCompressLevel != viewer.customCompressLevel.getValue()) {
- cp.customCompressLevel = viewer.customCompressLevel.getValue();
- encodingChange = true;
- }
- if (Integer.parseInt(options.compressLevel.getSelectedItem().toString()) >= 0 &&
- Integer.parseInt(options.compressLevel.getSelectedItem().toString()) <= 9) {
- viewer.compressLevel.setParam(options.compressLevel.getSelectedItem().toString());
- } else {
- viewer.compressLevel.setParam(viewer.compressLevel.getDefaultStr());
- }
- if (cp.compressLevel != viewer.compressLevel.getValue()) {
- cp.compressLevel = viewer.compressLevel.getValue();
- encodingChange = true;
- }
- viewer.noJpeg.setParam(!options.noJpeg.isSelected());
- if (cp.noJpeg != viewer.noJpeg.getValue()) {
- cp.noJpeg = viewer.noJpeg.getValue();
- encodingChange = true;
- }
- viewer.qualityLevel.setParam(options.qualityLevel.getSelectedItem().toString());
- if (cp.qualityLevel != viewer.qualityLevel.getValue()) {
- cp.qualityLevel = viewer.qualityLevel.getValue();
- encodingChange = true;
- }
- if (!options.x509ca.getText().equals(""))
- CSecurityTLS.x509ca.setParam(options.x509ca.getText());
- if (!options.x509crl.getText().equals(""))
- CSecurityTLS.x509crl.setParam(options.x509crl.getText());
- viewer.sendLocalUsername.setParam(options.sendLocalUsername.isSelected());
-
- viewer.viewOnly.setParam(options.viewOnly.isSelected());
- viewer.acceptClipboard.setParam(options.acceptClipboard.isSelected());
- viewer.sendClipboard.setParam(options.sendClipboard.isSelected());
- viewer.acceptBell.setParam(options.acceptBell.isSelected());
- String scaleString =
- options.scalingFactor.getSelectedItem().toString();
- String oldScaleFactor = viewer.scalingFactor.getValue();
- if (scaleString.equalsIgnoreCase("Fixed Aspect Ratio")) {
- scaleString = new String("FixedRatio");
- } else if (scaleString.equalsIgnoreCase("Auto")) {
- scaleString = new String("Auto");
- } else {
- scaleString=scaleString.substring(0, scaleString.length()-1);
- }
- if (!oldScaleFactor.equals(scaleString)) {
- viewer.scalingFactor.setParam(scaleString);
- if ((options.fullScreen.isSelected() == fullScreen) &&
- (desktop != null))
- recreateViewport();
- }
-
- clipboardDialog.setSendingEnabled(viewer.sendClipboard.getValue());
- viewer.menuKey.setParam(MenuKey.getMenuKeySymbols()[options.menuKey.getSelectedIndex()].name);
- F8Menu.f8.setText("Send "+KeyEvent.getKeyText(MenuKey.getMenuKeyCode()));
-
- setShared(options.shared.isSelected());
- viewer.useLocalCursor.setParam(options.useLocalCursor.isSelected());
- if (cp.supportsLocalCursor != viewer.useLocalCursor.getValue()) {
- cp.supportsLocalCursor = viewer.useLocalCursor.getValue();
- encodingChange = true;
- if (desktop != null)
- desktop.resetLocalCursor();
- }
- viewer.extSSH.setParam(options.sshUseExt.isSelected());
-
- checkEncodings();
-
- if (state() != RFBSTATE_NORMAL) {
- /* Process security types which don't use encryption */
- if (options.encNone.isSelected()) {
- if (options.secNone.isSelected())
- Security.EnableSecType(Security.secTypeNone);
- if (options.secVnc.isSelected())
- Security.EnableSecType(Security.secTypeVncAuth);
- if (options.secPlain.isSelected())
- Security.EnableSecType(Security.secTypePlain);
- if (options.secIdent.isSelected())
- Security.EnableSecType(Security.secTypeIdent);
- } else {
- Security.DisableSecType(Security.secTypeNone);
- Security.DisableSecType(Security.secTypeVncAuth);
- Security.DisableSecType(Security.secTypePlain);
- Security.DisableSecType(Security.secTypeIdent);
- }
-
- /* Process security types which use TLS encryption */
- if (options.encTLS.isSelected()) {
- if (options.secNone.isSelected())
- Security.EnableSecType(Security.secTypeTLSNone);
- if (options.secVnc.isSelected())
- Security.EnableSecType(Security.secTypeTLSVnc);
- if (options.secPlain.isSelected())
- Security.EnableSecType(Security.secTypeTLSPlain);
- if (options.secIdent.isSelected())
- Security.EnableSecType(Security.secTypeTLSIdent);
- } else {
- Security.DisableSecType(Security.secTypeTLSNone);
- Security.DisableSecType(Security.secTypeTLSVnc);
- Security.DisableSecType(Security.secTypeTLSPlain);
- Security.DisableSecType(Security.secTypeTLSIdent);
- }
-
- /* Process security types which use X509 encryption */
- if (options.encX509.isSelected()) {
- if (options.secNone.isSelected())
- Security.EnableSecType(Security.secTypeX509None);
- if (options.secVnc.isSelected())
- Security.EnableSecType(Security.secTypeX509Vnc);
- if (options.secPlain.isSelected())
- Security.EnableSecType(Security.secTypeX509Plain);
- if (options.secIdent.isSelected())
- Security.EnableSecType(Security.secTypeX509Ident);
- } else {
- Security.DisableSecType(Security.secTypeX509None);
- Security.DisableSecType(Security.secTypeX509Vnc);
- Security.DisableSecType(Security.secTypeX509Plain);
- Security.DisableSecType(Security.secTypeX509Ident);
- }
-
- /* Process *None security types */
- if (options.secNone.isSelected()) {
- if (options.encNone.isSelected())
- Security.EnableSecType(Security.secTypeNone);
- if (options.encTLS.isSelected())
- Security.EnableSecType(Security.secTypeTLSNone);
- if (options.encX509.isSelected())
- Security.EnableSecType(Security.secTypeX509None);
- } else {
- Security.DisableSecType(Security.secTypeNone);
- Security.DisableSecType(Security.secTypeTLSNone);
- Security.DisableSecType(Security.secTypeX509None);
- }
-
- /* Process *Vnc security types */
- if (options.secVnc.isSelected()) {
- if (options.encNone.isSelected())
- Security.EnableSecType(Security.secTypeVncAuth);
- if (options.encTLS.isSelected())
- Security.EnableSecType(Security.secTypeTLSVnc);
- if (options.encX509.isSelected())
- Security.EnableSecType(Security.secTypeX509Vnc);
- } else {
- Security.DisableSecType(Security.secTypeVncAuth);
- Security.DisableSecType(Security.secTypeTLSVnc);
- Security.DisableSecType(Security.secTypeX509Vnc);
- }
-
- /* Process *Plain security types */
- if (options.secPlain.isSelected()) {
- if (options.encNone.isSelected())
- Security.EnableSecType(Security.secTypePlain);
- if (options.encTLS.isSelected())
- Security.EnableSecType(Security.secTypeTLSPlain);
- if (options.encX509.isSelected())
- Security.EnableSecType(Security.secTypeX509Plain);
- } else {
- Security.DisableSecType(Security.secTypePlain);
- Security.DisableSecType(Security.secTypeTLSPlain);
- Security.DisableSecType(Security.secTypeX509Plain);
- }
-
- /* Process *Ident security types */
- if (options.secIdent.isSelected()) {
- if (options.encNone.isSelected())
- Security.EnableSecType(Security.secTypeIdent);
- if (options.encTLS.isSelected())
- Security.EnableSecType(Security.secTypeTLSIdent);
- if (options.encX509.isSelected())
- Security.EnableSecType(Security.secTypeX509Ident);
- } else {
- Security.DisableSecType(Security.secTypeIdent);
- Security.DisableSecType(Security.secTypeTLSIdent);
- Security.DisableSecType(Security.secTypeX509Ident);
- }
- if (options.sshTunnel.isSelected()) {
- if (options.sshUseGateway.isSelected()) {
- String user = options.sshUser.getText();
- String host = options.sshHost.getText();
- String port = options.sshPort.getText();
- viewer.via.setParam(user+"@"+host+":"+port);
- } else {
- viewer.tunnel.setParam(true);
- }
- }
- viewer.extSSH.setParam(options.sshUseExt.isSelected());
- viewer.extSSHClient.setParam(options.sshClient.getText());
- if (options.sshArgsCustom.isSelected())
- viewer.extSSHArgs.setParam(options.sshArguments.getText());
- viewer.sshConfig.setParam(options.sshConfig.getText());
- viewer.sshKeyFile.setParam(options.sshKeyFile.getText());
- }
- String desktopSize = (options.desktopSize.isSelected()) ?
- options.desktopWidth.getText() + "x" + options.desktopHeight.getText() : "";
- viewer.desktopSize.setParam(desktopSize);
- if (options.fullScreen.isSelected() ^ fullScreen) {
- viewer.fullScreenAllMonitors.setParam(options.fullScreenAllMonitors.isSelected());
- toggleFullScreen();
- } else {
- if (viewer.fullScreenAllMonitors.getValue() !=
- options.fullScreenAllMonitors.isSelected()) {
- viewer.fullScreenAllMonitors.setParam(options.fullScreenAllMonitors.isSelected());
- if (desktop != null)
- recreateViewport();
- } else {
- viewer.fullScreenAllMonitors.setParam(options.fullScreenAllMonitors.isSelected());
- }
- }
- }
-
- public void toggleFullScreen() {
- if (viewer.embed.getValue())
- return;
- fullScreen = !fullScreen;
- menu.fullScreen.setSelected(fullScreen);
- if (viewport != null) {
- if (!viewport.lionFSSupported()) {
- recreateViewport();
- } else {
- viewport.toggleLionFS();
- }
- }
- }
-
// writeClientCutText() is called from the clipboard dialog
public void writeClientCutText(String str, int len) {
if (state() != RFBSTATE_NORMAL || shuttingDown)
@@ -1369,11 +756,11 @@
}
public void writeKeyEvent(KeyEvent ev) {
- if (viewer.viewOnly.getValue() || shuttingDown)
+ if (viewOnly.getValue() || shuttingDown)
return;
boolean down = (ev.getID() == KeyEvent.KEY_PRESSED);
-
+
int keySym, keyCode = ev.getKeyCode();
// If neither the keyCode or keyChar are defined, then there's
@@ -1388,9 +775,9 @@
if (iter == null) {
// Note that dead keys will raise this sort of error falsely
// See https://bugs.openjdk.java.net/browse/JDK-6534883
- vlog.error("Unexpected key release of keyCode "+keyCode);
+ vlog.debug("Unexpected key release of keyCode "+keyCode);
String fmt = ev.paramString().replaceAll("%","%%");
- vlog.error(String.format(fmt.replaceAll(",","%n ")));
+ vlog.debug(String.format(fmt.replaceAll(",","%n ")));
return;
}
@@ -1474,15 +861,6 @@
break;
}
- if (cp.width != desktop.scaledWidth ||
- cp.height != desktop.scaledHeight) {
- int sx = (desktop.scaleWidthRatio == 1.00) ?
- ev.getX() : (int)Math.floor(ev.getX() / desktop.scaleWidthRatio);
- int sy = (desktop.scaleHeightRatio == 1.00) ?
- ev.getY() : (int)Math.floor(ev.getY() / desktop.scaleHeightRatio);
- ev.translatePoint(sx - ev.getX(), sy - ev.getY());
- }
-
writer().writePointerEvent(new Point(ev.getX(), ev.getY()), buttonMask);
}
@@ -1527,30 +905,13 @@
////////////////////////////////////////////////////////////////////
// The following methods are called from both RFB and GUI threads
- // checkEncodings() sends a setEncodings message if one is needed.
- private void checkEncodings() {
- if (encodingChange && (writer() != null)) {
- vlog.info("Requesting " + Encodings.encodingName(currentEncoding) +
- " encoding");
- writer().writeSetEncodings(currentEncoding, true);
- encodingChange = false;
- }
- }
-
// the following never change so need no synchronization:
-
- // viewer object is only ever accessed by the GUI thread so needs no
- // synchronization (except for one test in DesktopWindow - see comment
- // there).
- VncViewer viewer;
-
// access to desktop by different threads is specified in DesktopWindow
// the following need no synchronization:
public static UserPasswdGetter upg;
- public UserMsgBox msg;
// shuttingDown is set by the GUI thread and only ever tested by the RFB
// thread after the window has been destroyed.
@@ -1559,27 +920,21 @@
// reading and writing int and boolean is atomic in java, so no
// synchronization of the following flags is needed:
- int lowColourLevel;
-
// All menu, options, about and info stuff is done in the GUI thread (apart
// from when constructed).
- F8Menu menu;
- OptionsDialog options;
-
- // clipboard sync issues?
- ClipboardDialog clipboardDialog;
// the following are only ever accessed by the GUI thread:
int buttonMask;
+ private String serverHost;
+ private int serverPort;
private Socket sock;
protected DesktopWindow desktop;
- // FIXME: should be private
- public PixelFormat serverPF;
- private PixelFormat fullColourPF;
+ private PixelFormat serverPF;
+ private PixelFormat fullColorPF;
private boolean pendingPFChange;
private PixelFormat pendingPF;
@@ -1597,11 +952,6 @@
private boolean supportsSyncFence;
- public int menuKeyCode;
- Viewport viewport;
- private boolean fullColour;
- private boolean autoSelect;
- boolean fullScreen;
private HashMap<Integer, Integer> downKeySym;
public ActionListener closeListener = null;
diff --git a/java/com/tigervnc/vncviewer/ClipboardDialog.java b/java/com/tigervnc/vncviewer/ClipboardDialog.java
index 441846c..4a5d9f2 100644
--- a/java/com/tigervnc/vncviewer/ClipboardDialog.java
+++ b/java/com/tigervnc/vncviewer/ClipboardDialog.java
@@ -26,83 +26,125 @@
import java.nio.*;
import javax.swing.*;
import javax.swing.border.*;
+import javax.swing.event.*;
import javax.swing.text.*;
import com.tigervnc.rfb.LogWriter;
+import static com.tigervnc.vncviewer.Parameters.*;
+
class ClipboardDialog extends Dialog {
- private class VncTransferHandler extends TransferHandler {
- // Custom TransferHandler designed to limit the size of outbound
- // clipboard transfers to VncViewer.maxCutText.getValue() bytes.
- private LogWriter vlog = new LogWriter("VncTransferHandler");
+ protected static class MyJTextArea extends JTextArea {
- public void exportToClipboard(JComponent c, Clipboard clip, int a)
- throws IllegalStateException {
- if (!(c instanceof JTextComponent)) return;
- StringSelection selection =
- new StringSelection(((JTextComponent)c).getText());
- clip.setContents(selection, null);
- }
+ private class VncTransferHandler extends TransferHandler {
+ // Custom TransferHandler designed to limit the size of outbound
+ // clipboard transfers to VncViewer.maxCutText.getValue() bytes.
+ private LogWriter vlog = new LogWriter("VncTransferHandler");
- public boolean importData(JComponent c, Transferable t) {
- if (canImport(c, t.getTransferDataFlavors())) {
+ public void exportToClipboard(JComponent c, Clipboard clip, int a)
+ throws IllegalStateException {
+ if (!(c instanceof JTextComponent)) return;
+ String text = ((JTextComponent)c).getText();
try {
- DataFlavor VncFlavor = null;
- for (DataFlavor f : t.getTransferDataFlavors())
- if (f.isFlavorTextType() && f.isRepresentationClassInputStream())
- VncFlavor = f;
- if (VncFlavor == null) return false;
- Reader reader = (Reader)VncFlavor.getReaderForText(t);
- CharBuffer cbuf =
- CharBuffer.allocate(VncViewer.maxCutText.getValue());
- cbuf.limit(reader.read(cbuf.array(), 0, cbuf.length()));
- reader.close();
- if (c instanceof JTextComponent)
- ((JTextComponent)c).setText(cbuf.toString());
- return true;
- } catch (OutOfMemoryError oome) {
- vlog.error("ERROR: Too much data on local clipboard!");
- } catch (UnsupportedFlavorException ufe) {
- // Skip import
- vlog.info(ufe.toString());
- } catch (IOException ioe) {
- // Skip import
- vlog.info(ioe.toString());
+ if (text.equals((String)clip.getData(DataFlavor.stringFlavor))) return;
+ } catch (IOException e) {
+ // worst case we set the clipboard contents unnecessarily
+ vlog.info(e.toString());
+ } catch (UnsupportedFlavorException e) {
+ // worst case we set the clipboard contents unnecessarily
+ vlog.info(e.toString());
}
- }
- return false;
- }
+ StringSelection selection = new StringSelection(text);
+ clip.setContents(selection, null);
+ }
- public boolean canImport(JComponent c, DataFlavor[] flavors) {
- for (DataFlavor f : flavors)
- if (f.isFlavorTextType() && f.isRepresentationClassReader())
- return true;
- return false;
+ public boolean importData(JComponent c, Transferable t) {
+ if (canImport(c, t.getTransferDataFlavors())) {
+ try {
+ DataFlavor VncFlavor = null;
+ for (DataFlavor f : t.getTransferDataFlavors()) {
+ if (f.isMimeTypeEqual("text/plain") &&
+ f.isRepresentationClassInputStream()) {
+ VncFlavor = f;
+ break;
+ }
+ }
+ if (VncFlavor == null) return false;
+ CharBuffer cbuf =
+ CharBuffer.allocate(maxCutText.getValue());
+ Reader reader = (Reader)VncFlavor.getReaderForText(t);
+ int n = reader.read(cbuf.array(), 0, cbuf.length());
+ reader.close();
+ // reader returns -1 (EOF) for empty clipboard
+ cbuf.limit(n < 0 ? 0 : n);
+ if (c instanceof JTextComponent)
+ if (!cbuf.toString().equals(((JTextComponent)c).getText()))
+ ((JTextComponent)c).setText(cbuf.toString());
+ return true;
+ } catch (OutOfMemoryError e) {
+ vlog.error("ERROR: Too much data on local clipboard!");
+ } catch (UnsupportedFlavorException e) {
+ // Skip import
+ vlog.info(e.toString());
+ } catch (IOException e) {
+ // Skip import
+ vlog.info(e.toString());
+ }
+ }
+ return false;
+ }
+
+ public boolean canImport(JComponent c, DataFlavor[] flavors) {
+ for (DataFlavor f : flavors)
+ if (f.isMimeTypeEqual("text/plain") &&
+ f.isRepresentationClassReader())
+ return true;
+ return false;
+ }
+ }
+
+ private class MyTextListener implements DocumentListener {
+ public MyTextListener() { }
+
+ public void changedUpdate(DocumentEvent e) { }
+
+ public void insertUpdate(DocumentEvent e) {
+ if (!listen) return;
+ String text = textArea.getText();
+ if (sendClipboard.getValue())
+ VncViewer.cc.writeClientCutText(text, text.length());
+ }
+
+ public void removeUpdate(DocumentEvent e) { }
+ }
+
+ public MyJTextArea() {
+ super();
+ setTransferHandler(new VncTransferHandler());
+ getDocument().addDocumentListener(new MyTextListener());
+ // If the textArea can receive the focus, then text within the textArea
+ // can be selected. On platforms that don't support separate selection
+ // and clipboard buffers, this triggers a replacement of the textAra's
+ // contents with the selected text.
+ setFocusable(false);
+ setLineWrap(false);
+ setWrapStyleWord(true);
}
}
- public ClipboardDialog(CConn cc_) {
+ public ClipboardDialog() {
super(false);
setTitle("VNC Clipboard Viewer");
setPreferredSize(new Dimension(640, 480));
- addWindowFocusListener(new WindowAdapter() {
+ addWindowFocusListener(new WindowFocusListener() {
// Necessary to ensure that updates from the system clipboard
- // still occur when the ClipboardDialog has the focus.
- public void WindowGainedFocus(WindowEvent e) {
+ // are propagated to the textArea when the dialog is visible.
+ public void windowGainedFocus(WindowEvent e) {
clientCutText();
}
+ public void windowLostFocus(WindowEvent e) { }
});
- cc = cc_;
- textArea = new JTextArea();
- textArea.setTransferHandler(new VncTransferHandler());
- // If the textArea can receive the focus, then text within the textArea
- // can be selected. On platforms that don't support separate selection
- // and clipboard buffers, this triggers a replacement of the textAra's
- // contents with the selected text.
- textArea.setFocusable(false);
- textArea.setLineWrap(false);
- textArea.setWrapStyleWord(true);
JScrollPane sp = new JScrollPane(textArea);
getContentPane().add(sp, BorderLayout.CENTER);
// button panel placed below the scrollpane
@@ -118,19 +160,40 @@
pack();
}
- public void serverCutText(String str, int len) {
- textArea.setText(str);
- textArea.copy();
+ public static void showDialog(Container c) {
+ if (dialog == null)
+ dialog = new ClipboardDialog();
+ dialog.show(c);
}
- public void clientCutText() {
- int hc = textArea.getText().hashCode();
+ public void show(Container c) {
+ super.showDialog(c);
+ }
+
+ public void endDialog() {
+ super.endDialog();
+ dialog.dispose();
+ }
+
+ public static void serverCutText(String str) {
+ if (textArea.getText().equals(str))
+ return;
+ // Update the text area with incoming serverCutText. We need to diable
+ // the DocumentListener temporarily to prevent an clientCutText msg from
+ // being sent back to the server when the textArea is updated.
+ listen = false;
+ textArea.setText(str);
+ textArea.copy();
+ listen = true;
+ }
+
+ public static void clientCutText() {
+ // Update the textArea with the current contents of the system clipboard.
+ // The TransferHandler ensures that the textArea's contents are only
+ // changed when they differ from the clipboard's. If the textArea is
+ // updated, the DocumentListener will trigger an RFB clientCutText msg.
textArea.paste();
textArea.setCaretPosition(0);
- String text = textArea.getText();
- if (cc.viewer.sendClipboard.getValue())
- if (hc != text.hashCode())
- cc.writeClientCutText(text, text.length());
}
public void setSendingEnabled(boolean b) {
@@ -140,18 +203,20 @@
public void actionPerformed(ActionEvent e) {
Object s = e.getSource();
if (s instanceof JButton && (JButton)s == clearButton) {
- serverCutText(new String(""), 0);
+ serverCutText(new String(""));
} else if (s instanceof JButton && (JButton)s == sendButton) {
String text = textArea.getText();
- cc.writeClientCutText(text, text.length());
+ VncViewer.cc.writeClientCutText(text, text.length());
endDialog();
} else if (s instanceof JButton && (JButton)s == cancelButton) {
endDialog();
}
}
- CConn cc;
- JTextArea textArea;
- JButton clearButton, sendButton, cancelButton;
+ private JButton clearButton, sendButton, cancelButton;
+ private static boolean listen = true;
+ static ClipboardDialog dialog;
+ static MyJTextArea textArea = new MyJTextArea();
+ static Toolkit tk = Toolkit.getDefaultToolkit();
static LogWriter vlog = new LogWriter("ClipboardDialog");
}
diff --git a/java/com/tigervnc/vncviewer/DesktopWindow.java b/java/com/tigervnc/vncviewer/DesktopWindow.java
index ff48fc1..187fbad 100644
--- a/java/com/tigervnc/vncviewer/DesktopWindow.java
+++ b/java/com/tigervnc/vncviewer/DesktopWindow.java
@@ -1,8 +1,6 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright (C) 2006 Constantin Kaplinsky. All Rights Reserved.
- * Copyright (C) 2009 Paul Donohue. All Rights Reserved.
- * Copyright (C) 2010, 2012-2013 D. R. Commander. All Rights Reserved.
- * Copyright (C) 2011-2014 Brian P. Hinz
+ * Copyright (C) 2011-2016 Brian P. Hinz
+ * Copyright (C) 2012-2013 D. R. Commander. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,572 +18,631 @@
* USA.
*/
-//
-// DesktopWindow is an AWT Canvas representing a VNC desktop.
-//
-// Methods on DesktopWindow are called from both the GUI thread and the thread
-// which processes incoming RFB messages ("the RFB thread"). This means we
-// need to be careful with synchronization here.
-//
-
package com.tigervnc.vncviewer;
+
import java.awt.*;
import java.awt.event.*;
-import java.awt.image.*;
-import java.awt.datatransfer.DataFlavor;
-import java.awt.datatransfer.Transferable;
-import java.awt.datatransfer.Clipboard;
-import java.io.BufferedReader;
-import java.nio.CharBuffer;
+import java.lang.reflect.*;
+import java.util.*;
import javax.swing.*;
+import javax.swing.Timer;
+import javax.swing.border.*;
import com.tigervnc.rfb.*;
-import com.tigervnc.rfb.Cursor;
import com.tigervnc.rfb.Point;
+import java.lang.Exception;
-class DesktopWindow extends JPanel implements Runnable, MouseListener,
- MouseMotionListener, MouseWheelListener, KeyListener {
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS;
- ////////////////////////////////////////////////////////////////////
- // The following methods are all called from the RFB thread
+import static com.tigervnc.vncviewer.Parameters.*;
- public DesktopWindow(int width, int height, PixelFormat serverPF,
- CConn cc_) {
+public class DesktopWindow extends JFrame
+{
+
+ static LogWriter vlog = new LogWriter("DesktopWindow");
+
+ public DesktopWindow(int w, int h, String name,
+ PixelFormat serverPF, CConn cc_)
+ {
cc = cc_;
- setSize(width, height);
- setScaledSize();
- setOpaque(false);
- GraphicsEnvironment ge =
- GraphicsEnvironment.getLocalGraphicsEnvironment();
- GraphicsDevice gd = ge.getDefaultScreenDevice();
- GraphicsConfiguration gc = gd.getDefaultConfiguration();
- BufferCapabilities bufCaps = gc.getBufferCapabilities();
- ImageCapabilities imgCaps = gc.getImageCapabilities();
- if (bufCaps.isPageFlipping() || bufCaps.isMultiBufferAvailable() ||
- imgCaps.isAccelerated()) {
- vlog.debug("GraphicsDevice supports HW acceleration.");
- } else {
- vlog.debug("GraphicsDevice does not support HW acceleration.");
- }
- im = new BIPixelBuffer(width, height, cc, this);
+ firstUpdate = true;
+ delayedFullscreen = false; delayedDesktopSize = false;
- cursor = new Cursor();
- cursorBacking = new ManagedPixelBuffer();
- Dimension bestSize = tk.getBestCursorSize(16, 16);
- BufferedImage cursorImage;
- cursorImage = new BufferedImage(bestSize.width, bestSize.height,
- BufferedImage.TYPE_INT_ARGB);
- java.awt.Point hotspot = new java.awt.Point(0,0);
- nullCursor = tk.createCustomCursor(cursorImage, hotspot, "nullCursor");
- cursorImage.flush();
- if (!cc.cp.supportsLocalCursor && !bestSize.equals(new Dimension(0,0)))
- setCursor(nullCursor);
- addMouseListener(this);
- addMouseWheelListener(this);
- addMouseMotionListener(this);
- addKeyListener(this);
- addFocusListener(new FocusAdapter() {
- public void focusGained(FocusEvent e) {
- cc.clipboardDialog.clientCutText();
+ setFocusable(false);
+ setFocusTraversalKeysEnabled(false);
+ getToolkit().setDynamicLayout(false);
+ if (!VncViewer.os.startsWith("mac os x"))
+ setIconImage(VncViewer.frameIcon);
+ UIManager.getDefaults().put("ScrollPane.ancestorInputMap",
+ new UIDefaults.LazyInputMap(new Object[]{}));
+ scroll = new JScrollPane(new Viewport(w, h, serverPF, cc));
+ viewport = (Viewport)scroll.getViewport().getView();
+ scroll.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
+ getContentPane().add(scroll);
+
+ setName(name);
+
+ lastScaleFactor = scalingFactor.getValue();
+ if (VncViewer.os.startsWith("mac os x"))
+ if (!noLionFS.getValue())
+ enableLionFS();
+
+ OptionsDialog.addCallback("handleOptions", this);
+
+ addWindowFocusListener(new WindowAdapter() {
+ public void windowGainedFocus(WindowEvent e) {
+ if (isVisible())
+ if (scroll.getViewport() != null)
+ scroll.getViewport().getView().requestFocusInWindow();
}
- public void focusLost(FocusEvent e) {
+ public void windowLostFocus(WindowEvent e) {
cc.releaseDownKeys();
}
});
- setFocusTraversalKeysEnabled(false);
- setFocusable(true);
- }
- public int width() {
- return getWidth();
- }
+ addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) {
+ cc.close();
+ }
+ public void windowDeiconified(WindowEvent e) {
+ // ViewportBorder sometimes lost when window is shaded or de-iconified
+ repositionViewport();
+ }
+ });
- public int height() {
- return getHeight();
- }
-
- public final PixelFormat getPF() { return im.getPF(); }
-
- public void setViewport(JViewport viewport) {
- setScaledSize();
- viewport.setView(this);
- // pack() must be called on a JFrame before getInsets()
- // will return anything other than 0.
- if (viewport.getRootPane() != null)
- if (getRootPane().getParent() instanceof JFrame)
- ((JFrame)getRootPane().getParent()).pack();
- }
-
- // Methods called from the RFB thread - these need to be synchronized
- // wherever they access data shared with the GUI thread.
-
- public void setCursor(int w, int h, Point hotspot,
- int[] data, byte[] mask) {
- // strictly we should use a mutex around this test since useLocalCursor
- // might be being altered by the GUI thread. However it's only a single
- // boolean and it doesn't matter if we get the wrong value anyway.
-
- synchronized(cc.viewer.useLocalCursor) {
- if (!cc.viewer.useLocalCursor.getValue())
- return;
- }
-
- hideLocalCursor();
-
- cursor.hotspot = (hotspot != null) ? hotspot : new Point(0, 0);
- cursor.setSize(w, h);
- cursor.setPF(getPF());
-
- cursorBacking.setSize(cursor.width(), cursor.height());
- cursorBacking.setPF(getPF());
-
- cursor.data = new int[cursor.width() * cursor.height()];
- cursor.mask = new byte[cursor.maskLen()];
-
- int maskBytesPerRow = (w + 7) / 8;
- for (int y = 0; y < h; y++) {
- for (int x = 0; x < w; x++) {
- int byte_ = y * maskBytesPerRow + x / 8;
- int bit = 7 - x % 8;
- if ((mask[byte_] & (1 << bit)) > 0) {
- cursor.data[y * cursor.width() + x] = (0xff << 24) |
- (im.cm.getRed(data[y * w + x]) << 16) |
- (im.cm.getGreen(data[y * w + x]) << 8) |
- (im.cm.getBlue(data[y * w + x]));
+ addWindowStateListener(new WindowAdapter() {
+ public void windowStateChanged(WindowEvent e) {
+ int state = e.getNewState();
+ if ((state & JFrame.MAXIMIZED_BOTH) != JFrame.MAXIMIZED_BOTH) {
+ Rectangle b = getGraphicsConfiguration().getBounds();
+ if (!b.contains(getLocationOnScreen()))
+ setLocation((int)b.getX(), (int)b.getY());
}
+ // ViewportBorder sometimes lost when restoring on Windows
+ repositionViewport();
}
- System.arraycopy(mask, y * maskBytesPerRow, cursor.mask,
- y * ((cursor.width() + 7) / 8), maskBytesPerRow);
- }
+ });
- int cw = (int)Math.floor((float)cursor.width() * scaleWidthRatio);
- int ch = (int)Math.floor((float)cursor.height() * scaleHeightRatio);
- Dimension bestSize = tk.getBestCursorSize(cw, ch);
- MemoryImageSource cursorSrc;
- cursorSrc = new MemoryImageSource(cursor.width(), cursor.height(),
- ColorModel.getRGBdefault(),
- cursor.data, 0, cursor.width());
- Image srcImage = tk.createImage(cursorSrc);
- BufferedImage cursorImage;
- cursorImage = new BufferedImage(bestSize.width, bestSize.height,
- BufferedImage.TYPE_INT_ARGB);
- Graphics2D g2 = cursorImage.createGraphics();
- g2.setRenderingHint(RenderingHints.KEY_RENDERING,
- RenderingHints.VALUE_RENDER_SPEED);
- g2.drawImage(srcImage, 0, 0, (int)Math.min(cw, bestSize.width),
- (int)Math.min(ch, bestSize.height), 0, 0, cursor.width(),
- cursor.height(), null);
- g2.dispose();
- srcImage.flush();
-
- int x = (int)Math.floor((float)cursor.hotspot.x * scaleWidthRatio);
- int y = (int)Math.floor((float)cursor.hotspot.y * scaleHeightRatio);
- x = (int)Math.min(x, Math.max(bestSize.width - 1, 0));
- y = (int)Math.min(y, Math.max(bestSize.height - 1, 0));
- java.awt.Point hs = new java.awt.Point(x, y);
- if (!bestSize.equals(new Dimension(0, 0)))
- softCursor = tk.createCustomCursor(cursorImage, hs, "softCursor");
- cursorImage.flush();
-
- if (softCursor != null) {
- setCursor(softCursor);
- cursorAvailable = false;
- return;
- }
-
- if (!cursorAvailable) {
- cursorAvailable = true;
- }
-
- showLocalCursor();
- }
-
- public void setServerPF(PixelFormat pf) {
- im.setPF(pf);
- }
-
- public PixelFormat getPreferredPF() {
- return im.getNativePF();
- }
-
- // setColourMapEntries() changes some of the entries in the colourmap.
- // Unfortunately these messages are often sent one at a time, so we delay the
- // settings taking effect unless the whole colourmap has changed. This is
- // because getting java to recalculate its internal translation table and
- // redraw the screen is expensive.
-
- public synchronized void setColourMapEntries(int firstColour, int nColours,
- int[] rgbs) {
- im.setColourMapEntries(firstColour, nColours, rgbs);
- if (nColours <= 256) {
- im.updateColourMap();
- } else {
- if (setColourMapEntriesTimerThread == null) {
- setColourMapEntriesTimerThread = new Thread(this);
- setColourMapEntriesTimerThread.start();
+ // Window resize events
+ timer = new Timer(500, new AbstractAction() {
+ public void actionPerformed(ActionEvent e) {
+ handleResizeTimeout();
}
- }
- }
-
- // Update the actual window with the changed parts of the framebuffer.
- public void updateWindow() {
- Rect r = damage;
- if (!r.is_empty()) {
- if (cc.cp.width != scaledWidth || cc.cp.height != scaledHeight) {
- int x = (int)Math.floor(r.tl.x * scaleWidthRatio);
- int y = (int)Math.floor(r.tl.y * scaleHeightRatio);
- // Need one extra pixel to account for rounding.
- int width = (int)Math.ceil(r.width() * scaleWidthRatio) + 1;
- int height = (int)Math.ceil(r.height() * scaleHeightRatio) + 1;
- paintImmediately(x, y, width, height);
- } else {
- paintImmediately(r.tl.x, r.tl.y, r.width(), r.height());
- }
- damage.clear();
- }
- }
-
- // resize() is called when the desktop has changed size
- public void resize() {
- int w = cc.cp.width;
- int h = cc.cp.height;
- hideLocalCursor();
- setSize(w, h);
- setScaledSize();
- im.resize(w, h);
- }
-
- public final void fillRect(int x, int y, int w, int h, int pix) {
- if (overlapsCursor(x, y, w, h)) hideLocalCursor();
- im.fillRect(x, y, w, h, pix);
- damageRect(new Rect(x, y, x+w, y+h));
- showLocalCursor();
- }
-
- public final void imageRect(int x, int y, int w, int h,
- Object pix) {
- if (overlapsCursor(x, y, w, h)) hideLocalCursor();
- im.imageRect(x, y, w, h, pix);
- damageRect(new Rect(x, y, x+w, y+h));
- showLocalCursor();
- }
-
- public final void copyRect(int x, int y, int w, int h,
- int srcX, int srcY) {
- if (overlapsCursor(x, y, w, h) || overlapsCursor(srcX, srcY, w, h))
- hideLocalCursor();
- im.copyRect(x, y, w, h, srcX, srcY);
- damageRect(new Rect(x, y, x+w, y+h));
- showLocalCursor();
- }
-
-
- // mutex MUST be held when overlapsCursor() is called
- final boolean overlapsCursor(int x, int y, int w, int h) {
- return (x < cursorBackingX + cursorBacking.width() &&
- y < cursorBackingY + cursorBacking.height() &&
- x + w > cursorBackingX && y + h > cursorBackingY);
- }
-
-
- ////////////////////////////////////////////////////////////////////
- // The following methods are all called from the GUI thread
-
- void resetLocalCursor() {
- if (cc.cp.supportsLocalCursor) {
- if (softCursor != null)
- setCursor(softCursor);
- } else {
- setCursor(nullCursor);
- }
- hideLocalCursor();
- cursorAvailable = false;
- }
-
- //
- // Callback methods to determine geometry of our Component.
- //
-
- public Dimension getPreferredSize() {
- return new Dimension(scaledWidth, scaledHeight);
- }
-
- public Dimension getMinimumSize() {
- return new Dimension(scaledWidth, scaledHeight);
- }
-
- public Dimension getMaximumSize() {
- return new Dimension(scaledWidth, scaledHeight);
- }
-
- public void setScaledSize() {
- String scaleString = cc.viewer.scalingFactor.getValue();
- if (!scaleString.equalsIgnoreCase("Auto") &&
- !scaleString.equalsIgnoreCase("FixedRatio")) {
- int scalingFactor = Integer.parseInt(scaleString);
- scaledWidth =
- (int)Math.floor((float)cc.cp.width * (float)scalingFactor/100.0);
- scaledHeight =
- (int)Math.floor((float)cc.cp.height * (float)scalingFactor/100.0);
- } else {
- if (cc.viewport == null) {
- scaledWidth = cc.cp.width;
- scaledHeight = cc.cp.height;
- } else {
- Dimension vpSize = cc.viewport.getSize();
- Insets vpInsets = cc.viewport.getInsets();
- Dimension availableSize =
- new Dimension(vpSize.width - vpInsets.left - vpInsets.right,
- vpSize.height - vpInsets.top - vpInsets.bottom);
- if (availableSize.width == 0 || availableSize.height == 0)
- availableSize = new Dimension(cc.cp.width, cc.cp.height);
- if (scaleString.equalsIgnoreCase("FixedRatio")) {
- float widthRatio = (float)availableSize.width / (float)cc.cp.width;
- float heightRatio = (float)availableSize.height / (float)cc.cp.height;
- float ratio = Math.min(widthRatio, heightRatio);
- scaledWidth = (int)Math.floor(cc.cp.width * ratio);
- scaledHeight = (int)Math.floor(cc.cp.height * ratio);
+ });
+ timer.setRepeats(false);
+ addComponentListener(new ComponentAdapter() {
+ public void componentResized(ComponentEvent e) {
+ if (remoteResize.getValue()) {
+ if (timer.isRunning())
+ timer.restart();
+ else
+ // Try to get the remote size to match our window size, provided
+ // the following conditions are true:
+ //
+ // a) The user has this feature turned on
+ // b) The server supports it
+ // c) We're not still waiting for a chance to handle DesktopSize
+ // d) We're not still waiting for startup fullscreen to kick in
+ if (!firstUpdate && !delayedFullscreen &&
+ remoteResize.getValue() && cc.cp.supportsSetDesktopSize)
+ timer.start();
} else {
- scaledWidth = availableSize.width;
- scaledHeight = availableSize.height;
+ String scaleString = scalingFactor.getValue();
+ if (!scaleString.matches("^[0-9]+$")) {
+ Dimension maxSize = getContentPane().getSize();
+ if ((maxSize.width != viewport.scaledWidth) ||
+ (maxSize.height != viewport.scaledHeight))
+ viewport.setScaledSize(maxSize.width, maxSize.height);
+ if (!scaleString.equals("Auto")) {
+ if (!isMaximized() && !fullscreen_active()) {
+ int dx = getInsets().left + getInsets().right;
+ int dy = getInsets().top + getInsets().bottom;
+ setSize(viewport.scaledWidth+dx, viewport.scaledHeight+dy);
+ }
+ }
+ }
+ repositionViewport();
}
}
- }
- scaleWidthRatio = (float)scaledWidth / (float)cc.cp.width;
- scaleHeightRatio = (float)scaledHeight / (float)cc.cp.height;
+ });
+
}
- public void paintComponent(Graphics g) {
- Graphics2D g2 = (Graphics2D) g;
- if (cc.cp.width != scaledWidth || cc.cp.height != scaledHeight) {
- g2.setRenderingHint(RenderingHints.KEY_RENDERING,
- RenderingHints.VALUE_RENDER_QUALITY);
- g2.drawImage(im.getImage(), 0, 0, scaledWidth, scaledHeight, null);
+ // Remove resize listener in order to prevent recursion when resizing
+ @Override
+ public void setSize(Dimension d)
+ {
+ ComponentListener[] listeners = getListeners(ComponentListener.class);
+ for (ComponentListener l : listeners)
+ removeComponentListener(l);
+ super.setSize(d);
+ for (ComponentListener l : listeners)
+ addComponentListener(l);
+ }
+
+ @Override
+ public void setSize(int width, int height)
+ {
+ ComponentListener[] listeners = getListeners(ComponentListener.class);
+ for (ComponentListener l : listeners)
+ removeComponentListener(l);
+ super.setSize(width, height);
+ for (ComponentListener l : listeners)
+ addComponentListener(l);
+ }
+
+ @Override
+ public void setBounds(Rectangle r)
+ {
+ ComponentListener[] listeners = getListeners(ComponentListener.class);
+ for (ComponentListener l : listeners)
+ removeComponentListener(l);
+ super.setBounds(r);
+ for (ComponentListener l : listeners)
+ addComponentListener(l);
+ }
+
+ private void repositionViewport()
+ {
+ scroll.revalidate();
+ Rectangle r = scroll.getViewportBorderBounds();
+ int dx = r.width - viewport.scaledWidth;
+ int dy = r.height - viewport.scaledHeight;
+ int top = (int)Math.max(Math.floor(dy/2), 0);
+ int left = (int)Math.max(Math.floor(dx/2), 0);
+ int bottom = (int)Math.max(dy - top, 0);
+ int right = (int)Math.max(dx - left, 0);
+ Insets insets = new Insets(top, left, bottom, right);
+ scroll.setViewportBorder(new MatteBorder(insets, Color.BLACK));
+ scroll.revalidate();
+ }
+
+ public PixelFormat getPreferredPF()
+ {
+ return viewport.getPreferredPF();
+ }
+
+ public void setName(String name)
+ {
+ setTitle(name);
+ }
+
+ // Copy the areas of the framebuffer that have been changed (damaged)
+ // to the displayed window.
+
+ public void updateWindow()
+ {
+ if (firstUpdate) {
+ pack();
+ if (embed.getValue()) {
+ scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
+ VncViewer.setupEmbeddedFrame(scroll);
+ } else {
+ if (fullScreen.getValue())
+ fullscreen_on();
+ else
+ setVisible(true);
+
+ if (maximize.getValue())
+ setExtendedState(JFrame.MAXIMIZED_BOTH);
+ }
+
+ if (cc.cp.supportsSetDesktopSize && !desktopSize.getValue().equals("")) {
+ // Hack: Wait until we're in the proper mode and position until
+ // resizing things, otherwise we might send the wrong thing.
+ if (delayedFullscreen)
+ delayedDesktopSize = true;
+ else
+ handleDesktopSize();
+ }
+ firstUpdate = false;
+ }
+
+ viewport.updateWindow();
+ }
+
+ public void resizeFramebuffer(int new_w, int new_h)
+ {
+ if ((new_w == viewport.scaledWidth) && (new_h == viewport.scaledHeight))
+ return;
+
+ // If we're letting the viewport match the window perfectly, then
+ // keep things that way for the new size, otherwise just keep things
+ // like they are.
+ int dx = getInsets().left + getInsets().right;
+ int dy = getInsets().top + getInsets().bottom;
+ if (!fullscreen_active()) {
+ if ((w() == viewport.scaledWidth) && (h() == viewport.scaledHeight))
+ setSize(new_w+dx, new_h+dy);
+ else {
+ // Make sure the window isn't too big. We do this manually because
+ // we have to disable the window size restriction (and it isn't
+ // entirely trustworthy to begin with).
+ if ((w() > new_w) || (h() > new_h))
+ setSize(Math.min(w(), new_w)+dx, Math.min(h(), new_h)+dy);
+ }
+ }
+
+ viewport.resize(0, 0, new_w, new_h);
+
+ // We might not resize the main window, so we need to manually call this
+ // to make sure the viewport is centered.
+ repositionViewport();
+
+ // repositionViewport() makes sure the scroll widget notices any changes
+ // in position, but it might be just the size that changes so we also
+ // need a poke here as well.
+ validate();
+ }
+
+ public void setCursor(int width, int height, Point hotspot,
+ byte[] data, byte[] mask)
+ {
+ viewport.setCursor(width, height, hotspot, data, mask);
+ }
+
+ public void fullscreen_on()
+ {
+ fullScreen.setParam(true);
+ lastState = getExtendedState();
+ lastBounds = getBounds();
+ dispose();
+ // Screen bounds calculation affected by maximized window?
+ setExtendedState(JFrame.NORMAL);
+ setUndecorated(true);
+ setVisible(true);
+ setBounds(getScreenBounds());
+ }
+
+ public void fullscreen_off()
+ {
+ fullScreen.setParam(false);
+ dispose();
+ setUndecorated(false);
+ setExtendedState(lastState);
+ setBounds(lastBounds);
+ setVisible(true);
+ }
+
+ public boolean fullscreen_active()
+ {
+ return isUndecorated();
+ }
+
+ private void handleDesktopSize()
+ {
+ if (!desktopSize.getValue().equals("")) {
+ int width, height;
+
+ // An explicit size has been requested
+
+ if (desktopSize.getValue().split("x").length != 2)
+ return;
+
+ width = Integer.parseInt(desktopSize.getValue().split("x")[0]);
+ height = Integer.parseInt(desktopSize.getValue().split("x")[1]);
+ remoteResize(width, height);
+ } else if (remoteResize.getValue()) {
+ // No explicit size, but remote resizing is on so make sure it
+ // matches whatever size the window ended up being
+ remoteResize(w(), h());
+ }
+ }
+
+ public void handleResizeTimeout()
+ {
+ DesktopWindow self = (DesktopWindow)this;
+
+ assert(self != null);
+
+ self.remoteResize(self.w(), self.h());
+ }
+
+ private void remoteResize(int width, int height)
+ {
+ ScreenSet layout;
+ ListIterator<Screen> iter;
+
+ if (!fullscreen_active() || (width > w()) || (height > h())) {
+ // In windowed mode (or the framebuffer is so large that we need
+ // to scroll) we just report a single virtual screen that covers
+ // the entire framebuffer.
+
+ layout = cc.cp.screenLayout;
+
+ // Not sure why we have no screens, but adding a new one should be
+ // safe as there is nothing to conflict with...
+ if (layout.num_screens() == 0)
+ layout.add_screen(new Screen());
+ else if (layout.num_screens() != 1) {
+ // More than one screen. Remove all but the first (which we
+ // assume is the "primary").
+
+ while (true) {
+ iter = layout.begin();
+ Screen screen = iter.next();
+
+ if (iter == layout.end())
+ break;
+
+ layout.remove_screen(screen.id);
+ }
+ }
+
+ // Resize the remaining single screen to the complete framebuffer
+ ((Screen)layout.begin().next()).dimensions.tl.x = 0;
+ ((Screen)layout.begin().next()).dimensions.tl.y = 0;
+ ((Screen)layout.begin().next()).dimensions.br.x = width;
+ ((Screen)layout.begin().next()).dimensions.br.y = height;
} else {
- g2.drawImage(im.getImage(), 0, 0, null);
- }
- g2.dispose();
- }
+ layout = new ScreenSet();
+ int id;
+ int sx, sy, sw, sh;
+ Rect viewport_rect = new Rect();
+ Rect screen_rect = new Rect();
- // Mouse-Motion callback function
- private void mouseMotionCB(MouseEvent e) {
- if (!cc.viewer.viewOnly.getValue() &&
- e.getX() >= 0 && e.getX() <= scaledWidth &&
- e.getY() >= 0 && e.getY() <= scaledHeight)
- cc.writePointerEvent(e);
- // - If local cursor rendering is enabled then use it
- if (cursorAvailable) {
- // - Render the cursor!
- if (e.getX() != cursorPosX || e.getY() != cursorPosY) {
- hideLocalCursor();
- if (e.getX() >= 0 && e.getX() < im.width() &&
- e.getY() >= 0 && e.getY() < im.height()) {
- cursorPosX = e.getX();
- cursorPosY = e.getY();
- showLocalCursor();
+ // In full screen we report all screens that are fully covered.
+
+ viewport_rect.setXYWH(x() + (w() - width)/2, y() + (h() - height)/2,
+ width, height);
+
+ // If we can find a matching screen in the existing set, we use
+ // that, otherwise we create a brand new screen.
+ //
+ // FIXME: We should really track screens better so we can handle
+ // a resized one.
+ //
+ GraphicsEnvironment ge =
+ GraphicsEnvironment.getLocalGraphicsEnvironment();
+ for (GraphicsDevice gd : ge.getScreenDevices()) {
+ for (GraphicsConfiguration gc : gd.getConfigurations()) {
+ Rectangle bounds = gc.getBounds();
+ sx = bounds.x;
+ sy = bounds.y;
+ sw = bounds.width;
+ sh = bounds.height;
+
+ // Check that the screen is fully inside the framebuffer
+ screen_rect.setXYWH(sx, sy, sw, sh);
+ if (!screen_rect.enclosed_by(viewport_rect))
+ continue;
+
+ // Adjust the coordinates so they are relative to our viewport
+ sx -= viewport_rect.tl.x;
+ sy -= viewport_rect.tl.y;
+
+ // Look for perfectly matching existing screen...
+ for (iter = cc.cp.screenLayout.begin();
+ iter != cc.cp.screenLayout.end(); iter.next()) {
+ Screen screen = iter.next(); iter.previous();
+ if ((screen.dimensions.tl.x == sx) &&
+ (screen.dimensions.tl.y == sy) &&
+ (screen.dimensions.width() == sw) &&
+ (screen.dimensions.height() == sh))
+ break;
+ }
+
+ // Found it?
+ if (iter != cc.cp.screenLayout.end()) {
+ layout.add_screen(iter.next());
+ continue;
+ }
+
+ // Need to add a new one, which means we need to find an unused id
+ Random rng = new Random();
+ while (true) {
+ id = rng.nextInt();
+ for (iter = cc.cp.screenLayout.begin();
+ iter != cc.cp.screenLayout.end(); iter.next()) {
+ Screen screen = iter.next(); iter.previous();
+ if (screen.id == id)
+ break;
+ }
+
+ if (iter == cc.cp.screenLayout.end())
+ break;
+ }
+
+ layout.add_screen(new Screen(id, sx, sy, sw, sh, 0));
}
+
+ // If the viewport doesn't match a physical screen, then we might
+ // end up with no screens in the layout. Add a fake one...
+ if (layout.num_screens() == 0)
+ layout.add_screen(new Screen(0, 0, 0, width, height, 0));
}
}
- lastX = e.getX();
- lastY = e.getY();
- }
- public void mouseDragged(MouseEvent e) { mouseMotionCB(e); }
- public void mouseMoved(MouseEvent e) { mouseMotionCB(e); }
- // Mouse callback function
- private void mouseCB(MouseEvent e) {
- if (!cc.viewer.viewOnly.getValue()) {
- if ((e.getID() == MouseEvent.MOUSE_RELEASED) ||
- (e.getX() >= 0 && e.getX() <= scaledWidth &&
- e.getY() >= 0 && e.getY() <= scaledHeight))
- cc.writePointerEvent(e);
- }
- lastX = e.getX();
- lastY = e.getY();
- }
- public void mouseReleased(MouseEvent e) { mouseCB(e); }
- public void mousePressed(MouseEvent e) { mouseCB(e); }
- public void mouseClicked(MouseEvent e) {}
- public void mouseEntered(MouseEvent e) {
- if (cc.viewer.embed.getValue())
- requestFocus();
- }
- public void mouseExited(MouseEvent e) {}
+ // Do we actually change anything?
+ if ((width == cc.cp.width) &&
+ (height == cc.cp.height) &&
+ (layout == cc.cp.screenLayout))
+ return;
- // MouseWheel callback function
- private void mouseWheelCB(MouseWheelEvent e) {
- if (!cc.viewer.viewOnly.getValue())
- cc.writeWheelEvent(e);
- }
+ String buffer;
+ vlog.debug(String.format("Requesting framebuffer resize from %dx%d to %dx%d",
+ cc.cp.width, cc.cp.height, width, height));
+ layout.debug_print();
- public void mouseWheelMoved(MouseWheelEvent e) {
- mouseWheelCB(e);
- }
-
- private static final Integer keyEventLock = 0;
-
- // Handle the key-typed event.
- public void keyTyped(KeyEvent e) { }
-
- // Handle the key-released event.
- public void keyReleased(KeyEvent e) {
- synchronized(keyEventLock) {
- cc.writeKeyEvent(e);
- }
- }
-
- // Handle the key-pressed event.
- public void keyPressed(KeyEvent e) {
- if (e.getKeyCode() == MenuKey.getMenuKeyCode()) {
- int sx = (scaleWidthRatio == 1.00) ?
- lastX : (int)Math.floor(lastX * scaleWidthRatio);
- int sy = (scaleHeightRatio == 1.00) ?
- lastY : (int)Math.floor(lastY * scaleHeightRatio);
- java.awt.Point ev = new java.awt.Point(lastX, lastY);
- ev.translate(sx - lastX, sy - lastY);
- cc.showMenu((int)ev.getX(), (int)ev.getY());
+ if (!layout.validate(width, height)) {
+ vlog.error("Invalid screen layout computed for resize request!");
return;
}
- int ctrlAltShiftMask = Event.SHIFT_MASK | Event.CTRL_MASK | Event.ALT_MASK;
- if ((e.getModifiers() & ctrlAltShiftMask) == ctrlAltShiftMask) {
- switch (e.getKeyCode()) {
- case KeyEvent.VK_A:
- cc.showAbout();
- return;
- case KeyEvent.VK_F:
- cc.toggleFullScreen();
- return;
- case KeyEvent.VK_H:
- cc.refresh();
- return;
- case KeyEvent.VK_I:
- cc.showInfo();
- return;
- case KeyEvent.VK_O:
- cc.options.showDialog(cc.viewport);
- return;
- case KeyEvent.VK_W:
- VncViewer.newViewer(cc.viewer);
- return;
- case KeyEvent.VK_LEFT:
- case KeyEvent.VK_RIGHT:
- case KeyEvent.VK_UP:
- case KeyEvent.VK_DOWN:
- return;
- }
- }
- if ((e.getModifiers() & Event.META_MASK) == Event.META_MASK) {
- switch (e.getKeyCode()) {
- case KeyEvent.VK_COMMA:
- case KeyEvent.VK_N:
- case KeyEvent.VK_W:
- case KeyEvent.VK_I:
- case KeyEvent.VK_R:
- case KeyEvent.VK_L:
- case KeyEvent.VK_F:
- case KeyEvent.VK_Z:
- case KeyEvent.VK_T:
- return;
- }
- }
- synchronized(keyEventLock) {
- cc.writeKeyEvent(e);
- }
+
+ cc.writer().writeSetDesktopSize(width, height, layout);
}
- ////////////////////////////////////////////////////////////////////
- // The following methods are called from both RFB and GUI threads
+ boolean lionFSSupported() { return canDoLionFS; }
- // Note that mutex MUST be held when hideLocalCursor() and showLocalCursor()
- // are called.
+ private int x() { return getContentPane().getX(); }
+ private int y() { return getContentPane().getY(); }
+ private int w() { return getContentPane().getWidth(); }
+ private int h() { return getContentPane().getHeight(); }
- private synchronized void hideLocalCursor() {
- // - Blit the cursor backing store over the cursor
- if (cursorVisible) {
- cursorVisible = false;
- im.imageRect(cursorBackingX, cursorBackingY, cursorBacking.width(),
- cursorBacking.height(), cursorBacking.data);
- damageRect(new Rect(cursorBackingX, cursorBackingY,
- cursorBackingX+cursorBacking.width(),
- cursorBackingY+cursorBacking.height()));
- }
- }
-
- private synchronized void showLocalCursor() {
- if (cursorAvailable && !cursorVisible) {
- if (!im.getPF().equal(cursor.getPF()) ||
- cursor.width() == 0 || cursor.height() == 0) {
- vlog.debug("attempting to render invalid local cursor");
- cursorAvailable = false;
- return;
- }
- cursorVisible = true;
-
- int cursorLeft = cursor.hotspot.x;
- int cursorTop = cursor.hotspot.y;
- int cursorRight = cursorLeft + cursor.width();
- int cursorBottom = cursorTop + cursor.height();
-
- int x = (cursorLeft >= 0 ? cursorLeft : 0);
- int y = (cursorTop >= 0 ? cursorTop : 0);
- int w = ((cursorRight < im.width() ? cursorRight : im.width()) - x);
- int h = ((cursorBottom < im.height() ? cursorBottom : im.height()) - y);
-
- cursorBackingX = x;
- cursorBackingY = y;
- cursorBacking.setSize(w, h);
-
- for (int j = 0; j < h; j++)
- System.arraycopy(im.data, (y + j) * im.width() + x,
- cursorBacking.data, j * w, w);
-
- im.maskRect(cursorLeft, cursorTop, cursor.width(), cursor.height(),
- cursor.data, cursor.mask);
- damageRect(new Rect(x, y, x+w, y+h));
- }
- }
-
- void damageRect(Rect r) {
- if (damage.is_empty()) {
- damage.setXYWH(r.tl.x, r.tl.y, r.width(), r.height());
- } else {
- r = damage.union_boundary(r);
- damage.setXYWH(r.tl.x, r.tl.y, r.width(), r.height());
- }
- }
-
- // run() is executed by the setColourMapEntriesTimerThread - it sleeps for
- // 100ms before actually updating the colourmap.
- public synchronized void run() {
+ void enableLionFS() {
try {
- Thread.sleep(100);
- } catch(InterruptedException e) {}
- im.updateColourMap();
- setColourMapEntriesTimerThread = null;
+ String version = System.getProperty("os.version");
+ int firstDot = version.indexOf('.');
+ int lastDot = version.lastIndexOf('.');
+ if (lastDot > firstDot && lastDot >= 0) {
+ version = version.substring(0, version.indexOf('.', firstDot + 1));
+ }
+ double v = Double.parseDouble(version);
+ if (v < 10.7)
+ throw new Exception("Operating system version is " + v);
+
+ Class fsuClass = Class.forName("com.apple.eawt.FullScreenUtilities");
+ Class argClasses[] = new Class[]{Window.class, Boolean.TYPE};
+ Method setWindowCanFullScreen =
+ fsuClass.getMethod("setWindowCanFullScreen", argClasses);
+ setWindowCanFullScreen.invoke(fsuClass, this, true);
+
+ canDoLionFS = true;
+ } catch (Exception e) {
+ vlog.debug("Could not enable OS X 10.7+ full-screen mode: " +
+ e.getMessage());
+ }
}
- // access to cc by different threads is specified in CConn
- CConn cc;
+ public void toggleLionFS() {
+ try {
+ Class appClass = Class.forName("com.apple.eawt.Application");
+ Method getApplication = appClass.getMethod("getApplication",
+ (Class[])null);
+ Object app = getApplication.invoke(appClass);
+ Method requestToggleFullScreen =
+ appClass.getMethod("requestToggleFullScreen", Window.class);
+ requestToggleFullScreen.invoke(app, this);
+ } catch (Exception e) {
+ vlog.debug("Could not toggle OS X 10.7+ full-screen mode: " +
+ e.getMessage());
+ }
+ }
- // access to the following must be synchronized:
- PlatformPixelBuffer im;
- Thread setColourMapEntriesTimerThread;
- Cursor cursor;
- boolean cursorVisible = false; // Is cursor currently rendered?
- boolean cursorAvailable = false; // Is cursor available for rendering?
- int cursorPosX, cursorPosY;
- ManagedPixelBuffer cursorBacking;
- int cursorBackingX, cursorBackingY;
- java.awt.Cursor softCursor, nullCursor;
- static Toolkit tk = Toolkit.getDefaultToolkit();
+ public boolean isMaximized()
+ {
+ int state = getExtendedState();
+ return ((state & JFrame.MAXIMIZED_BOTH) == JFrame.MAXIMIZED_BOTH);
+ }
- public int scaledWidth = 0, scaledHeight = 0;
- float scaleWidthRatio, scaleHeightRatio;
+ public Dimension getScreenSize() {
+ return getScreenBounds().getSize();
+ }
- // the following are only ever accessed by the GUI thread:
- int lastX, lastY;
- Rect damage = new Rect();
+ public Rectangle getScreenBounds() {
+ GraphicsEnvironment ge =
+ GraphicsEnvironment.getLocalGraphicsEnvironment();
+ Rectangle r = new Rectangle();
+ if (fullScreenAllMonitors.getValue()) {
+ for (GraphicsDevice gd : ge.getScreenDevices())
+ for (GraphicsConfiguration gc : gd.getConfigurations())
+ r = r.union(gc.getBounds());
+ } else {
+ GraphicsConfiguration gc = getGraphicsConfiguration();
+ r = gc.getBounds();
+ }
+ return r;
+ }
- static LogWriter vlog = new LogWriter("DesktopWindow");
+ public static Window getFullScreenWindow() {
+ GraphicsEnvironment ge =
+ GraphicsEnvironment.getLocalGraphicsEnvironment();
+ for (GraphicsDevice gd : ge.getScreenDevices()) {
+ Window fullScreenWindow = gd.getFullScreenWindow();
+ if (fullScreenWindow != null)
+ return fullScreenWindow;
+ }
+ return null;
+ }
+
+ public static void setFullScreenWindow(Window fullScreenWindow) {
+ GraphicsEnvironment ge =
+ GraphicsEnvironment.getLocalGraphicsEnvironment();
+ if (fullScreenAllMonitors.getValue()) {
+ for (GraphicsDevice gd : ge.getScreenDevices())
+ gd.setFullScreenWindow(fullScreenWindow);
+ } else {
+ GraphicsDevice gd = ge.getDefaultScreenDevice();
+ gd.setFullScreenWindow(fullScreenWindow);
+ }
+ }
+
+ public void handleOptions()
+ {
+
+ if (fullScreen.getValue() && !fullscreen_active())
+ fullscreen_on();
+ else if (!fullScreen.getValue() && fullscreen_active())
+ fullscreen_off();
+
+ if (remoteResize.getValue()) {
+ scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
+ remoteResize(w(), h());
+ } else {
+ String scaleString = scalingFactor.getValue();
+ if (!scaleString.equals(lastScaleFactor)) {
+ if (scaleString.matches("^[0-9]+$")) {
+ scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
+ viewport.setScaledSize(cc.cp.width, cc.cp.height);
+ } else {
+ scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER);
+ scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_NEVER);
+ viewport.setScaledSize(w(), h());
+ }
+
+ if (isMaximized() || fullscreen_active()) {
+ repositionViewport();
+ } else {
+ int dx = getInsets().left + getInsets().right;
+ int dy = getInsets().top + getInsets().bottom;
+ setSize(viewport.scaledWidth+dx, viewport.scaledHeight+dy);
+ }
+
+ repositionViewport();
+ lastScaleFactor = scaleString;
+ }
+ }
+
+ if (isVisible()) {
+ toFront();
+ requestFocus();
+ }
+ }
+
+ public void handleFullscreenTimeout()
+ {
+ DesktopWindow self = (DesktopWindow)this;
+
+ assert(self != null);
+
+ self.delayedFullscreen = false;
+
+ if (self.delayedDesktopSize) {
+ self.handleDesktopSize();
+ self.delayedDesktopSize = false;
+ }
+ }
+
+ private CConn cc;
+ private JScrollPane scroll;
+ public Viewport viewport;
+
+ private boolean firstUpdate;
+ private boolean delayedFullscreen;
+ private boolean delayedDesktopSize;
+ private boolean canDoLionFS;
+ private String lastScaleFactor;
+ private Rectangle lastBounds;
+ private int lastState;
+ private Timer timer;
}
+
diff --git a/java/com/tigervnc/vncviewer/Dialog.java b/java/com/tigervnc/vncviewer/Dialog.java
index 8bf1979..a2fb04f 100644
--- a/java/com/tigervnc/vncviewer/Dialog.java
+++ b/java/com/tigervnc/vncviewer/Dialog.java
@@ -31,8 +31,10 @@
import java.awt.*;
import java.awt.Dialog.*;
import java.awt.event.*;
+import java.io.File;
import javax.swing.*;
import javax.swing.border.*;
+import javax.swing.filechooser.*;
import javax.swing.text.*;
class Dialog extends JDialog implements ActionListener,
@@ -52,7 +54,7 @@
}
}
- public boolean showDialog(Component c) {
+ public void showDialog(Component c) {
initDialog();
if (c != null) {
setLocationRelativeTo(c);
@@ -63,26 +65,19 @@
int y = (dpySize.height - mySize.height) / 2;
setLocation(x, y);
}
- fullScreenWindow = Viewport.getFullScreenWindow();
- if (fullScreenWindow != null)
- Viewport.setFullScreenWindow(null);
if (getModalityType() == ModalityType.APPLICATION_MODAL)
setAlwaysOnTop(true);
setVisible(true);
- return ret;
}
- public boolean showDialog() {
- return showDialog(null);
+ public void showDialog() {
+ showDialog(null);
}
public void endDialog() {
setVisible(false);
setAlwaysOnTop(false);
- fullScreenWindow = Viewport.getFullScreenWindow();
- if (fullScreenWindow != null)
- Viewport.setFullScreenWindow(fullScreenWindow);
}
// initDialog() can be overridden in a derived class. Typically it is used
@@ -137,6 +132,33 @@
return width + gap;
}
+ public static File showChooser(String title, File defFile,
+ Container c, FileNameExtensionFilter f) {
+ JFileChooser fc = new JFileChooser(defFile);
+ fc.setDialogTitle(title);
+ fc.setApproveButtonText("OK \u21B5");
+ fc.setFileHidingEnabled(false);
+ if (f != null)
+ fc.setFileFilter(f);
+ if (fc.showOpenDialog(c) == JFileChooser.APPROVE_OPTION)
+ return fc.getSelectedFile();
+ else
+ return null;
+ }
+
+ public static File showChooser(String title, File defFile, Container c) {
+ return showChooser(title, defFile, c, null);
+ }
+
+ protected File showChooser(String title, File defFile,
+ FileNameExtensionFilter f) {
+ return showChooser(title, defFile, this, f);
+ }
+
+ protected File showChooser(String title, File defFile) {
+ return showChooser(title, defFile, this);
+ }
+
protected class GroupedJRadioButton extends JRadioButton {
public GroupedJRadioButton(String l, ButtonGroup g, JComponent c) {
super(l);
@@ -181,9 +203,9 @@
JTextField jtf = (JTextField)editor.getEditorComponent();
jtf.setDocument(doc);
}
+
}
private Window fullScreenWindow;
- protected boolean ret = true;
}
diff --git a/java/com/tigervnc/vncviewer/ExtProcess.java b/java/com/tigervnc/vncviewer/ExtProcess.java
new file mode 100644
index 0000000..730a742
--- /dev/null
+++ b/java/com/tigervnc/vncviewer/ExtProcess.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2016 Brian P. Hinz. All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+package com.tigervnc.vncviewer;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.*;
+
+import com.tigervnc.rdr.*;
+import com.tigervnc.rfb.*;
+import com.tigervnc.rfb.Exception;
+import com.tigervnc.network.*;
+
+import static com.tigervnc.vncviewer.Parameters.*;
+
+public class ExtProcess implements Runnable {
+
+ private String cmd = null;
+ private LogWriter vlog = null;
+ private boolean shutdown = false;
+ private Process pid = null;
+
+ private static class MyProcessLogger extends Thread {
+ private final BufferedReader err;
+ private final LogWriter vlog;
+
+ public MyProcessLogger(Process p, LogWriter vlog) {
+ InputStreamReader reader =
+ new InputStreamReader(p.getErrorStream());
+ err = new BufferedReader(reader);
+ this.vlog = vlog;
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ String msg = err.readLine();
+ if (msg != null)
+ vlog.info(msg);
+ }
+ } catch(java.io.IOException e) {
+ vlog.info(e.getMessage());
+ } finally {
+ try {
+ if (err != null)
+ err.close();
+ } catch (java.io.IOException e ) { }
+ }
+ }
+ }
+
+ private static class MyShutdownHook extends Thread {
+
+ private Process proc = null;
+
+ public MyShutdownHook(Process p) {
+ proc = p;
+ }
+
+ @Override
+ public void run() {
+ try {
+ proc.exitValue();
+ } catch (IllegalThreadStateException e) {
+ try {
+ // wait for CConn to shutdown the socket
+ Thread.sleep(500);
+ } catch(InterruptedException ie) { }
+ proc.destroy();
+ }
+ }
+ }
+
+ public ExtProcess(String command, LogWriter vlog, boolean shutdown) {
+ cmd = command;
+ this.vlog = vlog;
+ this.shutdown = shutdown;
+ }
+
+ public ExtProcess(String command, LogWriter vlog) {
+ this(command, vlog, false);
+ }
+
+ public ExtProcess(String command) {
+ this(command, null, false);
+ }
+
+ public void run() {
+ try {
+ Runtime runtime = Runtime.getRuntime();
+ pid = runtime.exec(cmd);
+ if (shutdown)
+ runtime.addShutdownHook(new MyShutdownHook(pid));
+ if (vlog != null)
+ new MyProcessLogger(pid, vlog).start();
+ pid.waitFor();
+ } catch(InterruptedException e) {
+ vlog.info(e.getMessage());
+ } catch(java.io.IOException e) {
+ vlog.info(e.getMessage());
+ }
+ }
+
+ //static LogWriter vlog = new LogWriter("ExtProcess");
+}
diff --git a/java/com/tigervnc/vncviewer/F8Menu.java b/java/com/tigervnc/vncviewer/F8Menu.java
index 472f11f..0c67305 100644
--- a/java/com/tigervnc/vncviewer/F8Menu.java
+++ b/java/com/tigervnc/vncviewer/F8Menu.java
@@ -22,48 +22,59 @@
import java.awt.*;
import java.awt.Cursor;
import java.awt.event.*;
+import java.io.File;
+import javax.swing.filechooser.*;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JFileChooser;
-import javax.swing.JPopupMenu;
import javax.swing.JMenuItem;
-import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPopupMenu;
import com.tigervnc.rfb.*;
+import static com.tigervnc.vncviewer.Parameters.*;
+
public class F8Menu extends JPopupMenu implements ActionListener {
- public F8Menu(CConn cc_) {
+ public F8Menu(CConn cc) {
super("VNC Menu");
setLightWeightPopupEnabled(false);
- cc = cc_;
+ String os = System.getProperty("os.name");
+ if (os.startsWith("Windows"))
+ com.sun.java.swing.plaf.windows.WindowsLookAndFeel.setMnemonicHidden(false);
+ this.cc = cc;
restore = addMenuItem("Restore",KeyEvent.VK_R);
- restore.setEnabled(!cc.viewer.embed.getValue());
+ restore.setEnabled(!embed.getValue());
move = addMenuItem("Move");
move.setEnabled(false);
size = addMenuItem("Size");
size.setEnabled(false);
minimize = addMenuItem("Minimize", KeyEvent.VK_N);
- minimize.setEnabled(!cc.viewer.embed.getValue());
+ minimize.setEnabled(!embed.getValue());
maximize = addMenuItem("Maximize", KeyEvent.VK_X);
- maximize.setEnabled(!cc.viewer.embed.getValue());
+ maximize.setEnabled(!embed.getValue());
addSeparator();
exit = addMenuItem("Close Viewer", KeyEvent.VK_C);
addSeparator();
- fullScreen = new JCheckBoxMenuItem("Full Screen");
- fullScreen.setMnemonic(KeyEvent.VK_F);
- fullScreen.setSelected(cc.fullScreen);
- fullScreen.addActionListener(this);
- fullScreen.setEnabled(!cc.viewer.embed.getValue());
- add(fullScreen);
+ fullScreenCheckbox = new JCheckBoxMenuItem("Full Screen");
+ fullScreenCheckbox.setMnemonic(KeyEvent.VK_F);
+ fullScreenCheckbox.setSelected(fullScreen.getValue());
+ fullScreenCheckbox.addActionListener(this);
+ fullScreenCheckbox.setEnabled(!embed.getValue());
+ add(fullScreenCheckbox);
addSeparator();
clipboard = addMenuItem("Clipboard...");
addSeparator();
- f8 = addMenuItem("Send "+KeyEvent.getKeyText(MenuKey.getMenuKeyCode()), MenuKey.getMenuKeyCode());
+ int keyCode = MenuKey.getMenuKeyCode();
+ String keyText = KeyEvent.getKeyText(keyCode);
+ f8 = addMenuItem("Send "+keyText, keyCode);
ctrlAltDel = addMenuItem("Send Ctrl-Alt-Del");
addSeparator();
refresh = addMenuItem("Refresh Screen", KeyEvent.VK_H);
addSeparator();
newConn = addMenuItem("New connection...", KeyEvent.VK_W);
- newConn.setEnabled(!cc.viewer.embed.getValue());
+ newConn.setEnabled(!embed.getValue());
options = addMenuItem("Options...", KeyEvent.VK_O);
save = addMenuItem("Save connection info as...", KeyEvent.VK_S);
info = addMenuItem("Connection info...", KeyEvent.VK_I);
@@ -94,19 +105,25 @@
public void actionPerformed(ActionEvent ev) {
if (actionMatch(ev, exit)) {
cc.close();
- } else if (actionMatch(ev, fullScreen)) {
- cc.toggleFullScreen();
+ } else if (actionMatch(ev, fullScreenCheckbox)) {
+ if (fullScreenCheckbox.isSelected())
+ cc.desktop.fullscreen_on();
+ else
+ cc.desktop.fullscreen_off();
} else if (actionMatch(ev, restore)) {
- if (cc.fullScreen) cc.toggleFullScreen();
- cc.viewport.setExtendedState(JFrame.NORMAL);
+ if (cc.desktop.fullscreen_active())
+ cc.desktop.fullscreen_off();
+ cc.desktop.setExtendedState(JFrame.NORMAL);
} else if (actionMatch(ev, minimize)) {
- if (cc.fullScreen) cc.toggleFullScreen();
- cc.viewport.setExtendedState(JFrame.ICONIFIED);
+ if (cc.desktop.fullscreen_active())
+ cc.desktop.fullscreen_off();
+ cc.desktop.setExtendedState(JFrame.ICONIFIED);
} else if (actionMatch(ev, maximize)) {
- if (cc.fullScreen) cc.toggleFullScreen();
- cc.viewport.setExtendedState(JFrame.MAXIMIZED_BOTH);
+ if (cc.desktop.fullscreen_active())
+ cc.desktop.fullscreen_off();
+ cc.desktop.setExtendedState(JFrame.MAXIMIZED_BOTH);
} else if (actionMatch(ev, clipboard)) {
- cc.clipboardDialog.showDialog(cc.viewport);
+ ClipboardDialog.showDialog(cc.desktop);
} else if (actionMatch(ev, f8)) {
cc.writeKeyEvent(MenuKey.getMenuKeySym(), true);
cc.writeKeyEvent(MenuKey.getMenuKeySym(), false);
@@ -120,39 +137,67 @@
} else if (actionMatch(ev, refresh)) {
cc.refresh();
} else if (actionMatch(ev, newConn)) {
- VncViewer.newViewer(cc.viewer);
+ VncViewer.newViewer();
} else if (actionMatch(ev, options)) {
- cc.options.showDialog(cc.viewport);
+ OptionsDialog.showDialog(cc.desktop);
} else if (actionMatch(ev, save)) {
- JFileChooser fc = new JFileChooser();
- fc.setDialogTitle("Save current configuration as:");
- fc.setApproveButtonText("OK");
- fc.setFileHidingEnabled(false);
- Window fullScreenWindow = Viewport.getFullScreenWindow();
- if (fullScreenWindow != null)
- Viewport.setFullScreenWindow(null);
- int ret = fc.showOpenDialog(cc.viewport);
- if (fullScreenWindow != null)
- Viewport.setFullScreenWindow(fullScreenWindow);
- if (ret == JFileChooser.APPROVE_OPTION) {
- String filename = fc.getSelectedFile().toString();
- if (filename != null)
- Configuration.save(filename);
- }
+ String title = "Save the TigerVNC configuration to file";
+ File dflt = new File(FileUtils.getVncHomeDir().concat("default.tigervnc"));
+ if (!dflt.exists() || !dflt.isFile())
+ dflt = new File(FileUtils.getVncHomeDir());
+ FileNameExtensionFilter filter =
+ new FileNameExtensionFilter("TigerVNC configuration (*.tigervnc)", "tigervnc");
+ File f = Dialog.showChooser(title, dflt, this, filter);
+ while (f != null && f.exists() && f.isFile()) {
+ String msg = f.getAbsolutePath();
+ msg = msg.concat(" already exists. Do you want to overwrite?");
+ Object[] options = {"Overwrite", "No \u21B5"};
+ JOptionPane op =
+ new JOptionPane(msg, JOptionPane.QUESTION_MESSAGE,
+ JOptionPane.OK_CANCEL_OPTION, null, options, options[1]);
+ JDialog dlg = op.createDialog(this, "TigerVNC Viewer");
+ dlg.setIconImage(VncViewer.frameIcon);
+ dlg.setAlwaysOnTop(true);
+ dlg.setVisible(true);
+ if (op.getValue() == options[0])
+ break;
+ else
+ f = Dialog.showChooser(title, f, this, filter);
+ }
+ if (f != null && (!f.exists() || f.canWrite()))
+ saveViewerParameters(f.getAbsolutePath(), vncServerName.getValue());
} else if (actionMatch(ev, info)) {
cc.showInfo();
} else if (actionMatch(ev, about)) {
- cc.showAbout();
+ VncViewer.showAbout(cc.desktop);
} else if (actionMatch(ev, dismiss)) {
firePopupMenuCanceled();
}
}
+ public void show(Component invoker, int x, int y) {
+ // lightweight components can't show in FullScreen Exclusive mode
+ /*
+ Window fsw = DesktopWindow.getFullScreenWindow();
+ GraphicsDevice gd = null;
+ if (fsw != null) {
+ gd = fsw.getGraphicsConfiguration().getDevice();
+ if (gd.isFullScreenSupported())
+ DesktopWindow.setFullScreenWindow(null);
+ }
+ */
+ super.show(invoker, x, y);
+ /*
+ if (fsw != null && gd.isFullScreenSupported())
+ DesktopWindow.setFullScreenWindow(fsw);
+ */
+ }
+
CConn cc;
JMenuItem restore, move, size, minimize, maximize;
JMenuItem exit, clipboard, ctrlAltDel, refresh;
JMenuItem newConn, options, save, info, about, dismiss;
static JMenuItem f8;
- JCheckBoxMenuItem fullScreen;
+ JCheckBoxMenuItem fullScreenCheckbox;
static LogWriter vlog = new LogWriter("F8Menu");
}
diff --git a/java/com/tigervnc/vncviewer/JavaPixelBuffer.java b/java/com/tigervnc/vncviewer/JavaPixelBuffer.java
new file mode 100644
index 0000000..b639673
--- /dev/null
+++ b/java/com/tigervnc/vncviewer/JavaPixelBuffer.java
@@ -0,0 +1,59 @@
+/* Copyright (C) 2012-2016 Brian P. Hinz
+ * Copyright (C) 2012 D. R. Commander. All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+package com.tigervnc.vncviewer;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.nio.ByteOrder;
+
+import com.tigervnc.rfb.*;
+
+public class JavaPixelBuffer extends PlatformPixelBuffer
+{
+
+ public JavaPixelBuffer(int w, int h) {
+ super(getPreferredPF(), w, h,
+ getPreferredPF().getColorModel().createCompatibleWritableRaster(w,h));
+ }
+
+ private static PixelFormat getPreferredPF()
+ {
+ GraphicsEnvironment ge =
+ GraphicsEnvironment.getLocalGraphicsEnvironment();
+ GraphicsDevice gd = ge.getDefaultScreenDevice();
+ GraphicsConfiguration gc = gd.getDefaultConfiguration();
+ ColorModel cm = gc.getColorModel();
+ int depth = ((cm.getPixelSize() > 24) ? 24 : cm.getPixelSize());
+ int bpp = (depth > 16 ? 32 : (depth > 8 ? 16 : 8));
+ ByteOrder byteOrder = ByteOrder.nativeOrder();
+ boolean bigEndian = (byteOrder == ByteOrder.BIG_ENDIAN ? true : false);
+ boolean trueColour = true;
+ int redShift = cm.getComponentSize()[0] + cm.getComponentSize()[1];
+ int greenShift = cm.getComponentSize()[0];
+ int blueShift = 0;
+ int redMask = ((int)Math.pow(2, cm.getComponentSize()[2]) - 1);
+ int greenMask = ((int)Math.pow(2, cm.getComponentSize()[1]) - 1);
+ int blueMmask = ((int)Math.pow(2, cm.getComponentSize()[0]) - 1);
+ return new PixelFormat(bpp, depth, bigEndian, trueColour,
+ redMask, greenMask, blueMmask,
+ redShift, greenShift, blueShift);
+ }
+
+}
diff --git a/java/com/tigervnc/vncviewer/OptionsDialog.java b/java/com/tigervnc/vncviewer/OptionsDialog.java
index 369b965..a7c8778 100644
--- a/java/com/tigervnc/vncviewer/OptionsDialog.java
+++ b/java/com/tigervnc/vncviewer/OptionsDialog.java
@@ -22,14 +22,18 @@
import java.awt.*;
import java.awt.event.*;
import java.io.File;
+import java.lang.reflect.*;
import java.text.Format;
import java.text.NumberFormat;
import javax.swing.*;
import javax.swing.border.*;
+import javax.swing.filechooser.*;
import javax.swing.UIManager.*;
import javax.swing.text.*;
import java.util.*;
+import java.util.List;
import java.util.Map.Entry;
+import java.util.prefs.*;
import com.tigervnc.rfb.*;
@@ -44,6 +48,8 @@
import static java.awt.GridBagConstraints.REMAINDER;
import static java.awt.GridBagConstraints.VERTICAL;
+import static com.tigervnc.vncviewer.Parameters.*;
+
class OptionsDialog extends Dialog {
private class IntegerDocument extends PlainDocument {
@@ -88,47 +94,558 @@
}
}
- // Constants
- static LogWriter vlog = new LogWriter("OptionsDialog");
+ private static Map<Object, String> callbacks = new HashMap<Object, String>();
+ /* Compression */
+ JCheckBox autoselectCheckbox;
- CConn cc;
- @SuppressWarnings({"rawtypes"})
- JComboBox menuKey, compressLevel, qualityLevel, scalingFactor;
- ButtonGroup encodingGroup, colourGroup, sshArgsGroup;
- JRadioButton zrle, hextile, tight, raw, fullColour, mediumColour,
- lowColour, veryLowColour, sshArgsDefault, sshArgsCustom;
- JCheckBox autoSelect, customCompressLevel, noJpeg, viewOnly,
- acceptClipboard, sendClipboard, acceptBell, desktopSize,
- fullScreen, fullScreenAllMonitors, shared, useLocalCursor,
- secVeNCrypt, encNone, encTLS, encX509, secNone, secVnc,
- secPlain, secIdent, sendLocalUsername, sshTunnel, sshUseExt,
- sshUseGateway;
- JButton okButton, cancelButton, caButton, crlButton, cfLoadButton,
- cfSaveAsButton, defSaveButton, defReloadButton, defClearButton,
- sshConfigBrowser, sshKeyFileBrowser, sshClientBrowser;
- JTextField desktopWidth, desktopHeight, x509ca, x509crl, sshUser, sshHost,
- sshPort, sshClient, sshArguments, sshConfig, sshKeyFile;
- JTabbedPane tabPane;
+ ButtonGroup encodingGroup;
+ JRadioButton tightButton;
+ JRadioButton zrleButton;
+ JRadioButton hextileButton;
+ JRadioButton rawButton;
+
+ ButtonGroup colorlevelGroup;
+ JRadioButton fullcolorButton;
+ JRadioButton mediumcolorButton;
+ JRadioButton lowcolorButton;
+ JRadioButton verylowcolorButton;
+
+ JCheckBox compressionCheckbox;
+ JCheckBox jpegCheckbox;
+ JComboBox compressionInput;
+ JComboBox jpegInput;
+
+ /* Security */
+ JCheckBox encNoneCheckbox;
+ JCheckBox encTLSCheckbox;
+ JCheckBox encX509Checkbox;
+ JTextField caInput;
+ JTextField crlInput;
+ JButton caChooser;
+ JButton crlChooser;
+
+ JCheckBox authNoneCheckbox;
+ JCheckBox authVncCheckbox;
+ JCheckBox authPlainCheckbox;
+ JCheckBox authIdentCheckbox;
+ JCheckBox sendLocalUsernameCheckbox;
+
+ /* Input */
+ JCheckBox viewOnlyCheckbox;
+ JCheckBox acceptClipboardCheckbox;
+ JCheckBox sendClipboardCheckbox;
+ JComboBox menuKeyChoice;
+
+ /* Screen */
+ JCheckBox desktopSizeCheckbox;
+ JTextField desktopWidthInput;
+ JTextField desktopHeightInput;
+
+ ButtonGroup sizingGroup;
+ JRadioButton remoteResizeButton;
+ JRadioButton remoteScaleButton;
+ JComboBox scalingFactorInput;
+
+ JCheckBox fullScreenCheckbox;
+ JCheckBox fullScreenAllMonitorsCheckbox;
+
+ /* Misc. */
+ JCheckBox sharedCheckbox;
+ JCheckBox dotWhenNoCursorCheckbox;
+ JCheckBox acceptBellCheckbox;
+
+ /* SSH */
+ JCheckBox tunnelCheckbox;
+ JCheckBox viaCheckbox;
+ JTextField viaUserInput;
+ JTextField viaHostInput;
+ JTextField viaPortInput;
+ JCheckBox extSSHCheckbox;
+ JTextField sshClientInput;
+ JButton sshClientChooser;
+ JRadioButton sshArgsDefaultButton;
+ JRadioButton sshArgsCustomButton;
+ JTextField sshArgsInput;
+ JTextField sshConfigInput;
+ JTextField sshKeyFileInput;
+ JButton sshConfigChooser;
+ JButton sshKeyFileChooser;
@SuppressWarnings({"rawtypes","unchecked"})
- public OptionsDialog(CConn cc_) {
+ public OptionsDialog() {
super(true);
- cc = cc_;
setTitle("VNC Viewer Options");
setResizable(false);
getContentPane().setLayout(
new BoxLayout(getContentPane(), BoxLayout.PAGE_AXIS));
- tabPane = new JTabbedPane();
+ JTabbedPane tabPane = new JTabbedPane();
tabPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
encodingGroup = new ButtonGroup();
- colourGroup = new ButtonGroup();
- sshArgsGroup = new ButtonGroup();
- int indent = 0;
+ colorlevelGroup = new ButtonGroup();
- // Compression tab
+ // tabPane
+ tabPane.addTab("Compression", createCompressionPanel());
+ tabPane.addTab("Security", createSecurityPanel());
+ tabPane.addTab("Input", createInputPanel());
+ tabPane.addTab("Screen", createScreenPanel());
+ tabPane.addTab("Misc", createMiscPanel());
+ tabPane.addTab("SSH", createSshPanel());
+ tabPane.setBorder(BorderFactory.createEmptyBorder());
+ // Resize the tabPane if necessary to prevent scrolling
+ int minWidth = 0;
+ Object tpi = UIManager.get("TabbedPane:TabbedPaneTabArea.contentMargins");
+ if (tpi != null)
+ minWidth += ((Insets)tpi).left + ((Insets)tpi).right;
+ for (int i = 0; i < tabPane.getTabCount(); i++)
+ minWidth += tabPane.getBoundsAt(i).width;
+ int minHeight = tabPane.getPreferredSize().height;
+ if (tabPane.getPreferredSize().width < minWidth)
+ tabPane.setPreferredSize(new Dimension(minWidth, minHeight));
+
+ // button pane
+ JButton okButton = new JButton("OK \u21B5");
+ okButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ storeOptions();
+ endDialog();
+ }
+ });
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ endDialog();
+ }
+ });
+
+ JPanel buttonPane = new JPanel(new GridLayout(1, 5, 10, 10));
+ buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 5, 5, 5));
+ buttonPane.add(Box.createRigidArea(new Dimension()));
+ buttonPane.add(Box.createRigidArea(new Dimension()));
+ buttonPane.add(Box.createRigidArea(new Dimension()));
+ buttonPane.add(cancelButton);
+ buttonPane.add(okButton);
+
+ this.add(tabPane);
+ this.add(buttonPane);
+ addListeners(this);
+ pack();
+ }
+
+ public static void showDialog(Container c) {
+ OptionsDialog dialog = new OptionsDialog();
+ dialog.show(c);
+ }
+
+ public void show(Container c) {
+ loadOptions();
+ super.showDialog(c);
+ }
+
+ public static void addCallback(String cb, Object obj)
+ {
+ callbacks.put(obj, cb);
+ }
+
+ public static void removeCallback(Object obj)
+ {
+ callbacks.remove(obj);
+ }
+
+ public void endDialog() {
+ super.endDialog();
+ // Making a new dialog is so cheap that it's not worth keeping
+ this.dispose();
+ }
+
+ public void setEmbeddedFeatures(boolean s) {
+ fullScreenCheckbox.setEnabled(s);
+ fullScreenAllMonitorsCheckbox.setEnabled(s);
+ scalingFactorInput.setEnabled(s);
+ Enumeration<AbstractButton> e = sizingGroup.getElements();
+ while (e.hasMoreElements())
+ e.nextElement().setEnabled(s);
+ }
+
+ private void loadOptions()
+ {
+ /* Compression */
+ autoselectCheckbox.setSelected(autoSelect.getValue());
+
+ int encNum = Encodings.encodingNum(preferredEncoding.getValueStr());
+
+ switch (encNum) {
+ case Encodings.encodingTight:
+ tightButton.setSelected(true);
+ break;
+ case Encodings.encodingZRLE:
+ zrleButton.setSelected(true);
+ break;
+ case Encodings.encodingHextile:
+ hextileButton.setSelected(true);
+ break;
+ case Encodings.encodingRaw:
+ rawButton.setSelected(true);
+ break;
+ }
+
+ if (fullColor.getValue())
+ fullcolorButton.setSelected(true);
+ else {
+ switch (lowColorLevel.getValue()) {
+ case 0:
+ verylowcolorButton.setSelected(true);
+ break;
+ case 1:
+ lowcolorButton.setSelected(true);
+ break;
+ case 2:
+ mediumcolorButton.setSelected(true);
+ break;
+ }
+ }
+
+ int digit = 0;
+
+ compressionCheckbox.setSelected(customCompressLevel.getValue());
+ jpegCheckbox.setSelected(!noJpeg.getValue());
+ digit = 0 + compressLevel.getValue();
+ compressionInput.setSelectedItem(digit);
+ digit = 0 + qualityLevel.getValue();
+ jpegInput.setSelectedItem(digit);
+
+ handleAutoselect();
+ handleCompression();
+ handleJpeg();
+
+ /* Security */
+ Security security = new Security(SecurityClient.secTypes);
+
+ List<Integer> secTypes;
+ Iterator<Integer> iter;
+
+ List<Integer> secTypesExt;
+ Iterator<Integer> iterExt;
+
+ encNoneCheckbox.setSelected(false);
+ encTLSCheckbox.setSelected(false);
+ encX509Checkbox.setSelected(false);
+
+ authNoneCheckbox.setSelected(false);
+ authVncCheckbox.setSelected(false);
+ authPlainCheckbox.setSelected(false);
+ authIdentCheckbox.setSelected(false);
+ sendLocalUsernameCheckbox.setSelected(sendLocalUsername.getValue());
+
+ secTypes = security.GetEnabledSecTypes();
+ for (iter = secTypes.iterator(); iter.hasNext(); ) {
+ switch ((Integer)iter.next()) {
+ case Security.secTypeNone:
+ encNoneCheckbox.setSelected(true);
+ authNoneCheckbox.setSelected(true);
+ break;
+ case Security.secTypeVncAuth:
+ encNoneCheckbox.setSelected(true);
+ authVncCheckbox.setSelected(true);
+ break;
+ }
+ }
+
+ secTypesExt = security.GetEnabledExtSecTypes();
+ for (iterExt = secTypesExt.iterator(); iterExt.hasNext(); ) {
+ switch ((Integer)iterExt.next()) {
+ case Security.secTypePlain:
+ encNoneCheckbox.setSelected(true);
+ authPlainCheckbox.setSelected(true);
+ break;
+ case Security.secTypeIdent:
+ encNoneCheckbox.setSelected(true);
+ authIdentCheckbox.setSelected(true);
+ break;
+ case Security.secTypeTLSNone:
+ encTLSCheckbox.setSelected(true);
+ authNoneCheckbox.setSelected(true);
+ break;
+ case Security.secTypeTLSVnc:
+ encTLSCheckbox.setSelected(true);
+ authVncCheckbox.setSelected(true);
+ break;
+ case Security.secTypeTLSPlain:
+ encTLSCheckbox.setSelected(true);
+ authPlainCheckbox.setSelected(true);
+ break;
+ case Security.secTypeTLSIdent:
+ encTLSCheckbox.setSelected(true);
+ authIdentCheckbox.setSelected(true);
+ break;
+ case Security.secTypeX509None:
+ encX509Checkbox.setSelected(true);
+ authNoneCheckbox.setSelected(true);
+ break;
+ case Security.secTypeX509Vnc:
+ encX509Checkbox.setSelected(true);
+ authVncCheckbox.setSelected(true);
+ break;
+ case Security.secTypeX509Plain:
+ encX509Checkbox.setSelected(true);
+ authPlainCheckbox.setSelected(true);
+ break;
+ case Security.secTypeX509Ident:
+ encX509Checkbox.setSelected(true);
+ authIdentCheckbox.setSelected(true);
+ break;
+ }
+ }
+
+ File caFile = new File(CSecurityTLS.X509CA.getValueStr());
+ if (caFile.exists() && caFile.canRead())
+ caInput.setText(caFile.getAbsolutePath());
+ File crlFile = new File(CSecurityTLS.X509CRL.getValueStr());
+ if (crlFile.exists() && crlFile.canRead())
+ crlInput.setText(crlFile.getAbsolutePath());
+
+ handleX509();
+ handleSendLocalUsername();
+
+ /* Input */
+ viewOnlyCheckbox.setSelected(viewOnly.getValue());
+ acceptClipboardCheckbox.setSelected(acceptClipboard.getValue());
+ sendClipboardCheckbox.setSelected(sendClipboard.getValue());
+
+ menuKeyChoice.setSelectedIndex(0);
+
+ String menuKeyStr = menuKey.getValueStr();
+ for (int i = 0; i < menuKeyChoice.getItemCount(); i++)
+ if (menuKeyStr.equals(menuKeyChoice.getItemAt(i)))
+ menuKeyChoice.setSelectedIndex(i);
+
+ /* Screen */
+ String width, height;
+
+ if (desktopSize.getValueStr().isEmpty() ||
+ desktopSize.getValueStr().split("x").length != 2) {
+ desktopSizeCheckbox.setSelected(false);
+ desktopWidthInput.setText("1024");
+ desktopHeightInput.setText("768");
+ } else {
+ desktopSizeCheckbox.setSelected(true);
+ width = desktopSize.getValueStr().split("x")[0];
+ desktopWidthInput.setText(width);
+ height = desktopSize.getValueStr().split("x")[1];
+ desktopHeightInput.setText(height);
+ }
+ if (remoteResize.getValue())
+ remoteResizeButton.setSelected(true);
+ else
+ remoteScaleButton.setSelected(true);
+ fullScreenCheckbox.setSelected(fullScreen.getValue());
+ fullScreenAllMonitorsCheckbox.setSelected(fullScreenAllMonitors.getValue());
+
+ scalingFactorInput.setSelectedItem("100%");
+ String scaleStr = scalingFactor.getValueStr();
+ if (scaleStr.matches("^[0-9]+$"))
+ scaleStr = scaleStr.concat("%");
+ if (scaleStr.matches("^FixedRatio$"))
+ scaleStr = new String("Fixed Aspect Ratio");
+ for (int i = 0; i < scalingFactorInput.getItemCount(); i++)
+ if (scaleStr.equals(scalingFactorInput.getItemAt(i)))
+ scalingFactorInput.setSelectedIndex(i);
+
+ handleDesktopSize();
+
+ /* Misc. */
+ sharedCheckbox.setSelected(shared.getValue());
+ dotWhenNoCursorCheckbox.setSelected(dotWhenNoCursor.getValue());
+ acceptBellCheckbox.setSelected(acceptBell.getValue());
+
+ /* SSH */
+ File f;
+ tunnelCheckbox.setSelected(tunnel.getValue() || !via.getValueStr().isEmpty());
+ viaCheckbox.setSelected(!via.getValueStr().isEmpty());
+ if (viaCheckbox.isSelected()) {
+ viaUserInput.setText(Tunnel.getSshUser());
+ viaHostInput.setText(Tunnel.getSshHost());
+ viaPortInput.setText(Integer.toString(Tunnel.getSshPort()));
+ }
+ extSSHCheckbox.setSelected(extSSH.getValue());
+ f = new File(extSSHClient.getValueStr());
+ if (f.exists() && f.isFile() && f.canExecute())
+ sshClientInput.setText(f.getAbsolutePath());
+ if (extSSHArgs.getValueStr().isEmpty()) {
+ sshArgsDefaultButton.setSelected(true);
+ } else {
+ sshArgsCustomButton.setSelected(true);
+ sshArgsInput.setText(extSSHArgs.getValueStr());
+ }
+ f = new File(sshKeyFile.getValueStr());
+ if (f.exists() && f.isFile() && f.canRead())
+ sshKeyFileInput.setText(f.getAbsolutePath());
+ f = new File(sshConfig.getValueStr());
+ if (f.exists() && f.isFile() && f.canRead())
+ sshConfigInput.setText(f.getAbsolutePath());
+
+ handleTunnel();
+ handleVia();
+ handleExtSSH();
+ handleEmbed();
+ handleRfbState();
+ }
+
+ private void storeOptions() {
+ /* Compression */
+ autoSelect.setParam(autoselectCheckbox.isSelected());
+
+ if (tightButton.isSelected())
+ preferredEncoding.setParam(Encodings.encodingName(Encodings.encodingTight));
+ else if (zrleButton.isSelected())
+ preferredEncoding.setParam(Encodings.encodingName(Encodings.encodingZRLE));
+ else if (hextileButton.isSelected())
+ preferredEncoding.setParam(Encodings.encodingName(Encodings.encodingHextile));
+ else if (rawButton.isSelected())
+ preferredEncoding.setParam(Encodings.encodingName(Encodings.encodingRaw));
+
+ fullColor.setParam(fullcolorButton.isSelected());
+ if (verylowcolorButton.isSelected())
+ lowColorLevel.setParam(0);
+ else if (lowcolorButton.isSelected())
+ lowColorLevel.setParam(1);
+ else if (mediumcolorButton.isSelected())
+ lowColorLevel.setParam(2);
+
+ customCompressLevel.setParam(compressionCheckbox.isSelected());
+ noJpeg.setParam(!jpegCheckbox.isSelected());
+ compressLevel.setParam((Integer)compressionInput.getSelectedItem());
+ qualityLevel.setParam((Integer)jpegInput.getSelectedItem());
+
+ /* Security */
+ Security security = new Security();
+
+ /* Process security types which don't use encryption */
+ if (encNoneCheckbox.isSelected()) {
+ if (authNoneCheckbox.isSelected())
+ security.EnableSecType(Security.secTypeNone);
+ if (authVncCheckbox.isSelected())
+ security.EnableSecType(Security.secTypeVncAuth);
+ if (authPlainCheckbox.isSelected())
+ security.EnableSecType(Security.secTypePlain);
+ if (authIdentCheckbox.isSelected())
+ security.EnableSecType(Security.secTypeIdent);
+ }
+
+ /* Process security types which use TLS encryption */
+ if (encTLSCheckbox.isSelected()) {
+ if (authNoneCheckbox.isSelected())
+ security.EnableSecType(Security.secTypeTLSNone);
+ if (authVncCheckbox.isSelected())
+ security.EnableSecType(Security.secTypeTLSVnc);
+ if (authPlainCheckbox.isSelected())
+ security.EnableSecType(Security.secTypeTLSPlain);
+ if (authIdentCheckbox.isSelected())
+ security.EnableSecType(Security.secTypeTLSIdent);
+ }
+
+ /* Process security types which use X509 encryption */
+ if (encX509Checkbox.isSelected()) {
+ if (authNoneCheckbox.isSelected())
+ security.EnableSecType(Security.secTypeX509None);
+ if (authVncCheckbox.isSelected())
+ security.EnableSecType(Security.secTypeX509Vnc);
+ if (authPlainCheckbox.isSelected())
+ security.EnableSecType(Security.secTypeX509Plain);
+ if (authIdentCheckbox.isSelected())
+ security.EnableSecType(Security.secTypeX509Ident);
+ }
+
+ if (authIdentCheckbox.isSelected() ||
+ authPlainCheckbox.isSelected()) {
+ sendLocalUsername.setParam(sendLocalUsernameCheckbox.isSelected());
+ }
+
+ SecurityClient.secTypes.setParam(security.ToString());
+
+ File caFile = new File(caInput.getText());
+ if (caFile.exists() && caFile.canRead())
+ CSecurityTLS.X509CA.setParam(caFile.getAbsolutePath());
+ File crlFile = new File(crlInput.getText());
+ if (crlFile.exists() && crlFile.canRead())
+ CSecurityTLS.X509CRL.setParam(crlFile.getAbsolutePath());
+
+ /* Input */
+ viewOnly.setParam(viewOnlyCheckbox.isSelected());
+ acceptClipboard.setParam(acceptClipboardCheckbox.isSelected());
+ sendClipboard.setParam(sendClipboardCheckbox.isSelected());
+
+ String menuKeyStr =
+ MenuKey.getMenuKeySymbols()[menuKeyChoice.getSelectedIndex()].name;
+ menuKey.setParam(menuKeyStr);
+
+ /* Screen */
+ if (desktopSizeCheckbox.isSelected() &&
+ !desktopWidthInput.getText().isEmpty() &&
+ !desktopHeightInput.getText().isEmpty()) {
+ String width = desktopWidthInput.getText();
+ String height = desktopHeightInput.getText();
+ desktopSize.setParam(width.concat("x").concat(height));
+ } else {
+ desktopSize.setParam("");
+ }
+ remoteResize.setParam(remoteResizeButton.isSelected());
+ fullScreen.setParam(fullScreenCheckbox.isSelected());
+ fullScreenAllMonitors.setParam(fullScreenAllMonitorsCheckbox.isSelected());
+
+ String scaleStr =
+ ((String)scalingFactorInput.getSelectedItem()).replace("%", "");
+ scaleStr.replace("Fixed Aspect Ratio", "FixedRatio");
+ scalingFactor.setParam(scaleStr);
+
+ /* Misc. */
+ shared.setParam(sharedCheckbox.isSelected());
+ dotWhenNoCursor.setParam(dotWhenNoCursorCheckbox.isSelected());
+ acceptBell.setParam(acceptBellCheckbox.isSelected());
+
+ /* SSH */
+ tunnel.setParam(tunnelCheckbox.isSelected());
+ if (viaCheckbox.isSelected() &&
+ !viaUserInput.getText().isEmpty() &&
+ !viaHostInput.getText().isEmpty() &&
+ !viaPortInput.getText().isEmpty()) {
+ String sshUser = viaUserInput.getText();
+ String sshHost = viaHostInput.getText();
+ String sshPort = viaPortInput.getText();
+ String viaStr = sshUser.concat("@").concat(sshHost).concat(":").concat(sshPort);
+ via.setParam(viaStr);
+ }
+ extSSH.setParam(extSSHCheckbox.isSelected());
+ if (!sshClientInput.getText().isEmpty())
+ extSSHClient.setParam(sshClientInput.getText());
+ if (sshArgsDefaultButton.isSelected())
+ if (!sshArgsInput.getText().isEmpty())
+ extSSHArgs.setParam(sshArgsInput.getText());
+ if (!sshConfigInput.getText().isEmpty())
+ sshConfig.setParam(sshConfigInput.getText());
+ if (!sshKeyFileInput.getText().isEmpty())
+ sshKeyFile.setParam(sshKeyFileInput.getText());
+
+ try {
+ for (Map.Entry<Object, String> iter : callbacks.entrySet()) {
+ Object obj = iter.getKey();
+ Method cb = obj.getClass().getMethod(iter.getValue(), new Class[]{});
+ if (cb == null)
+ vlog.info(obj.getClass().getName());
+ cb.invoke(obj);
+ }
+ } catch (NoSuchMethodException e) {
+ vlog.error("NoSuchMethodException: "+e.getMessage());
+ } catch (IllegalAccessException e) {
+ vlog.error("IllegalAccessException: "+e.getMessage());
+ } catch (InvocationTargetException e) {
+ vlog.error("InvocationTargetException: "+e.getMessage());
+ }
+ }
+
+ private JPanel createCompressionPanel() {
JPanel FormatPanel = new JPanel();
FormatPanel.setLayout(new BoxLayout(FormatPanel,
BoxLayout.PAGE_AXIS));
@@ -138,61 +655,72 @@
autoSelectPane.setLayout(new BoxLayout(autoSelectPane,
BoxLayout.LINE_AXIS));
autoSelectPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0));
- autoSelect = new JCheckBox("Auto Select");
- autoSelectPane.add(autoSelect);
+ autoselectCheckbox = new JCheckBox("Auto Select");
+ autoselectCheckbox.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ handleAutoselect();
+ }
+ });
+ autoSelectPane.add(autoselectCheckbox);
autoSelectPane.add(Box.createHorizontalGlue());
JPanel encodingPanel = new JPanel(new GridLayout(4, 1));
- encodingPanel.
- setBorder(BorderFactory.createTitledBorder("Preferred encoding"));
- tight = new GroupedJRadioButton("Tight",
- encodingGroup, encodingPanel);
- zrle = new GroupedJRadioButton("ZRLE",
- encodingGroup, encodingPanel);
- hextile = new GroupedJRadioButton("Hextile",
- encodingGroup, encodingPanel);
- raw = new GroupedJRadioButton("Raw", encodingGroup, encodingPanel);
+ encodingPanel.setBorder(BorderFactory.createTitledBorder("Preferred encoding"));
+ tightButton = new GroupedJRadioButton("Tight", encodingGroup, encodingPanel);
+ zrleButton = new GroupedJRadioButton("ZRLE", encodingGroup, encodingPanel);
+ hextileButton = new GroupedJRadioButton("Hextile", encodingGroup, encodingPanel);
+ rawButton = new GroupedJRadioButton("Raw", encodingGroup, encodingPanel);
- JPanel colourPanel = new JPanel(new GridLayout(4, 1));
- colourPanel.setBorder(BorderFactory.createTitledBorder("Color level"));
- fullColour = new GroupedJRadioButton("Full (all available colors)",
- colourGroup, colourPanel);
- mediumColour = new GroupedJRadioButton("Medium (256 colors)",
- colourGroup, colourPanel);
- lowColour = new GroupedJRadioButton("Low (64 colours)",
- colourGroup, colourPanel);
- veryLowColour = new GroupedJRadioButton("Very low(8 colors)",
- colourGroup, colourPanel);
+ JPanel colorPanel = new JPanel(new GridLayout(4, 1));
+ colorPanel.setBorder(BorderFactory.createTitledBorder("Color level"));
+ fullcolorButton = new GroupedJRadioButton("Full (all available colors)",
+ colorlevelGroup, colorPanel);
+ mediumcolorButton = new GroupedJRadioButton("Medium (256 colors)",
+ colorlevelGroup, colorPanel);
+ lowcolorButton = new GroupedJRadioButton("Low (64 colors)",
+ colorlevelGroup, colorPanel);
+ verylowcolorButton = new GroupedJRadioButton("Very low (8 colors)",
+ colorlevelGroup, colorPanel);
JPanel encodingPane = new JPanel(new GridLayout(1, 2, 5, 0));
encodingPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0));
encodingPane.add(encodingPanel);
- encodingPane.add(colourPanel);
+ encodingPane.add(colorPanel);
JPanel tightPanel = new JPanel(new GridBagLayout());
- customCompressLevel = new JCheckBox("Custom Compression Level");
+ compressionCheckbox = new JCheckBox("Custom Compression Level");
+ compressionCheckbox.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ handleCompression();
+ }
+ });
Object[] compressionLevels = { 1, 2, 3, 4, 5, 6 };
- compressLevel = new MyJComboBox(compressionLevels);
- ((MyJComboBox)compressLevel).setDocument(new IntegerDocument(1));
- compressLevel.setPrototypeDisplayValue("0.");
- compressLevel.setEditable(true);
+ compressionInput = new MyJComboBox(compressionLevels);
+ ((MyJComboBox)compressionInput).setDocument(new IntegerDocument(1));
+ compressionInput.setPrototypeDisplayValue("0.");
+ compressionInput.setEditable(true);
JLabel compressionLabel =
new JLabel("Level (1=fast, 6=best [4-6 are rarely useful])");
- noJpeg = new JCheckBox("Allow JPEG Compression");
+ jpegCheckbox = new JCheckBox("Allow JPEG Compression");
+ jpegCheckbox.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ handleJpeg();
+ }
+ });
Object[] qualityLevels = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
- qualityLevel = new MyJComboBox(qualityLevels);
- qualityLevel.setPrototypeDisplayValue("0.");
+ jpegInput = new MyJComboBox(qualityLevels);
+ jpegInput.setPrototypeDisplayValue("0.");
JLabel qualityLabel = new JLabel("Quality (0=poor, 9=best)");
- tightPanel.add(customCompressLevel,
+ tightPanel.add(compressionCheckbox,
new GridBagConstraints(0, 0,
REMAINDER, 1,
LIGHT, LIGHT,
LINE_START, NONE,
new Insets(0, 0, 0, 0),
NONE, NONE));
- indent = getButtonLabelInset(customCompressLevel);
- tightPanel.add(compressLevel,
+ int indent = getButtonLabelInset(compressionCheckbox);
+ tightPanel.add(compressionInput,
new GridBagConstraints(0, 1,
1, 1,
LIGHT, LIGHT,
@@ -206,15 +734,15 @@
LINE_START, HORIZONTAL,
new Insets(0, 5, 0, 0),
NONE, NONE));
- tightPanel.add(noJpeg,
+ tightPanel.add(jpegCheckbox,
new GridBagConstraints(0, 2,
REMAINDER, 1,
LIGHT, LIGHT,
LINE_START, NONE,
new Insets(5, 0, 0, 0),
NONE, NONE));
- indent = getButtonLabelInset(noJpeg);
- tightPanel.add(qualityLevel,
+ indent = getButtonLabelInset(jpegCheckbox);
+ tightPanel.add(jpegInput,
new GridBagConstraints(0, 3,
1, 1,
LIGHT, LIGHT,
@@ -238,8 +766,10 @@
FormatPanel.add(autoSelectPane);
FormatPanel.add(encodingPane);
FormatPanel.add(tightPanel);
+ return FormatPanel;
+ }
- // security tab
+ private JPanel createSecurityPanel() {
JPanel SecPanel = new JPanel();
SecPanel.setLayout(new BoxLayout(SecPanel,
BoxLayout.PAGE_AXIS));
@@ -249,46 +779,67 @@
vencryptPane.setLayout(new BoxLayout(vencryptPane,
BoxLayout.LINE_AXIS));
vencryptPane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
- secVeNCrypt = new JCheckBox("Extended encryption and "+
- "authentication methods (VeNCrypt)");
- vencryptPane.add(secVeNCrypt);
- vencryptPane.add(Box.createHorizontalGlue());
JPanel encrPanel = new JPanel(new GridBagLayout());
encrPanel.setBorder(BorderFactory.createTitledBorder("Encryption"));
- encNone = new JCheckBox("None");
- encTLS = new JCheckBox("Anonymous TLS");
- encX509 = new JCheckBox("TLS with X.509 certificates");
+ encNoneCheckbox = new JCheckBox("None");
+ encTLSCheckbox = new JCheckBox("Anonymous TLS");
+ encX509Checkbox = new JCheckBox("TLS with X.509 certificates");
+ encX509Checkbox.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ handleX509();
+ }
+ });
JLabel caLabel = new JLabel("X.509 CA Certificate");
- x509ca = new JTextField();
- x509ca.setName(Configuration.getParam("x509ca").getName());
- caButton = new JButton("Browse");
+ caInput = new JTextField();
+ caChooser = new JButton("Browse");
+ caChooser.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ JComponent c = ((JButton)e.getSource()).getRootPane();
+ File dflt = new File(CSecurityTLS.X509CA.getValueStr());
+ FileNameExtensionFilter filter =
+ new FileNameExtensionFilter("X.509 certificate", "crt", "cer", "pem");
+ File f = showChooser("Path to X509 CA certificate", dflt, c, filter);
+ if (f != null && f.exists() && f.canRead())
+ caInput.setText(f.getAbsolutePath());
+ }
+ });
JLabel crlLabel = new JLabel("X.509 CRL file");
- x509crl = new JTextField();
- x509crl.setName(Configuration.getParam("x509crl").getName());
- crlButton = new JButton("Browse");
- encrPanel.add(encNone,
+ crlInput = new JTextField();
+ crlChooser = new JButton("Browse");
+ crlChooser.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ JComponent c = ((JButton)e.getSource()).getRootPane();
+ File dflt = new File(CSecurityTLS.X509CRL.getValueStr());
+ FileNameExtensionFilter filter =
+ new FileNameExtensionFilter("X.509 CRL", "crl");
+ File f = showChooser("Path to X509 CRL file", dflt, c, filter);
+ if (f != null && f.exists() && f.canRead())
+ crlInput.setText(f.getAbsolutePath());
+ }
+ });
+ encrPanel.add(encNoneCheckbox,
new GridBagConstraints(0, 0,
REMAINDER, 1,
HEAVY, LIGHT,
LINE_START, NONE,
new Insets(0, 0, 4, 0),
NONE, NONE));
- encrPanel.add(encTLS,
+ encrPanel.add(encTLSCheckbox,
new GridBagConstraints(0, 1,
REMAINDER, 1,
HEAVY, LIGHT,
LINE_START, NONE,
new Insets(0, 0, 4, 0),
NONE, NONE));
- encrPanel.add(encX509,
+ encrPanel.add(encX509Checkbox,
new GridBagConstraints(0, 2,
3, 1,
HEAVY, LIGHT,
LINE_START, NONE,
new Insets(0, 0, 0, 0),
NONE, NONE));
- indent = getButtonLabelInset(encX509);
+ int indent = getButtonLabelInset(encX509Checkbox);
encrPanel.add(caLabel,
new GridBagConstraints(0, 3,
1, 1,
@@ -296,14 +847,14 @@
LINE_END, NONE,
new Insets(0, indent, 5, 0),
0, 0));
- encrPanel.add(x509ca,
+ encrPanel.add(caInput,
new GridBagConstraints(1, 3,
1, 1,
HEAVY, LIGHT,
LINE_START, HORIZONTAL,
new Insets(0, 5, 5, 0),
0, 0));
- encrPanel.add(caButton,
+ encrPanel.add(caChooser,
new GridBagConstraints(2, 3,
1, 1,
LIGHT, LIGHT,
@@ -317,14 +868,14 @@
LINE_END, NONE,
new Insets(0, indent, 0, 0),
0, 0));
- encrPanel.add(x509crl,
+ encrPanel.add(crlInput,
new GridBagConstraints(1, 4,
1, 1,
HEAVY, LIGHT,
LINE_START, HORIZONTAL,
new Insets(0, 5, 0, 0),
0, 0));
- encrPanel.add(crlButton,
+ encrPanel.add(crlChooser,
new GridBagConstraints(2, 4,
1, 1,
LIGHT, LIGHT,
@@ -335,40 +886,50 @@
JPanel authPanel = new JPanel(new GridBagLayout());
authPanel.setBorder(BorderFactory.createTitledBorder("Authentication"));
- secNone = new JCheckBox("None");
- secVnc = new JCheckBox("Standard VNC");
- secPlain = new JCheckBox("Plaintext");
- secIdent = new JCheckBox("Ident");
- sendLocalUsername = new JCheckBox("Send Local Username");
- authPanel.add(secNone,
+ authNoneCheckbox = new JCheckBox("None");
+ authVncCheckbox = new JCheckBox("Standard VNC");
+ authPlainCheckbox = new JCheckBox("Plaintext");
+ authPlainCheckbox.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ handleSendLocalUsername();
+ }
+ });
+ authIdentCheckbox = new JCheckBox("Ident");
+ authIdentCheckbox.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ handleSendLocalUsername();
+ }
+ });
+ sendLocalUsernameCheckbox = new JCheckBox("Send Local Username");
+ authPanel.add(authNoneCheckbox,
new GridBagConstraints(0, 0,
REMAINDER, 1,
LIGHT, LIGHT,
LINE_START, NONE,
new Insets(0, 0, 4, 0),
NONE, NONE));
- authPanel.add(secVnc,
+ authPanel.add(authVncCheckbox,
new GridBagConstraints(0, 1,
REMAINDER, 1,
LIGHT, LIGHT,
LINE_START, NONE,
new Insets(0, 0, 4, 0),
NONE, NONE));
- authPanel.add(secPlain,
+ authPanel.add(authPlainCheckbox,
new GridBagConstraints(0, 2,
1, 1,
LIGHT, LIGHT,
LINE_START, NONE,
new Insets(0, 0, 2, 0),
NONE, NONE));
- authPanel.add(secIdent,
+ authPanel.add(authIdentCheckbox,
new GridBagConstraints(0, 3,
1, 1,
LIGHT, LIGHT,
LINE_START, NONE,
new Insets(2, 0, 0, 0),
NONE, NONE));
- authPanel.add(sendLocalUsername,
+ authPanel.add(sendLocalUsernameCheckbox,
new GridBagConstraints(1, 2,
1, 2,
HEAVY, LIGHT,
@@ -404,35 +965,40 @@
LINE_START, BOTH,
new Insets(0, 0, 0, 0),
NONE, NONE));
+ return SecPanel;
+ }
- // Input tab
+ private JPanel createInputPanel() {
JPanel inputPanel = new JPanel(new GridBagLayout());
inputPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
- viewOnly = new JCheckBox("View Only (ignore mouse & keyboard)");
- acceptClipboard = new JCheckBox("Accept clipboard from server");
- sendClipboard = new JCheckBox("Send clipboard to server");
- JLabel menuKeyLabel = new JLabel("Menu Key");
+ viewOnlyCheckbox = new JCheckBox("View only (ignore mouse and keyboard)");
+ acceptClipboardCheckbox = new JCheckBox("Accept clipboard from server");
+ sendClipboardCheckbox = new JCheckBox("Send clipboard to server");
+ JLabel menuKeyLabel = new JLabel("Menu key");
String[] menuKeys = new String[MenuKey.getMenuKeySymbolCount()];
+ //String[] menuKeys = new String[MenuKey.getMenuKeySymbolCount()+1];
+ //menuKeys[0] = "None";
for (int i = 0; i < MenuKey.getMenuKeySymbolCount(); i++)
menuKeys[i] = MenuKey.getKeyText(MenuKey.getMenuKeySymbols()[i]);
- menuKey = new JComboBox(menuKeys);
+ //menuKeys[i+1] = MenuKey.getKeyText(MenuKey.getMenuKeySymbols()[i]);
+ menuKeyChoice = new JComboBox(menuKeys);
- inputPanel.add(viewOnly,
+ inputPanel.add(viewOnlyCheckbox,
new GridBagConstraints(0, 0,
REMAINDER, 1,
HEAVY, LIGHT,
LINE_START, NONE,
new Insets(0, 0, 4, 0),
NONE, NONE));
- inputPanel.add(acceptClipboard,
+ inputPanel.add(acceptClipboardCheckbox,
new GridBagConstraints(0, 1,
REMAINDER, 1,
HEAVY, LIGHT,
LINE_START, NONE,
new Insets(0, 0, 4, 0),
NONE, NONE));
- inputPanel.add(sendClipboard,
+ inputPanel.add(sendClipboardCheckbox,
new GridBagConstraints(0, 2,
REMAINDER, 1,
HEAVY, LIGHT,
@@ -446,7 +1012,7 @@
LINE_START, NONE,
new Insets(0, 0, 0, 0),
NONE, NONE));
- inputPanel.add(menuKey,
+ inputPanel.add(menuKeyChoice,
new GridBagConstraints(1, 3,
1, 1,
HEAVY, LIGHT,
@@ -460,108 +1026,147 @@
LINE_START, BOTH,
new Insets(0, 0, 0, 0),
NONE, NONE));
+ return inputPanel;
+ }
- // Screen tab
+ private JPanel createScreenPanel() {
JPanel ScreenPanel = new JPanel(new GridBagLayout());
ScreenPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
- desktopSize = new JCheckBox("Resize remote session on connect");
- desktopSize.setEnabled(!cc.viewer.embed.getValue() &&
- (cc.viewer.desktopSize.getValue() != null));
- desktopWidth = new IntegerTextField(5);
- desktopWidth.setEnabled(desktopSize.isSelected());
- desktopHeight = new IntegerTextField(5);
- desktopHeight.setEnabled(desktopSize.isSelected());
+
+ JPanel SizingPanel = new JPanel(new GridBagLayout());
+ SizingPanel.setBorder(BorderFactory.createTitledBorder("Desktop Sizing"));
+ desktopSizeCheckbox = new JCheckBox("Resize remote session on connect");
+ desktopSizeCheckbox.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ handleDesktopSize();
+ }
+ });
+ desktopWidthInput = new IntegerTextField(5);
+ desktopHeightInput = new IntegerTextField(5);
JPanel desktopSizePanel =
new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
- desktopSizePanel.add(desktopWidth);
+ desktopSizePanel.add(desktopWidthInput);
desktopSizePanel.add(new JLabel(" x "));
- desktopSizePanel.add(desktopHeight);
- fullScreen = new JCheckBox("Full-screen mode");
- fullScreen.setEnabled(!cc.viewer.embed.getValue());
- fullScreenAllMonitors =
- new JCheckBox("Enable full-screen mode over all monitors");
- fullScreenAllMonitors.setEnabled(!cc.viewer.embed.getValue());
+ desktopSizePanel.add(desktopHeightInput);
+ sizingGroup = new ButtonGroup();
+ remoteResizeButton =
+ new JRadioButton("Resize remote session to the local window");
+ sizingGroup.add(remoteResizeButton);
+ remoteScaleButton =
+ new JRadioButton("Scale remote session to the local window");
+ sizingGroup.add(remoteScaleButton);
+ remoteResizeButton.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ handleRemoteResize();
+ }
+ });
JLabel scalingFactorLabel = new JLabel("Scaling Factor");
Object[] scalingFactors = {
"Auto", "Fixed Aspect Ratio", "50%", "75%", "95%", "100%", "105%",
"125%", "150%", "175%", "200%", "250%", "300%", "350%", "400%" };
- scalingFactor = new MyJComboBox(scalingFactors);
- scalingFactor.setEditable(true);
- scalingFactor.setEnabled(!cc.viewer.embed.getValue());
- ScreenPanel.add(desktopSize,
+ scalingFactorInput = new MyJComboBox(scalingFactors);
+ scalingFactorInput.setEditable(true);
+ fullScreenCheckbox = new JCheckBox("Full-screen mode");
+ fullScreenAllMonitorsCheckbox =
+ new JCheckBox("Enable full-screen mode over all monitors");
+ SizingPanel.add(desktopSizeCheckbox,
new GridBagConstraints(0, 0,
REMAINDER, 1,
LIGHT, LIGHT,
LINE_START, NONE,
new Insets(0, 0, 0, 0),
NONE, NONE));
- indent = getButtonLabelInset(desktopSize);
- ScreenPanel.add(desktopSizePanel,
+ int indent = getButtonLabelInset(desktopSizeCheckbox);
+ SizingPanel.add(desktopSizePanel,
new GridBagConstraints(0, 1,
REMAINDER, 1,
LIGHT, LIGHT,
LINE_START, NONE,
new Insets(0, indent, 0, 0),
NONE, NONE));
- ScreenPanel.add(fullScreen,
+ SizingPanel.add(remoteResizeButton,
new GridBagConstraints(0, 2,
REMAINDER, 1,
LIGHT, LIGHT,
LINE_START, NONE,
new Insets(0, 0, 4, 0),
NONE, NONE));
- indent = getButtonLabelInset(fullScreen);
- ScreenPanel.add(fullScreenAllMonitors,
+ SizingPanel.add(remoteScaleButton,
new GridBagConstraints(0, 3,
REMAINDER, 1,
LIGHT, LIGHT,
LINE_START, NONE,
- new Insets(0, indent, 4, 0),
+ new Insets(0, 0, 4, 0),
NONE, NONE));
- ScreenPanel.add(scalingFactorLabel,
+ indent = getButtonLabelInset(remoteScaleButton);
+ SizingPanel.add(scalingFactorLabel,
new GridBagConstraints(0, 4,
1, 1,
LIGHT, LIGHT,
LINE_START, NONE,
- new Insets(0, 0, 4, 0),
+ new Insets(0, indent, 4, 0),
NONE, NONE));
- ScreenPanel.add(scalingFactor,
+ SizingPanel.add(scalingFactorInput,
new GridBagConstraints(1, 4,
1, 1,
HEAVY, LIGHT,
LINE_START, NONE,
new Insets(0, 5, 4, 0),
NONE, NONE));
+ ScreenPanel.add(SizingPanel,
+ new GridBagConstraints(0, 0,
+ REMAINDER, 1,
+ LIGHT, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(0, 0, 4, 0),
+ NONE, NONE));
+ ScreenPanel.add(fullScreenCheckbox,
+ new GridBagConstraints(0, 1,
+ REMAINDER, 1,
+ LIGHT, LIGHT,
+ LINE_START, NONE,
+ new Insets(0, 0, 4, 0),
+ NONE, NONE));
+ indent = getButtonLabelInset(fullScreenCheckbox);
+ ScreenPanel.add(fullScreenAllMonitorsCheckbox,
+ new GridBagConstraints(0, 2,
+ REMAINDER, 1,
+ LIGHT, LIGHT,
+ LINE_START, NONE,
+ new Insets(0, indent, 4, 0),
+ NONE, NONE));
ScreenPanel.add(Box.createRigidArea(new Dimension(5, 0)),
- new GridBagConstraints(0, 5,
+ new GridBagConstraints(0, 3,
REMAINDER, REMAINDER,
HEAVY, HEAVY,
LINE_START, BOTH,
new Insets(0, 0, 0, 0),
NONE, NONE));
+ return ScreenPanel;
+ }
- // Misc tab
+ private JPanel createMiscPanel() {
JPanel MiscPanel = new JPanel(new GridBagLayout());
MiscPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
- shared =
- new JCheckBox("Shared connection (do not disconnect other viewers)");
- useLocalCursor = new JCheckBox("Render cursor locally");
- acceptBell = new JCheckBox("Beep when requested by the server");
- MiscPanel.add(shared,
+ sharedCheckbox =
+ new JCheckBox("Shared (don't disconnect other viewers)");
+ dotWhenNoCursorCheckbox = new JCheckBox("Show dot when no cursor");
+ acceptBellCheckbox = new JCheckBox("Beep when requested by the server");
+ MiscPanel.add(sharedCheckbox,
new GridBagConstraints(0, 0,
1, 1,
LIGHT, LIGHT,
LINE_START, NONE,
new Insets(0, 0, 4, 0),
NONE, NONE));
- MiscPanel.add(useLocalCursor,
+ MiscPanel.add(dotWhenNoCursorCheckbox,
new GridBagConstraints(0, 1,
1, 1,
LIGHT, LIGHT,
LINE_START, NONE,
new Insets(0, 0, 4, 0),
NONE, NONE));
- MiscPanel.add(acceptBell,
+ MiscPanel.add(acceptBellCheckbox,
new GridBagConstraints(0, 2,
1, 1,
LIGHT, LIGHT,
@@ -575,52 +1180,101 @@
LINE_START, BOTH,
new Insets(0, 0, 0, 0),
NONE, NONE));
+ return MiscPanel;
+ }
- // SSH tab
+ private JPanel createSshPanel() {
JPanel sshPanel = new JPanel(new GridBagLayout());
sshPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
- sshTunnel = new JCheckBox("Tunnel VNC over SSH");
+ ButtonGroup sshArgsGroup = new ButtonGroup();
+ tunnelCheckbox = new JCheckBox("Tunnel VNC over SSH");
+ tunnelCheckbox.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ handleTunnel();
+ }
+ });
JPanel tunnelPanel = new JPanel(new GridBagLayout());
- sshUseGateway = new JCheckBox("Use SSH gateway");
+ viaCheckbox = new JCheckBox("Use SSH gateway");
+ viaCheckbox.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ handleVia();
+ }
+ });
JLabel sshUserLabel = new JLabel("Username");
- sshUser = new JTextField();
+ viaUserInput = new JTextField();
JLabel sshUserAtLabel = new JLabel("@");
JLabel sshHostLabel = new JLabel("Hostname (or IP address)");
- sshHost = new JTextField("");
+ viaHostInput = new JTextField("");
JLabel sshPortLabel = new JLabel("Port");
- sshPort = new IntegerTextField(5);
+ viaPortInput = new IntegerTextField(5);
- sshUseExt = new JCheckBox("Use external SSH client");
- sshClient = new JTextField();
- sshClient.setName(Configuration.getParam("extSSHClient").getName());
- sshClientBrowser = new JButton("Browse");
+ extSSHCheckbox = new JCheckBox("Use external SSH client");
+ extSSHCheckbox.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ handleExtSSH();
+ }
+ });
+ sshClientInput = new JTextField();
+ sshClientChooser = new JButton("Browse");
+ sshClientChooser.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ JComponent c = ((JButton)e.getSource()).getRootPane();
+ File dflt = new File(extSSHClient.getValueStr());
+ File f = showChooser("Path to external SSH client", dflt, c);
+ if (f != null && f.exists() && f.isFile() && f.canExecute())
+ sshClientInput.setText(f.getAbsolutePath());
+ }
+ });
JLabel sshConfigLabel = new JLabel("SSH config file");
- sshConfig = new JTextField();
- sshConfig.setName(Configuration.getParam("sshConfig").getName());
- sshConfigBrowser = new JButton("Browse");
+ sshConfigInput = new JTextField();
+ sshConfigChooser = new JButton("Browse");
+ sshConfigChooser.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ JComponent c = ((JButton)e.getSource()).getRootPane();
+ File dflt = new File(sshConfig.getValueStr());
+ File f = showChooser("Path to OpenSSH client config file", dflt, c);
+ if (f != null && f.exists() && f.isFile() && f.canRead())
+ sshConfigInput.setText(f.getAbsolutePath());
+ }
+ });
JLabel sshKeyFileLabel = new JLabel("SSH identity file");
- sshKeyFile = new JTextField();
- sshKeyFile.setName(Configuration.getParam("sshKeyFile").getName());
- sshKeyFileBrowser = new JButton("Browse");
+ sshKeyFileInput = new JTextField();
+ sshKeyFileChooser = new JButton("Browse");
+ sshKeyFileChooser.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ JComponent c = ((JButton)e.getSource()).getRootPane();
+ File f = showChooser("Path to SSH key file", null, c);
+ if (f != null && f.exists() && f.isFile() && f.canRead())
+ sshKeyFileInput.setText(f.getAbsolutePath());
+ }
+ });
JPanel sshArgsPanel = new JPanel(new GridBagLayout());
JLabel sshArgsLabel = new JLabel("Arguments:");
- sshArgsDefault =
- new GroupedJRadioButton("Default", sshArgsGroup, sshArgsPanel);
- sshArgsCustom =
- new GroupedJRadioButton("Custom", sshArgsGroup, sshArgsPanel);
- sshArguments = new JTextField();
+ sshArgsDefaultButton = new GroupedJRadioButton("Default", sshArgsGroup, sshArgsPanel);
+ sshArgsDefaultButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ sshArgsInput.setEnabled(sshArgsCustomButton.isSelected());
+ }
+ });
+ sshArgsCustomButton = new GroupedJRadioButton("Custom", sshArgsGroup, sshArgsPanel);
+ sshArgsCustomButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ sshArgsInput.setEnabled(sshArgsCustomButton.isSelected());
+ }
+ });
+ sshArgsInput = new JTextField();
JPanel gatewayPanel = new JPanel(new GridBagLayout());
- gatewayPanel.add(sshUseGateway,
+ gatewayPanel.add(viaCheckbox,
new GridBagConstraints(0, 0,
REMAINDER, 1,
LIGHT, LIGHT,
LINE_START, NONE,
new Insets(0, 0, 4, 0),
NONE, NONE));
- indent = getButtonLabelInset(sshUseGateway);
+ int indent = getButtonLabelInset(viaCheckbox);
gatewayPanel.add(sshUserLabel,
new GridBagConstraints(0, 1,
1, 1,
@@ -642,7 +1296,7 @@
LINE_START, HORIZONTAL,
new Insets(0, 5, 4, 0),
NONE, NONE));
- gatewayPanel.add(sshUser,
+ gatewayPanel.add(viaUserInput,
new GridBagConstraints(0, 2,
1, 1,
LIGHT, LIGHT,
@@ -656,14 +1310,14 @@
LINE_START, HORIZONTAL,
new Insets(0, 2, 0, 2),
NONE, NONE));
- gatewayPanel.add(sshHost,
+ gatewayPanel.add(viaHostInput,
new GridBagConstraints(2, 2,
1, 1,
HEAVY, LIGHT,
LINE_START, HORIZONTAL,
new Insets(0, 0, 0, 0),
NONE, NONE));
- gatewayPanel.add(sshPort,
+ gatewayPanel.add(viaPortInput,
new GridBagConstraints(3, 2,
1, 1,
LIGHT, LIGHT,
@@ -672,21 +1326,21 @@
NONE, NONE));
JPanel clientPanel = new JPanel(new GridBagLayout());
- clientPanel.add(sshUseExt,
+ clientPanel.add(extSSHCheckbox,
new GridBagConstraints(0, 0,
1, 1,
LIGHT, LIGHT,
LINE_START, NONE,
new Insets(0, 0, 0, 0),
NONE, NONE));
- clientPanel.add(sshClient,
+ clientPanel.add(sshClientInput,
new GridBagConstraints(1, 0,
1, 1,
HEAVY, LIGHT,
LINE_START, HORIZONTAL,
new Insets(0, 5, 0, 0),
NONE, NONE));
- clientPanel.add(sshClientBrowser,
+ clientPanel.add(sshClientChooser,
new GridBagConstraints(2, 0,
1, 1,
LIGHT, LIGHT,
@@ -700,28 +1354,28 @@
LINE_START, NONE,
new Insets(0, 0, 0, 0),
NONE, NONE));
- sshArgsPanel.add(sshArgsDefault,
+ sshArgsPanel.add(sshArgsDefaultButton,
new GridBagConstraints(1, 1,
1, 1,
LIGHT, LIGHT,
LINE_START, NONE,
new Insets(0, 5, 0, 0),
NONE, NONE));
- sshArgsPanel.add(sshArgsCustom,
+ sshArgsPanel.add(sshArgsCustomButton,
new GridBagConstraints(2, 1,
1, 1,
LIGHT, LIGHT,
LINE_START, NONE,
new Insets(0, 5, 0, 0),
NONE, NONE));
- sshArgsPanel.add(sshArguments,
+ sshArgsPanel.add(sshArgsInput,
new GridBagConstraints(3, 1,
1, 1,
HEAVY, LIGHT,
LINE_START, HORIZONTAL,
new Insets(0, 5, 0, 0),
NONE, NONE));
- indent = getButtonLabelInset(sshUseExt);
+ indent = getButtonLabelInset(extSSHCheckbox);
clientPanel.add(sshArgsPanel,
new GridBagConstraints(0, 1,
REMAINDER, 1,
@@ -731,7 +1385,9 @@
NONE, NONE));
JPanel opensshPanel = new JPanel(new GridBagLayout());
- opensshPanel.setBorder(BorderFactory.createTitledBorder("Embedded SSH client configuration"));
+ TitledBorder border =
+ BorderFactory.createTitledBorder("Embedded SSH client configuration");
+ opensshPanel.setBorder(border);
opensshPanel.add(sshConfigLabel,
new GridBagConstraints(0, 0,
1, 1,
@@ -739,14 +1395,14 @@
LINE_START, NONE,
new Insets(0, 0, 5, 0),
NONE, NONE));
- opensshPanel.add(sshConfig,
+ opensshPanel.add(sshConfigInput,
new GridBagConstraints(1, 0,
1, 1,
HEAVY, LIGHT,
LINE_START, HORIZONTAL,
new Insets(0, 5, 5, 0),
NONE, NONE));
- opensshPanel.add(sshConfigBrowser,
+ opensshPanel.add(sshConfigChooser,
new GridBagConstraints(2, 0,
1, 1,
LIGHT, LIGHT,
@@ -760,14 +1416,14 @@
LINE_START, NONE,
new Insets(0, 0, 0, 0),
NONE, NONE));
- opensshPanel.add(sshKeyFile,
+ opensshPanel.add(sshKeyFileInput,
new GridBagConstraints(1, 1,
1, 1,
HEAVY, LIGHT,
LINE_START, HORIZONTAL,
new Insets(0, 5, 0, 0),
NONE, NONE));
- opensshPanel.add(sshKeyFileBrowser,
+ opensshPanel.add(sshKeyFileChooser,
new GridBagConstraints(2, 1,
1, 1,
LIGHT, LIGHT,
@@ -796,14 +1452,14 @@
new Insets(0, 0, 0, 0),
NONE, NONE));
- sshPanel.add(sshTunnel,
+ sshPanel.add(tunnelCheckbox,
new GridBagConstraints(0, 0,
REMAINDER, 1,
LIGHT, LIGHT,
LINE_START, NONE,
new Insets(0, 0, 4, 0),
NONE, NONE));
- indent = getButtonLabelInset(sshTunnel);
+ indent = getButtonLabelInset(tunnelCheckbox);
sshPanel.add(tunnelPanel,
new GridBagConstraints(0, 2,
REMAINDER, 1,
@@ -818,601 +1474,149 @@
LINE_START, BOTH,
new Insets(0, 0, 0, 0),
NONE, NONE));
-
- // load/save tab
- JPanel loadSavePanel = new JPanel(new GridBagLayout());
- loadSavePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
- JPanel configPanel = new JPanel(new GridBagLayout());
- configPanel.
- setBorder(BorderFactory.createTitledBorder("Configuration File"));
- cfLoadButton = new JButton("Load");
- cfSaveAsButton = new JButton("Save As...");
- configPanel.add(cfLoadButton,
- new GridBagConstraints(0, 0,
- 1, 1,
- HEAVY, LIGHT,
- CENTER, HORIZONTAL,
- new Insets(0, 0, 5, 0),
- NONE, NONE));
- configPanel.add(cfSaveAsButton,
- new GridBagConstraints(0, 1,
- 1, 1,
- HEAVY, HEAVY,
- CENTER, HORIZONTAL,
- new Insets(0, 0, 0, 0),
- NONE, NONE));
-
- JPanel defaultsPanel = new JPanel(new GridBagLayout());
- defaultsPanel.setBorder(BorderFactory.createTitledBorder("Defaults"));
- defClearButton = new JButton("Clear");
- defReloadButton = new JButton("Reload");
- defSaveButton = new JButton("Save");
- defaultsPanel.add(defClearButton,
- new GridBagConstraints(0, 0,
- 1, 1,
- HEAVY, LIGHT,
- CENTER, HORIZONTAL,
- new Insets(0, 0, 5, 0),
- NONE, NONE));
- defaultsPanel.add(defReloadButton,
- new GridBagConstraints(0, 1,
- 1, 1,
- HEAVY, LIGHT,
- CENTER, HORIZONTAL,
- new Insets(0, 0, 5, 0),
- NONE, NONE));
- defaultsPanel.add(defSaveButton,
- new GridBagConstraints(0, 2,
- 1, 1,
- HEAVY, HEAVY,
- CENTER, HORIZONTAL,
- new Insets(0, 0, 0, 0),
- NONE, NONE));
-
- loadSavePanel.add(configPanel,
- new GridBagConstraints(0, 0,
- 1, 1,
- HEAVY, LIGHT,
- PAGE_START, HORIZONTAL,
- new Insets(0, 0, 0, 0),
- NONE, NONE));
- loadSavePanel.add(Box.createRigidArea(new Dimension(5, 0)),
- new GridBagConstraints(1, 1,
- 1, 1,
- LIGHT, LIGHT,
- LINE_START, NONE,
- new Insets(0, 0, 0, 0),
- NONE, NONE));
- loadSavePanel.add(defaultsPanel,
- new GridBagConstraints(2, 0,
- 1, 1,
- HEAVY, LIGHT,
- PAGE_START, HORIZONTAL,
- new Insets(0, 0, 0, 0),
- NONE, NONE));
- loadSavePanel.add(Box.createRigidArea(new Dimension(5, 0)),
- new GridBagConstraints(0, 1,
- REMAINDER, REMAINDER,
- HEAVY, HEAVY,
- LINE_START, BOTH,
- new Insets(0, 0, 0, 0),
- NONE, NONE));
-
- // tabPane
- tabPane.addTab("Compression", FormatPanel);
- tabPane.addTab("Security", SecPanel);
- tabPane.addTab("Input", inputPanel);
- tabPane.addTab("Screen", ScreenPanel);
- tabPane.addTab("Misc", MiscPanel);
- tabPane.addTab("SSH", sshPanel);
- tabPane.addTab("Load / Save", loadSavePanel);
- tabPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
- // Resize the tabPane if necessary to prevent scrolling
- Insets tpi =
- (Insets)UIManager.get("TabbedPane:TabbedPaneTabArea.contentMargins");
- int minWidth = tpi.left + tpi.right;
- for (int i = 0; i < tabPane.getTabCount(); i++)
- minWidth += tabPane.getBoundsAt(i).width;
- int minHeight = tabPane.getPreferredSize().height;
- if (tabPane.getPreferredSize().width < minWidth)
- tabPane.setPreferredSize(new Dimension(minWidth, minHeight));
-
- // button pane
- okButton = new JButton("OK");
- cancelButton = new JButton("Cancel");
-
- JPanel buttonPane = new JPanel(new GridLayout(1, 5, 10, 10));
- buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 5, 5, 5));
- buttonPane.add(Box.createRigidArea(new Dimension()));
- buttonPane.add(Box.createRigidArea(new Dimension()));
- buttonPane.add(Box.createRigidArea(new Dimension()));
- buttonPane.add(okButton);
- buttonPane.add(cancelButton);
-
- this.add(tabPane);
- this.add(buttonPane);
- addListeners(this);
- pack();
+ return sshPanel;
}
- public void initDialog() {
- if (cc != null) cc.setOptions();
- zrle.setEnabled(!autoSelect.isSelected());
- hextile.setEnabled(!autoSelect.isSelected());
- tight.setEnabled(!autoSelect.isSelected());
- raw.setEnabled(!autoSelect.isSelected());
- fullColour.setEnabled(!autoSelect.isSelected());
- mediumColour.setEnabled(!autoSelect.isSelected());
- lowColour.setEnabled(!autoSelect.isSelected());
- veryLowColour.setEnabled(!autoSelect.isSelected());
- compressLevel.setEnabled(customCompressLevel.isSelected());
- qualityLevel.setEnabled(noJpeg.isSelected());
- sendLocalUsername.setEnabled(secVeNCrypt.isEnabled() &&
- (secPlain.isSelected() || secIdent.isSelected()));
- sshArguments.setEnabled(sshTunnel.isSelected() &&
- (sshUseExt.isSelected() && sshArgsCustom.isSelected()));
+ private void handleAutoselect()
+ {
+ ButtonGroup[] groups = { encodingGroup, colorlevelGroup };
+ for (ButtonGroup grp : groups) {
+ Enumeration<AbstractButton> elems = grp.getElements();
+ while (elems.hasMoreElements())
+ elems.nextElement().setEnabled(!autoselectCheckbox.isSelected());
+ }
+
+ // JPEG setting is also affected by autoselection
+ jpegCheckbox.setEnabled(!autoselectCheckbox.isSelected());
+ handleJpeg();
}
- private void updatePreferences() {
- if (autoSelect.isSelected()) {
- UserPreferences.set("global", "AutoSelect", true);
- } else {
- UserPreferences.set("global", "AutoSelect", false);
- if (zrle.isSelected()) {
- UserPreferences.set("global", "PreferredEncoding", "ZRLE");
- } else if (hextile.isSelected()) {
- UserPreferences.set("global", "PreferredEncoding", "hextile");
- } else if (tight.isSelected()) {
- UserPreferences.set("global", "PreferredEncoding", "Tight");
- } else if (raw.isSelected()) {
- UserPreferences.set("global", "PreferredEncoding", "raw");
- }
- }
- if (fullColour.isSelected()) {
- UserPreferences.set("global", "FullColour", true);
- } else {
- UserPreferences.set("global", "FullColour", false);
- if (mediumColour.isSelected()) {
- UserPreferences.set("global", "LowColorLevel", 2);
- } else if (lowColour.isSelected()) {
- UserPreferences.set("global", "LowColorLevel", 1);
- } else if (veryLowColour.isSelected()) {
- UserPreferences.set("global", "LowColorLevel", 0);
- }
- }
- UserPreferences.set("global", "NoJPEG", !noJpeg.isSelected());
- UserPreferences.set("global",
- "QualityLevel", (Integer)qualityLevel.getSelectedItem());
- UserPreferences.set("global",
- "CustomCompressLevel", customCompressLevel.isSelected());
- UserPreferences.set("global",
- "CompressLevel", (Integer)compressLevel.getSelectedItem());
- UserPreferences.set("global", "ViewOnly", viewOnly.isSelected());
- UserPreferences.set("global",
- "AcceptClipboard", acceptClipboard.isSelected());
- UserPreferences.set("global", "SendClipboard", sendClipboard.isSelected());
- String menuKeyStr =
- MenuKey.getMenuKeySymbols()[menuKey.getSelectedIndex()].name;
- UserPreferences.set("global", "MenuKey", menuKeyStr);
- String desktopSizeString =
- desktopSize.isSelected() ?
- desktopWidth.getText() + "x" + desktopHeight.getText() : "";
- UserPreferences.set("global", "DesktopSize", desktopSizeString);
- UserPreferences.set("global", "FullScreen", fullScreen.isSelected());
- UserPreferences.set("global",
- "FullScreenAllMonitors", fullScreenAllMonitors.isSelected());
- UserPreferences.set("global", "Shared", shared.isSelected());
- UserPreferences.set("global",
- "UseLocalCursor", useLocalCursor.isSelected());
- UserPreferences.set("global", "AcceptBell", acceptBell.isSelected());
- String scaleString = scalingFactor.getSelectedItem().toString();
- if (scaleString.equalsIgnoreCase("Auto")) {
- UserPreferences.set("global", "ScalingFactor", "Auto");
- } else if(scaleString.equalsIgnoreCase("Fixed Aspect Ratio")) {
- UserPreferences.set("global", "ScalingFactor", "FixedRatio");
- } else {
- scaleString=scaleString.substring(0, scaleString.length()-1);
- UserPreferences.set("global", "ScalingFactor", scaleString);
- }
- UserPreferences.set("viewer", "secVeNCrypt", secVeNCrypt.isSelected());
- UserPreferences.set("viewer", "encNone", encNone.isSelected());
- UserPreferences.set("viewer", "encTLS", encTLS.isSelected());
- UserPreferences.set("viewer", "encX509", encX509.isSelected());
- UserPreferences.set("viewer", "secNone", secNone.isSelected());
- UserPreferences.set("viewer", "secVnc", secVnc.isSelected());
- UserPreferences.set("viewer", "secPlain", secPlain.isSelected());
- UserPreferences.set("viewer", "secIdent", secIdent.isSelected());
- UserPreferences.set("global",
- "SendLocalUsername", sendLocalUsername.isSelected());
- if (!CSecurityTLS.x509ca.getValueStr().equals(""))
- UserPreferences.set("viewer", "x509ca",
- CSecurityTLS.x509ca.getValueStr());
- if (!CSecurityTLS.x509crl.getValueStr().equals(""))
- UserPreferences.set("viewer", "x509crl",
- CSecurityTLS.x509crl.getValueStr());
- UserPreferences.set("global", "Tunnel", sshTunnel.isSelected());
- if (sshUseGateway.isSelected()) {
- String via = sshUser.getText()+"@"+sshHost.getText()+":"+sshPort.getText();
- UserPreferences.set("global", "Via", via);
- }
- if (sshUseExt.isSelected()) {
- UserPreferences.set("global", "extSSH", sshUseExt.isSelected());
- UserPreferences.set("global", "extSSHClient", sshClient.getText());
- if (!sshArguments.getText().isEmpty())
- UserPreferences.set("global", "extSSHArgs", sshArguments.getText());
- }
- UserPreferences.set("global", "SSHConfig", sshConfig.getText());
- UserPreferences.set("global", "SSHKeyFile", sshKeyFile.getText());
+ private void handleCompression()
+ {
+ compressionInput.setEnabled(compressionCheckbox.isSelected());
}
- private void restorePreferences() {
- autoSelect.setSelected(UserPreferences.getBool("global", "AutoSelect"));
- if (!autoSelect.isSelected()) {
- if (UserPreferences.getBool("global", "FullColour")) {
- fullColour.setSelected(true);
- } else {
- switch (UserPreferences.getInt("global", "LowColorLevel")) {
- case 2:
- mediumColour.setSelected(true);
- break;
- case 1:
- lowColour.setSelected(true);
- break;
- case 0:
- veryLowColour.setSelected(true);
- break;
- }
- }
- String encoding = UserPreferences.get("global", "PreferredEncoding");
- if (encoding != null) {
- switch (Encodings.encodingNum(encoding)) {
- case Encodings.encodingZRLE:
- zrle.setSelected(true);
- break;
- case Encodings.encodingHextile:
- hextile.setSelected(true);
- break;
- case Encodings.encodingRaw:
- raw.setSelected(true);
- break;
- default:
- tight.setSelected(true);
- }
- }
- }
- noJpeg.setSelected(!UserPreferences.getBool("global", "NoJPEG"));
- qualityLevel.setSelectedItem(UserPreferences.getInt("global",
- "QualityLevel"));
- customCompressLevel.setSelected(UserPreferences.getBool("global",
- "CustomCompressLevel"));
- compressLevel.setSelectedItem(UserPreferences.getInt("global",
- "CompressLevel"));
- viewOnly.setSelected(UserPreferences.getBool("global", "ViewOnly"));
- acceptClipboard.setSelected(UserPreferences.getBool("global",
- "AcceptClipboard"));
- sendClipboard.setSelected(UserPreferences.getBool("global",
- "SendClipboard"));
- menuKey.setSelectedItem(UserPreferences.get("global", "MenuKey"));
- desktopSize.setSelected(!UserPreferences.get("global", "DesktopSize").isEmpty());
- if (desktopSize.isSelected()) {
- String desktopSizeString = UserPreferences.get("global", "DesktopSize");
- desktopWidth.setText(desktopSizeString.split("x")[0]);
- desktopHeight.setText(desktopSizeString.split("x")[1]);
- }
- fullScreen.setSelected(UserPreferences.getBool("global", "FullScreen"));
- fullScreenAllMonitors.setSelected(UserPreferences.getBool("global",
- "FullScreenAllMonitors"));
- if (shared.isEnabled())
- shared.setSelected(UserPreferences.getBool("global", "Shared"));
- useLocalCursor.setSelected(UserPreferences.getBool("global",
- "UseLocalCursor"));
- acceptBell.setSelected(UserPreferences.getBool("global", "AcceptBell"));
- String scaleString = UserPreferences.get("global", "ScalingFactor");
- if (scaleString != null) {
- if (scaleString.equalsIgnoreCase("Auto")) {
- scalingFactor.setSelectedItem("Auto");
- } else if (scaleString.equalsIgnoreCase("FixedRatio")) {
- scalingFactor.setSelectedItem("Fixed Aspect Ratio");
- } else {
- scalingFactor.setSelectedItem(scaleString+"%");
- }
- }
- if (secVeNCrypt.isEnabled()) {
- secVeNCrypt.setSelected(UserPreferences.getBool("viewer",
- "secVeNCrypt", true));
- if (secVeNCrypt.isSelected()) {
- encNone.setSelected(UserPreferences.getBool("viewer", "encNone", true));
- encTLS.setSelected(UserPreferences.getBool("viewer", "encTLS", true));
- encX509.setSelected(UserPreferences.getBool("viewer", "encX509", true));
- secPlain.setSelected(UserPreferences.getBool("viewer", "secPlain", true));
- secIdent.setSelected(UserPreferences.getBool("viewer", "secIdent", true));
- sendLocalUsername.setSelected(UserPreferences.getBool("global",
- "SendLocalUsername"));
- }
- }
- if (secNone.isEnabled())
- secNone.setSelected(UserPreferences.getBool("viewer", "secNone", true));
- if (secVnc.isEnabled())
- secVnc.setSelected(UserPreferences.getBool("viewer", "secVnc", true));
- sshTunnel.setSelected(UserPreferences.getBool("global", "Tunnel"));
- sshUseGateway.setSelected(UserPreferences.get("global", "Via") != null);
- if (sshUseGateway.isSelected())
- cc.viewer.via.setParam(UserPreferences.get("global", "Via"));
- sshUser.setText(Tunnel.getSshUser(cc));
- sshHost.setText(Tunnel.getSshHost(cc));
- sshPort.setText(Integer.toString(Tunnel.getSshPort(cc)));
- sshUseExt.setSelected(UserPreferences.getBool("global", "extSSH"));
- File f = new File(UserPreferences.get("global", "extSSHClient"));
- if (f.exists() && f.canExecute())
- sshClient.setText(f.getAbsolutePath());
- sshArguments.setText(UserPreferences.get("global", "extSSHArgs"));
- if (sshArguments.getText().isEmpty())
- sshArgsDefault.setSelected(true);
+ private void handleJpeg()
+ {
+ if (jpegCheckbox.isSelected() &&
+ !autoselectCheckbox.isSelected())
+ jpegInput.setEnabled(true);
else
- sshArgsCustom.setSelected(true);
- f = new File(UserPreferences.get("global", "SSHConfig"));
- if (f.exists() && f.canRead())
- sshConfig.setText(f.getAbsolutePath());
- if (UserPreferences.get("global", "SSHKeyFile") != null) {
- f = new File(UserPreferences.get("global", "SSHKeyFile"));
- if (f.exists() && f.canRead())
- sshKeyFile.setText(f.getAbsolutePath());
+ jpegInput.setEnabled(false);
+ }
+
+ private void handleX509()
+ {
+ caInput.setEnabled(encX509Checkbox.isSelected());
+ caChooser.setEnabled(encX509Checkbox.isSelected());
+ crlInput.setEnabled(encX509Checkbox.isSelected());
+ crlChooser.setEnabled(encX509Checkbox.isSelected());
+ }
+
+ private void handleSendLocalUsername()
+ {
+ boolean value = authIdentCheckbox.isSelected() ||
+ authPlainCheckbox.isSelected();
+ sendLocalUsernameCheckbox.setEnabled(value);
+ }
+
+ private void handleDesktopSize()
+ {
+ desktopWidthInput.setEnabled(desktopSizeCheckbox.isSelected());
+ desktopHeightInput.setEnabled(desktopSizeCheckbox.isSelected());
+ }
+
+ private void handleRemoteResize()
+ {
+ scalingFactorInput.setEnabled(!remoteResizeButton.isSelected());
+ }
+
+ private void handleTunnel()
+ {
+ viaCheckbox.setEnabled(tunnelCheckbox.isSelected());
+ extSSHCheckbox.setEnabled(tunnelCheckbox.isSelected());
+ if (tunnelCheckbox.isSelected()) {
+ JComponent[] components = { viaUserInput, viaHostInput, viaPortInput };
+ for (JComponent c : components)
+ c.setEnabled(viaCheckbox.isSelected());
+ sshClientInput.setEnabled(extSSHCheckbox.isSelected());
+ sshClientChooser.setEnabled(extSSHCheckbox.isSelected());
+ sshArgsDefaultButton.setEnabled(extSSHCheckbox.isSelected());
+ sshArgsCustomButton.setEnabled(extSSHCheckbox.isSelected());
+ sshArgsInput.setEnabled(extSSHCheckbox.isSelected());
+ sshConfigInput.setEnabled(!extSSHCheckbox.isSelected());
+ sshConfigChooser.setEnabled(!extSSHCheckbox.isSelected());
+ sshKeyFileInput.setEnabled(!extSSHCheckbox.isSelected());
+ sshKeyFileChooser.setEnabled(!extSSHCheckbox.isSelected());
} else {
- sshKeyFile.setText(Tunnel.getSshKeyFile(cc));
- }
- sshUseGateway.setEnabled(sshTunnel.isSelected());
- sshUser.setEnabled(sshTunnel.isSelected() &&
- sshUseGateway.isEnabled() &&
- sshUseGateway.isSelected());
- sshHost.setEnabled(sshTunnel.isSelected() &&
- sshUseGateway.isEnabled() &&
- sshUseGateway.isSelected());
- sshPort.setEnabled(sshTunnel.isSelected() &&
- sshUseGateway.isEnabled() &&
- sshUseGateway.isSelected());
- sshUseExt.setEnabled(sshTunnel.isSelected());
- sshClient.setEnabled(sshTunnel.isSelected() &&
- sshUseExt.isEnabled());
- sshClientBrowser.setEnabled(sshTunnel.isSelected() &&
- sshUseExt.isEnabled() &&
- sshUseExt.isSelected());
- sshArgsDefault.setEnabled(sshTunnel.isSelected() &&
- sshUseExt.isEnabled() &&
- sshUseExt.isSelected());
- sshArgsCustom.setEnabled(sshTunnel.isSelected() &&
- sshUseExt.isEnabled() &&
- sshUseExt.isSelected());
- sshArguments.setEnabled(sshTunnel.isSelected() &&
- sshUseExt.isEnabled() &&
- sshUseExt.isSelected() &&
- sshArgsCustom.isSelected());
- sshConfig.setEnabled(sshTunnel.isSelected() &&
- sshUseExt.isEnabled() &&
- !sshUseExt.isSelected());
- sshConfigBrowser.setEnabled(sshTunnel.isSelected() &&
- sshUseExt.isEnabled() &&
- !sshUseExt.isSelected());
- sshKeyFile.setEnabled(sshTunnel.isSelected() &&
- sshUseExt.isEnabled() &&
- !sshUseExt.isSelected());
- sshKeyFileBrowser.setEnabled(sshTunnel.isSelected() &&
- sshUseExt.isEnabled() &&
- !sshUseExt.isSelected());
- }
-
- public void endDialog() {
- super.endDialog();
- if (cc.viewport != null && cc.viewport.isVisible()) {
- cc.viewport.toFront();
- cc.viewport.requestFocus();
+ JComponent[] components = {
+ viaUserInput, viaHostInput, viaPortInput, sshClientInput,
+ sshClientChooser, sshArgsDefaultButton, sshArgsCustomButton,
+ sshArgsInput, sshConfigInput, sshConfigChooser, sshKeyFileInput,
+ sshKeyFileChooser, };
+ for (JComponent c : components)
+ c.setEnabled(false);
}
}
- public void actionPerformed(ActionEvent e) {
- Object s = e.getSource();
- if (s instanceof JButton) {
- JButton button = (JButton)s;
- if (button == okButton) {
- JTextField[] fields =
- { x509ca, x509crl, sshClient, sshConfig, sshKeyFile };
- for (JTextField field : fields) {
- if (field.getText() != null && !field.getText().equals("")) {
- File f = new File(field.getText());
- if (!f.exists() || !f.canRead()) {
- String msg = new String("The file "+f.getAbsolutePath()+
- " specified for option "+field.getName()+
- " does not exist or cannot be read. Please"+
- " correct before proceeding.");
- JOptionPane.showMessageDialog(this, msg, "WARNING",
- JOptionPane.WARNING_MESSAGE);
- return;
- }
- }
- }
- if (cc != null) cc.getOptions();
- endDialog();
- } else if (button == cancelButton) {
- endDialog();
- } else if (button == cfLoadButton) {
- JFileChooser fc = new JFileChooser();
- fc.setDialogTitle("Path to configuration file");
- fc.setApproveButtonText("OK");
- fc.setFileHidingEnabled(false);
- int ret = fc.showOpenDialog(this);
- if (ret == JFileChooser.APPROVE_OPTION) {
- String filename = fc.getSelectedFile().toString();
- if (filename != null)
- Configuration.load(filename);
- cc.setOptions();
- }
- } else if (button == cfSaveAsButton) {
- JFileChooser fc = new JFileChooser();
- fc.setDialogTitle("Save current configuration as:");
- fc.setApproveButtonText("OK");
- fc.setFileHidingEnabled(false);
- int ret = fc.showOpenDialog(this);
- if (ret == JFileChooser.APPROVE_OPTION) {
- String filename = fc.getSelectedFile().toString();
- if (filename != null)
- Configuration.save(filename);
- }
- } else if (button == defSaveButton) {
- updatePreferences();
- UserPreferences.save();
- } else if (button == defReloadButton) {
- restorePreferences();
- } else if (button == defClearButton) {
- UserPreferences.clear();
- cc.setOptions();
- } else if (button == caButton) {
- JFileChooser fc =
- new JFileChooser(new File(CSecurityTLS.getDefaultCA()));
- fc.setDialogTitle("Path to X509 CA certificate");
- fc.setApproveButtonText("OK");
- fc.setFileHidingEnabled(false);
- int ret = fc.showOpenDialog(this);
- if (ret == JFileChooser.APPROVE_OPTION)
- x509ca.setText(fc.getSelectedFile().toString());
- } else if (button == crlButton) {
- JFileChooser fc =
- new JFileChooser(new File(CSecurityTLS.getDefaultCRL()));
- fc.setDialogTitle("Path to X509 CRL file");
- fc.setApproveButtonText("OK");
- fc.setFileHidingEnabled(false);
- int ret = fc.showOpenDialog(this);
- if (ret == JFileChooser.APPROVE_OPTION)
- x509crl.setText(fc.getSelectedFile().toString());
- } else if (button == sshClientBrowser) {
- JFileChooser fc = new JFileChooser();
- fc.setDialogTitle("Path to external SSH client");
- fc.setApproveButtonText("OK");
- fc.setFileHidingEnabled(false);
- int ret = fc.showOpenDialog(this);
- if (ret == JFileChooser.APPROVE_OPTION)
- sshClient.setText(fc.getSelectedFile().toString());
- } else if (button == sshConfigBrowser) {
- JFileChooser fc = new JFileChooser();
- fc.setDialogTitle("Path to OpenSSH client config file");
- fc.setApproveButtonText("OK");
- fc.setFileHidingEnabled(false);
- int ret = fc.showOpenDialog(this);
- if (ret == JFileChooser.APPROVE_OPTION)
- sshConfig.setText(fc.getSelectedFile().toString());
- } else if (button == sshKeyFileBrowser) {
- JFileChooser fc = new JFileChooser();
- fc.setDialogTitle("Path to SSH key file");
- fc.setApproveButtonText("OK");
- fc.setFileHidingEnabled(false);
- int ret = fc.showOpenDialog(this);
- if (ret == JFileChooser.APPROVE_OPTION)
- sshKeyFile.setText(fc.getSelectedFile().toString());
- }
- } else if (s instanceof JRadioButton) {
- JRadioButton button = (JRadioButton)s;
- if (button == sshArgsCustom || button == sshArgsDefault) {
- sshArguments.setEnabled(sshArgsCustom.isSelected());
- }
+ private void handleVia()
+ {
+ if (tunnelCheckbox.isSelected()) {
+ viaUserInput.setEnabled(viaCheckbox.isSelected());
+ viaHostInput.setEnabled(viaCheckbox.isSelected());
+ viaPortInput.setEnabled(viaCheckbox.isSelected());
}
}
- public void itemStateChanged(ItemEvent e) {
- Object s = e.getSource();
- if (s instanceof JCheckBox) {
- JCheckBox item = (JCheckBox)s;
- boolean enable = item.isSelected();
- if (item == autoSelect) {
- ButtonGroup[] groups = { encodingGroup, colourGroup };
- for (ButtonGroup grp : groups) {
- Enumeration<AbstractButton> elems = grp.getElements();
- while (elems.hasMoreElements())
- elems.nextElement().setEnabled(!enable);
- }
- } else if (item == customCompressLevel) {
- compressLevel.setEnabled(enable);
- } else if (item == desktopSize) {
- desktopWidth.setEnabled(enable);
- desktopHeight.setEnabled(enable);
- } else if (item == noJpeg) {
- qualityLevel.setEnabled(enable);
- } else if (item == encX509) {
- x509ca.setEnabled(enable);
- caButton.setEnabled(enable);
- x509crl.setEnabled(enable);
- crlButton.setEnabled(enable);
- } else if (item == secVeNCrypt) {
- encNone.setEnabled(enable);
- encTLS.setEnabled(enable);
- encX509.setEnabled(enable);
- x509ca.setEnabled(enable && encX509.isSelected());
- caButton.setEnabled(enable && encX509.isSelected());
- x509crl.setEnabled(enable && encX509.isSelected());
- crlButton.setEnabled(enable && encX509.isSelected());
- secIdent.setEnabled(enable);
- secPlain.setEnabled(enable);
- sendLocalUsername.setEnabled(enable);
- } else if (item == encNone) {
- secNone.setSelected(enable &&
- UserPreferences.getBool("viewer", "secNone", true));
- secVnc.setSelected(enable &&
- UserPreferences.getBool("viewer", "secVnc", true));
- } else if (item == secIdent || item == secPlain) {
- sendLocalUsername.setEnabled(secIdent.isSelected() ||
- secPlain.isSelected());
- } else if (item == sshTunnel) {
- sshUseGateway.setEnabled(enable);
- sshUser.setEnabled(enable &&
- sshUseGateway.isEnabled() &&
- sshUseGateway.isSelected());
- sshHost.setEnabled(enable &&
- sshUseGateway.isEnabled() &&
- sshUseGateway.isSelected());
- sshPort.setEnabled(enable &&
- sshUseGateway.isEnabled() &&
- sshUseGateway.isSelected());
- sshUseExt.setEnabled(enable);
- sshClient.setEnabled(enable &&
- sshUseExt.isEnabled() &&
- sshUseExt.isSelected());
- sshClientBrowser.setEnabled(enable &&
- sshUseExt.isEnabled() &&
- sshUseExt.isSelected());
- sshArgsDefault.setEnabled(enable &&
- sshUseExt.isEnabled() &&
- sshUseExt.isSelected());
- sshArgsCustom.setEnabled(enable &&
- sshUseExt.isEnabled() &&
- sshUseExt.isSelected());
- sshArguments.setEnabled(enable &&
- sshUseExt.isEnabled() &&
- sshUseExt.isSelected() &&
- sshArgsCustom.isSelected());
- sshConfig.setEnabled(enable &&
- sshUseExt.isEnabled() &&
- !sshUseExt.isSelected());
- sshConfigBrowser.setEnabled(enable &&
- sshUseExt.isEnabled() &&
- !sshUseExt.isSelected());
- sshKeyFile.setEnabled(enable &&
- sshUseExt.isEnabled() &&
- !sshUseExt.isSelected());
- sshKeyFileBrowser.setEnabled(enable &&
- sshUseExt.isEnabled() &&
- !sshUseExt.isSelected());
- } else if (item == sshUseExt) {
- sshClient.setEnabled(enable);
- sshClientBrowser.setEnabled(enable);
- sshArgsDefault.setEnabled(enable);
- sshArgsCustom.setEnabled(enable);
- sshArguments.setEnabled(enable && sshArgsCustom.isSelected());
- sshConfig.setEnabled(!enable);
- sshConfigBrowser.setEnabled(!enable);
- sshKeyFile.setEnabled(!enable);
- sshKeyFileBrowser.setEnabled(!enable);
- } else if (item == sshUseGateway) {
- sshUser.setEnabled(enable);
- sshHost.setEnabled(enable);
- sshPort.setEnabled(enable);
- }
+ private void handleExtSSH()
+ {
+ if (tunnelCheckbox.isSelected()) {
+ sshClientInput.setEnabled(extSSHCheckbox.isSelected());
+ sshClientChooser.setEnabled(extSSHCheckbox.isSelected());
+ sshArgsDefaultButton.setEnabled(extSSHCheckbox.isSelected());
+ sshArgsCustomButton.setEnabled(extSSHCheckbox.isSelected());
+ sshConfigInput.setEnabled(!extSSHCheckbox.isSelected());
+ sshConfigChooser.setEnabled(!extSSHCheckbox.isSelected());
+ sshKeyFileInput.setEnabled(!extSSHCheckbox.isSelected());
+ sshKeyFileChooser.setEnabled(!extSSHCheckbox.isSelected());
+ if (sshArgsCustomButton.isSelected())
+ sshArgsInput.setEnabled(extSSHCheckbox.isSelected());
+ else
+ sshArgsInput.setEnabled(false);
}
}
+
+ private void handleEmbed()
+ {
+ if (embed.getValue()) {
+ desktopSizeCheckbox.setEnabled(false);
+ desktopWidthInput.setEnabled(false);
+ desktopHeightInput.setEnabled(false);
+ remoteResizeButton.setEnabled(false);
+ remoteScaleButton.setEnabled(false);
+ fullScreenCheckbox.setEnabled(false);
+ fullScreenAllMonitorsCheckbox.setEnabled(false);
+ scalingFactorInput.setEnabled(false);
+ }
+ }
+
+ private void handleRfbState()
+ {
+ CConn cc = VncViewer.cc;
+ if (cc != null && cc.state() == CConnection.RFBSTATE_NORMAL) {
+ JComponent[] components = {
+ encNoneCheckbox, encTLSCheckbox, encX509Checkbox, authNoneCheckbox,
+ authVncCheckbox, authVncCheckbox, authIdentCheckbox, authPlainCheckbox,
+ sendLocalUsernameCheckbox, caInput, caChooser, crlInput, crlChooser,
+ sharedCheckbox, tunnelCheckbox, viaCheckbox, viaUserInput, viaHostInput,
+ viaPortInput, extSSHCheckbox, sshClientInput, sshClientChooser,
+ sshArgsDefaultButton, sshArgsCustomButton, sshArgsInput, sshConfigInput,
+ sshKeyFileInput, sshConfigChooser, sshKeyFileChooser,
+ };
+ for (JComponent c : components)
+ c.setEnabled(false);
+ }
+ }
+
+ static LogWriter vlog = new LogWriter("OptionsDialog");
}
diff --git a/java/com/tigervnc/vncviewer/OptionsDialogCallback.java b/java/com/tigervnc/vncviewer/OptionsDialogCallback.java
deleted file mode 100644
index efb8c06..0000000
--- a/java/com/tigervnc/vncviewer/OptionsDialogCallback.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this software; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
- * USA.
- */
-
-package com.tigervnc.vncviewer;
-
-public interface OptionsDialogCallback {
- public void setOptions();
- public void getOptions();
-}
diff --git a/java/com/tigervnc/vncviewer/Parameters.java b/java/com/tigervnc/vncviewer/Parameters.java
new file mode 100644
index 0000000..50e26cb
--- /dev/null
+++ b/java/com/tigervnc/vncviewer/Parameters.java
@@ -0,0 +1,646 @@
+/* Copyright (C) 2016 Brian P. Hinz
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+package com.tigervnc.vncviewer;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.PrintWriter;
+import java.util.StringTokenizer;
+
+import com.tigervnc.rfb.*;
+import com.tigervnc.rfb.Exception;
+
+public class Parameters {
+
+ public static BoolParameter noLionFS
+ = new BoolParameter("NoLionFS",
+ "On Mac systems, setting this parameter will force the use of the old "+
+ "(pre-Lion) full-screen mode, even if the viewer is running on OS X 10.7 "+
+ "Lion or later.",
+ false);
+
+ public static BoolParameter embed
+ = new BoolParameter("Embed",
+ "If the viewer is being run as an applet, display its output to " +
+ "an embedded frame in the browser window rather than to a dedicated " +
+ "window. Embed=1 implies FullScreen=0 and Scale=100.",
+ false);
+
+ public static BoolParameter dotWhenNoCursor
+ = new BoolParameter("DotWhenNoCursor",
+ "Show the dot cursor when the server sends an invisible cursor",
+ false);
+
+ public static BoolParameter sendLocalUsername
+ = new BoolParameter("SendLocalUsername",
+ "Send the local username for SecurityTypes "+
+ "such as Plain rather than prompting",
+ true);
+
+ public static StringParameter passwordFile
+ = new StringParameter("PasswordFile",
+ "Password file for VNC authentication",
+ "");
+
+ public static AliasParameter passwd
+ = new AliasParameter("passwd",
+ "Alias for PasswordFile",
+ passwordFile);
+
+ public static BoolParameter autoSelect
+ = new BoolParameter("AutoSelect",
+ "Auto select pixel format and encoding",
+ true);
+
+ public static BoolParameter fullColor
+ = new BoolParameter("FullColor",
+ "Use full color - otherwise 6-bit colour is used "+
+ "until AutoSelect decides the link is fast enough",
+ true);
+
+ public static AliasParameter fullColorAlias
+ = new AliasParameter("FullColour",
+ "Alias for FullColor",
+ Parameters.fullColor);
+
+ public static IntParameter lowColorLevel
+ = new IntParameter("LowColorLevel",
+ "Color level to use on slow connections. "+
+ "0 = Very Low (8 colors), 1 = Low (64 colors), "+
+ "2 = Medium (256 colors)",
+ 2);
+
+ public static AliasParameter lowColorLevelAlias
+ = new AliasParameter("LowColourLevel",
+ "Alias for LowColorLevel",
+ lowColorLevel);
+
+ public static StringParameter preferredEncoding
+ = new StringParameter("PreferredEncoding",
+ "Preferred encoding to use (Tight, ZRLE, "+
+ "hextile or raw) - implies AutoSelect=0",
+ "Tight");
+
+ public static BoolParameter remoteResize
+ = new BoolParameter("RemoteResize",
+ "Dynamically resize the remote desktop size as "+
+ "the size of the local client window changes. "+
+ "(Does not work with all servers)",
+ true);
+
+ public static BoolParameter viewOnly
+ = new BoolParameter("ViewOnly",
+ "Don't send any mouse or keyboard events to the server",
+ false);
+
+ public static BoolParameter shared
+ = new BoolParameter("Shared",
+ "Don't disconnect other viewers upon "+
+ "connection - share the desktop instead",
+ false);
+
+ public static BoolParameter maximize
+ = new BoolParameter("Maximize",
+ "Maximize viewer window",
+ false);
+
+ public static BoolParameter fullScreen
+ = new BoolParameter("FullScreen",
+ "Full Screen Mode",
+ false);
+
+ public static BoolParameter fullScreenAllMonitors
+ = new BoolParameter("FullScreenAllMonitors",
+ "Enable full screen over all monitors",
+ true);
+
+ public static BoolParameter acceptClipboard
+ = new BoolParameter("AcceptClipboard",
+ "Accept clipboard changes from the server",
+ true);
+
+ public static BoolParameter sendClipboard
+ = new BoolParameter("SendClipboard",
+ "Send clipboard changes to the server",
+ true);
+
+ public static IntParameter maxCutText
+ = new IntParameter("MaxCutText",
+ "Maximum permitted length of an outgoing clipboard update",
+ 262144);
+
+ public static StringParameter menuKey
+ = new StringParameter("MenuKey",
+ "The key which brings up the popup menu",
+ "F8");
+
+ public static StringParameter desktopSize
+ = new StringParameter("DesktopSize",
+ "Reconfigure desktop size on the server on connect (if possible)",
+ "");
+
+ public static BoolParameter listenMode
+ = new BoolParameter("listen",
+ "Listen for connections from VNC servers",
+ false);
+
+ public static StringParameter scalingFactor
+ = new StringParameter("ScalingFactor",
+ "Reduce or enlarge the remote desktop image. "+
+ "The value is interpreted as a scaling factor "+
+ "in percent. If the parameter is set to "+
+ "\"Auto\", then automatic scaling is "+
+ "performed. Auto-scaling tries to choose a "+
+ "scaling factor in such a way that the whole "+
+ "remote desktop will fit on the local screen. "+
+ "If the parameter is set to \"FixedRatio\", "+
+ "then automatic scaling is performed, but the "+
+ "original aspect ratio is preserved.",
+ "100");
+
+ public static BoolParameter alwaysShowServerDialog
+ = new BoolParameter("AlwaysShowServerDialog",
+ "Always show the server dialog even if a server has been "+
+ "specified in an applet parameter or on the command line",
+ false);
+
+ public static StringParameter vncServerName
+ = new StringParameter("Server",
+ "The VNC server <host>[:<dpyNum>] or <host>::<port>",
+ "");
+
+ public static BoolParameter acceptBell
+ = new BoolParameter("AcceptBell",
+ "Produce a system beep when requested to by the server.",
+ true);
+
+ public static StringParameter via
+ = new StringParameter("Via",
+ "Automatically create an encrypted TCP tunnel to "+
+ "the gateway machine, then connect to the VNC host "+
+ "through that tunnel. By default, this option invokes "+
+ "SSH local port forwarding using the embedded JSch "+
+ "client, however an external SSH client may be specified "+
+ "using the \"-extSSH\" parameter. Note that when using "+
+ "the -via option, the VNC host machine name should be "+
+ "specified from the point of view of the gateway machine, "+
+ "e.g. \"localhost\" denotes the gateway, "+
+ "not the machine on which the viewer was launched. "+
+ "See the System Properties section below for "+
+ "information on configuring the -Via option.", "");
+
+ public static BoolParameter tunnel
+ = new BoolParameter("Tunnel",
+ "The -Tunnel command is basically a shorthand for the "+
+ "-via command when the VNC server and SSH gateway are "+
+ "one and the same. -Tunnel creates an SSH connection "+
+ "to the server and forwards the VNC through the tunnel "+
+ "without the need to specify anything else.", false);
+
+ public static BoolParameter extSSH
+ = new BoolParameter("extSSH",
+ "By default, SSH tunneling uses the embedded JSch client "+
+ "for tunnel creation. This option causes the client to "+
+ "invoke an external SSH client application for all tunneling "+
+ "operations. By default, \"/usr/bin/ssh\" is used, however "+
+ "the path to the external application may be specified using "+
+ "the -SSHClient option.", false);
+
+ public static StringParameter extSSHClient
+ = new StringParameter("extSSHClient",
+ "Specifies the path to an external SSH client application "+
+ "that is to be used for tunneling operations when the -extSSH "+
+ "option is in effect.", "/usr/bin/ssh");
+
+ public static StringParameter extSSHArgs
+ = new StringParameter("extSSHArgs",
+ "Specifies the arguments string or command template to be used "+
+ "by the external SSH client application when the -extSSH option "+
+ "is in effect. The string will be processed according to the same "+
+ "pattern substitution rules as the VNC_TUNNEL_CMD and VNC_VIA_CMD "+
+ "system properties, and can be used to override those in a more "+
+ "command-line friendly way. If not specified, then the appropriate "+
+ "VNC_TUNNEL_CMD or VNC_VIA_CMD command template will be used.", "");
+
+ public static StringParameter sshConfig
+ = new StringParameter("SSHConfig",
+ "Specifies the path to an OpenSSH configuration file that to "+
+ "be parsed by the embedded JSch SSH client during tunneling "+
+ "operations.", FileUtils.getHomeDir()+".ssh/config");
+
+ public static StringParameter sshKey
+ = new StringParameter("SSHKey",
+ "When using the Via or Tunnel options with the embedded SSH client, "+
+ "this parameter specifies the text of the SSH private key to use when "+
+ "authenticating with the SSH server. You can use \\n within the string "+
+ "to specify a new line.", "");
+
+ public static StringParameter sshKeyFile
+ = new StringParameter("SSHKeyFile",
+ "When using the Via or Tunnel options with the embedded SSH client, "+
+ "this parameter specifies a file that contains an SSH private key "+
+ "(or keys) to use when authenticating with the SSH server. If not "+
+ "specified, ~/.ssh/id_dsa or ~/.ssh/id_rsa will be used (if they exist). "+
+ "Otherwise, the client will fallback to prompting for an SSH password.",
+ "");
+
+ public static StringParameter sshKeyPass
+ = new StringParameter("SSHKeyPass",
+ "When using the Via or Tunnel options with the embedded SSH client, "+
+ "this parameter specifies the passphrase for the SSH key.", "");
+
+ public static BoolParameter customCompressLevel
+ = new BoolParameter("CustomCompressLevel",
+ "Use custom compression level. Default if CompressLevel is specified.",
+ false);
+
+ public static IntParameter compressLevel
+ = new IntParameter("CompressLevel",
+ "Use specified compression level. 0 = Low, 6 = High",
+ 1);
+
+ public static BoolParameter noJpeg
+ = new BoolParameter("NoJPEG",
+ "Disable lossy JPEG compression in Tight encoding.",
+ false);
+
+ public static IntParameter qualityLevel
+ = new IntParameter("QualityLevel",
+ "JPEG quality level. 0 = Low, 9 = High",
+ 8);
+
+ private static final String IDENTIFIER_STRING
+ = "TigerVNC Configuration file Version 1.0";
+
+ static VoidParameter[] parameterArray = {
+ CSecurityTLS.X509CA,
+ CSecurityTLS.X509CRL,
+ SecurityClient.secTypes,
+ autoSelect,
+ fullColor,
+ lowColorLevel,
+ preferredEncoding,
+ customCompressLevel,
+ compressLevel,
+ noJpeg,
+ qualityLevel,
+ maximize,
+ fullScreen,
+ fullScreenAllMonitors,
+ desktopSize,
+ remoteResize,
+ viewOnly,
+ shared,
+ acceptClipboard,
+ sendClipboard,
+ menuKey,
+ noLionFS,
+ sendLocalUsername,
+ maxCutText,
+ scalingFactor,
+ acceptBell,
+ via,
+ tunnel,
+ extSSH,
+ extSSHClient,
+ extSSHArgs,
+ sshConfig,
+ sshKeyFile,
+ };
+
+
+ static LogWriter vlog = new LogWriter("Parameters");
+
+ public static void saveViewerParameters(String filename, String servername) {
+
+ // Write to the registry or a predefined file if no filename was specified.
+ String filepath;
+ if (filename == null || filename.isEmpty()) {
+ saveToReg(servername);
+ return;
+ /*
+ String homeDir = FileUtils.getVncHomeDir();
+ if (homeDir == null)
+ throw new Exception("Failed to read configuration file, "+
+ "can't obtain home directory path.");
+ filepath = homeDir.concat("default.tigervnc");
+ */
+ } else {
+ filepath = filename;
+ }
+
+ /* Write parameters to file */
+ File f = new File(filepath);
+ if (f.exists() && !f.canWrite())
+ throw new Exception(String.format("Failed to write configuration file,"+
+ "can't open %s", filepath));
+
+ PrintWriter pw = null;
+ try {
+ pw = new PrintWriter(f, "UTF-8");
+ } catch (java.lang.Exception e) {
+ throw new Exception(e.getMessage());
+ }
+
+ pw.println(IDENTIFIER_STRING);
+ pw.println("");
+
+ if (servername != null && !servername.isEmpty()) {
+ pw.println(String.format("ServerName=%s\n", servername));
+ updateConnHistory(servername);
+ }
+
+ for (int i = 0; i < parameterArray.length; i++) {
+ if (parameterArray[i] instanceof StringParameter) {
+ //if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName()))
+ pw.println(String.format("%s=%s",parameterArray[i].getName(),
+ parameterArray[i].getValueStr()));
+ } else if (parameterArray[i] instanceof IntParameter) {
+ //if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName()))
+ pw.println(String.format("%s=%s",parameterArray[i].getName(),
+ parameterArray[i].getValueStr()));
+ } else if (parameterArray[i] instanceof BoolParameter) {
+ //if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName()))
+ pw.println(String.format("%s=%s",parameterArray[i].getName(),
+ parameterArray[i].getValueStr()));
+ } else {
+ vlog.error(String.format("Unknown parameter type for parameter %s",
+ parameterArray[i].getName()));
+ }
+ }
+
+ pw.flush();
+ pw.close();
+ }
+
+ public static String loadViewerParameters(String filename) throws Exception {
+ String servername = "";
+
+ String filepath;
+ if (filename == null) {
+
+ return loadFromReg();
+
+ /*
+ String homeDir = FileUtils.getVncHomeDir();
+ if (homeDir == null)
+ throw new Exception("Failed to read configuration file, "+
+ "can't obtain home directory path.");
+ filepath = homeDir.concat("default.tigervnc");
+ */
+ } else {
+ filepath = filename;
+ }
+
+ /* Read parameters from file */
+ File f = new File(filepath);
+ if (!f.exists() || !f.canRead()) {
+ if (filename == null || filename.isEmpty())
+ return null;
+ throw new Exception(String.format("Failed to read configuration file, can't open %s",
+ filepath));
+ }
+
+ String line = "";
+ LineNumberReader reader;
+ try {
+ reader = new LineNumberReader(new FileReader(f));
+ } catch (FileNotFoundException e) {
+ throw new Exception(e.getMessage());
+ }
+
+ int lineNr = 0;
+ while (line != null) {
+
+ // Read the next line
+ try {
+ line = reader.readLine();
+ lineNr = reader.getLineNumber();
+ if (line == null)
+ break;
+ } catch (IOException e) {
+ throw new Exception(String.format("Failed to read line %d in file %s: %s",
+ lineNr, filepath, e.getMessage()));
+ }
+
+ // Make sure that the first line of the file has the file identifier string
+ if(lineNr == 1) {
+ if(line.equals(IDENTIFIER_STRING))
+ continue;
+ else
+ throw new Exception(String.format("Configuration file %s is in an invalid format", filename));
+ }
+
+ // Skip empty lines and comments
+ if (line.trim().isEmpty() || line.trim().startsWith("#"))
+ continue;
+
+ // Find the parameter value
+ int idx = line.indexOf("=");
+ if (idx == -1) {
+ vlog.error(String.format("Failed to read line %d in file %s: %s",
+ lineNr, filename, "Invalid format"));
+ continue;
+ }
+ String value = line.substring(idx+1).trim();
+ boolean invalidParameterName = true; // Will be set to false below if
+ // the line contains a valid name.
+
+ if (line.substring(0,idx).trim().equalsIgnoreCase("ServerName")) {
+ if (value.length() > 256) {
+ vlog.error(String.format("Failed to read line %d in file %s: %s",
+ lineNr, filepath, "Invalid format or too large value"));
+ continue;
+ }
+ servername = value;
+ invalidParameterName = false;
+ } else {
+ for (int i = 0; i < parameterArray.length; i++) {
+ if (parameterArray[i] instanceof StringParameter) {
+ if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) {
+ if (value.length() > 256) {
+ vlog.error(String.format("Failed to read line %d in file %s: %s",
+ lineNr, filepath, "Invalid format or too large value"));
+ continue;
+ }
+ ((StringParameter)parameterArray[i]).setParam(value);
+ invalidParameterName = false;
+ }
+ } else if (parameterArray[i] instanceof IntParameter) {
+ if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) {
+ ((IntParameter)parameterArray[i]).setParam(value);
+ invalidParameterName = false;
+ }
+ } else if (parameterArray[i] instanceof BoolParameter) {
+ if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) {
+ ((BoolParameter)parameterArray[i]).setParam(value);
+ invalidParameterName = false;
+ }
+ } else {
+ vlog.error(String.format("Unknown parameter type for parameter %s",
+ parameterArray[i].getName()));
+
+ }
+ }
+ }
+
+ if (invalidParameterName)
+ vlog.info(String.format("Unknown parameter %s on line %d in file %s",
+ line, lineNr, filepath));
+ }
+ try {
+ reader.close();
+ } catch (IOException e) {
+ vlog.info(e.getMessage());
+ } finally {
+ try {
+ if (reader != null)
+ reader.close();
+ } catch (IOException e) { }
+ }
+
+ return servername;
+ }
+
+ public static void saveToReg(String servername) {
+ String hKey = "global";
+
+ if (servername != null && !servername.isEmpty()) {
+ UserPreferences.set(hKey, "ServerName", servername);
+ updateConnHistory(servername);
+ }
+
+ for (int i = 0; i < parameterArray.length; i++) {
+ if (parameterArray[i] instanceof StringParameter) {
+ UserPreferences.set(hKey, parameterArray[i].getName(),
+ parameterArray[i].getValueStr());
+ } else if (parameterArray[i] instanceof IntParameter) {
+ UserPreferences.set(hKey, parameterArray[i].getName(),
+ ((IntParameter)parameterArray[i]).getValue());
+ } else if (parameterArray[i] instanceof BoolParameter) {
+ UserPreferences.set(hKey, parameterArray[i].getName(),
+ ((BoolParameter)parameterArray[i]).getValue());
+ } else {
+ vlog.error(String.format("Unknown parameter type for parameter %s",
+ parameterArray[i].getName()));
+ }
+ }
+
+ UserPreferences.save(hKey);
+ }
+
+ public static String loadFromReg() {
+
+ String hKey = "global";
+
+ String servername = UserPreferences.get(hKey, "ServerName");
+ if (servername == null)
+ servername = "";
+
+ for (int i = 0; i < parameterArray.length; i++) {
+ if (parameterArray[i] instanceof StringParameter) {
+ if (UserPreferences.get(hKey, parameterArray[i].getName()) != null) {
+ String stringValue =
+ UserPreferences.get(hKey, parameterArray[i].getName());
+ ((StringParameter)parameterArray[i]).setParam(stringValue);
+ }
+ } else if (parameterArray[i] instanceof IntParameter) {
+ if (UserPreferences.get(hKey, parameterArray[i].getName()) != null) {
+ int intValue =
+ UserPreferences.getInt(hKey, parameterArray[i].getName());
+ ((IntParameter)parameterArray[i]).setParam(intValue);
+ }
+ } else if (parameterArray[i] instanceof BoolParameter) {
+ if (UserPreferences.get(hKey, parameterArray[i].getName()) != null) {
+ boolean booleanValue =
+ UserPreferences.getBool(hKey, parameterArray[i].getName());
+ ((BoolParameter)parameterArray[i]).setParam(booleanValue);
+ }
+ } else {
+ vlog.error(String.format("Unknown parameter type for parameter %s",
+ parameterArray[i].getName()));
+ }
+ }
+
+ return servername;
+ }
+
+ public static String loadAppletParameters(VncViewer applet) {
+ String servername = applet.getParameter("Server");
+ String serverport = applet.getParameter("Port");
+ String embedParam = applet.getParameter("Embed");
+
+ if (servername == null)
+ servername = applet.getCodeBase().getHost();
+
+ if (serverport != null)
+ servername = servername.concat("::"+serverport);
+ else
+ servername = servername.concat("::5900");
+
+ if (embedParam != null)
+ embed.setParam(embedParam);
+
+ for (int i = 0; i < parameterArray.length; i++) {
+ String value = applet.getParameter(parameterArray[i].getName());
+ if (value == null)
+ continue;
+ if (parameterArray[i] instanceof StringParameter) {
+ if (value.length() > 256) {
+ vlog.error(String.format("Failed to read applet parameter %s: %s",
+ parameterArray[i].getName(),
+ "Invalid format or too large value"));
+ continue;
+ }
+ ((StringParameter)parameterArray[i]).setParam(value);
+ } else if (parameterArray[i] instanceof IntParameter) {
+ ((IntParameter)parameterArray[i]).setParam(value);
+ } else if (parameterArray[i] instanceof BoolParameter) {
+ ((BoolParameter)parameterArray[i]).setParam(value);
+ } else {
+ vlog.error(String.format("Unknown parameter type for parameter %s",
+ parameterArray[i].getName()));
+
+ }
+ }
+
+ return servername;
+ }
+
+ private static void updateConnHistory(String serverName) {
+ String hKey = "ServerDialog";
+ if (serverName != null && !serverName.isEmpty()) {
+ String valueStr = UserPreferences.get(hKey, "history");
+ String t = (valueStr == null) ? "" : valueStr;
+ StringTokenizer st = new StringTokenizer(t, ",");
+ StringBuffer sb = new StringBuffer().append(serverName);
+ while (st.hasMoreTokens()) {
+ String str = st.nextToken();
+ if (!str.equals(serverName) && !str.equals(""))
+ sb.append(',').append(str);
+ }
+ UserPreferences.set(hKey, "history", sb.toString());
+ UserPreferences.save(hKey);
+ }
+ }
+
+}
diff --git a/java/com/tigervnc/vncviewer/PasswdDialog.java b/java/com/tigervnc/vncviewer/PasswdDialog.java
index fbaf991..9d5cc7c 100644
--- a/java/com/tigervnc/vncviewer/PasswdDialog.java
+++ b/java/com/tigervnc/vncviewer/PasswdDialog.java
@@ -149,7 +149,6 @@
String instruction,
String[] prompt,
boolean[] echo) {
- vlog.info("OK");
Container panel = new JPanel(new GridBagLayout());
((JPanel)panel).setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
diff --git a/java/com/tigervnc/vncviewer/PlatformPixelBuffer.java b/java/com/tigervnc/vncviewer/PlatformPixelBuffer.java
index e0e24f0..564eb8e 100644
--- a/java/com/tigervnc/vncviewer/PlatformPixelBuffer.java
+++ b/java/com/tigervnc/vncviewer/PlatformPixelBuffer.java
@@ -24,86 +24,38 @@
import java.nio.ByteOrder;
import com.tigervnc.rfb.*;
+import com.tigervnc.rfb.Point;
-abstract public class PlatformPixelBuffer extends PixelBuffer
+public class PlatformPixelBuffer extends FullFramePixelBuffer
{
- public PlatformPixelBuffer(int w, int h, CConn cc_, DesktopWindow desktop_) {
- cc = cc_;
- desktop = desktop_;
- PixelFormat nativePF = getNativePF();
- if (nativePF.depth > cc.serverPF.depth) {
- setPF(cc.serverPF);
- } else {
- setPF(nativePF);
- }
- resize(w, h);
+ public PlatformPixelBuffer(PixelFormat pf,
+ int w, int h,
+ WritableRaster data)
+ {
+ super(pf, w, h, data);
+ damage = new Rect(0, 0, w, h);
}
- // resize() resizes the image, preserving the image data where possible.
- abstract public void resize(int w, int h);
-
- public PixelFormat getNativePF() {
- PixelFormat pf;
- cm = tk.getColorModel();
- if (cm.getColorSpace().getType() == java.awt.color.ColorSpace.TYPE_RGB) {
- int depth = ((cm.getPixelSize() > 24) ? 24 : cm.getPixelSize());
- int bpp = (depth > 16 ? 32 : (depth > 8 ? 16 : 8));
- ByteOrder byteOrder = ByteOrder.nativeOrder();
- boolean bigEndian = (byteOrder == ByteOrder.BIG_ENDIAN ? true : false);
- boolean trueColour = (depth > 8 ? true : false);
- int redShift = cm.getComponentSize()[0] + cm.getComponentSize()[1];
- int greenShift = cm.getComponentSize()[0];
- int blueShift = 0;
- pf = new PixelFormat(bpp, depth, bigEndian, trueColour,
- (depth > 8 ? 0xff : 0),
- (depth > 8 ? 0xff : 0),
- (depth > 8 ? 0xff : 0),
- (depth > 8 ? redShift : 0),
- (depth > 8 ? greenShift : 0),
- (depth > 8 ? blueShift : 0));
- } else {
- pf = new PixelFormat(8, 8, false, false, 7, 7, 3, 0, 3, 6);
- }
- vlog.debug("Native pixel format is "+pf.print());
- return pf;
- }
-
- abstract public void imageRect(int x, int y, int w, int h, Object pix);
-
- // setColourMapEntries() changes some of the entries in the colourmap.
- // However these settings won't take effect until updateColourMap() is
- // called. This is because getting java to recalculate its internal
- // translation table and redraw the screen is expensive.
-
- public void setColourMapEntries(int firstColour, int nColours_,
- int[] rgbs) {
- nColours = nColours_;
- reds = new byte[nColours];
- blues = new byte[nColours];
- greens = new byte[nColours];
- for (int i = 0; i < nColours; i++) {
- reds[firstColour+i] = (byte)(rgbs[i*3] >> 8);
- greens[firstColour+i] = (byte)(rgbs[i*3+1] >> 8);
- blues[firstColour+i] = (byte)(rgbs[i*3+2] >> 8);
+ public void commitBufferRW(Rect r)
+ {
+ super.commitBufferRW(r);
+ synchronized(damage) {
+ Rect n = damage.union_boundary(r);
+ damage.setXYWH(n.tl.x, n.tl.y, n.width(), n.height());
}
}
- public void updateColourMap() {
- cm = new IndexColorModel(8, nColours, reds, greens, blues);
+ public Rect getDamage() {
+ Rect r = new Rect();
+
+ synchronized(damage) {
+ r.setXYWH(damage.tl.x, damage.tl.y, damage.width(), damage.height());
+ damage.clear();
+ }
+
+ return r;
}
- protected static Toolkit tk = Toolkit.getDefaultToolkit();
+ protected Rect damage;
- abstract public Image getImage();
-
- protected Image image;
-
- int nColours;
- byte[] reds;
- byte[] greens;
- byte[] blues;
-
- CConn cc;
- DesktopWindow desktop;
- static LogWriter vlog = new LogWriter("PlatformPixelBuffer");
}
diff --git a/java/com/tigervnc/vncviewer/ServerDialog.java b/java/com/tigervnc/vncviewer/ServerDialog.java
index 172bde6..001e231 100644
--- a/java/com/tigervnc/vncviewer/ServerDialog.java
+++ b/java/com/tigervnc/vncviewer/ServerDialog.java
@@ -21,8 +21,11 @@
import java.awt.*;
import java.awt.event.*;
+import java.io.File;
+import java.nio.CharBuffer;
import javax.swing.*;
import javax.swing.border.*;
+import javax.swing.filechooser.*;
import javax.swing.WindowConstants.*;
import java.util.*;
@@ -30,51 +33,64 @@
import static java.awt.GridBagConstraints.HORIZONTAL;
import static java.awt.GridBagConstraints.LINE_START;
+import static java.awt.GridBagConstraints.LINE_END;
import static java.awt.GridBagConstraints.NONE;
import static java.awt.GridBagConstraints.REMAINDER;
-class ServerDialog extends Dialog {
+import static com.tigervnc.vncviewer.Parameters.*;
+
+class ServerDialog extends Dialog implements Runnable {
@SuppressWarnings({"unchecked","rawtypes"})
- public ServerDialog(OptionsDialog options_,
- String defaultServerName, CConn cc_) {
-
+ public ServerDialog(String defaultServerName,
+ CharBuffer vncServerName) {
super(true);
- cc = cc_;
+ this.vncServerName = vncServerName;
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
setTitle("VNC Viewer: Connection Details");
setResizable(false);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
- if (VncViewer.nViewers == 1) {
- cc.viewer.exit(1);
- } else {
- ret = false;
- endDialog();
- }
+ endDialog();
+ System.exit(1);
}
});
- options = options_;
+ JLabel serverLabel = new JLabel("VNC server:", JLabel.RIGHT);
+ String valueStr = new String(defaultServerName);
+ ArrayList<String> servernames = new ArrayList<String>();
+ if (!valueStr.isEmpty())
+ servernames.add(valueStr);
+ String history = UserPreferences.get("ServerDialog", "history");
+ if (history != null) {
+ for (String s : history.split(",")) {
+ if (servernames.indexOf(s) < 0)
+ servernames.add(s);
+ }
+ }
+ serverName = new MyJComboBox(servernames.toArray());
+ serverName.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ JComboBox s = (JComboBox)e.getSource();
+ if (e.getActionCommand().equals("comboBoxEdited")) {
+ s.insertItemAt(editor.getItem(), 0);
+ s.setSelectedIndex(0);
+ }
+ }
+ });
+ if (servernames.size() == 0)
+ serverName.setPrototypeDisplayValue("255.255.255.255:5900");
- JLabel serverLabel = new JLabel("VNC Server:", JLabel.RIGHT);
- String valueStr = new String("");
- if (UserPreferences.get("ServerDialog", "history") != null)
- valueStr = UserPreferences.get("ServerDialog", "history");
- server = new MyJComboBox(valueStr.split(","));
- if (valueStr.equals(""))
- server.setPrototypeDisplayValue("255.255.255.255:5900");
-
- server.setEditable(true);
- editor = server.getEditor();
+ serverName.setEditable(true);
+ editor = serverName.getEditor();
editor.getEditorComponent().addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent e) {}
public void keyReleased(KeyEvent e) {}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
- server.insertItemAt(editor.getItem(), 0);
- server.setSelectedIndex(0);
- commit();
+ serverName.insertItemAt(editor.getItem(), 0);
+ serverName.setSelectedIndex(0);
+ handleConnect();
}
}
});
@@ -84,9 +100,41 @@
JLabel icon = new JLabel(VncViewer.logoIcon);
optionsButton = new JButton("Options...");
+ optionsButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ handleOptions();
+ }
+ });
+ JButton loadButton = new JButton("Load...");
+ loadButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ handleLoad();
+ }
+ });
+ JButton saveAsButton = new JButton("Save As...");
+ saveAsButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ handleSaveAs();
+ }
+ });
aboutButton = new JButton("About...");
- okButton = new JButton("OK");
+ aboutButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ handleAbout();
+ }
+ });
cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ handleCancel();
+ }
+ });
+ connectButton = new JButton("Connect \u21B5");
+ connectButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ handleConnect();
+ }
+ });
contentPane.add(icon,
new GridBagConstraints(0, 0,
@@ -102,89 +150,145 @@
LINE_START, NONE,
new Insets(5, 10, 5, 5),
NONE, NONE));
- contentPane.add(server,
+ contentPane.add(serverName,
new GridBagConstraints(2, 0,
REMAINDER, 1,
HEAVY, LIGHT,
LINE_START, HORIZONTAL,
new Insets(5, 0, 5, 5),
NONE, NONE));
- JPanel buttonPane = new JPanel();
- buttonPane.setLayout(new GridLayout(1, 4, 5, 5));
- buttonPane.add(aboutButton);
- buttonPane.add(optionsButton);
- buttonPane.add(okButton);
- buttonPane.add(cancelButton);
- contentPane.add(buttonPane,
+ JPanel buttonPane1 = new JPanel();
+ Box box = Box.createHorizontalBox();
+ JSeparator separator1 = new JSeparator();
+ JSeparator separator2 = new JSeparator();
+ GroupLayout layout = new GroupLayout(buttonPane1);
+ buttonPane1.setLayout(layout);
+ layout.setAutoCreateGaps(false);
+ layout.setAutoCreateContainerGaps(false);
+ layout.setHorizontalGroup(
+ layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGap(10)
+ .addComponent(optionsButton))
+ .addComponent(separator1)
+ .addGroup(layout.createSequentialGroup()
+ .addGap(10)
+ .addComponent(aboutButton)))
+ .addGroup(layout.createParallelGroup(GroupLayout.Alignment.TRAILING)
+ .addGroup(layout.createSequentialGroup()
+ .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(loadButton)
+ .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(saveAsButton)
+ .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(box)
+ .addGap(10))
+ .addComponent(separator2)
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(cancelButton)
+ .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(connectButton)
+ .addGap(10)))
+ );
+ layout.setVerticalGroup(
+ layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
+ .addComponent(optionsButton)
+ .addComponent(loadButton)
+ .addComponent(saveAsButton)
+ .addComponent(box))
+ .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
+ .addComponent(separator1)
+ .addComponent(separator2))
+ .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
+ .addComponent(aboutButton)
+ .addComponent(cancelButton)
+ .addComponent(connectButton))
+ .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
+ );
+ layout.linkSize(SwingConstants.HORIZONTAL,
+ optionsButton, loadButton, saveAsButton,
+ aboutButton, cancelButton, box);
+ contentPane.add(buttonPane1,
new GridBagConstraints(0, 1,
REMAINDER, 1,
LIGHT, LIGHT,
LINE_START, HORIZONTAL,
- new Insets(5, 5, 5, 5),
+ new Insets(5, 0, 10, 0),
NONE, NONE));
- addListeners(this);
pack();
}
- @SuppressWarnings({"unchecked","rawtypes"})
- public void actionPerformed(ActionEvent e) {
- Object s = e.getSource();
- if (s instanceof JButton && (JButton)s == okButton) {
- commit();
- } else if (s instanceof JButton && (JButton)s == cancelButton) {
- if (VncViewer.nViewers == 1)
- cc.viewer.exit(1);
- ret = false;
- endDialog();
- } else if (s instanceof JButton && (JButton)s == optionsButton) {
- options.showDialog(this);
- } else if (s instanceof JButton && (JButton)s == aboutButton) {
- cc.showAbout();
- } else if (s instanceof JComboBox && (JComboBox)s == server) {
- if (e.getActionCommand().equals("comboBoxEdited")) {
- server.insertItemAt(editor.getItem(), 0);
- server.setSelectedIndex(0);
- }
- }
+ public void run() {
+ this.showDialog();
}
- private void commit() {
- String serverName = (String)server.getSelectedItem();
- if (serverName == null || serverName.equals("")) {
- vlog.error("Invalid servername specified");
- if (VncViewer.nViewers == 1)
- cc.viewer.exit(1);
- ret = false;
- endDialog();
+ private void handleOptions() {
+ OptionsDialog.showDialog(this);
+ }
+
+ private void handleLoad() {
+ String title = "Select a TigerVNC configuration file";
+ File dflt = new File(FileUtils.getVncHomeDir().concat("default.tigervnc"));
+ FileNameExtensionFilter filter =
+ new FileNameExtensionFilter("TigerVNC configuration (*.tigervnc)", "tigervnc");
+ File f = showChooser(title, dflt, filter);
+ if (f != null && f.exists() && f.canRead())
+ loadViewerParameters(f.getAbsolutePath());
+ }
+
+ private void handleSaveAs() {
+ String title = "Save the TigerVNC configuration to file";
+ File dflt = new File(FileUtils.getVncHomeDir().concat("default.tigervnc"));
+ if (!dflt.exists() || !dflt.isFile())
+ dflt = new File(FileUtils.getVncHomeDir());
+ FileNameExtensionFilter filter =
+ new FileNameExtensionFilter("TigerVNC configuration (*.tigervnc)", "tigervnc");
+ File f = showChooser(title, dflt, filter);
+ while (f != null && f.exists() && f.isFile()) {
+ String msg = f.getAbsolutePath();
+ msg = msg.concat(" already exists. Do you want to overwrite?");
+ Object[] options = {"Overwrite", "No \u21B5"};
+ JOptionPane op =
+ new JOptionPane(msg, JOptionPane.QUESTION_MESSAGE,
+ JOptionPane.OK_CANCEL_OPTION, null, options, options[1]);
+ JDialog dlg = op.createDialog(this, "TigerVNC Viewer");
+ dlg.setIconImage(VncViewer.frameIcon);
+ dlg.setAlwaysOnTop(true);
+ dlg.setVisible(true);
+ if (op.getValue() == options[0])
+ break;
+ else
+ f = showChooser(title, f, filter);
}
- // set params
- Configuration.setParam("Server", Hostname.getHost(serverName));
- Configuration.setParam("Port",
- Integer.toString(Hostname.getPort(serverName)));
- // Update the history list
- String valueStr = UserPreferences.get("ServerDialog", "history");
- String t = (valueStr == null) ? "" : valueStr;
- StringTokenizer st = new StringTokenizer(t, ",");
- StringBuffer sb =
- new StringBuffer().append((String)server.getSelectedItem());
- while (st.hasMoreTokens()) {
- String str = st.nextToken();
- if (!str.equals((String)server.getSelectedItem()) && !str.equals("")) {
- sb.append(',');
- sb.append(str);
- }
- }
- UserPreferences.set("ServerDialog", "history", sb.toString());
- UserPreferences.save("ServerDialog");
+ if (f != null && (!f.exists() || f.canWrite()))
+ saveViewerParameters(f.getAbsolutePath(), (String)serverName.getSelectedItem());
+ }
+
+ private void handleAbout() {
+ VncViewer.showAbout(this);
+ }
+
+ private void handleCancel() {
endDialog();
}
- CConn cc;
+ private void handleConnect() {
+ String servername = (String)serverName.getSelectedItem();
+ vncServerName.put(servername).flip();
+ saveViewerParameters(null, servername);
+ endDialog();
+ }
+
@SuppressWarnings("rawtypes")
- MyJComboBox server;
+ MyJComboBox serverName;
ComboBoxEditor editor;
- JButton aboutButton, optionsButton, okButton, cancelButton;
+ JButton aboutButton, optionsButton, connectButton, cancelButton;
OptionsDialog options;
+ CharBuffer vncServerName;
static LogWriter vlog = new LogWriter("ServerDialog");
}
diff --git a/java/com/tigervnc/vncviewer/Tunnel.java b/java/com/tigervnc/vncviewer/Tunnel.java
index 90ab38e..bb98ec7 100644
--- a/java/com/tigervnc/vncviewer/Tunnel.java
+++ b/java/com/tigervnc/vncviewer/Tunnel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2016 All Rights Reserved.
+ * Copyright (C) 2012-2016 Brian P. Hinz. All Rights Reserved.
* Copyright (C) 2000 Const Kaplinsky. All Rights Reserved.
* Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
*
@@ -43,6 +43,8 @@
import com.jcraft.jsch.OpenSSHConfig;
import com.jcraft.jsch.Session;
+import static com.tigervnc.vncviewer.Parameters.*;
+
public class Tunnel {
private final static String DEFAULT_TUNNEL_TEMPLATE
@@ -56,27 +58,27 @@
String remoteHost;
remotePort = cc.getServerPort();
- if (cc.viewer.tunnel.getValue()) {
+ if (tunnel.getValue()) {
gatewayHost = cc.getServerName();
remoteHost = "localhost";
} else {
- gatewayHost = getSshHost(cc);
+ gatewayHost = getSshHost();
remoteHost = cc.getServerName();
}
- String pattern = cc.viewer.extSSHArgs.getValue();
- if (pattern == null) {
- if (cc.viewer.tunnel.getValue())
+ String pattern = extSSHArgs.getValue();
+ if (pattern == null || pattern.isEmpty()) {
+ if (tunnel.getValue())
pattern = System.getProperty("VNC_TUNNEL_CMD");
else
pattern = System.getProperty("VNC_VIA_CMD");
}
- if (cc.viewer.extSSH.getValue() ||
+ if (extSSH.getValue() ||
(pattern != null && pattern.length() > 0)) {
- createTunnelExt(gatewayHost, remoteHost, remotePort, localPort, pattern, cc);
+ createTunnelExt(gatewayHost, remoteHost, remotePort, localPort, pattern);
} else {
- createTunnelJSch(gatewayHost, remoteHost, remotePort, localPort, cc);
+ createTunnelJSch(gatewayHost, remoteHost, remotePort, localPort);
}
}
@@ -99,10 +101,10 @@
}
}
- public static String getSshHost(CConn cc) {
- String sshHost = cc.viewer.via.getValue();
- if (sshHost == null)
- return cc.getServerName();
+ public static String getSshHost() {
+ String sshHost = via.getValue();
+ if (sshHost.isEmpty())
+ return vncServerName.getValue();
int end = sshHost.indexOf(":");
if (end < 0)
end = sshHost.length();
@@ -110,37 +112,42 @@
return sshHost;
}
- public static String getSshUser(CConn cc) {
+ public static String getSshUser() {
String sshUser = (String)System.getProperties().get("user.name");
- String via = cc.viewer.via.getValue();
- if (via != null && via.indexOf("@") > 0)
- sshUser = via.substring(0, via.indexOf("@"));
+ String viaStr = via.getValue();
+ if (!viaStr.isEmpty() && viaStr.indexOf("@") > 0)
+ sshUser = viaStr.substring(0, viaStr.indexOf("@"));
return sshUser;
}
- public static int getSshPort(CConn cc) {
+ public static int getSshPort() {
String sshPort = "22";
- String via = cc.viewer.via.getValue();
- if (via != null && via.indexOf(":") > 0)
- sshPort = via.substring(via.indexOf(":")+1, via.length());
+ String viaStr = via.getValue();
+ if (!viaStr.isEmpty() && viaStr.indexOf(":") > 0)
+ sshPort = viaStr.substring(viaStr.indexOf(":")+1, viaStr.length());
return Integer.parseInt(sshPort);
}
- public static String getSshKeyFile(CConn cc) {
- if (cc.viewer.sshKeyFile.getValue() != null)
- return cc.viewer.sshKeyFile.getValue();
+ public static String getSshKeyFile() {
+ if (!sshKeyFile.getValue().isEmpty())
+ return sshKeyFile.getValue();
String[] ids = { "id_dsa", "id_rsa" };
for (String id : ids) {
File f = new File(FileUtils.getHomeDir()+".ssh/"+id);
if (f.exists() && f.canRead())
return(f.getAbsolutePath());
}
- return null;
+ return "";
+ }
+
+ public static String getSshKey() {
+ if (!sshKey.getValue().isEmpty())
+ return sshKeyFile.getValue().replaceAll("\\\\n", "\n");
+ return "";
}
private static void createTunnelJSch(String gatewayHost, String remoteHost,
- int remotePort, int localPort,
- CConn cc) throws Exception {
+ int remotePort, int localPort) throws Exception {
JSch.setLogger(new MyJSchLogger());
JSch jsch=new JSch();
@@ -152,44 +159,39 @@
if (knownHosts.exists() && knownHosts.canRead())
jsch.setKnownHosts(knownHosts.getAbsolutePath());
ArrayList<File> privateKeys = new ArrayList<File>();
- String sshKeyFile = cc.options.sshKeyFile.getText();
- String sshKey = cc.viewer.sshKey.getValue();
- if (sshKey != null) {
- String sshKeyPass = cc.viewer.sshKeyPass.getValue();
+ if (!getSshKey().isEmpty()) {
byte[] keyPass = null, key;
- if (sshKeyPass != null)
- keyPass = sshKeyPass.getBytes();
- sshKey = sshKey.replaceAll("\\\\n", "\n");
- key = sshKey.getBytes();
- jsch.addIdentity("TigerVNC", key, null, keyPass);
- } else if (!sshKeyFile.equals("")) {
- File f = new File(sshKeyFile);
+ if (!sshKeyPass.getValue().isEmpty())
+ keyPass = sshKeyPass.getValue().getBytes();
+ jsch.addIdentity("TigerVNC", getSshKey().getBytes(), null, keyPass);
+ } else if (!getSshKeyFile().isEmpty()) {
+ File f = new File(getSshKeyFile());
if (!f.exists() || !f.canRead())
- throw new Exception("Cannot access SSH key file "+ sshKeyFile);
+ throw new Exception("Cannot access SSH key file "+getSshKeyFile());
privateKeys.add(f);
}
for (Iterator<File> i = privateKeys.iterator(); i.hasNext();) {
File privateKey = (File)i.next();
if (privateKey.exists() && privateKey.canRead())
- if (cc.viewer.sshKeyPass.getValue() != null)
+ if (!sshKeyPass.getValue().isEmpty())
jsch.addIdentity(privateKey.getAbsolutePath(),
- cc.viewer.sshKeyPass.getValue());
+ sshKeyPass.getValue());
else
jsch.addIdentity(privateKey.getAbsolutePath());
}
-
- String user = getSshUser(cc);
+
+ String user = getSshUser();
String label = new String("SSH Authentication");
PasswdDialog dlg =
new PasswdDialog(label, (user == null ? false : true), false);
dlg.userEntry.setText(user != null ? user : "");
- File ssh_config = new File(cc.viewer.sshConfig.getValue());
+ File ssh_config = new File(sshConfig.getValue());
if (ssh_config.exists() && ssh_config.canRead()) {
ConfigRepository repo =
OpenSSHConfig.parse(ssh_config.getAbsolutePath());
jsch.setConfigRepository(repo);
}
- Session session=jsch.getSession(user, gatewayHost, getSshPort(cc));
+ Session session=jsch.getSession(user, gatewayHost, getSshPort());
session.setUserInfo(dlg);
// OpenSSHConfig doesn't recognize StrictHostKeyChecking
if (session.getConfig("StrictHostKeyChecking") == null)
@@ -201,93 +203,19 @@
}
}
- private static class MyExtProcess implements Runnable {
-
- private String cmd = null;
- private Process pid = null;
-
- private static class MyProcessLogger extends Thread {
- private final BufferedReader err;
-
- public MyProcessLogger(Process p) {
- InputStreamReader reader =
- new InputStreamReader(p.getErrorStream());
- err = new BufferedReader(reader);
- }
-
- @Override
- public void run() {
- try {
- while (true) {
- String msg = err.readLine();
- if (msg != null)
- vlog.info(msg);
- }
- } catch(java.io.IOException e) {
- vlog.info(e.getMessage());
- } finally {
- try {
- if (err != null)
- err.close();
- } catch (java.io.IOException e ) { }
- }
- }
- }
-
- private static class MyShutdownHook extends Thread {
-
- private Process proc = null;
-
- public MyShutdownHook(Process p) {
- proc = p;
- }
-
- @Override
- public void run() {
- try {
- proc.exitValue();
- } catch (IllegalThreadStateException e) {
- try {
- // wait for CConn to shutdown the socket
- Thread.sleep(500);
- } catch(InterruptedException ie) { }
- proc.destroy();
- }
- }
- }
-
- public MyExtProcess(String command) {
- cmd = command;
- }
-
- public void run() {
- try {
- Runtime runtime = Runtime.getRuntime();
- pid = runtime.exec(cmd);
- runtime.addShutdownHook(new MyShutdownHook(pid));
- new MyProcessLogger(pid).start();
- pid.waitFor();
- } catch(InterruptedException e) {
- vlog.info(e.getMessage());
- } catch(java.io.IOException e) {
- vlog.info(e.getMessage());
- }
- }
- }
-
private static void createTunnelExt(String gatewayHost, String remoteHost,
int remotePort, int localPort,
- String pattern, CConn cc) throws Exception {
+ String pattern) throws Exception {
if (pattern == null || pattern.length() < 1) {
- if (cc.viewer.tunnel.getValue())
+ if (tunnel.getValue())
pattern = DEFAULT_TUNNEL_TEMPLATE;
else
pattern = DEFAULT_VIA_TEMPLATE;
}
String cmd = fillCmdPattern(pattern, gatewayHost, remoteHost,
- remotePort, localPort, cc);
+ remotePort, localPort);
try {
- Thread t = new Thread(new MyExtProcess(cmd));
+ Thread t = new Thread(new ExtProcess(cmd, vlog, true));
t.start();
// wait for the ssh process to start
Thread.sleep(1000);
@@ -298,21 +226,21 @@
private static String fillCmdPattern(String pattern, String gatewayHost,
String remoteHost, int remotePort,
- int localPort, CConn cc) {
+ int localPort) {
boolean H_found = false, G_found = false, R_found = false, L_found = false;
boolean P_found = false;
- String cmd = cc.options.sshClient.getText() + " ";
+ String cmd = extSSHClient.getValue() + " ";
pattern.replaceAll("^\\s+", "");
- String user = getSshUser(cc);
- int sshPort = getSshPort(cc);
+ String user = getSshUser();
+ int sshPort = getSshPort();
gatewayHost = user + "@" + gatewayHost;
for (int i = 0; i < pattern.length(); i++) {
if (pattern.charAt(i) == '%') {
switch (pattern.charAt(++i)) {
case 'H':
- cmd += (cc.viewer.tunnel.getValue() ? gatewayHost : remoteHost);
+ cmd += (tunnel.getValue() ? gatewayHost : remoteHost);
H_found = true;
continue;
case 'G':
@@ -342,7 +270,7 @@
if (!H_found || !R_found || !L_found)
throw new Exception("%H, %R or %L absent in tunneling command template.");
- if (!cc.viewer.tunnel.getValue() && !G_found)
+ if (!tunnel.getValue() && !G_found)
throw new Exception("%G pattern absent in tunneling command template.");
vlog.info("SSH command line: "+cmd);
diff --git a/java/com/tigervnc/vncviewer/Viewport.java b/java/com/tigervnc/vncviewer/Viewport.java
index b55744b..bf07d2d 100644
--- a/java/com/tigervnc/vncviewer/Viewport.java
+++ b/java/com/tigervnc/vncviewer/Viewport.java
@@ -1,6 +1,8 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright (C) 2011-2015 Brian P. Hinz
- * Copyright (C) 2012-2013 D. R. Commander. All Rights Reserved.
+ * Copyright (C) 2006 Constantin Kaplinsky. All Rights Reserved.
+ * Copyright (C) 2009 Paul Donohue. All Rights Reserved.
+ * Copyright (C) 2010, 2012-2013 D. R. Commander. All Rights Reserved.
+ * Copyright (C) 2011-2014 Brian P. Hinz
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,203 +20,443 @@
* USA.
*/
-package com.tigervnc.vncviewer;
+//
+// DesktopWindow is an AWT Canvas representing a VNC desktop.
+//
+// Methods on DesktopWindow are called from both the GUI thread and the thread
+// which processes incoming RFB messages ("the RFB thread"). This means we
+// need to be careful with synchronization here.
+//
+package com.tigervnc.vncviewer;
+import java.awt.*;
import java.awt.Color;
+import java.awt.color.ColorSpace;
import java.awt.event.*;
-import java.awt.Dimension;
-import java.awt.Event;
-import java.awt.GraphicsConfiguration;
-import java.awt.GraphicsDevice;
-import java.awt.GraphicsEnvironment;
-import java.awt.Image;
-import java.awt.Insets;
-import java.awt.Window;
-import java.lang.reflect.*;
+import java.awt.geom.AffineTransform;
+import java.awt.image.*;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.Clipboard;
+import java.io.BufferedReader;
+import java.nio.*;
import javax.swing.*;
+import javax.imageio.*;
+import java.io.*;
+
import com.tigervnc.rfb.*;
-import java.lang.Exception;
-import java.awt.Rectangle;
+import com.tigervnc.rfb.Cursor;
+import com.tigervnc.rfb.Point;
-import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
-import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER;
-import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
-import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
+import static com.tigervnc.vncviewer.Parameters.*;
-public class Viewport extends JFrame
-{
- public Viewport(String name, CConn cc_) {
+class Viewport extends JPanel implements MouseListener,
+ MouseMotionListener, MouseWheelListener, KeyListener {
+
+ static LogWriter vlog = new LogWriter("Viewport");
+
+ public Viewport(int w, int h, PixelFormat serverPF, CConn cc_)
+ {
cc = cc_;
- setTitle(name+" - TigerVNC");
- setFocusable(false);
- setFocusTraversalKeysEnabled(false);
- if (!VncViewer.os.startsWith("mac os x"))
- setIconImage(VncViewer.frameIcon);
- UIManager.getDefaults().put("ScrollPane.ancestorInputMap",
- new UIDefaults.LazyInputMap(new Object[]{}));
- sp = new JScrollPane();
- sp.getViewport().setBackground(Color.BLACK);
- sp.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
- getContentPane().add(sp);
- if (VncViewer.os.startsWith("mac os x")) {
- if (!VncViewer.noLionFS.getValue())
- enableLionFS();
- }
- addWindowFocusListener(new WindowAdapter() {
- public void windowGainedFocus(WindowEvent e) {
- if (isVisible())
- sp.getViewport().getView().requestFocusInWindow();
+ setScaledSize(cc.cp.width, cc.cp.height);
+ frameBuffer = createFramebuffer(serverPF, w, h);
+ assert(frameBuffer != null);
+ setBackground(Color.BLACK);
+
+ cc.setFramebuffer(frameBuffer);
+ OptionsDialog.addCallback("handleOptions", this);
+
+ addMouseListener(this);
+ addMouseWheelListener(this);
+ addMouseMotionListener(this);
+ addKeyListener(this);
+ addFocusListener(new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ ClipboardDialog.clientCutText();
}
- public void windowLostFocus(WindowEvent e) {
+ public void focusLost(FocusEvent e) {
cc.releaseDownKeys();
}
});
- addWindowListener(new WindowAdapter() {
- public void windowClosing(WindowEvent e) {
- if (VncViewer.nViewers == 1) {
- if (cc.closeListener != null) {
- cc.close();
- } else {
- cc.viewer.exit(1);
- }
- } else {
- cc.close();
- }
- }
- });
- addComponentListener(new ComponentAdapter() {
- public void componentResized(ComponentEvent e) {
- String scaleString = cc.viewer.scalingFactor.getValue();
- if (scaleString.equalsIgnoreCase("Auto") ||
- scaleString.equalsIgnoreCase("FixedRatio")) {
- if ((sp.getSize().width != cc.desktop.scaledWidth) ||
- (sp.getSize().height != cc.desktop.scaledHeight)) {
- cc.desktop.setScaledSize();
- sp.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER);
- sp.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_NEVER);
- sp.validate();
- if (getExtendedState() != JFrame.MAXIMIZED_BOTH &&
- !cc.fullScreen) {
- sp.setSize(new Dimension(cc.desktop.scaledWidth,
- cc.desktop.scaledHeight));
- int w = cc.desktop.scaledWidth + getInsets().left +
- getInsets().right;
- int h = cc.desktop.scaledHeight + getInsets().top +
- getInsets().bottom;
- if (scaleString.equalsIgnoreCase("FixedRatio"))
- setSize(w, h);
- }
- }
- } else {
- sp.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
- sp.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
- sp.validate();
- }
- if (cc.desktop.cursor != null) {
- Cursor cursor = cc.desktop.cursor;
- cc.setCursor(cursor.width(),cursor.height(),cursor.hotspot,
- cursor.data, cursor.mask);
- }
- }
- });
+ setFocusTraversalKeysEnabled(false);
+ setFocusable(true);
+
+ // Send a fake pointer event so that the server will stop rendering
+ // a server-side cursor. Ideally we'd like to send the actual pointer
+ // position, but we can't really tell when the window manager is done
+ // placing us so we don't have a good time for that.
+ cc.writer().writePointerEvent(new Point(w/2, h/2), 0);
}
- boolean lionFSSupported() { return canDoLionFS; }
+ // Most efficient format (from Viewport's point of view)
+ public PixelFormat getPreferredPF()
+ {
+ return frameBuffer.getPF();
+ }
- void enableLionFS() {
- try {
- String version = System.getProperty("os.version");
- int firstDot = version.indexOf('.');
- int lastDot = version.lastIndexOf('.');
- if (lastDot > firstDot && lastDot >= 0) {
- version = version.substring(0, version.indexOf('.', firstDot + 1));
+ // Copy the areas of the framebuffer that have been changed (damaged)
+ // to the displayed window.
+ public void updateWindow() {
+ Rect r = frameBuffer.getDamage();
+ if (!r.is_empty()) {
+ if (image == null)
+ image = (BufferedImage)createImage(frameBuffer.width(), frameBuffer.height());
+ image.getRaster().setDataElements(r.tl.x, r.tl.y, frameBuffer.getBuffer(r));
+ if (cc.cp.width != scaledWidth ||
+ cc.cp.height != scaledHeight) {
+ AffineTransform t = new AffineTransform();
+ t.scale((double)scaleRatioX, (double)scaleRatioY);
+ Rectangle s = new Rectangle(r.tl.x, r.tl.y, r.width(), r.height());
+ s = t.createTransformedShape(s).getBounds();
+ paintImmediately(s.x, s.y, s.width, s.height);
+ } else {
+ paintImmediately(r.tl.x, r.tl.y, r.width(), r.height());
}
- double v = Double.parseDouble(version);
- if (v < 10.7)
- throw new Exception("Operating system version is " + v);
-
- Class fsuClass = Class.forName("com.apple.eawt.FullScreenUtilities");
- Class argClasses[] = new Class[]{Window.class, Boolean.TYPE};
- Method setWindowCanFullScreen =
- fsuClass.getMethod("setWindowCanFullScreen", argClasses);
- setWindowCanFullScreen.invoke(fsuClass, this, true);
-
- canDoLionFS = true;
- } catch (Exception e) {
- vlog.debug("Could not enable OS X 10.7+ full-screen mode: " +
- e.getMessage());
}
}
- public void toggleLionFS() {
- try {
- Class appClass = Class.forName("com.apple.eawt.Application");
- Method getApplication = appClass.getMethod("getApplication",
- (Class[])null);
- Object app = getApplication.invoke(appClass);
- Method requestToggleFullScreen =
- appClass.getMethod("requestToggleFullScreen", Window.class);
- requestToggleFullScreen.invoke(app, this);
- } catch (Exception e) {
- vlog.debug("Could not toggle OS X 10.7+ full-screen mode: " +
- e.getMessage());
- }
- }
+ static final int[] dotcursor_xpm = {
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0xff000000, 0xff000000, 0xff000000, 0x00000000,
+ 0x00000000, 0xff000000, 0xff000000, 0xff000000, 0x00000000,
+ 0x00000000, 0xff000000, 0xff000000, 0xff000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ };
- public JViewport getViewport() {
- return sp.getViewport();
- }
+ public void setCursor(int width, int height, Point hotspot,
+ byte[] data, byte[] mask)
+ {
- public void setGeometry(int x, int y, int w, int h) {
- pack();
- if (!cc.fullScreen)
- setLocation(x, y);
- }
+ int mask_len = ((width+7)/8) * height;
+ int i;
- public Dimension getScreenSize() {
- return getScreenBounds().getSize();
- }
+ for (i = 0; i < mask_len; i++)
+ if ((mask[i] & 0xff) != 0) break;
- public Rectangle getScreenBounds() {
- GraphicsEnvironment ge =
- GraphicsEnvironment.getLocalGraphicsEnvironment();
- Rectangle r = new Rectangle();
- setMaximizedBounds(null);
- if (cc.viewer.fullScreenAllMonitors.getValue()) {
- for (GraphicsDevice gd : ge.getScreenDevices())
- for (GraphicsConfiguration gc : gd.getConfigurations())
- r = r.union(gc.getBounds());
- Rectangle mb = new Rectangle(r);
- mb.grow(getInsets().left, getInsets().bottom);
- setMaximizedBounds(mb);
+ if ((i == mask_len) && dotWhenNoCursor.getValue()) {
+ vlog.debug("cursor is empty - using dot");
+ cursor = new BufferedImage(5, 5, BufferedImage.TYPE_INT_ARGB_PRE);
+ cursor.setRGB(0, 0, 5, 5, dotcursor_xpm, 0, 5);
+ cursorHotspot.x = cursorHotspot.y = 3;
} else {
- GraphicsDevice gd = ge.getDefaultScreenDevice();
- GraphicsConfiguration gc = gd.getDefaultConfiguration();
- r = gc.getBounds();
+ if ((width == 0) || (height == 0)) {
+ cursor = new BufferedImage(tk.getBestCursorSize(0, 0).width,
+ tk.getBestCursorSize(0, 0).height,
+ BufferedImage.TYPE_INT_ARGB_PRE);
+ cursorHotspot.x = cursorHotspot.y = 0;
+ } else {
+ ByteBuffer buffer = ByteBuffer.allocate(width*height*4);
+ ByteBuffer in, o, m;
+ int m_width;
+
+ PixelFormat pf;
+
+ pf = cc.cp.pf();
+
+ in = (ByteBuffer)ByteBuffer.wrap(data).mark();
+ o = (ByteBuffer)buffer.duplicate().mark();
+ m = ByteBuffer.wrap(mask);
+ m_width = (width+7)/8;
+
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ // NOTE: BufferedImage needs ARGB, rather than RGBA
+ if ((m.get((m_width*y)+(x/8)) & 0x80>>(x%8)) != 0)
+ o.put((byte)255);
+ else
+ o.put((byte)0);
+
+ pf.rgbFromBuffer(o, in.duplicate(), 1);
+
+ o.position(o.reset().position() + 4).mark();
+ in.position(in.position() + pf.bpp/8);
+ }
+ }
+
+ IntBuffer rgb =
+ IntBuffer.allocate(width*height).put(buffer.asIntBuffer());
+ cursor = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
+ cursor.setRGB(0, 0, width, height, rgb.array(), 0, width);
+
+ cursorHotspot = hotspot;
+
+ }
}
- return r;
+
+ int cw = (int)Math.floor((float)cursor.getWidth() * scaleRatioX);
+ int ch = (int)Math.floor((float)cursor.getHeight() * scaleRatioY);
+
+ int x = (int)Math.floor((float)cursorHotspot.x * scaleRatioX);
+ int y = (int)Math.floor((float)cursorHotspot.y * scaleRatioY);
+
+ java.awt.Cursor softCursor;
+
+ Dimension cs = tk.getBestCursorSize(cw, ch);
+ if (cs.width != cw && cs.height != ch) {
+ cw = Math.min(cw, cs.width);
+ ch = Math.min(ch, cs.height);
+ x = (int)Math.min(x, Math.max(cs.width - 1, 0));
+ y = (int)Math.min(y, Math.max(cs.height - 1, 0));
+ BufferedImage scaledImage =
+ new BufferedImage(cs.width, cs.height, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2 = scaledImage.createGraphics();
+ g2.setRenderingHint(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+ g2.drawImage(cursor,
+ 0, 0, cw, ch,
+ 0, 0, cursor.getWidth(), cursor.getHeight(), null);
+ g2.dispose();
+ java.awt.Point hs = new java.awt.Point(x, y);
+ softCursor = tk.createCustomCursor(scaledImage, hs, "softCursor");
+ scaledImage.flush();
+ } else {
+ java.awt.Point hs = new java.awt.Point(x, y);
+ softCursor = tk.createCustomCursor(cursor, hs, "softCursor");
+ }
+
+ cursor.flush();
+
+ setCursor(softCursor);
+
}
- public static Window getFullScreenWindow() {
- GraphicsEnvironment ge =
- GraphicsEnvironment.getLocalGraphicsEnvironment();
- GraphicsDevice gd = ge.getDefaultScreenDevice();
- Window fullScreenWindow = gd.getFullScreenWindow();
- return fullScreenWindow;
+ public void resize(int x, int y, int w, int h) {
+ if ((w != frameBuffer.width()) || (h != frameBuffer.height())) {
+ vlog.debug("Resizing framebuffer from "+frameBuffer.width()+"x"+
+ frameBuffer.height()+" to "+w+"x"+h);
+ frameBuffer = createFramebuffer(frameBuffer.getPF(), w, h);
+ assert(frameBuffer != null);
+ cc.setFramebuffer(frameBuffer);
+ image = null;
+ }
+ setScaledSize(w, h);
}
- public static void setFullScreenWindow(Window fullScreenWindow) {
- GraphicsEnvironment ge =
- GraphicsEnvironment.getLocalGraphicsEnvironment();
- GraphicsDevice gd = ge.getDefaultScreenDevice();
- if (gd.isFullScreenSupported())
- gd.setFullScreenWindow(fullScreenWindow);
+ private PlatformPixelBuffer createFramebuffer(PixelFormat pf, int w, int h)
+ {
+ PlatformPixelBuffer fb;
+
+ fb = new JavaPixelBuffer(w, h);
+
+ return fb;
}
- CConn cc;
- JScrollPane sp;
- boolean canDoLionFS;
- static LogWriter vlog = new LogWriter("Viewport");
+ //
+ // Callback methods to determine geometry of our Component.
+ //
+
+ public Dimension getPreferredSize() {
+ return new Dimension(scaledWidth, scaledHeight);
+ }
+
+ public Dimension getMinimumSize() {
+ return new Dimension(scaledWidth, scaledHeight);
+ }
+
+ public Dimension getMaximumSize() {
+ return new Dimension(scaledWidth, scaledHeight);
+ }
+
+ public void paintComponent(Graphics g) {
+ Graphics2D g2 = (Graphics2D)g;
+ if (cc.cp.width != scaledWidth ||
+ cc.cp.height != scaledHeight) {
+ g2.setRenderingHint(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+ g2.drawImage(image, 0, 0, scaledWidth, scaledHeight, null);
+ } else {
+ g2.drawImage(image, 0, 0, null);
+ }
+ g2.dispose();
+ }
+
+ // Mouse-Motion callback function
+ private void mouseMotionCB(MouseEvent e) {
+ if (!viewOnly.getValue() &&
+ e.getX() >= 0 && e.getX() <= scaledWidth &&
+ e.getY() >= 0 && e.getY() <= scaledHeight)
+ cc.writePointerEvent(translateMouseEvent(e));
+ }
+ public void mouseDragged(MouseEvent e) { mouseMotionCB(e); }
+ public void mouseMoved(MouseEvent e) { mouseMotionCB(e); }
+
+ // Mouse callback function
+ private void mouseCB(MouseEvent e) {
+ if (!viewOnly.getValue())
+ if ((e.getID() == MouseEvent.MOUSE_RELEASED) ||
+ (e.getX() >= 0 && e.getX() <= scaledWidth &&
+ e.getY() >= 0 && e.getY() <= scaledHeight))
+ cc.writePointerEvent(translateMouseEvent(e));
+ }
+ public void mouseReleased(MouseEvent e) { mouseCB(e); }
+ public void mousePressed(MouseEvent e) { mouseCB(e); }
+ public void mouseClicked(MouseEvent e) {}
+ public void mouseEntered(MouseEvent e) {
+ if (embed.getValue())
+ requestFocus();
+ }
+ public void mouseExited(MouseEvent e) {}
+
+ // MouseWheel callback function
+ private void mouseWheelCB(MouseWheelEvent e) {
+ if (!viewOnly.getValue())
+ cc.writeWheelEvent(e);
+ }
+
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ mouseWheelCB(e);
+ }
+
+ private static final Integer keyEventLock = 0;
+
+ // Handle the key-typed event.
+ public void keyTyped(KeyEvent e) { }
+
+ // Handle the key-released event.
+ public void keyReleased(KeyEvent e) {
+ synchronized(keyEventLock) {
+ cc.writeKeyEvent(e);
+ }
+ }
+
+ // Handle the key-pressed event.
+ public void keyPressed(KeyEvent e)
+ {
+ if (e.getKeyCode() == MenuKey.getMenuKeyCode()) {
+ java.awt.Point pt = e.getComponent().getMousePosition();
+ if (pt != null) {
+ F8Menu menu = new F8Menu(cc);
+ menu.show(e.getComponent(), (int)pt.getX(), (int)pt.getY());
+ }
+ return;
+ }
+ int ctrlAltShiftMask = Event.SHIFT_MASK | Event.CTRL_MASK | Event.ALT_MASK;
+ if ((e.getModifiers() & ctrlAltShiftMask) == ctrlAltShiftMask) {
+ switch (e.getKeyCode()) {
+ case KeyEvent.VK_A:
+ VncViewer.showAbout(this);
+ return;
+ case KeyEvent.VK_F:
+ if (cc.desktop.fullscreen_active())
+ cc.desktop.fullscreen_on();
+ else
+ cc.desktop.fullscreen_off();
+ return;
+ case KeyEvent.VK_H:
+ cc.refresh();
+ return;
+ case KeyEvent.VK_I:
+ cc.showInfo();
+ return;
+ case KeyEvent.VK_O:
+ OptionsDialog.showDialog(this);
+ return;
+ case KeyEvent.VK_W:
+ VncViewer.newViewer();
+ return;
+ case KeyEvent.VK_LEFT:
+ case KeyEvent.VK_RIGHT:
+ case KeyEvent.VK_UP:
+ case KeyEvent.VK_DOWN:
+ return;
+ }
+ }
+ if ((e.getModifiers() & Event.META_MASK) == Event.META_MASK) {
+ switch (e.getKeyCode()) {
+ case KeyEvent.VK_COMMA:
+ case KeyEvent.VK_N:
+ case KeyEvent.VK_W:
+ case KeyEvent.VK_I:
+ case KeyEvent.VK_R:
+ case KeyEvent.VK_L:
+ case KeyEvent.VK_F:
+ case KeyEvent.VK_Z:
+ case KeyEvent.VK_T:
+ return;
+ }
+ }
+ synchronized(keyEventLock) {
+ cc.writeKeyEvent(e);
+ }
+ }
+
+ public void setScaledSize(int width, int height)
+ {
+ assert(width != 0 && height != 0);
+ String scaleString = scalingFactor.getValue();
+ if (remoteResize.getValue()) {
+ scaledWidth = width;
+ scaledHeight = height;
+ scaleRatioX = 1.00f;
+ scaleRatioY = 1.00f;
+ } else {
+ if (scaleString.matches("^[0-9]+$")) {
+ int scalingFactor = Integer.parseInt(scaleString);
+ scaledWidth =
+ (int)Math.floor((float)width * (float)scalingFactor/100.0);
+ scaledHeight =
+ (int)Math.floor((float)height * (float)scalingFactor/100.0);
+ } else if (scaleString.equalsIgnoreCase("Auto")) {
+ scaledWidth = width;
+ scaledHeight = height;
+ } else {
+ float widthRatio = (float)width / (float)cc.cp.width;
+ float heightRatio = (float)height / (float)cc.cp.height;
+ float ratio = Math.min(widthRatio, heightRatio);
+ scaledWidth = (int)Math.floor(cc.cp.width * ratio);
+ scaledHeight = (int)Math.floor(cc.cp.height * ratio);
+ }
+ scaleRatioX = (float)scaledWidth / (float)cc.cp.width;
+ scaleRatioY = (float)scaledHeight / (float)cc.cp.height;
+ }
+ if (scaledWidth != getWidth() || scaledHeight != getHeight())
+ setSize(new Dimension(scaledWidth, scaledHeight));
+ }
+
+ private MouseEvent translateMouseEvent(MouseEvent e)
+ {
+ if (cc.cp.width != scaledWidth ||
+ cc.cp.height != scaledHeight) {
+ int sx = (scaleRatioX == 1.00) ?
+ e.getX() : (int)Math.floor(e.getX() / scaleRatioX);
+ int sy = (scaleRatioY == 1.00) ?
+ e.getY() : (int)Math.floor(e.getY() / scaleRatioY);
+ e.translatePoint(sx - e.getX(), sy - e.getY());
+ }
+ return e;
+ }
+
+ public void handleOptions()
+ {
+ /*
+ setScaledSize(cc.cp.width, cc.cp.height);
+ if (!oldSize.equals(new Dimension(scaledWidth, scaledHeight))) {
+ // Re-layout the DesktopWindow when the scaled size changes.
+ // Ideally we'd do this with a ComponentListener, but unfortunately
+ // sometimes a spurious resize event is triggered on the viewport
+ // when the DesktopWindow is manually resized via the drag handles.
+ if (cc.desktop != null && cc.desktop.isVisible()) {
+ JScrollPane scroll = (JScrollPane)((JViewport)getParent()).getParent();
+ scroll.setViewportBorder(BorderFactory.createEmptyBorder(0,0,0,0));
+ cc.desktop.pack();
+ }
+ */
+ }
+
+ // access to cc by different threads is specified in CConn
+ private CConn cc;
+ private BufferedImage image;
+
+ // access to the following must be synchronized:
+ public PlatformPixelBuffer frameBuffer;
+
+ static Toolkit tk = Toolkit.getDefaultToolkit();
+
+ public int scaledWidth = 0, scaledHeight = 0;
+ float scaleRatioX, scaleRatioY;
+
+ BufferedImage cursor;
+ Point cursorHotspot = new Point();
+
}
-
diff --git a/java/com/tigervnc/vncviewer/VncViewer.java b/java/com/tigervnc/vncviewer/VncViewer.java
index fc9c7b5..d690cc2 100644
--- a/java/com/tigervnc/vncviewer/VncViewer.java
+++ b/java/com/tigervnc/vncviewer/VncViewer.java
@@ -1,7 +1,7 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB
* Copyright (C) 2011-2013 D. R. Commander. All Rights Reserved.
- * Copyright (C) 2011-2015 Brian P. Hinz
+ * Copyright (C) 2011-2016 Brian P. Hinz
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -35,29 +35,38 @@
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
+import java.io.BufferedReader;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.IOException;
import java.io.File;
import java.lang.Character;
import java.lang.reflect.*;
+import java.net.URL;
+import java.nio.CharBuffer;
+import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
-import java.util.*;
import javax.swing.*;
+import javax.swing.border.*;
import javax.swing.plaf.FontUIResource;
+import javax.swing.SwingUtilities;
import javax.swing.UIManager.*;
import com.tigervnc.rdr.*;
import com.tigervnc.rfb.*;
import com.tigervnc.network.*;
+import static com.tigervnc.vncviewer.Parameters.*;
+
public class VncViewer extends javax.swing.JApplet
implements Runnable, ActionListener {
- public static final String aboutText = new String("TigerVNC Java Viewer v%s (%s)%n"+
- "Built on %s at %s%n"+
- "Copyright (C) 1999-2016 TigerVNC Team and many others (see README.txt)%n"+
- "See http://www.tigervnc.org for information on TigerVNC.");
+ public static final String aboutText =
+ new String("TigerVNC Java Viewer v%s (%s)%n"+
+ "Built on %s at %s%n"+
+ "Copyright (C) 1999-2016 TigerVNC Team and many others (see README.txt)%n"+
+ "See http://www.tigervnc.org for information on TigerVNC.");
public static String version = null;
public static String build = null;
@@ -73,6 +82,11 @@
VncViewer.class.getResourceAsStream("timestamp");
public static final String os =
System.getProperty("os.name").toLowerCase();
+ private static VncViewer applet;
+
+ private String defaultServerName;
+ int VNCSERVERNAMELEN = 64;
+ CharBuffer vncServerName = CharBuffer.allocate(VNCSERVERNAMELEN);
public static void setLookAndFeel() {
try {
@@ -133,12 +147,12 @@
viewer.start();
}
+ public VncViewer() {
+ // Only called in applet mode
+ this(new String[0]);
+ }
public VncViewer(String[] argv) {
- embed.setParam(false);
-
- // load user preferences
- UserPreferences.load("global");
SecurityClient.setDefaults();
@@ -150,14 +164,23 @@
Configuration.enableViewerParams();
+ /* Load the default parameter settings */
+ try {
+ defaultServerName = loadViewerParameters(null);
+ } catch (com.tigervnc.rfb.Exception e) {
+ defaultServerName = "";
+ vlog.info(e.getMessage());
+ }
+
// Override defaults with command-line options
for (int i = 0; i < argv.length; i++) {
if (argv[i].length() == 0)
continue;
if (argv[i].equalsIgnoreCase("-config")) {
- if (++i >= argv.length) usage();
- Configuration.load(argv[i]);
+ if (++i >= argv.length)
+ usage();
+ defaultServerName = loadViewerParameters(argv[i]);
continue;
}
@@ -181,28 +204,9 @@
usage();
}
- if (vncServerName.getValue() != null)
- usage();
- vncServerName.setParam(argv[i]);
+ vncServerName.put(argv[i].toCharArray()).flip();
}
- if (!autoSelect.hasBeenSet()) {
- // Default to AutoSelect=0 if -PreferredEncoding or -FullColor is used
- autoSelect.setParam(!preferredEncoding.hasBeenSet() &&
- !fullColour.hasBeenSet() &&
- !fullColourAlias.hasBeenSet());
- }
- if (!fullColour.hasBeenSet() && !fullColourAlias.hasBeenSet()) {
- // Default to FullColor=0 if AutoSelect=0 && LowColorLevel is set
- if (!autoSelect.getValue() && (lowColourLevel.hasBeenSet() ||
- lowColourLevelAlias.hasBeenSet())) {
- fullColour.setParam(false);
- }
- }
- if (!customCompressLevel.hasBeenSet()) {
- // Default to CustomCompressLevel=1 if CompressLevel is used.
- customCompressLevel.setParam(compressLevel.hasBeenSet());
- }
}
public static void usage() {
@@ -272,26 +276,27 @@
System.exit(1);
}
- public VncViewer() {
- UserPreferences.load("global");
- embed.setParam(true);
- }
-
- public static void newViewer(VncViewer oldViewer, Socket sock, boolean close) {
- VncViewer viewer = new VncViewer();
- viewer.embed.setParam(oldViewer.embed.getValue());
- viewer.sock = sock;
- viewer.start();
- if (close)
- oldViewer.exit(0);
- }
-
- public static void newViewer(VncViewer oldViewer, Socket sock) {
- newViewer(oldViewer, sock, false);
- }
-
- public static void newViewer(VncViewer oldViewer) {
- newViewer(oldViewer, null);
+ public static void newViewer() {
+ String cmd = "java -jar ";
+ try {
+ URL url =
+ VncViewer.class.getProtectionDomain().getCodeSource().getLocation();
+ File f = new File(url.toURI());
+ if (!f.exists() || !f.canRead()) {
+ String msg = new String("The jar file "+f.getAbsolutePath()+
+ " does not exist or cannot be read.");
+ JOptionPane.showMessageDialog(null, msg, "ERROR",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ cmd = cmd.concat(f.getAbsolutePath());
+ Thread t = new Thread(new ExtProcess(cmd, vlog));
+ t.start();
+ } catch (java.net.URISyntaxException e) {
+ vlog.info(e.getMessage());
+ } catch (java.lang.Exception e) {
+ vlog.info(e.getMessage());
+ }
}
public boolean isAppletDragStart(MouseEvent e) {
@@ -311,7 +316,7 @@
public void appletDragStarted() {
embed.setParam(false);
- cc.recreateViewport();
+ //cc.recreateViewport();
JFrame f = (JFrame)JOptionPane.getFrameForComponent(this);
// The default JFrame created by the drag event will be
// visible briefly between appletDragStarted and Finished.
@@ -320,7 +325,6 @@
}
public void appletDragFinished() {
- cc.setEmbeddedFeatures(true);
JFrame f = (JFrame)JOptionPane.getFrameForComponent(this);
if (f != null)
f.dispose();
@@ -331,24 +335,70 @@
}
public void appletRestored() {
- cc.setEmbeddedFeatures(false);
cc.setCloseListener(null);
}
- public void init() {
- vlog.debug("init called");
- Container parent = getParent();
- while (!parent.isFocusCycleRoot()) {
- parent = parent.getParent();
+ public static void setupEmbeddedFrame(JScrollPane sp) {
+ InputMap im = sp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
+ int ctrlAltShiftMask = Event.SHIFT_MASK | Event.CTRL_MASK | Event.ALT_MASK;
+ if (im != null) {
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, ctrlAltShiftMask),
+ "unitScrollUp");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, ctrlAltShiftMask),
+ "unitScrollDown");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, ctrlAltShiftMask),
+ "unitScrollLeft");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, ctrlAltShiftMask),
+ "unitScrollRight");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, ctrlAltShiftMask),
+ "scrollUp");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, ctrlAltShiftMask),
+ "scrollDown");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, ctrlAltShiftMask),
+ "scrollLeft");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, ctrlAltShiftMask),
+ "scrollRight");
}
- ((Frame)parent).setModalExclusionType(null);
- parent.setFocusable(false);
- parent.setFocusTraversalKeysEnabled(false);
- setLookAndFeel();
- setBackground(Color.white);
+ applet.getContentPane().removeAll();
+ applet.getContentPane().add(sp);
+ applet.validate();
}
- private void getTimestamp() {
+ public void init() {
+ // Called right after zero-arg constructor in applet mode
+ setLookAndFeel();
+ setBackground(Color.white);
+ applet = this;
+ vncServerName.put(loadAppletParameters(applet).toCharArray()).flip();
+ if (embed.getValue()) {
+ fullScreen.setParam(false);
+ remoteResize.setParam(false);
+ maximize.setParam(false);
+ scalingFactor.setParam("100");
+ }
+ setFocusTraversalKeysEnabled(false);
+ addFocusListener(new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ if (cc != null && cc.desktop != null)
+ cc.desktop.viewport.requestFocusInWindow();
+ }
+ });
+ Frame frame = (Frame)getFocusCycleRootAncestor();
+ frame.setFocusTraversalKeysEnabled(false);
+ frame.addWindowListener(new WindowAdapter() {
+ // Transfer focus to scrollpane when browser receives it
+ public void windowActivated(WindowEvent e) {
+ if (cc != null && cc.desktop != null)
+ cc.desktop.viewport.requestFocusInWindow();
+ }
+ public void windowDeactivated(WindowEvent e) {
+ if (cc != null)
+ cc.releaseDownKeys();
+ }
+ });
+ }
+
+ private static void getTimestamp() {
if (version == null || build == null) {
try {
Manifest manifest = new Manifest(timestamp);
@@ -361,31 +411,40 @@
}
}
+ public static void showAbout(Container parent) {
+ String pkgDate = "";
+ String pkgTime = "";
+ try {
+ Manifest manifest = new Manifest(VncViewer.timestamp);
+ Attributes attributes = manifest.getMainAttributes();
+ pkgDate = attributes.getValue("Package-Date");
+ pkgTime = attributes.getValue("Package-Time");
+ } catch (java.lang.Exception e) { }
+
+ Window fullScreenWindow = DesktopWindow.getFullScreenWindow();
+ if (fullScreenWindow != null)
+ DesktopWindow.setFullScreenWindow(null);
+ String msg =
+ String.format(VncViewer.aboutText, VncViewer.version, VncViewer.build,
+ VncViewer.buildDate, VncViewer.buildTime);
+ Object[] options = {"Close \u21B5"};
+ JOptionPane op =
+ new JOptionPane(msg, JOptionPane.INFORMATION_MESSAGE,
+ JOptionPane.DEFAULT_OPTION, VncViewer.logoIcon, options);
+ JDialog dlg = op.createDialog(parent, "About TigerVNC Viewer for Java");
+ dlg.setIconImage(VncViewer.frameIcon);
+ dlg.setAlwaysOnTop(true);
+ dlg.setVisible(true);
+ if (fullScreenWindow != null)
+ DesktopWindow.setFullScreenWindow(fullScreenWindow);
+ }
+
public void start() {
- vlog.debug("start called");
- getTimestamp();
- if (embed.getValue() && nViewers == 0) {
- alwaysShowServerDialog.setParam(false);
- Configuration.global().readAppletParams(this);
- fullScreen.setParam(false);
- scalingFactor.setParam("100");
- String host = getCodeBase().getHost();
- if (vncServerName.getValue() == null && vncServerPort.getValue() != 0) {
- int port = vncServerPort.getValue();
- vncServerName.setParam(host + ((port >= 5900 && port <= 5999)
- ? (":"+(port-5900))
- : ("::"+port)));
- }
- }
- nViewers++;
thread = new Thread(this);
thread.start();
}
public void exit(int n) {
- nViewers--;
- if (nViewers > 0)
- return;
if (embed.getValue())
destroy();
else
@@ -425,16 +484,26 @@
}
}
- CConn cc;
public void run() {
cc = null;
+ Socket sock = null;
+
+ /* Specifying -via and -listen together is nonsense */
+ if (listenMode.getValue() && !via.getValueStr().isEmpty()) {
+ vlog.error("Parameters -listen and -via are incompatible");
+ String msg =
+ new String("Parameters -listen and -via are incompatible");
+ JOptionPane.showMessageDialog(null, msg, "ERROR",
+ JOptionPane.ERROR_MESSAGE);
+ exit(1);
+ }
if (listenMode.getValue()) {
int port = 5500;
- if (vncServerName.getValue() != null &&
- Character.isDigit(vncServerName.getValue().charAt(0)))
- port = Integer.parseInt(vncServerName.getValue());
+ if (vncServerName.charAt(0) != 0 &&
+ Character.isDigit(vncServerName.charAt(0)))
+ port = Integer.parseInt(vncServerName.toString());
TcpListener listener = null;
try {
@@ -446,15 +515,25 @@
vlog.info("Listening on port "+port);
- while (true) {
- Socket new_sock = listener.accept();
- if (new_sock != null)
- newViewer(this, new_sock, true);
+ while (sock == null)
+ sock = listener.accept();
+ } else {
+ if (vncServerName.charAt(0) == 0) {
+ try {
+ SwingUtilities.invokeAndWait(
+ new ServerDialog(defaultServerName, vncServerName));
+ } catch (InvocationTargetException e) {
+ reportException(e);
+ } catch (InterruptedException e) {
+ reportException(e);
+ }
+ if (vncServerName.charAt(0) == 0)
+ exit(0);
}
}
try {
- cc = new CConn(this, sock, vncServerName.getValue());
+ cc = new CConn(vncServerName.toString(), sock);
while (!cc.shuttingDown)
cc.processMsg();
exit(0);
@@ -462,7 +541,7 @@
if (cc == null || !cc.shuttingDown) {
reportException(e);
if (cc != null)
- cc.deleteWindow();
+ cc.close();
} else if (embed.getValue()) {
reportException(new java.lang.Exception("Connection closed"));
exit(0);
@@ -471,243 +550,11 @@
}
}
- static BoolParameter noLionFS
- = new BoolParameter("NoLionFS",
- "On Mac systems, setting this parameter will force the use of the old "+
- "(pre-Lion) full-screen mode, even if the viewer is running on OS X 10.7 "+
- "Lion or later.",
- false);
-
- BoolParameter embed
- = new BoolParameter("Embed",
- "If the viewer is being run as an applet, display its output to " +
- "an embedded frame in the browser window rather than to a dedicated " +
- "window. Embed=1 implies FullScreen=0 and Scale=100.",
- false);
-
- BoolParameter useLocalCursor
- = new BoolParameter("UseLocalCursor",
- "Render the mouse cursor locally",
- true);
- BoolParameter sendLocalUsername
- = new BoolParameter("SendLocalUsername",
- "Send the local username for SecurityTypes "+
- "such as Plain rather than prompting",
- true);
- StringParameter passwordFile
- = new StringParameter("PasswordFile",
- "Password file for VNC authentication",
- "");
- AliasParameter passwd
- = new AliasParameter("passwd",
- "Alias for PasswordFile",
- passwordFile);
- BoolParameter autoSelect
- = new BoolParameter("AutoSelect",
- "Auto select pixel format and encoding",
- true);
- BoolParameter fullColour
- = new BoolParameter("FullColour",
- "Use full colour - otherwise 6-bit colour is "+
- "used until AutoSelect decides the link is "+
- "fast enough",
- true);
- AliasParameter fullColourAlias
- = new AliasParameter("FullColor",
- "Alias for FullColour",
- fullColour);
- IntParameter lowColourLevel
- = new IntParameter("LowColorLevel",
- "Color level to use on slow connections. "+
- "0 = Very Low (8 colors), 1 = Low (64 colors), "+
- "2 = Medium (256 colors)",
- 2);
- AliasParameter lowColourLevelAlias
- = new AliasParameter("LowColourLevel",
- "Alias for LowColorLevel",
- lowColourLevel);
- StringParameter preferredEncoding
- = new StringParameter("PreferredEncoding",
- "Preferred encoding to use (Tight, ZRLE, "+
- "hextile or raw) - implies AutoSelect=0",
- "Tight");
- BoolParameter viewOnly
- = new BoolParameter("ViewOnly",
- "Don't send any mouse or keyboard events to "+
- "the server",
- false);
- BoolParameter shared
- = new BoolParameter("Shared",
- "Don't disconnect other viewers upon "+
- "connection - share the desktop instead",
- false);
- BoolParameter fullScreen
- = new BoolParameter("FullScreen",
- "Full Screen Mode",
- false);
- BoolParameter fullScreenAllMonitors
- = new BoolParameter("FullScreenAllMonitors",
- "Enable full screen over all monitors",
- true);
- BoolParameter acceptClipboard
- = new BoolParameter("AcceptClipboard",
- "Accept clipboard changes from the server",
- true);
- BoolParameter sendClipboard
- = new BoolParameter("SendClipboard",
- "Send clipboard changes to the server",
- true);
- static IntParameter maxCutText
- = new IntParameter("MaxCutText",
- "Maximum permitted length of an outgoing clipboard update",
- 262144);
- StringParameter menuKey
- = new StringParameter("MenuKey",
- "The key which brings up the popup menu",
- "F8");
- StringParameter desktopSize
- = new StringParameter("DesktopSize",
- "Reconfigure desktop size on the server on "+
- "connect (if possible)", "");
- BoolParameter listenMode
- = new BoolParameter("listen",
- "Listen for connections from VNC servers",
- false);
- StringParameter scalingFactor
- = new StringParameter("ScalingFactor",
- "Reduce or enlarge the remote desktop image. "+
- "The value is interpreted as a scaling factor "+
- "in percent. If the parameter is set to "+
- "\"Auto\", then automatic scaling is "+
- "performed. Auto-scaling tries to choose a "+
- "scaling factor in such a way that the whole "+
- "remote desktop will fit on the local screen. "+
- "If the parameter is set to \"FixedRatio\", "+
- "then automatic scaling is performed, but the "+
- "original aspect ratio is preserved.",
- "100");
- BoolParameter alwaysShowServerDialog
- = new BoolParameter("AlwaysShowServerDialog",
- "Always show the server dialog even if a server "+
- "has been specified in an applet parameter or on "+
- "the command line",
- false);
- StringParameter vncServerName
- = new StringParameter("Server",
- "The VNC server <host>[:<dpyNum>] or "+
- "<host>::<port>",
- null);
- IntParameter vncServerPort
- = new IntParameter("Port",
- "The VNC server's port number, assuming it is on "+
- "the host from which the applet was downloaded",
- 0);
- BoolParameter acceptBell
- = new BoolParameter("AcceptBell",
- "Produce a system beep when requested to by the server.",
- true);
- StringParameter via
- = new StringParameter("Via",
- "Automatically create an encrypted TCP tunnel to "+
- "the gateway machine, then connect to the VNC host "+
- "through that tunnel. By default, this option invokes "+
- "SSH local port forwarding using the embedded JSch "+
- "client, however an external SSH client may be specified "+
- "using the \"-extSSH\" parameter. Note that when using "+
- "the -via option, the VNC host machine name should be "+
- "specified from the point of view of the gateway machine, "+
- "e.g. \"localhost\" denotes the gateway, "+
- "not the machine on which the viewer was launched. "+
- "See the System Properties section below for "+
- "information on configuring the -Via option.", null);
- BoolParameter tunnel
- = new BoolParameter("Tunnel",
- "The -Tunnel command is basically a shorthand for the "+
- "-via command when the VNC server and SSH gateway are "+
- "one and the same. -Tunnel creates an SSH connection "+
- "to the server and forwards the VNC through the tunnel "+
- "without the need to specify anything else.", false);
- BoolParameter extSSH
- = new BoolParameter("extSSH",
- "By default, SSH tunneling uses the embedded JSch client "+
- "for tunnel creation. This option causes the client to "+
- "invoke an external SSH client application for all tunneling "+
- "operations. By default, \"/usr/bin/ssh\" is used, however "+
- "the path to the external application may be specified using "+
- "the -SSHClient option.", false);
- StringParameter extSSHClient
- = new StringParameter("extSSHClient",
- "Specifies the path to an external SSH client application "+
- "that is to be used for tunneling operations when the -extSSH "+
- "option is in effect.", "/usr/bin/ssh");
- StringParameter extSSHArgs
- = new StringParameter("extSSHArgs",
- "Specifies the arguments string or command template to be used "+
- "by the external SSH client application when the -extSSH option "+
- "is in effect. The string will be processed according to the same "+
- "pattern substitution rules as the VNC_TUNNEL_CMD and VNC_VIA_CMD "+
- "system properties, and can be used to override those in a more "+
- "command-line friendly way. If not specified, then the appropriate "+
- "VNC_TUNNEL_CMD or VNC_VIA_CMD command template will be used.", null);
- StringParameter sshConfig
- = new StringParameter("SSHConfig",
- "Specifies the path to an OpenSSH configuration file that to "+
- "be parsed by the embedded JSch SSH client during tunneling "+
- "operations.", FileUtils.getHomeDir()+".ssh/config");
- StringParameter sshKey
- = new StringParameter("SSHKey",
- "When using the Via or Tunnel options with the embedded SSH client, "+
- "this parameter specifies the text of the SSH private key to use when "+
- "authenticating with the SSH server. You can use \\n within the string "+
- "to specify a new line.", null);
- StringParameter sshKeyFile
- = new StringParameter("SSHKeyFile",
- "When using the Via or Tunnel options with the embedded SSH client, "+
- "this parameter specifies a file that contains an SSH private key "+
- "(or keys) to use when authenticating with the SSH server. If not "+
- "specified, ~/.ssh/id_dsa or ~/.ssh/id_rsa will be used (if they exist). "+
- "Otherwise, the client will fallback to prompting for an SSH password.",
- null);
- StringParameter sshKeyPass
- = new StringParameter("SSHKeyPass",
- "When using the Via or Tunnel options with the embedded SSH client, "+
- "this parameter specifies the passphrase for the SSH key.", null);
- BoolParameter customCompressLevel
- = new BoolParameter("CustomCompressLevel",
- "Use custom compression level. "+
- "Default if CompressLevel is specified.",
- false);
- IntParameter compressLevel
- = new IntParameter("CompressLevel",
- "Use specified compression level "+
- "0 = Low, 6 = High",
- 1);
- BoolParameter noJpeg
- = new BoolParameter("NoJPEG",
- "Disable lossy JPEG compression in Tight encoding.",
- false);
- IntParameter qualityLevel
- = new IntParameter("QualityLevel",
- "JPEG quality level. "+
- "0 = Low, 9 = High",
- 8);
- StringParameter x509ca
- = new StringParameter("X509CA",
- "Path to CA certificate to use when authenticating remote servers "+
- "using any of the X509 security schemes (X509None, X509Vnc, etc.). "+
- "Must be in PEM format.",
- FileUtils.getHomeDir()+".vnc/x509_ca.pem");
- StringParameter x509crl
- = new StringParameter("X509CRL",
- "Path to certificate revocation list to use in conjunction with "+
- "-X509CA. Must also be in PEM format.",
- FileUtils.getHomeDir()+".vnc/x509_crl.pem");
- StringParameter config
- = new StringParameter("config",
+ public static CConn cc;
+ public static StringParameter config
+ = new StringParameter("Config",
"Specifies a configuration file to load.", null);
Thread thread;
- Socket sock;
- static int nViewers;
static LogWriter vlog = new LogWriter("VncViewer");
}
diff --git a/po/fr.po b/po/fr.po
index 260e9e9..db708e3 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -3,127 +3,119 @@
# This file is distributed under the same license as the tigervnc package.
#
# Alain Portal <alain.portal@free.fr>, 2010
-# Stéphane Aulery <lkppo@free.fr>, 2015.
+# Stéphane Aulery <lkppo@free.fr>, 2015-2016.
#
# Traduction complète et relecture, S. Aulery, 25-04-2015.
+# Mise à jour, S. Aulery, 23-12-2016.
#
msgid ""
msgstr ""
-"Project-Id-Version: tigervnc 1.5.90\n"
+"Project-Id-Version: tigervnc 1.6.90\n"
"Report-Msgid-Bugs-To: tigervnc-devel@googlegroups.com\n"
-"POT-Creation-Date: 2015-11-26 11:33+0000\n"
-"PO-Revision-Date: 2015-12-23 01:30+0100\n"
+"POT-Creation-Date: 2016-07-01 10:15+0000\n"
+"PO-Revision-Date: 2016-12-23 20:56+0100\n"
"Last-Translator: Stéphane Aulery <lkppo@free.fr>\n"
"Language-Team: French <traduc@traduc.org>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"X-Bugs: Report translation errors to the Language-Team address.\n"
"X-Generator: Lokalize 1.0\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-#: vncviewer/CConn.cxx:111
+#: vncviewer/CConn.cxx:110
#, c-format
msgid "connected to host %s port %d"
msgstr "connecté à l’hôte %s par le port %d"
-#: vncviewer/CConn.cxx:173
+#: vncviewer/CConn.cxx:169
#, c-format
msgid "Desktop name: %.80s"
msgstr "Nom du bureau : %.80s"
-#: vncviewer/CConn.cxx:178
+#: vncviewer/CConn.cxx:174
#, c-format
msgid "Host: %.80s port: %d"
msgstr "Hôte : %.80s port : %d"
-#: vncviewer/CConn.cxx:183
+#: vncviewer/CConn.cxx:179
#, c-format
msgid "Size: %d x %d"
msgstr "Taille : %d x %d"
-#: vncviewer/CConn.cxx:191
+#: vncviewer/CConn.cxx:187
#, c-format
msgid "Pixel format: %s"
msgstr "Format de pixel : %s"
-#: vncviewer/CConn.cxx:198
+#: vncviewer/CConn.cxx:194
#, c-format
msgid "(server default %s)"
msgstr "(serveur par défaut %s)"
-#: vncviewer/CConn.cxx:203
+#: vncviewer/CConn.cxx:199
#, c-format
msgid "Requested encoding: %s"
msgstr "Encodage demandé : %s"
-#: vncviewer/CConn.cxx:208
+#: vncviewer/CConn.cxx:204
#, c-format
msgid "Last used encoding: %s"
msgstr "Dernier encodage utilisé : %s"
-#: vncviewer/CConn.cxx:213
+#: vncviewer/CConn.cxx:209
#, c-format
msgid "Line speed estimate: %d kbit/s"
msgstr "Vitesse estimée de la connexion : %d kbit/s"
-#: vncviewer/CConn.cxx:218
+#: vncviewer/CConn.cxx:214
#, c-format
msgid "Protocol version: %d.%d"
msgstr "Version du protocol : %d.%d"
-#: vncviewer/CConn.cxx:223
+#: vncviewer/CConn.cxx:219
#, c-format
msgid "Security method: %s"
msgstr "Méthode de sécurité : %s"
-#: vncviewer/CConn.cxx:329
+#: vncviewer/CConn.cxx:319
#, c-format
msgid "SetDesktopSize failed: %d"
msgstr "SetDesktopSize échoué : %d"
-#: vncviewer/CConn.cxx:398
+#: vncviewer/CConn.cxx:411
msgid "Invalid SetColourMapEntries from server!"
msgstr "Opération SetColourMapEntries du serveur invalide !"
-#. TRANSLATORS: Refers to a VNC protocol encoding type
-#: vncviewer/CConn.cxx:444 vncviewer/CConn.cxx:451
-#, c-format
-msgid "Unknown encoding %d"
-msgstr "Encodage inconnu %d"
-
-#: vncviewer/CConn.cxx:445 vncviewer/CConn.cxx:452
-msgid "Unknown encoding"
-msgstr "Encodage inconnu"
-
-#: vncviewer/CConn.cxx:484
+#: vncviewer/CConn.cxx:485
msgid "Enabling continuous updates"
msgstr "Autorisation de mises à jour en continu"
-#: vncviewer/CConn.cxx:554
+#: vncviewer/CConn.cxx:555
#, c-format
msgid "Throughput %d kbit/s - changing to quality %d"
msgstr "Débit %d kbit/s - %d pour une meilleure qualité"
-#: vncviewer/CConn.cxx:576
+#: vncviewer/CConn.cxx:577
#, c-format
msgid "Throughput %d kbit/s - full color is now %s"
msgstr "Débit %d kbit/s - pleine couleur est à présent %s"
-#: vncviewer/CConn.cxx:578
+#: vncviewer/CConn.cxx:579
msgid "disabled"
msgstr "désactivé"
-#: vncviewer/CConn.cxx:578
+#: vncviewer/CConn.cxx:579
msgid "enabled"
msgstr "activé"
-#: vncviewer/CConn.cxx:588
+#: vncviewer/CConn.cxx:589
#, c-format
msgid "Using %s encoding"
msgstr "Utilise l’encodage %s"
-#: vncviewer/CConn.cxx:635
+#: vncviewer/CConn.cxx:636
#, c-format
msgid "Using pixel format %s"
msgstr "Utilisation du format de pixel %s"
@@ -132,25 +124,25 @@
msgid "Invalid geometry specified!"
msgstr "Géométrie spécifiée invalide !"
-#: vncviewer/DesktopWindow.cxx:309
+#: vncviewer/DesktopWindow.cxx:303
msgid "Adjusting window size to avoid accidental full screen request"
msgstr "Ajustement de la taille de la fenêtre pour éviter une demande accidentelle de plein écran"
-#: vncviewer/DesktopWindow.cxx:491 vncviewer/DesktopWindow.cxx:497
-#: vncviewer/DesktopWindow.cxx:510
+#: vncviewer/DesktopWindow.cxx:485 vncviewer/DesktopWindow.cxx:491
+#: vncviewer/DesktopWindow.cxx:504
msgid "Failure grabbing keyboard"
msgstr "Échec de saisie clavier"
-#: vncviewer/DesktopWindow.cxx:522
+#: vncviewer/DesktopWindow.cxx:516
msgid "Failure grabbing mouse"
msgstr "Échec de saisie souris"
-#: vncviewer/DesktopWindow.cxx:752
+#: vncviewer/DesktopWindow.cxx:746
msgid "Invalid screen layout computed for resize request!"
msgstr "Calcul de sortie d’écran invalide pour la demande de redimensionnement !"
#: vncviewer/FLTKPixelBuffer.cxx:33 vncviewer/OSXPixelBuffer.cxx:48
-#: vncviewer/X11PixelBuffer.cxx:113
+#: vncviewer/X11PixelBuffer.cxx:117
msgid "Not enough memory for framebuffer"
msgstr "Pas assez de mémoire pour le framebuffer"
@@ -167,160 +159,164 @@
msgstr "Visionneuse VNC : options de la connexion"
#: vncviewer/OptionsDialog.cxx:83 vncviewer/ServerDialog.cxx:91
-#: vncviewer/vncviewer.cxx:268
+#: vncviewer/vncviewer.cxx:282
msgid "Cancel"
msgstr "Annuler"
-#: vncviewer/OptionsDialog.cxx:88 vncviewer/vncviewer.cxx:267
+#: vncviewer/OptionsDialog.cxx:88 vncviewer/vncviewer.cxx:281
msgid "OK"
msgstr "Ok"
-#: vncviewer/OptionsDialog.cxx:413
+#: vncviewer/OptionsDialog.cxx:423
msgid "Compression"
msgstr "Compression"
-#: vncviewer/OptionsDialog.cxx:429
+#: vncviewer/OptionsDialog.cxx:439
msgid "Auto select"
msgstr "Sélection automatique"
-#: vncviewer/OptionsDialog.cxx:441
+#: vncviewer/OptionsDialog.cxx:451
msgid "Preferred encoding"
msgstr "Encodage préféré"
-#: vncviewer/OptionsDialog.cxx:489
+#: vncviewer/OptionsDialog.cxx:499
msgid "Color level"
msgstr "Niveau de couleur"
-#: vncviewer/OptionsDialog.cxx:500
+#: vncviewer/OptionsDialog.cxx:510
msgid "Full (all available colors)"
msgstr "Complet (toutes les couleurs disponibles)"
-#: vncviewer/OptionsDialog.cxx:507
+#: vncviewer/OptionsDialog.cxx:517
msgid "Medium (256 colors)"
msgstr "Moyen (256 couleurs)"
-#: vncviewer/OptionsDialog.cxx:514
+#: vncviewer/OptionsDialog.cxx:524
msgid "Low (64 colors)"
msgstr "Faible (64 couleurs)"
-#: vncviewer/OptionsDialog.cxx:521
+#: vncviewer/OptionsDialog.cxx:531
msgid "Very low (8 colors)"
msgstr "Très faible (8 couleurs)"
-#: vncviewer/OptionsDialog.cxx:538
+#: vncviewer/OptionsDialog.cxx:548
msgid "Custom compression level:"
msgstr "Niveau de compression personnalisé :"
-#: vncviewer/OptionsDialog.cxx:544
+#: vncviewer/OptionsDialog.cxx:554
msgid "level (1=fast, 6=best [4-6 are rarely useful])"
msgstr "niveau (1=rapide, 6=optimal [4-6 sont rarement utiles])"
-#: vncviewer/OptionsDialog.cxx:551
+#: vncviewer/OptionsDialog.cxx:561
msgid "Allow JPEG compression:"
msgstr "Autoriser la compression JPEG :"
-#: vncviewer/OptionsDialog.cxx:557
+#: vncviewer/OptionsDialog.cxx:567
msgid "quality (0=poor, 9=best)"
msgstr "qualité (0=faible, 9=optimale)"
-#: vncviewer/OptionsDialog.cxx:568
+#: vncviewer/OptionsDialog.cxx:578
msgid "Security"
msgstr "Sécurité"
-#: vncviewer/OptionsDialog.cxx:583
+#: vncviewer/OptionsDialog.cxx:593
msgid "Encryption"
msgstr "Cryptage"
-#: vncviewer/OptionsDialog.cxx:594 vncviewer/OptionsDialog.cxx:647
-#: vncviewer/OptionsDialog.cxx:715
+#: vncviewer/OptionsDialog.cxx:604 vncviewer/OptionsDialog.cxx:657
+#: vncviewer/OptionsDialog.cxx:735
msgid "None"
msgstr "Aucun"
-#: vncviewer/OptionsDialog.cxx:600
+#: vncviewer/OptionsDialog.cxx:610
msgid "TLS with anonymous certificates"
msgstr "TLS avec certificats anonymes"
-#: vncviewer/OptionsDialog.cxx:606
+#: vncviewer/OptionsDialog.cxx:616
msgid "TLS with X509 certificates"
msgstr "TLS avec certificat X509"
-#: vncviewer/OptionsDialog.cxx:613
+#: vncviewer/OptionsDialog.cxx:623
msgid "Path to X509 CA certificate"
msgstr "Certificat du chemin pour X509 CA"
-#: vncviewer/OptionsDialog.cxx:620
+#: vncviewer/OptionsDialog.cxx:630
msgid "Path to X509 CRL file"
msgstr "Fichier du chemin pour X509 CRL"
-#: vncviewer/OptionsDialog.cxx:636
+#: vncviewer/OptionsDialog.cxx:646
msgid "Authentication"
msgstr "Authentification"
-#: vncviewer/OptionsDialog.cxx:653
+#: vncviewer/OptionsDialog.cxx:663
msgid "Standard VNC (insecure without encryption)"
msgstr "VNC standard (non sécurisé et sans cryptage)"
-#: vncviewer/OptionsDialog.cxx:659
+#: vncviewer/OptionsDialog.cxx:669
msgid "Username and password (insecure without encryption)"
msgstr "Nom d’utilisateur et mot de passe (non sécurisé et sans cryptage)"
-#: vncviewer/OptionsDialog.cxx:678
+#: vncviewer/OptionsDialog.cxx:688
msgid "Input"
msgstr "Entrée"
-#: vncviewer/OptionsDialog.cxx:686
+#: vncviewer/OptionsDialog.cxx:696
msgid "View only (ignore mouse and keyboard)"
msgstr "Vue passive (ignorer la souris et le clavier)"
-#: vncviewer/OptionsDialog.cxx:692
+#: vncviewer/OptionsDialog.cxx:702
msgid "Accept clipboard from server"
msgstr "Accepter le presse-papier du serveur"
-#: vncviewer/OptionsDialog.cxx:698
+#: vncviewer/OptionsDialog.cxx:709
+msgid "Also set primary selection"
+msgstr "Définir également la sélection principale"
+
+#: vncviewer/OptionsDialog.cxx:716
msgid "Send clipboard to server"
msgstr "Envoyer le presse-papier au serveur"
-#: vncviewer/OptionsDialog.cxx:704
-msgid "Send primary selection and cut buffer as clipboard"
-msgstr "Envoyer la sélection principale et couper les données vers le presse-papiers"
+#: vncviewer/OptionsDialog.cxx:723
+msgid "Send primary selection as clipboard"
+msgstr "Envoyer la sélection principale vers le presse-papiers"
-#: vncviewer/OptionsDialog.cxx:710
+#: vncviewer/OptionsDialog.cxx:730
msgid "Pass system keys directly to server (full screen)"
msgstr "Donner directement les clefs système au serveur (plein écran)"
-#: vncviewer/OptionsDialog.cxx:713
+#: vncviewer/OptionsDialog.cxx:733
msgid "Menu key"
msgstr "Menu clef"
-#: vncviewer/OptionsDialog.cxx:729
+#: vncviewer/OptionsDialog.cxx:749
msgid "Screen"
msgstr "Écran"
-#: vncviewer/OptionsDialog.cxx:737
+#: vncviewer/OptionsDialog.cxx:757
msgid "Resize remote session on connect"
msgstr "Redimensionner la session distance à la connexion"
-#: vncviewer/OptionsDialog.cxx:750
+#: vncviewer/OptionsDialog.cxx:770
msgid "Resize remote session to the local window"
msgstr "Redimensionner la session distante pour la fenêtre locale"
-#: vncviewer/OptionsDialog.cxx:756
+#: vncviewer/OptionsDialog.cxx:776
msgid "Full-screen mode"
msgstr "Mode plein écran"
-#: vncviewer/OptionsDialog.cxx:762
+#: vncviewer/OptionsDialog.cxx:782
msgid "Enable full-screen mode over all monitors"
msgstr "Activer le mode plein écran pour tous les écrans"
-#: vncviewer/OptionsDialog.cxx:771
+#: vncviewer/OptionsDialog.cxx:791
msgid "Misc."
msgstr "Divers"
-#: vncviewer/OptionsDialog.cxx:779
+#: vncviewer/OptionsDialog.cxx:799
msgid "Shared (don't disconnect other viewers)"
msgstr "Partagé (ne pas déconnecter les autres visionneuses)"
-#: vncviewer/OptionsDialog.cxx:785
+#: vncviewer/OptionsDialog.cxx:805
msgid "Show dot when no cursor"
msgstr "Afficher un point s’il n’y a pas de curseur"
@@ -372,112 +368,112 @@
msgid "Username:"
msgstr "Nom d’utilisateur :"
-#: vncviewer/Viewport.cxx:433
+#: vncviewer/Viewport.cxx:391
#, c-format
msgid "Unable to create platform specific framebuffer: %s"
msgstr "Impossible de créer un framebuffer adapté à la plateforme : %s"
-#: vncviewer/Viewport.cxx:434
+#: vncviewer/Viewport.cxx:392
msgid "Using platform independent framebuffer"
msgstr "Utilisation d’un framebuffer indépendant de la plateforme"
-#: vncviewer/Viewport.cxx:668
+#: vncviewer/Viewport.cxx:628
#, c-format
msgid "No scan code for extended virtual key 0x%02x"
msgstr "Pas de scan code pour la clef virtuelle étendue 0x%02x"
-#: vncviewer/Viewport.cxx:670
+#: vncviewer/Viewport.cxx:630
#, c-format
msgid "No scan code for virtual key 0x%02x"
msgstr "Pas de scan code pour la clef virtuelle 0x%02x"
-#: vncviewer/Viewport.cxx:687
+#: vncviewer/Viewport.cxx:647
#, c-format
msgid "No symbol for extended virtual key 0x%02x"
msgstr "Pas de symbole pour la clef virtuelle étendue 0x%02x"
-#: vncviewer/Viewport.cxx:689
+#: vncviewer/Viewport.cxx:649
#, c-format
msgid "No symbol for virtual key 0x%02x"
msgstr "Pas de symbole pour la clef virtuelle 0x%02x"
-#: vncviewer/Viewport.cxx:727
+#: vncviewer/Viewport.cxx:687
#, c-format
msgid "No symbol for key code 0x%02x (in the current state)"
msgstr "Pas de scan code pour le code clef 0x%02x (en l'état actuel)"
-#: vncviewer/Viewport.cxx:753
+#: vncviewer/Viewport.cxx:713
#, c-format
msgid "No symbol for key code %d (in the current state)"
msgstr "Pas de scan code pour le code clef %d (en l'état actuel)"
-#: vncviewer/Viewport.cxx:790
+#: vncviewer/Viewport.cxx:750
msgctxt "ContextMenu|"
msgid "E&xit viewer"
msgstr "&Quitte la visionneuse"
-#: vncviewer/Viewport.cxx:793
+#: vncviewer/Viewport.cxx:753
msgctxt "ContextMenu|"
msgid "&Full screen"
msgstr "&Plein écran"
-#: vncviewer/Viewport.cxx:796
+#: vncviewer/Viewport.cxx:756
msgctxt "ContextMenu|"
msgid "Minimi&ze"
msgstr "&Réduire"
-#: vncviewer/Viewport.cxx:798
+#: vncviewer/Viewport.cxx:758
msgctxt "ContextMenu|"
msgid "Resize &window to session"
msgstr "Redimensionner la &fenêtre pour la session"
-#: vncviewer/Viewport.cxx:803
+#: vncviewer/Viewport.cxx:763
msgctxt "ContextMenu|"
msgid "&Ctrl"
msgstr "&Ctrl"
-#: vncviewer/Viewport.cxx:806
+#: vncviewer/Viewport.cxx:766
msgctxt "ContextMenu|"
msgid "&Alt"
msgstr "&Alt"
-#: vncviewer/Viewport.cxx:812
+#: vncviewer/Viewport.cxx:772
#, c-format
msgctxt "ContextMenu|"
msgid "Send %s"
msgstr "Envoyer %s"
-#: vncviewer/Viewport.cxx:818
+#: vncviewer/Viewport.cxx:778
msgctxt "ContextMenu|"
msgid "Send Ctrl-Alt-&Del"
msgstr "Envoyer Ctrl-Alt-&Sup"
-#: vncviewer/Viewport.cxx:821
+#: vncviewer/Viewport.cxx:781
msgctxt "ContextMenu|"
msgid "&Refresh screen"
msgstr "R&afraîchir l’écran"
-#: vncviewer/Viewport.cxx:824
+#: vncviewer/Viewport.cxx:784
msgctxt "ContextMenu|"
msgid "&Options..."
msgstr "&Options…"
-#: vncviewer/Viewport.cxx:826
+#: vncviewer/Viewport.cxx:786
msgctxt "ContextMenu|"
msgid "Connection &info..."
msgstr "&Informations de connexion…"
-#: vncviewer/Viewport.cxx:828
+#: vncviewer/Viewport.cxx:788
msgctxt "ContextMenu|"
msgid "About &TigerVNC viewer..."
msgstr "À propos de la visionneuse &TigerVNC…"
-#: vncviewer/Viewport.cxx:831
+#: vncviewer/Viewport.cxx:791
msgctxt "ContextMenu|"
msgid "Dismiss &menu"
msgstr "Masquer le &menu"
-#: vncviewer/Viewport.cxx:915
+#: vncviewer/Viewport.cxx:875
msgid "VNC connection info"
msgstr "Informations de la connexion VNC"
@@ -499,123 +495,123 @@
#. TRANSLATORS: "pixmap" is an X11 concept and may not be suitable
#. to translate.
-#: vncviewer/X11PixelBuffer.cxx:61
+#: vncviewer/X11PixelBuffer.cxx:65
msgid "Display lacks pixmap format for default depth"
msgstr "Afficher les formats de pixmap manquants pour la profondeur par défaut"
#. TRANSLATORS: "pixmap" is an X11 concept and may not be suitable
#. to translate.
-#: vncviewer/X11PixelBuffer.cxx:72
+#: vncviewer/X11PixelBuffer.cxx:76
msgid "Couldn't find suitable pixmap format"
msgstr "Impossible de trouver un format de pixmap utilisable"
-#: vncviewer/X11PixelBuffer.cxx:81
+#: vncviewer/X11PixelBuffer.cxx:85
msgid "Only true colour displays supported"
msgstr "Ne prend en charge que l’affiche en vrais couleurs"
-#: vncviewer/X11PixelBuffer.cxx:83
+#: vncviewer/X11PixelBuffer.cxx:87
#, c-format
msgid "Using default colormap and visual, TrueColor, depth %d."
msgstr "Utilisation de la carte de couleur et du visuel par défaut, TrueColor, profondeur %d."
-#: vncviewer/X11PixelBuffer.cxx:109
+#: vncviewer/X11PixelBuffer.cxx:113
msgid "Could not create framebuffer image"
msgstr "Impossible de créer l’image framebuffer"
-#: vncviewer/parameters.cxx:279 vncviewer/parameters.cxx:313
+#: vncviewer/parameters.cxx:286 vncviewer/parameters.cxx:320
#, c-format
msgid "The name of the parameter %s was too large to write to the registry"
msgstr "Le nom du paramètre %s était trop long pour l’écrire dans le registre."
-#: vncviewer/parameters.cxx:285 vncviewer/parameters.cxx:292
+#: vncviewer/parameters.cxx:292 vncviewer/parameters.cxx:299
#, c-format
msgid "The parameter %s was too large to write to the registry"
msgstr "Le paramètre %s était trop long pour l’écrire dans le registre."
-#: vncviewer/parameters.cxx:298 vncviewer/parameters.cxx:319
+#: vncviewer/parameters.cxx:305 vncviewer/parameters.cxx:326
#, c-format
msgid "Failed to write parameter %s of type %s to the registry: %ld"
msgstr "Impossible d’écrire le paramètre %s de type %s dans le registre : %ld"
-#: vncviewer/parameters.cxx:334 vncviewer/parameters.cxx:373
+#: vncviewer/parameters.cxx:341 vncviewer/parameters.cxx:380
#, c-format
msgid "The name of the parameter %s was too large to read from the registry"
msgstr "Le nom du paramètre %s était trop long pour lire le registre"
-#: vncviewer/parameters.cxx:343 vncviewer/parameters.cxx:382
+#: vncviewer/parameters.cxx:350 vncviewer/parameters.cxx:389
#, c-format
msgid "Failed to read parameter %s from the registry: %ld"
msgstr "Impossible de lire le paramètre %s depuis le registre : %ld"
-#: vncviewer/parameters.cxx:352
+#: vncviewer/parameters.cxx:359
#, c-format
msgid "The parameter %s was too large to read from the registry"
msgstr "Le paramètre %s était trop long pour le lire depuis le registre"
-#: vncviewer/parameters.cxx:402
+#: vncviewer/parameters.cxx:409
#, c-format
msgid "Failed to create registry key: %ld"
msgstr "Impossible de créer la clef de regitre : %ld"
-#: vncviewer/parameters.cxx:416 vncviewer/parameters.cxx:465
-#: vncviewer/parameters.cxx:527 vncviewer/parameters.cxx:658
+#: vncviewer/parameters.cxx:423 vncviewer/parameters.cxx:472
+#: vncviewer/parameters.cxx:534 vncviewer/parameters.cxx:665
#, c-format
msgid "Unknown parameter type for parameter %s"
msgstr "Type de paramètre inconnu pour le paramètre %s"
-#: vncviewer/parameters.cxx:423 vncviewer/parameters.cxx:472
+#: vncviewer/parameters.cxx:430 vncviewer/parameters.cxx:479
#, c-format
msgid "Failed to close registry key: %ld"
msgstr "Impossible de fermer la clef de registre : %ld"
-#: vncviewer/parameters.cxx:439
+#: vncviewer/parameters.cxx:446
#, c-format
msgid "Failed to open registry key: %ld"
msgstr "Impossible d’ouvrir la clef de registre : %ld"
-#: vncviewer/parameters.cxx:496
+#: vncviewer/parameters.cxx:503
msgid "Failed to write configuration file, can't obtain home directory path."
msgstr "Impossible d'écrire le fichier de configuration, impossible d'obtenir le chemin du répertoire personnel."
-#: vncviewer/parameters.cxx:509
+#: vncviewer/parameters.cxx:516
#, c-format
msgid "Failed to write configuration file, can't open %s: %s"
msgstr "Impossible d'écrire le fichier de configuration, impossible d’ouvrir %s : %s"
-#: vncviewer/parameters.cxx:552
+#: vncviewer/parameters.cxx:559
msgid "Failed to read configuration file, can't obtain home directory path."
msgstr "Impossible de lire le fichier de configuration, impossible d’obtenir le chemin du répertoire personnel."
-#: vncviewer/parameters.cxx:565
+#: vncviewer/parameters.cxx:572
#, c-format
msgid "Failed to read configuration file, can't open %s: %s"
msgstr "Impossible de lire le fichier de configuration, impossible d’ouvrir %s : %s"
-#: vncviewer/parameters.cxx:578 vncviewer/parameters.cxx:583
-#: vncviewer/parameters.cxx:608 vncviewer/parameters.cxx:621
-#: vncviewer/parameters.cxx:637
+#: vncviewer/parameters.cxx:585 vncviewer/parameters.cxx:590
+#: vncviewer/parameters.cxx:615 vncviewer/parameters.cxx:628
+#: vncviewer/parameters.cxx:644
#, c-format
msgid "Failed to read line %d in file %s: %s"
msgstr "Impossible de lire la ligne %d du fichier %s : %s"
-#: vncviewer/parameters.cxx:584
+#: vncviewer/parameters.cxx:591
msgid "Line too long"
msgstr "Ligne trop longue"
-#: vncviewer/parameters.cxx:591
+#: vncviewer/parameters.cxx:598
#, c-format
msgid "Configuration file %s is in an invalid format"
msgstr "Fichier de configuration %s dans un format invalide"
-#: vncviewer/parameters.cxx:609
+#: vncviewer/parameters.cxx:616
msgid "Invalid format"
msgstr "Format invalide"
-#: vncviewer/parameters.cxx:622 vncviewer/parameters.cxx:638
+#: vncviewer/parameters.cxx:629 vncviewer/parameters.cxx:645
msgid "Invalid format or too large value"
msgstr "Format invalide ou valeur trop grande"
-#: vncviewer/parameters.cxx:665
+#: vncviewer/parameters.cxx:672
#, c-format
msgid "Unknown parameter %s on line %d in file %s"
msgstr "Paramètre %s inconnu à la ligne %d du fichier %s"
@@ -637,86 +633,92 @@
msgid "About TigerVNC Viewer"
msgstr "À propos de la visionneuse TigerVNC"
-#: vncviewer/vncviewer.cxx:144 vncviewer/vncviewer.cxx:156
+#: vncviewer/vncviewer.cxx:140
+msgid "Internal FLTK error. Exiting."
+msgstr "Erreur FLTK interne. Procédure de sortie."
+
+#: vncviewer/vncviewer.cxx:158 vncviewer/vncviewer.cxx:170
#, c-format
msgid "Error starting new TigerVNC Viewer: %s"
msgstr "Une erreur à démarrer une nouvelle visionneuse TigerVNC : %s"
-#: vncviewer/vncviewer.cxx:165
+#: vncviewer/vncviewer.cxx:179
#, c-format
msgid "Termination signal %d has been received. TigerVNC Viewer will now exit."
msgstr "Signal de fin %d reçu. La visionneuse TigerVNC va se fermer."
-#: vncviewer/vncviewer.cxx:257
+#: vncviewer/vncviewer.cxx:271
msgid "TigerVNC Viewer"
msgstr "Visionneuse TigerVNC"
-#: vncviewer/vncviewer.cxx:265
+#: vncviewer/vncviewer.cxx:279
msgid "No"
msgstr "Non"
-#: vncviewer/vncviewer.cxx:266
+#: vncviewer/vncviewer.cxx:280
msgid "Yes"
msgstr "Oui"
-#: vncviewer/vncviewer.cxx:269
+#: vncviewer/vncviewer.cxx:283
msgid "Close"
msgstr "Fermer"
-#: vncviewer/vncviewer.cxx:274
+#: vncviewer/vncviewer.cxx:288
msgid "About"
msgstr "À propos"
-#: vncviewer/vncviewer.cxx:277
+#: vncviewer/vncviewer.cxx:291
msgid "Hide"
msgstr "Cacher"
-#: vncviewer/vncviewer.cxx:280
+#: vncviewer/vncviewer.cxx:294
msgid "Quit"
msgstr "Quitter"
-#: vncviewer/vncviewer.cxx:284
+#: vncviewer/vncviewer.cxx:298
msgid "Services"
msgstr "Services"
-#: vncviewer/vncviewer.cxx:285
+#: vncviewer/vncviewer.cxx:299
msgid "Hide Others"
msgstr "Cacher les autres"
-#: vncviewer/vncviewer.cxx:286
+#: vncviewer/vncviewer.cxx:300
msgid "Show All"
msgstr "Afficher tout"
-#: vncviewer/vncviewer.cxx:295
+#: vncviewer/vncviewer.cxx:309
msgctxt "SysMenu|"
msgid "&File"
msgstr "&Fichier"
-#: vncviewer/vncviewer.cxx:298
+#: vncviewer/vncviewer.cxx:312
msgctxt "SysMenu|File|"
msgid "&New Connection"
msgstr "&Nouvelle connexion"
-#: vncviewer/vncviewer.cxx:310
+#: vncviewer/vncviewer.cxx:324
msgid "Could not create VNC home directory: can't obtain home directory path."
msgstr "Impossible de créer le répertoire VNC de départ : impossible d’obtenir le chemin du répertoire de départ."
-#: vncviewer/vncviewer.cxx:315
+#: vncviewer/vncviewer.cxx:329
#, c-format
msgid "Could not create VNC home directory: %s."
msgstr "Impossible de créer le répertoire VNC de départ : %s."
#. TRANSLATORS: "Parameters" are command line arguments, or settings
#. from a file or the Windows registry.
-#: vncviewer/vncviewer.cxx:520 vncviewer/vncviewer.cxx:521
+#: vncviewer/vncviewer.cxx:534 vncviewer/vncviewer.cxx:535
msgid "Parameters -listen and -via are incompatible"
msgstr "Les paramètres -listen et -via sont incompatibles"
-#: vncviewer/vncviewer.cxx:536
+#: vncviewer/vncviewer.cxx:550
#, c-format
msgid "Listening on port %d"
msgstr "Écoute du port %d"
-#: vncviewer/vncviewer.cxx:601
-msgid "Internal FLTK error. Exiting."
-msgstr "Erreur FLTK interne. Procédure de sortie."
+#~ msgid "Unknown encoding %d"
+#~ msgstr "Encodage inconnu %d"
+
+#~ msgid "Unknown encoding"
+#~ msgstr "Encodage inconnu"
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index bfd69dc..8ea9925 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -1,3 +1,6 @@
+include_directories(${FLTK_INCLUDE_DIR})
+include_directories(${GETTEXT_INCLUDE_DIR})
+
include_directories(${CMAKE_SOURCE_DIR}/common)
add_library(test_util STATIC util.cxx)
@@ -16,3 +19,23 @@
add_executable(hostport hostport.cxx)
target_link_libraries(hostport rfb)
+
+set(FBPERF_SOURCES
+ fbperf.cxx
+ ../vncviewer/PlatformPixelBuffer.cxx
+ ../vncviewer/Surface.cxx)
+if(WIN32)
+ set(FBPERF_SOURCES ${FBPERF_SOURCES} ../vncviewer/Surface_Win32.cxx)
+elseif(APPLE)
+ set(FBPERF_SOURCES ${FBPERF_SOURCES} ../vncviewer/Surface_OSX.cxx)
+else()
+ set(FBPERF_SOURCES ${FBPERF_SOURCES} ../vncviewer/Surface_X11.cxx)
+endif()
+add_executable(fbperf ${FBPERF_SOURCES})
+target_link_libraries(fbperf test_util rfb ${FLTK_LIBRARIES} ${GETTEXT_LIBRARIES})
+if(WIN32)
+ target_link_libraries(fbperf msimg32)
+endif()
+if(APPLE)
+ target_link_libraries(fbperf "-framework Cocoa" "-framework Carbon")
+endif()
diff --git a/tests/decperf.cxx b/tests/decperf.cxx
index 1fd763e..3b929a4 100644
--- a/tests/decperf.cxx
+++ b/tests/decperf.cxx
@@ -49,7 +49,7 @@
virtual void setDesktopSize(int w, int h);
virtual void setPixelFormat(const rfb::PixelFormat& pf);
- virtual void setCursor(int, int, const rfb::Point&, void*, void*);
+ virtual void setCursor(int, int, const rfb::Point&, const rdr::U8*);
virtual void framebufferUpdateStart();
virtual void framebufferUpdateEnd();
virtual void setColourMapEntries(int, int, rdr::U16*);
@@ -94,7 +94,7 @@
CConnection::setPixelFormat(filePF);
}
-void CConn::setCursor(int, int, const rfb::Point&, void*, void*)
+void CConn::setCursor(int, int, const rfb::Point&, const rdr::U8*)
{
}
diff --git a/tests/encperf.cxx b/tests/encperf.cxx
index d58d82e..7b9ff81 100644
--- a/tests/encperf.cxx
+++ b/tests/encperf.cxx
@@ -90,7 +90,7 @@
unsigned long long& rawEquivalent);
virtual void setDesktopSize(int w, int h);
- virtual void setCursor(int, int, const rfb::Point&, void*, void*);
+ virtual void setCursor(int, int, const rfb::Point&, const rdr::U8*);
virtual void framebufferUpdateStart();
virtual void framebufferUpdateEnd();
virtual void dataRect(const rfb::Rect&, int);
@@ -207,7 +207,7 @@
setFramebuffer(pb);
}
-void CConn::setCursor(int, int, const rfb::Point&, void*, void*)
+void CConn::setCursor(int, int, const rfb::Point&, const rdr::U8*)
{
}
diff --git a/tests/fbperf.cxx b/tests/fbperf.cxx
new file mode 100644
index 0000000..a19ee47
--- /dev/null
+++ b/tests/fbperf.cxx
@@ -0,0 +1,399 @@
+/* Copyright 2016 Pierre Ossman <ossman@cendio.se> for Cendio AB
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#include <math.h>
+#include <sys/time.h>
+
+#include <FL/Fl.H>
+#include <FL/Fl_Window.H>
+#include <FL/fl_draw.H>
+
+#include <rdr/Exception.h>
+#include <rfb/util.h>
+
+#include "../vncviewer/PlatformPixelBuffer.h"
+
+#include "util.h"
+
+class TestWindow: public Fl_Window {
+public:
+ TestWindow();
+ ~TestWindow();
+
+ virtual void start(int width, int height);
+ virtual void stop();
+
+ virtual void draw();
+
+protected:
+ virtual void flush();
+
+ void update();
+ virtual void changefb();
+
+ static void timer(void* data);
+
+public:
+ unsigned long long pixels, frames;
+ double time;
+
+protected:
+ PlatformPixelBuffer* fb;
+};
+
+class PartialTestWindow: public TestWindow {
+protected:
+ virtual void changefb();
+};
+
+class OverlayTestWindow: public PartialTestWindow {
+public:
+ OverlayTestWindow();
+
+ virtual void start(int width, int height);
+ virtual void stop();
+
+ virtual void draw();
+
+protected:
+ Surface* overlay;
+ Surface* offscreen;
+};
+
+TestWindow::TestWindow() :
+ Fl_Window(0, 0, "Framebuffer Performance Test"),
+ fb(NULL)
+{
+}
+
+TestWindow::~TestWindow()
+{
+ stop();
+}
+
+void TestWindow::start(int width, int height)
+{
+ rdr::U32 pixel;
+
+ stop();
+
+ resize(x(), y(), width, height);
+
+ pixels = 0;
+ frames = 0;
+ time = 0;
+
+ fb = new PlatformPixelBuffer(w(), h());
+
+ pixel = 0;
+ fb->fillRect(fb->getRect(), &pixel);
+
+ show();
+}
+
+void TestWindow::stop()
+{
+ hide();
+
+ delete fb;
+ fb = NULL;
+
+ Fl::remove_idle(timer, this);
+}
+
+void TestWindow::draw()
+{
+ int X, Y, W, H;
+
+ // We cannot update the damage region from inside the draw function,
+ // so delegate this to an idle function
+ Fl::add_idle(timer, this);
+
+ // Check what actually needs updating
+ fl_clip_box(0, 0, w(), h(), X, Y, W, H);
+ if ((W == 0) || (H == 0))
+ return;
+
+ fb->draw(X, Y, X, Y, W, H);
+
+ pixels += W*H;
+ frames++;
+}
+
+void TestWindow::flush()
+{
+ startTimeCounter();
+ Fl_Window::flush();
+#if !defined(WIN32) && !defined(__APPLE__)
+ // Make sure we measure any work we queue up
+ XSync(fl_display, False);
+#endif
+ endTimeCounter();
+
+ time += getTimeCounter();
+}
+
+void TestWindow::update()
+{
+ rfb::Rect r;
+
+ startTimeCounter();
+
+ changefb();
+
+ r = fb->getDamage();
+ damage(FL_DAMAGE_USER1, r.tl.x, r.tl.y, r.width(), r.height());
+
+#if !defined(WIN32) && !defined(__APPLE__)
+ // Make sure we measure any work we queue up
+ XSync(fl_display, False);
+#endif
+
+ endTimeCounter();
+
+ time += getTimeCounter();
+}
+
+void TestWindow::changefb()
+{
+ rdr::U32 pixel;
+
+ pixel = rand();
+ fb->fillRect(fb->getRect(), &pixel);
+}
+
+void TestWindow::timer(void* data)
+{
+ TestWindow* self;
+
+ Fl::remove_idle(timer, data);
+
+ self = (TestWindow*)data;
+ self->update();
+}
+
+void PartialTestWindow::changefb()
+{
+ rfb::Rect r;
+ rdr::U32 pixel;
+
+ r = fb->getRect();
+ r.tl.x += w() / 4;
+ r.tl.y += h() / 4;
+ r.br.x -= w() / 4;
+ r.br.y -= h() / 4;
+
+ pixel = rand();
+ fb->fillRect(r, &pixel);
+}
+
+OverlayTestWindow::OverlayTestWindow() :
+ overlay(NULL), offscreen(NULL)
+{
+}
+
+void OverlayTestWindow::start(int width, int height)
+{
+ PartialTestWindow::start(width, height);
+
+ overlay = new Surface(400, 200);
+ overlay->clear(0xff, 0x80, 0x00, 0xcc);
+
+ // X11 needs an off screen buffer for compositing to avoid flicker,
+ // and alpha blending doesn't work for windows on Win32
+#if !defined(__APPLE__)
+ offscreen = new Surface(w(), h());
+#else
+ offscreen = NULL;
+#endif
+}
+
+void OverlayTestWindow::stop()
+{
+ PartialTestWindow::stop();
+
+ delete offscreen;
+ offscreen = NULL;
+ delete overlay;
+ overlay = NULL;
+}
+
+void OverlayTestWindow::draw()
+{
+ int ox, oy, ow, oh;
+ int X, Y, W, H;
+
+ // We cannot update the damage region from inside the draw function,
+ // so delegate this to an idle function
+ Fl::add_idle(timer, this);
+
+ // Check what actually needs updating
+ fl_clip_box(0, 0, w(), h(), X, Y, W, H);
+ if ((W == 0) || (H == 0))
+ return;
+
+ // We might get a redraw before we are fully ready
+ if (!overlay)
+ return;
+
+ // Simplify the clip region to a simple rectangle in order to
+ // properly draw all the layers even if they only partially overlap
+ fl_push_no_clip();
+ fl_push_clip(X, Y, W, H);
+
+ if (offscreen)
+ fb->draw(offscreen, X, Y, X, Y, W, H);
+ else
+ fb->draw(X, Y, X, Y, W, H);
+
+ pixels += W*H;
+ frames++;
+
+ ox = (w() - overlay->width()) / 2;
+ oy = h() / 4 - overlay->height() / 2;
+ ow = overlay->width();
+ oh = overlay->height();
+ fl_clip_box(ox, oy, ow, oh, X, Y, W, H);
+ if ((W != 0) && (H != 0)) {
+ if (offscreen)
+ overlay->blend(offscreen, X - ox, Y - oy, X, Y, W, H);
+ else
+ overlay->blend(X - ox, Y - oy, X, Y, W, H);
+ }
+
+ fl_pop_clip();
+ fl_pop_clip();
+
+ if (offscreen) {
+ fl_clip_box(0, 0, w(), h(), X, Y, W, H);
+ offscreen->draw(X, Y, X, Y, W, H);
+ }
+}
+
+static void dosubtest(TestWindow* win, int width, int height,
+ unsigned long long* pixels,
+ unsigned long long* frames,
+ double* time)
+{
+ struct timeval start;
+
+ win->start(width, height);
+
+ gettimeofday(&start, NULL);
+ while (rfb::msSince(&start) < 3000)
+ Fl::wait();
+
+ win->stop();
+
+ *pixels = win->pixels;
+ *frames = win->frames;
+ *time = win->time;
+}
+
+static bool is_constant(double a, double b)
+{
+ return (fabs(a - b) / a) < 0.1;
+}
+
+static void dotest(TestWindow* win)
+{
+ unsigned long long pixels[3];
+ unsigned long long frames[3];
+ double time[3];
+
+ double delay, rate;
+ char s[1024];
+
+ // Run the test several times at different resolutions...
+ dosubtest(win, 800, 600, &pixels[0], &frames[0], &time[0]);
+ dosubtest(win, 1024, 768, &pixels[1], &frames[1], &time[1]);
+ dosubtest(win, 1280, 960, &pixels[2], &frames[2], &time[2]);
+
+ // ...in order to compute how much of the rendering time is static,
+ // and how much depends on the number of pixels
+ // (i.e. solve: time = delay * frames + rate * pixels)
+ delay = (((time[0] - (double)pixels[0] / pixels[1] * time[1]) /
+ (frames[0] - (double)pixels[0] / pixels[1] * frames[1])) +
+ ((time[1] - (double)pixels[1] / pixels[2] * time[2]) /
+ (frames[1] - (double)pixels[1] / pixels[2] * frames[2]))) / 2.0;
+ rate = (((time[0] - (double)frames[0] / frames[1] * time[1]) /
+ (pixels[0] - (double)frames[0] / frames[1] * pixels[1])) +
+ ((time[1] - (double)frames[1] / frames[2] * time[2]) /
+ (pixels[1] - (double)frames[1] / frames[2] * pixels[2]))) / 2.0;
+
+ // However, we have some corner cases:
+
+ // We are restricted by some delay, e.g. refresh rate
+ if (is_constant(frames[0]/time[0], frames[2]/time[2])) {
+ fprintf(stderr, "WARNING: Fixed delay dominating updates.\n\n");
+ delay = time[2]/frames[2];
+ rate = 0.0;
+ }
+
+ // There isn't any fixed delay, we are only restricted by pixel
+ // throughput
+ if (fabs(delay) < 0.001) {
+ delay = 0.0;
+ rate = time[2]/pixels[2];
+ }
+
+ // We can hit cache limits that causes performance to drop
+ // with increasing update size, screwing up our calculations
+ if ((pixels[2] / time[2]) < (pixels[0] / time[0] * 0.9)) {
+ fprintf(stderr, "WARNING: Unexpected behaviour. Measurement unreliable.\n\n");
+
+ // We can't determine the proportions between these, so divide the
+ // time spent evenly
+ delay = time[2] / 2.0 / frames[2];
+ rate = time[2] / 2.0 / pixels[2];
+ }
+
+ fprintf(stderr, "Rendering delay: %g ms/frame\n", delay * 1000.0);
+ if (rate == 0.0)
+ strcpy(s, "N/A pixels/s");
+ else
+ rfb::siPrefix(1.0 / rate, "pixels/s", s, sizeof(s));
+ fprintf(stderr, "Rendering rate: %s\n", s);
+ fprintf(stderr, "Maximum FPS: %g fps @ 1920x1080\n",
+ 1.0 / (delay + rate * 1920 * 1080));
+}
+
+int main(int argc, char** argv)
+{
+ TestWindow* win;
+
+ fprintf(stderr, "Full window update:\n\n");
+ win = new TestWindow();
+ dotest(win);
+ delete win;
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, "Partial window update:\n\n");
+ win = new PartialTestWindow();
+ dotest(win);
+ delete win;
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, "Partial window update with overlay:\n\n");
+ win = new OverlayTestWindow();
+ dotest(win);
+ delete win;
+ fprintf(stderr, "\n");
+
+ return 0;
+}
diff --git a/tests/results/multicore/README b/tests/results/multicore/README
index ffeca4e..c93b2d7 100644
--- a/tests/results/multicore/README
+++ b/tests/results/multicore/README
@@ -34,7 +34,7 @@
point and tracing doesn't reveal anything obvious. It may be because it
is not a true quad core system, but rather uses HyperThreading.
-So in summary, the new code can do a noticable improvement on decoding
+So in summary, the new code can do a noticeable improvement on decoding
time. However it does so at a cost of efficiency. Four times the CPUs
only gives you about twice the performance. More improvements may be
possible.
diff --git a/tests/util.cxx b/tests/util.cxx
index 4683d35..17a8369 100644
--- a/tests/util.cxx
+++ b/tests/util.cxx
@@ -24,6 +24,7 @@
#include <windows.h>
#else
#include <sys/resource.h>
+#include <sys/time.h>
#endif
#include "util.h"
@@ -132,3 +133,46 @@
return sysSeconds + userSeconds;
}
+
+#ifdef WIN32
+static LARGE_INTEGER timeStart, timeEnd;
+#else
+static struct timeval timeStart, timeEnd;
+#endif
+
+void startTimeCounter(void)
+{
+#ifdef WIN32
+ QueryPerformanceCounter(&timeStart);
+#else
+ gettimeofday(&timeStart, NULL);
+#endif
+}
+
+void endTimeCounter(void)
+{
+#ifdef WIN32
+ QueryPerformanceCounter(&timeEnd);
+#else
+ gettimeofday(&timeEnd, NULL);
+#endif
+}
+
+double getTimeCounter(void)
+{
+ double time;
+
+#ifdef WIN32
+ LARGE_INTEGER freq;
+
+ QueryPerformanceFrequency(&freq);
+
+ time = timeEnd.QuadPart - timeStart.QuadPart;
+ time = time / freq.QuadPart;
+#else
+ time = (double)timeEnd.tv_sec - timeStart.tv_sec;
+ time += (double)(timeEnd.tv_usec - timeStart.tv_usec) / 1000000.0;
+#endif
+
+ return time;
+}
diff --git a/tests/util.h b/tests/util.h
index 16f2ba2..2b8ab4a 100644
--- a/tests/util.h
+++ b/tests/util.h
@@ -34,4 +34,9 @@
double getCpuCounter(cpucounter_t c);
+void startTimeCounter(void);
+void endTimeCounter(void);
+
+double getTimeCounter(void);
+
#endif
diff --git a/unix/tx/TXDialog.h b/unix/tx/TXDialog.h
index c8d601c..7a52f92 100644
--- a/unix/tx/TXDialog.h
+++ b/unix/tx/TXDialog.h
@@ -78,7 +78,7 @@
// to make sure that checkboxes have the right state, etc.
virtual void initDialog() {}
- // resize() is overidden here to re-center the dialog
+ // resize() is overridden here to re-center the dialog
void resize(int w, int h) {
TXWindow::resize(w,h);
int dpyWidth = WidthOfScreen(DefaultScreenOfDisplay(dpy));
diff --git a/unix/x0vncserver/TimeMillis.h b/unix/x0vncserver/TimeMillis.h
index e79db12..115ce08 100644
--- a/unix/x0vncserver/TimeMillis.h
+++ b/unix/x0vncserver/TimeMillis.h
@@ -31,7 +31,7 @@
TimeMillis();
- // Set this object to current time, returns true on sucess.
+ // Set this object to current time, returns true on success.
bool update();
// Return difference in milliseconds between two time points.
diff --git a/unix/xserver/hw/vnc/Input.c b/unix/xserver/hw/vnc/Input.c
index 64305cb..33e8604 100644
--- a/unix/xserver/hw/vnc/Input.c
+++ b/unix/xserver/hw/vnc/Input.c
@@ -74,7 +74,7 @@
/*
* Init input device.
* This has to be called after core pointer/keyboard
- * initialization which unfortunately is after extesions
+ * initialization which unfortunately is after extensions
* initialization (which means we cannot call it in
* vncExtensionInit(). Check InitExtensions(),
* InitCoreDevices() and InitInput() calls in dix/main.c.
diff --git a/unix/xserver/hw/vnc/XserverDesktop.cc b/unix/xserver/hw/vnc/XserverDesktop.cc
index b681ae7..4836782 100644
--- a/unix/xserver/hw/vnc/XserverDesktop.cc
+++ b/unix/xserver/hw/vnc/XserverDesktop.cc
@@ -1,5 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright 2009-2015 Pierre Ossman for Cendio AB
+ * Copyright 2009-2017 Pierre Ossman for Cendio AB
* Copyright 2014 Brian P. Hinz
*
* This is free software; you can redistribute it and/or modify
@@ -344,46 +344,37 @@
const unsigned char *rgbaData)
{
rdr::U8* cursorData;
- rdr::U8* cursorMask;
- int rfbMaskBytesPerRow;
rdr::U8 *out;
const unsigned char *in;
- rdr::U8 rgb[3];
- cursorData = new rdr::U8[width * height * (getPF().bpp / 8)];
+ cursorData = new rdr::U8[width * height * 4];
- rfbMaskBytesPerRow = (width + 7) / 8;
-
- cursorMask = new rdr::U8[rfbMaskBytesPerRow * height];
-
- memset(cursorMask, 0, rfbMaskBytesPerRow * height);
-
+ // Un-premultiply alpha
in = rgbaData;
out = cursorData;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
- rgb[0] = *in++;
- rgb[1] = *in++;
- rgb[2] = *in++;
+ rdr::U8 alpha;
- getPF().bufferFromRGB(out, rgb, 1);
+ alpha = in[3];
+ if (alpha == 0)
+ alpha = 1; // Avoid division by zero
- if (*in++ > 127)
- cursorMask[y * rfbMaskBytesPerRow + x/8] |= 0x80>>(x%8);
-
- out += getPF().bpp/8;
+ *out++ = (unsigned)*in++ * 255/alpha;
+ *out++ = (unsigned)*in++ * 255/alpha;
+ *out++ = (unsigned)*in++ * 255/alpha;
+ *out++ = *in++;
}
}
try {
- server->setCursor(width, height, Point(hotX, hotY), cursorData, cursorMask);
+ server->setCursor(width, height, Point(hotX, hotY), cursorData);
} catch (rdr::Exception& e) {
vlog.error("XserverDesktop::setCursor: %s",e.str());
}
delete [] cursorData;
- delete [] cursorMask;
}
void XserverDesktop::add_changed(const rfb::Region ®ion)
@@ -443,6 +434,7 @@
sock->outStream().setBlocking(false);
vlog.debug("new client, sock %d", sock->getFd());
sockserv->addSocket(sock);
+ vncSetNotifyFd(sock->getFd(), screenIndex, true, false);
return true;
}
@@ -536,6 +528,7 @@
void XserverDesktop::addClient(Socket* sock, bool reverse)
{
vlog.debug("new client, sock %d reverse %d",sock->getFd(),reverse);
+ sock->outStream().setBlocking(false);
server->addSocket(sock, reverse);
vncSetNotifyFd(sock->getFd(), screenIndex, true, false);
}
diff --git a/unix/xserver/hw/vnc/vncBlockHandler.c b/unix/xserver/hw/vnc/vncBlockHandler.c
index 390a9b3..24c2013 100644
--- a/unix/xserver/hw/vnc/vncBlockHandler.c
+++ b/unix/xserver/hw/vnc/vncBlockHandler.c
@@ -66,9 +66,9 @@
{
#if XORG >= 119
int mask = (read ? X_NOTIFY_READ : 0) | (write ? X_NOTIFY_WRITE : 0);
- SetNotifyFd(fd, vncSocketNotify, mask, (void*)scrIdx);
+ SetNotifyFd(fd, vncSocketNotify, mask, (void*)(intptr_t)scrIdx);
#else
- static struct vncFdEntry* entry;
+ struct vncFdEntry* entry;
entry = fdsHead;
while (entry) {
@@ -99,8 +99,8 @@
#if XORG >= 119
RemoveNotifyFd(fd);
#else
- static struct vncFdEntry** prev;
- static struct vncFdEntry* entry;
+ struct vncFdEntry** prev;
+ struct vncFdEntry* entry;
prev = &fdsHead;
entry = fdsHead;
@@ -122,7 +122,7 @@
{
int scrIdx;
- scrIdx = (int)data;
+ scrIdx = (intptr_t)data;
vncHandleSocketEvent(fd, scrIdx,
xevents & X_NOTIFY_READ,
xevents & X_NOTIFY_WRITE);
diff --git a/unix/xserver/hw/vnc/vncHooks.c b/unix/xserver/hw/vnc/vncHooks.c
index 29f3f8b..ef340b3 100644
--- a/unix/xserver/hw/vnc/vncHooks.c
+++ b/unix/xserver/hw/vnc/vncHooks.c
@@ -1,5 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright 2009-2015 Pierre Ossman for Cendio AB
+ * Copyright 2009-2017 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -73,22 +73,31 @@
GlyphsProcPtr Glyphs;
#endif
#ifdef RANDR
- RRSetConfigProcPtr RandRSetConfig;
- RRScreenSetSizeProcPtr RandRScreenSetSize;
- RRCrtcSetProcPtr RandRCrtcSet;
+ RRSetConfigProcPtr rrSetConfig;
+ RRScreenSetSizeProcPtr rrScreenSetSize;
+ RRCrtcSetProcPtr rrCrtcSet;
#endif
} vncHooksScreenRec, *vncHooksScreenPtr;
typedef struct _vncHooksGCRec {
#if XORG >= 116
- const GCFuncs *wrappedFuncs;
- const GCOps *wrappedOps;
+ const GCFuncs *funcs;
+ const GCOps *ops;
#else
- GCFuncs *wrappedFuncs;
- GCOps *wrappedOps;
+ GCFuncs *funcs;
+ GCOps *ops;
#endif
} vncHooksGCRec, *vncHooksGCPtr;
+#define wrap(priv, real, mem, func) {\
+ priv->mem = real->mem; \
+ real->mem = func; \
+}
+
+#define unwrap(priv, real, mem) {\
+ real->mem = priv->mem; \
+}
+
#if XORG < 19
static int vncHooksScreenPrivateKeyIndex;
static int vncHooksGCPrivateKeyIndex;
@@ -284,55 +293,28 @@
vncHooksScreen->ignoreHooks = 0;
- vncHooksScreen->CloseScreen = pScreen->CloseScreen;
- vncHooksScreen->CreateGC = pScreen->CreateGC;
- vncHooksScreen->CopyWindow = pScreen->CopyWindow;
- vncHooksScreen->ClearToBackground = pScreen->ClearToBackground;
+ wrap(vncHooksScreen, pScreen, CloseScreen, vncHooksCloseScreen);
+ wrap(vncHooksScreen, pScreen, CreateGC, vncHooksCreateGC);
+ wrap(vncHooksScreen, pScreen, CopyWindow, vncHooksCopyWindow);
+ wrap(vncHooksScreen, pScreen, ClearToBackground, vncHooksClearToBackground);
#if XORG < 110
- vncHooksScreen->RestoreAreas = pScreen->RestoreAreas;
+ wrap(vncHooksScreen, pScreen, RestoreAreas, vncHooksRestoreAreas);
#endif
- vncHooksScreen->DisplayCursor = pScreen->DisplayCursor;
- vncHooksScreen->BlockHandler = pScreen->BlockHandler;
+ wrap(vncHooksScreen, pScreen, DisplayCursor, vncHooksDisplayCursor);
+ wrap(vncHooksScreen, pScreen, BlockHandler, vncHooksBlockHandler);
#ifdef RENDER
ps = GetPictureScreenIfSet(pScreen);
if (ps) {
- vncHooksScreen->Composite = ps->Composite;
- vncHooksScreen->Glyphs = ps->Glyphs;
+ wrap(vncHooksScreen, ps, Composite, vncHooksComposite);
+ wrap(vncHooksScreen, ps, Glyphs, vncHooksGlyphs);
}
#endif
#ifdef RANDR
rp = rrGetScrPriv(pScreen);
if (rp) {
- vncHooksScreen->RandRSetConfig = rp->rrSetConfig;
- vncHooksScreen->RandRScreenSetSize = rp->rrScreenSetSize;
- vncHooksScreen->RandRCrtcSet = rp->rrCrtcSet;
- }
-#endif
-
- pScreen->CloseScreen = vncHooksCloseScreen;
- pScreen->CreateGC = vncHooksCreateGC;
- pScreen->CopyWindow = vncHooksCopyWindow;
- pScreen->ClearToBackground = vncHooksClearToBackground;
-#if XORG < 110
- pScreen->RestoreAreas = vncHooksRestoreAreas;
-#endif
- pScreen->DisplayCursor = vncHooksDisplayCursor;
- pScreen->BlockHandler = vncHooksBlockHandler;
-#ifdef RENDER
- if (ps) {
- ps->Composite = vncHooksComposite;
- ps->Glyphs = vncHooksGlyphs;
- }
-#endif
-#ifdef RANDR
- if (rp) {
- /* Some RandR callbacks are optional */
- if (rp->rrSetConfig)
- rp->rrSetConfig = vncHooksRandRSetConfig;
- if (rp->rrScreenSetSize)
- rp->rrScreenSetSize = vncHooksRandRScreenSetSize;
- if (rp->rrCrtcSet)
- rp->rrCrtcSet = vncHooksRandRCrtcSet;
+ wrap(vncHooksScreen, rp, rrSetConfig, vncHooksRandRSetConfig);
+ wrap(vncHooksScreen, rp, rrScreenSetSize, vncHooksRandRScreenSetSize);
+ wrap(vncHooksScreen, rp, rrCrtcSet, vncHooksRandRCrtcSet);
}
#endif
@@ -433,15 +415,16 @@
// screen functions
//
-// SCREEN_UNWRAP and SCREEN_REWRAP unwrap and rewrap the given screen function.
+// Unwrap and rewrap helpers
-#define SCREEN_UNWRAP(scrn,field) \
+#define SCREEN_PROLOGUE(scrn,field) \
ScreenPtr pScreen = scrn; \
vncHooksScreenPtr vncHooksScreen = vncHooksScreenPrivate(pScreen); \
- pScreen->field = vncHooksScreen->field; \
+ unwrap(vncHooksScreen, pScreen, field); \
DBGPRINT((stderr,"vncHooks" #field " called\n"));
-#define SCREEN_REWRAP(field) pScreen->field = vncHooks##field;
+#define SCREEN_EPILOGUE(field) \
+ wrap(vncHooksScreen, pScreen, field, vncHooks##field); \
// CloseScreen - unwrap the screen functions and call the original CloseScreen
@@ -460,29 +443,29 @@
rrScrPrivPtr rp;
#endif
- SCREEN_UNWRAP(pScreen_, CloseScreen);
+ SCREEN_PROLOGUE(pScreen_, CloseScreen);
- pScreen->CreateGC = vncHooksScreen->CreateGC;
- pScreen->CopyWindow = vncHooksScreen->CopyWindow;
- pScreen->ClearToBackground = vncHooksScreen->ClearToBackground;
+ unwrap(vncHooksScreen, pScreen, CreateGC);
+ unwrap(vncHooksScreen, pScreen, CopyWindow);
+ unwrap(vncHooksScreen, pScreen, ClearToBackground);
#if XORG < 110
- pScreen->RestoreAreas = vncHooksScreen->RestoreAreas;
+ unwrap(vncHooksScreen, pScreen, RestoreAreas);
#endif
- pScreen->DisplayCursor = vncHooksScreen->DisplayCursor;
- pScreen->BlockHandler = vncHooksScreen->BlockHandler;
+ unwrap(vncHooksScreen, pScreen, DisplayCursor);
+ unwrap(vncHooksScreen, pScreen, BlockHandler);
#ifdef RENDER
ps = GetPictureScreenIfSet(pScreen);
if (ps) {
- ps->Composite = vncHooksScreen->Composite;
- ps->Glyphs = vncHooksScreen->Glyphs;
+ unwrap(vncHooksScreen, ps, Composite);
+ unwrap(vncHooksScreen, ps, Glyphs);
}
#endif
#ifdef RANDR
rp = rrGetScrPriv(pScreen);
if (rp) {
- rp->rrSetConfig = vncHooksScreen->RandRSetConfig;
- rp->rrScreenSetSize = vncHooksScreen->RandRScreenSetSize;
- rp->rrCrtcSet = vncHooksScreen->RandRCrtcSet;
+ unwrap(vncHooksScreen, rp, rrSetConfig);
+ unwrap(vncHooksScreen, rp, rrScreenSetSize);
+ unwrap(vncHooksScreen, rp, rrCrtcSet);
}
#endif
@@ -502,15 +485,15 @@
vncHooksGCPtr vncHooksGC = vncHooksGCPrivate(pGC);
Bool ret;
- SCREEN_UNWRAP(pGC->pScreen, CreateGC);
+ SCREEN_PROLOGUE(pGC->pScreen, CreateGC);
ret = (*pScreen->CreateGC) (pGC);
- vncHooksGC->wrappedOps = 0;
- vncHooksGC->wrappedFuncs = pGC->funcs;
+ vncHooksGC->ops = NULL;
+ vncHooksGC->funcs = pGC->funcs;
pGC->funcs = &vncHooksGCFuncs;
- SCREEN_REWRAP(CreateGC);
+ SCREEN_EPILOGUE(CreateGC);
return ret;
}
@@ -526,7 +509,7 @@
BoxRec screen_box;
RegionRec copied, screen_rgn;
- SCREEN_UNWRAP(pWin->drawable.pScreen, CopyWindow);
+ SCREEN_PROLOGUE(pWin->drawable.pScreen, CopyWindow);
REGION_NULL(pScreen, &copied);
REGION_COPY(pScreen, &copied, pOldRegion);
@@ -558,7 +541,7 @@
REGION_UNINIT(pScreen, &copied);
REGION_UNINIT(pScreen, &screen_rgn);
- SCREEN_REWRAP(CopyWindow);
+ SCREEN_EPILOGUE(CopyWindow);
}
// ClearToBackground - changed region is the given rectangle, clipped by
@@ -570,7 +553,7 @@
BoxRec box;
RegionRec reg;
- SCREEN_UNWRAP(pWin->drawable.pScreen, ClearToBackground);
+ SCREEN_PROLOGUE(pWin->drawable.pScreen, ClearToBackground);
box.x1 = x + pWin->drawable.x;
box.y1 = y + pWin->drawable.y;
@@ -588,7 +571,7 @@
REGION_UNINIT(pScreen, ®);
- SCREEN_REWRAP(ClearToBackground);
+ SCREEN_EPILOGUE(ClearToBackground);
}
#if XORG < 110
@@ -598,7 +581,7 @@
{
RegionRec reg;
- SCREEN_UNWRAP(pWin->drawable.pScreen, RestoreAreas);
+ SCREEN_PROLOGUE(pWin->drawable.pScreen, RestoreAreas);
REGION_NULL(pScreen, ®);
REGION_COPY(pScreen, ®, pRegion);
@@ -609,7 +592,7 @@
REGION_UNINIT(pScreen, ®);
- SCREEN_REWRAP(RestoreAreas);
+ SCREEN_EPILOGUE(RestoreAreas);
return result;
}
@@ -622,7 +605,7 @@
{
Bool ret;
- SCREEN_UNWRAP(pScreen_, DisplayCursor);
+ SCREEN_PROLOGUE(pScreen_, DisplayCursor);
ret = (*pScreen->DisplayCursor) (pDev, pScreen, cursor);
@@ -707,7 +690,7 @@
}
out:
- SCREEN_REWRAP(DisplayCursor);
+ SCREEN_EPILOGUE(DisplayCursor);
return ret;
}
@@ -726,9 +709,9 @@
#endif
{
#if XORG <= 112
- SCREEN_UNWRAP(screenInfo.screens[i], BlockHandler);
+ SCREEN_PROLOGUE(screenInfo.screens[i], BlockHandler);
#else
- SCREEN_UNWRAP(pScreen_, BlockHandler);
+ SCREEN_PROLOGUE(pScreen_, BlockHandler);
#endif
vncHooksScreen->ignoreHooks++;
@@ -743,23 +726,33 @@
vncHooksScreen->ignoreHooks--;
- SCREEN_REWRAP(BlockHandler);
+ SCREEN_EPILOGUE(BlockHandler);
}
#ifdef RENDER
+// Unwrap and rewrap helpers
+
+#define RENDER_PROLOGUE(scrn,field) \
+ ScreenPtr pScreen = scrn; \
+ PictureScreenPtr ps = GetPictureScreen(pScreen); \
+ vncHooksScreenPtr vncHooksScreen = vncHooksScreenPrivate(pScreen); \
+ unwrap(vncHooksScreen, ps, field); \
+ DBGPRINT((stderr,"vncHooks" #field " called\n"));
+
+#define RENDER_EPILOGUE(field) \
+ wrap(vncHooksScreen, ps, field, vncHooks##field); \
+
// Composite - The core of XRENDER
static void vncHooksComposite(CARD8 op, PicturePtr pSrc, PicturePtr pMask,
PicturePtr pDst, INT16 xSrc, INT16 ySrc, INT16 xMask,
INT16 yMask, INT16 xDst, INT16 yDst, CARD16 width, CARD16 height)
{
- ScreenPtr pScreen = pDst->pDrawable->pScreen;
- vncHooksScreenPtr vncHooksScreen = vncHooksScreenPrivate(pScreen);
- PictureScreenPtr ps = GetPictureScreen(pScreen);
-
RegionRec changed;
+ RENDER_PROLOGUE(pDst->pDrawable->pScreen, Composite);
+
if (is_visible(pDst->pDrawable)) {
BoxRec box;
RegionRec fbreg;
@@ -784,15 +777,15 @@
}
- ps->Composite = vncHooksScreen->Composite;
(*ps->Composite)(op, pSrc, pMask, pDst, xSrc, ySrc,
xMask, yMask, xDst, yDst, width, height);
- ps->Composite = vncHooksComposite;
if (REGION_NOTEMPTY(pScreen, &changed))
add_changed(pScreen, &changed);
REGION_UNINIT(pScreen, &changed);
+
+ RENDER_EPILOGUE(Composite);
}
static int
@@ -849,12 +842,10 @@
PictFormatPtr maskFormat, INT16 xSrc, INT16 ySrc, int nlists,
GlyphListPtr lists, GlyphPtr * glyphs)
{
- ScreenPtr pScreen = pDst->pDrawable->pScreen;
- vncHooksScreenPtr vncHooksScreen = vncHooksScreenPrivate(pScreen);
- PictureScreenPtr ps = GetPictureScreen(pScreen);
-
RegionPtr changed;
+ RENDER_PROLOGUE(pDst->pDrawable->pScreen, Glyphs);
+
if (is_visible(pDst->pDrawable)) {
BoxRec fbbox;
RegionRec fbreg;
@@ -876,37 +867,46 @@
changed = REGION_CREATE(pScreen, NullBox, 0);
}
- ps->Glyphs = vncHooksScreen->Glyphs;
(*ps->Glyphs)(op, pSrc, pDst, maskFormat, xSrc, ySrc, nlists, lists, glyphs);
- ps->Glyphs = vncHooksGlyphs;
if (REGION_NOTEMPTY(pScreen, changed))
add_changed(pScreen, changed);
REGION_DESTROY(pScreen, changed);
+
+ RENDER_EPILOGUE(Glyphs);
}
#endif /* RENDER */
-// RandRSetConfig - follow any framebuffer changes
-
#ifdef RANDR
+// Unwrap and rewrap helpers
+
+#define RANDR_PROLOGUE(field) \
+ rrScrPrivPtr rp = rrGetScrPriv(pScreen); \
+ vncHooksScreenPtr vncHooksScreen = vncHooksScreenPrivate(pScreen); \
+ unwrap(vncHooksScreen, rp, rr##field); \
+ DBGPRINT((stderr,"vncHooksRandR" #field " called\n"));
+
+#define RANDR_EPILOGUE(field) \
+ wrap(vncHooksScreen, rp, rr##field, vncHooksRandR##field); \
+
+// RandRSetConfig - follow any framebuffer changes
+
static Bool vncHooksRandRSetConfig(ScreenPtr pScreen, Rotation rotation,
int rate, RRScreenSizePtr pSize)
{
- vncHooksScreenPtr vncHooksScreen = vncHooksScreenPrivate(pScreen);
- rrScrPrivPtr rp = rrGetScrPriv(pScreen);
Bool ret;
+ RANDR_PROLOGUE(SetConfig);
+
vncPreScreenResize(pScreen->myNum);
-
- rp->rrSetConfig = vncHooksScreen->RandRSetConfig;
ret = (*rp->rrSetConfig)(pScreen, rotation, rate, pSize);
- rp->rrSetConfig = vncHooksRandRSetConfig;
-
vncPostScreenResize(pScreen->myNum, ret, pScreen->width, pScreen->height);
+ RANDR_EPILOGUE(SetConfig);
+
if (!ret)
return FALSE;
@@ -917,18 +917,16 @@
CARD16 width, CARD16 height,
CARD32 mmWidth, CARD32 mmHeight)
{
- vncHooksScreenPtr vncHooksScreen = vncHooksScreenPrivate(pScreen);
- rrScrPrivPtr rp = rrGetScrPriv(pScreen);
Bool ret;
+ RANDR_PROLOGUE(ScreenSetSize);
+
vncPreScreenResize(pScreen->myNum);
-
- rp->rrScreenSetSize = vncHooksScreen->RandRScreenSetSize;
ret = (*rp->rrScreenSetSize)(pScreen, width, height, mmWidth, mmHeight);
- rp->rrScreenSetSize = vncHooksRandRScreenSetSize;
-
vncPostScreenResize(pScreen->myNum, ret, pScreen->width, pScreen->height);
+ RANDR_EPILOGUE(ScreenSetSize);
+
if (!ret)
return FALSE;
@@ -940,14 +938,14 @@
Rotation rotation, int num_outputs,
RROutputPtr *outputs)
{
- vncHooksScreenPtr vncHooksScreen = vncHooksScreenPrivate(pScreen);
- rrScrPrivPtr rp = rrGetScrPriv(pScreen);
Bool ret;
- rp->rrCrtcSet = vncHooksScreen->RandRCrtcSet;
+ RANDR_PROLOGUE(CrtcSet);
+
ret = (*rp->rrCrtcSet)(pScreen, crtc, mode, x, y, rotation,
num_outputs, outputs);
- rp->rrCrtcSet = vncHooksRandRCrtcSet;
+
+ RANDR_EPILOGUE(CrtcSet);
if (!ret)
return FALSE;
@@ -968,18 +966,13 @@
#define GC_FUNC_PROLOGUE(pGC, name)\
vncHooksGCPtr pGCPriv = vncHooksGCPrivate(pGC);\
- (pGC)->funcs = pGCPriv->wrappedFuncs;\
- if(pGCPriv->wrappedOps)\
- (pGC)->ops = pGCPriv->wrappedOps; \
+ unwrap(pGCPriv, pGC, funcs);\
+ if (pGCPriv->ops) unwrap(pGCPriv, pGC, ops)\
DBGPRINT((stderr,"vncHooks" #name " called\n"))
#define GC_FUNC_EPILOGUE(pGC)\
- pGCPriv->wrappedFuncs = (pGC)->funcs;\
- (pGC)->funcs = &vncHooksGCFuncs;\
- if(pGCPriv->wrappedOps) {\
- pGCPriv->wrappedOps = (pGC)->ops;\
- (pGC)->ops = &vncHooksGCOps;\
- }
+ wrap(pGCPriv, pGC, funcs, &vncHooksGCFuncs);\
+ if (pGCPriv->ops) wrap(pGCPriv, pGC, ops, &vncHooksGCOps)
// ValidateGC - wrap the "ops" if the drawable is on screen
@@ -989,10 +982,10 @@
GC_FUNC_PROLOGUE(pGC, ValidateGC);
(*pGC->funcs->ValidateGC) (pGC, changes, pDrawable);
if (is_visible(pDrawable)) {
- pGCPriv->wrappedOps = pGC->ops;
+ pGCPriv->ops = pGC->ops;
DBGPRINT((stderr,"vncHooksValidateGC: wrapped GC ops\n"));
} else {
- pGCPriv->wrappedOps = NULL;
+ pGCPriv->ops = NULL;
}
GC_FUNC_EPILOGUE(pGC);
}
@@ -1043,22 +1036,21 @@
#define GC_OP_PROLOGUE(pGC, name)\
vncHooksGCPtr pGCPriv = vncHooksGCPrivate(pGC);\
const GCFuncs *oldFuncs = pGC->funcs;\
- pGC->funcs = pGCPriv->wrappedFuncs;\
- pGC->ops = pGCPriv->wrappedOps; \
+ unwrap(pGCPriv, pGC, funcs);\
+ unwrap(pGCPriv, pGC, ops);\
DBGPRINT((stderr,"vncHooks" #name " called\n"))
#else
#define GC_OP_PROLOGUE(pGC, name)\
vncHooksGCPtr pGCPriv = vncHooksGCPrivate(pGC);\
GCFuncs *oldFuncs = pGC->funcs;\
- pGC->funcs = pGCPriv->wrappedFuncs;\
- pGC->ops = pGCPriv->wrappedOps; \
+ unwrap(pGCPriv, pGC, funcs);\
+ unwrap(pGCPriv, pGC, ops);\
DBGPRINT((stderr,"vncHooks" #name " called\n"))
#endif
#define GC_OP_EPILOGUE(pGC)\
- pGCPriv->wrappedOps = pGC->ops;\
- pGC->funcs = oldFuncs;\
- pGC->ops = &vncHooksGCOps
+ wrap(pGCPriv, pGC, funcs, oldFuncs); \
+ wrap(pGCPriv, pGC, ops, &vncHooksGCOps)
// FillSpans - assume the entire clip region is damaged. This is pessimistic,
// but I believe this function is rarely used so it doesn't matter.
diff --git a/unix/xserver/hw/vnc/vncSelection.c b/unix/xserver/hw/vnc/vncSelection.c
index e50548a..6a8fdc3 100644
--- a/unix/xserver/hw/vnc/vncSelection.c
+++ b/unix/xserver/hw/vnc/vncSelection.c
@@ -230,23 +230,24 @@
Atom targets[] = { xaTARGETS, xaTIMESTAMP,
xaSTRING, xaTEXT, xaUTF8_STRING };
- rc = ChangeWindowProperty(pWin, realProperty, XA_ATOM, 32,
- PropModeReplace,
- sizeof(targets)/sizeof(targets[0]),
- targets, TRUE);
+ rc = dixChangeWindowProperty(serverClient, pWin, realProperty,
+ XA_ATOM, 32, PropModeReplace,
+ sizeof(targets)/sizeof(targets[0]),
+ targets, TRUE);
if (rc != Success)
return rc;
} else if (target == xaTIMESTAMP) {
- rc = ChangeWindowProperty(pWin, realProperty, XA_INTEGER, 32,
- PropModeReplace, 1,
- &pSel->lastTimeChanged.milliseconds,
- TRUE);
+ rc = dixChangeWindowProperty(serverClient, pWin, realProperty,
+ XA_INTEGER, 32, PropModeReplace, 1,
+ &pSel->lastTimeChanged.milliseconds,
+ TRUE);
if (rc != Success)
return rc;
} else if ((target == xaSTRING) || (target == xaTEXT)) {
- rc = ChangeWindowProperty(pWin, realProperty, XA_STRING, 8,
- PropModeReplace, clientCutTextLen,
- clientCutText, TRUE);
+ rc = dixChangeWindowProperty(serverClient, pWin, realProperty,
+ XA_STRING, 8, PropModeReplace,
+ clientCutTextLen, clientCutText,
+ TRUE);
if (rc != Success)
return rc;
} else if (target == xaUTF8_STRING) {
@@ -279,8 +280,9 @@
}
}
- rc = ChangeWindowProperty(pWin, realProperty, xaUTF8_STRING, 8,
- PropModeReplace, len, buffer, TRUE);
+ rc = dixChangeWindowProperty(serverClient, pWin, realProperty,
+ xaUTF8_STRING, 8, PropModeReplace,
+ len, buffer, TRUE);
free(buffer);
if (rc != Success)
return rc;
diff --git a/unix/xserver/hw/vnc/xvnc.c b/unix/xserver/hw/vnc/xvnc.c
index c5b684d..ef30d69 100644
--- a/unix/xserver/hw/vnc/xvnc.c
+++ b/unix/xserver/hw/vnc/xvnc.c
@@ -572,9 +572,17 @@
if (strcmp(argv[i], "-inetd") == 0)
{
+ int nullfd;
+
dup2(0,3);
vncInetdSock = 3;
- close(2);
+
+ /* Avoid xserver >= 1.19's epoll-fd becoming fd 2 / stderr only to be
+ replaced by /dev/null by OsInit() because the pollfd is not
+ writable, breaking ospoll_wait(). */
+ nullfd = open("/dev/null", O_WRONLY);
+ dup2(nullfd, 2);
+ close(nullfd);
if (!displaySpecified) {
int port = vncGetSocketPort(vncInetdSock);
diff --git a/vncviewer/CConn.cxx b/vncviewer/CConn.cxx
index 140b1d6..addc30d 100644
--- a/vncviewer/CConn.cxx
+++ b/vncviewer/CConn.cxx
@@ -73,7 +73,7 @@
CConn::CConn(const char* vncServerName, network::Socket* socket=NULL)
: serverHost(0), serverPort(0), desktop(NULL),
- pendingPFChange(false),
+ frameCount(0), pixelCount(0), pendingPFChange(false),
currentEncoding(encodingTight), lastServerEncoding((unsigned int)-1),
formatChange(false), encodingChange(false),
firstUpdate(true), pendingUpdate(false), continuousUpdates(false),
@@ -223,6 +223,21 @@
return infoText;
}
+unsigned CConn::getFrameCount()
+{
+ return frameCount;
+}
+
+unsigned CConn::getPixelCount()
+{
+ return pixelCount;
+}
+
+unsigned CConn::getPosition()
+{
+ return sock->inStream().pos();
+}
+
// The RFB core is not properly asynchronous, so it calls this callback
// whenever it needs to block to wait for more data. Since FLTK is
// monitoring the socket, we just make sure FLTK gets to run.
@@ -254,6 +269,15 @@
// until the buffers are empty or things will stall.
do {
cc->processMsg();
+
+ // Make sure that the FLTK handling and the timers gets some CPU
+ // time in case of back to back messages
+ Fl::check();
+ Timer::checkTimeouts();
+
+ // Also check if we need to stop reading and terminate
+ if (should_exit())
+ break;
} while (cc->sock->inStream().checkNoWait(1));
} catch (rdr::EndOfStream& e) {
vlog.info("%s", e.str());
@@ -337,9 +361,6 @@
// one.
void CConn::framebufferUpdateStart()
{
- ModifiablePixelBuffer* pb;
- PlatformPixelBuffer* ppb;
-
CConnection::framebufferUpdateStart();
// Note: This might not be true if sync fences are supported
@@ -347,22 +368,6 @@
requestNewUpdate();
- // We might still be rendering the previous update
- pb = getFramebuffer();
- assert(pb != NULL);
- ppb = dynamic_cast<PlatformPixelBuffer*>(pb);
- assert(ppb != NULL);
- if (ppb->isRendering()) {
- // Need to stop monitoring the socket or we'll just busy loop
- assert(sock != NULL);
- Fl::remove_fd(sock->getFd());
-
- while (ppb->isRendering())
- run_mainloop();
-
- Fl::add_fd(sock->getFd(), FL_READ | FL_EXCEPT, socketEvent, this);
- }
-
// Update the screen prematurely for very slow updates
Fl::add_timeout(1.0, handleUpdateTimeout, this);
}
@@ -375,6 +380,8 @@
{
CConnection::framebufferUpdateEnd();
+ frameCount++;
+
Fl::remove_timeout(handleUpdateTimeout, this);
desktop->updateWindow();
@@ -397,11 +404,6 @@
// Compute new settings based on updated bandwidth values
if (autoSelect)
autoSelectFormatAndEncoding();
-
- // Make sure that the FLTK handling and the timers gets some CPU time
- // in case of back to back framebuffer updates.
- Fl::check();
- Timer::checkTimeouts();
}
// The rest of the callbacks are fairly self-explanatory...
@@ -456,12 +458,14 @@
CConnection::dataRect(r, encoding);
sock->inStream().stopTiming();
+
+ pixelCount += r.area();
}
void CConn::setCursor(int width, int height, const Point& hotspot,
- void* data, void* mask)
+ const rdr::U8* data)
{
- desktop->setCursor(width, height, hotspot, data, mask);
+ desktop->setCursor(width, height, hotspot, data);
}
void CConn::fence(rdr::U32 flags, unsigned len, const char data[])
diff --git a/vncviewer/CConn.h b/vncviewer/CConn.h
index c934f3d..93cc278 100644
--- a/vncviewer/CConn.h
+++ b/vncviewer/CConn.h
@@ -40,6 +40,10 @@
const char *connectionInfo();
+ unsigned getFrameCount();
+ unsigned getPixelCount();
+ unsigned getPosition();
+
// FdInStreamBlockCallback methods
void blockCallback();
@@ -66,7 +70,7 @@
void dataRect(const rfb::Rect& r, int encoding);
void setCursor(int width, int height, const rfb::Point& hotspot,
- void* data, void* mask);
+ const rdr::U8* data);
void fence(rdr::U32 flags, unsigned len, const char data[]);
@@ -89,6 +93,9 @@
DesktopWindow *desktop;
+ unsigned frameCount;
+ unsigned pixelCount;
+
rfb::PixelFormat serverPF;
rfb::PixelFormat fullColourPF;
diff --git a/vncviewer/CMakeLists.txt b/vncviewer/CMakeLists.txt
index f11bae3..1833e70 100644
--- a/vncviewer/CMakeLists.txt
+++ b/vncviewer/CMakeLists.txt
@@ -6,9 +6,9 @@
menukey.cxx
CConn.cxx
DesktopWindow.cxx
- FLTKPixelBuffer.cxx
UserDialog.cxx
ServerDialog.cxx
+ Surface.cxx
OptionsDialog.cxx
PlatformPixelBuffer.cxx
Viewport.cxx
@@ -33,11 +33,11 @@
endif()
if(WIN32)
- set(VNCVIEWER_SOURCES ${VNCVIEWER_SOURCES} Win32PixelBuffer.cxx)
+ set(VNCVIEWER_SOURCES ${VNCVIEWER_SOURCES} Surface_Win32.cxx)
elseif(APPLE)
- set(VNCVIEWER_SOURCES ${VNCVIEWER_SOURCES} OSXPixelBuffer.cxx)
+ set(VNCVIEWER_SOURCES ${VNCVIEWER_SOURCES} Surface_OSX.cxx)
else()
- set(VNCVIEWER_SOURCES ${VNCVIEWER_SOURCES} X11PixelBuffer.cxx)
+ set(VNCVIEWER_SOURCES ${VNCVIEWER_SOURCES} Surface_X11.cxx)
endif()
if(WIN32 AND NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
@@ -48,6 +48,10 @@
target_link_libraries(vncviewer rfb network rdr os ${FLTK_LIBRARIES} ${GETTEXT_LIBRARIES})
+if(WIN32)
+ target_link_libraries(vncviewer msimg32)
+endif()
+
if(APPLE)
target_link_libraries(vncviewer "-framework Cocoa" "-framework Carbon")
endif()
diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx
index 2787bee..1f0f55f 100644
--- a/vncviewer/DesktopWindow.cxx
+++ b/vncviewer/DesktopWindow.cxx
@@ -24,6 +24,7 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
+#include <sys/time.h>
#include <rfb/LogWriter.h>
#include <rfb/CMsgWriter.h>
@@ -34,10 +35,13 @@
#include "parameters.h"
#include "vncviewer.h"
#include "CConn.h"
+#include "Surface.h"
#include "Viewport.h"
#include <FL/Fl.H>
-#include <FL/Fl_Scroll.H>
+#include <FL/Fl_Image_Surface.H>
+#include <FL/Fl_Scrollbar.H>
+#include <FL/fl_draw.H>
#include <FL/x.H>
#ifdef WIN32
@@ -58,18 +62,29 @@
DesktopWindow::DesktopWindow(int w, int h, const char *name,
const rfb::PixelFormat& serverPF,
CConn* cc_)
- : Fl_Window(w, h), cc(cc_), firstUpdate(true),
- delayedFullscreen(false), delayedDesktopSize(false)
+ : Fl_Window(w, h), cc(cc_), offscreen(NULL), overlay(NULL),
+ firstUpdate(true),
+ delayedFullscreen(false), delayedDesktopSize(false),
+ statsLastFrame(0), statsLastPixels(0), statsLastPosition(0),
+ statsGraph(NULL)
{
- scroll = new Fl_Scroll(0, 0, w, h);
- scroll->color(FL_BLACK);
+ Fl_Group* group;
- // Automatically adjust the scroll box to the window
- resizable(scroll);
+ // Dummy group to prevent FLTK from moving our widgets around
+ group = new Fl_Group(0, 0, w, h);
+ group->resizable(NULL);
+ resizable(group);
viewport = new Viewport(w, h, serverPF, cc);
- scroll->end();
+ // Position will be adjusted later
+ hscroll = new Fl_Scrollbar(0, 0, 0, 0);
+ vscroll = new Fl_Scrollbar(0, 0, 0, 0);
+ hscroll->type(FL_HORIZONTAL);
+ hscroll->callback(handleScroll, this);
+ vscroll->callback(handleScroll, this);
+
+ group->end();
callback(handleClose, this);
@@ -150,11 +165,8 @@
}
#endif
- // The window manager might give us an initial window size that is different
- // than the one we requested, and in those cases we need to manually adjust
- // the scroll widget for things to behave sanely.
- if ((w != this->w()) || (h != this->h()))
- scroll->size(this->w(), this->h());
+ // Adjust layout now that we're visible and know our final size
+ repositionWidgets();
if (delayedFullscreen) {
// Hack: Fullscreen requests may be ignored, so we need a timeout for
@@ -163,6 +175,15 @@
Fl::add_timeout(0.5, handleFullscreenTimeout, this);
fullscreen_on();
}
+
+ // Throughput graph for debugging
+ if (vlog.getLevel() >= LogWriter::LEVEL_DEBUG) {
+ memset(&stats, 0, sizeof(stats));
+ Fl::add_timeout(0, handleStatsTimeout, this);
+ }
+
+ // Show hint about menu key
+ Fl::add_timeout(0.5, menuOverlay, this);
}
@@ -174,9 +195,17 @@
Fl::remove_timeout(handleResizeTimeout, this);
Fl::remove_timeout(handleFullscreenTimeout, this);
Fl::remove_timeout(handleEdgeScroll, this);
+ Fl::remove_timeout(handleStatsTimeout, this);
+ Fl::remove_timeout(menuOverlay, this);
+ Fl::remove_timeout(updateOverlay, this);
OptionsDialog::removeCallback(handleOptions);
+ delete overlay;
+ delete offscreen;
+
+ delete statsGraph;
+
// FLTK automatically deletes all child widgets, so we shouldn't touch
// them ourselves here
}
@@ -242,21 +271,123 @@
viewport->size(new_w, new_h);
- // We might not resize the main window, so we need to manually call this
- // to make sure the viewport is centered.
- repositionViewport();
-
- // repositionViewport() makes sure the scroll widget notices any changes
- // in position, but it might be just the size that changes so we also
- // need a poke here as well.
- redraw();
+ repositionWidgets();
}
-void DesktopWindow::setCursor(int width, int height, const Point& hotspot,
- void* data, void* mask)
+void DesktopWindow::setCursor(int width, int height,
+ const rfb::Point& hotspot,
+ const rdr::U8* data)
{
- viewport->setCursor(width, height, hotspot, data, mask);
+ viewport->setCursor(width, height, hotspot, data);
+}
+
+
+void DesktopWindow::draw()
+{
+ bool redraw;
+
+ int X, Y, W, H;
+
+ // X11 needs an off screen buffer for compositing to avoid flicker,
+ // and alpha blending doesn't work for windows on Win32
+#if !defined(__APPLE__)
+
+ // Adjust offscreen surface dimensions
+ if ((offscreen == NULL) ||
+ (offscreen->width() != w()) || (offscreen->height() != h())) {
+ delete offscreen;
+ offscreen = new Surface(w(), h());
+ }
+
+#endif
+
+ // Active area inside scrollbars
+ W = w() - (vscroll->visible() ? vscroll->w() : 0);
+ H = h() - (hscroll->visible() ? hscroll->h() : 0);
+
+ // Full redraw?
+ redraw = (damage() & ~FL_DAMAGE_CHILD);
+
+ // Simplify the clip region to a simple rectangle in order to
+ // properly draw all the layers even if they only partially overlap
+ if (redraw)
+ X = Y = 0;
+ else
+ fl_clip_box(0, 0, W, H, X, Y, W, H);
+ fl_push_no_clip();
+ fl_push_clip(X, Y, W, H);
+
+ // Redraw background only on full redraws
+ if (redraw) {
+ if (offscreen)
+ offscreen->clear(40, 40, 40);
+ else
+ fl_rectf(0, 0, W, H, 40, 40, 40);
+ }
+
+ if (offscreen) {
+ viewport->draw(offscreen);
+ viewport->clear_damage();
+ } else {
+ if (redraw)
+ draw_child(*viewport);
+ else
+ update_child(*viewport);
+ }
+
+ // Debug graph (if active)
+ if (statsGraph) {
+ int ox, oy, ow, oh;
+
+ ox = X = w() - statsGraph->width() - 30;
+ oy = Y = h() - statsGraph->height() - 30;
+ ow = statsGraph->width();
+ oh = statsGraph->height();
+
+ fl_clip_box(ox, oy, ow, oh, ox, oy, ow, oh);
+
+ if (offscreen)
+ statsGraph->blend(offscreen, ox - X, oy - Y, ox, oy, ow, oh, 204);
+ else
+ statsGraph->blend(ox - X, oy - Y, ox, oy, ow, oh, 204);
+ }
+
+ // Overlay (if active)
+ if (overlay) {
+ int ox, oy, ow, oh;
+
+ ox = X = (w() - overlay->width()) / 2;
+ oy = Y = 50;
+ ow = overlay->width();
+ oh = overlay->height();
+
+ fl_clip_box(ox, oy, ow, oh, ox, oy, ow, oh);
+
+ if (offscreen)
+ overlay->blend(offscreen, ox - X, oy - Y, ox, oy, ow, oh, overlayAlpha);
+ else
+ overlay->blend(ox - X, oy - Y, ox, oy, ow, oh, overlayAlpha);
+ }
+
+ // Flush offscreen surface to screen
+ if (offscreen) {
+ fl_clip_box(0, 0, w(), h(), X, Y, W, H);
+ offscreen->draw(X, Y, X, Y, W, H);
+ }
+
+ fl_pop_clip();
+ fl_pop_clip();
+
+ // Finally the scrollbars
+
+ if (redraw) {
+ draw_child(*hscroll);
+ draw_child(*vscroll);
+ } else {
+ update_child(*hscroll);
+ update_child(*vscroll);
+ }
}
@@ -334,27 +465,152 @@
Fl::add_timeout(0.5, handleResizeTimeout, this);
}
- // Deal with some scrolling corner cases
- repositionViewport();
+ repositionWidgets();
}
}
+void DesktopWindow::menuOverlay(void* data)
+{
+ DesktopWindow *self;
+
+ self = (DesktopWindow*)data;
+ self->setOverlay(_("Press %s to open the context menu"),
+ (const char*)menuKey);
+}
+
+void DesktopWindow::setOverlay(const char* text, ...)
+{
+ va_list ap;
+ char textbuf[1024];
+
+ Fl_Image_Surface *surface;
+
+ Fl_RGB_Image* imageText;
+ Fl_RGB_Image* image;
+
+ unsigned char* buffer;
+
+ int x, y;
+ int w, h;
+
+ unsigned char* a;
+ const unsigned char* b;
+
+ delete overlay;
+ Fl::remove_timeout(updateOverlay, this);
+
+ va_start(ap, text);
+ vsnprintf(textbuf, sizeof(textbuf), text, ap);
+ textbuf[sizeof(textbuf)-1] = '\0';
+ va_end(ap);
+
+#if !defined(WIN32) && !defined(__APPLE__)
+ // FLTK < 1.3.5 crashes if fl_gc is unset
+ if (!fl_gc)
+ fl_gc = XDefaultGC(fl_display, 0);
+#endif
+
+ fl_font(FL_HELVETICA, FL_NORMAL_SIZE * 2);
+ w = 0;
+ fl_measure(textbuf, w, h);
+
+ // Margins
+ w += 80;
+ h += 40;
+
+ surface = new Fl_Image_Surface(w, h);
+ surface->set_current();
+
+ fl_rectf(0, 0, w, h, 0, 0, 0);
+
+ fl_font(FL_HELVETICA, FL_NORMAL_SIZE * 2);
+ fl_color(FL_WHITE);
+ fl_draw(textbuf, 40, 20 + fl_height() - fl_descent());
+
+ imageText = surface->image();
+ delete surface;
+
+ Fl_Display_Device::display_device()->set_current();
+
+ buffer = new unsigned char[w * h * 4];
+ image = new Fl_RGB_Image(buffer, w, h, 4);
+
+ a = buffer;
+ for (x = 0;x < image->w() * image->h();x++) {
+ a[0] = a[1] = a[2] = 0x40;
+ a[3] = 0xcc;
+ a += 4;
+ }
+
+ a = buffer;
+ b = (const unsigned char*)imageText->data()[0];
+ for (y = 0;y < h;y++) {
+ for (x = 0;x < w;x++) {
+ unsigned char alpha;
+ alpha = *b;
+ a[0] = (unsigned)a[0] * (255 - alpha) / 255 + alpha;
+ a[1] = (unsigned)a[1] * (255 - alpha) / 255 + alpha;
+ a[2] = (unsigned)a[2] * (255 - alpha) / 255 + alpha;
+ a[3] = 255 - (255 - a[3]) * (255 - alpha) / 255;
+ a += 4;
+ b += imageText->d();
+ }
+ if (imageText->ld() != 0)
+ b += imageText->ld() - w * imageText->d();
+ }
+
+ delete imageText;
+
+ x = (this->w() - image->w()) / 2;
+ y = 50;
+ w = image->w();
+ h = image->h();
+
+ overlay = new Surface(image);
+ overlayAlpha = 0;
+ gettimeofday(&overlayStart, NULL);
+
+ delete image;
+
+ Fl::add_timeout(1.0/60, updateOverlay, this);
+}
+
+void DesktopWindow::updateOverlay(void *data)
+{
+ DesktopWindow *self;
+ unsigned elapsed;
+
+ self = (DesktopWindow*)data;
+
+ elapsed = msSince(&self->overlayStart);
+
+ if (elapsed < 500) {
+ self->overlayAlpha = (unsigned)255 * elapsed / 500;
+ Fl::add_timeout(1.0/60, updateOverlay, self);
+ } else if (elapsed < 3500) {
+ self->overlayAlpha = 255;
+ Fl::add_timeout(3.0, updateOverlay, self);
+ } else if (elapsed < 4000) {
+ self->overlayAlpha = (unsigned)255 * (4000 - elapsed) / 500;
+ Fl::add_timeout(1.0/60, updateOverlay, self);
+ } else {
+ delete self->overlay;
+ self->overlay = NULL;
+ }
+
+ self->damage(FL_DAMAGE_USER1);
+}
+
+
int DesktopWindow::handle(int event)
{
switch (event) {
case FL_FULLSCREEN:
fullScreen.setParam(fullscreen_active());
- if (fullscreen_active())
- scroll->type(0);
- else
- scroll->type(Fl_Scroll::BOTH);
-
- // The scroll widget isn't clever enough to actually redraw the
- // scroll bars when they are added/removed, so we need to give
- // it a push.
- scroll->redraw();
+ // Update scroll bars
+ repositionWidgets();
if (!fullscreenSystemKeys)
break;
@@ -667,7 +923,7 @@
int i;
rdr::U32 id;
int sx, sy, sw, sh;
- Rect viewport_rect, screen_rect;
+ rfb::Rect viewport_rect, screen_rect;
// In full screen we report all screens that are fully covered.
@@ -751,17 +1007,11 @@
}
-void DesktopWindow::repositionViewport()
+void DesktopWindow::repositionWidgets()
{
int new_x, new_y;
- // Deal with some scrolling corner cases:
- //
- // a) If the window is larger then the viewport, center the viewport.
- // b) If the window is smaller than the viewport, make sure there is
- // no wasted space on the sides.
- //
- // FIXME: Doesn't compensate for scroll widget size properly.
+ // Viewport position
new_x = viewport->x();
new_y = viewport->y();
@@ -787,11 +1037,40 @@
if ((new_x != viewport->x()) || (new_y != viewport->y())) {
viewport->position(new_x, new_y);
-
- // The scroll widget does not notice when you move around child widgets,
- // so redraw everything to make sure things update.
- redraw();
+ damage(FL_DAMAGE_SCROLL);
}
+
+ // Scrollbars visbility
+
+ if (!fullscreen_active() && (w() < viewport->w()))
+ hscroll->show();
+ else
+ hscroll->hide();
+
+ if (!fullscreen_active() && (h() < viewport->h()))
+ vscroll->show();
+ else
+ vscroll->hide();
+
+ // Scrollbars positions
+
+ hscroll->resize(0, h() - Fl::scrollbar_size(),
+ w() - (vscroll->visible() ? Fl::scrollbar_size() : 0),
+ Fl::scrollbar_size());
+ vscroll->resize(w() - Fl::scrollbar_size(), 0,
+ Fl::scrollbar_size(),
+ h() - (hscroll->visible() ? Fl::scrollbar_size() : 0));
+
+ // Scrollbars range
+
+ hscroll->value(-viewport->x(),
+ w() - (vscroll->visible() ? vscroll->w() : 0),
+ 0, viewport->w());
+ vscroll->value(-viewport->y(),
+ h() - (hscroll->visible() ? hscroll->h() : 0),
+ 0, viewport->h());
+ hscroll->value(hscroll->clamp(hscroll->value()));
+ vscroll->value(vscroll->clamp(vscroll->value()));
}
void DesktopWindow::handleClose(Fl_Widget *wnd, void *data)
@@ -829,6 +1108,38 @@
}
}
+void DesktopWindow::scrollTo(int x, int y)
+{
+ x = hscroll->clamp(x);
+ y = vscroll->clamp(y);
+
+ hscroll->value(x);
+ vscroll->value(y);
+
+ if (!hscroll->visible())
+ x = -viewport->x();
+ if (!vscroll->visible())
+ y = -viewport->y();
+
+ // Scrollbar position results in inverse movement of
+ // the viewport widget
+ x = -x;
+ y = -y;
+
+ if ((viewport->x() == x) && (viewport->y() == y))
+ return;
+
+ viewport->position(x, y);
+ damage(FL_DAMAGE_SCROLL);
+}
+
+void DesktopWindow::handleScroll(Fl_Widget *widget, void *data)
+{
+ DesktopWindow *self = (DesktopWindow *)data;
+
+ self->scrollTo(self->hscroll->value(), self->vscroll->value());
+}
+
void DesktopWindow::handleEdgeScroll(void *data)
{
DesktopWindow *self = (DesktopWindow *)data;
@@ -874,17 +1185,132 @@
if ((dx == 0) && (dy == 0))
return;
- // Make sure we don't move the viewport too much
- if (self->viewport->x() + dx > 0)
- dx = -self->viewport->x();
- if (self->viewport->x() + dx + self->viewport->w() < self->w())
- dx = self->w() - (self->viewport->x() + self->viewport->w());
- if (self->viewport->y() + dy > 0)
- dy = -self->viewport->y();
- if (self->viewport->y() + dy + self->viewport->h() < self->h())
- dy = self->h() - (self->viewport->y() + self->viewport->h());
-
- self->scroll->scroll_to(self->scroll->xposition() - dx, self->scroll->yposition() - dy);
+ self->scrollTo(self->hscroll->value() + dx, self->vscroll->value() + dy);
Fl::repeat_timeout(0.1, handleEdgeScroll, data);
}
+
+void DesktopWindow::handleStatsTimeout(void *data)
+{
+ DesktopWindow *self = (DesktopWindow*)data;
+
+ const size_t statsCount = sizeof(stats)/sizeof(stats[0]);
+
+ unsigned frame, pixels, pos;
+ unsigned elapsed;
+
+ const unsigned statsWidth = 200;
+ const unsigned statsHeight = 100;
+ const unsigned graphWidth = statsWidth - 10;
+ const unsigned graphHeight = statsHeight - 25;
+
+ Fl_Image_Surface *surface;
+ Fl_RGB_Image *image;
+
+ unsigned maxFPS, maxPPS, maxBPS;
+ size_t i;
+
+ char buffer[256];
+
+ frame = self->cc->getFrameCount();
+ pixels = self->cc->getPixelCount();
+ pos = self->cc->getPosition();
+ elapsed = msSince(&self->statsLastTime);
+ if (elapsed < 1)
+ elapsed = 1;
+
+ memmove(&self->stats[0], &self->stats[1], sizeof(stats[0])*(statsCount-1));
+
+ self->stats[statsCount-1].fps = (frame - self->statsLastFrame) * 1000 / elapsed;
+ self->stats[statsCount-1].pps = (pixels - self->statsLastPixels) * 1000 / elapsed;
+ self->stats[statsCount-1].bps = (pos - self->statsLastPosition) * 1000 / elapsed;
+
+ gettimeofday(&self->statsLastTime, NULL);
+ self->statsLastFrame = frame;
+ self->statsLastPixels = pixels;
+ self->statsLastPosition = pos;
+
+#if !defined(WIN32) && !defined(__APPLE__)
+ // FLTK < 1.3.5 crashes if fl_gc is unset
+ if (!fl_gc)
+ fl_gc = XDefaultGC(fl_display, 0);
+#endif
+
+ surface = new Fl_Image_Surface(statsWidth, statsHeight);
+ surface->set_current();
+
+ fl_rectf(0, 0, statsWidth, statsHeight, FL_BLACK);
+
+ fl_rect(5, 5, graphWidth, graphHeight, FL_WHITE);
+
+ maxFPS = maxPPS = maxBPS = 0;
+ for (i = 0;i < statsCount;i++) {
+ if (self->stats[i].fps > maxFPS)
+ maxFPS = self->stats[i].fps;
+ if (self->stats[i].pps > maxPPS)
+ maxPPS = self->stats[i].pps;
+ if (self->stats[i].bps > maxBPS)
+ maxBPS = self->stats[i].bps;
+ }
+
+ if (maxFPS != 0) {
+ fl_color(FL_GREEN);
+ for (i = 0;i < statsCount-1;i++) {
+ fl_line(5 + i * graphWidth / statsCount,
+ 5 + graphHeight - graphHeight * self->stats[i].fps / maxFPS,
+ 5 + (i+1) * graphWidth / statsCount,
+ 5 + graphHeight - graphHeight * self->stats[i+1].fps / maxFPS);
+ }
+ }
+
+ if (maxPPS != 0) {
+ fl_color(FL_YELLOW);
+ for (i = 0;i < statsCount-1;i++) {
+ fl_line(5 + i * graphWidth / statsCount,
+ 5 + graphHeight - graphHeight * self->stats[i].pps / maxPPS,
+ 5 + (i+1) * graphWidth / statsCount,
+ 5 + graphHeight - graphHeight * self->stats[i+1].pps / maxPPS);
+ }
+ }
+
+ if (maxBPS != 0) {
+ fl_color(FL_RED);
+ for (i = 0;i < statsCount-1;i++) {
+ fl_line(5 + i * graphWidth / statsCount,
+ 5 + graphHeight - graphHeight * self->stats[i].bps / maxBPS,
+ 5 + (i+1) * graphWidth / statsCount,
+ 5 + graphHeight - graphHeight * self->stats[i+1].bps / maxBPS);
+ }
+ }
+
+ fl_font(FL_HELVETICA, 10);
+
+ fl_color(FL_GREEN);
+ snprintf(buffer, sizeof(buffer), "%u fps", self->stats[statsCount-1].fps);
+ fl_draw(buffer, 5, statsHeight - 5);
+
+ fl_color(FL_YELLOW);
+ siPrefix(self->stats[statsCount-1].pps * 8, "pix/s",
+ buffer, sizeof(buffer), 3);
+ fl_draw(buffer, 5 + (statsWidth-10)/3, statsHeight - 5);
+
+ fl_color(FL_RED);
+ iecPrefix(self->stats[statsCount-1].bps * 8, "Bps",
+ buffer, sizeof(buffer), 3);
+ fl_draw(buffer, 5 + (statsWidth-10)*2/3, statsHeight - 5);
+
+ image = surface->image();
+ delete surface;
+
+ Fl_Display_Device::display_device()->set_current();
+
+ delete self->statsGraph;
+ self->statsGraph = new Surface(image);
+ delete image;
+
+ self->damage(FL_DAMAGE_CHILD, self->w() - statsWidth - 30,
+ self->h() - statsHeight - 30,
+ statsWidth, statsHeight);
+
+ Fl::repeat_timeout(0.5, handleStatsTimeout, data);
+}
diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h
index d755b6d..4224699 100644
--- a/vncviewer/DesktopWindow.h
+++ b/vncviewer/DesktopWindow.h
@@ -22,6 +22,8 @@
#include <map>
+#include <sys/time.h>
+
#include <rfb/Rect.h>
#include <rfb/Pixel.h>
@@ -30,9 +32,16 @@
namespace rfb { class ModifiablePixelBuffer; }
class CConn;
+class Surface;
class Viewport;
-class Fl_Scroll;
+class Fl_Scrollbar;
+
+#ifdef __GNUC__
+# define __printf_attr(a, b) __attribute__((__format__ (__printf__, a, b)))
+#else
+# define __printf_attr(a, b)
+#endif // __GNUC__
class DesktopWindow : public Fl_Window {
public:
@@ -55,9 +64,10 @@
// New image for the locally rendered cursor
void setCursor(int width, int height, const rfb::Point& hotspot,
- void* data, void* mask);
+ const rdr::U8* data);
// Fl_Window callback methods
+ void draw();
void resize(int x, int y, int w, int h);
int handle(int event);
@@ -65,6 +75,11 @@
void fullscreen_on();
private:
+ static void menuOverlay(void *data);
+
+ void setOverlay(const char *text, ...) __printf_attr(2, 3);
+ static void updateOverlay(void *data);
+
static int fltkHandle(int event, Fl_Window *win);
void grabKeyboard();
@@ -78,7 +93,7 @@
static void handleResizeTimeout(void *data);
void remoteResize(int width, int height);
- void repositionViewport();
+ void repositionWidgets();
static void handleClose(Fl_Widget *wnd, void *data);
@@ -86,16 +101,38 @@
static void handleFullscreenTimeout(void *data);
+ void scrollTo(int x, int y);
+ static void handleScroll(Fl_Widget *wnd, void *data);
static void handleEdgeScroll(void *data);
+ static void handleStatsTimeout(void *data);
+
private:
CConn* cc;
- Fl_Scroll *scroll;
+ Fl_Scrollbar *hscroll, *vscroll;
Viewport *viewport;
+ Surface *offscreen;
+ Surface *overlay;
+ unsigned char overlayAlpha;
+ struct timeval overlayStart;
bool firstUpdate;
bool delayedFullscreen;
bool delayedDesktopSize;
+
+ struct statsEntry {
+ unsigned fps;
+ unsigned pps;
+ unsigned bps;
+ };
+ struct statsEntry stats[100];
+
+ struct timeval statsLastTime;
+ unsigned statsLastFrame;
+ unsigned statsLastPixels;
+ unsigned statsLastPosition;
+
+ Surface *statsGraph;
};
#endif
diff --git a/vncviewer/OSXPixelBuffer.cxx b/vncviewer/OSXPixelBuffer.cxx
deleted file mode 100644
index e9100f2..0000000
--- a/vncviewer/OSXPixelBuffer.cxx
+++ /dev/null
@@ -1,111 +0,0 @@
-/* Copyright 2011-2014 Pierre Ossman for Cendio AB
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this software; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
- * USA.
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <ApplicationServices/ApplicationServices.h>
-
-#include <FL/Fl_Window.H>
-#include <FL/x.H>
-
-#include <rfb/LogWriter.h>
-#include <rfb/Exception.h>
-
-#include "i18n.h"
-#include "OSXPixelBuffer.h"
-
-using namespace rfb;
-
-static rfb::LogWriter vlog("OSXPixelBuffer");
-
-OSXPixelBuffer::OSXPixelBuffer(int width, int height) :
- PlatformPixelBuffer(rfb::PixelFormat(32, 24, false, true,
- 255, 255, 255, 16, 8, 0),
- width, height, NULL, width),
- bitmap(NULL)
-{
- CGColorSpaceRef lut;
-
- data = new rdr::U8[width * height * format.bpp/8];
- if (data == NULL)
- throw rfb::Exception(_("Not enough memory for framebuffer"));
-
- lut = CGColorSpaceCreateDeviceRGB();
- if (!lut)
- throw rfb::Exception(_("Could not create framebuffer device"));
-
- bitmap = CGBitmapContextCreate(data, width, height, 8, width*4, lut,
- kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little);
- CGColorSpaceRelease(lut);
- if (!bitmap)
- throw rfb::Exception(_("Could not create framebuffer bitmap"));
-}
-
-
-OSXPixelBuffer::~OSXPixelBuffer()
-{
- CFRelease((CGContextRef)bitmap);
- delete [] data;
-}
-
-
-void OSXPixelBuffer::draw(int src_x, int src_y, int x, int y, int w, int h)
-{
- CGRect rect;
- CGContextRef gc;
- CGAffineTransform at;
- CGImageRef image;
-
- gc = (CGContextRef)fl_gc;
-
- CGContextSaveGState(gc);
-
- // We have to use clipping to partially display an image
-
- rect.origin.x = x - 0.5;
- rect.origin.y = y - 0.5;
- rect.size.width = w;
- rect.size.height = h;
-
- CGContextClipToRect(gc, rect);
-
- // Oh the hackiness that is OS X image handling...
- // The CGContextDrawImage() routine magically flips the images and offsets
- // them by 0.5,0.5 in order to compensate for OS X unfamiliar coordinate
- // system. But this breaks horribly when you set up the CTM to get the
- // more familiar top-down system (which FLTK does), meaning we have to
- // reset the CTM back to the identity matrix and calculate new origin
- // coordinates.
-
- at = CGContextGetCTM(gc);
- CGContextScaleCTM(gc, 1, -1);
- CGContextTranslateCTM(gc, -at.tx, -at.ty);
-
- rect.origin.x = x - src_x;
- rect.origin.y = Fl_Window::current()->h() - (y - src_y);
- rect.size.width = width();
- rect.size.height = -height(); // Negative height does _not_ flip the image
-
- image = CGBitmapContextCreateImage((CGContextRef)bitmap);
- CGContextDrawImage(gc, rect, image);
- CGImageRelease(image);
-
- CGContextRestoreGState(gc);
-}
diff --git a/vncviewer/OSXPixelBuffer.h b/vncviewer/OSXPixelBuffer.h
deleted file mode 100644
index 8ae0184..0000000
--- a/vncviewer/OSXPixelBuffer.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/* Copyright 2011-2014 Pierre Ossman for Cendio AB
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this software; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
- * USA.
- */
-
-#ifndef __OSXPIXELBUFFER_H__
-#define __OSXPIXELBUFFER_H__
-
-#include "PlatformPixelBuffer.h"
-
-class OSXPixelBuffer: public PlatformPixelBuffer {
-public:
- OSXPixelBuffer(int width, int height);
- ~OSXPixelBuffer();
-
- virtual void draw(int src_x, int src_y, int x, int y, int w, int h);
-
-protected:
- // This is really a CGContextRef, but Apple headers conflict with FLTK
- void *bitmap;
-};
-
-
-#endif
diff --git a/vncviewer/OptionsDialog.cxx b/vncviewer/OptionsDialog.cxx
index 2f9df9c..b018c95 100644
--- a/vncviewer/OptionsDialog.cxx
+++ b/vncviewer/OptionsDialog.cxx
@@ -700,6 +700,7 @@
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Accept clipboard from server")));
+ acceptClipboardCheckbox->callback(handleClipboard, this);
ty += CHECK_HEIGHT + TIGHT_MARGIN;
#if !defined(WIN32) && !defined(__APPLE__)
@@ -714,10 +715,11 @@
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Send clipboard to server")));
+ sendClipboardCheckbox->callback(handleClipboard, this);
ty += CHECK_HEIGHT + TIGHT_MARGIN;
#if !defined(WIN32) && !defined(__APPLE__)
- sendPrimaryCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty,
+ sendPrimaryCheckbox = new Fl_Check_Button(LBLRIGHT(tx + INDENT, ty,
CHECK_MIN_WIDTH,
CHECK_HEIGHT,
_("Send primary selection as clipboard")));
@@ -876,6 +878,22 @@
}
}
+void OptionsDialog::handleClipboard(Fl_Widget *widget, void *data)
+{
+#if !defined(WIN32) && !defined(__APPLE__)
+ OptionsDialog *dialog = (OptionsDialog*)data;
+
+ if (dialog->acceptClipboardCheckbox->value())
+ dialog->setPrimaryCheckbox->activate();
+ else
+ dialog->setPrimaryCheckbox->deactivate();
+ if (dialog->sendClipboardCheckbox->value())
+ dialog->sendPrimaryCheckbox->activate();
+ else
+ dialog->sendPrimaryCheckbox->deactivate();
+#endif
+}
+
void OptionsDialog::handleCancel(Fl_Widget *widget, void *data)
{
OptionsDialog *dialog = (OptionsDialog*)data;
diff --git a/vncviewer/OptionsDialog.h b/vncviewer/OptionsDialog.h
index 6984c72..c177560 100644
--- a/vncviewer/OptionsDialog.h
+++ b/vncviewer/OptionsDialog.h
@@ -64,6 +64,8 @@
static void handleDesktopSize(Fl_Widget *widget, void *data);
+ static void handleClipboard(Fl_Widget *widget, void *data);
+
static void handleCancel(Fl_Widget *widget, void *data);
static void handleOK(Fl_Widget *widget, void *data);
diff --git a/vncviewer/PlatformPixelBuffer.cxx b/vncviewer/PlatformPixelBuffer.cxx
index 876ab94..4802ba4 100644
--- a/vncviewer/PlatformPixelBuffer.cxx
+++ b/vncviewer/PlatformPixelBuffer.cxx
@@ -1,4 +1,4 @@
-/* Copyright 2011-2014 Pierre Ossman for Cendio AB
+/* Copyright 2011-2016 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,13 +16,70 @@
* USA.
*/
+#include <assert.h>
+
+#if !defined(WIN32) && !defined(__APPLE__)
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#endif
+
+#include <FL/Fl.H>
+#include <FL/x.H>
+
+#include <rfb/LogWriter.h>
+#include <rdr/Exception.h>
+
#include "PlatformPixelBuffer.h"
-PlatformPixelBuffer::PlatformPixelBuffer(const rfb::PixelFormat& pf,
- int width, int height,
- rdr::U8* data, int stride) :
- FullFramePixelBuffer(pf, width, height, data, stride)
+static rfb::LogWriter vlog("PlatformPixelBuffer");
+
+PlatformPixelBuffer::PlatformPixelBuffer(int width, int height) :
+ FullFramePixelBuffer(rfb::PixelFormat(32, 24, false, true,
+ 255, 255, 255, 16, 8, 0),
+ width, height, 0, stride),
+ Surface(width, height)
+#if !defined(WIN32) && !defined(__APPLE__)
+ , shminfo(NULL), xim(NULL)
+#endif
{
+#if !defined(WIN32) && !defined(__APPLE__)
+ if (!setupShm()) {
+ xim = XCreateImage(fl_display, CopyFromParent, 32,
+ ZPixmap, 0, 0, width, height, 32, 0);
+ if (!xim)
+ throw rdr::Exception("XCreateImage");
+
+ xim->data = (char*)malloc(xim->bytes_per_line * xim->height);
+ if (!xim->data)
+ throw rdr::Exception("malloc");
+
+ vlog.debug("Using standard XImage");
+ }
+
+ data = (rdr::U8*)xim->data;
+ stride = xim->bytes_per_line / (getPF().bpp/8);
+#else
+ FullFramePixelBuffer::data = (rdr::U8*)Surface::data;
+ stride = width;
+#endif
+}
+
+PlatformPixelBuffer::~PlatformPixelBuffer()
+{
+#if !defined(WIN32) && !defined(__APPLE__)
+ if (shminfo) {
+ vlog.debug("Freeing shared memory XImage");
+ shmdt(shminfo->shmaddr);
+ shmctl(shminfo->shmid, IPC_RMID, 0);
+ delete shminfo;
+ shminfo = NULL;
+ }
+
+ // XDestroyImage() will free(xim->data) if appropriate
+ if (xim)
+ XDestroyImage(xim);
+ xim = NULL;
+#endif
}
void PlatformPixelBuffer::commitBufferRW(const rfb::Rect& r)
@@ -42,10 +99,104 @@
damage.clear();
mutex.unlock();
+#if !defined(WIN32) && !defined(__APPLE__)
+ GC gc;
+
+ gc = XCreateGC(fl_display, pixmap, 0, NULL);
+ if (shminfo) {
+ XShmPutImage(fl_display, pixmap, gc, xim,
+ r.tl.x, r.tl.y, r.tl.x, r.tl.y,
+ r.width(), r.height(), False);
+ // Need to make sure the X server has finished reading the
+ // shared memory before we return
+ XSync(fl_display, False);
+ } else {
+ XPutImage(fl_display, pixmap, gc, xim,
+ r.tl.x, r.tl.y, r.tl.x, r.tl.y, r.width(), r.height());
+ }
+ XFreeGC(fl_display, gc);
+#endif
+
return r;
}
-bool PlatformPixelBuffer::isRendering(void)
+#if !defined(WIN32) && !defined(__APPLE__)
+
+static bool caughtError;
+
+static int XShmAttachErrorHandler(Display *dpy, XErrorEvent *error)
{
- return false;
+ caughtError = true;
+ return 0;
}
+
+bool PlatformPixelBuffer::setupShm()
+{
+ int major, minor;
+ Bool pixmaps;
+ XErrorHandler old_handler;
+ const char *display_name = XDisplayName (NULL);
+
+ /* Don't use MIT-SHM on remote displays */
+ if (*display_name && *display_name != ':')
+ return false;
+
+ if (!XShmQueryVersion(fl_display, &major, &minor, &pixmaps))
+ return false;
+
+ shminfo = new XShmSegmentInfo;
+
+ xim = XShmCreateImage(fl_display, CopyFromParent, 32,
+ ZPixmap, 0, shminfo, width(), height());
+ if (!xim)
+ goto free_shminfo;
+
+ shminfo->shmid = shmget(IPC_PRIVATE,
+ xim->bytes_per_line * xim->height,
+ IPC_CREAT|0777);
+ if (shminfo->shmid == -1)
+ goto free_xim;
+
+ shminfo->shmaddr = xim->data = (char*)shmat(shminfo->shmid, 0, 0);
+ shmctl(shminfo->shmid, IPC_RMID, 0); // to avoid memory leakage
+ if (shminfo->shmaddr == (char *)-1)
+ goto free_xim;
+
+ shminfo->readOnly = True;
+
+ // This is the only way we can detect that shared memory won't work
+ // (e.g. because we're accessing a remote X11 server)
+ caughtError = false;
+ old_handler = XSetErrorHandler(XShmAttachErrorHandler);
+
+ if (!XShmAttach(fl_display, shminfo)) {
+ XSetErrorHandler(old_handler);
+ goto free_shmaddr;
+ }
+
+ XSync(fl_display, False);
+
+ XSetErrorHandler(old_handler);
+
+ if (caughtError)
+ goto free_shmaddr;
+
+ vlog.debug("Using shared memory XImage");
+
+ return true;
+
+free_shmaddr:
+ shmdt(shminfo->shmaddr);
+
+free_xim:
+ XDestroyImage(xim);
+ xim = NULL;
+
+free_shminfo:
+ delete shminfo;
+ shminfo = NULL;
+
+ return 0;
+}
+
+#endif
diff --git a/vncviewer/PlatformPixelBuffer.h b/vncviewer/PlatformPixelBuffer.h
index 9f0e3b1..f9038cd 100644
--- a/vncviewer/PlatformPixelBuffer.h
+++ b/vncviewer/PlatformPixelBuffer.h
@@ -1,4 +1,4 @@
-/* Copyright 2011-2014 Pierre Ossman for Cendio AB
+/* Copyright 2011-2016 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,26 +19,46 @@
#ifndef __PLATFORMPIXELBUFFER_H__
#define __PLATFORMPIXELBUFFER_H__
+#if !defined(WIN32) && !defined(__APPLE__)
+#include <X11/Xlib.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <X11/extensions/XShm.h>
+#endif
+
+#include <list>
+
#include <os/Mutex.h>
#include <rfb/PixelBuffer.h>
#include <rfb/Region.h>
-class PlatformPixelBuffer: public rfb::FullFramePixelBuffer {
+#include "Surface.h"
+
+class PlatformPixelBuffer: public rfb::FullFramePixelBuffer, public Surface {
public:
- PlatformPixelBuffer(const rfb::PixelFormat& pf, int width, int height,
- rdr::U8* data, int stride);
+ PlatformPixelBuffer(int width, int height);
+ ~PlatformPixelBuffer();
virtual void commitBufferRW(const rfb::Rect& r);
- virtual void draw(int src_x, int src_y, int x, int y, int w, int h) = 0;
rfb::Rect getDamage(void);
- virtual bool isRendering(void);
+ using rfb::FullFramePixelBuffer::width;
+ using rfb::FullFramePixelBuffer::height;
protected:
os::Mutex mutex;
rfb::Region damage;
+
+#if !defined(WIN32) && !defined(__APPLE__)
+protected:
+ bool setupShm();
+
+protected:
+ XShmSegmentInfo *shminfo;
+ XImage *xim;
+#endif
};
#endif
diff --git a/vncviewer/Win32PixelBuffer.h b/vncviewer/Surface.cxx
similarity index 63%
rename from vncviewer/Win32PixelBuffer.h
rename to vncviewer/Surface.cxx
index 728e594..fbd4143 100644
--- a/vncviewer/Win32PixelBuffer.h
+++ b/vncviewer/Surface.cxx
@@ -1,4 +1,4 @@
-/* Copyright 2011-2014 Pierre Ossman for Cendio AB
+/* Copyright 2016 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,23 +16,24 @@
* USA.
*/
-#ifndef __WIN32PIXELBUFFER_H__
-#define __WIN32PIXELBUFFER_H__
+#include <FL/Fl_RGB_Image.H>
-#include <windows.h>
+#include "Surface.h"
-#include "PlatformPixelBuffer.h"
+Surface::Surface(int width, int height) :
+ w(width), h(height)
+{
+ alloc();
+}
-class Win32PixelBuffer: public PlatformPixelBuffer {
-public:
- Win32PixelBuffer(int width, int height);
- ~Win32PixelBuffer();
+Surface::Surface(const Fl_RGB_Image* image) :
+ w(image->w()), h(image->h())
+{
+ alloc();
+ update(image);
+}
- virtual void draw(int src_x, int src_y, int x, int y, int w, int h);
-
-protected:
- HBITMAP bitmap;
-};
-
-
-#endif
+Surface::~Surface()
+{
+ dealloc();
+}
diff --git a/vncviewer/Surface.h b/vncviewer/Surface.h
new file mode 100644
index 0000000..032889b
--- /dev/null
+++ b/vncviewer/Surface.h
@@ -0,0 +1,72 @@
+/* Copyright 2016 Pierre Ossman for Cendio AB
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#ifndef __SURFACE_H__
+#define __SURFACE_H__
+
+#if defined(WIN32)
+#include <windows.h>
+#elif defined(__APPLE__)
+// Apple headers conflict with FLTK, so redefine types here
+typedef struct CGImage* CGImageRef;
+#else
+#include <X11/extensions/Xrender.h>
+#endif
+
+class Fl_RGB_Image;
+
+class Surface {
+public:
+ Surface(int width, int height);
+ Surface(const Fl_RGB_Image* image);
+ ~Surface();
+
+ int width() { return w; }
+ int height() { return h; }
+
+ void clear(unsigned char r, unsigned char g, unsigned char b, unsigned char a=255);
+
+ void draw(int src_x, int src_y, int x, int y, int w, int h);
+ void draw(Surface* dst, int src_x, int src_y, int x, int y, int w, int h);
+
+ void blend(int src_x, int src_y, int x, int y, int w, int h, int a=255);
+ void blend(Surface* dst, int src_x, int src_y, int x, int y, int w, int h, int a=255);
+
+protected:
+ void alloc();
+ void dealloc();
+ void update(const Fl_RGB_Image* image);
+
+protected:
+ int w, h;
+
+#if defined(WIN32)
+ RGBQUAD* data;
+ HBITMAP bitmap;
+#elif defined(__APPLE__)
+ unsigned char* data;
+ CGImageRef image;
+#else
+ Pixmap pixmap;
+ Picture picture;
+ XRenderPictFormat* visFormat;
+#endif
+};
+
+#endif
+
diff --git a/vncviewer/Surface_OSX.cxx b/vncviewer/Surface_OSX.cxx
new file mode 100644
index 0000000..2dfaa47
--- /dev/null
+++ b/vncviewer/Surface_OSX.cxx
@@ -0,0 +1,249 @@
+/* Copyright 2016 Pierre Ossman for Cendio AB
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#include <assert.h>
+
+#include <ApplicationServices/ApplicationServices.h>
+
+#include <FL/Fl_RGB_Image.H>
+#include <FL/Fl_Window.H>
+#include <FL/x.H>
+
+#include <rdr/Exception.h>
+
+#include "Surface.h"
+
+static void render(CGContextRef gc, CGImageRef image,
+ CGBlendMode mode, CGFloat alpha,
+ int src_x, int src_y, int src_w, int src_h,
+ int x, int y, int w, int h)
+{
+ CGRect rect;
+
+ CGContextSaveGState(gc);
+
+ CGContextSetBlendMode(gc, mode);
+ CGContextSetAlpha(gc, alpha);
+
+ // We have to use clipping to partially display an image
+ rect.origin.x = x;
+ rect.origin.y = y;
+ rect.size.width = w;
+ rect.size.height = h;
+
+ CGContextClipToRect(gc, rect);
+
+ rect.origin.x = x - src_x;
+ rect.origin.y = y - src_y;
+ rect.size.width = src_w;
+ rect.size.height = src_h;
+
+ CGContextDrawImage(gc, rect, image);
+
+ CGContextRestoreGState(gc);
+}
+
+static CGContextRef make_bitmap(int width, int height, unsigned char* data)
+{
+ CGColorSpaceRef lut;
+ CGContextRef bitmap;
+
+ lut = CGDisplayCopyColorSpace(kCGDirectMainDisplay);
+ if (!lut) {
+ lut = CGColorSpaceCreateDeviceRGB();
+ if (!lut)
+ throw rdr::Exception("CGColorSpaceCreateDeviceRGB");
+ }
+
+ bitmap = CGBitmapContextCreate(data, width, height, 8, width*4, lut,
+ kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
+ CGColorSpaceRelease(lut);
+ if (!bitmap)
+ throw rdr::Exception("CGBitmapContextCreate");
+
+ return bitmap;
+}
+
+void Surface::clear(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+ unsigned char* out;
+ int x, y;
+
+ r = (unsigned)r * a / 255;
+ g = (unsigned)g * a / 255;
+ b = (unsigned)b * a / 255;
+
+ out = data;
+ for (y = 0;y < width();y++) {
+ for (x = 0;x < height();x++) {
+ *out++ = b;
+ *out++ = g;
+ *out++ = r;
+ *out++ = a;
+ }
+ }
+}
+
+void Surface::draw(int src_x, int src_y, int x, int y, int w, int h)
+{
+ CGContextSaveGState(fl_gc);
+
+ // Reset the transformation matrix back to the default identity
+ // matrix as otherwise we get a massive performance hit
+ CGContextConcatCTM(fl_gc, CGAffineTransformInvert(CGContextGetCTM(fl_gc)));
+
+ // macOS Coordinates are from bottom left, not top left
+ src_y = height() - (src_y + h);
+ y = Fl_Window::current()->h() - (y + h);
+
+ render(fl_gc, image, kCGBlendModeCopy, 1.0,
+ src_x, src_y, width(), height(), x, y, w, h);
+
+ CGContextRestoreGState(fl_gc);
+}
+
+void Surface::draw(Surface* dst, int src_x, int src_y, int x, int y, int w, int h)
+{
+ CGContextRef bitmap;
+
+ bitmap = make_bitmap(dst->width(), dst->height(), dst->data);
+
+ // macOS Coordinates are from bottom left, not top left
+ src_y = height() - (src_y + h);
+ y = dst->height() - (y + h);
+
+ render(bitmap, image, kCGBlendModeCopy, 1.0,
+ src_x, src_y, width(), height(), x, y, w, h);
+
+ CGContextRelease(bitmap);
+}
+
+void Surface::blend(int src_x, int src_y, int x, int y, int w, int h, int a)
+{
+ CGContextSaveGState(fl_gc);
+
+ // Reset the transformation matrix back to the default identity
+ // matrix as otherwise we get a massive performance hit
+ CGContextConcatCTM(fl_gc, CGAffineTransformInvert(CGContextGetCTM(fl_gc)));
+
+ // macOS Coordinates are from bottom left, not top left
+ src_y = height() - (src_y + h);
+ y = Fl_Window::current()->h() - (y + h);
+
+ render(fl_gc, image, kCGBlendModeNormal, (CGFloat)a/255.0,
+ src_x, src_y, width(), height(), x, y, w, h);
+
+ CGContextRestoreGState(fl_gc);
+}
+
+void Surface::blend(Surface* dst, int src_x, int src_y, int x, int y, int w, int h, int a)
+{
+ CGContextRef bitmap;
+
+ bitmap = make_bitmap(dst->width(), dst->height(), dst->data);
+
+ // macOS Coordinates are from bottom left, not top left
+ src_y = height() - (src_y + h);
+ y = dst->height() - (y + h);
+
+ render(bitmap, image, kCGBlendModeNormal, (CGFloat)a/255.0,
+ src_x, src_y, width(), height(), x, y, w, h);
+
+ CGContextRelease(bitmap);
+}
+
+void Surface::alloc()
+{
+ CGColorSpaceRef lut;
+ CGDataProviderRef provider;
+
+ data = new unsigned char[width() * height() * 4];
+
+ lut = CGDisplayCopyColorSpace(kCGDirectMainDisplay);
+ if (!lut) {
+ lut = CGColorSpaceCreateDeviceRGB();
+ if (!lut)
+ throw rdr::Exception("CGColorSpaceCreateDeviceRGB");
+ }
+
+ provider = CGDataProviderCreateWithData(NULL, data,
+ width() * height() * 4, NULL);
+ if (!provider)
+ throw rdr::Exception("CGDataProviderCreateWithData");
+
+ image = CGImageCreate(width(), height(), 8, 32, width() * 4, lut,
+ kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little,
+ provider, NULL, false, kCGRenderingIntentDefault);
+ CGColorSpaceRelease(lut);
+ CGDataProviderRelease(provider);
+ if (!image)
+ throw rdr::Exception("CGImageCreate");
+}
+
+void Surface::dealloc()
+{
+ CGImageRelease(image);
+ delete [] data;
+}
+
+void Surface::update(const Fl_RGB_Image* image)
+{
+ int x, y;
+ const unsigned char* in;
+ unsigned char* out;
+
+ assert(image->w() == width());
+ assert(image->h() == height());
+
+ // Convert data and pre-multiply alpha
+ in = (const unsigned char*)image->data()[0];
+ out = data;
+ for (y = 0;y < image->h();y++) {
+ for (x = 0;x < image->w();x++) {
+ switch (image->d()) {
+ case 1:
+ *out++ = in[0];
+ *out++ = in[0];
+ *out++ = in[0];
+ *out++ = 0xff;
+ break;
+ case 2:
+ *out++ = (unsigned)in[0] * in[1] / 255;
+ *out++ = (unsigned)in[0] * in[1] / 255;
+ *out++ = (unsigned)in[0] * in[1] / 255;
+ *out++ = in[1];
+ break;
+ case 3:
+ *out++ = in[2];
+ *out++ = in[1];
+ *out++ = in[0];
+ *out++ = 0xff;
+ break;
+ case 4:
+ *out++ = (unsigned)in[2] * in[3] / 255;
+ *out++ = (unsigned)in[1] * in[3] / 255;
+ *out++ = (unsigned)in[0] * in[3] / 255;
+ *out++ = in[3];
+ break;
+ }
+ in += image->d();
+ }
+ if (image->ld() != 0)
+ in += image->ld() - image->w() * image->d();
+ }
+}
diff --git a/vncviewer/Surface_Win32.cxx b/vncviewer/Surface_Win32.cxx
new file mode 100644
index 0000000..9400f53
--- /dev/null
+++ b/vncviewer/Surface_Win32.cxx
@@ -0,0 +1,207 @@
+/* Copyright 2016 Pierre Ossman for Cendio AB
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#include <assert.h>
+
+#include <FL/Fl_RGB_Image.H>
+#include <FL/x.H>
+
+#include <rdr/Exception.h>
+
+#include "Surface.h"
+
+void Surface::clear(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+ RGBQUAD* out;
+ int x, y;
+
+ r = (unsigned)r * a / 255;
+ g = (unsigned)g * a / 255;
+ b = (unsigned)b * a / 255;
+
+ out = data;
+ for (y = 0;y < width();y++) {
+ for (x = 0;x < height();x++) {
+ out->rgbRed = r;
+ out->rgbGreen = g;
+ out->rgbBlue = b;
+ out->rgbReserved = a;
+ out++;
+ }
+ }
+}
+
+void Surface::draw(int src_x, int src_y, int x, int y, int w, int h)
+{
+ HDC dc;
+
+ dc = CreateCompatibleDC(fl_gc);
+ if (!dc)
+ throw rdr::SystemException("CreateCompatibleDC", GetLastError());
+
+ if (!SelectObject(dc, bitmap))
+ throw rdr::SystemException("SelectObject", GetLastError());
+
+ if (!BitBlt(fl_gc, x, y, w, h, dc, src_x, src_y, SRCCOPY)) {
+ // If the desktop we're rendering to is inactive (like when the screen
+ // is locked or the UAC is active), then GDI calls will randomly fail.
+ // This is completely undocumented so we have no idea how best to deal
+ // with it. For now, we've only seen this error and for this function
+ // so only ignore this combination.
+ if (GetLastError() != ERROR_INVALID_HANDLE)
+ throw rdr::SystemException("BitBlt", GetLastError());
+ }
+
+ DeleteDC(dc);
+}
+
+void Surface::draw(Surface* dst, int src_x, int src_y, int x, int y, int w, int h)
+{
+ HDC origdc, dstdc;
+
+ dstdc = CreateCompatibleDC(NULL);
+ if (!dstdc)
+ throw rdr::SystemException("CreateCompatibleDC", GetLastError());
+
+ if (!SelectObject(dstdc, dst->bitmap))
+ throw rdr::SystemException("SelectObject", GetLastError());
+
+ origdc = fl_gc;
+ fl_gc = dstdc;
+ draw(src_x, src_y, x, y, w, h);
+ fl_gc = origdc;
+
+ DeleteDC(dstdc);
+}
+
+void Surface::blend(int src_x, int src_y, int x, int y, int w, int h, int a)
+{
+ // Compositing doesn't work properly for window DC:s
+ assert(false);
+}
+
+void Surface::blend(Surface* dst, int src_x, int src_y, int x, int y, int w, int h, int a)
+{
+ HDC dstdc, srcdc;
+ BLENDFUNCTION blend;
+
+ dstdc = CreateCompatibleDC(NULL);
+ if (!dstdc)
+ throw rdr::SystemException("CreateCompatibleDC", GetLastError());
+ srcdc = CreateCompatibleDC(NULL);
+ if (!srcdc)
+ throw rdr::SystemException("CreateCompatibleDC", GetLastError());
+
+ if (!SelectObject(dstdc, dst->bitmap))
+ throw rdr::SystemException("SelectObject", GetLastError());
+ if (!SelectObject(srcdc, bitmap))
+ throw rdr::SystemException("SelectObject", GetLastError());
+
+ blend.BlendOp = AC_SRC_OVER;
+ blend.BlendFlags = 0;
+ blend.SourceConstantAlpha = a;
+ blend.AlphaFormat = AC_SRC_ALPHA;
+
+ if (!AlphaBlend(dstdc, x, y, w, h, srcdc, src_x, src_y, w, h, blend)) {
+ // If the desktop we're rendering to is inactive (like when the screen
+ // is locked or the UAC is active), then GDI calls will randomly fail.
+ // This is completely undocumented so we have no idea how best to deal
+ // with it. For now, we've only seen this error and for this function
+ // so only ignore this combination.
+ if (GetLastError() != ERROR_INVALID_HANDLE)
+ throw rdr::SystemException("BitBlt", GetLastError());
+ }
+
+ DeleteDC(srcdc);
+ DeleteDC(dstdc);
+}
+
+void Surface::alloc()
+{
+ BITMAPINFOHEADER bih;
+
+ data = new RGBQUAD[width() * height()];
+
+ memset(&bih, 0, sizeof(bih));
+
+ bih.biSize = sizeof(BITMAPINFOHEADER);
+ bih.biBitCount = 32;
+ bih.biPlanes = 1;
+ bih.biWidth = width();
+ bih.biHeight = -height(); // Negative to get top-down
+ bih.biCompression = BI_RGB;
+
+ bitmap = CreateDIBSection(NULL, (BITMAPINFO*)&bih,
+ DIB_RGB_COLORS, (void**)&data, NULL, 0);
+ if (!bitmap)
+ throw rdr::SystemException("CreateDIBSection", GetLastError());
+}
+
+void Surface::dealloc()
+{
+ DeleteObject(bitmap);
+}
+
+void Surface::update(const Fl_RGB_Image* image)
+{
+ const unsigned char* in;
+ RGBQUAD* out;
+ int x, y;
+
+ assert(image->w() == width());
+ assert(image->h() == height());
+
+ // Convert data and pre-multiply alpha
+ in = (const unsigned char*)image->data()[0];
+ out = data;
+ for (y = 0;y < image->w();y++) {
+ for (x = 0;x < image->h();x++) {
+ switch (image->d()) {
+ case 1:
+ out->rgbBlue = in[0];
+ out->rgbGreen = in[0];
+ out->rgbRed = in[0];
+ out->rgbReserved = 0xff;
+ break;
+ case 2:
+ out->rgbBlue = (unsigned)in[0] * in[1] / 255;
+ out->rgbGreen = (unsigned)in[0] * in[1] / 255;
+ out->rgbRed = (unsigned)in[0] * in[1] / 255;
+ out->rgbReserved = in[1];
+ break;
+ case 3:
+ out->rgbBlue = in[2];
+ out->rgbGreen = in[1];
+ out->rgbRed = in[0];
+ out->rgbReserved = 0xff;
+ break;
+ case 4:
+ out->rgbBlue = (unsigned)in[2] * in[3] / 255;
+ out->rgbGreen = (unsigned)in[1] * in[3] / 255;
+ out->rgbRed = (unsigned)in[0] * in[3] / 255;
+ out->rgbReserved = in[3];
+ break;
+ }
+ in += image->d();
+ out++;
+ }
+ if (image->ld() != 0)
+ in += image->ld() - image->w() * image->d();
+ }
+}
+
diff --git a/vncviewer/Surface_X11.cxx b/vncviewer/Surface_X11.cxx
new file mode 100644
index 0000000..3523da3
--- /dev/null
+++ b/vncviewer/Surface_X11.cxx
@@ -0,0 +1,198 @@
+/* Copyright 2016 Pierre Ossman for Cendio AB
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#include <assert.h>
+
+#include <FL/Fl_RGB_Image.H>
+#include <FL/x.H>
+
+#include <rdr/Exception.h>
+
+#include "Surface.h"
+
+void Surface::clear(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+ XRenderColor color;
+
+ color.red = (unsigned)r * 65535 / 255 * a / 255;
+ color.green = (unsigned)g * 65535 / 255 * a / 255;
+ color.blue = (unsigned)b * 65535 / 255 * a / 255;
+ color.alpha = (unsigned)a * 65535 / 255;
+
+ XRenderFillRectangle(fl_display, PictOpSrc, picture, &color,
+ 0, 0, width(), height());
+}
+
+void Surface::draw(int src_x, int src_y, int x, int y, int w, int h)
+{
+ Picture winPict;
+
+ winPict = XRenderCreatePicture(fl_display, fl_window, visFormat, 0, NULL);
+ XRenderComposite(fl_display, PictOpSrc, picture, None, winPict,
+ src_x, src_y, 0, 0, x, y, w, h);
+ XRenderFreePicture(fl_display, winPict);
+}
+
+void Surface::draw(Surface* dst, int src_x, int src_y, int x, int y, int w, int h)
+{
+ XRenderComposite(fl_display, PictOpSrc, picture, None, dst->picture,
+ src_x, src_y, 0, 0, x, y, w, h);
+}
+
+static Picture alpha_mask(int a)
+{
+ Pixmap pixmap;
+ XRenderPictFormat* format;
+ XRenderPictureAttributes rep;
+ Picture pict;
+ XRenderColor color;
+
+ if (a == 255)
+ return None;
+
+ pixmap = XCreatePixmap(fl_display, XDefaultRootWindow(fl_display),
+ 1, 1, 8);
+
+ format = XRenderFindStandardFormat(fl_display, PictStandardA8);
+ rep.repeat = RepeatNormal;
+ pict = XRenderCreatePicture(fl_display, pixmap, format, CPRepeat, &rep);
+ XFreePixmap(fl_display, pixmap);
+
+ color.alpha = (unsigned)a * 65535 / 255;
+
+ XRenderFillRectangle(fl_display, PictOpSrc, pict, &color,
+ 0, 0, 1, 1);
+
+ return pict;
+}
+
+void Surface::blend(int src_x, int src_y, int x, int y, int w, int h, int a)
+{
+ Picture winPict, alpha;
+
+ winPict = XRenderCreatePicture(fl_display, fl_window, visFormat, 0, NULL);
+ alpha = alpha_mask(a);
+ XRenderComposite(fl_display, PictOpOver, picture, alpha, winPict,
+ src_x, src_y, 0, 0, x, y, w, h);
+ XRenderFreePicture(fl_display, winPict);
+
+ if (alpha != None)
+ XRenderFreePicture(fl_display, alpha);
+}
+
+void Surface::blend(Surface* dst, int src_x, int src_y, int x, int y, int w, int h, int a)
+{
+ Picture alpha;
+
+ alpha = alpha_mask(a);
+ XRenderComposite(fl_display, PictOpOver, picture, alpha, dst->picture,
+ src_x, src_y, 0, 0, x, y, w, h);
+ if (alpha != None)
+ XRenderFreePicture(fl_display, alpha);
+}
+
+
+void Surface::alloc()
+{
+ XRenderPictFormat* format;
+
+ // Might not be open at this point
+ fl_open_display();
+
+ pixmap = XCreatePixmap(fl_display, XDefaultRootWindow(fl_display),
+ width(), height(), 32);
+
+ format = XRenderFindStandardFormat(fl_display, PictStandardARGB32);
+ picture = XRenderCreatePicture(fl_display, pixmap, format, 0, NULL);
+
+ visFormat = XRenderFindVisualFormat(fl_display, fl_visual->visual);
+}
+
+void Surface::dealloc()
+{
+ XRenderFreePicture(fl_display, picture);
+ XFreePixmap(fl_display, pixmap);
+}
+
+void Surface::update(const Fl_RGB_Image* image)
+{
+ XImage* img;
+ GC gc;
+
+ int x, y;
+ const unsigned char* in;
+ unsigned char* out;
+
+ assert(image->w() == width());
+ assert(image->h() == height());
+
+ img = XCreateImage(fl_display, CopyFromParent, 32,
+ ZPixmap, 0, NULL, width(), height(),
+ 32, 0);
+ if (!img)
+ throw rdr::Exception("XCreateImage");
+
+ img->data = (char*)malloc(img->bytes_per_line * img->height);
+ if (!img->data)
+ throw rdr::Exception("malloc");
+
+ // Convert data and pre-multiply alpha
+ in = (const unsigned char*)image->data()[0];
+ out = (unsigned char*)img->data;
+ for (y = 0;y < img->height;y++) {
+ for (x = 0;x < img->width;x++) {
+ switch (image->d()) {
+ case 1:
+ *out++ = in[0];
+ *out++ = in[0];
+ *out++ = in[0];
+ *out++ = 0xff;
+ break;
+ case 2:
+ *out++ = (unsigned)in[0] * in[1] / 255;
+ *out++ = (unsigned)in[0] * in[1] / 255;
+ *out++ = (unsigned)in[0] * in[1] / 255;
+ *out++ = in[1];
+ break;
+ case 3:
+ *out++ = in[2];
+ *out++ = in[1];
+ *out++ = in[0];
+ *out++ = 0xff;
+ break;
+ case 4:
+ *out++ = (unsigned)in[2] * in[3] / 255;
+ *out++ = (unsigned)in[1] * in[3] / 255;
+ *out++ = (unsigned)in[0] * in[3] / 255;
+ *out++ = in[3];
+ break;
+ }
+ in += image->d();
+ }
+ if (image->ld() != 0)
+ in += image->ld() - image->w() * image->d();
+ }
+
+ gc = XCreateGC(fl_display, pixmap, 0, NULL);
+ XPutImage(fl_display, pixmap, gc, img,
+ 0, 0, 0, 0, img->width, img->height);
+ XFreeGC(fl_display, gc);
+
+ XDestroyImage(img);
+}
+
diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx
index a12f783..6a23526 100644
--- a/vncviewer/Viewport.cxx
+++ b/vncviewer/Viewport.cxx
@@ -62,15 +62,6 @@
#include "vncviewer.h"
#include "PlatformPixelBuffer.h"
-#include "FLTKPixelBuffer.h"
-
-#if defined(WIN32)
-#include "Win32PixelBuffer.h"
-#elif defined(__APPLE__)
-#include "OSXPixelBuffer.h"
-#else
-#include "X11PixelBuffer.h"
-#endif
#include <FL/fl_draw.H>
#include <FL/fl_ask.H>
@@ -111,7 +102,7 @@
// We need to intercept keyboard events early
Fl::add_system_handler(handleSystemEvent, this);
- frameBuffer = createFramebuffer(w, h);
+ frameBuffer = new PlatformPixelBuffer(w, h);
assert(frameBuffer);
cc->setFramebuffer(frameBuffer);
@@ -189,21 +180,20 @@
" "};
void Viewport::setCursor(int width, int height, const Point& hotspot,
- void* data, void* mask)
+ const rdr::U8* data)
{
+ int i;
+
if (cursor) {
if (!cursor->alloc_array)
delete [] cursor->array;
delete cursor;
}
- int mask_len = ((width+7)/8) * height;
- int i;
+ for (i = 0; i < width*height; i++)
+ if (data[i*4 + 3] != 0) break;
- for (i = 0; i < mask_len; i++)
- if (((rdr::U8*)mask)[i]) break;
-
- if ((i == mask_len) && dotWhenNoCursor) {
+ if ((i == width*height) && dotWhenNoCursor) {
vlog.debug("cursor is empty - using dot");
Fl_Pixmap pxm(dotcursor_xpm);
@@ -216,34 +206,9 @@
cursor = new Fl_RGB_Image(buffer, 1, 1, 4);
cursorHotspot.x = cursorHotspot.y = 0;
} else {
- U8 *buffer = new U8[width*height*4];
- U8 *i, *o, *m;
- int m_width;
-
- const PixelFormat *pf;
-
- pf = &cc->cp.pf();
-
- i = (U8*)data;
- o = buffer;
- m = (U8*)mask;
- m_width = (width+7)/8;
- for (int y = 0;y < height;y++) {
- for (int x = 0;x < width;x++) {
- pf->rgbFromBuffer(o, i, 1);
-
- if (m[(m_width*y)+(x/8)] & 0x80>>(x%8))
- o[3] = 255;
- else
- o[3] = 0;
-
- o += 4;
- i += pf->bpp/8;
- }
- }
-
+ U8 *buffer = new U8[width * height * 4];
+ memcpy(buffer, data, width * height * 4);
cursor = new Fl_RGB_Image(buffer, width, height, 4);
-
cursorHotspot = hotspot;
}
}
@@ -253,6 +218,19 @@
}
+void Viewport::draw(Surface* dst)
+{
+ int X, Y, W, H;
+
+ // Check what actually needs updating
+ fl_clip_box(x(), y(), w(), h(), X, Y, W, H);
+ if ((W == 0) || (H == 0))
+ return;
+
+ frameBuffer->draw(dst, X - x(), Y - y(), X, Y, W, H);
+}
+
+
void Viewport::draw()
{
int X, Y, W, H;
@@ -272,7 +250,7 @@
vlog.debug("Resizing framebuffer from %dx%d to %dx%d",
frameBuffer->width(), frameBuffer->height(), w, h);
- frameBuffer = createFramebuffer(w, h);
+ frameBuffer = new PlatformPixelBuffer(w, h);
assert(frameBuffer);
cc->setFramebuffer(frameBuffer);
}
@@ -374,35 +352,15 @@
return Fl_Widget::handle(event);
}
-
-PlatformPixelBuffer* Viewport::createFramebuffer(int w, int h)
-{
- PlatformPixelBuffer *fb;
-
- try {
-#if defined(WIN32)
- fb = new Win32PixelBuffer(w, h);
-#elif defined(__APPLE__)
- fb = new OSXPixelBuffer(w, h);
-#else
- fb = new X11PixelBuffer(w, h);
-#endif
- } catch (rdr::Exception& e) {
- vlog.error(_("Unable to create platform specific framebuffer: %s"), e.str());
- vlog.error(_("Using platform independent framebuffer"));
- fb = new FLTKPixelBuffer(w, h);
- }
-
- return fb;
-}
-
-
void Viewport::handleClipboardChange(int source, void *data)
{
Viewport *self = (Viewport *)data;
assert(self);
+ if (!sendClipboard)
+ return;
+
#if !defined(WIN32) && !defined(__APPLE__)
if (!sendPrimary && (source == 0))
return;
diff --git a/vncviewer/Viewport.h b/vncviewer/Viewport.h
index f73a27d..6f0710d 100644
--- a/vncviewer/Viewport.h
+++ b/vncviewer/Viewport.h
@@ -22,17 +22,14 @@
#include <map>
-namespace rfb { class ModifiablePixelBuffer; }
-
#include <FL/Fl_Widget.H>
class Fl_Menu_Button;
class Fl_RGB_Image;
-namespace rfb { class PixelTransformer; }
-
class CConn;
class PlatformPixelBuffer;
+class Surface;
class Viewport : public Fl_Widget {
public:
@@ -48,7 +45,9 @@
// New image for the locally rendered cursor
void setCursor(int width, int height, const rfb::Point& hotspot,
- void* data, void* mask);
+ const rdr::U8* data);
+
+ void draw(Surface* dst);
// Fl_Widget callback methods
@@ -60,8 +59,6 @@
private:
- PlatformPixelBuffer* createFramebuffer(int w, int h);
-
static void handleClipboardChange(int source, void *data);
void handlePointerEvent(const rfb::Point& pos, int buttonMask);
diff --git a/vncviewer/Win32PixelBuffer.cxx b/vncviewer/Win32PixelBuffer.cxx
deleted file mode 100644
index 3f82530..0000000
--- a/vncviewer/Win32PixelBuffer.cxx
+++ /dev/null
@@ -1,95 +0,0 @@
-/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright 2011-2014 Pierre Ossman for Cendio AB
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this software; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
- * USA.
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdlib.h>
-
-#include <windows.h>
-
-#include <FL/x.H>
-
-#include <rfb/LogWriter.h>
-#include <rfb/Exception.h>
-
-#include "i18n.h"
-#include "Win32PixelBuffer.h"
-
-using namespace rfb;
-
-static rfb::LogWriter vlog("Win32PixelBuffer");
-
-Win32PixelBuffer::Win32PixelBuffer(int width, int height) :
- PlatformPixelBuffer(rfb::PixelFormat(32, 24, false, true,
- 255, 255, 255, 16, 8, 0),
- width, height, NULL, width),
- bitmap(NULL)
-{
- BITMAPINFOHEADER bih;
-
- memset(&bih, 0, sizeof(bih));
-
- bih.biSize = sizeof(BITMAPINFOHEADER);
- bih.biBitCount = getPF().bpp;
- bih.biSizeImage = (getPF().bpp / 8) * width * height;
- bih.biPlanes = 1;
- bih.biWidth = width;
- bih.biHeight = -height; // Negative to get top-down
- bih.biCompression = BI_RGB;
-
- bitmap = CreateDIBSection(NULL, (BITMAPINFO*)&bih,
- DIB_RGB_COLORS, (void**)&data, NULL, 0);
- if (!bitmap) {
- int err = GetLastError();
- throw rdr::SystemException(_("unable to create DIB section"), err);
- }
-}
-
-
-Win32PixelBuffer::~Win32PixelBuffer()
-{
- DeleteObject(bitmap);
-}
-
-
-void Win32PixelBuffer::draw(int src_x, int src_y, int x, int y, int w, int h)
-{
- HDC dc;
-
- dc = CreateCompatibleDC(fl_gc);
- if (!dc)
- throw rdr::SystemException(_("CreateCompatibleDC failed"), GetLastError());
-
- if (!SelectObject(dc, bitmap))
- throw rdr::SystemException(_("SelectObject failed"), GetLastError());
-
- if (!BitBlt(fl_gc, x, y, w, h, dc, src_x, src_y, SRCCOPY)) {
- // If the desktop we're rendering to is inactive (like when the screen
- // is locked or the UAC is active), then GDI calls will randomly fail.
- // This is completely undocumented so we have no idea how best to deal
- // with it. For now, we've only seen this error and for this function
- // so only ignore this combination.
- if (GetLastError() != ERROR_INVALID_HANDLE)
- throw rdr::SystemException(_("BitBlt failed"), GetLastError());
- }
-
- DeleteDC(dc);
-}
diff --git a/vncviewer/X11PixelBuffer.cxx b/vncviewer/X11PixelBuffer.cxx
deleted file mode 100644
index ce5c4d8..0000000
--- a/vncviewer/X11PixelBuffer.cxx
+++ /dev/null
@@ -1,279 +0,0 @@
-/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright 2011-2014 Pierre Ossman for Cendio AB
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this software; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
- * USA.
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <assert.h>
-#include <stdlib.h>
-
-#include <FL/Fl.H>
-#include <FL/x.H>
-
-#include <rfb/LogWriter.h>
-#include <rfb/Exception.h>
-
-#include "i18n.h"
-#include "X11PixelBuffer.h"
-
-using namespace rfb;
-
-static rfb::LogWriter vlog("X11PixelBuffer");
-
-std::list<X11PixelBuffer*> X11PixelBuffer::shmList;
-
-static PixelFormat display_pf()
-{
- int i;
-
- int bpp;
- int trueColour, bigEndian;
- int redShift, greenShift, blueShift;
- int redMax, greenMax, blueMax;
-
- int nformats;
- XPixmapFormatValues* format;
-
- // Might not be open at this point
- fl_open_display();
-
- format = XListPixmapFormats(fl_display, &nformats);
-
- for (i = 0; i < nformats; i++)
- if (format[i].depth == fl_visual->depth) break;
-
- if (i == nformats)
- // TRANSLATORS: "pixmap" is an X11 concept and may not be suitable
- // to translate.
- throw rfb::Exception(_("Display lacks pixmap format for default depth"));
-
- switch (format[i].bits_per_pixel) {
- case 8:
- case 16:
- case 32:
- bpp = format[i].bits_per_pixel;
- break;
- default:
- // TRANSLATORS: "pixmap" is an X11 concept and may not be suitable
- // to translate.
- throw rfb::Exception(_("Couldn't find suitable pixmap format"));
- }
-
- XFree(format);
-
- bigEndian = (ImageByteOrder(fl_display) == MSBFirst);
- trueColour = (fl_visual->c_class == TrueColor);
-
- if (!trueColour)
- throw rfb::Exception(_("Only true colour displays supported"));
-
- vlog.info(_("Using default colormap and visual, TrueColor, depth %d."),
- fl_visual->depth);
-
- redShift = ffs(fl_visual->red_mask) - 1;
- greenShift = ffs(fl_visual->green_mask) - 1;
- blueShift = ffs(fl_visual->blue_mask) - 1;
- redMax = fl_visual->red_mask >> redShift;
- greenMax = fl_visual->green_mask >> greenShift;
- blueMax = fl_visual->blue_mask >> blueShift;
-
- return PixelFormat(bpp, fl_visual->depth, bigEndian, trueColour,
- redMax, greenMax, blueMax,
- redShift, greenShift, blueShift);
-}
-
-X11PixelBuffer::X11PixelBuffer(int width, int height) :
- PlatformPixelBuffer(display_pf(), width, height, NULL, 0),
- shminfo(NULL), xim(NULL), pendingPutImage(0), pendingDrawable(0)
-{
- // Might not be open at this point
- fl_open_display();
-
- if (!setupShm()) {
- xim = XCreateImage(fl_display, fl_visual->visual, fl_visual->depth,
- ZPixmap, 0, 0, width, height, BitmapPad(fl_display), 0);
- if (!xim)
- throw rfb::Exception(_("Could not create framebuffer image"));
-
- xim->data = (char*)malloc(xim->bytes_per_line * xim->height);
- if (!xim->data)
- throw rfb::Exception(_("Not enough memory for framebuffer"));
- }
-
- data = (rdr::U8*)xim->data;
- stride = xim->bytes_per_line / (getPF().bpp/8);
-}
-
-
-X11PixelBuffer::~X11PixelBuffer()
-{
- if (shminfo) {
- vlog.debug("Freeing shared memory XImage");
- shmList.remove(this);
- Fl::remove_system_handler(handleSystemEvent);
- shmdt(shminfo->shmaddr);
- shmctl(shminfo->shmid, IPC_RMID, 0);
- delete shminfo;
- shminfo = NULL;
- }
-
- // XDestroyImage() will free(xim->data) if appropriate
- if (xim)
- XDestroyImage(xim);
- xim = NULL;
-}
-
-
-void X11PixelBuffer::draw(int src_x, int src_y, int x, int y, int w, int h)
-{
- if (shminfo) {
- XShmPutImage(fl_display, fl_window, fl_gc, xim, src_x, src_y, x, y, w, h, True);
- pendingPutImage++;
- assert((pendingPutImage == 1) || (pendingDrawable == fl_window));
- pendingDrawable = fl_window;
- } else {
- XPutImage(fl_display, fl_window, fl_gc, xim, src_x, src_y, x, y, w, h);
- }
-}
-
-bool X11PixelBuffer::isRendering(void)
-{
- return pendingPutImage > 0;
-}
-
-static bool caughtError;
-
-static int XShmAttachErrorHandler(Display *dpy, XErrorEvent *error)
-{
- caughtError = true;
- return 0;
-}
-
-int X11PixelBuffer::setupShm()
-{
- int major, minor;
- Bool pixmaps;
- XErrorHandler old_handler;
- const char *display_name = XDisplayName (NULL);
-
- /* Don't use MIT-SHM on remote displays */
- if (*display_name && *display_name != ':')
- return 0;
-
- if (!XShmQueryVersion(fl_display, &major, &minor, &pixmaps))
- return 0;
-
- shminfo = new XShmSegmentInfo;
-
- xim = XShmCreateImage(fl_display, fl_visual->visual, fl_visual->depth,
- ZPixmap, 0, shminfo, width(), height());
- if (!xim)
- goto free_shminfo;
-
- shminfo->shmid = shmget(IPC_PRIVATE,
- xim->bytes_per_line * xim->height,
- IPC_CREAT|0777);
- if (shminfo->shmid == -1)
- goto free_xim;
-
- shminfo->shmaddr = xim->data = (char*)shmat(shminfo->shmid, 0, 0);
- if (shminfo->shmaddr == (char *)-1)
- goto free_shm;
-
- shminfo->readOnly = True;
-
- // This is the only way we can detect that shared memory won't work
- // (e.g. because we're accessing a remote X11 server)
- caughtError = false;
- old_handler = XSetErrorHandler(XShmAttachErrorHandler);
-
- if (!XShmAttach(fl_display, shminfo)) {
- XSetErrorHandler(old_handler);
- goto free_shmaddr;
- }
-
- XSync(fl_display, False);
-
- XSetErrorHandler(old_handler);
-
- if (caughtError)
- goto free_shmaddr;
-
- // FLTK is a bit stupid and unreliable if you register the same
- // callback with different data values.
- Fl::add_system_handler(handleSystemEvent, NULL);
- shmList.push_back(this);
-
- vlog.debug("Using shared memory XImage");
-
- return 1;
-
-free_shmaddr:
- shmdt(shminfo->shmaddr);
-
-free_shm:
- shmctl(shminfo->shmid, IPC_RMID, 0);
-
-free_xim:
- XDestroyImage(xim);
- xim = NULL;
-
-free_shminfo:
- delete shminfo;
- shminfo = NULL;
-
- return 0;
-}
-
-int X11PixelBuffer::handleSystemEvent(void* event, void* data)
-{
- XEvent* xevent;
- XShmCompletionEvent* shmevent;
-
- std::list<X11PixelBuffer*>::iterator iter;
-
- xevent = (XEvent*)event;
- assert(xevent);
-
- if (xevent->type != XShmGetEventBase(fl_display) + ShmCompletion)
- return 0;
-
- shmevent = (XShmCompletionEvent*)event;
-
- if (shmevent->send_event)
- return 0;
-
- for (iter = shmList.begin();iter != shmList.end();++iter) {
- if (shmevent->shmseg != (*iter)->shminfo->shmseg)
- continue;
-
- /* HP has a buggy X server on their thin clients that sends bogus
- * extra events with an incorrect drawable id */
- if (shmevent->drawable != (*iter)->pendingDrawable)
- continue;
-
- (*iter)->pendingPutImage--;
- assert((*iter)->pendingPutImage >= 0);
-
- return 1;
- }
-
- return 0;
-}
diff --git a/vncviewer/X11PixelBuffer.h b/vncviewer/X11PixelBuffer.h
deleted file mode 100644
index a616672..0000000
--- a/vncviewer/X11PixelBuffer.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/* Copyright 2011-2014 Pierre Ossman for Cendio AB
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this software; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
- * USA.
- */
-
-#ifndef __X11PIXELBUFFER_H__
-#define __X11PIXELBUFFER_H__
-
-#include <X11/Xlib.h>
-#include <sys/ipc.h>
-#include <sys/shm.h>
-#include <X11/extensions/XShm.h>
-
-#include <list>
-
-#include "PlatformPixelBuffer.h"
-
-class X11PixelBuffer: public PlatformPixelBuffer {
-public:
- X11PixelBuffer(int width, int height);
- ~X11PixelBuffer();
-
- virtual void draw(int src_x, int src_y, int x, int y, int w, int h);
-
- virtual bool isRendering(void);
-
-protected:
- int setupShm();
-
- static int handleSystemEvent(void* event, void* data);
-
-protected:
- XShmSegmentInfo *shminfo;
- XImage *xim;
- int pendingPutImage;
- Drawable pendingDrawable;
-
- static std::list<X11PixelBuffer*> shmList;
-};
-
-
-#endif
diff --git a/vncviewer/cocoa.mm b/vncviewer/cocoa.mm
index 3919cb8..fc3b5bc 100644
--- a/vncviewer/cocoa.mm
+++ b/vncviewer/cocoa.mm
@@ -37,6 +37,10 @@
#define NoSymbol 0
+// These are undocumented for unknown reasons
+const int kVK_RightCommand = 0x36;
+const int kVK_Menu = 0x6E;
+
static bool captured = false;
int cocoa_capture_display(Fl_Window *win, bool all_displays)
@@ -135,37 +139,37 @@
UInt32 mask;
// We don't see any event on release of CapsLock
- if ([nsevent keyCode] == 0x39)
+ if ([nsevent keyCode] == kVK_CapsLock)
return 1;
// These are entirely undocumented, but I cannot find any other way
// of differentiating between left and right keys
switch ([nsevent keyCode]) {
- case 0x36:
+ case kVK_RightCommand:
mask = 0x0010;
break;
- case 0x37:
+ case kVK_Command:
mask = 0x0008;
break;
- case 0x38:
+ case kVK_Shift:
mask = 0x0002;
break;
- case 0x39:
+ case kVK_CapsLock:
// We don't see any event on release of CapsLock
return 1;
- case 0x3A:
+ case kVK_Option:
mask = 0x0020;
break;
- case 0x3B:
+ case kVK_Control:
mask = 0x0001;
break;
- case 0x3C:
+ case kVK_RightShift:
mask = 0x0004;
break;
- case 0x3D:
+ case kVK_RightOption:
mask = 0x0040;
break;
- case 0x3E:
+ case kVK_RightControl:
mask = 0x2000;
break;
default:
@@ -197,7 +201,6 @@
layout = NULL;
-#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) || defined(__x86_64__)
TISInputSourceRef keyboard;
CFDataRef uchr;
@@ -208,101 +211,6 @@
return nil;
layout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr);
-#else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
- KeyboardLayoutRef old_layout;
- int kind;
-
- err = KLGetCurrentKeyboardLayout(&old_layout);
- if (err != noErr)
- return nil;
-
- err = KLGetKeyboardLayoutProperty(old_layout, kKLKind,
- (const void**)&kind);
- if (err != noErr)
- return nil;
-
- // Old, crufty layout format?
- if (kind == kKLKCHRKind) {
- void *kchr_layout;
-
- UInt32 chars, state;
- char buf[3];
-
- unichar result[16];
- ByteCount in_len, out_len;
-
- err = KLGetKeyboardLayoutProperty(old_layout, kKLKCHRData,
- (const void**)&kchr_layout);
- if (err != noErr)
- return nil;
-
- state = 0;
-
- keyCode &= 0x7f;
- modifierFlags &= 0xff00;
-
- chars = KeyTranslate(kchr_layout, keyCode | modifierFlags, &state);
-
- // Dead key?
- if (state != 0) {
- // We have no fool proof way of asking what dead key this is.
- // Assume we get a spacing equivalent if we press the
- // same key again, and try to deduce something from that.
- chars = KeyTranslate(kchr_layout, keyCode | modifierFlags, &state);
- }
-
- buf[0] = (chars >> 16) & 0xff;
- buf[1] = chars & 0xff;
- buf[2] = '\0';
-
- if (buf[0] == '\0') {
- buf[0] = buf[1];
- buf[1] = '\0';
- }
-
- // The data is now in some layout specific encoding. Need to convert
- // this to unicode.
-
- ScriptCode script;
- TextEncoding encoding;
- TECObjectRef converter;
-
- script = (ScriptCode)GetScriptManagerVariable(smKeyScript);
-
- err = UpgradeScriptInfoToTextEncoding(script, kTextLanguageDontCare,
- kTextRegionDontCare, NULL,
- &encoding);
- if (err != noErr)
- return nil;
-
- err = TECCreateConverter(&converter, encoding, kTextEncodingUnicodeV4_0);
- if (err != noErr)
- return nil;
-
- in_len = strlen(buf);
- out_len = sizeof(result);
-
- err = TECConvertText(converter, (ConstTextPtr)buf, in_len, &in_len,
- (TextPtr)result, out_len, &out_len);
-
- TECDisposeConverter(converter);
-
- if (err != noErr)
- return nil;
-
- return [NSString stringWithCharacters:result
- length:(out_len / sizeof(unichar))];
- }
-
- if ((kind != kKLKCHRuchrKind) && (kind != kKLuchrKind))
- return nil;
-
- err = KLGetKeyboardLayoutProperty(old_layout, kKLuchrData,
- (const void**)&layout);
- if (err != noErr)
- return nil;
-#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
-
if (layout == NULL)
return nil;
@@ -336,81 +244,78 @@
return [NSString stringWithCharacters:string length:actual_len];
}
-// FIXME: We use hard coded values here as the constants didn't appear
-// in the OS X headers until 10.5.
static const int kvk_map[][2] = {
- { 0x24, XK_Return },
- { 0x30, XK_Tab },
- { 0x31, XK_space },
- { 0x33, XK_BackSpace },
- { 0x35, XK_Escape },
- // This one is undocumented for unknown reasons
- { 0x36, XK_Super_R },
- { 0x37, XK_Super_L },
- { 0x38, XK_Shift_L },
- { 0x39, XK_Caps_Lock },
- { 0x3A, XK_Alt_L },
- { 0x3B, XK_Control_L },
- { 0x3C, XK_Shift_R },
- { 0x3D, XK_Alt_R },
- { 0x3E, XK_Control_R },
- { 0x40, XK_F17 },
- { 0x48, XF86XK_AudioRaiseVolume },
- { 0x49, XF86XK_AudioLowerVolume },
- { 0x4A, XF86XK_AudioMute },
- { 0x4F, XK_F18 },
- { 0x50, XK_F19 },
- { 0x5A, XK_F20 },
- { 0x60, XK_F5 },
- { 0x61, XK_F6 },
- { 0x62, XK_F7 },
- { 0x63, XK_F3 },
- { 0x64, XK_F8 },
- { 0x65, XK_F9 },
- { 0x67, XK_F11 },
- { 0x69, XK_F13 },
- { 0x6A, XK_F16 },
- { 0x6B, XK_F14 },
- { 0x6D, XK_F10 },
- // Also undocumented
- { 0x6E, XK_Menu },
- { 0x6F, XK_F12 },
- { 0x71, XK_F15 },
+ { kVK_Return, XK_Return },
+ { kVK_Tab, XK_Tab },
+ { kVK_Space, XK_space },
+ { kVK_Delete, XK_BackSpace },
+ { kVK_Escape, XK_Escape },
+ { kVK_RightCommand, XK_Super_R },
+ { kVK_Command, XK_Super_L },
+ { kVK_Shift, XK_Shift_L },
+ { kVK_CapsLock, XK_Caps_Lock },
+ { kVK_Option, XK_Alt_L },
+ { kVK_Control, XK_Control_L },
+ { kVK_RightShift, XK_Shift_R },
+ { kVK_RightOption, XK_Alt_R },
+ { kVK_RightControl, XK_Control_R },
+ { kVK_F17, XK_F17 },
+ { kVK_VolumeUp, XF86XK_AudioRaiseVolume },
+ { kVK_VolumeDown, XF86XK_AudioLowerVolume },
+ { kVK_Mute, XF86XK_AudioMute },
+ { kVK_F18, XK_F18 },
+ { kVK_F19, XK_F19 },
+ { kVK_F20, XK_F20 },
+ { kVK_F5, XK_F5 },
+ { kVK_F6, XK_F6 },
+ { kVK_F7, XK_F7 },
+ { kVK_F3, XK_F3 },
+ { kVK_F8, XK_F8 },
+ { kVK_F9, XK_F9 },
+ { kVK_F11, XK_F11 },
+ { kVK_F13, XK_F13 },
+ { kVK_F16, XK_F16 },
+ { kVK_F14, XK_F14 },
+ { kVK_F10, XK_F10 },
+ { kVK_Menu, XK_Menu },
+ { kVK_F12, XK_F12 },
+ { kVK_F15, XK_F15 },
// Should we send Insert here?
- { 0x72, XK_Help },
- { 0x73, XK_Home },
- { 0x74, XK_Page_Up },
- { 0x75, XK_Delete },
- { 0x76, XK_F4 },
- { 0x77, XK_End },
- { 0x78, XK_F2 },
- { 0x79, XK_Page_Down },
- { 0x7A, XK_F1 },
- { 0x7B, XK_Left },
- { 0x7C, XK_Right },
- { 0x7D, XK_Down },
- { 0x7E, XK_Up },
+ { kVK_Help, XK_Help },
+ { kVK_Home, XK_Home },
+ { kVK_PageUp, XK_Page_Up },
+ { kVK_ForwardDelete, XK_Delete },
+ { kVK_F4, XK_F4 },
+ { kVK_End, XK_End },
+ { kVK_F2, XK_F2 },
+ { kVK_PageDown, XK_Page_Down },
+ { kVK_F1, XK_F1 },
+ { kVK_LeftArrow, XK_Left },
+ { kVK_RightArrow, XK_Right },
+ { kVK_DownArrow, XK_Down },
+ { kVK_UpArrow, XK_Up },
+
// The OS X headers claim these keys are not layout independent.
// Could it be because of the state of the decimal key?
- /* { 0x41, XK_KP_Decimal }, */ // see below
- { 0x43, XK_KP_Multiply },
- { 0x45, XK_KP_Add },
+ /* { kVK_ANSI_KeypadDecimal, XK_KP_Decimal }, */ // see below
+ { kVK_ANSI_KeypadMultiply, XK_KP_Multiply },
+ { kVK_ANSI_KeypadPlus, XK_KP_Add },
// OS X doesn't have NumLock, so is this really correct?
- { 0x47, XK_Num_Lock },
- { 0x4B, XK_KP_Divide },
- { 0x4C, XK_KP_Enter },
- { 0x4E, XK_KP_Subtract },
- { 0x51, XK_KP_Equal },
- { 0x52, XK_KP_0 },
- { 0x53, XK_KP_1 },
- { 0x54, XK_KP_2 },
- { 0x55, XK_KP_3 },
- { 0x56, XK_KP_4 },
- { 0x57, XK_KP_5 },
- { 0x58, XK_KP_6 },
- { 0x59, XK_KP_7 },
- { 0x5B, XK_KP_8 },
- { 0x5C, XK_KP_9 },
+ { kVK_ANSI_KeypadClear, XK_Num_Lock },
+ { kVK_ANSI_KeypadDivide, XK_KP_Divide },
+ { kVK_ANSI_KeypadEnter, XK_KP_Enter },
+ { kVK_ANSI_KeypadMinus, XK_KP_Subtract },
+ { kVK_ANSI_KeypadEquals, XK_KP_Equal },
+ { kVK_ANSI_Keypad0, XK_KP_0 },
+ { kVK_ANSI_Keypad1, XK_KP_1 },
+ { kVK_ANSI_Keypad2, XK_KP_2 },
+ { kVK_ANSI_Keypad3, XK_KP_3 },
+ { kVK_ANSI_Keypad4, XK_KP_4 },
+ { kVK_ANSI_Keypad5, XK_KP_5 },
+ { kVK_ANSI_Keypad6, XK_KP_6 },
+ { kVK_ANSI_Keypad7, XK_KP_7 },
+ { kVK_ANSI_Keypad8, XK_KP_8 },
+ { kVK_ANSI_Keypad9, XK_KP_9 },
};
int cocoa_event_keysym(const void *event)
diff --git a/win/rfb_win32/DeviceFrameBuffer.cxx b/win/rfb_win32/DeviceFrameBuffer.cxx
index 22841f7..3138cce 100644
--- a/win/rfb_win32/DeviceFrameBuffer.cxx
+++ b/win/rfb_win32/DeviceFrameBuffer.cxx
@@ -1,5 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright 2014 Pierre Ossman for Cendio AB
+ * Copyright 2014-2017 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -42,7 +42,7 @@
// -=- DeviceFrameBuffer class
DeviceFrameBuffer::DeviceFrameBuffer(HDC deviceContext, const Rect& wRect)
- : DIBSectionBuffer(deviceContext), device(deviceContext), cursorBm(deviceContext),
+ : DIBSectionBuffer(deviceContext), device(deviceContext),
ignoreGrabErrors(false)
{
@@ -76,9 +76,6 @@
// Configure the underlying DIB to match the device
DIBSectionBuffer::setPF(DeviceContext::getPF(device));
DIBSectionBuffer::setSize(w, h);
-
- // Configure the cursor buffer
- cursorBm.setPF(format);
}
DeviceFrameBuffer::~DeviceFrameBuffer() {
@@ -134,15 +131,14 @@
// - If hCursor is null then there is no cursor - clear the old one
if (hCursor == 0) {
- server->setCursor(0, 0, Point(), 0, 0);
+ server->setCursor(0, 0, Point(), NULL);
return;
}
try {
- const rdr::U8* buffer;
- rdr::U8* rwbuffer;
- int stride;
+ int width, height;
+ rdr::U8Array buffer;
// - Get the size and other details about the cursor.
@@ -156,97 +152,170 @@
if (maskInfo.bmBitsPixel != 1)
throw rdr::Exception("unsupported cursor mask format");
- // - Create the cursor pixel buffer and mask storage
- // NB: The cursor pixel buffer is NOT used here. Instead, we
- // pass the cursorBm.data pointer directly, to save overhead.
-
- cursor.setSize(maskInfo.bmWidth, maskInfo.bmHeight);
- cursor.setPF(format);
- cursor.hotspot = Point(iconInfo.xHotspot, iconInfo.yHotspot);
-
- // - Get the AND and XOR masks. There is only an XOR mask if this is not a
- // colour cursor.
-
+ width = maskInfo.bmWidth;
+ height = maskInfo.bmHeight;
if (!iconInfo.hbmColor)
- cursor.setSize(cursor.width(), cursor.height() / 2);
- rdr::U8Array mask(maskInfo.bmWidthBytes * maskInfo.bmHeight);
- rdr::U8* xorMask = mask.buf + cursor.height() * maskInfo.bmWidthBytes;
+ height /= 2;
- if (!GetBitmapBits(iconInfo.hbmMask,
- maskInfo.bmWidthBytes * maskInfo.bmHeight, mask.buf))
- throw rdr::SystemException("GetBitmapBits failed", GetLastError());
+ buffer.buf = new rdr::U8[width * height * 4];
- // Configure the cursor bitmap
- cursorBm.setSize(cursor.width(), cursor.height());
+ Point hotspot = Point(iconInfo.xHotspot, iconInfo.yHotspot);
- // Draw the cursor into the bitmap
- BitmapDC dc(device, cursorBm.bitmap);
- if (!DrawIconEx(dc, 0, 0, hCursor, 0, 0, 0, NULL, DI_NORMAL | DI_COMPAT))
- throw rdr::SystemException("unable to render cursor", GetLastError());
+ if (iconInfo.hbmColor) {
+ // Colour cursor
- // Replace any XORed pixels with xorColour, because RFB doesn't support
- // XORing of cursors. XORing is used for the I-beam cursor, which is most
- // often used over a white background, but also sometimes over a black
- // background. We set the XOR'd pixels to black, then draw a white outline
- // around the whole cursor.
+ BITMAPV5HEADER bi;
+ BitmapDC dc(device, iconInfo.hbmColor);
- // *** should we replace any pixels not set in mask to zero, to ensure
- // that irrelevant data doesn't screw compression?
+ memset(&bi, 0, sizeof(BITMAPV5HEADER));
- bool doOutline = false;
- if (!iconInfo.hbmColor) {
- rwbuffer = cursorBm.getBufferRW(cursorBm.getRect(), &stride);
- Pixel xorColour = format.pixelFromRGB((rdr::U16)0, (rdr::U16)0, (rdr::U16)0);
- for (int y = 0; y < cursor.height(); y++) {
- for (int x = 0; x < cursor.width(); x++) {
- int byte = y * maskInfo.bmWidthBytes + x / 8;
- int bit = 7 - x % 8;
- if ((mask.buf[byte] & (1 << bit)) && (xorMask[byte] & (1 << bit)))
- {
- mask.buf[byte] &= ~(1 << bit);
+ bi.bV5Size = sizeof(BITMAPV5HEADER);
+ bi.bV5Width = width;
+ bi.bV5Height = -height; // Negative for top-down
+ bi.bV5Planes = 1;
+ bi.bV5BitCount = 32;
+ bi.bV5Compression = BI_BITFIELDS;
+ bi.bV5RedMask = 0x000000FF;
+ bi.bV5GreenMask = 0x0000FF00;
+ bi.bV5BlueMask = 0x00FF0000;
+ bi.bV5AlphaMask = 0xFF000000;
- switch (format.bpp) {
- case 8:
- rwbuffer[y * cursor.width() + x] = xorColour; break;
- case 16:
- rwbuffer[y * cursor.width() + x] = xorColour; break;
- case 32:
- rwbuffer[y * cursor.width() + x] = xorColour; break;
- }
+ if (!GetDIBits(dc, iconInfo.hbmColor, 0, height,
+ buffer.buf, (LPBITMAPINFO)&bi, DIB_RGB_COLORS))
+ throw rdr::SystemException("GetDIBits", GetLastError());
- doOutline = true;
- }
+ // We may not get the RGBA order we want, so shuffle things around
+ int ridx, gidx, bidx, aidx;
+
+ ridx = __builtin_ffs(bi.bV5RedMask) / 8;
+ gidx = __builtin_ffs(bi.bV5GreenMask) / 8;
+ bidx = __builtin_ffs(bi.bV5BlueMask) / 8;
+ // Usually not set properly
+ aidx = 6 - ridx - gidx - bidx;
+
+ if ((bi.bV5RedMask != ((unsigned)0xff << ridx*8)) ||
+ (bi.bV5GreenMask != ((unsigned)0xff << gidx*8)) ||
+ (bi.bV5BlueMask != ((unsigned)0xff << bidx*8)))
+ throw rdr::Exception("unsupported cursor colour format");
+
+ rdr::U8* rwbuffer = buffer.buf;
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ rdr::U8 r, g, b, a;
+
+ r = rwbuffer[ridx];
+ g = rwbuffer[gidx];
+ b = rwbuffer[bidx];
+ a = rwbuffer[aidx];
+
+ rwbuffer[0] = r;
+ rwbuffer[1] = g;
+ rwbuffer[2] = b;
+ rwbuffer[3] = a;
+
+ rwbuffer += 4;
}
}
- cursorBm.commitBufferRW(cursorBm.getRect());
+ } else {
+ // B/W cursor
+
+ rdr::U8Array mask(maskInfo.bmWidthBytes * maskInfo.bmHeight);
+ rdr::U8* andMask = mask.buf;
+ rdr::U8* xorMask = mask.buf + height * maskInfo.bmWidthBytes;
+
+ if (!GetBitmapBits(iconInfo.hbmMask,
+ maskInfo.bmWidthBytes * maskInfo.bmHeight, mask.buf))
+ throw rdr::SystemException("GetBitmapBits", GetLastError());
+
+ bool doOutline = false;
+ rdr::U8* rwbuffer = buffer.buf;
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int byte = y * maskInfo.bmWidthBytes + x / 8;
+ int bit = 7 - x % 8;
+
+ if (!(andMask[byte] & (1 << bit))) {
+ // Valid pixel, so make it opaque
+ rwbuffer[3] = 0xff;
+
+ // Black or white?
+ if (xorMask[byte] & (1 << bit))
+ rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = 0xff;
+ else
+ rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = 0;
+ } else if (xorMask[byte] & (1 << bit)) {
+ // Replace any XORed pixels with black, because RFB doesn't support
+ // XORing of cursors. XORing is used for the I-beam cursor, which is most
+ // often used over a white background, but also sometimes over a black
+ // background. We set the XOR'd pixels to black, then draw a white outline
+ // around the whole cursor.
+
+ rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = 0;
+ rwbuffer[3] = 0xff;
+
+ doOutline = true;
+ } else {
+ // Transparent pixel
+ rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = rwbuffer[3] = 0;
+ }
+
+ rwbuffer += 4;
+ }
+ }
+
+ if (doOutline) {
+ vlog.debug("drawing cursor outline!");
+
+ // The buffer needs to be slightly larger to make sure there
+ // is room for the outline pixels
+ rdr::U8Array outline((width + 2)*(height + 2)*4);
+ memset(outline.buf, 0, (width + 2)*(height + 2)*4);
+
+ // Pass 1, outline everything
+ rdr::U8* in = buffer.buf;
+ rdr::U8* out = outline.buf + width*4 + 4;
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ // Visible pixel?
+ if (in[3] > 0) {
+ // Outline above...
+ memset(out - (width+2)*4 - 4, 0xff, 4 * 3);
+ // ...besides...
+ memset(out - 4, 0xff, 4 * 3);
+ // ...and above
+ memset(out + (width+2)*4 - 4, 0xff, 4 * 3);
+ }
+ in += 4;
+ out += 4;
+ }
+ // outline is slightly larger
+ out += 2*4;
+ }
+
+ // Pass 2, overwrite with actual cursor
+ in = buffer.buf;
+ out = outline.buf + width*4 + 4;
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ if (in[3] > 0)
+ memcpy(out, in, 4);
+ in += 4;
+ out += 4;
+ }
+ out += 2*4;
+ }
+
+ width += 2;
+ height += 2;
+ hotspot.x += 1;
+ hotspot.y += 1;
+
+ delete [] buffer.buf;
+ buffer.buf = outline.takeBuf();
+ }
}
- // Finally invert the AND mask so it's suitable for RFB and pack it into
- // the minimum number of bytes per row.
-
- int maskBytesPerRow = (cursor.width() + 7) / 8;
-
- for (int j = 0; j < cursor.height(); j++) {
- for (int i = 0; i < maskBytesPerRow; i++)
- cursor.mask.buf[j * maskBytesPerRow + i]
- = ~mask.buf[j * maskInfo.bmWidthBytes + i];
- }
-
- if (doOutline) {
- vlog.debug("drawing cursor outline!");
-
- buffer = cursorBm.getBuffer(cursorBm.getRect(), &stride);
- cursor.imageRect(cursorBm.getRect(), buffer, stride);
-
- cursor.drawOutline(format.pixelFromRGB((rdr::U16)0xffff, (rdr::U16)0xffff, (rdr::U16)0xffff));
-
- buffer = cursor.getBuffer(cursor.getRect(), &stride);
- cursorBm.imageRect(cursor.getRect(), buffer, stride);
- }
-
- buffer = cursorBm.getBuffer(cursorBm.getRect(), &stride);
- server->setCursor(cursor.width(), cursor.height(), cursor.hotspot,
- buffer, cursor.mask.buf);
+ server->setCursor(width, height, hotspot, buffer.buf);
} catch (rdr::Exception& e) {
vlog.error("%s", e.str());
diff --git a/win/rfb_win32/DeviceFrameBuffer.h b/win/rfb_win32/DeviceFrameBuffer.h
index 8e280f8..6fccf9f 100644
--- a/win/rfb_win32/DeviceFrameBuffer.h
+++ b/win/rfb_win32/DeviceFrameBuffer.h
@@ -92,8 +92,6 @@
Point desktopToDevice(const Point p) const {return p.translate(deviceCoords.tl);}
HDC device;
- DIBSectionBuffer cursorBm;
- Cursor cursor;
Rect deviceCoords;
bool ignoreGrabErrors;
};
diff --git a/win/rfb_win32/LaunchProcess.h b/win/rfb_win32/LaunchProcess.h
index 38521dc..7321378 100644
--- a/win/rfb_win32/LaunchProcess.h
+++ b/win/rfb_win32/LaunchProcess.h
@@ -44,7 +44,7 @@
// as an extra flag to the process creation call.
void start(HANDLE userToken, bool createConsole=false);
- // Detatch from the child process. After detatching from a child
+ // Detach from the child process. After detaching from a child
// process, no other methods should be called on the object
// that started it
void detach();
diff --git a/win/rfb_win32/keymap.h b/win/rfb_win32/keymap.h
index a340d09..664312a 100644
--- a/win/rfb_win32/keymap.h
+++ b/win/rfb_win32/keymap.h
@@ -62,7 +62,7 @@
{ XK_Help, VK_HELP, 0 },
{ XK_Break, VK_CANCEL, 1 },
- // Auxilliary Functions - must come before XK_KP_F1, etc
+ // Auxiliary Functions - must come before XK_KP_F1, etc
{ XK_F1, VK_F1, 0 },
{ XK_F2, VK_F2, 0 },
diff --git a/win/winvnc/VNCServerWin32.h b/win/winvnc/VNCServerWin32.h
index 225e634..f384bbe 100644
--- a/win/winvnc/VNCServerWin32.h
+++ b/win/winvnc/VNCServerWin32.h
@@ -91,7 +91,7 @@
virtual void processAddressChange();
// RegConfig::Callback interface
- // Called via the EventManager whenver RegConfig sees the registry change
+ // Called via the EventManager whenever RegConfig sees the registry change
virtual void regConfigChanged();
// EventHandler interface