From ae93cd6840de4fa0038ce743ff7df32a45d11e70 Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Fri, 17 Feb 2023 14:04:32 +0200 Subject: [PATCH 1/6] Initial support for intensity-tinted alpha watermark --- CMakeLists.txt | 3 + common/rfb/CMakeLists.txt | 5 +- common/rfb/ConnParams.cxx | 2 + common/rfb/EncodeManager.cxx | 30 ++++ common/rfb/EncodeManager.h | 1 + common/rfb/Region.cxx | 5 + common/rfb/Region.h | 2 + common/rfb/ServerCore.cxx | 17 +++ common/rfb/ServerCore.h | 4 + common/rfb/TightConstants.h | 3 +- common/rfb/TightEncoder.cxx | 22 +++ common/rfb/TightEncoder.h | 5 + common/rfb/VNCSConnectionST.cxx | 3 + common/rfb/VNCServerST.cxx | 4 + common/rfb/VNCServerST.h | 2 + common/rfb/Watermark.cxx | 233 ++++++++++++++++++++++++++++++ common/rfb/Watermark.h | 43 ++++++ unix/xserver/hw/vnc/Xvnc.man | 22 +++ unix/xserver/hw/vnc/vncExtInit.cc | 4 + 19 files changed, 407 insertions(+), 3 deletions(-) create mode 100644 common/rfb/Watermark.cxx create mode 100644 common/rfb/Watermark.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ab5725d..e463d54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,9 @@ endif() # Check for zlib find_package(ZLIB REQUIRED) +# Check for libpng +find_package(PNG REQUIRED) + # Check for libjpeg find_package(JPEG REQUIRED) diff --git a/common/rfb/CMakeLists.txt b/common/rfb/CMakeLists.txt index 01ce5cb..905e76e 100644 --- a/common/rfb/CMakeLists.txt +++ b/common/rfb/CMakeLists.txt @@ -1,4 +1,4 @@ -include_directories(${CMAKE_SOURCE_DIR}/common ${JPEG_INCLUDE_DIR} +include_directories(${CMAKE_SOURCE_DIR}/common ${JPEG_INCLUDE_DIR} ${PNG_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/unix/kasmvncpasswd) set(RFB_SOURCES @@ -65,6 +65,7 @@ set(RFB_SOURCES VNCServerST.cxx ZRLEEncoder.cxx ZRLEDecoder.cxx + Watermark.cxx cpuid.cxx encodings.cxx util.cxx @@ -79,7 +80,7 @@ if(WIN32) set(RFB_SOURCES ${RFB_SOURCES} WinPasswdValidator.cxx) endif(WIN32) -set(RFB_LIBRARIES ${JPEG_LIBRARIES} os rdr Xregion) +set(RFB_LIBRARIES ${JPEG_LIBRARIES} ${PNG_LIBRARIES} os rdr Xregion) if(HAVE_PAM) set(RFB_SOURCES ${RFB_SOURCES} UnixPasswordValidator.cxx diff --git a/common/rfb/ConnParams.cxx b/common/rfb/ConnParams.cxx index 177ab73..77269db 100644 --- a/common/rfb/ConnParams.cxx +++ b/common/rfb/ConnParams.cxx @@ -285,6 +285,8 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings) // QOI-specific overrides if (supportsQOI) useCopyRect = false; + if (Server::DLP_WatermarkImage[0]) + useCopyRect = false; } void ConnParams::setLEDState(unsigned int state) diff --git a/common/rfb/EncodeManager.cxx b/common/rfb/EncodeManager.cxx index bcc9772..cc6f571 100644 --- a/common/rfb/EncodeManager.cxx +++ b/common/rfb/EncodeManager.cxx @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -162,6 +163,7 @@ static void updateMaxVideoRes(uint16_t *x, uint16_t *y) { EncodeManager::EncodeManager(SConnection* conn_, EncCache *encCache_) : conn(conn_), dynamicQualityMin(-1), dynamicQualityOff(-1), areaCur(0), videoDetected(false), videoTimer(this), + watermarkStats(0), maxEncodingTime(0), framesSinceEncPrint(0), encCache(encCache_) { @@ -299,6 +301,11 @@ void EncodeManager::logStats() vlog.info(" Total: %s, %s", a, b); iecPrefix(bytes, "B", a, sizeof(a)); vlog.info(" %s (1:%g ratio)", a, ratio); + + if (watermarkData) { + siPrefix(watermarkStats, "B", a, sizeof(a)); + vlog.info(" Watermark data sent: %s", a); + } } bool EncodeManager::supported(int encoding) @@ -408,8 +415,14 @@ void EncodeManager::doUpdate(bool allowLossy, const Region& changed_, nRects += copypassed.size(); nRects += computeNumRects(changed); nRects += computeNumRects(cursorRegion); + + if (watermarkData) + nRects++; } + if (watermarkData) + packWatermark(changed); + conn->writer()->writeFramebufferUpdateStart(nRects); writeCopyRects(copied, copyDelta); @@ -427,6 +440,23 @@ void EncodeManager::doUpdate(bool allowLossy, const Region& changed_, if (!videoDetected) // In case detection happened between the calls writeRects(cursorRegion, renderedCursor); + if (watermarkData) { + beforeLength = conn->getOutStream(conn->cp.supportsUdp)->length(); + + const Rect rect(0, 0, pb->width(), pb->height()); + TightEncoder *encoder = ((TightEncoder *) encoders[encoderTight]); + + conn->writer()->startRect(rect, encoder->encoding); + encoder->writeWatermarkRect(watermarkData, watermarkDataLen, + watermarkInfo.r, + watermarkInfo.g, + watermarkInfo.b, + watermarkInfo.a); + conn->writer()->endRect(); + + watermarkStats += conn->getOutStream(conn->cp.supportsUdp)->length() - beforeLength; + } + updateQualities(); conn->writer()->writeFramebufferUpdateEnd(); diff --git a/common/rfb/EncodeManager.h b/common/rfb/EncodeManager.h index dfa5e53..20ba837 100644 --- a/common/rfb/EncodeManager.h +++ b/common/rfb/EncodeManager.h @@ -193,6 +193,7 @@ namespace rfb { unsigned updates; EncoderStats copyStats; StatsVector stats; + unsigned long long watermarkStats; int activeType; int beforeLength; size_t curMaxUpdateSize; diff --git a/common/rfb/Region.cxx b/common/rfb/Region.cxx index ed0ddb6..98ca2c6 100644 --- a/common/rfb/Region.cxx +++ b/common/rfb/Region.cxx @@ -239,3 +239,8 @@ void rfb::Region::debug_print(const char* prefix) const xrgn->rects[i].y2-xrgn->rects[i].y1); } } + +bool rfb::Region::contains(int x, int y) const +{ + return XPointInRegion(xrgn, x, y); +} diff --git a/common/rfb/Region.h b/common/rfb/Region.h index 7cc0eaa..7f10708 100644 --- a/common/rfb/Region.h +++ b/common/rfb/Region.h @@ -73,6 +73,8 @@ namespace rfb { void debug_print(const char *prefix) const; + bool contains(int x, int y) const; + protected: struct _XRegion* xrgn; diff --git a/common/rfb/ServerCore.cxx b/common/rfb/ServerCore.cxx index c95bca4..2af90bb 100644 --- a/common/rfb/ServerCore.cxx +++ b/common/rfb/ServerCore.cxx @@ -185,6 +185,23 @@ rfb::BoolParameter rfb::Server::DLP_RegionAllowRelease "Allow click releases inside the blacked-out region", true); +rfb::IntParameter rfb::Server::DLP_WatermarkRepeatSpace +("DLP_WatermarkRepeatSpace", + "Number of pixels between repeats of the watermark", + 0, 0, 4096); +rfb::StringParameter rfb::Server::DLP_WatermarkImage +("DLP_WatermarkImage", + "PNG file to use as a watermark", + ""); +rfb::StringParameter rfb::Server::DLP_WatermarkLocation +("DLP_WatermarkLocation", + "Place the watermark at this position from the corner.", + ""); +rfb::StringParameter rfb::Server::DLP_WatermarkTint +("DLP_WatermarkTint", + "Tint the greyscale watermark by this color.", + "255,255,255,255"); + rfb::StringParameter rfb::Server::maxVideoResolution ("MaxVideoResolution", "When in video mode, downscale the screen to max this size.", diff --git a/common/rfb/ServerCore.h b/common/rfb/ServerCore.h index 46db780..a33e7aa 100644 --- a/common/rfb/ServerCore.h +++ b/common/rfb/ServerCore.h @@ -48,9 +48,13 @@ namespace rfb { static IntParameter DLP_ClipAcceptMax; static IntParameter DLP_ClipDelay; static IntParameter DLP_KeyRateLimit; + static IntParameter DLP_WatermarkRepeatSpace; static StringParameter DLP_ClipLog; static StringParameter DLP_Region; static StringParameter DLP_Clip_Types; + static StringParameter DLP_WatermarkImage; + static StringParameter DLP_WatermarkLocation; + static StringParameter DLP_WatermarkTint; static BoolParameter DLP_RegionAllowClick; static BoolParameter DLP_RegionAllowRelease; static IntParameter jpegVideoQuality; diff --git a/common/rfb/TightConstants.h b/common/rfb/TightConstants.h index 70f0ea2..fa89d23 100644 --- a/common/rfb/TightConstants.h +++ b/common/rfb/TightConstants.h @@ -25,7 +25,8 @@ namespace rfb { const unsigned int tightPng = 0x0a; const unsigned int tightWebp = 0x0b; const unsigned int tightQoi = 0x0c; - const unsigned int tightMaxSubencoding = 0x0c; + const unsigned int tightIT = 0x0d; + const unsigned int tightMaxSubencoding = 0x0d; // Filters to improve compression efficiency const unsigned int tightFilterCopy = 0x00; diff --git a/common/rfb/TightEncoder.cxx b/common/rfb/TightEncoder.cxx index fadabfa..dc28e0c 100644 --- a/common/rfb/TightEncoder.cxx +++ b/common/rfb/TightEncoder.cxx @@ -277,6 +277,28 @@ void TightEncoder::resetZlib() zlibNeedsReset = true; } +void TightEncoder::writeWatermarkRect(const rdr::U8 *data, const unsigned len, + const rdr::U8 r, + const rdr::U8 g, + const rdr::U8 b, + const rdr::U8 a) +{ + rdr::OutStream* os; + + os = conn->getOutStream(conn->cp.supportsUdp); + + os->writeU8(tightIT << 4); + + writeCompact(os, len + 4); + + os->writeU8(r); + os->writeU8(g); + os->writeU8(b); + os->writeU8(a); + + os->writeBytes(data, len); +} + // // Including BPP-dependent implementation of the encoder. // diff --git a/common/rfb/TightEncoder.h b/common/rfb/TightEncoder.h index 370b50c..222b636 100644 --- a/common/rfb/TightEncoder.h +++ b/common/rfb/TightEncoder.h @@ -39,6 +39,11 @@ namespace rfb { virtual void writeSolidRect(int width, int height, const PixelFormat& pf, const rdr::U8* colour); + void writeWatermarkRect(const rdr::U8 *data, const unsigned len, + const rdr::U8 r, + const rdr::U8 g, + const rdr::U8 b, + const rdr::U8 a); void resetZlib(); protected: diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index 5629ed6..6c96cc6 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -1777,6 +1777,9 @@ void VNCSConnectionST::udpDowngrade(const bool byServer) cp.useCopyRect = true; encodeManager.resetZlib(); + if (Server::DLP_WatermarkImage[0]) + cp.useCopyRect = false; + vlog.info("Client %s downgrading from udp by %s", sock->getPeerAddress(), byServer ? "the server" : "its own request"); } diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index 7abd972..2e8d16f 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -62,6 +62,7 @@ #include #include #include +#include #include #include @@ -1048,6 +1049,9 @@ void VNCServerST::writeUpdate() memset(&jpegstats, 0, sizeof(EncodeManager::codecstats_t)); memset(&webpstats, 0, sizeof(EncodeManager::codecstats_t)); + if (watermarkData) + updateWatermark(); + for (ci = clients.begin(); ci != clients.end(); ci = ci_next) { ci_next = ci; ci_next++; diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h index bdd43b0..da84cab 100644 --- a/common/rfb/VNCServerST.h +++ b/common/rfb/VNCServerST.h @@ -256,6 +256,8 @@ namespace rfb { bool getComparerState(); + void updateWatermark(); + QueryConnectionHandler* queryConnectionHandler; KeyRemapper* keyRemapper; diff --git a/common/rfb/Watermark.cxx b/common/rfb/Watermark.cxx new file mode 100644 index 0000000..f83b0b0 --- /dev/null +++ b/common/rfb/Watermark.cxx @@ -0,0 +1,233 @@ +/* Copyright (C) 2023 Kasm + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "Watermark.h" + +using namespace rfb; + +static LogWriter vlog("watermark"); + +watermarkInfo_t watermarkInfo; + +uint8_t *watermarkData, *watermarkUnpacked, *watermarkTmp; +uint32_t watermarkDataLen; +static uint16_t rw, rh; + +#define MAXW 4096 +#define MAXH 4096 + +static bool loadimage(const char path[]) { + + FILE *f = fopen(path, "r"); + if (!f) { + vlog.error("Can't open %s", path); + return false; + } + + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL); + if (!png_ptr) return false; + png_infop info = png_create_info_struct(png_ptr); + if (!info) return false; + if (setjmp(png_jmpbuf(png_ptr))) return false; + + png_init_io(png_ptr, f); + png_read_png(png_ptr, info, + PNG_TRANSFORM_PACKING | + PNG_TRANSFORM_STRIP_16 | + PNG_TRANSFORM_STRIP_ALPHA | + PNG_TRANSFORM_EXPAND, NULL); + + uint8_t **rows = png_get_rows(png_ptr, info); + const unsigned imgw = png_get_image_width(png_ptr, info); + const unsigned imgh = png_get_image_height(png_ptr, info); + + watermarkInfo.w = imgw; + watermarkInfo.h = imgh; + watermarkInfo.src = (uint8_t *) calloc(imgw, imgh); + + unsigned x, y; + for (y = 0; y < imgh; y++) { + for (x = 0; x < imgw; x++) { + const uint8_t r = rows[y][x * 3 + 0]; + const uint8_t g = rows[y][x * 3 + 1]; + const uint8_t b = rows[y][x * 3 + 2]; + + const uint8_t grey = r * .2126f + + g * .7152f + + b * .0722f; + + const uint8_t out = (grey + 8) >> 4; + watermarkInfo.src[y * imgw + x] = out < 16 ? out : 15; + } + } + + fclose(f); + png_destroy_info_struct(png_ptr, &info); + png_destroy_read_struct(&png_ptr, NULL, NULL); + + return true; +} + +bool watermarkInit() { + memset(&watermarkInfo, 0, sizeof(watermarkInfo_t)); + watermarkData = watermarkUnpacked = watermarkTmp = NULL; + rw = rh = 0; + + if (!Server::DLP_WatermarkImage[0]) + return true; + + if (!loadimage(Server::DLP_WatermarkImage)) + return false; + + if (Server::DLP_WatermarkRepeatSpace && Server::DLP_WatermarkLocation[0]) { + vlog.error("Repeat and location can't be used together"); + return false; + } + + if (sscanf(Server::DLP_WatermarkTint, "%hhu,%hhu,%hhu,%hhu", + &watermarkInfo.r, + &watermarkInfo.g, + &watermarkInfo.b, + &watermarkInfo.a) != 4) { + vlog.error("Invalid tint"); + return false; + } + + watermarkInfo.repeat = Server::DLP_WatermarkRepeatSpace; + + if (Server::DLP_WatermarkLocation[0]) { + if (sscanf(Server::DLP_WatermarkLocation, "%hd,%hd", + &watermarkInfo.x, + &watermarkInfo.y) != 2) { + vlog.error("Invalid location"); + return false; + } + } + + watermarkUnpacked = (uint8_t *) calloc(MAXW, MAXH); + watermarkTmp = (uint8_t *) calloc(MAXW, MAXH / 2); + watermarkData = (uint8_t *) calloc(MAXW, MAXH / 2); + + return true; +} + +// update the screen-size rendered watermark whenever the screen is resized +void VNCServerST::updateWatermark() { + if (rw == pb->width() && + rh == pb->height()) + return; + + rw = pb->width(); + rh = pb->height(); + + memset(watermarkUnpacked, 0, rw * rh); + + uint16_t x, y, srcy; + + if (watermarkInfo.repeat) { + for (y = 0, srcy = 0; y < rh; y++) { + for (x = 0; x < rw;) { + if (x + watermarkInfo.w < rw) + memcpy(&watermarkUnpacked[y * rw + x], + &watermarkInfo.src[srcy * watermarkInfo.w], + watermarkInfo.w); + else + memcpy(&watermarkUnpacked[y * rw + x], + &watermarkInfo.src[srcy * watermarkInfo.w], + rw - x); + + x += watermarkInfo.w + watermarkInfo.repeat; + } + + srcy++; + if (srcy == watermarkInfo.h) { + srcy = 0; + y += watermarkInfo.repeat; + } + } + } else { + int16_t sx, sy; + + if (!watermarkInfo.x) + sx = (rw - watermarkInfo.w) / 2; + else if (watermarkInfo.x > 0) + sx = watermarkInfo.x; + else + sx = rw - watermarkInfo.w + watermarkInfo.x; + + if (sx < 0) + sx = 0; + + if (!watermarkInfo.y) + sy = (rh - watermarkInfo.h) / 2; + else if (watermarkInfo.y > 0) + sy = watermarkInfo.y; + else + sy = rh - watermarkInfo.h + watermarkInfo.y; + + if (sy < 0) + sy = 0; + + for (y = 0; y < watermarkInfo.h; y++) { + if (sx + watermarkInfo.w < rw) + memcpy(&watermarkUnpacked[(sy + y) * rw + sx], + &watermarkInfo.src[y * watermarkInfo.w], + watermarkInfo.w); + else + memcpy(&watermarkUnpacked[(sy + y) * rw + sx], + &watermarkInfo.src[y * watermarkInfo.w], + rw - sx); + } + } +} + +void packWatermark(const Region &changed) { + // Take the expanded 4-bit data, filter it by the changed rects, pack + // to shared bytes, and compress with zlib + + uint16_t x, y; + uint8_t pix[2], cur = 0; + uint8_t *dst = watermarkTmp; + for (y = 0; y < rh; y++) { + for (x = 0; x < rw; x++) { + pix[cur] = 0; + if (changed.contains(x, y)) + pix[cur] = watermarkUnpacked[y * rw + x]; + + if (cur || (y == rh - 1 && x == rw - 1)) + *dst++ = pix[0] | (pix[1] << 4); + + cur ^= 1; + } + } + + uLong destLen = MAXW * MAXH / 2; + if (compress2(watermarkData, &destLen, watermarkTmp, rw * rh / 2 + 1, 1) != Z_OK) + vlog.error("Zlib compression error"); + + watermarkDataLen = destLen; +} diff --git a/common/rfb/Watermark.h b/common/rfb/Watermark.h new file mode 100644 index 0000000..6cb493d --- /dev/null +++ b/common/rfb/Watermark.h @@ -0,0 +1,43 @@ +/* Copyright (C) 2023 Kasm + * + * 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 WATERMARK_H +#define WATERMARK_H + +#include +#include + +struct watermarkInfo_t { + uint8_t *src; + uint16_t w, h; + + int16_t x, y; + uint16_t repeat; + + uint8_t r, g, b, a; +}; + +extern watermarkInfo_t watermarkInfo; + +bool watermarkInit(); +void packWatermark(const rfb::Region &changed); // filter and pack the watermark for sending + +extern uint8_t *watermarkData; +extern uint32_t watermarkDataLen; + +#endif diff --git a/unix/xserver/hw/vnc/Xvnc.man b/unix/xserver/hw/vnc/Xvnc.man index 16ba11d..8da31b6 100644 --- a/unix/xserver/hw/vnc/Xvnc.man +++ b/unix/xserver/hw/vnc/Xvnc.man @@ -341,6 +341,28 @@ Log clipboard and keyboard actions. Info logs just clipboard direction and size, verbose adds the contents for both. . .TP +.B \-DLP_WatermarkImage \fIpath/to/file.png\fP +Add a watermark. The PNG file should be greyscale, black is treated as transparent +and white as opaque. +. +.TP +.B \-DLP_WatermarkLocation \fIx,y\fP +Place the watermark at this position from the corner. Positive numbers are from top-left, +negative from bottom-right. Negative numbers count from the bottom-right edge of the image. +If not set, the watermark will be centered. Cannot be used together with repeat. +. +.TP +.B \-DLP_WatermarkRepeatSpace \fInum\fP +If set, repeat the watermark over the entire image, with \fBnum\fP pixels between +repetitions. Cannot be used together with location. +. +.TP +.B \-DLP_WatermarkTint \fIr,g,b,a\fP +Tint the greyscale watermark by this color. Default is 255,255,255,255 - full white. +The color components can be used to colorize the greyscale watermark, and the alpha +can be used to make it fainter. +. +.TP .B \-selfBench Run a set of self-benchmarks and exit. . diff --git a/unix/xserver/hw/vnc/vncExtInit.cc b/unix/xserver/hw/vnc/vncExtInit.cc index 38cbf87..53136b9 100644 --- a/unix/xserver/hw/vnc/vncExtInit.cc +++ b/unix/xserver/hw/vnc/vncExtInit.cc @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -232,6 +233,9 @@ void vncExtensionInit(void) dummyY < 16) vncFatalError("Invalid value to %s", Server::maxVideoResolution.getName()); + if (!watermarkInit()) + vncFatalError("Invalid watermark params"); + pipe(wakeuppipe); const int flags = fcntl(wakeuppipe[0], F_GETFL, 0); fcntl(wakeuppipe[0], F_SETFL, flags | O_NONBLOCK); From 5b3786e5ad4771802d30e97ac246027b4b94fa4f Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Fri, 17 Feb 2023 14:45:31 +0200 Subject: [PATCH 2/6] Small optimization --- common/rfb/Watermark.cxx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/rfb/Watermark.cxx b/common/rfb/Watermark.cxx index f83b0b0..99af084 100644 --- a/common/rfb/Watermark.cxx +++ b/common/rfb/Watermark.cxx @@ -212,10 +212,13 @@ void packWatermark(const Region &changed) { uint16_t x, y; uint8_t pix[2], cur = 0; uint8_t *dst = watermarkTmp; + + const Rect &bounding = changed.get_bounding_rect(); + for (y = 0; y < rh; y++) { for (x = 0; x < rw; x++) { pix[cur] = 0; - if (changed.contains(x, y)) + if (bounding.contains(Point(x, y)) && changed.contains(x, y)) pix[cur] = watermarkUnpacked[y * rw + x]; if (cur || (y == rh - 1 && x == rw - 1)) From 834bb9a92555e74bb8f5be58efe44dd20d7acfca Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Fri, 17 Feb 2023 14:52:26 +0200 Subject: [PATCH 3/6] Another optimization --- common/rfb/Watermark.cxx | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/common/rfb/Watermark.cxx b/common/rfb/Watermark.cxx index 99af084..6a21dc1 100644 --- a/common/rfb/Watermark.cxx +++ b/common/rfb/Watermark.cxx @@ -216,15 +216,27 @@ void packWatermark(const Region &changed) { const Rect &bounding = changed.get_bounding_rect(); for (y = 0; y < rh; y++) { - for (x = 0; x < rw; x++) { - pix[cur] = 0; - if (bounding.contains(Point(x, y)) && changed.contains(x, y)) - pix[cur] = watermarkUnpacked[y * rw + x]; + // Is the entire line outside the changed area? + if (bounding.tl.y > y || bounding.br.y < y) { + for (x = 0; x < rw; x++) { + pix[cur] = 0; - if (cur || (y == rh - 1 && x == rw - 1)) - *dst++ = pix[0] | (pix[1] << 4); + if (cur || (y == rh - 1 && x == rw - 1)) + *dst++ = pix[0] | (pix[1] << 4); - cur ^= 1; + cur ^= 1; + } + } else { + for (x = 0; x < rw; x++) { + pix[cur] = 0; + if (bounding.contains(Point(x, y)) && changed.contains(x, y)) + pix[cur] = watermarkUnpacked[y * rw + x]; + + if (cur || (y == rh - 1 && x == rw - 1)) + *dst++ = pix[0] | (pix[1] << 4); + + cur ^= 1; + } } } From 6a8adf0fb153fdf5f35d198c9ccfa0eebf759b34 Mon Sep 17 00:00:00 2001 From: mattmcclaskey Date: Fri, 3 Mar 2023 08:58:14 -0500 Subject: [PATCH 4/6] Add yaml configs for new settings --- spec/fixtures/defaults_config.yaml | 5 ++++ unix/kasmvnc_defaults.yaml | 8 ++++-- unix/vncserver | 44 ++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/spec/fixtures/defaults_config.yaml b/spec/fixtures/defaults_config.yaml index 71b57fe..9c214c5 100644 --- a/spec/fixtures/defaults_config.yaml +++ b/spec/fixtures/defaults_config.yaml @@ -42,6 +42,11 @@ data_loss_prevention: keyboard: enabled: true rate_limit: unlimited + watermark: + # image: /etc/kasmvnc/picture.png + # location: 10,10 + # tint: 255,20,20,128 + # repeat_spacing: 10 logging: level: off diff --git a/unix/kasmvnc_defaults.yaml b/unix/kasmvnc_defaults.yaml index a4997e2..1556825 100644 --- a/unix/kasmvnc_defaults.yaml +++ b/unix/kasmvnc_defaults.yaml @@ -84,9 +84,13 @@ data_loss_prevention: keyboard: enabled: true rate_limit: unlimited - # "verbose" SETTING LOGS YOUR PRIVATE INFORMATION. Keypresses and clipboard - # content. + watermark: + # image: /etc/kasmvnc/picture.png + # location: 10,10 + # tint: 255,20,20,128 + # repeat_spacing: 10 logging: + # "verbose" SETTING LOGS YOUR PRIVATE INFORMATION. Keypresses and clipboard content level: off encoding: diff --git a/unix/vncserver b/unix/vncserver index cae50ac..14a3c22 100755 --- a/unix/vncserver +++ b/unix/vncserver @@ -1720,6 +1720,50 @@ sub DefineConfigToCLIConversion { $value; } }), + KasmVNC::CliOption->new({ + name => 'DLP_WatermarkImage', + configKeys => [ + KasmVNC::ConfigKey->new({ + name => "data_loss_prevention.watermark.image", + type => KasmVNC::ConfigKey::ANY + }) + ] + }), + KasmVNC::CliOption->new({ + name => 'DLP_WatermarkLocation', + configKeys => [ + KasmVNC::ConfigKey->new({ + name => "data_loss_prevention.watermark.location", + type => KasmVNC::ConfigKey::ANY, + validator => KasmVNC::PatternValidator->new({ + pattern => qr/^\d+,\d+$/, + errorMessage => "Must be an x and y offset separated by a comma: 10,10" + }) + }) + ] + }), + KasmVNC::CliOption->new({ + name => 'DLP_WatermarkTint', + configKeys => [ + KasmVNC::ConfigKey->new({ + name => "data_loss_prevention.watermark.tint", + type => KasmVNC::ConfigKey::ANY, + validator => KasmVNC::PatternValidator->new({ + pattern => qr/^\d{1,3},\d{1,3},\d{1,3},\d{1,3}$/, + errorMessage => "Must be RBGA formatted: 255,255,255,128" + }) + }) + ] + }), + KasmVNC::CliOption->new({ + name => 'DLP_WatermarkRepeatSpace', + configKeys => [ + KasmVNC::ConfigKey->new({ + name => "data_loss_prevention.watermark.repeat_spacing", + type => KasmVNC::ConfigKey::INT + }) + ] + }), KasmVNC::CliOption->new({ name => 'DLP_Log', configKeys => [ From cc4ec630c2a709a703bc44f5ca7326602f106e83 Mon Sep 17 00:00:00 2001 From: mattmcclaskey Date: Tue, 14 Mar 2023 08:26:20 -0400 Subject: [PATCH 5/6] update noVNC ref --- kasmweb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kasmweb b/kasmweb index 31b1a93..f17512e 160000 --- a/kasmweb +++ b/kasmweb @@ -1 +1 @@ -Subproject commit 31b1a93335c1cb4947d4eac06dd1311bb18f5022 +Subproject commit f17512ee88b80ea7ee4e5b94c74963a4729a6c45 From a2b223048923fa3844555aa4d6f2edbb6a461aa4 Mon Sep 17 00:00:00 2001 From: mattmcclaskey Date: Tue, 14 Mar 2023 11:18:36 -0400 Subject: [PATCH 6/6] fix for opensuse build --- builder/dockerfile.opensuse_15.build | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builder/dockerfile.opensuse_15.build b/builder/dockerfile.opensuse_15.build index 4d98116..4b624d9 100644 --- a/builder/dockerfile.opensuse_15.build +++ b/builder/dockerfile.opensuse_15.build @@ -24,6 +24,8 @@ RUN zypper install -ny \ libgnutls-devel \ libopenssl-devel \ libpng16-devel \ + libpnglite0 \ + png++-devel \ libtiff-devel \ libXfont2-devel \ libxkbcommon-x11-devel \