This commit is contained in:
El 2025-10-23 18:07:46 +00:00
parent 1c1b98d7dd
commit 9c609b5fc1
No known key found for this signature in database
GPG Key ID: 205388FEB607950A
44 changed files with 844 additions and 442 deletions

View File

@ -157,7 +157,8 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings)
encodings_.clear();
encodings_.insert(encodingRaw);
bool canChangeSettings = !shandler || shandler->canChangeKasmSettings();
const bool canChangeSettings = !shandler || shandler->canChangeKasmSettings();
const bool can_apply = !rfb::Server::ignoreClientSettingsKasm && canChangeSettings;
for (int i = nEncodings-1; i >= 0; i--) {
switch (encodings[i]) {
@ -283,116 +284,90 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings)
clientparlog("fineQualityLevel", fineQualityLevel, true);
}
if (!rfb::Server::ignoreClientSettingsKasm && canChangeSettings) {
if (encodings[i] >= pseudoEncodingJpegVideoQualityLevel0 &&
encodings[i] <= pseudoEncodingJpegVideoQualityLevel9) {
Server::jpegVideoQuality.setParam(encodings[i] - pseudoEncodingJpegVideoQualityLevel0);
clientparlog("jpegVideoQuality", encodings[i] - pseudoEncodingJpegVideoQualityLevel0, true);
}
if (encodings[i] >= pseudoEncodingJpegVideoQualityLevel0 && encodings[i] <= pseudoEncodingJpegVideoQualityLevel9) {
if (can_apply)
Server::jpegVideoQuality.setParam(encodings[i] - pseudoEncodingJpegVideoQualityLevel0);
clientparlog("jpegVideoQuality", encodings[i] - pseudoEncodingJpegVideoQualityLevel0, can_apply);
}
if (encodings[i] >= pseudoEncodingWebpVideoQualityLevel0 &&
encodings[i] <= pseudoEncodingWebpVideoQualityLevel9) {
Server::webpVideoQuality.setParam(encodings[i] - pseudoEncodingWebpVideoQualityLevel0);
clientparlog("webpVideoQuality", encodings[i] - pseudoEncodingWebpVideoQualityLevel0, true);
}
if (encodings[i] >= pseudoEncodingWebpVideoQualityLevel0 && encodings[i] <= pseudoEncodingWebpVideoQualityLevel9) {
if (can_apply)
Server::webpVideoQuality.setParam(encodings[i] - pseudoEncodingWebpVideoQualityLevel0);
clientparlog("webpVideoQuality", encodings[i] - pseudoEncodingWebpVideoQualityLevel0, can_apply);
}
if (encodings[i] >= pseudoEncodingTreatLosslessLevel0 &&
encodings[i] <= pseudoEncodingTreatLosslessLevel10) {
Server::treatLossless.setParam(encodings[i] - pseudoEncodingTreatLosslessLevel0);
clientparlog("treatLossless", encodings[i] - pseudoEncodingTreatLosslessLevel0, true);
}
if (encodings[i] >= pseudoEncodingTreatLosslessLevel0 && encodings[i] <= pseudoEncodingTreatLosslessLevel10) {
if (can_apply)
Server::treatLossless.setParam(encodings[i] - pseudoEncodingTreatLosslessLevel0);
clientparlog("treatLossless", encodings[i] - pseudoEncodingTreatLosslessLevel0, can_apply);
}
if (encodings[i] >= pseudoEncodingDynamicQualityMinLevel0 &&
encodings[i] <= pseudoEncodingDynamicQualityMinLevel9) {
Server::dynamicQualityMin.setParam(encodings[i] - pseudoEncodingDynamicQualityMinLevel0);
clientparlog("dynamicQualityMin", encodings[i] - pseudoEncodingDynamicQualityMinLevel0, true);
}
if (encodings[i] >= pseudoEncodingDynamicQualityMinLevel0 && encodings[i] <= pseudoEncodingDynamicQualityMinLevel9) {
if (can_apply)
Server::dynamicQualityMin.setParam(encodings[i] - pseudoEncodingDynamicQualityMinLevel0);
clientparlog("dynamicQualityMin", encodings[i] - pseudoEncodingDynamicQualityMinLevel0, can_apply);
}
if (encodings[i] >= pseudoEncodingDynamicQualityMaxLevel0 &&
encodings[i] <= pseudoEncodingDynamicQualityMaxLevel9) {
Server::dynamicQualityMax.setParam(encodings[i] - pseudoEncodingDynamicQualityMaxLevel0);
clientparlog("dynamicQualityMax", encodings[i] - pseudoEncodingDynamicQualityMaxLevel0, true);
}
if (encodings[i] >= pseudoEncodingDynamicQualityMaxLevel0 && encodings[i] <= pseudoEncodingDynamicQualityMaxLevel9) {
if (can_apply)
Server::dynamicQualityMax.setParam(encodings[i] - pseudoEncodingDynamicQualityMaxLevel0);
clientparlog("dynamicQualityMax", encodings[i] - pseudoEncodingDynamicQualityMaxLevel0, can_apply);
}
if (encodings[i] >= pseudoEncodingVideoAreaLevel1 &&
encodings[i] <= pseudoEncodingVideoAreaLevel100) {
Server::videoArea.setParam(encodings[i] - pseudoEncodingVideoAreaLevel1 + 1);
clientparlog("videoArea", encodings[i] - pseudoEncodingVideoAreaLevel1 + 1, true);
}
if (encodings[i] >= pseudoEncodingVideoAreaLevel1 && encodings[i] <= pseudoEncodingVideoAreaLevel100) {
if (can_apply)
Server::videoArea.setParam(encodings[i] - pseudoEncodingVideoAreaLevel1 + 1);
clientparlog("videoArea", encodings[i] - pseudoEncodingVideoAreaLevel1 + 1, can_apply);
}
if (encodings[i] >= pseudoEncodingVideoTimeLevel0 &&
encodings[i] <= pseudoEncodingVideoTimeLevel100) {
Server::videoTime.setParam(encodings[i] - pseudoEncodingVideoTimeLevel0);
clientparlog("videoTime", encodings[i] - pseudoEncodingVideoTimeLevel0, true);
}
if (encodings[i] >= pseudoEncodingVideoTimeLevel0 && encodings[i] <= pseudoEncodingVideoTimeLevel100) {
if (can_apply)
Server::videoTime.setParam(encodings[i] - pseudoEncodingVideoTimeLevel0);
clientparlog("videoTime", encodings[i] - pseudoEncodingVideoTimeLevel0, can_apply);
}
if (encodings[i] >= pseudoEncodingVideoOutTimeLevel1 &&
encodings[i] <= pseudoEncodingVideoOutTimeLevel100) {
Server::videoOutTime.setParam(encodings[i] - pseudoEncodingVideoOutTimeLevel1 + 1);
clientparlog("videoOutTime", encodings[i] - pseudoEncodingVideoOutTimeLevel1 + 1, true);
}
if (encodings[i] >= pseudoEncodingVideoOutTimeLevel1 && encodings[i] <= pseudoEncodingVideoOutTimeLevel100) {
if (can_apply)
Server::videoOutTime.setParam(encodings[i] - pseudoEncodingVideoOutTimeLevel1 + 1);
clientparlog("videoOutTime", encodings[i] - pseudoEncodingVideoOutTimeLevel1 + 1, can_apply);
}
if (encodings[i] >= pseudoEncodingFrameRateLevel10 &&
encodings[i] <= pseudoEncodingFrameRateLevel60) {
Server::frameRate.setParam(encodings[i] - pseudoEncodingFrameRateLevel10 + 10);
clientparlog("frameRate", encodings[i] - pseudoEncodingFrameRateLevel10 + 10, true);
}
if (encodings[i] >= pseudoEncodingFrameRateLevel10 && encodings[i] <= pseudoEncodingFrameRateLevel60) {
if (can_apply)
Server::frameRate.setParam(encodings[i] - pseudoEncodingFrameRateLevel10 + 10);
clientparlog("frameRate", encodings[i] - pseudoEncodingFrameRateLevel10 + 10, can_apply);
}
if (encodings[i] >= pseudoEncodingVideoScalingLevel0 &&
encodings[i] <= pseudoEncodingVideoScalingLevel9) {
Server::videoScaling.setParam(encodings[i] - pseudoEncodingVideoScalingLevel0);
clientparlog("videoScaling", encodings[i] - pseudoEncodingVideoScalingLevel0, true);
}
} else {
if (encodings[i] >= pseudoEncodingJpegVideoQualityLevel0 &&
encodings[i] <= pseudoEncodingJpegVideoQualityLevel9) {
clientparlog("jpegVideoQuality", encodings[i] - pseudoEncodingJpegVideoQualityLevel0, false);
}
if (encodings[i] >= pseudoEncodingVideoScalingLevel0 && encodings[i] <= pseudoEncodingVideoScalingLevel9) {
if (can_apply)
Server::videoScaling.setParam(encodings[i] - pseudoEncodingVideoScalingLevel0);
clientparlog("videoScaling", encodings[i] - pseudoEncodingVideoScalingLevel0, can_apply);
}
if (encodings[i] >= pseudoEncodingWebpVideoQualityLevel0 &&
encodings[i] <= pseudoEncodingWebpVideoQualityLevel9) {
clientparlog("webpVideoQuality", encodings[i] - pseudoEncodingWebpVideoQualityLevel0, false);
}
// encs.push(encodings.pseudoEncodingStreamingMode + this.streamMode);
if (encodings[i] >= pseudoEncodingTreatLosslessLevel0 &&
encodings[i] <= pseudoEncodingTreatLosslessLevel10) {
clientparlog("treatLossless", encodings[i] - pseudoEncodingTreatLosslessLevel0, false);
}
// if (encodings[i] >= pseudoEncodingHardwareProfile0 && encodings[i] <= pseudoEncodingHardwareProfile4) {
// if (appliable)
// Server::hardwareProfile.setParam(encodings[i] - pseudoEncodingHardwareProfile0);
// clientparlog("hardwareProfile", encodings[i] - pseudoEncodingHardwareProfile0, appliable);
// }
if (encodings[i] >= pseudoEncodingDynamicQualityMinLevel0 &&
encodings[i] <= pseudoEncodingDynamicQualityMinLevel9) {
clientparlog("dynamicQualityMin", encodings[i] - pseudoEncodingDynamicQualityMinLevel0, false);
}
if (encodings[i] >= pseudoEncodingGOP1 && encodings[i] <= pseudoEncodingGOP60) {
if (can_apply)
Server::groupOfPicture.setParam(encodings[i] - pseudoEncodingGOP1);
clientparlog("groupOfPicture", encodings[i] - pseudoEncodingGOP1, can_apply);
}
if (encodings[i] >= pseudoEncodingDynamicQualityMaxLevel0 &&
encodings[i] <= pseudoEncodingDynamicQualityMaxLevel9) {
clientparlog("dynamicQualityMax", encodings[i] - pseudoEncodingDynamicQualityMaxLevel0, false);
}
if (encodings[i] >= pseudoEncodingStreamingVideoQualityLevel0 && encodings[i] <= pseudoEncodingStreamingVideoQualityLevel63) {
if (can_apply)
Server::videoQualityCRFCQP.setParam(pseudoEncodingStreamingVideoQualityLevel63 - encodings[i]);
clientparlog("videoQualityCRFCQP", pseudoEncodingStreamingVideoQualityLevel63 - encodings[i], can_apply);
}
if (encodings[i] >= pseudoEncodingVideoAreaLevel1 &&
encodings[i] <= pseudoEncodingVideoAreaLevel100) {
clientparlog("videoArea", encodings[i] - pseudoEncodingVideoAreaLevel1 + 1, false);
}
if (encodings[i] >= pseudoEncodingVideoTimeLevel0 &&
encodings[i] <= pseudoEncodingVideoTimeLevel100) {
clientparlog("videoTime", encodings[i] - pseudoEncodingVideoTimeLevel0, false);
}
if (encodings[i] >= pseudoEncodingVideoOutTimeLevel1 &&
encodings[i] <= pseudoEncodingVideoOutTimeLevel100) {
clientparlog("videoOutTime", encodings[i] - pseudoEncodingVideoOutTimeLevel1 + 1, false);
}
if (encodings[i] >= pseudoEncodingFrameRateLevel10 &&
encodings[i] <= pseudoEncodingFrameRateLevel60) {
clientparlog("frameRate", encodings[i] - pseudoEncodingFrameRateLevel10 + 10, false);
}
if (encodings[i] >= pseudoEncodingVideoScalingLevel0 &&
encodings[i] <= pseudoEncodingVideoScalingLevel9) {
clientparlog("videoScaling", encodings[i] - pseudoEncodingVideoScalingLevel0, false);
}
if (encodings[i] >=pseudoEncodingStreamingModeAV1QSV && encodings[i] <= pseudoEncodingStreamingModeJpegWebp) {
if (can_apply)
encoder = KasmVideoEncoders::from_encoding(encodings[i]);
clientparlog("Encoder", encodings[i], can_apply);
}
if (encodings[i] > 0)

View File

@ -29,18 +29,19 @@
#include <rfb/Cursor.h>
#include <rfb/PixelFormat.h>
#include <rfb/ScreenSet.h>
#include <rfb/encoders/KasmVideoConstants.h>
namespace rdr { class InStream; }
namespace rfb {
const int subsampleUndefined = -1;
const int subsampleNone = 0;
const int subsampleGray = 1;
const int subsample2X = 2;
const int subsample4X = 3;
const int subsample8X = 4;
const int subsample16X = 5;
constexpr int subsampleUndefined = -1;
constexpr int subsampleNone = 0;
constexpr int subsampleGray = 1;
constexpr int subsample2X = 2;
constexpr int subsample4X = 3;
constexpr int subsample8X = 4;
constexpr int subsample16X = 5;
class SMsgHandler;
@ -91,7 +92,7 @@ namespace rfb {
void setEncodings(int nEncodings, const rdr::S32* encodings);
unsigned int ledState() { return ledState_; }
unsigned int ledState() const { return ledState_; }
void setLEDState(unsigned int state);
rdr::U32 clipboardFlags() const { return clipFlags; }
@ -143,6 +144,8 @@ namespace rfb {
};
bool kasmPassed[KASM_NUM_SETTINGS];
KasmVideoEncoders::Encoder encoder{KasmVideoEncoders::Encoder::unavailable};
KasmVideoEncoders::Encoders available_encoders;
private:

View File

@ -167,9 +167,9 @@ static void updateMaxVideoRes(uint16_t *x, uint16_t *y) {
}
}
EncodeManager::EncodeManager(SConnection *conn_, EncCache *encCache_, const FFmpeg& ffmpeg) :
EncodeManager::EncodeManager(SConnection *conn_, EncCache *encCache_, const FFmpeg& ffmpeg_) :
conn(conn_), dynamicQualityMin(-1), dynamicQualityOff(-1), areaCur(0), videoDetected(false), videoTimer(this),
watermarkStats(0), maxEncodingTime(0), framesSinceEncPrint(0), ffmpeg_available(ffmpeg.is_available()), encCache(encCache_)
watermarkStats(0), maxEncodingTime(0), framesSinceEncPrint(0), ffmpeg(ffmpeg_), ffmpeg_available(ffmpeg.is_available()), encCache(encCache_)
{
encoders.resize(encoderClassMax, nullptr);
activeEncoders.resize(encoderTypeMax, encoderRaw);
@ -437,24 +437,41 @@ void EncodeManager::doUpdate(bool allowLossy, const Region& changed_,
writeCopyRects(copied, copyDelta);
writeCopyPassRects(copypassed);
// If a video codec is enabled, send that stream constantly
/*if (ffmpeg_available && video_mode_available) {
startRect(pb->getRect(), encoderFullColour, true, STARTRECT_OVERRIDE_KASMVIDEO);
if (video_mode_available && conn->cp.encoder != KasmVideoEncoders::Encoder::unavailable) {
encoders[encoderKasmVideo]->writeRect(pb, Palette());
endRect(STARTRECT_OVERRIDE_KASMVIDEO);
}*/
if (video_mode_available) {
auto *screen_encoder_manager = dynamic_cast<ScreenEncoderManager *>(encoders[encoderKasmVideo]);
auto begin = msSince(&start);
auto *screen_encoder_manager = dynamic_cast<ScreenEncoderManager<> *>(encoders[encoderKasmVideo]);
if (screen_encoder_manager) {
if (screen_encoder_manager->get_encoder() != conn->cp.encoder) {
delete encoders[encoderKasmVideo];
//puts("CHANGING ENCODER!!!");
//puts("CHANGING ENCODER!!!");
//puts("CHANGING ENCODER!!!");
//puts("CHANGING ENCODER!!!");
//puts("CHANGING ENCODER!!!");
encoders[encoderKasmVideo] = new ScreenEncoderManager(ffmpeg,
conn->cp.encoder,
video_encoders::available_encoders,
conn,
{0,
0,
static_cast<uint8_t>(Server::frameRate),
static_cast<uint8_t>(Server::groupOfPicture),
static_cast<uint8_t>(Server::videoQualityCRFCQP)});
}
screen_encoder_manager->sync_layout(layout);
for (auto screen_encoder: *screen_encoder_manager) {
uint32_t screen_id = screen_encoder->getId();
auto *encoder = startRect(pb->getRect(), encoderFullColour, true, STARTRECT_OVERRIDE_KASMVIDEO);
screen_encoder->writeRect(pb, Palette());
const Palette palette;
for (auto &screen: *screen_encoder_manager) {
if (!screen.encoder)
continue;
startRect(screen.layout.dimensions, encoderFullColour, true, STARTRECT_OVERRIDE_KASMVIDEO);
screen.encoder->writeRect(pb, palette);
endRect(STARTRECT_OVERRIDE_KASMVIDEO);
}
}
@ -462,6 +479,10 @@ void EncodeManager::doUpdate(bool allowLossy, const Region& changed_,
std::vector<Rect> rects;
changed.get_rects(&rects);
updateVideoStats(rects, pb);
auto end = msSince(&start) - begin;
printf("ENCODING TOOK: %d ms\n", end);
} else {
/*
* We start by searching for solid rects, which are then removed
@ -747,7 +768,7 @@ Encoder *EncodeManager::startRect(const Rect &rect, int type, const bool trackQu
return encoder;
}
void EncodeManager::endRect(const enum startRectOverride overrider)
void EncodeManager::endRect(const startRectOverride overrider)
{
conn->writer()->endRect();
const auto length = conn->getOutStream(conn->cp.supportsUdp)->length() - beforeLength;

View File

@ -216,6 +216,7 @@ namespace rfb {
unsigned maxEncodingTime, framesSinceEncPrint;
unsigned scalingTime;
const FFmpeg &ffmpeg;
bool ffmpeg_available;
bool video_mode_available{false};

View File

@ -50,7 +50,7 @@ namespace rfb {
// 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;
virtual bool isSupported() const = 0;
virtual void setCompressLevel(int level) {};
virtual void setQualityLevel(int level) {};

View File

@ -49,11 +49,7 @@ HextileEncoder::HextileEncoder(SConnection* conn) :
{
}
HextileEncoder::~HextileEncoder()
{
}
bool HextileEncoder::isSupported()
bool HextileEncoder::isSupported() const
{
return conn->cp.supportsEncoding(encodingHextile);
}

View File

@ -23,15 +23,13 @@
namespace rfb {
class HextileEncoder : public Encoder {
public:
HextileEncoder(SConnection* conn);
virtual ~HextileEncoder();
virtual bool isSupported();
virtual void writeRect(const PixelBuffer* pb, const Palette& palette);
virtual void writeSolidRect(int width, int height,
const PixelFormat& pf,
const rdr::U8* colour);
};
class HextileEncoder : public Encoder {
public:
HextileEncoder(SConnection* conn);
~HextileEncoder() override = default;
bool isSupported() const override;
void writeRect(const PixelBuffer* pb, const Palette& palette) override;
void writeSolidRect(int width, int height, const PixelFormat &pf, const rdr::U8 *colour) override;
};
}
#endif

View File

@ -41,11 +41,7 @@ RREEncoder::RREEncoder(SConnection* conn) :
{
}
RREEncoder::~RREEncoder()
{
}
bool RREEncoder::isSupported()
bool RREEncoder::isSupported() const
{
return conn->cp.supportsEncoding(encodingRRE);
}

View File

@ -28,12 +28,12 @@ namespace rfb {
class RREEncoder : public Encoder {
public:
RREEncoder(SConnection* conn);
virtual ~RREEncoder();
virtual bool isSupported();
virtual void writeRect(const PixelBuffer* pb, const Palette& palette);
virtual void writeSolidRect(int width, int height,
~RREEncoder() override = default;
bool isSupported() const override;
void writeRect(const PixelBuffer* pb, const Palette& palette) override;
void writeSolidRect(int width, int height,
const PixelFormat& pf,
const rdr::U8* colour);
const rdr::U8* colour) override;
private:
rdr::MemOutStream mos;
ManagedPixelBuffer bufferCopy;

View File

@ -29,11 +29,7 @@ RawEncoder::RawEncoder(SConnection* conn) :
{
}
RawEncoder::~RawEncoder()
{
}
bool RawEncoder::isSupported()
bool RawEncoder::isSupported() const
{
// Implicitly required;
return true;

View File

@ -26,12 +26,12 @@ namespace rfb {
class RawEncoder : public Encoder {
public:
RawEncoder(SConnection* conn);
virtual ~RawEncoder();
virtual bool isSupported();
virtual void writeRect(const PixelBuffer* pb, const Palette& palette);
virtual void writeSolidRect(int width, int height,
~RawEncoder() override = default;
bool isSupported() const override;
void writeRect(const PixelBuffer* pb, const Palette& palette) override;
void writeSolidRect(int width, int height,
const PixelFormat& pf,
const rdr::U8* colour);
const rdr::U8* colour) override;
};
}
#endif

View File

@ -101,6 +101,7 @@ namespace rfb {
virtual void subscribeUnixRelay(const char *name) = 0;
virtual void unixRelay(const char *name, const rdr::U8 *buf, const unsigned len) = 0;
virtual void videoEncodersRequest(const std::vector<int32_t> &encoders) = 0;
ConnParams cp;
};

View File

@ -53,7 +53,7 @@ void SMsgReader::readClientInit()
void SMsgReader::readMsg()
{
int msgType = is->readU8();
const int msgType = is->readU8();
switch (msgType) {
case msgTypeSetPixelFormat:
readSetPixelFormat();
@ -106,6 +106,9 @@ void SMsgReader::readMsg()
case msgTypeUnixRelay:
readUnixRelay();
break;
case msgTypeVideoEncoders:
readVideoEncodersRequest();
break;
case msgTypeKeepAlive:
readKeepAlive();
break;
@ -413,3 +416,13 @@ void SMsgReader::readUnixRelay()
handler->unixRelay(name, buf, len);
}
void SMsgReader::readVideoEncodersRequest() const {
const auto len = is->readU8();
std::vector<int32_t> buf(len);
for (int i = 0; i < len; ++i)
buf[i] = is->readU32();
handler->videoEncodersRequest(buf);
}

View File

@ -68,6 +68,7 @@ namespace rfb {
void readSubscribeUnixRelay();
void readUnixRelay();
void readVideoEncodersRequest() const;
SMsgHandler* handler;
rdr::InStream* is;

View File

@ -778,6 +778,26 @@ void SMsgWriter::writeUnixRelay(const char *name, const rdr::U8 *buf, const unsi
endMsg();
}
void SMsgWriter::writeVideoEncoders(const std::vector<int32_t> &encoders) {
startMsg(msgTypeVideoEncoders);
std::vector<int32_t> conjunction;
for (const auto encoder: cp->available_encoders) {
if (std::ranges::find(encoders, KasmVideoEncoders::to_streaming_mode(encoder)) != encoders.end()) {
conjunction.push_back(KasmVideoEncoders::to_encoding(encoder));
}
}
const uint8_t size = conjunction.size();
os->writeU8(size);
for (auto encoder: conjunction)
os->writeS32(encoder);
endMsg();
}
void SMsgWriter::writeUserJoinedSession(const std::string& username)
{
startMsg(msgTypeUserAddedToSession);

View File

@ -132,6 +132,7 @@ namespace rfb {
void writeSubscribeUnixRelay(const bool success, const char *msg);
void writeUnixRelay(const char *name, const rdr::U8 *buf, const unsigned len);
void writeVideoEncoders(const std::vector<int32_t> &encoders);
void writeUserJoinedSession(const std::string& username);
void writeUserLeftSession(const std::string& username);

View File

@ -62,11 +62,7 @@ TightEncoder::TightEncoder(SConnection* conn) :
setCompressLevel(-1);
}
TightEncoder::~TightEncoder()
{
}
bool TightEncoder::isSupported()
bool TightEncoder::isSupported() const
{
return conn->cp.supportsEncoding(encodingTight);
}

View File

@ -29,16 +29,16 @@ namespace rfb {
class TightEncoder : public Encoder {
public:
TightEncoder(SConnection* conn);
virtual ~TightEncoder();
~TightEncoder() override = default;
virtual bool isSupported();
bool isSupported() const override;
virtual void setCompressLevel(int level);
void setCompressLevel(int level) override;
virtual void writeRect(const PixelBuffer* pb, const Palette& palette);
virtual void writeSolidRect(int width, int height,
void writeRect(const PixelBuffer* pb, const Palette& palette) override;
void writeSolidRect(int width, int height,
const PixelFormat& pf,
const rdr::U8* colour);
const rdr::U8* colour) override;
void writeWatermarkRect(const rdr::U8 *data, const unsigned len,
const rdr::U8 r,
const rdr::U8 g,

View File

@ -76,11 +76,7 @@ TightJPEGEncoder::TightJPEGEncoder(SConnection* conn) :
{
}
TightJPEGEncoder::~TightJPEGEncoder()
{
}
bool TightJPEGEncoder::isSupported()
bool TightJPEGEncoder::isSupported() const
{
if (!conn->cp.supportsEncoding(encodingTight))
return false;

View File

@ -30,22 +30,22 @@ namespace rfb {
class TightJPEGEncoder : public Encoder {
public:
TightJPEGEncoder(SConnection* conn);
virtual ~TightJPEGEncoder();
~TightJPEGEncoder() override = default;
virtual bool isSupported();
bool isSupported() const override;
virtual void setQualityLevel(int level);
virtual void setFineQualityLevel(int quality, int subsampling);
void setQualityLevel(int level) override;
void setFineQualityLevel(int quality, int subsampling) override;
virtual bool treatLossless();
bool treatLossless() override;
virtual void writeRect(const PixelBuffer* pb, const Palette& palette);
void writeRect(const PixelBuffer* pb, const Palette& palette) override;
virtual void compressOnly(const PixelBuffer* pb, const uint8_t quality,
std::vector<uint8_t> &out, const bool lowVideoQuality) const;
virtual void writeOnly(const std::vector<uint8_t> &out) const;
virtual void writeSolidRect(int width, int height,
void writeSolidRect(int width, int height,
const PixelFormat& pf,
const rdr::U8* colour);
const rdr::U8* colour) override;
protected:
void writeCompact(rdr::U32 value, rdr::OutStream* os) const;

View File

@ -156,11 +156,7 @@ TightQOIEncoder::TightQOIEncoder(SConnection* conn) :
{
}
TightQOIEncoder::~TightQOIEncoder()
{
}
bool TightQOIEncoder::isSupported()
bool TightQOIEncoder::isSupported() const
{
if (!conn->cp.supportsEncoding(encodingTight))
return false;

View File

@ -27,17 +27,17 @@ namespace rfb {
class TightQOIEncoder : public Encoder {
public:
TightQOIEncoder(SConnection* conn);
virtual ~TightQOIEncoder();
~TightQOIEncoder() override = default;
virtual bool isSupported();
bool isSupported() const override;
virtual void writeRect(const PixelBuffer* pb, const Palette& palette);
void writeRect(const PixelBuffer* pb, const Palette& palette) override;
virtual void compressOnly(const PixelBuffer* pb, const uint8_t quality,
std::vector<uint8_t> &out, const bool lowVideoQuality) const;
virtual void writeOnly(const std::vector<uint8_t> &out) const;
virtual void writeSolidRect(int width, int height,
void writeSolidRect(int width, int height,
const PixelFormat& pf,
const rdr::U8* colour);
const rdr::U8* colour) override;
protected:
void writeCompact(rdr::U32 value, rdr::OutStream* os) const;

View File

@ -90,7 +90,7 @@ TightWEBPEncoder::~TightWEBPEncoder()
{
}
bool TightWEBPEncoder::isSupported()
bool TightWEBPEncoder::isSupported() const
{
if (!conn->cp.supportsEncoding(encodingTight))
return false;

View File

@ -29,7 +29,7 @@ namespace rfb {
TightWEBPEncoder(SConnection* conn);
virtual ~TightWEBPEncoder();
virtual bool isSupported();
bool isSupported() const override;
virtual void setQualityLevel(int level);
virtual void setFineQualityLevel(int quality, int subsampling);

View File

@ -41,6 +41,7 @@
#include <cstdint>
#include <wordexp.h>
#include "encoders/EncoderProbe.h"
#include "kasmpasswd.h"
using namespace rfb;
@ -89,9 +90,11 @@ VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s,
user[0] = '\0';
const char *at = strrchr(peerEndpoint.buf, '@');
if (at && at - peerEndpoint.buf > 1 && at - peerEndpoint.buf < USERNAME_LEN) {
memcpy(user, peerEndpoint.buf, at - peerEndpoint.buf);
user[at - peerEndpoint.buf] = '\0';
const auto offset = at - peerEndpoint.buf;
if (at && offset > 1 && static_cast<size_t>(offset) < USERNAME_LEN) {
memcpy(user, peerEndpoint.buf, offset);
user[offset] = '\0';
}
bool read, write, owner;
@ -112,6 +115,8 @@ VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s,
gettimeofday(&lastClipboardOp, nullptr);
gettimeofday(&lastKeyEvent, nullptr);
cp.available_encoders = video_encoders::available_encoders;
server->clients.push_front(this);
if (server->apimessager)
@ -1908,6 +1913,10 @@ void VNCSConnectionST::unixRelay(const char *name, const rdr::U8 *buf, const uns
}
}
void VNCSConnectionST::videoEncodersRequest(const std::vector<int32_t> &encoders) {
writer()->writeVideoEncoders(encoders);
}
void VNCSConnectionST::sendUnixRelayData(const char name[], const unsigned char *buf,
const unsigned len)
{

View File

@ -258,12 +258,13 @@ namespace rfb {
virtual void udpUpgrade(const char *resp);
virtual void subscribeUnixRelay(const char *name);
virtual void unixRelay(const char *name, const rdr::U8 *buf, const unsigned len);
void videoEncodersRequest(std::vector<int32_t> const &encoders) override;
virtual void supportsLocalCursor();
virtual void supportsFence();
virtual void supportsContinuousUpdates();
virtual void supportsLEDState();
virtual bool canChangeKasmSettings() const {
bool canChangeKasmSettings() const override {
return (accessRights & (AccessPtrEvents | AccessKeyEvents)) ==
(AccessPtrEvents | AccessKeyEvents);
}

View File

@ -160,7 +160,7 @@ VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_)
std::string available_accelerators{};
for (const auto encoder: video_encoders::available_encoders) {
if (encoder != KasmVideoEncoders::Encoder::h264_software) {
if (KasmVideoEncoders::is_accelerated(encoder)) {
if (!available_accelerators.empty())
available_accelerators.append(", ");
@ -168,8 +168,8 @@ VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_)
}
}
const auto str = available_accelerators;
slog.info("Hardware video encoding acceleration capability: %s", str.empty() ? "none" : str.c_str());
slog.info("Hardware video encoding acceleration capability: %s",
available_accelerators.empty() ? "none" : available_accelerators.c_str());
DLPRegion.enabled = DLPRegion.percents = false;
@ -259,6 +259,20 @@ VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_)
if (selected_codec[0] && !SupportedVideoEncoders::is_supported(selected_codec))
throw std::invalid_argument(fmt::format("Unknown video codec: {}", selected_codec));
const auto selected_encoders = SupportedVideoEncoders::parse(selected_codec);
for (auto encoder: video_encoders::available_encoders) {
if (std::ranges::find(selected_encoders, encoder) != selected_encoders.end())
encoders.push_back(encoder);
}
std::string encoder_names;
for (auto encoder: encoders)
encoder_names.append(KasmVideoEncoders::to_string(encoder)).append(" ");
slog.info("Using CLI-specified video codecs (supported subset): %s", encoder_names.c_str());
if (Server::selfBench)
SelfBench();

View File

@ -26,15 +26,16 @@
#include <sys/time.h>
#include <rfb/EncCache.h>
#include <rfb/SDesktop.h>
#include <rfb/VNCServer.h>
#include <rfb/LogWriter.h>
#include <network/Socket.h>
#include <rfb/Blacklist.h>
#include <rfb/Cursor.h>
#include <rfb/Timer.h>
#include <network/Socket.h>
#include <rfb/EncCache.h>
#include <rfb/LogWriter.h>
#include <rfb/SDesktop.h>
#include <rfb/ScreenSet.h>
#include <rfb/Timer.h>
#include <rfb/VNCServer.h>
#include <rfb/encoders/KasmVideoConstants.h>
#include <string>
namespace rfb {
@ -237,6 +238,7 @@ namespace rfb {
std::list<network::Socket*> closingSockets;
static EncCache encCache;
std::vector<KasmVideoEncoders::Encoder> encoders{};
ComparingUpdateTracker* comparer;

View File

@ -41,7 +41,7 @@ ZRLEEncoder::~ZRLEEncoder()
zos.setUnderlying(NULL);
}
bool ZRLEEncoder::isSupported()
bool ZRLEEncoder::isSupported() const
{
return conn->cp.supportsEncoding(encodingZRLE);
}

View File

@ -28,14 +28,14 @@ namespace rfb {
class ZRLEEncoder : public Encoder {
public:
ZRLEEncoder(SConnection* conn);
virtual ~ZRLEEncoder();
~ZRLEEncoder() override;
virtual bool isSupported();
bool isSupported() const override;
virtual void writeRect(const PixelBuffer* pb, const Palette& palette);
virtual void writeSolidRect(int width, int height,
void writeRect(const PixelBuffer* pb, const Palette& palette) override;
void writeSolidRect(int width, int height,
const PixelFormat& pf,
const rdr::U8* colour);
const rdr::U8* colour) override;
protected:
void writePaletteTile(const Rect& tile, const PixelBuffer* pb,

View File

@ -118,6 +118,8 @@ namespace benchmarking {
void unixRelay(const char *name, const rdr::U8 *buf, const unsigned len) override {}
void videoEncodersRequest(const std::vector<int32_t> &encoders) override {}
void handleFrameStats(rdr::U32 all, rdr::U32 render) override {}
[[nodiscard]] auto getJpegStats() const {

View File

@ -21,21 +21,21 @@
namespace rfb {
// Formats
const unsigned int clipboardUTF8 = 1 << 0;
const unsigned int clipboardRTF = 1 << 1;
const unsigned int clipboardHTML = 1 << 2;
const unsigned int clipboardDIB = 1 << 3;
const unsigned int clipboardFiles = 1 << 4;
constexpr unsigned int clipboardUTF8 = 1 << 0;
constexpr unsigned int clipboardRTF = 1 << 1;
constexpr unsigned int clipboardHTML = 1 << 2;
constexpr unsigned int clipboardDIB = 1 << 3;
constexpr unsigned int clipboardFiles = 1 << 4;
const unsigned int clipboardFormatMask = 0x0000ffff;
constexpr unsigned int clipboardFormatMask = 0x0000ffff;
// Actions
const unsigned int clipboardCaps = 1 << 24;
const unsigned int clipboardRequest = 1 << 25;
const unsigned int clipboardPeek = 1 << 26;
const unsigned int clipboardNotify = 1 << 27;
const unsigned int clipboardProvide = 1 << 28;
constexpr unsigned int clipboardCaps = 1 << 24;
constexpr unsigned int clipboardRequest = 1 << 25;
constexpr unsigned int clipboardPeek = 1 << 26;
constexpr unsigned int clipboardNotify = 1 << 27;
constexpr unsigned int clipboardProvide = 1 << 28;
const unsigned int clipboardActionMask = 0xff000000;
constexpr unsigned int clipboardActionMask = 0xff000000;
}
#endif

View File

@ -19,7 +19,7 @@ namespace rfb {
FFMPEGVAAPIEncoder::FFMPEGVAAPIEncoder(Screen layout_, const FFmpeg &ffmpeg_, SConnection *conn,
KasmVideoEncoders::Encoder encoder_, VideoEncoderParams params) :
Encoder(layout_.id, conn, encodingKasmVideo, static_cast<EncoderFlags>(EncoderUseNativePF | EncoderLossy), -1), layout(layout_),
ffmpeg(ffmpeg_), encoder(encoder_), current_params(params) {
ffmpeg(ffmpeg_), encoder(encoder_), current_params(params), msg_codec_id(KasmVideoEncoders::to_msg_id(encoder)) {
AVBufferRef *hw_device_ctx{};
int err{};
@ -29,9 +29,10 @@ namespace rfb {
}
hw_device_ctx_guard.reset(hw_device_ctx);
codec = ffmpeg.avcodec_find_encoder_by_name("h264_vaapi");
const auto *enc_name = KasmVideoEncoders::to_string(encoder);
codec = ffmpeg.avcodec_find_encoder_by_name(enc_name);
if (!codec)
throw std::runtime_error("Could not find h264_vaapi encoder");
throw std::runtime_error(fmt::format("Could not find {} encoder", enc_name));
auto *frame = ffmpeg.av_frame_alloc();
if (!frame)
@ -160,14 +161,14 @@ namespace rfb {
}
auto *sws_ctx = ffmpeg.sws_getContext(
width, height, AV_PIX_FMT_RGB32, params.width, params.height, AV_PIX_FMT_NV12, SWS_BILINEAR, nullptr, nullptr, nullptr);
width, height, AV_PIX_FMT_RGB32, params.width, params.height, AV_PIX_FMT_NV12, SWS_BILINEAR, nullptr, nullptr, nullptr);
sws_guard.reset(sws_ctx);
return true;
}
bool FFMPEGVAAPIEncoder::isSupported() {
bool FFMPEGVAAPIEncoder::isSupported() const {
return conn->cp.supportsEncoding(encodingKasmVideo);
}
@ -191,10 +192,10 @@ namespace rfb {
dst_height = height & ~1;
VideoEncoderParams params{dst_width,
dst_height,
static_cast<uint8_t>(Server::frameRate),
static_cast<uint8_t>(Server::groupOfPicture),
static_cast<uint8_t>(Server::videoQualityCRFCQP)};
dst_height,
static_cast<uint8_t>(Server::frameRate),
static_cast<uint8_t>(Server::groupOfPicture),
static_cast<uint8_t>(Server::videoQualityCRFCQP)};
if (current_params != params) {
bpp = pb->getPF().bpp >> 3;
@ -221,7 +222,7 @@ namespace rfb {
if (err = ffmpeg.av_hwframe_transfer_data(hw_frame_guard.get(), frame, 0); err < 0) {
vlog.error(
"Error while transferring frame data to surface (%s). Error code: %d", ffmpeg.get_error_description(err).c_str(), err);
"Error while transferring frame data to surface (%s). Error code: %d", ffmpeg.get_error_description(err).c_str(), err);
}
if (err = ffmpeg.avcodec_send_frame(ctx_guard.get(), hw_frame_guard.get()); err < 0) {
@ -247,7 +248,8 @@ namespace rfb {
vlog.debug("Key frame %ld", frame->pts);
auto *os = conn->getOutStream(conn->cp.supportsUdp);
os->writeU8(kasmVideoH264 << 4);
os->writeU8(layout.id);
os->writeU8(msg_codec_id);
os->writeU8(pkt->flags & AV_PKT_FLAG_KEY);
write_compact(os, pkt->size);
os->writeBytes(&pkt->data[0], pkt->size);

View File

@ -23,6 +23,7 @@ class FFMPEGVAAPIEncoder final : public Encoder, public VideoEncoder {
KasmVideoEncoders::Encoder encoder;
VideoEncoderParams current_params{};
uint8_t msg_codec_id;
int64_t pts{};
int bpp{};
@ -36,7 +37,7 @@ class FFMPEGVAAPIEncoder final : public Encoder, public VideoEncoder {
VideoEncoderParams params);
public:
bool isSupported() override;
bool isSupported() const override;
void writeRect(const PixelBuffer *pb, const Palette &palette) override;
void writeSolidRect(int width, int height, const PixelFormat &pf, const rdr::U8 *colour) override;
void writeSkipRect() override;

View File

@ -17,33 +17,273 @@
*/
#pragma once
#include <algorithm>
#include <array>
#include <cassert>
#include <cstdint>
#include <rfb/encodings.h>
#include <string_view>
#include <type_traits>
#include <vector>
template<typename E>
class EnumRange {
public:
using val_t = std::underlying_type_t<E>;
class EnumIterator {
val_t value;
public:
explicit EnumIterator(E v) :
value(static_cast<std::underlying_type_t<E>>(v)) {}
E operator*() const {
return static_cast<E>(value);
}
EnumIterator &operator++() {
++value;
return *this;
}
bool operator!=(const EnumIterator &other) const {
return value != other.value;
}
};
EnumRange(E begin, E end) :
begin_iter(EnumIterator(begin)),
end_iter(++EnumIterator(end)) {}
EnumIterator begin() const {
return begin_iter;
}
EnumIterator end() const {
return end_iter;
}
EnumIterator begin() {
return begin_iter;
}
EnumIterator end() {
return end_iter;
}
private:
EnumIterator begin_iter;
EnumIterator end_iter;
};
template<typename T>
auto enum_range(T begin, T end) {
return EnumRange<T>(begin, end);
}
namespace rfb {
// Compression control
static constexpr unsigned int kasmVideoH264 = 0x01; // H.264 encoding
static constexpr unsigned int kasmVideoH264 = 0x01 << 4; // H.264 encoding
static constexpr unsigned int kasmVideoH265 = 0x02 << 4; // H.265 encoding
static constexpr unsigned int kasmVideoAV1 = 0x03 << 4; // AV1 encoding
static constexpr unsigned int kasmVideoSkip = 0x00; // Skip frame
static constexpr int GroupOfPictureSize = 10; // interval between I-frames
static constexpr auto drm_device_paths = std::to_array<std::string_view>({
"/dev/dri/renderD128",
"/dev/dri/card0",
"/dev/dri/renderD129",
"/dev/dri/card1",
});
static constexpr std::array<std::string_view, 4> drm_device_paths = {
"/dev/dri/renderD128",
"/dev/dri/card0",
"/dev/dri/renderD129",
"/dev/dri/card1",
struct KasmVideoEncoders {
// Codecs are ordered by preferred usage quality
enum class Encoder : uint8_t
{
av1_vaapi,
av1_ffmpeg_vaapi,
av1_nvenc,
av1_software,
h265_vaapi, // h265
h265_ffmpeg_vaapi,
h265_nvenc,
h265_software,
h264_vaapi,
h264_ffmpeg_vaapi,
h264_nvenc,
h264_software,
unavailable
};
using Encoders = std::vector<Encoder>;
static inline auto EncoderNames = std::to_array<std::string_view>({"av1_vaapi",
"av1_vaapi",
"av1_nvenc",
"libsvtav1",
"hevc_vaapi",
"hevc_vaapi",
"hevc_nvenc",
"libx265",
"h264_vaapi",
"h264_vaapi",
"h264_nvenc",
"libx264",
"unavailable"});
static inline auto Encodings = std::to_array<int>({pseudoEncodingStreamingModeAV1VAAPI,
pseudoEncodingStreamingModeAV1VAAPI,
pseudoEncodingStreamingModeAV1NVENC,
pseudoEncodingStreamingModeAV1SW,
pseudoEncodingStreamingModeHEVCVAAPI,
pseudoEncodingStreamingModeHEVCVAAPI,
pseudoEncodingStreamingModeHEVCNVENC,
pseudoEncodingStreamingModeHEVCSW,
pseudoEncodingStreamingModeAVCVAAPI,
pseudoEncodingStreamingModeAVCVAAPI,
pseudoEncodingStreamingModeAVCNVENC,
pseudoEncodingStreamingModeAVCSW,
pseudoEncodingStreamingModeJpegWebp});
static bool is_accelerated(Encoder encoder) {
return encoder != Encoder::h264_software && encoder != Encoder::h265_software && encoder != Encoder::av1_software;
}
static std::string_view to_string(Encoder encoder) {
return EncoderNames[static_cast<uint8_t>(encoder)];
}
static int to_encoding(Encoder encoder) {
return Encodings[static_cast<uint8_t>(encoder)];
}
static Encoder from_encoding(int encoding) {
for (auto encoder: enum_range(Encoder::av1_vaapi, Encoder::unavailable)) {
if (to_encoding(encoder) == encoding) {
switch (encoder) {
case Encoder::av1_vaapi:
return Encoder::av1_ffmpeg_vaapi;
case Encoder::h265_vaapi:
return Encoder::h265_ffmpeg_vaapi;
case Encoder::h264_vaapi:
return Encoder::h264_ffmpeg_vaapi;
default:
return encoder;
}
}
}
return Encoder::unavailable;
}
static unsigned int to_msg_id(Encoder encoder) {
switch (encoder) {
case Encoder::av1_vaapi:
case Encoder::av1_ffmpeg_vaapi:
case Encoder::av1_nvenc:
case Encoder::av1_software:
return kasmVideoAV1;
case Encoder::h265_vaapi: // h265
case Encoder::h265_ffmpeg_vaapi:
case Encoder::h265_nvenc:
case Encoder::h265_software:
return kasmVideoH265;
case Encoder::h264_vaapi:
case Encoder::h264_ffmpeg_vaapi:
case Encoder::h264_nvenc:
case Encoder::h264_software:
return kasmVideoH264;
default:
assert(false);
}
}
static int32_t to_streaming_mode(Encoder encoder) {
switch (encoder) {
case Encoder::av1_vaapi:
case Encoder::av1_ffmpeg_vaapi:
case Encoder::av1_nvenc:
case Encoder::av1_software:
return pseudoEncodingStreamingModeAV1;
case Encoder::h265_vaapi: // h265
case Encoder::h265_ffmpeg_vaapi:
case Encoder::h265_nvenc:
case Encoder::h265_software:
return pseudoEncodingStreamingModeHEVC;
case Encoder::h264_vaapi:
case Encoder::h264_ffmpeg_vaapi:
case Encoder::h264_nvenc:
case Encoder::h264_software:
return pseudoEncodingStreamingModeAVC;
default:
return pseudoEncodingStreamingModeJpegWebp;
}
}
};
struct SupportedVideoEncoders {
enum class Codecs : uint8_t
{
H264,
H265,
AV1
h264,
h264_vaapi,
h264_nvenc,
avc,
avc_vaapi,
avc_nvenc,
h265,
h265_vaapi,
h265_nvenc,
hevc,
hevc_vaapi,
hevc_nvenc,
av1,
av1_vaapi,
av1_nvenc,
auto_detect,
unavailable
};
static inline std::array<std::string_view, 3> CodecNames = {"h264", "h265", "av1"};
static constexpr auto MappedCodecs = std::to_array<KasmVideoEncoders::Encoder>({KasmVideoEncoders::Encoder::h264_software,
KasmVideoEncoders::Encoder::h264_ffmpeg_vaapi,
KasmVideoEncoders::Encoder::h264_nvenc,
KasmVideoEncoders::Encoder::h264_software,
KasmVideoEncoders::Encoder::h264_ffmpeg_vaapi,
KasmVideoEncoders::Encoder::h264_nvenc,
KasmVideoEncoders::Encoder::h265_software,
KasmVideoEncoders::Encoder::h265_ffmpeg_vaapi,
KasmVideoEncoders::Encoder::h265_nvenc,
KasmVideoEncoders::Encoder::h265_software,
KasmVideoEncoders::Encoder::h265_ffmpeg_vaapi,
KasmVideoEncoders::Encoder::h265_nvenc,
KasmVideoEncoders::Encoder::av1_software,
KasmVideoEncoders::Encoder::av1_ffmpeg_vaapi,
KasmVideoEncoders::Encoder::av1_nvenc,
KasmVideoEncoders::Encoder::h264_software,
KasmVideoEncoders::Encoder::unavailable});
static inline auto CodecNames = std::to_array<std::string_view>({"h264",
"h264_vaapi",
"h264_nvenc",
"avc",
"avc_vaapi",
"avc_nvenc",
"h265",
"h265_vaapi",
"h265_nvenc",
"hevc",
"hevc_vaapi",
"hevc_nvenc",
"av1",
"av1_vaapi",
"av1_nvenc",
"auto"});
static std::string_view to_string(Codecs codec) {
return CodecNames[static_cast<uint8_t>(codec)];
@ -53,38 +293,96 @@ namespace rfb {
if (codec.empty())
return false;
for (auto supported_codec: CodecNames)
for (const auto supported_codec: CodecNames)
if (supported_codec == codec)
return true;
if (codec == "hevc")
return true;
return false;
}
};
struct KasmVideoEncoders {
enum class Encoder : uint8_t
{
av1_vaapi,
av1_software,
hevc_vaapi, // h265
h265_software,
h264_vaapi,
h264_ffmpeg_vaapi,
h264_nvenc,
h264_software,
unavailable
};
static auto get_codec(std::string_view codec) {
for (auto codec_impl: enum_range(Codecs::h264, Codecs::auto_detect)) {
if (to_string(codec_impl) == codec)
return codec_impl;
}
static inline std::array<std::string_view, 9> EncoderNames = {
"av1_vaapi", "av1_software", "hevc_vaapi", "libx265", "h264_vaapi", "h264_vaapi", "h264_nvenc", "libx264", "unavailable"};
return Codecs::unavailable;
}
static std::string_view to_string(Encoder encoder) {
return EncoderNames[static_cast<uint8_t>(encoder)];
static constexpr auto map_encoder(Codecs impl) {
return MappedCodecs[static_cast<uint8_t>(impl)];
}
static std::vector<std::string_view> parse(std::string_view codecs) {
std::vector<std::string_view> result;
if (codecs.empty())
return {};
size_t pos{};
size_t start{};
while (pos < codecs.size()) {
pos = codecs.find_first_of(',', pos);
if (pos == std::string_view::npos)
pos = codecs.size();
result.push_back(codecs.substr(start, pos - start));
start = ++pos;
}
return result;
}
static KasmVideoEncoders::Encoders map_encoders(const std::vector<std::string_view> &codecs) {
KasmVideoEncoders::Encoders result;
if (codecs.empty())
return {};
for (auto codec_name: codecs) {
const auto codec = get_codec(codec_name);
switch (codec) {
case Codecs::auto_detect:
if (!result.empty())
result.clear();
result.push_back(map_encoder(Codecs::av1_nvenc));
result.push_back(map_encoder(Codecs::av1_vaapi));
result.push_back(map_encoder(Codecs::av1));
result.push_back(map_encoder(Codecs::h265_nvenc));
result.push_back(map_encoder(Codecs::h265_vaapi));
result.push_back(map_encoder(Codecs::h265));
result.push_back(map_encoder(Codecs::h264_nvenc));
result.push_back(map_encoder(Codecs::h264_vaapi));
result.push_back(map_encoder(Codecs::h264));
return result;
default:
{
const auto encoder = map_encoder(codec);
if (std::find(result.begin(), result.end(), encoder) == result.end())
result.push_back(encoder);
}
}
}
return result;
}
static KasmVideoEncoders::Encoders filter_available_encoders(
const KasmVideoEncoders::Encoders &encoders, const KasmVideoEncoders::Encoders &available) {
KasmVideoEncoders::Encoders result;
for (auto encoder: available) {
if (std::ranges::find(encoders.begin(), encoders.end(), encoder) != encoders.end())
result.push_back(encoder);
}
return result;
}
};
} // namespace rfb

View File

@ -6,15 +6,21 @@
#include "rfb/encodings.h"
namespace rfb {
ScreenEncoderManager::ScreenEncoderManager(const FFmpeg &ffmpeg_, KasmVideoEncoders::Encoder encoder,
template<int T>
ScreenEncoderManager<T>::ScreenEncoderManager(const FFmpeg &ffmpeg_, KasmVideoEncoders::Encoder encoder,
const std::vector<KasmVideoEncoders::Encoder> &encoders, SConnection *conn,
VideoEncoderParams params) :
Encoder(conn, encodingKasmVideo, static_cast<EncoderFlags>(EncoderUseNativePF | EncoderLossy), -1), ffmpeg(ffmpeg_),
current_params(params), base_video_encoder(encoder), available_encoders(encoders) {}
ScreenEncoderManager::~ScreenEncoderManager() = default;
template<int T>
ScreenEncoderManager<T>::~ScreenEncoderManager() {
for (uint8_t i = 0; i < get_screen_count(); ++i)
remove_screen(i);
};
Encoder *ScreenEncoderManager::add_screen(const Screen &layout) const {
template<int T>
Encoder *ScreenEncoderManager<T>::add_encoder(const Screen &layout) const {
Encoder *encoder{};
try {
encoder = create_encoder(layout, &ffmpeg, conn, base_video_encoder, current_params);
@ -33,63 +39,65 @@ namespace rfb {
return encoder;
}
size_t ScreenEncoderManager::get_screen_count() const {
return encoders.size();
template<int T>
void ScreenEncoderManager<T>::add_screen(uint8_t index, const Screen &layout) {
printf("SCREEN ADDED: %d (%d, %d, %d, %d)\n", index, layout.dimensions.tl.x, layout.dimensions.tl.y, layout.dimensions.br.x, layout.dimensions.br.y);
auto *encoder = add_encoder(layout);
assert(encoder);
screens[index] = {layout, encoder};
head = std::min(head, index);
tail = std::max(tail, index);
}
void ScreenEncoderManager::remove_screen(Encoder *encoder) {
std::erase(encoders, encoder);
template<int T>
size_t ScreenEncoderManager<T>::get_screen_count() const {
return std::max(0, tail - head + 1);
}
Encoder *ScreenEncoderManager::get_screen(size_t idx) const {
assert(idx < encoders.size());
return encoders[idx];
}
VideoEncoder *ScreenEncoderManager::get_video_encoder(size_t index) const {
assert(index < encoders.size());
return reinterpret_cast<VideoEncoder *>(encoders[index]);
}
void ScreenEncoderManager::sync_layout(const ScreenSet &layout) {
const auto new_count = layout.num_screens();
const auto current_count = static_cast<int>(encoders.size());
const auto min_count = std::min(new_count, current_count);
for (size_t i = 0; i < min_count; ++i) {
auto *encoder = encoders[i];
if (!encoder || layout.screens[i].id != encoder->getId()) {
delete encoders[i];
encoders[i] = add_screen(layout.screens[i]);
}
template<int T>
void ScreenEncoderManager<T>::remove_screen(uint8_t index) {
if (screens[index].encoder) {
delete screens[index].encoder;
screens[index].encoder = nullptr;
}
screens[index].layout = {};
}
if (new_count < current_count) {
encoders.erase(encoders.begin() + new_count, encoders.end());
} else if (new_count > current_count) {
encoders.reserve(new_count);
template<int T>
void ScreenEncoderManager<T>::sync_layout(const ScreenSet &layout) {
for (uint8_t i = 0; i < layout.num_screens(); ++i) {
const auto &screen = layout.screens[i];
auto id = screen.id;
if (id > ScreenSet::MAX_SCREENS) {
assert("Wrong id");
id = 0;
}
for (size_t i = current_count; i < new_count; ++i) {
encoders.emplace_back(add_screen(layout.screens[i]));
if (!screens[id].layout.dimensions.equals(screen.dimensions)) {
remove_screen(id);
add_screen(id, screen);
}
}
}
bool ScreenEncoderManager::isSupported() {
auto *base_encoder = encoders.front();
assert(!base_encoder);
return base_encoder->isSupported();
template<int T>
bool ScreenEncoderManager<T>::isSupported() const {
if (const auto *encoder = screens[head].encoder; encoder)
return encoder->isSupported();
return false;
}
void ScreenEncoderManager::writeRect(const PixelBuffer *pb, const Palette &palette) {
auto *base_encoder = encoders.front();
assert(!base_encoder);
base_encoder->writeRect(pb, palette);
template<int T>
void ScreenEncoderManager<T>::writeRect(const PixelBuffer *pb, const Palette &palette) {
if (auto *encoder = screens[head].encoder; encoder)
return encoder->writeRect(pb, palette);
}
void ScreenEncoderManager::writeSolidRect(int width, int height, const PixelFormat &pf, const rdr::U8 *colour) {
auto *base_encoder = encoders.front();
assert(!base_encoder);
base_encoder->writeSolidRect(width, height, pf, colour);
template<int T>
void ScreenEncoderManager<T>::writeSolidRect(int width, int height, const PixelFormat &pf, const rdr::U8 *colour) {
if (auto *encoder = screens[head].encoder; encoder)
encoder->writeSolidRect(width, height, pf, colour);
}
} // namespace rfb

View File

@ -6,36 +6,45 @@
#include "rfb/ffmpeg.h"
namespace rfb {
template<int T = ScreenSet::MAX_SCREENS>
class ScreenEncoderManager final : public Encoder {
struct screen_t {
Screen layout{};
Encoder *encoder{};
};
uint8_t head{};
uint8_t tail{};
std::array<screen_t, T> screens{};
const FFmpeg &ffmpeg;
VideoEncoderParams current_params;
KasmVideoEncoders::Encoder base_video_encoder;
std::vector<KasmVideoEncoders::Encoder> available_encoders;
std::vector<Encoder *> encoders;
Encoder *add_screen(const Screen &layout) const;
Encoder *add_encoder(const Screen &layout) const;
void add_screen(uint8_t index, const Screen &layout);
[[nodiscard]] size_t get_screen_count() const;
void remove_screen(Encoder *encoder);
Encoder *get_screen(size_t screen_id) const;
VideoEncoder *get_video_encoder(size_t index) const;
void remove_screen(uint8_t index);
public:
// Iterators
using iterator = std::vector<Encoder *>::iterator;
using const_iterator = std::vector<Encoder *>::const_iterator;
// Iterator
using iterator = typename std::array<screen_t, T>::iterator;
using const_iterator = typename std::array<screen_t, T>::const_iterator;
iterator begin() {
return encoders.begin();
return screens.begin();
}
iterator end() {
return encoders.end();
return screens.end();
}
[[nodiscard]] const_iterator cbegin() const {
return encoders.begin();
return screens.begin();
}
[[nodiscard]] const_iterator cend() const {
return encoders.end();
return screens.end();
}
explicit ScreenEncoderManager(const FFmpeg &ffmpeg_, KasmVideoEncoders::Encoder encoder,
@ -50,10 +59,14 @@ namespace rfb {
void sync_layout(const ScreenSet &layout);
KasmVideoEncoders::Encoder get_encoder() const { return base_video_encoder; }
// Encoder
bool isSupported() override;
bool isSupported() const override;
void writeRect(const PixelBuffer *pb, const Palette &palette) override;
void writeSolidRect(int width, int height, const PixelFormat &pf, const rdr::U8 *colour) override;
};
template class ScreenEncoderManager<>;
} // namespace rfb

View File

@ -4,22 +4,24 @@ extern "C" {
#include <libavutil/opt.h>
}
#include "KasmVideoConstants.h"
#include "rfb/LogWriter.h"
#include "rfb/SConnection.h"
#include "rfb/ServerCore.h"
#include "rfb/encodings.h"
#include "rfb/ffmpeg.h"
#include <rfb/LogWriter.h>
#include <rfb/SConnection.h>
#include <rfb/ServerCore.h>
#include <rfb/encodings.h>
#include <rfb/ffmpeg.h>
#include <fmt/format.h>
static rfb::LogWriter vlog("H264SoftwareEncoder");
static rfb::LogWriter vlog("SoftwareEncoder");
namespace rfb {
SoftwareEncoder::SoftwareEncoder(Screen layout_, const FFmpeg &ffmpeg_, SConnection *conn, KasmVideoEncoders::Encoder encoder_,
VideoEncoderParams params) :
Encoder(conn, encodingKasmVideo, static_cast<EncoderFlags>(EncoderUseNativePF | EncoderLossy), -1), layout(layout_),
ffmpeg(ffmpeg_), encoder(encoder_), current_params(params) {
codec = ffmpeg.avcodec_find_encoder(AV_CODEC_ID_H264);
ffmpeg(ffmpeg_), encoder(encoder_), current_params(params), msg_codec_id(KasmVideoEncoders::to_msg_id(encoder)) {
const auto *enc_name = KasmVideoEncoders::to_string(encoder);
codec = ffmpeg.avcodec_find_encoder_by_name(enc_name);
if (!codec)
throw std::runtime_error("Could not find H264 encoder");
throw std::runtime_error(fmt::format("Could not find {} encoder", enc_name));
auto *frame = ffmpeg.av_frame_alloc();
if (!frame) {
@ -34,7 +36,7 @@ namespace rfb {
pkt_guard.reset(pkt);
}
bool SoftwareEncoder::isSupported() {
bool SoftwareEncoder::isSupported() const {
return conn->cp.supportsEncoding(encodingKasmVideo);
}
@ -111,7 +113,8 @@ namespace rfb {
vlog.debug("Key frame %ld", frame->pts);
auto *os = conn->getOutStream(conn->cp.supportsUdp);
os->writeU8(kasmVideoH264 << 4);
os->writeU8(layout.id);
os->writeU8(msg_codec_id);
os->writeU8(pkt->flags & AV_PKT_FLAG_KEY);
write_compact(os, pkt->size);
os->writeBytes(&pkt->data[0], pkt->size);

View File

@ -19,6 +19,7 @@ namespace rfb {
KasmVideoEncoders::Encoder encoder;
VideoEncoderParams current_params{};
uint8_t msg_codec_id;
int64_t pts{};
int bpp{};
@ -30,7 +31,7 @@ namespace rfb {
SoftwareEncoder(Screen layout, const FFmpeg &ffmpeg, SConnection *conn, KasmVideoEncoders::Encoder encoder,
VideoEncoderParams params);
public:
bool isSupported() override;
bool isSupported() const override;
void writeRect(const PixelBuffer *pb, const Palette &palette) override;
void writeSolidRect(int width, int height, const PixelFormat &pf, const rdr::U8 *colour) override;
void writeSkipRect() override;

View File

@ -259,7 +259,7 @@ namespace rfb {
return true;*/
}
bool VAAPIEncoder::isSupported() {
bool VAAPIEncoder::isSupported() const {
return conn->cp.supportsEncoding(encodingKasmVideo);
}

View File

@ -62,7 +62,7 @@ namespace rfb {
public:
VAAPIEncoder(uint32_t id, SConnection *conn, uint8_t frame_rate, uint16_t bit_rate);
bool isSupported() override;
bool isSupported() const override;
void writeRect(const PixelBuffer *pb, const Palette &palette) override;
void writeSolidRect(int width, int height, const PixelFormat &pf, const rdr::U8 *colour) override;
void writeSkipRect() override;

View File

@ -100,15 +100,23 @@ namespace rfb {
VideoEncoderParams params) {
switch (video_encoder) {
case KasmVideoEncoders::Encoder::h264_vaapi:
case KasmVideoEncoders::Encoder::h265_vaapi:
case KasmVideoEncoders::Encoder::av1_vaapi:
// return
// H264VAAPIEncoderBuilder::create().with_connection(conn).with_frame_rate(frame_rate).with_bit_rate(bit_rate).build();
case KasmVideoEncoders::Encoder::h264_ffmpeg_vaapi:
case KasmVideoEncoders::Encoder::h265_ffmpeg_vaapi:
case KasmVideoEncoders::Encoder::av1_ffmpeg_vaapi:
return FFMPEGVAAPIEncoderBuilder::create(ffmpeg)
.with_layout(layout)
.with_connection(conn)
.with_encoder(video_encoder)
.with_params(params)
.build();
case KasmVideoEncoders::Encoder::h264_nvenc:
case KasmVideoEncoders::Encoder::h265_nvenc:
case KasmVideoEncoders::Encoder::av1_nvenc:
throw std::runtime_error("NVENC is not supported yet");
default:
return SoftwareEncoderBuilder::create(ffmpeg)
.with_layout(layout)

View File

@ -21,80 +21,109 @@
namespace rfb {
const int encodingRaw = 0;
const int encodingCopyRect = 1;
const int encodingRRE = 2;
const int encodingCoRRE = 4;
const int encodingHextile = 5;
const int encodingTight = 7;
const int encodingUdp = 8;
const int encodingZRLE = 16;
const int encodingKasmVideo = 17;
constexpr int encodingRaw = 0;
constexpr int encodingCopyRect = 1;
constexpr int encodingRRE = 2;
constexpr int encodingCoRRE = 4;
constexpr int encodingHextile = 5;
constexpr int encodingTight = 7;
constexpr int encodingUdp = 8;
constexpr int encodingZRLE = 16;
constexpr int encodingKasmVideo = 17;
const int encodingMax = 255;
constexpr int encodingMax = 255;
const int pseudoEncodingXCursor = -240;
const int pseudoEncodingCursor = -239;
const int pseudoEncodingDesktopSize = -223;
const int pseudoEncodingLEDState = -261;
const int pseudoEncodingExtendedDesktopSize = -308;
const int pseudoEncodingDesktopName = -307;
const int pseudoEncodingFence = -312;
const int pseudoEncodingContinuousUpdates = -313;
const int pseudoEncodingCursorWithAlpha = -314;
const int pseudoEncodingQEMUKeyEvent = -258;
constexpr int pseudoEncodingXCursor = -240;
constexpr int pseudoEncodingCursor = -239;
constexpr int pseudoEncodingDesktopSize = -223;
constexpr int pseudoEncodingLEDState = -261;
constexpr int pseudoEncodingExtendedDesktopSize = -308;
constexpr int pseudoEncodingDesktopName = -307;
constexpr int pseudoEncodingFence = -312;
constexpr int pseudoEncodingContinuousUpdates = -313;
constexpr int pseudoEncodingCursorWithAlpha = -314;
constexpr int pseudoEncodingQEMUKeyEvent = -258;
// TightVNC-specific
const int pseudoEncodingLastRect = -224;
const int pseudoEncodingQualityLevel0 = -32;
const int pseudoEncodingQualityLevel9 = -23;
const int pseudoEncodingCompressLevel0 = -256;
const int pseudoEncodingCompressLevel9 = -247;
constexpr int pseudoEncodingLastRect = -224;
constexpr int pseudoEncodingQualityLevel0 = -32;
constexpr int pseudoEncodingQualityLevel9 = -23;
constexpr int pseudoEncodingCompressLevel0 = -256;
constexpr int pseudoEncodingCompressLevel9 = -247;
// TurboVNC-specific
const int pseudoEncodingFineQualityLevel0 = -512;
const int pseudoEncodingFineQualityLevel100 = -412;
const int pseudoEncodingSubsamp1X = -768;
const int pseudoEncodingSubsamp4X = -767;
const int pseudoEncodingSubsamp2X = -766;
const int pseudoEncodingSubsampGray = -765;
const int pseudoEncodingSubsamp8X = -764;
const int pseudoEncodingSubsamp16X = -763;
constexpr int pseudoEncodingFineQualityLevel0 = -512;
constexpr int pseudoEncodingFineQualityLevel100 = -412;
constexpr int pseudoEncodingSubsamp1X = -768;
constexpr int pseudoEncodingSubsamp4X = -767;
constexpr int pseudoEncodingSubsamp2X = -766;
constexpr int pseudoEncodingSubsampGray = -765;
constexpr int pseudoEncodingSubsamp8X = -764;
constexpr int pseudoEncodingSubsamp16X = -763;
// Kasm-specific
const int pseudoEncodingWEBP = -1024;
const int pseudoEncodingJpegVideoQualityLevel0 = -1023;
const int pseudoEncodingJpegVideoQualityLevel9 = -1014;
const int pseudoEncodingWebpVideoQualityLevel0 = -1013;
const int pseudoEncodingWebpVideoQualityLevel9 = -1004;
const int pseudoEncodingTreatLosslessLevel0 = -1003;
const int pseudoEncodingTreatLosslessLevel10 = -993;
const int pseudoEncodingPreferBandwidth = -992;
const int pseudoEncodingDynamicQualityMinLevel0 = -991;
const int pseudoEncodingDynamicQualityMinLevel9 = -982;
const int pseudoEncodingDynamicQualityMaxLevel0 = -981;
const int pseudoEncodingDynamicQualityMaxLevel9 = -972;
const int pseudoEncodingVideoAreaLevel1 = -971;
const int pseudoEncodingVideoAreaLevel100 = -871;
const int pseudoEncodingVideoTimeLevel0 = -870;
const int pseudoEncodingVideoTimeLevel100 = -770;
constexpr int pseudoEncodingWEBP = -1024;
constexpr int pseudoEncodingJpegVideoQualityLevel0 = -1023;
constexpr int pseudoEncodingJpegVideoQualityLevel9 = -1014;
constexpr int pseudoEncodingWebpVideoQualityLevel0 = -1013;
constexpr int pseudoEncodingWebpVideoQualityLevel9 = -1004;
constexpr int pseudoEncodingTreatLosslessLevel0 = -1003;
constexpr int pseudoEncodingTreatLosslessLevel10 = -993;
constexpr int pseudoEncodingPreferBandwidth = -992;
constexpr int pseudoEncodingDynamicQualityMinLevel0 = -991;
constexpr int pseudoEncodingDynamicQualityMinLevel9 = -982;
constexpr int pseudoEncodingDynamicQualityMaxLevel0 = -981;
constexpr int pseudoEncodingDynamicQualityMaxLevel9 = -972;
constexpr int pseudoEncodingVideoAreaLevel1 = -971;
constexpr int pseudoEncodingVideoAreaLevel100 = -871;
constexpr int pseudoEncodingVideoTimeLevel0 = -870;
constexpr int pseudoEncodingVideoTimeLevel100 = -770;
const int pseudoEncodingFrameRateLevel10 = -2048;
const int pseudoEncodingFrameRateLevel60 = -1998;
const int pseudoEncodingMaxVideoResolution = -1997;
const int pseudoEncodingVideoScalingLevel0 = -1996;
const int pseudoEncodingVideoScalingLevel9 = -1987;
const int pseudoEncodingVideoOutTimeLevel1 = -1986;
const int pseudoEncodingVideoOutTimeLevel100 = -1887;
const int pseudoEncodingQOI = -1886;
const int pseudoEncodingKasmDisconnectNotify = -1885;
constexpr int pseudoEncodingFrameRateLevel10 = -2048;
constexpr int pseudoEncodingFrameRateLevel60 = -1998;
constexpr int pseudoEncodingMaxVideoResolution = -1997;
constexpr int pseudoEncodingVideoScalingLevel0 = -1996;
constexpr int pseudoEncodingVideoScalingLevel9 = -1987;
constexpr int pseudoEncodingVideoOutTimeLevel1 = -1986;
constexpr int pseudoEncodingVideoOutTimeLevel100 = -1887;
constexpr int pseudoEncodingQOI = -1886;
constexpr int pseudoEncodingKasmDisconnectNotify = -1885;
constexpr int pseudoEncodingHardwareProfile0 = -1170;
constexpr int pseudoEncodingHardwareProfile4 = -1166;
constexpr int pseudoEncodingGOP1 = -1165;
constexpr int pseudoEncodingGOP60 = -1105;
constexpr int pseudoEncodingStreamingVideoQualityLevel0 = -1104;
constexpr int pseudoEncodingStreamingVideoQualityLevel63 = -1041;
// AV1
constexpr int pseudoEncodingStreamingModeAV1QSV = -1040;
constexpr int pseudoEncodingStreamingModeAV1NVENC = -1039;
constexpr int pseudoEncodingStreamingModeAV1VAAPI = -1038;
constexpr int pseudoEncodingStreamingModeAV1SW = -1037;
constexpr int pseudoEncodingStreamingModeAV1 = -1036;
// h.265
constexpr int pseudoEncodingStreamingModeHEVCQSV = -1035;
constexpr int pseudoEncodingStreamingModeHEVCNVENC = -1034;
constexpr int pseudoEncodingStreamingModeHEVCVAAPI = -1033;
constexpr int pseudoEncodingStreamingModeHEVCSW = -1032;
constexpr int pseudoEncodingStreamingModeHEVC = -1031;
// h.264
constexpr int pseudoEncodingStreamingModeAVCQSV = -1030;
constexpr int pseudoEncodingStreamingModeAVCNVENC = -1029;
constexpr int pseudoEncodingStreamingModeAVCVAAPI = -1028;
constexpr int pseudoEncodingStreamingModeAVCSW = -1027;
constexpr int pseudoEncodingStreamingModeAVC = -1026;
constexpr int pseudoEncodingStreamingModeJpegWebp = -1025;
// VMware-specific
const int pseudoEncodingVMwareCursor = 0x574d5664;
const int pseudoEncodingVMwareCursorPosition = 0x574d5666;
constexpr int pseudoEncodingVMwareCursor = 0x574d5664;
constexpr int pseudoEncodingVMwareCursorPosition = 0x574d5666;
// UltraVNC-specific
const int pseudoEncodingExtendedClipboard = 0xC0A1E5CE;
constexpr int pseudoEncodingExtendedClipboard = 0xC0A1E5CE;
int encodingNum(const char* name);
const char* encodingName(int num);

View File

@ -21,57 +21,58 @@
namespace rfb {
// server to client
const int msgTypeFramebufferUpdate = 0;
const int msgTypeSetColourMapEntries = 1;
const int msgTypeBell = 2;
const int msgTypeServerCutText = 3;
constexpr int msgTypeFramebufferUpdate = 0;
constexpr int msgTypeSetColourMapEntries = 1;
constexpr int msgTypeBell = 2;
constexpr int msgTypeServerCutText = 3;
const int msgTypeEndOfContinuousUpdates = 150;
constexpr int msgTypeEndOfContinuousUpdates = 150;
// kasm
const int msgTypeStats = 178;
const int msgTypeRequestFrameStats = 179;
const int msgTypeBinaryClipboard = 180;
const int msgTypeUpgradeToUdp = 181;
const int msgTypeSubscribeUnixRelay = 182;
const int msgTypeUnixRelay = 183;
const int msgTypeKeepAlive = 184;
const int msgTypeServerDisconnect = 185;
const int msgTypeServerFence = 248;
const int msgTypeUserAddedToSession = 253;
const int msgTypeUserRemovedFromSession = 254;
constexpr int msgTypeStats = 178;
constexpr int msgTypeRequestFrameStats = 179;
constexpr int msgTypeBinaryClipboard = 180;
constexpr int msgTypeUpgradeToUdp = 181;
constexpr int msgTypeSubscribeUnixRelay = 182;
constexpr int msgTypeUnixRelay = 183;
constexpr int msgTypeVideoEncoders = 184;
constexpr int msgTypeKeepAlive = 185;
constexpr int msgTypeServerDisconnect = 186;
constexpr int msgTypeServerFence = 248;
constexpr int msgTypeUserAddedToSession = 253;
constexpr int msgTypeUserRemovedFromSession = 254;
// client to server
const int msgTypeSetPixelFormat = 0;
const int msgTypeFixColourMapEntries = 1;
const int msgTypeSetEncodings = 2;
const int msgTypeFramebufferUpdateRequest = 3;
const int msgTypeKeyEvent = 4;
const int msgTypePointerEvent = 5;
const int msgTypeClientCutText = 6;
constexpr int msgTypeSetPixelFormat = 0;
constexpr int msgTypeFixColourMapEntries = 1;
constexpr int msgTypeSetEncodings = 2;
constexpr int msgTypeFramebufferUpdateRequest = 3;
constexpr int msgTypeKeyEvent = 4;
constexpr int msgTypePointerEvent = 5;
constexpr int msgTypeClientCutText = 6;
const int msgTypeEnableContinuousUpdates = 150;
constexpr int msgTypeEnableContinuousUpdates = 150;
// kasm
const int msgTypeRequestStats = 178;
const int msgTypeFrameStats = 179;
constexpr int msgTypeRequestStats = 178;
constexpr int msgTypeFrameStats = 179;
// same as the other direction
//const int msgTypeBinaryClipboard = 180;
//const int msgTypeUpgradeToUdp = 181;
//const int msgTypeSubscribeUnixRelay = 182;
//const int msgTypeUnixRelay = 183;
//const int msgTypeKeepAlive = 184;
//const int msgTypeServerDisconnect = 185;
//constexpr int msgTypeBinaryClipboard = 180;
//constexpr int msgTypeUpgradeToUdp = 181;
//constexpr int msgTypeSubscribeUnixRelay = 182;
//constexpr int msgTypeUnixRelay = 183;
//constexpr int msgTypeVideoEncoders = 184;
//constexpr int msgTypeKeepAlive = 185;
//constexpr int msgTypeServerDisconnect = 186;
const int msgTypeClientFence = 248;
constexpr int msgTypeClientFence = 248;
const int msgTypeSetDesktopSize = 251;
constexpr int msgTypeSetDesktopSize = 251;
const int msgTypeSetMaxVideoResolution = 252;
constexpr int msgTypeSetMaxVideoResolution = 252;
const int msgTypeQEMUClientMessage = 255;
constexpr int msgTypeQEMUClientMessage = 255;
}
#endif