rebase and fix conflicts

This commit is contained in:
matt 2025-05-03 09:31:00 +00:00
commit 38eb2c6110
No known key found for this signature in database
56 changed files with 6855 additions and 406 deletions

View file

@ -29,6 +29,7 @@ stages:
- pwd
- apk add bash
- mkdir -p "$GITLAB_SHARED_DIND_DIR" && chmod 777 "$GITLAB_SHARED_DIND_DIR"
- docker login --username $DOCKER_HUB_USERNAME --password $DOCKER_HUB_PASSWORD
.prepare_www: &prepare_www
- tar -zxf output/www/kasm_www.tar.gz -C builder/
@ -527,9 +528,14 @@ build_fedora_fortyone_arm:
test:
stage: test
tags:
- oci-fixed-amd
- kasmvnc-x86
before_script:
- *prepare_build
artifacts:
reports:
junit:
- SelfBench.xml
- Benchmark.xml
script:
- bash builder/test-vncserver

View file

@ -91,7 +91,7 @@ npm run build # <-- only run this on subsequent changes to front-end code
cd ..
# build dependencies
sudo builder/scripts/build-webp
sudo builder/scripts/build-build-libjpeg-turbo
sudo builder/scripts/build-libjpeg-turbo
# Build KasmVNC
builder/build.sh
```

View file

@ -227,6 +227,7 @@ include_directories(${CMAKE_BINARY_DIR})
include(cmake/StaticBuild.cmake)
add_subdirectory(third_party)
add_subdirectory(common)
if(WIN32)
@ -239,11 +240,12 @@ else()
endif()
if(ENABLE_NLS)
add_subdirectory(po)
add_subdirectory(po)
endif()
#############add_subdirectory(tests)
if (TESTS)
add_subdirectory(tests)
endif()
include(cmake/BuildPackages.cmake)

View file

@ -44,38 +44,41 @@ if [[ "${XORG_VER}" == 21* ]]; then
else
XORG_PATCH=$(echo "$XORG_VER" | grep -Po '^\d.\d+' | sed 's#\.##')
fi
wget --no-check-certificate https://www.x.org/archive/individual/xserver/xorg-server-${XORG_VER}.tar.gz
TARBALL="xorg-server-${XORG_VER}.tar.gz"
if [ ! -f "$TARBALL" ]; then
wget --no-check-certificate https://www.x.org/archive/individual/xserver/"$TARBALL"
fi
#git clone https://kasmweb@bitbucket.org/kasmtech/kasmvnc.git
#cd kasmvnc
#git checkout dynjpeg
cd /src
# We only want the server, so FLTK and manual tests aren't useful.
# Alternatively, install fltk 1.3 and its dev packages.
sed -i -e '/find_package(FLTK/s@^@#@' \
-e '/add_subdirectory(tests/s@^@#@' \
CMakeLists.txt
cmake -D CMAKE_BUILD_TYPE=RelWithDebInfo . -DBUILD_VIEWER:BOOL=OFF \
-DENABLE_GNUTLS:BOOL=OFF
make -j5
make -j"$(nproc)"
tar -C unix/xserver -xf /tmp/xorg-server-${XORG_VER}.tar.gz --strip-components=1
if [ ! -d unix/xserver/include ]; then
tar -C unix/xserver -xf /tmp/"$TARBALL" --strip-components=1
cd unix/xserver
# Apply patches
patch -Np1 -i ../xserver${XORG_PATCH}.patch
case "$XORG_VER" in
1.20.*)
patch -s -p0 < ../CVE-2022-2320-v1.20.patch
if [ -f ../xserver120.7.patch ]; then
patch -Np1 -i ../xserver120.7.patch
fi ;;
1.19.*)
patch -s -p0 < ../CVE-2022-2320-v1.19.patch
;;
esac
cd unix/xserver
# Apply patches
patch -Np1 -i ../xserver"${XORG_PATCH}".patch
case "$XORG_VER" in
1.20.*)
patch -s -p0 < ../CVE-2022-2320-v1.20.patch
if [ -f ../xserver120.7.patch ]; then
patch -Np1 -i ../xserver120.7.patch
fi ;;
1.19.*)
patch -s -p0 < ../CVE-2022-2320-v1.19.patch
;;
esac
else
cd unix/xserver
fi
autoreconf -i
# Configuring Xorg is long and has many distro-specific paths.
@ -110,23 +113,27 @@ fi
--with-sha1=libcrypto \
--with-xkb-bin-directory=/usr/bin \
--with-xkb-output=/var/lib/xkb \
--with-xkb-path=/usr/share/X11/xkb ${CONFIG_OPTIONS}
--with-xkb-path=/usr/share/X11/xkb "${CONFIG_OPTIONS}"
# remove array bounds errors for new versions of GCC
find . -name "Makefile" -exec sed -i 's/-Werror=array-bounds//g' {} \;
make -j5
make -j"$(nproc)"
# modifications for the servertarball
cd /src
mkdir -p xorg.build/bin
mkdir -p xorg.build/lib
cd xorg.build/bin/
ln -sf /src/unix/xserver/hw/vnc/Xvnc Xvnc
ln -sfn /src/unix/xserver/hw/vnc/Xvnc Xvnc
cd ..
mkdir -p man/man1
touch man/man1/Xserver.1
cp /src/unix/xserver/hw/vnc/Xvnc.man man/man1/Xvnc.1
mkdir -p lib
cd lib
if [ -d /usr/lib/x86_64-linux-gnu/dri ]; then
ln -sfn /usr/lib/x86_64-linux-gnu/dri dri

View file

@ -14,6 +14,7 @@ RUN \
bash \
ca-certificates \
cmake \
nasm \
coreutils \
curl \
eudev-dev \
@ -66,7 +67,8 @@ RUN \
xorgproto \
xorg-server-common \
xorg-server-dev \
xtrans
xtrans \
ffmpeg-dev
ENV SCRIPTS_DIR=/tmp/scripts

View file

@ -14,6 +14,7 @@ RUN \
bash \
ca-certificates \
cmake \
nasm \
coreutils \
curl \
eudev-dev \
@ -66,7 +67,8 @@ RUN \
xorgproto \
xorg-server-common \
xorg-server-dev \
xtrans
xtrans \
ffmpeg-dev
ENV SCRIPTS_DIR=/tmp/scripts

View file

@ -14,6 +14,7 @@ RUN \
bash \
ca-certificates \
cmake \
nasm \
coreutils \
curl \
eudev-dev \
@ -66,7 +67,8 @@ RUN \
xorgproto \
xorg-server-common \
xorg-server-dev \
xtrans
xtrans \
ffmpeg-dev
ENV SCRIPTS_DIR=/tmp/scripts

View file

@ -1,7 +1,7 @@
FROM alpine:3.21
RUN apk add shadow bash
RUN apk add abuild sudo less
RUN apk add abuild sudo less ffmpeg-dev
ENV HOME /src/alpine
WORKDIR $HOME/kasmvncserver

View file

@ -14,6 +14,7 @@ RUN \
bash \
ca-certificates \
cmake \
nasm \
coreutils \
curl \
eudev-dev \
@ -66,7 +67,8 @@ RUN \
xorgproto \
xorg-server-common \
xorg-server-dev \
xtrans
xtrans \
ffmpeg-dev
ENV SCRIPTS_DIR=/tmp/scripts

View file

@ -22,8 +22,9 @@ RUN apt-get update && \
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends tzdata
RUN apt-get update && apt-get -y build-dep xorg-server libxfont-dev
RUN apt-get update && apt-get -y install cmake git libgnutls28-dev vim wget tightvncserver curl
RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev libxcursor-dev
RUN apt-get update && apt-get -y install ninja-build cmake nasm git libgnutls28-dev vim wget tightvncserver curl
RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev \
libxcursor-dev libavformat-dev libswscale-dev
ENV SCRIPTS_DIR=/tmp/scripts
COPY builder/scripts $SCRIPTS_DIR

View file

@ -12,8 +12,9 @@ RUN apt-get update && \
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends tzdata
RUN apt-get update && apt-get -y build-dep xorg-server libxfont-dev
RUN apt-get update && apt-get -y install git libgnutls28-dev vim wget tightvncserver curl
RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev libxcursor-dev
RUN apt-get update && apt-get -y install ninja-build nasm git libgnutls28-dev vim wget tightvncserver curl
RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev \
libxcursor-dev libavformat-dev libswscale-dev
RUN CMAKE_URL="https://cmake.org/files/v3.22/cmake-3.22.0" && \
ARCH=$(arch) && \

View file

@ -12,7 +12,7 @@ RUN apt-get update && \
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends tzdata
RUN apt-get update && apt-get -y build-dep xorg-server libxfont-dev
RUN apt-get update && apt-get -y install cmake git libgnutls28-dev vim wget tightvncserver curl
RUN apt-get update && apt-get -y install ninja-build cmake nasm git libgnutls28-dev vim wget tightvncserver curl
RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev libxcursor-dev
ENV SCRIPTS_DIR=/tmp/scripts

View file

@ -16,6 +16,7 @@ RUN \
byacc \
bzip2 \
cmake \
nasm \
diffutils \
doxygen \
file \
@ -71,7 +72,9 @@ RUN \
xorg-x11-server-common \
xorg-x11-server-devel \
xorg-x11-xtrans-devel \
xsltproc
xsltproc \
libavformat-free-devel \
libswscale-free-devel
ENV SCRIPTS_DIR=/tmp/scripts
COPY builder/scripts $SCRIPTS_DIR

View file

@ -17,6 +17,7 @@ RUN \
byacc \
bzip2 \
cmake \
nasm \
diffutils \
doxygen \
file \
@ -72,7 +73,9 @@ RUN \
xorg-x11-server-common \
xorg-x11-server-devel \
xorg-x11-xtrans-devel \
xsltproc
xsltproc \
libavformat-free-devel \
libswscale-free-devel
ENV SCRIPTS_DIR=/tmp/scripts
COPY builder/scripts $SCRIPTS_DIR

View file

@ -16,6 +16,7 @@ RUN \
byacc \
bzip2 \
cmake \
nasm \
diffutils \
doxygen \
file \
@ -71,7 +72,9 @@ RUN \
xorg-x11-server-common \
xorg-x11-server-devel \
xorg-x11-xtrans-devel \
xsltproc
xsltproc \
libavformat-free-devel \
libswscale-free-devel
ENV SCRIPTS_DIR=/tmp/scripts
COPY builder/scripts $SCRIPTS_DIR

View file

@ -13,8 +13,9 @@ RUN apt-get update && \
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends tzdata
RUN apt-get update && apt-get -y build-dep xorg-server libxfont-dev
RUN apt-get update && apt-get -y install gcc g++ curl
RUN apt-get update && apt-get -y install cmake git libgnutls28-dev vim wget tightvncserver
RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev libxcursor-dev
RUN apt-get update && apt-get -y install ninja-build cmake nasm git libgnutls28-dev vim wget tightvncserver
RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev \
libxcursor-dev libavformat-dev libswscale-dev
ENV SCRIPTS_DIR=/tmp/scripts
COPY builder/scripts $SCRIPTS_DIR

View file

@ -8,9 +8,13 @@ ENV XORG_VER 1.20.3
RUN zypper install -ny \
bdftopcf \
bigreqsproto-devel \
ninja \
cmake \
nasm \
curl \
ffmpeg-4-libavcodec-devel \
ffmpeg-4-libswscale-devel \
ffmpeg-4-libavformat-devel \
fonttosfnt \
font-util \
gcc14 \
@ -49,7 +53,6 @@ RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 140 \
--slave /usr/bin/g++ g++ /usr/bin/g++-14 \
--slave /usr/bin/gcov gcov /usr/bin/gcov-14
RUN useradd -u 1000 docker && \
groupadd -g 1000 docker && \
usermod -a -G docker docker

View file

@ -11,7 +11,9 @@ RUN \
dnf install -y \
bzip2-devel \
ca-certificates \
ninja-build \
cmake \
nasm \
dnf-plugins-core \
gcc \
gcc-c++ \

View file

@ -11,7 +11,9 @@ RUN \
dnf install -y \
bzip2-devel \
ca-certificates \
ninja-build \
cmake \
nasm \
dnf-plugins-core \
gcc \
gcc-c++ \
@ -41,6 +43,7 @@ RUN dnf install -y --nogpgcheck https://mirrors.rpmfusion.org/free/el/rpmfusion-
# Install from new repos
RUN dnf install -y \
giflib-devel \
ffmpeg-devel \
lbzip2 \
libXfont2-devel \
libxkbfile-devel \

View file

@ -12,13 +12,12 @@ RUN apt-get update && \
RUN apt-get update && apt-get install -y --no-install-recommends tzdata
RUN apt-get update && apt-get -y build-dep xorg-server libxfont-dev
RUN apt-get update && apt-get -y install cmake git vim wget curl
RUN apt-get update && apt-get -y install libtbb-dev libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev libxcursor-dev
RUN apt-get update && apt-get -y install ninja-build nasm git vim wget curl
RUN apt-get update && apt-get -y install libtbb-dev libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev \
libxcursor-dev libavformat-dev libswscale-dev
ENV SCRIPTS_DIR=/tmp/scripts
COPY builder/scripts $SCRIPTS_DIR
RUN $SCRIPTS_DIR/build-webp
RUN $SCRIPTS_DIR/build-libjpeg-turbo
RUN useradd -m docker && echo "docker:docker" | chpasswd && adduser docker sudo
@ -35,6 +34,9 @@ RUN ARCH=$(arch) && \
(echo y; echo n) | bash cmake.sh --prefix=/usr/local --skip-license && \
rm cmake.sh
RUN $SCRIPTS_DIR/build-webp
RUN $SCRIPTS_DIR/build-libjpeg-turbo
COPY --chown=docker:docker . /src/
USER docker

View file

@ -6,7 +6,7 @@ RUN apt-get update && apt-get install -y vim less
RUN apt-get update && apt-get install -y python3-pip
RUN apt-get update && apt-get install -y strace silversearcher-ag xfonts-base
RUN apt-get update && apt-get install -y cinnamon
RUN apt-get update && apt-get install -y mate
RUN apt-get update && apt-get install -y mate wget
RUN useradd -m docker

View file

@ -12,8 +12,9 @@ RUN apt-get update && \
RUN apt-get update && apt-get install -y --no-install-recommends tzdata
RUN apt-get update && apt-get -y build-dep xorg-server libxfont-dev
RUN apt-get update && apt-get -y install cmake git libgnutls28-dev vim wget tightvncserver curl
RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev libxcursor-dev
RUN apt-get update && apt-get -y install ninja-build cmake nasm git libgnutls28-dev vim wget tightvncserver curl
RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev \
libxcursor-dev libavformat-dev libswscale-dev
ENV SCRIPTS_DIR=/tmp/scripts
COPY builder/scripts $SCRIPTS_DIR

View file

@ -14,15 +14,58 @@ COPY builder/conf/nginx_kasm.conf /etc/nginx/conf.d/
RUN sed -i 's$# deb-src$deb-src$' /etc/apt/sources.list && \
apt update && \
apt install -y socat vim wget tightvncserver curl inotify-tools sudo \
libxfont-dev libgnutls28-dev libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev libxcursor-dev pkg-config libfreetype6-dev \
libxtst-dev xutils-dev libpixman-1-dev libxshmfence-dev libxcvt-dev libxkbfile-dev x11proto-dev libgbm-dev \
cmake git autoconf automake libtool && \
apt install -y \
ninja-build \
gdb \
valgrind \
rsync \
dos2unix \
socat \
sudo \
libxfont-dev \
cmake \
nasm \
git \
libgnutls28-dev \
vim \
wget \
tightvncserver \
curl \
libpng-dev \
libtiff-dev \
libgif-dev \
libavformat-dev \
libavcodec-dev \
libswscale-dev \
libssl-dev \
libxrandr-dev \
libxcursor-dev \
pkg-config \
libfreetype6-dev \
libxtst-dev \
autoconf \
automake \
libtool \
xutils-dev \
libpixman-1-dev \
libxshmfence-dev \
libxcvt-dev \
libxkbfile-dev \
x11proto-dev \
libgbm-dev \
inotify-tools && \
echo "kasm-user ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
RUN apt install -y nodejs nginx
COPY builder/scripts/build-webp /tmp
COPY builder/scripts/build-libjpeg-turbo /tmp
COPY builder/common.sh /tmp
RUN chmod +x /tmp/build-webp && /tmp/build-webp
RUN chmod +x /tmp/build-libjpeg-turbo && /tmp/build-libjpeg-turbo
USER 1000
WORKDIR /src

View file

@ -12,8 +12,9 @@ RUN apt-get update && \
RUN apt-get update && apt-get install -y --no-install-recommends tzdata
RUN apt-get update && apt-get -y build-dep xorg-server libxfont-dev
RUN apt-get update && apt-get -y install cmake git libgnutls28-dev vim wget curl
RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev libxcursor-dev
RUN apt-get update && apt-get -y install ninja-build cmake nasm git libgnutls28-dev vim wget curl
RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev \
libxcursor-dev libavformat-dev libswscale-dev
ENV SCRIPTS_DIR=/tmp/scripts
COPY builder/scripts $SCRIPTS_DIR

View file

@ -3,20 +3,8 @@
set -euo pipefail
build_and_install() {
export MAKEFLAGS=-j`nproc`
export CFLAGS="-fpic"
cmake -DCMAKE_INSTALL_PREFIX=/usr/local -G"Unix Makefiles"
make
make install
}
install_build_dependencies() {
install_packages cmake gcc
ensure_libjpeg_is_fast
}
ensure_libjpeg_is_fast() {
install_packages nasm
cmake -B build -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_POSITION_INDEPENDENT_CODE=ON -GNinja .
ninja -C build install
}
prepare_libjpeg_source() {
@ -32,6 +20,5 @@ prepare_libjpeg_source() {
source_dir=$(dirname "$0")
. "$source_dir/common.sh"
install_build_dependencies
prepare_libjpeg_source
build_and_install

View file

@ -9,7 +9,7 @@ add_subdirectory(rfb)
# because PIC code does not exist on that platform and MinGW complains if -fPIC
# is passed (additionally, libvnc is not used on Windows.)
if(NOT WIN32)
set_target_properties(os rdr network Xregion rfb
PROPERTIES COMPILE_FLAGS -fPIC)
endif()
if (NOT WIN32)
set_target_properties(os rdr network Xregion rfb
PROPERTIES COMPILE_FLAGS -fPIC)
endif ()

View file

@ -1,84 +1,84 @@
include_directories(${CMAKE_SOURCE_DIR}/common ${JPEG_INCLUDE_DIR} ${PNG_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/unix/kasmvncpasswd)
set(RFB_SOURCES
Blacklist.cxx
Congestion.cxx
CConnection.cxx
CMsgHandler.cxx
CMsgReader.cxx
CMsgWriter.cxx
CSecurityPlain.cxx
CSecurityStack.cxx
CSecurityVeNCrypt.cxx
CSecurityVncAuth.cxx
ComparingUpdateTracker.cxx
Configuration.cxx
ConnParams.cxx
CopyRectDecoder.cxx
Cursor.cxx
DecodeManager.cxx
Decoder.cxx
d3des.c
EncCache.cxx
EncodeManager.cxx
Encoder.cxx
HextileDecoder.cxx
HextileEncoder.cxx
JpegCompressor.cxx
JpegDecompressor.cxx
KeyRemapper.cxx
LogWriter.cxx
Logger.cxx
Logger_file.cxx
Logger_stdio.cxx
Password.cxx
PixelBuffer.cxx
PixelFormat.cxx
RREEncoder.cxx
RREDecoder.cxx
RawDecoder.cxx
RawEncoder.cxx
Region.cxx
SConnection.cxx
SMsgHandler.cxx
SMsgReader.cxx
SMsgWriter.cxx
ServerCore.cxx
Security.cxx
SecurityServer.cxx
SecurityClient.cxx
SelfBench.cxx
SSecurityPlain.cxx
SSecurityStack.cxx
SSecurityVncAuth.cxx
SSecurityVeNCrypt.cxx
ScaleFilters.cxx
Timer.cxx
TightDecoder.cxx
TightEncoder.cxx
TightJPEGEncoder.cxx
TightWEBPEncoder.cxx
TightQOIEncoder.cxx
UpdateTracker.cxx
VNCSConnectionST.cxx
VNCServerST.cxx
ZRLEEncoder.cxx
ZRLEDecoder.cxx
Watermark.cxx
cpuid.cxx
encodings.cxx
util.cxx
xxhash.c)
benchmark.cxx
Blacklist.cxx
Congestion.cxx
CConnection.cxx
CMsgHandler.cxx
CMsgReader.cxx
CMsgWriter.cxx
CSecurityPlain.cxx
CSecurityStack.cxx
CSecurityVeNCrypt.cxx
CSecurityVncAuth.cxx
ComparingUpdateTracker.cxx
Configuration.cxx
ConnParams.cxx
CopyRectDecoder.cxx
Cursor.cxx
DecodeManager.cxx
Decoder.cxx
d3des.c
EncCache.cxx
EncodeManager.cxx
Encoder.cxx
HextileDecoder.cxx
HextileEncoder.cxx
JpegCompressor.cxx
JpegDecompressor.cxx
KeyRemapper.cxx
LogWriter.cxx
Logger.cxx
Logger_file.cxx
Logger_stdio.cxx
Password.cxx
PixelBuffer.cxx
PixelFormat.cxx
RREEncoder.cxx
RREDecoder.cxx
RawDecoder.cxx
RawEncoder.cxx
Region.cxx
SConnection.cxx
SMsgHandler.cxx
SMsgReader.cxx
SMsgWriter.cxx
ServerCore.cxx
Security.cxx
SecurityServer.cxx
SecurityClient.cxx
SelfBench.cxx
SSecurityPlain.cxx
SSecurityStack.cxx
SSecurityVncAuth.cxx
SSecurityVeNCrypt.cxx
ScaleFilters.cxx
Timer.cxx
TightDecoder.cxx
TightEncoder.cxx
TightJPEGEncoder.cxx
TightWEBPEncoder.cxx
TightQOIEncoder.cxx
UpdateTracker.cxx
VNCSConnectionST.cxx
VNCServerST.cxx
ZRLEEncoder.cxx
ZRLEDecoder.cxx
Watermark.cxx
cpuid.cxx
encodings.cxx
util.cxx
xxhash.c
ffmpeg.cxx
)
if(UNIX)
set(RFB_SOURCES ${RFB_SOURCES} Logger_syslog.cxx)
endif()
if (UNIX)
set(RFB_SOURCES ${RFB_SOURCES} Logger_syslog.cxx)
endif ()
if(WIN32)
include_directories(${CMAKE_SOURCE_DIR}/win)
set(RFB_SOURCES ${RFB_SOURCES} WinPasswdValidator.cxx)
endif(WIN32)
if (WIN32)
include_directories(${CMAKE_SOURCE_DIR}/win)
set(RFB_SOURCES ${RFB_SOURCES} WinPasswdValidator.cxx)
endif (WIN32)
set(RFB_LIBRARIES ${JPEG_LIBRARIES} ${PNG_LIBRARIES} os rdr Xregion)
@ -88,49 +88,64 @@ if ((CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_
set(RFB_LIBRARIES ${RFB_LIBRARIES} tbb)
endif ()
if(HAVE_PAM)
set(RFB_SOURCES ${RFB_SOURCES} UnixPasswordValidator.cxx
UnixPasswordValidator.h pam.c pam.h)
set(RFB_LIBRARIES ${RFB_LIBRARIES} ${PAM_LIBS})
endif()
if (HAVE_PAM)
set(RFB_SOURCES
${RFB_SOURCES}
UnixPasswordValidator.cxx
UnixPasswordValidator.h pam.c pam.h)
set(RFB_LIBRARIES ${RFB_LIBRARIES} ${PAM_LIBS})
endif ()
if(GNUTLS_FOUND)
set(RFB_SOURCES
${RFB_SOURCES}
CSecurityTLS.cxx
SSecurityTLS.cxx
)
set(RFB_LIBRARIES
${RFB_LIBRARIES}
${GNUTLS_LIBRARIES}
)
endif()
if (GNUTLS_FOUND)
set(RFB_SOURCES
${RFB_SOURCES}
CSecurityTLS.cxx
SSecurityTLS.cxx
)
set(RFB_LIBRARIES
${RFB_LIBRARIES}
${GNUTLS_LIBRARIES}
)
endif ()
# SSE2
set(SSE2_SOURCES
scale_sse2.cxx)
scale_sse2.cxx)
set(SCALE_DUMMY_SOURCES
scale_dummy.cxx)
scale_dummy.cxx)
if(COMPILER_SUPPORTS_SSE2)
set_source_files_properties(${SSE2_SOURCES} PROPERTIES COMPILE_FLAGS ${COMPILE_FLAGS} -msse2)
set(RFB_SOURCES
${RFB_SOURCES}
${SSE2_SOURCES}
)
else()
set(RFB_SOURCES
${RFB_SOURCES}
${SCALE_DUMMY_SOURCES}
)
endif()
if (COMPILER_SUPPORTS_SSE2)
set_source_files_properties(${SSE2_SOURCES} PROPERTIES COMPILE_FLAGS ${COMPILE_FLAGS} -msse2)
set(RFB_SOURCES
${RFB_SOURCES}
${SSE2_SOURCES}
)
else ()
set(RFB_SOURCES
${RFB_SOURCES}
${SCALE_DUMMY_SOURCES}
)
endif ()
find_package(PkgConfig REQUIRED)
pkg_check_modules(FFMPEG REQUIRED libavcodec libavformat libavutil libswscale)
add_library(rfb STATIC ${RFB_SOURCES})
target_link_libraries(rfb ${RFB_LIBRARIES})
target_include_directories(rfb PRIVATE
${CMAKE_SOURCE_DIR}/common
${JPEG_INCLUDE_DIR}
${PNG_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/unix/kasmvncpasswd
${CMAKE_SOURCE_DIR}/third_party/tinyxml2
${FFMPEG_INCLUDE_DIRS}
)
if(UNIX)
libtool_create_control_file(rfb)
endif()
target_link_libraries(rfb PRIVATE ${RFB_LIBRARIES} tinyxml2_objs)
if (UNIX)
libtool_create_control_file(rfb)
endif ()

View file

@ -360,7 +360,6 @@ void EncodeManager::doUpdate(bool allowLossy, const Region& changed_,
int nRects;
Region changed, cursorRegion;
struct timeval start;
unsigned screenArea;
updates++;
if (conn->cp.supportsUdp)
@ -390,16 +389,7 @@ void EncodeManager::doUpdate(bool allowLossy, const Region& changed_,
memset(&webpstats, 0, sizeof(codecstats_t));
if (allowLossy && activeEncoders[encoderFullColour] == encoderTightWEBP) {
const unsigned rate = 1024 * 1000 / rfb::Server::frameRate;
screenArea = pb->getRect().width() * pb->getRect().height();
screenArea *= 1024;
screenArea /= 256 * 256;
screenArea *= webpBenchResult;
// Encoding the entire screen would take this many 1024*msecs, worst case
// Calculate how many us we can send webp for, before switching to jpeg
webpFallbackUs = rate * rate / screenArea;
webpFallbackUs = (1000 * 1000 / rfb::Server::frameRate) * (static_cast<double>(Server::webpEncodingTime) / 100.0);
}
/*
@ -883,7 +873,7 @@ void EncodeManager::findSolidRect(const Rect& rect, Region *changed,
void EncodeManager::checkWebpFallback(const timeval *start) {
// Have we taken too long for the frame? If so, drop from WEBP to JPEG
if (start && activeEncoders[encoderFullColour] == encoderTightWEBP && !webpTookTooLong.load(std::memory_order_relaxed)) {
const auto us = msSince(start) * 1024;
const auto us = msSince(start) * 1000;
if (us > webpFallbackUs)
webpTookTooLong.store(true, std::memory_order_relaxed);
}

View file

@ -20,8 +20,6 @@
#include <string.h>
#include <rfb/Exception.h>
#include <rfb/Security.h>
#include <rfb/clipboardTypes.h>
#include <rfb/msgTypes.h>
#include <rfb/fenceTypes.h>
#include <rfb/SMsgReader.h>
#include <rfb/SMsgWriter.h>
@ -274,19 +272,16 @@ void SConnection::writeConnFailedFromScratch(const char* msg,
os->flush();
}
void SConnection::setEncodings(int nEncodings, const rdr::S32* encodings)
{
int i;
preferredEncoding = encodingRaw;
for (i = 0;i < nEncodings;i++) {
if (EncodeManager::supported(encodings[i])) {
preferredEncoding = encodings[i];
break;
void SConnection::setEncodings(int nEncodings, const rdr::S32 *encodings) {
preferredEncoding = encodingRaw;
for (int i = 0; i < nEncodings; i++) {
if (EncodeManager::supported(encodings[i])) {
preferredEncoding = encodings[i];
break;
}
}
}
SMsgHandler::setEncodings(nEncodings, encodings);
SMsgHandler::setEncodings(nEncodings, encodings);
}
void SConnection::clearBinaryClipboard()

View file

@ -26,27 +26,32 @@
#include <rfb/TightWEBPEncoder.h>
#include <rfb/util.h>
#include <sys/time.h>
#include <stdint.h>
#include <stdlib.h>
#include <cstdint>
#include <cstdlib>
#include <chrono>
#include <tinyxml2.h>
using namespace rfb;
static LogWriter vlog("SelfBench");
static const PixelFormat pfRGBX(32, 24, false, true, 255, 255, 255, 0, 8, 16);
#define RUNS 64
static constexpr uint32_t RUNS = 64;
#define W 1600
#define H 1200
static constexpr uint32_t WIDTH = 1600;
static constexpr uint32_t HEIGHT = 1200;
void SelfBench() {
tinyxml2::XMLDocument doc;
unsigned i, runs;
struct timeval start;
auto *test_suit = doc.NewElement("testsuite");
test_suit->SetAttribute("name", "SelfBench");
ManagedPixelBuffer f1(pfRGBX, W, H);
ManagedPixelBuffer f2(pfRGBX, W, H);
ManagedPixelBuffer screen(pfRGBX, W, H);
doc.InsertFirstChild(test_suit);
ManagedPixelBuffer f1(pfRGBX, WIDTH, HEIGHT);
ManagedPixelBuffer f2(pfRGBX, WIDTH, HEIGHT);
ManagedPixelBuffer screen(pfRGBX, WIDTH, HEIGHT);
int stride;
rdr::U8 *f1ptr = f1.getBufferRW(f1.getRect(), &stride);
@ -56,7 +61,7 @@ void SelfBench() {
rdr::U8 * const f1orig = f1ptr;
rdr::U8 * const f2orig = f2ptr;
for (i = 0; i < W * H * 4; i += 4) {
for (uint32_t i = 0; i < WIDTH * HEIGHT * 4; i += 4) {
f1ptr[0] = rand();
f1ptr[1] = rand();
f1ptr[2] = rand();
@ -74,124 +79,116 @@ void SelfBench() {
// Encoding
std::vector<uint8_t> vec;
TightJPEGEncoder jpeg(NULL);
TightJPEGEncoder jpeg(nullptr);
gettimeofday(&start, NULL);
runs = RUNS;
for (i = 0; i < runs; i++) {
uint32_t test_cases {};
uint64_t total_time {};
auto benchmark = [&doc, &test_suit, &test_cases, &total_time](const char *name, uint32_t runs, auto func) {
auto now = std::chrono::high_resolution_clock::now();
for (uint32_t i = 0; i < runs; i++) {
func(i);
}
++test_cases;
auto value = elapsedMs(now);
double junit_value = value / 1000.;
total_time += value;
vlog.info("%s took %lu ms (%u runs)", name, value, runs);
auto *test_case = doc.NewElement("testcase");
test_case->SetAttribute("name", name);
test_case->SetAttribute("time", junit_value);
test_case->SetAttribute("runs", runs);
test_case->SetAttribute("classname", "KasmVNC");
test_suit->InsertEndChild(test_case);
};
benchmark("Jpeg compression at quality 8", RUNS, [&jpeg, &vec, &f1](uint32_t) {
jpeg.compressOnly(&f1, 8, vec, false);
}
vlog.info("Jpeg compression at quality 8 took %u ms (%u runs)", msSince(&start), runs);
});
gettimeofday(&start, NULL);
runs = RUNS;
for (i = 0; i < runs; i++) {
benchmark("Jpeg compression at quality 4", RUNS, [&jpeg, &vec, &f1](uint32_t) {
jpeg.compressOnly(&f1, 4, vec, false);
}
vlog.info("Jpeg compression at quality 4 took %u ms (%u runs)", msSince(&start), runs);
});
TightWEBPEncoder webp(nullptr);
TightWEBPEncoder webp(NULL);
gettimeofday(&start, NULL);
runs = RUNS / 8;
for (i = 0; i < runs; i++) {
benchmark("Webp compression at quality 8", RUNS / 8, [&webp,&f1, &vec](uint32_t) {
webp.compressOnly(&f1, 8, vec, false);
}
vlog.info("Webp compression at quality 8 took %u ms (%u runs)", msSince(&start), runs);
});
gettimeofday(&start, NULL);
runs = RUNS / 4;
for (i = 0; i < runs; i++) {
benchmark("Webp compression at quality 4", RUNS / 4, [&webp, &f1, &vec](uint32_t) {
webp.compressOnly(&f1, 4, vec, false);
}
vlog.info("Webp compression at quality 4 took %u ms (%u runs)", msSince(&start), runs);
});
// Scaling
gettimeofday(&start, NULL);
runs = RUNS;
for (i = 0; i < runs; i++) {
PixelBuffer *pb = nearestScale(&f1, W * 0.8, H * 0.8, 0.8);
benchmark("Nearest scaling to 80%", RUNS, [&f1](uint32_t) {
PixelBuffer *pb = nearestScale(&f1, WIDTH * 0.8, HEIGHT * 0.8, 0.8);
delete pb;
}
vlog.info("Nearest scaling to 80%% took %u ms (%u runs)", msSince(&start), runs);
});
gettimeofday(&start, NULL);
runs = RUNS;
for (i = 0; i < runs; i++) {
PixelBuffer *pb = nearestScale(&f1, W * 0.4, H * 0.4, 0.4);
benchmark("Nearest scaling to 40%", RUNS, [&f1](uint32_t) {
PixelBuffer *pb = nearestScale(&f1, WIDTH * 0.4, HEIGHT * 0.4, 0.4);
delete pb;
}
vlog.info("Nearest scaling to 40%% took %u ms (%u runs)", msSince(&start), runs);
});
gettimeofday(&start, NULL);
runs = RUNS;
for (i = 0; i < runs; i++) {
PixelBuffer *pb = bilinearScale(&f1, W * 0.8, H * 0.8, 0.8);
benchmark("Bilinear scaling to 80%", RUNS, [&f1](uint32_t) {
PixelBuffer *pb = bilinearScale(&f1, WIDTH * 0.8, HEIGHT * 0.8, 0.8);
delete pb;
}
vlog.info("Bilinear scaling to 80%% took %u ms (%u runs)", msSince(&start), runs);
});
gettimeofday(&start, NULL);
runs = RUNS;
for (i = 0; i < runs; i++) {
PixelBuffer *pb = bilinearScale(&f1, W * 0.4, H * 0.4, 0.4);
benchmark("Bilinear scaling to 40%", RUNS, [&f1](uint32_t) {
PixelBuffer *pb = bilinearScale(&f1, WIDTH * 0.4, HEIGHT * 0.4, 0.4);
delete pb;
}
vlog.info("Bilinear scaling to 40%% took %u ms (%u runs)", msSince(&start), runs);
});
gettimeofday(&start, NULL);
runs = RUNS;
for (i = 0; i < runs; i++) {
PixelBuffer *pb = progressiveBilinearScale(&f1, W * 0.8, H * 0.8, 0.8);
delete pb;
}
vlog.info("Progressive bilinear scaling to 80%% took %u ms (%u runs)", msSince(&start), runs);
gettimeofday(&start, NULL);
runs = RUNS;
for (i = 0; i < runs; i++) {
PixelBuffer *pb = progressiveBilinearScale(&f1, W * 0.4, H * 0.4, 0.4);
benchmark("Progressive bilinear scaling to 80%", RUNS, [&f1](uint32_t) {
PixelBuffer *pb = progressiveBilinearScale(&f1, WIDTH * 0.8, HEIGHT * 0.8, 0.8);
delete pb;
}
vlog.info("Progressive bilinear scaling to 40%% took %u ms (%u runs)", msSince(&start), runs);
});
benchmark("Progressive bilinear scaling to 40%", RUNS, [&f1](uint32_t) {
PixelBuffer *pb = progressiveBilinearScale(&f1, WIDTH * 0.4, HEIGHT * 0.4, 0.4);
delete pb;
});
// Analysis
ComparingUpdateTracker *comparer = new ComparingUpdateTracker(&screen);
auto *comparer = new ComparingUpdateTracker(&screen);
Region cursorReg;
Server::detectScrolling.setParam(false);
Server::detectHorizontal.setParam(false);
gettimeofday(&start, NULL);
runs = RUNS;
for (i = 0; i < runs; i++) {
memcpy(screenptr, i % 2 ? f1orig : f2orig, W * H * 4);
comparer->compare(true, cursorReg);
}
vlog.info("Analysis took %u ms (%u runs) (incl. memcpy overhead)", msSince(&start), runs);
benchmark("Analysis (incl. memcpy overhead)", RUNS,
[&screenptr, &comparer, &cursorReg, f1orig, f2orig](uint32_t i) {
memcpy(screenptr, i % 2 ? f1orig : f2orig, WIDTH * HEIGHT * 4);
comparer->compare(true, cursorReg);
});
Server::detectScrolling.setParam(true);
gettimeofday(&start, NULL);
runs = RUNS;
for (i = 0; i < runs; i++) {
memcpy(screenptr, i % 2 ? f1orig : f2orig, W * H * 4);
comparer->compare(false, cursorReg);
}
vlog.info("Analysis w/ scroll detection took %u ms (%u runs) (incl. memcpy overhead)", msSince(&start), runs);
benchmark("Analysis w/ scroll detection (incl. memcpy overhead)", RUNS,
[&screenptr, &comparer, &cursorReg, f1orig, f2orig](uint32_t i) {
memcpy(screenptr, i % 2 ? f1orig : f2orig, WIDTH * HEIGHT * 4);
comparer->compare(false, cursorReg);
});
Server::detectHorizontal.setParam(true);
delete comparer;
comparer = new ComparingUpdateTracker(&screen);
gettimeofday(&start, NULL);
runs = RUNS / 2;
for (i = 0; i < runs; i++) {
memcpy(screenptr, i % 2 ? f1orig : f2orig, W * H * 4);
comparer->compare(false, cursorReg);
}
vlog.info("Analysis w/ horizontal scroll detection took %u ms (%u runs) (incl. memcpy overhead)", msSince(&start), runs);
benchmark("Analysis w/ horizontal scroll detection (incl. memcpy overhead)", RUNS / 2,
[&screenptr, &comparer, &cursorReg, f1orig, f2orig](uint32_t i) {
memcpy(screenptr, i % 2 ? f1orig : f2orig, WIDTH * HEIGHT * 4);
comparer->compare(false, cursorReg);
});
test_suit->SetAttribute("tests", test_cases);
test_suit->SetAttribute("failures", 0);
test_suit->SetAttribute("time", total_time);
doc.SaveFile("SelfBench.xml");
exit(0);
}

View file

@ -32,15 +32,15 @@ rfb::IntParameter rfb::Server::idleTimeout
0, 0);
rfb::IntParameter rfb::Server::maxDisconnectionTime
("MaxDisconnectionTime",
"Terminate when no client has been connected for s seconds",
"Terminate when no client has been connected for s seconds",
0, 0);
rfb::IntParameter rfb::Server::maxConnectionTime
("MaxConnectionTime",
"Terminate when a client has been connected for s seconds",
"Terminate when a client has been connected for s seconds",
0, 0);
rfb::IntParameter rfb::Server::maxIdleTime
("MaxIdleTime",
"Terminate after s seconds of user inactivity",
"Terminate after s seconds of user inactivity",
0, 0);
rfb::IntParameter rfb::Server::clientWaitTimeMillis
("ClientWaitTimeMillis",
@ -117,6 +117,16 @@ rfb::BoolParameter rfb::Server::selfBench
("SelfBench",
"Run self-benchmarks and exit.",
false);
rfb::StringParameter rfb::Server::benchmark(
"Benchmark",
"Run extended benchmarks and exit.",
"");
rfb::StringParameter rfb::Server::benchmarkResults(
"BenchmarkResults",
"The file to save becnhmark results to.",
"Benchmark.xml");
rfb::IntParameter rfb::Server::dynamicQualityMin
("DynamicQualityMin",
"The minimum dynamic JPEG quality, 0 = low, 9 = high",
@ -271,19 +281,24 @@ rfb::IntParameter rfb::Server::udpFullFrameFrequency
("udpFullFrameFrequency",
"Send a full frame every N frames for clients using UDP. 0 to disable",
0, 0, 1000);
rfb::IntParameter rfb::Server::udpPort
("udpPort",
"Which port to use for UDP. Default same as websocket",
0, 0, 65535);
static void bandwidthPreset() {
rfb::Server::dynamicQualityMin.setParam(2);
rfb::Server::dynamicQualityMax.setParam(9);
rfb::Server::treatLossless.setParam(8);
rfb::Server::dynamicQualityMin.setParam(2);
rfb::Server::dynamicQualityMax.setParam(9);
rfb::Server::treatLossless.setParam(8);
}
rfb::PresetParameter rfb::Server::preferBandwidth
("PreferBandwidth",
"Set various options for lower bandwidth use. The default is off, aka to prefer quality.",
false, bandwidthPreset);
rfb::IntParameter rfb::Server::webpEncodingTime
("webpEncodingTime",
"Percentage of time allotted for encoding a frame, that can be used for encoding rects in webp.",
30, 0, 100);

View file

@ -28,72 +28,71 @@
#include <rfb/util.h>
namespace rfb {
class Server {
public:
static IntParameter idleTimeout;
static IntParameter maxDisconnectionTime;
static IntParameter maxConnectionTime;
static IntParameter maxIdleTime;
static IntParameter clientWaitTimeMillis;
static IntParameter compareFB;
static IntParameter frameRate;
static IntParameter dynamicQualityMin;
static IntParameter dynamicQualityMax;
static IntParameter treatLossless;
static IntParameter scrollDetectLimit;
static IntParameter rectThreads;
static IntParameter DLP_ClipSendMax;
static IntParameter DLP_ClipAcceptMax;
static IntParameter DLP_ClipDelay;
static IntParameter DLP_KeyRateLimit;
static IntParameter DLP_WatermarkRepeatSpace;
static IntParameter DLP_WatermarkFontSize;
static IntParameter DLP_WatermarkTimeOffset;
static IntParameter DLP_WatermarkTimeOffsetMinutes;
static IntParameter DLP_WatermarkTextAngle;
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 StringParameter DLP_WatermarkText;
static StringParameter DLP_WatermarkFont;
static BoolParameter DLP_RegionAllowClick;
static BoolParameter DLP_RegionAllowRelease;
static IntParameter jpegVideoQuality;
static IntParameter webpVideoQuality;
static StringParameter maxVideoResolution;
static IntParameter videoTime;
static IntParameter videoOutTime;
static IntParameter videoArea;
static IntParameter videoScaling;
static IntParameter udpFullFrameFrequency;
static IntParameter udpPort;
static StringParameter kasmPasswordFile;
static StringParameter publicIP;
static StringParameter stunServer;
static BoolParameter printVideoArea;
static BoolParameter protocol3_3;
static BoolParameter alwaysShared;
static BoolParameter neverShared;
static BoolParameter disconnectClients;
static BoolParameter acceptKeyEvents;
static BoolParameter acceptPointerEvents;
static BoolParameter acceptCutText;
static BoolParameter sendCutText;
static BoolParameter acceptSetDesktopSize;
static BoolParameter queryConnect;
static BoolParameter detectScrolling;
static BoolParameter detectHorizontal;
static BoolParameter ignoreClientSettingsKasm;
static BoolParameter selfBench;
static PresetParameter preferBandwidth;
};
class Server {
public:
static IntParameter idleTimeout;
static IntParameter maxDisconnectionTime;
static IntParameter maxConnectionTime;
static IntParameter maxIdleTime;
static IntParameter clientWaitTimeMillis;
static IntParameter compareFB;
static IntParameter frameRate;
static IntParameter dynamicQualityMin;
static IntParameter dynamicQualityMax;
static IntParameter treatLossless;
static IntParameter scrollDetectLimit;
static IntParameter rectThreads;
static IntParameter DLP_ClipSendMax;
static IntParameter DLP_ClipAcceptMax;
static IntParameter DLP_ClipDelay;
static IntParameter DLP_KeyRateLimit;
static IntParameter DLP_WatermarkRepeatSpace;
static IntParameter DLP_WatermarkFontSize;
static IntParameter DLP_WatermarkTimeOffset;
static IntParameter DLP_WatermarkTimeOffsetMinutes;
static IntParameter DLP_WatermarkTextAngle;
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 StringParameter DLP_WatermarkText;
static StringParameter DLP_WatermarkFont;
static BoolParameter DLP_RegionAllowClick;
static BoolParameter DLP_RegionAllowRelease;
static IntParameter jpegVideoQuality;
static IntParameter webpVideoQuality;
static StringParameter maxVideoResolution;
static IntParameter videoTime;
static IntParameter videoOutTime;
static IntParameter videoArea;
static IntParameter videoScaling;
static IntParameter udpFullFrameFrequency;
static IntParameter udpPort;
static StringParameter kasmPasswordFile;
static StringParameter publicIP;
static StringParameter stunServer;
static BoolParameter printVideoArea;
static BoolParameter protocol3_3;
static BoolParameter alwaysShared;
static BoolParameter neverShared;
static BoolParameter disconnectClients;
static BoolParameter acceptKeyEvents;
static BoolParameter acceptPointerEvents;
static BoolParameter acceptCutText;
static BoolParameter sendCutText;
static BoolParameter acceptSetDesktopSize;
static BoolParameter queryConnect;
static BoolParameter detectScrolling;
static BoolParameter detectHorizontal;
static BoolParameter ignoreClientSettingsKasm;
static BoolParameter selfBench;
static StringParameter benchmark;
static StringParameter benchmarkResults;
static PresetParameter preferBandwidth;
static IntParameter webpEncodingTime;
};
};
#endif // __RFB_SERVER_CORE_H__
#endif // __RFB_SERVER_CORE_H__

View file

@ -25,6 +25,12 @@
#include <rfb/TightJPEGEncoder.h>
#include <rfb/TightConstants.h>
#include <ctime>
#include <fstream>
#include <sstream>
#include <iomanip>
using namespace rfb;
struct TightJPEGConfiguration {

View file

@ -259,13 +259,14 @@ void TightWEBPEncoder::writeRect(const PixelBuffer* pb, const Palette& palette)
WebPMemoryWriterClear(&wrt);
}
// How many milliseconds would it take to encode a 256x256 block at quality 8
// How many milliseconds would it take to encode a 256x256 block at quality 5
rdr::U32 TightWEBPEncoder::benchmark() const
{
rdr::U8* buffer;
struct timeval start;
int stride, i;
const uint8_t quality = 8, method = 2;
// the minimum WebP quality settings used in KasmVNC
const uint8_t quality = 5, method = 0;
WebPConfig cfg;
WebPPicture pic;
WebPMemoryWriter wrt;

View file

@ -62,7 +62,7 @@ VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s,
losslessTimer(this), kbdLogTimer(this), binclipTimer(this),
server(server_), updates(false),
updateRenderedCursor(false), removeRenderedCursor(false),
continuousUpdates(false), encodeManager(this, &server_->encCache),
continuousUpdates(false), encodeManager(this, &VNCServerST::encCache),
needsPermCheck(false), pointerEventTime(0),
clientHasCursor(false),
accessRights(AccessDefault), startTime(time(0)), frameTracking(false),

View file

@ -48,8 +48,8 @@
// otherwise blacklisted connections might be "forgotten".
#include <assert.h>
#include <stdlib.h>
#include <cassert>
#include <cstdlib>
#include <network/GetAPI.h>
#include <network/Udp.h>
@ -73,6 +73,8 @@
#include <sys/inotify.h>
#include <unistd.h>
#include <wordexp.h>
#include <filesystem>
#include <string_view>
using namespace rfb;
@ -82,6 +84,8 @@ EncCache VNCServerST::encCache;
void SelfBench();
void benchmark(std::string_view, std::string_view);
//
// -=- VNCServerST Implementation
//
@ -128,13 +132,13 @@ static void parseRegionPart(const bool percents, rdr::U16 &pcdest, int &dest,
VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_)
: blHosts(&blacklist), desktop(desktop_), desktopStarted(false),
blockCounter(0), pb(0), blackedpb(0), ledState(ledUnknown),
name(strDup(name_)), pointerClient(0), clipboardClient(0),
comparer(0), cursor(new Cursor(0, 0, Point(), NULL)),
blockCounter(0), pb(nullptr), blackedpb(nullptr), ledState(ledUnknown),
name(strDup(name_)), pointerClient(nullptr), clipboardClient(nullptr),
comparer(nullptr), cursor(new Cursor(0, 0, Point(), nullptr)),
renderedCursorInvalid(false),
queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance),
queryConnectionHandler(nullptr), keyRemapper(&KeyRemapper::defInstance),
lastConnectionTime(0), disableclients(false),
frameTimer(this), apimessager(NULL), trackingFrameStats(0),
frameTimer(this), apimessager(nullptr), trackingFrameStats(0),
clipboardId(0), sendWatermark(false)
{
lastUserInputTime = lastDisconnectTime = time(0);
@ -198,7 +202,7 @@ VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_)
}
}
DLPRegion.enabled = 1;
DLPRegion.enabled = true;
}
kasmpasswdpath[0] = '\0';
@ -223,11 +227,18 @@ VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_)
trackingClient[0] = 0;
if (watermarkData)
sendWatermark = true;
if (watermarkData)
sendWatermark = true;
if (Server::selfBench)
SelfBench();
if (Server::selfBench)
SelfBench();
if (Server::benchmark[0]) {
auto *file_name = Server::benchmark.getValueStr();
if (!std::filesystem::exists(file_name))
throw Exception("Benchmarking video file does not exist");
benchmark(file_name, Server::benchmarkResults.getValueStr());
}
}
VNCServerST::~VNCServerST()

View file

@ -148,7 +148,7 @@ namespace rfb {
// the connection.
enum queryResult { ACCEPT, REJECT, PENDING };
struct QueryConnectionHandler {
virtual ~QueryConnectionHandler() {}
virtual ~QueryConnectionHandler() = default;
virtual queryResult queryConnection(network::Socket* sock,
const char* userName,
char** reason) = 0;

396
common/rfb/benchmark.cxx Normal file
View file

@ -0,0 +1,396 @@
/* Copyright 2015 Pierre Ossman <ossman@cendio.se> for Cendio AB
* Copyright (C) 2015 D. R. Commander. All Rights Reserved.
* Copyright (C) 2025 Kasm Technologies Corp
*
* 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 "benchmark.h"
#include <string_view>
#include <rfb/LogWriter.h>
#include <numeric>
#include <tinyxml2.h>
#include <algorithm>
#include <cassert>
#include "ServerCore.h"
#include <cmath>
#include "EncCache.h"
#include "EncodeManager.h"
#include "SConnection.h"
#include "screenTypes.h"
#include "SMsgWriter.h"
#include "UpdateTracker.h"
#include "rdr/BufferedInStream.h"
#include "rdr/OutStream.h"
#include "ffmpeg.h"
namespace benchmarking {
class MockBufferStream final : public rdr::BufferedInStream {
bool fillBuffer(size_t maxSize, bool wait) override {
return true;
}
};
class MockStream final : public rdr::OutStream {
public:
MockStream() {
offset = 0;
ptr = buf;
end = buf + sizeof(buf);
}
private:
void overrun(size_t needed) override {
assert(end >= ptr);
if (needed > static_cast<size_t>(end - ptr))
flush();
}
public:
size_t length() override {
flush();
return offset;
}
void flush() override {
offset += ptr - buf;
ptr = buf;
}
private:
ptrdiff_t offset;
rdr::U8 buf[8192]{};
};
class MockSConnection final : public rfb::SConnection {
public:
MockSConnection() {
setStreams(nullptr, &out);
setWriter(new rfb::SMsgWriter(&cp, &out, &udps));
}
~MockSConnection() override = default;
void writeUpdate(const rfb::UpdateInfo &ui, const rfb::PixelBuffer *pb) {
cache.clear();
manager.clearEncodingTime();
if (!ui.is_empty()) {
manager.writeUpdate(ui, pb, nullptr);
} else {
rfb::Region region{pb->getRect()};
manager.writeLosslessRefresh(region, pb, nullptr, 2000);
}
}
void setDesktopSize(int fb_width, int fb_height,
const rfb::ScreenSet &layout) override {
cp.width = fb_width;
cp.height = fb_height;
cp.screenLayout = layout;
writer()->writeExtendedDesktopSize(rfb::reasonServer, 0, cp.width, cp.height,
cp.screenLayout);
}
void sendStats(const bool toClient) override {
}
[[nodiscard]] bool canChangeKasmSettings() const override {
return true;
}
void udpUpgrade(const char *resp) override {
}
void udpDowngrade(const bool) override {
}
void subscribeUnixRelay(const char *name) override {
}
void unixRelay(const char *name, const rdr::U8 *buf, const unsigned len) override {
}
void handleFrameStats(rdr::U32 all, rdr::U32 render) override {
}
[[nodiscard]] auto getJpegStats() const {
return manager.jpegstats;
}
[[nodiscard]] auto getWebPStats() const {
return manager.webpstats;
}
[[nodiscard]] auto bytes() { return out.length(); }
[[nodiscard]] auto udp_bytes() { return udps.length(); }
protected:
MockStream out{};
MockStream udps{};
EncCache cache{};
EncodeManager manager{this, &cache};
};
class MockCConnection final : public MockTestConnection {
public:
explicit MockCConnection(const std::vector<rdr::S32> &encodings, rfb::ManagedPixelBuffer *pb) {
setStreams(&in, nullptr);
// Need to skip the initial handshake and ServerInit
setState(RFBSTATE_NORMAL);
// That also means that the reader and writer weren't set up
setReader(new rfb::CMsgReader(this, &in));
auto &pf = pb->getPF();
CMsgHandler::setPixelFormat(pf);
MockCConnection::setDesktopSize(pb->width(), pb->height());
cp.setPF(pf);
sc.cp.setPF(pf);
sc.setEncodings(std::size(encodings), encodings.data());
setFramebuffer(pb);
}
void setCursor(int width, int height, const rfb::Point &hotspot, const rdr::U8 *data,
const bool resizing) override {
}
~MockCConnection() override = default;
struct stats_t {
EncodeManager::codecstats_t jpeg_stats;
EncodeManager::codecstats_t webp_stats;
uint64_t bytes;
uint64_t udp_bytes;
};
[[nodiscard]] stats_t getStats() {
return {
sc.getJpegStats(),
sc.getWebPStats(),
sc.bytes(),
sc.udp_bytes()
};
}
void setDesktopSize(int w, int h) override {
CConnection::setDesktopSize(w, h);
if (screen_layout.num_screens())
screen_layout.remove_screen(0);
screen_layout.add_screen(rfb::Screen(0, 0, 0, w, h, 0));
}
void setNewFrame(const AVFrame *frame) override {
auto *pb = getFramebuffer();
const int width = pb->width();
const int height = pb->height();
const rfb::Rect rect(0, 0, width, height);
int dstStride{};
auto *buffer = pb->getBufferRW(rect, &dstStride);
const rfb::PixelFormat &pf = pb->getPF();
// Source data and stride from FFmpeg
const auto *srcData = frame->data[0];
const int srcStride = frame->linesize[0] / 3; // Convert bytes to pixels
// Convert from the RGB format to the PixelBuffer's format
pf.bufferFromRGB(buffer, srcData, width, srcStride, height);
// Commit changes
pb->commitBufferRW(rect);
}
void framebufferUpdateStart() override {
updates.clear();
}
void framebufferUpdateEnd() override {
const rfb::PixelBuffer *pb = getFramebuffer();
rfb::UpdateInfo ui;
const rfb::Region clip(pb->getRect());
updates.add_changed(pb->getRect());
updates.getUpdateInfo(&ui, clip);
sc.writeUpdate(ui, pb);
}
void dataRect(const rfb::Rect &r, int encoding) override {
}
void setColourMapEntries(int, int, rdr::U16 *) override {
}
void bell() override {
}
void serverCutText(const char *, rdr::U32) override {
}
void serverCutText(const char *str) override {
}
protected:
MockBufferStream in;
rfb::ScreenSet screen_layout;
rfb::SimpleUpdateTracker updates;
MockSConnection sc;
};
}
void report(std::vector<uint64_t> &totals, std::vector<uint64_t> &timings,
std::vector<benchmarking::MockCConnection::stats_t> &stats, const std::string_view results_file) {
auto totals_sum = std::accumulate(totals.begin(), totals.end(), 0.);
auto totals_avg = totals_sum / static_cast<double>(totals.size());
auto variance = 0.;
for (auto t: totals)
variance += (static_cast<double>(t) - totals_avg) * (static_cast<double>(t) - totals_avg);
variance /= static_cast<double>(totals.size());
auto stddev = std::sqrt(variance);
const auto sum = std::accumulate(timings.begin(), timings.end(), 0.);
const auto size = timings.size();
const auto average = sum / static_cast<double>(size);
double median{};
std::sort(timings.begin(), timings.end());
if (size % 2 == 0)
median = static_cast<double>(timings[size / 2]);
else
median = static_cast<double>(timings[size / 2 - 1] + timings[size / 2]) / 2.;
vlog.info("Mean time encoding frame: %f ms", average);
vlog.info("Median time encoding frame: %f ms", median);
vlog.info("Total time (mean): %f ms", totals_avg);
vlog.info("Total time (stddev): %f ms", stddev);
uint32_t jpeg_sum{}, jpeg_rects{}, webp_sum{}, webp_rects{};
uint64_t bytes{};
for (const auto &item: stats) {
jpeg_sum += item.jpeg_stats.ms;
jpeg_rects += item.jpeg_stats.rects;
webp_sum += item.webp_stats.ms;
webp_rects += item.webp_stats.rects;
bytes += item.bytes;
}
auto jpeg_ms = jpeg_sum / static_cast<double>(stats.size());
vlog.info("JPEG stats: %f ms", jpeg_ms);
jpeg_rects /= stats.size();
vlog.info("JPEG stats: %u rects", jpeg_rects);
auto webp_ms = webp_sum / static_cast<double>(stats.size());
webp_rects /= stats.size();
bytes /= stats.size();
vlog.info("WebP stats: %f ms", webp_ms);
vlog.info("WebP stats: %u rects", webp_rects);
vlog.info("Total bytes sent: %lu bytes", bytes);
tinyxml2::XMLDocument doc;
auto *test_suit = doc.NewElement("testsuite");
test_suit->SetAttribute("name", "Benchmark");
doc.InsertFirstChild(test_suit);
auto total_tests{0};
auto add_benchmark_item = [&doc, &test_suit, &total_tests](const char *name, auto time_value, auto other_value) {
auto *test_case = doc.NewElement("testcase");
test_case->SetAttribute("name", name);
test_case->SetAttribute("file", other_value);
test_case->SetAttribute("time", time_value);
test_case->SetAttribute("runs", 1);
test_case->SetAttribute("classname", "KasmVNC");
test_suit->InsertEndChild(test_case);
++total_tests;
};
constexpr auto mult = 1 / 1000.;
add_benchmark_item("Average time encoding frame, ms", average * mult, "");
add_benchmark_item("Median time encoding frame, ms", median * mult, "");
add_benchmark_item("Total time encoding, ms", 0, totals_avg);
add_benchmark_item("Total time encoding, stddev", 0, stddev);
add_benchmark_item("Mean JPEG stats, ms", jpeg_ms, "");
add_benchmark_item("Mean JPEG stats, rects", 0., jpeg_rects);
add_benchmark_item("Mean WebP stats, ms", webp_ms, "");
add_benchmark_item("Mean WebP stats, rects", 0, webp_rects);
add_benchmark_item("Data sent, KBs", 0, bytes / 1024);
doc.SaveFile(results_file.data());
}
void benchmark(std::string_view path, const std::string_view results_file) {
try {
vlog.info("Benchmarking with video file %s", path.data());
FFmpegFrameFeeder frame_feeder{};
frame_feeder.open(path);
static const rfb::PixelFormat pf{32, 24, false, true, 0xFF, 0xFF, 0xFF, 0, 8, 16};
std::vector<rdr::S32> encodings{
std::begin(benchmarking::default_encodings), std::end(benchmarking::default_encodings)
};
constexpr auto runs = 20;
std::vector<uint64_t> totals(runs, 0);
std::vector<benchmarking::MockCConnection::stats_t> stats(runs);
std::vector<uint64_t> timings{};
auto [width, height] = frame_feeder.get_frame_dimensions();
for (int run = 0; run < runs; ++run) {
auto *pb = new rfb::ManagedPixelBuffer{pf, width, height};
benchmarking::MockCConnection connection{encodings, pb};
vlog.info("RUN %d. Reading frames...", run);
auto play_stats = frame_feeder.play(&connection);
vlog.info("RUN %d. Done reading frames...", run);
timings.insert(timings.end(), play_stats.timings.begin(), play_stats.timings.end());
totals[run] = play_stats.total;
stats[run] = connection.getStats();
vlog.info("JPEG stats: %u ms", stats[run].jpeg_stats.ms);
vlog.info("WebP stats: %u ms", stats[run].webp_stats.ms);
vlog.info("RUN %d. Bytes sent %lu..", run, stats[run].bytes);
}
if (!timings.empty())
report(totals, timings, stats, results_file);
exit(0);
} catch (std::exception &e) {
vlog.error("Benchmarking failed: %s", e.what());
exit(1);
}
}

67
common/rfb/benchmark.h Normal file
View file

@ -0,0 +1,67 @@
/* Copyright (C) 2025 Kasm Technologies Corp
*
* 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.
*/
#pragma once
#include "CConnection.h"
#include "CMsgReader.h"
#include "LogWriter.h"
extern "C" {
#include <libavutil/frame.h>
}
static rfb::LogWriter vlog("Benchmarking");
namespace benchmarking {
using namespace rfb;
class MockTestConnection : public CConnection {
public:
virtual void setNewFrame(const AVFrame *frame) = 0;
};
static constexpr rdr::S32 default_encodings[] = {
encodingTight,
encodingHextile,
encodingRRE,
encodingRaw,
pseudoEncodingQualityLevel0 + 6,
pseudoEncodingCompressLevel0 + 2,
pseudoEncodingDesktopSize,
pseudoEncodingLastRect,
pseudoEncodingQEMUKeyEvent,
pseudoEncodingExtendedDesktopSize,
pseudoEncodingFence,
pseudoEncodingContinuousUpdates,
pseudoEncodingDesktopName,
pseudoEncodingExtendedClipboard,
pseudoEncodingWEBP,
pseudoEncodingJpegVideoQualityLevel0 + 7,
pseudoEncodingWebpVideoQualityLevel0 + 7,
pseudoEncodingTreatLosslessLevel0 + 7,
pseudoEncodingDynamicQualityMinLevel0 + 4,
pseudoEncodingDynamicQualityMaxLevel0 + 9,
pseudoEncodingVideoAreaLevel1 - 1 + 65,
pseudoEncodingVideoTimeLevel0 + 5,
pseudoEncodingVideoOutTimeLevel1 - 1 + 3,
pseudoEncodingFrameRateLevel10 - 10 + 60,
pseudoEncodingMaxVideoResolution
};
}

210
common/rfb/ffmpeg.cxx Normal file
View file

@ -0,0 +1,210 @@
/* Copyright (C) 2025 Kasm Technologies Corp
*
* 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 "ffmpeg.h"
#include <array>
#include <string_view>
#include <filesystem>
FFmpegFrameFeeder::FFmpegFrameFeeder() {
static constexpr std::array<std::string_view, 2> paths = {
"/usr/lib/",
"/usr/lib64"
};
namespace fs = std::filesystem;
using namespace std::string_literals;
auto load_lib = [](auto *lib) {
void *handle{};
for (const auto &dir: paths) {
if (!fs::exists(dir) || !fs::is_directory(dir))
continue;
for (const auto &entry: fs::recursive_directory_iterator(dir)) {
if (!entry.is_regular_file())
continue;
const std::string filename = entry.path().filename().string();
if (filename.find(lib) != std::string::npos) {
handle = dlopen(filename.c_str(), RTLD_LAZY);
break;
}
}
}
if (!handle)
throw std::runtime_error("Could not open "s + lib);
return DlHandlerGuard{handle};
};
// libavformat
libavformat = load_lib("libavformat.so");
auto handle = libavformat.get();
avformat_open_input_f = D_LOOKUP_SYM(handle, avformat_open_input);
avformat_find_stream_info_f = D_LOOKUP_SYM(handle, avformat_find_stream_info);
avcodec_find_decoder_f = D_LOOKUP_SYM(handle, avcodec_find_decoder);
avcodec_parameters_to_context_f = D_LOOKUP_SYM(handle, avcodec_parameters_to_context);
av_read_frame_f = D_LOOKUP_SYM(handle, av_read_frame);
av_seek_frame_f = D_LOOKUP_SYM(handle, av_seek_frame);
avformat_close_input_f = D_LOOKUP_SYM(handle, avformat_close_input);
vlog.info("libavformat.so loaded");
// libavutil
libavutil = load_lib("libavutil.so");
handle = libavutil.get();
av_frame_free_f = D_LOOKUP_SYM(handle, av_frame_free);
av_frame_alloc_f = D_LOOKUP_SYM(handle, av_frame_alloc);
av_frame_get_buffer_f = D_LOOKUP_SYM(handle, av_frame_get_buffer);
vlog.info("libavutil.so loaded");
// libswscale
libswscale = load_lib("libswscale.so");
handle = libswscale.get();
sws_freeContext_f = D_LOOKUP_SYM(handle, sws_freeContext);
sws_getContext_f = D_LOOKUP_SYM(handle, sws_getContext);
sws_scale_f = D_LOOKUP_SYM(handle, sws_scale);
// libavcodec
libavcodec = load_lib("libavcodec.so");
handle = libavcodec.get();
avcodec_open2_f = D_LOOKUP_SYM(handle, avcodec_open2);
avcodec_alloc_context3_f = D_LOOKUP_SYM(handle, avcodec_alloc_context3);
avcodec_send_packet_f = D_LOOKUP_SYM(handle, avcodec_send_packet);
avcodec_receive_frame_f = D_LOOKUP_SYM(handle, avcodec_receive_frame);
av_packet_unref_f = D_LOOKUP_SYM(handle, av_packet_unref);
avcodec_flush_buffers_f = D_LOOKUP_SYM(handle, avcodec_flush_buffers);
avcodec_close_f = D_LOOKUP_SYM(handle, avcodec_close);
av_packet_alloc_f = D_LOOKUP_SYM(handle, av_packet_alloc);
av_packet_free_f = D_LOOKUP_SYM(handle, av_packet_free);
}
FFmpegFrameFeeder::~FFmpegFrameFeeder() {
avformat_close_input_f(&format_ctx);
avcodec_close_f(codec_ctx);
avcodec_free_context_f(&codec_ctx);
}
void FFmpegFrameFeeder::open(const std::string_view path) {
if (avformat_open_input_f(&format_ctx, path.data(), nullptr, nullptr) < 0)
throw std::runtime_error("Could not open video file");
// Find stream info
if (avformat_find_stream_info_f(format_ctx, nullptr) < 0)
throw std::runtime_error("Could not find stream info");
// Find video stream
for (uint32_t i = 0; i < format_ctx->nb_streams; ++i) {
if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream_idx = static_cast<int>(i);
break;
}
}
if (video_stream_idx == -1)
throw std::runtime_error("No video stream found");
// Get codec parameters and decoder
const auto *codec_parameters = format_ctx->streams[video_stream_idx]->codecpar;
const auto *codec = avcodec_find_decoder_f(codec_parameters->codec_id);
if (!codec)
throw std::runtime_error("Codec not found");
codec_ctx = avcodec_alloc_context3_f(codec);
if (!codec_ctx || avcodec_parameters_to_context_f(codec_ctx, codec_parameters) < 0)
throw std::runtime_error("Failed to set up codec context");
if (avcodec_open2_f(codec_ctx, codec, nullptr) < 0)
throw std::runtime_error("Could not open codec");
}
FFmpegFrameFeeder::play_stats_t FFmpegFrameFeeder::play(benchmarking::MockTestConnection *connection) const {
// Allocate frame and packet
const FrameGuard frame{av_frame_alloc_f()};
const PacketGuard packet{av_packet_alloc_f()};
if (!frame || !packet)
throw std::runtime_error("Could not allocate frame or packet");
// Scaling context to convert to RGB24
SwsContext *sws_ctx = sws_getContext_f(
codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
codec_ctx->width, codec_ctx->height, AV_PIX_FMT_RGB24,
SWS_BILINEAR, nullptr, nullptr, nullptr
);
if (!sws_ctx)
throw std::runtime_error("Could not create scaling context");
const std::unique_ptr<SwsContext, void(*)(SwsContext *)> sws_ctx_guard{sws_ctx, sws_freeContext_f};
const FrameGuard rgb_frame{av_frame_alloc_f()};
if (!rgb_frame)
throw std::runtime_error("Could not allocate frame");
rgb_frame->format = AV_PIX_FMT_RGB24;
rgb_frame->width = codec_ctx->width;
rgb_frame->height = codec_ctx->height;
if (av_frame_get_buffer_f(rgb_frame.get(), 0) != 0)
throw std::runtime_error("Could not allocate frame data");
play_stats_t stats{};
const auto total_frame_count = get_total_frame_count();
stats.timings.reserve(total_frame_count > 0 ? total_frame_count : 2048);
while (av_read_frame_f(format_ctx, packet.get()) == 0) {
if (packet->stream_index == video_stream_idx) {
if (avcodec_send_packet_f(codec_ctx, packet.get()) == 0) {
while (avcodec_receive_frame_f(codec_ctx, frame.get()) == 0) {
// Convert to RGB
sws_scale_f(sws_ctx_guard.get(), frame->data, frame->linesize, 0,
frame->height,
rgb_frame->data, rgb_frame->linesize);
connection->framebufferUpdateStart();
connection->setNewFrame(rgb_frame.get());
using namespace std::chrono;
auto now = high_resolution_clock::now();
connection->framebufferUpdateEnd();
const auto duration = duration_cast<milliseconds>(high_resolution_clock::now() - now).count();
//vlog.info("Frame took %lu ms", duration);
stats.total += duration;
stats.timings.push_back(duration);
}
}
}
av_packet_unref_f(packet.get());
}
if (av_seek_frame_f(format_ctx, video_stream_idx, 0, AVSEEK_FLAG_BACKWARD) < 0)
throw std::runtime_error("Could not seek to start of video");
avcodec_flush_buffers_f(codec_ctx);
return stats;
}

168
common/rfb/ffmpeg.h Normal file
View file

@ -0,0 +1,168 @@
/* Copyright (C) 2025 Kasm Technologies Corp
*
* 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.
*/
#pragma once
#include <dlfcn.h>
#include <memory>
#include <string>
#include "LogWriter.h"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
}
#include "benchmark.h"
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
#define CONCAT_STR(a, b) a b
#define D_LOOKUP_SYM(handle, name) \
[](auto handle, auto *sym_name) -> auto { \
auto *sym = reinterpret_cast<name##_func>(dlsym(handle, sym_name)); \
if (!sym) \
throw std::runtime_error("Failed to load symbol "s + sym_name); \
return sym; \
}(handle, STR(name))
#define DEFINE_GUARD(name, type, deleter) \
using name##Guard = std::unique_ptr<type, decltype([](auto *ptr){deleter##_f(&ptr);})>;
//using SwsContextGuard = std::unique_ptr<SwsContext, SwsContextDeleter>;
class FFmpegFrameFeeder final {
// libavformat
using avformat_close_input_func = void(*)(AVFormatContext **);
using avformat_open_input_func = int(*)(AVFormatContext **ps, const char *url, const AVInputFormat *fmt,
AVDictionary **options);
using avformat_find_stream_info_func = int (*)(AVFormatContext *ic, AVDictionary **options);
using av_read_frame_func = int (*)(AVFormatContext *s, AVPacket *pkt);
using av_seek_frame_func = int (*)(AVFormatContext *s, int stream_index, int64_t timestamp, int flags);
// libavutil
using av_frame_free_func = void (*)(AVFrame **);
using av_frame_alloc_func = AVFrame *(*)();
using av_frame_get_buffer_func = int (*)(AVFrame *frame, int align);
// libswscale
using sws_freeContext_func = void (*)(SwsContext *);
using sws_getContext_func = SwsContext * (*)(int srcW, int srcH, AVPixelFormat srcFormat, int dstW, int dstH,
AVPixelFormat dstFormat, int flags, SwsFilter *srcFilter,
SwsFilter *dstFilter, const double *param);
using sws_scale_func = int(*)(SwsContext *c, const uint8_t *const srcSlice[], const int srcStride[], int srcSliceY,
int srcSliceH, uint8_t *const dst[], const int dstStride[]);
// libavcodec
using avcodec_free_context_func = void (*)(AVCodecContext **);
using av_packet_free_func = void (*)(AVPacket **);
using avcodec_find_decoder_func = const AVCodec * (*)(AVCodecID id);
using avcodec_alloc_context3_func = AVCodecContext* (*)(const AVCodec *codec);
using avcodec_parameters_to_context_func = int (*)(AVCodecContext *codec, const AVCodecParameters *par);
using avcodec_open2_func = int (*)(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
using av_packet_alloc_func = AVPacket *(*)();
using avcodec_send_packet_func = int(*)(AVCodecContext *avctx, const AVPacket *avpkt);
using avcodec_receive_frame_func = int(*)(AVCodecContext *avctx, AVFrame *frame);
using av_packet_unref_func = void (*)(AVPacket *pkt);
using avcodec_flush_buffers_func = void (*)(AVCodecContext *avctx);
using avcodec_close_func = int (*)(AVCodecContext *avctx);
struct DlHandler {
void operator()(void *handle) const {
dlclose(handle);
}
};
using DlHandlerGuard = std::unique_ptr<void, DlHandler>;
// libavformat
avformat_close_input_func avformat_close_input_f{};
avformat_open_input_func avformat_open_input_f{};
avformat_find_stream_info_func avformat_find_stream_info_f{};
av_read_frame_func av_read_frame_f{};
av_seek_frame_func av_seek_frame_f{};
// libavutil
static inline av_frame_free_func av_frame_free_f{};
av_frame_alloc_func av_frame_alloc_f{};
av_frame_get_buffer_func av_frame_get_buffer_f{};
// libswscale
sws_freeContext_func sws_freeContext_f{};
sws_getContext_func sws_getContext_f{};
sws_scale_func sws_scale_f{};
// libavcodec
avcodec_free_context_func avcodec_free_context_f{};
static inline av_packet_free_func av_packet_free_f{};
avcodec_find_decoder_func avcodec_find_decoder_f{};
avcodec_alloc_context3_func avcodec_alloc_context3_f{};
avcodec_parameters_to_context_func avcodec_parameters_to_context_f{};
avcodec_open2_func avcodec_open2_f{};
av_packet_alloc_func av_packet_alloc_f{};
avcodec_send_packet_func avcodec_send_packet_f{};
avcodec_receive_frame_func avcodec_receive_frame_f{};
av_packet_unref_func av_packet_unref_f{};
avcodec_flush_buffers_func avcodec_flush_buffers_f{};
avcodec_close_func avcodec_close_f{};
rfb::LogWriter vlog{"FFmpeg"};
DEFINE_GUARD(Frame, AVFrame, av_frame_free)
DEFINE_GUARD(Packet, AVPacket, av_packet_free)
AVFormatContext *format_ctx{};
AVCodecContext *codec_ctx{};
int video_stream_idx{-1};
DlHandlerGuard libavformat{};
DlHandlerGuard libavutil{};
DlHandlerGuard libswscale{};
DlHandlerGuard libavcodec{};
public:
FFmpegFrameFeeder();
~FFmpegFrameFeeder();
void open(std::string_view path);
[[nodiscard]] int64_t get_total_frame_count() const {
return format_ctx->streams[video_stream_idx]->nb_frames;
}
struct frame_dimensions_t {
int width{};
int height{};
};
[[nodiscard]] frame_dimensions_t get_frame_dimensions() const {
return {codec_ctx->width, codec_ctx->height};
}
struct play_stats_t {
uint64_t frames{};
uint64_t total{};
std::vector<uint64_t> timings;
};
play_stats_t play(benchmarking::MockTestConnection *connection) const;
};

View file

@ -21,8 +21,8 @@
#include <config.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#include <cstdarg>
#include <cstdio>
#include <sys/time.h>
#include <rfb/util.h>
@ -572,6 +572,11 @@ namespace rfb {
return msBetween(then, &now);
}
uint64_t elapsedMs(std::chrono::high_resolution_clock::time_point start)
{
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start).count();
}
bool isBefore(const struct timeval *first,
const struct timeval *second)
{

View file

@ -28,8 +28,9 @@
#include <config.h>
#endif
#include <limits.h>
#include <string.h>
#include <climits>
#include <cstring>
#include <chrono>
struct timeval;
@ -127,6 +128,15 @@ namespace rfb {
// Returns time elapsed since given moment in milliseconds.
unsigned msSince(const struct timeval *then);
/**
* Calculates the number of milliseconds elapsed since a given starting point.
*
* @param start The starting time point of type `std::chrono::high_resolution_clock::time_point`.
*
* @return The elapsed time in milliseconds as an unsigned 64-bit integer.
*/
uint64_t elapsedMs(std::chrono::high_resolution_clock::time_point start);
// Returns true if first happened before seconds
bool isBefore(const struct timeval *first,
const struct timeval *second);

@ -1 +1 @@
Subproject commit 5c46b2e13ab1dd7232b28f017fd7e49ca740f5a4
Subproject commit 0d8a3c5f07defffe340dd67a1485be5214bec4ee

View file

@ -23,6 +23,18 @@ def write_config(config_text):
f.write(config_text)
def clean_locks():
tmp = '/tmp'
temporary_lock_file = os.path.join(tmp, '.X1-lock')
if (os.path.exists(temporary_lock_file)):
os.remove(temporary_lock_file)
temporary_lock_file = os.path.join(tmp, '.X11-unix')
temporary_lock_file = os.path.join(temporary_lock_file, 'X1')
if (os.path.exists(temporary_lock_file)):
os.remove(temporary_lock_file)
def clean_env():
clean_kasm_users()
@ -30,6 +42,7 @@ def clean_env():
vnc_dir = os.path.join(home_dir, ".vnc")
Path(vnc_dir).rmtree(ignore_errors=True)
clean_locks()
def clean_kasm_users():
home_dir = os.environ['HOME']
@ -91,3 +104,4 @@ def kill_xvnc():
run_cmd('vncserver -kill :1', print_stderr=False)
running_xvnc = False
clean_locks()

View file

@ -0,0 +1,15 @@
from mamba import description, context, it, fit, before, after
from expects import expect, equal, contain, match
from helper.spec_helper import run_cmd, clean_env, kill_xvnc
with description("Benchmarking"):
with before.each:
clean_env()
with after.each:
kill_xvnc()
with it("runs benchmarks"):
run_cmd("wget --no-check-certificate https://kasmweb-build-artifacts.s3.us-east-1.amazonaws.com/kasmvnc/static/127072-737747495_small.mp4 -O /tmp/video.mp4")
completed_process = run_cmd("Xvnc -interface 0.0.0.0 :1 -Benchmark /tmp/video.mp4 -VideoArea 100")
command = '''sed -i "s/KasmVNC/$(grep -E '^ID=' /etc/os-release | cut -d= -f2 | tr -d '"') $(grep -E '^VERSION_CODENAME=' /etc/os-release | cut -d= -f2 | tr -d '"')/g" Benchmark.xml'''
run_cmd(command)
expect(completed_process.returncode).to(equal(0))

View file

@ -0,0 +1,12 @@
from mamba import description, context, it, fit, before, after
from expects import expect, equal, contain, match
from helper.spec_helper import run_cmd, clean_env, kill_xvnc, clean_locks
with description("Benchmarking"):
with before.each:
clean_env()
with after.each:
kill_xvnc()
with it("runs benchmarks"):
completed_process = run_cmd("Xvnc -interface 0.0.0.0 :1 -selfBench")
expect(completed_process.returncode).to(equal(0))

1
third_party/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1 @@
add_subdirectory(tinyxml2)

5
third_party/tinyxml2/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,5 @@
add_library(tinyxml2_objs OBJECT tinyxml2.cpp)
if (NOT WIN32)
set_target_properties(tinyxml2_objs PROPERTIES POSITION_INDEPENDENT_CODE ON)
endif ()
target_include_directories(tinyxml2_objs PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

18
third_party/tinyxml2/LICENSE.txt vendored Normal file
View file

@ -0,0 +1,18 @@
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.

3019
third_party/tinyxml2/tinyxml2.cpp vendored Normal file

File diff suppressed because it is too large Load diff

2383
third_party/tinyxml2/tinyxml2.h vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -37,7 +37,7 @@ user_session:
keyboard:
remap_keys:
# - 0x22->0x40
# - 0x22->0x40
ignore_numlock: false
raw_keyboard: false
@ -129,6 +129,7 @@ encoding:
logging:
level: off
scaling_algorithm: progressive_bilinear
webp_encoding_time: 30
compare_framebuffer: auto
zrle_zlib_level: auto

View file

@ -2068,6 +2068,15 @@ sub DefineConfigToCLIConversion {
$value;
}
}),
KasmVNC::CliOption->new({
name => 'WebpEncodingTime',
configKeys => [
KasmVNC::ConfigKey->new({
name => "encoding.video_encoding_mode.webp_encoding_time",
type => KasmVNC::ConfigKey::INT
})
]
}),
KasmVNC::CliOption->new({
name => 'CompareFB',
configKeys => [

View file

@ -574,6 +574,16 @@ When \fBNoClipboard\fP parameter is set, allowing override of \fBSendCutText\fP
and \fBAcceptCutText\fP has no effect.
Default is \fBdesktop,AcceptPointerEvents,SendCutText,AcceptCutText,SendPrimary,SetPrimary\fP.
.
TP
.B -Benchmark <video_file>
Run the built-in benchmarking routines on the specified video file and exit.
When this option is used, benchmarking results can be saved to a file specified by the \fB-BenchmarkResults\fP option; otherwise, the results are saved to \fBBenchmark.xml\fP by default.
.
.TP
.B -BenchmarkResults <results_file.xml>
Save the benchmarking results to the specified file.
Use this option together with \fB-Benchmark\fP to output the report to a custom file.
.SH USAGE WITH INETD
By configuring the \fBinetd\fP(1) service appropriately, Xvnc can be launched

View file

@ -184,7 +184,7 @@ static void parseClipTypes()
vlog.debug("Adding DLP binary mime type %s", m.mime);
}
vlog.debug("Total %u binary mime types", dlp_mimetypes.size());
vlog.debug("Total %lu binary mime types", dlp_mimetypes.size());
free(origstr);
}