diff --git a/builder/build.sh b/builder/build.sh index c7a6168..095a7a1 100755 --- a/builder/build.sh +++ b/builder/build.sh @@ -96,7 +96,9 @@ cd /src detect_quilt if [ -n "$QUILT_PRESENT" ]; then quilt push -a + echo 'Patches applied!' fi + make servertarball cp kasmvnc*.tar.gz /build/kasmvnc.${KASMVNC_BUILD_OS}_${KASMVNC_BUILD_OS_CODENAME}.tar.gz diff --git a/builder/dockerfile.centos_core.build b/builder/dockerfile.centos_core.build index a94ee76..476c1e4 100644 --- a/builder/dockerfile.centos_core.build +++ b/builder/dockerfile.centos_core.build @@ -3,7 +3,7 @@ FROM centos:centos7 ENV KASMVNC_BUILD_OS centos ENV KASMVNC_BUILD_OS_CODENAME core -RUN yum update -y ca-certificates +RUN yum install -y ca-certificates RUN yum install -y build-dep xorg-server libxfont-dev sudo RUN yum install -y gcc cmake git libjpeg-dev libgnutls28-dev vim wget tightvncserver RUN yum install -y libjpeg-dev libpng-dev libtiff-dev libgif-dev libavcodec-dev openssl-devel @@ -11,8 +11,10 @@ RUN yum install -y make RUN yum group install -y "Development Tools" RUN yum install -y xorg-x11-server-devel zlib-devel libjpeg-turbo-devel RUN yum install -y libxkbfile-devel libXfont2-devel xorg-x11-font-utils \ - xorg-x11-xtrans-devel xorg-x11-xkb-utils-devel + xorg-x11-xtrans-devel xorg-x11-xkb-utils-devel libXrandr-devel pam-devel \ + gnutls-devel libX11-devel libXtst-devel libXcursor-devel RUN yum install -y mesa-dri-drivers +RUN yum install -y ca-certificates # Additions for webp RUN cd /tmp && wget https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.0.2.tar.gz diff --git a/builder/dockerfile.debian_bullseye.build b/builder/dockerfile.debian_bullseye.build index 707facd..b1a1908 100644 --- a/builder/dockerfile.debian_bullseye.build +++ b/builder/dockerfile.debian_bullseye.build @@ -13,7 +13,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 libjpeg-dev libgnutls28-dev vim wget tightvncserver -RUN apt-get update && apt-get -y install libjpeg-dev libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev +RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev libxcursor-dev # Additions for webp RUN cd /tmp && wget https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.0.2.tar.gz diff --git a/builder/dockerfile.debian_buster.build b/builder/dockerfile.debian_buster.build index 569cc2f..9849856 100644 --- a/builder/dockerfile.debian_buster.build +++ b/builder/dockerfile.debian_buster.build @@ -13,7 +13,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 libjpeg-dev libgnutls28-dev vim wget tightvncserver -RUN apt-get update && apt-get -y install libjpeg-dev libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev +RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev libxcursor-dev # Additions for webp RUN cd /tmp && wget https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.0.2.tar.gz diff --git a/builder/dockerfile.fedora_thirtythree.build b/builder/dockerfile.fedora_thirtythree.build index 537ab6b..b9f5720 100644 --- a/builder/dockerfile.fedora_thirtythree.build +++ b/builder/dockerfile.fedora_thirtythree.build @@ -18,7 +18,8 @@ RUN dnf install -y make RUN dnf group install -y "Development Tools" RUN dnf install -y xorg-x11-server-devel zlib-devel libjpeg-turbo-devel RUN dnf install -y libxkbfile-devel libXfont2-devel xorg-x11-font-utils \ - xorg-x11-xtrans-devel xorg-x11-xkb-utils-devel + xorg-x11-xtrans-devel xorg-x11-xkb-utils-devel libXrandr-devel libXtst-devel \ + libXcursor-devel RUN dnf install -y mesa-dri-drivers RUN dnf install -y bzip2 redhat-lsb-core diff --git a/builder/dockerfile.kali_kali-rolling.build b/builder/dockerfile.kali_kali-rolling.build index 05e5ebb..006edee 100644 --- a/builder/dockerfile.kali_kali-rolling.build +++ b/builder/dockerfile.kali_kali-rolling.build @@ -13,7 +13,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 libjpeg-dev libgnutls28-dev vim wget tightvncserver -RUN apt-get update && apt-get -y install libjpeg-dev libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev +RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev libxcursor-dev # Additions for webp RUN cd /tmp && wget https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.0.2.tar.gz diff --git a/builder/dockerfile.oracle_8.build b/builder/dockerfile.oracle_8.build index 8baf9e4..60fc801 100644 --- a/builder/dockerfile.oracle_8.build +++ b/builder/dockerfile.oracle_8.build @@ -41,7 +41,10 @@ RUN dnf install -y \ libxkbfile-devel \ xorg-x11-server-devel \ xorg-x11-xkb-utils-devel \ - xorg-x11-xtrans-devel + xorg-x11-xtrans-devel \ + libXrandr-devel \ + libXtst-devel \ + libXcursor-devel # Additions for webp RUN cd /tmp && wget https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.0.2.tar.gz diff --git a/builder/dockerfile.ubuntu_bionic+libjpeg-turbo_latest.build b/builder/dockerfile.ubuntu_bionic+libjpeg-turbo_latest.build index 12482e6..2615bff 100644 --- a/builder/dockerfile.ubuntu_bionic+libjpeg-turbo_latest.build +++ b/builder/dockerfile.ubuntu_bionic+libjpeg-turbo_latest.build @@ -11,7 +11,7 @@ RUN apt-get update && \ 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 -RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev +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 install -y cmake nasm gcc RUN git clone https://github.com/libjpeg-turbo/libjpeg-turbo.git diff --git a/builder/dockerfile.ubuntu_bionic.build b/builder/dockerfile.ubuntu_bionic.build index 5584fe2..f54123b 100644 --- a/builder/dockerfile.ubuntu_bionic.build +++ b/builder/dockerfile.ubuntu_bionic.build @@ -11,7 +11,7 @@ RUN apt-get update && \ RUN apt-get update && apt-get -y build-dep xorg-server libxfont-dev RUN apt-get update && apt-get -y install cmake git libjpeg-dev libgnutls28-dev vim wget tightvncserver -RUN apt-get update && apt-get -y install libjpeg-dev libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev +RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev libxcursor-dev # Additions for webp RUN cd /tmp && wget https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.0.2.tar.gz diff --git a/builder/dockerfile.ubuntu_focal.build b/builder/dockerfile.ubuntu_focal.build index fb3f800..3688f5d 100644 --- a/builder/dockerfile.ubuntu_focal.build +++ b/builder/dockerfile.ubuntu_focal.build @@ -13,7 +13,7 @@ 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 libjpeg-dev libgnutls28-dev vim wget tightvncserver -RUN apt-get update && apt-get -y install libjpeg-dev libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev +RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev libxcursor-dev # Additions for webp RUN cd /tmp && wget https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.0.2.tar.gz diff --git a/centos/kasmvncserver.spec b/centos/kasmvncserver.spec index a43face..6d49fdf 100644 --- a/centos/kasmvncserver.spec +++ b/centos/kasmvncserver.spec @@ -53,6 +53,7 @@ cp $SRC_BIN/Xvnc $DESTDIR/usr/bin; cp $SRC_BIN/vncserver $DESTDIR/usr/bin; cp $SRC_BIN/vncconfig $DESTDIR/usr/bin; cp $SRC_BIN/kasmvncpasswd $DESTDIR/usr/bin; +cp $SRC_BIN/kasmxproxy $DESTDIR/usr/bin; cd $DESTDIR/usr/bin && ln -s kasmvncpasswd vncpasswd; cp -r $SRC/share/doc/kasmvnc*/* $DESTDIR/usr/share/doc/kasmvncserver/ rsync -r --exclude '.git*' --exclude po2js --exclude xgettext-html \ @@ -62,8 +63,10 @@ cp $SRC/man/man1/Xvnc.1 $DESTDIR/usr/share/man/man1/; cp $SRC/share/man/man1/vncserver.1 $DST_MAN; cp $SRC/share/man/man1/vncconfig.1 $DST_MAN; cp $SRC/share/man/man1/vncpasswd.1 $DST_MAN; +cp $SRC/share/man/man1/kasmxproxy.1 $DST_MAN; cd $DST_MAN && ln -s vncpasswd.1 kasmvncpasswd.1; + %files /usr/bin/* /usr/share/man/man1/* diff --git a/debian/Makefile.to_fakebuild_tar_package b/debian/Makefile.to_fakebuild_tar_package index 4a0938d..4cf3a73 100644 --- a/debian/Makefile.to_fakebuild_tar_package +++ b/debian/Makefile.to_fakebuild_tar_package @@ -16,12 +16,14 @@ install: unpack_tarball cp $(SRC_BIN)/vncserver $(DESTDIR)/usr/bin/kasmvncserver cp $(SRC_BIN)/vncconfig $(DESTDIR)/usr/bin/kasmvncconfig cp $(SRC_BIN)/kasmvncpasswd $(DESTDIR)/usr/bin/ + cp $(SRC_BIN)/kasmxproxy $(DESTDIR)/usr/bin/ cp -r $(SRC)/share/doc/kasmvnc*/* $(DESTDIR)/usr/share/doc/kasmvncserver/ rsync -r --exclude '.git*' --exclude po2js --exclude xgettext-html \ --exclude www/utils/ --exclude .eslintrc \ $(SRC)/share/kasmvnc $(DESTDIR)/usr/share cp $(SRC)/man/man1/Xvnc.1 $(DESTDIR)/usr/share/man/man1/Xkasmvnc.1 cp $(SRC)/share/man/man1/vncserver.1 $(DST_MAN)/kasmvncserver.1 + cp $(SRC)/share/man/man1/kasmxproxy.1 $(DST_MAN)/kasmxproxy.1 cp $(SRC)/share/man/man1/vncpasswd.1 $(DST_MAN)/kasmvncpasswd.1 cp $(SRC)/share/man/man1/vncconfig.1 $(DST_MAN)/kasmvncconfig.1 diff --git a/debian/control b/debian/control index 7ead94c..64d63b8 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: x11 Priority: optional Maintainer: Kasm Technologies LLC Build-Depends: debhelper (>= 11), rsync, libjpeg-dev, libjpeg-dev, libpng-dev, - libtiff-dev, libgif-dev, libavcodec-dev, libssl-dev, libgl1, libxfont2, libsm6, libunwind8 + libtiff-dev, libgif-dev, libavcodec-dev, libssl-dev, libgl1, libxfont2, libsm6, libxext-dev, libxrandr-dev, libxtst-dev, libxcursor-dev, libunwind8 Standards-Version: 4.1.3 Homepage: https://github.com/kasmtech/KasmVNC #Vcs-Browser: https://salsa.debian.org/debian/kasmvnc diff --git a/debian/postinst b/debian/postinst index 8a1f407..ffc58a7 100644 --- a/debian/postinst +++ b/debian/postinst @@ -21,7 +21,7 @@ case "$1" in configure) bindir=/usr/bin mandir=/usr/share/man - commands="kasmvncserver kasmvncpasswd kasmvncconfig Xkasmvnc" + commands="kasmvncserver kasmvncpasswd kasmvncconfig Xkasmvnc kasmxproxy" for kasm_command in $commands; do generic_command=`echo "$kasm_command" | sed -e 's/kasm//'`; diff --git a/debian/prerm b/debian/prerm index 8c0802f..d02176a 100644 --- a/debian/prerm +++ b/debian/prerm @@ -21,7 +21,7 @@ case "$1" in remove) bindir=/usr/bin mandir=/usr/share/man - commands="kasmvncserver kasmvncpasswd kasmvncconfig Xkasmvnc" + commands="kasmvncserver kasmvncpasswd kasmvncconfig Xkasmvnc kasmxproxy" for kasm_command in $commands; do generic_command=`echo "$kasm_command" | sed -e 's/kasm//'`; diff --git a/opensuse/kasmvncserver.spec b/opensuse/kasmvncserver.spec index 88edcc6..bc8fa00 100644 --- a/opensuse/kasmvncserver.spec +++ b/opensuse/kasmvncserver.spec @@ -53,6 +53,7 @@ cp $SRC_BIN/Xvnc $DESTDIR/usr/bin; cp $SRC_BIN/vncserver $DESTDIR/usr/bin; cp $SRC_BIN/vncconfig $DESTDIR/usr/bin; cp $SRC_BIN/kasmvncpasswd $DESTDIR/usr/bin; +cp $SRC_BIN/kasmxproxy $DESTDIR/usr/bin; cd $DESTDIR/usr/bin && ln -s kasmvncpasswd vncpasswd; cp -r $SRC/share/doc/kasmvnc*/* $DESTDIR/usr/share/doc/kasmvncserver/ rsync -r --exclude '.git*' --exclude po2js --exclude xgettext-html \ @@ -62,8 +63,10 @@ cp $SRC/man/man1/Xvnc.1 $DESTDIR/usr/share/man/man1/; cp $SRC/share/man/man1/vncserver.1 $DST_MAN; cp $SRC/share/man/man1/vncconfig.1 $DST_MAN; cp $SRC/share/man/man1/vncpasswd.1 $DST_MAN; +cp $SRC/share/man/man1/kasmxproxy.1 $DST_MAN; cd $DST_MAN && ln -s vncpasswd.1 kasmvncpasswd.1; + %files /usr/bin/* /usr/share/man/man1/* diff --git a/oracle/kasmvncserver.spec b/oracle/kasmvncserver.spec index ae67ac8..3ff4f37 100644 --- a/oracle/kasmvncserver.spec +++ b/oracle/kasmvncserver.spec @@ -53,6 +53,7 @@ cp $SRC_BIN/Xvnc $DESTDIR/usr/bin; cp $SRC_BIN/vncserver $DESTDIR/usr/bin; cp $SRC_BIN/vncconfig $DESTDIR/usr/bin; cp $SRC_BIN/kasmvncpasswd $DESTDIR/usr/bin; +cp $SRC_BIN/kasmxproxy $DESTDIR/usr/bin; cd $DESTDIR/usr/bin && ln -s kasmvncpasswd vncpasswd; cp -r $SRC/share/doc/kasmvnc*/* $DESTDIR/usr/share/doc/kasmvncserver/ rsync -r --exclude '.git*' --exclude po2js --exclude xgettext-html \ @@ -62,8 +63,10 @@ cp $SRC/man/man1/Xvnc.1 $DESTDIR/usr/share/man/man1/; cp $SRC/share/man/man1/vncserver.1 $DST_MAN; cp $SRC/share/man/man1/vncconfig.1 $DST_MAN; cp $SRC/share/man/man1/vncpasswd.1 $DST_MAN; +cp $SRC/share/man/man1/kasmxproxy.1 $DST_MAN; cd $DST_MAN && ln -s vncpasswd.1 kasmvncpasswd.1; + %files /usr/bin/* /usr/share/man/man1/* diff --git a/release/maketarball.in b/release/maketarball.in index 02888f7..5b382a9 100644 --- a/release/maketarball.in +++ b/release/maketarball.in @@ -45,6 +45,7 @@ mkdir -p $OUTDIR/man/man1 make DESTDIR=$TMPDIR/inst install if [ $SERVER = 1 ]; then + install -m 755 ./unix/kasmxproxy/kasmxproxy $OUTDIR/bin/ install -m 755 ./xorg.build/bin/Xvnc $OUTDIR/bin/ install -m 644 ./xorg.build/man/man1/Xvnc.1 $OUTDIR/man/man1/Xvnc.1 install -m 644 ./xorg.build/man/man1/Xserver.1 $OUTDIR/man/man1/Xserver.1 diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 4c1009c..d72eaf8 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -3,6 +3,7 @@ add_subdirectory(common) add_subdirectory(vncconfig) add_subdirectory(vncpasswd) add_subdirectory(kasmvncpasswd) +add_subdirectory(kasmxproxy) install(PROGRAMS vncserver DESTINATION ${BIN_DIR}) install(FILES vncserver.man DESTINATION ${MAN_DIR}/man1 RENAME vncserver.1) diff --git a/unix/kasmxproxy/.gitignore b/unix/kasmxproxy/.gitignore new file mode 100644 index 0000000..78f1a3d --- /dev/null +++ b/unix/kasmxproxy/.gitignore @@ -0,0 +1 @@ +kasmxproxy diff --git a/unix/kasmxproxy/CMakeLists.txt b/unix/kasmxproxy/CMakeLists.txt new file mode 100644 index 0000000..bafb8e1 --- /dev/null +++ b/unix/kasmxproxy/CMakeLists.txt @@ -0,0 +1,11 @@ +include_directories(${X11_INCLUDE_DIR}) + +add_executable(kasmxproxy + xxhash.c + kasmxproxy.c) + +target_link_libraries(kasmxproxy ${X11_LIBRARIES} ${X11_XTest_LIB} ${X11_Xrandr_LIB} + ${X11_Xcursor_LIB} ${X11_Xfixes_LIB}) + +install(TARGETS kasmxproxy DESTINATION ${BIN_DIR}) +install(FILES kasmxproxy.man DESTINATION ${MAN_DIR}/man1 RENAME kasmxproxy.1) diff --git a/unix/kasmxproxy/kasmxproxy.c b/unix/kasmxproxy/kasmxproxy.c new file mode 100644 index 0000000..f275956 --- /dev/null +++ b/unix/kasmxproxy/kasmxproxy.c @@ -0,0 +1,530 @@ +/* Copyright (C) 2021 Kasm. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xxhash.h" + +#define min(a, b) ((a) < (b) ? (a) : (b)) + +static void help(const char name[]) { + printf("Usage: %s [opts]\n\n" + "-a --app-display disp App display, default :0\n" + "-v --vnc-display disp VNC display, default :1\n" + "\n" + "-f --fps fps FPS, default 30\n" + "-r --resize Enable resize, default disabled.\n" + " Do not enable this if there's a physical screen\n" + " connected to the app display.\n", + name); + exit(1); +} + +#define CUT_MAX (16 * 1024) +static uint8_t cutbuf[CUT_MAX]; + +static void supplyselection(Display *disp, const XEvent * const ev, const Atom xa_targets) { + XSelectionEvent sev; + + sev.type = SelectionNotify; + sev.display = disp; + sev.requestor = ev->xselectionrequest.requestor; + sev.selection = ev->xselectionrequest.selection; + sev.target = ev->xselectionrequest.target; + sev.time = ev->xselectionrequest.time; + /*printf("someone wants our clipboard, sel %lu, tgt %lu, prop %lu\n", + sev.selection, sev.target, + ev.xselectionrequest.property);*/ + + if (ev->xselectionrequest.property == None) + sev.property = sev.target; + else + sev.property = ev->xselectionrequest.property; + + const uint32_t len = strlen((char *) cutbuf); + + if (xa_targets != None && + sev.target == xa_targets) { + // Which formats can we do + Atom tgt[2] = { + xa_targets, + XA_STRING + }; + + XChangeProperty(disp, sev.requestor, + ev->xselectionrequest.property, + XA_ATOM, 32, + PropModeReplace, + (unsigned char *) tgt, + 2); + //puts("sent targets"); + } else { + // Data + XChangeProperty(disp, sev.requestor, + ev->xselectionrequest.property, + sev.target, 8, + PropModeReplace, + cutbuf, len); + //printf("sent data, of len %u\n", len); + } + + // Send the notify event + XSendEvent(disp, sev.requestor, False, 0, + (XEvent *) &sev); +} + +int main(int argc, char **argv) { + + const char *appstr = ":0"; + const char *vncstr = ":1"; + uint8_t resize = 0; + uint8_t fps = 30; + + const struct option longargs[] = { + {"app-display", 1, NULL, 'a'}, + {"vnc-display", 1, NULL, 'v'}, + {"resize", 0, NULL, 'r'}, + {"fps", 1, NULL, 'f'}, + + {NULL, 0, NULL, 0}, + }; + + while (1) { + int c = getopt_long(argc, argv, "a:v:rf:", longargs, NULL); + if (c == -1) + break; + switch (c) { + case 'a': + appstr = strdup(optarg); + break; + case 'v': + vncstr = strdup(optarg); + break; + case 'r': + resize = 1; + break; + case 'f': + fps = atoi(optarg); + if (fps < 1 || fps > 120) { + printf("Invalid fps\n"); + return 1; + } + break; + default: + help(argv[0]); + break; + } + } + + Display *appdisp = XOpenDisplay(appstr); + if (!appdisp) { + printf("Cannot open display %s\n", appstr); + return 1; + } + if (!XShmQueryExtension(appdisp)) { + printf("Display %s lacks SHM extension\n", appstr); + return 1; + } + + Display *vncdisp = XOpenDisplay(vncstr); + if (!vncdisp) { + printf("Cannot open display %s\n", vncstr); + return 1; + } + if (!XShmQueryExtension(vncdisp)) { + printf("Display %s lacks SHM extension\n", vncstr); + return 1; + } + + const int appscreen = DefaultScreen(appdisp); + const int vncscreen = DefaultScreen(vncdisp); + Visual *appvis = DefaultVisual(appdisp, appscreen); + //Visual *vncvis = DefaultVisual(vncdisp, vncscreen); + const int appdepth = DefaultDepth(appdisp, appscreen); + const int vncdepth = DefaultDepth(vncdisp, vncscreen); + if (appdepth != vncdepth) { + printf("Depths don't match, app %u vnc %u\n", appdepth, vncdepth); + return 1; + } + + Window approot = DefaultRootWindow(appdisp); + Window vncroot = DefaultRootWindow(vncdisp); + XWindowAttributes appattr, vncattr; + + XGCValues gcval; + gcval.plane_mask = AllPlanes; + gcval.function = GXcopy; + GC gc = XCreateGC(vncdisp, vncroot, GCFunction | GCPlaneMask, &gcval); + + XImage *img = NULL; + XShmSegmentInfo shminfo; + unsigned imgw = 0, imgh = 0; + + if (XGrabPointer(vncdisp, vncroot, False, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, None, None, + CurrentTime) != Success) + return 1; + if (XGrabKeyboard(vncdisp, vncroot, False, GrabModeAsync, GrabModeAsync, + CurrentTime) != Success) + return 1; + + int xfixesbase, xfixeserrbase; + XFixesQueryExtension(appdisp, &xfixesbase, &xfixeserrbase); + XFixesSelectSelectionInput(appdisp, approot, XA_PRIMARY, + XFixesSetSelectionOwnerNotifyMask); + + int xfixesbasevnc, xfixeserrbasevnc; + XFixesQueryExtension(vncdisp, &xfixesbasevnc, &xfixeserrbasevnc); + XFixesSelectSelectionInput(vncdisp, vncroot, XA_PRIMARY, + XFixesSetSelectionOwnerNotifyMask); + + Atom xa_targets_vnc = XInternAtom(vncdisp, "TARGETS", False); + Atom xa_targets_app = XInternAtom(appdisp, "TARGETS", False); + Window selwin = XCreateSimpleWindow(appdisp, approot, 3, 2, 1, 1, 0, 0, 0); + Window vncselwin = XCreateSimpleWindow(vncdisp, vncroot, 3, 2, 1, 1, 0, 0, 0); + + XFixesCursorImage *cursor = NULL; + uint64_t cursorhash = 0; + Cursor xcursor = None; + + const unsigned sleeptime = 1000 * 1000 / fps; + + while (1) { + if (!XGetWindowAttributes(appdisp, approot, &appattr)) + break; + if (!XGetWindowAttributes(vncdisp, vncroot, &vncattr)) + break; + if (resize && (appattr.width != vncattr.width || + appattr.height != vncattr.height)) { + // resize app display to VNC display size + XRRScreenConfiguration *config = XRRGetScreenInfo(appdisp, approot); + + int nsizes, i, match = -1; + XRRScreenSize *sizes = XRRConfigSizes(config, &nsizes); + //printf("%u sizes\n", nsizes); + for (i = 0; i < nsizes; i++) { + if (sizes[i].width == vncattr.width && + sizes[i].height == vncattr.height) { + //printf("match %u\n", i); + match = i; + break; + } + } + + if (match >= 0) { + XRRSetScreenConfig(appdisp, config, approot, match, + RR_Rotate_0, CurrentTime); + } else { + /*XRRSetScreenSize(appdisp, approot, + vncattr.width, vncattr.height, + sizes[0].mwidth, sizes[0].mheight);*/ + XRRScreenResources *res = XRRGetScreenResources(appdisp, approot); + //printf("%u outputs, %u crtcs\n", res->noutput, res->ncrtc); + // Nvidia crap uses a *different* list for 1.0 and 1.2! + unsigned oldmode = 0xffff; + //printf("1.2 modes %u\n", res->nmode); + for (i = 0; i < res->nmode; i++) { + if (res->modes[i].width == vncattr.width && + res->modes[i].height == vncattr.height) { + oldmode = i; + //printf("old mode %u matched\n", i); + break; + } + } + + unsigned tgt = 0; + if (res->noutput > 1) { + for (i = 0; i < res->noutput; i++) { + XRROutputInfo *info = XRRGetOutputInfo(appdisp, res, res->outputs[i]); + if (info->connection == RR_Connected) + tgt = i; + //printf("%u %s %u\n", i, info->name, info->connection); + XRRFreeOutputInfo(info); + } + } + + if (oldmode < 0xffff) { + Status s; + // nvidia needs this weird dance + s = XRRSetCrtcConfig(appdisp, res, res->crtcs[tgt], + CurrentTime, + 0, 0, + None, RR_Rotate_0, + NULL, 0); + //printf("disable %u\n", s); + XRRSetScreenSize(appdisp, approot, + vncattr.width, vncattr.height, + sizes[0].mwidth, sizes[0].mheight); + s = XRRSetCrtcConfig(appdisp, res, res->crtcs[tgt], + CurrentTime, + 0, 0, + res->modes[oldmode].id, RR_Rotate_0, + &res->outputs[tgt], 1); + //printf("set %u\n", s); + } else { + char name[32]; + sprintf(name, "%ux%u_60", vncattr.width, vncattr.height); + printf("Creating new Mode %s\n", name); + XRRModeInfo *mode = XRRAllocModeInfo(name, strlen(name)); + + mode->width = vncattr.width; + mode->height = vncattr.height; + + RRMode rmode = XRRCreateMode(appdisp, approot, mode); + XRRAddOutputMode(appdisp, + res->outputs[tgt], + rmode); + XRRFreeModeInfo(mode); + } + + XRRFreeScreenResources(res); + } + + XRRFreeScreenConfigInfo(config); + } + + const unsigned w = min(appattr.width, vncattr.width); + const unsigned h = min(appattr.height, vncattr.height); + + if (w != imgw || h != imgh) { + if (img) { + XShmDetach(appdisp, &shminfo); + XDestroyImage(img); + shmdt(shminfo.shmaddr); + shmctl(shminfo.shmid, IPC_RMID, NULL); + } + img = XShmCreateImage(appdisp, appvis, appdepth, ZPixmap, + NULL, &shminfo, w, h); + if (!img) + break; + + shminfo.shmid = shmget(IPC_PRIVATE, + img->bytes_per_line * img->height, + IPC_CREAT | 0666); + if (shminfo.shmid == -1) + break; + shminfo.shmaddr = img->data = shmat(shminfo.shmid, 0, 0); + shminfo.readOnly = False; + if (!XShmAttach(appdisp, &shminfo)) + break; + + imgw = w; + imgh = h; + } + + XShmGetImage(appdisp, approot, img, 0, 0, 0xffffffff); + XPutImage(vncdisp, vncroot, gc, img, 0, 0, 0, 0, w, h); + + // Handle events + while (XPending(vncdisp)) { + XEvent ev; + XNextEvent(vncdisp, &ev); + + if (ev.type == xfixesbasevnc + XFixesSelectionNotify) { + XFixesSelectionNotifyEvent *xfe = + (XFixesSelectionNotifyEvent *) &ev; +// printf("vnc disp did a copy, owner %lu, root %lu\n", +// xfe->owner, vncroot); + if (xfe->owner == None || xfe->owner == vncroot) + continue; + + XConvertSelection(vncdisp, XA_PRIMARY, XA_STRING, XA_STRING, + vncselwin, CurrentTime); + } else switch (ev.type) { + case KeyPress: + case KeyRelease: + XTestFakeKeyEvent(appdisp, ev.xkey.keycode, + ev.type == KeyPress, + CurrentTime); + break; + case ButtonPress: + case ButtonRelease: + XTestFakeButtonEvent(appdisp, ev.xbutton.button, + ev.type == ButtonPress, + CurrentTime); + break; + case MotionNotify: + XTestFakeMotionEvent(appdisp, appscreen, + ev.xmotion.x, + ev.xmotion.y, + CurrentTime); + break; + case SelectionRequest: + supplyselection(vncdisp, &ev, xa_targets_vnc); + break; + case SelectionNotify: + { + Atom realtype; + int fmt; + unsigned long nitems, bytes_rem; + unsigned char *data; + if (XGetWindowProperty(vncdisp, vncselwin, + XA_STRING, + 0, CUT_MAX / 4, + False, AnyPropertyType, + &realtype, &fmt, + &nitems, &bytes_rem, + &data) == Success) { + + if (bytes_rem) { + printf("Clipboard too large, ignoring\n"); + } else { + const uint32_t len = nitems * (fmt / 8); + //printf("realtype %lu, fmt %u, nitems %lu\n", + // realtype, fmt, nitems); + memcpy(cutbuf, data, len); + if (len < CUT_MAX) + cutbuf[len] = 0; + else + cutbuf[CUT_MAX - 1] = 0; + + // Send it to the app screen + XSetSelectionOwner(appdisp, XA_PRIMARY, + approot, + CurrentTime); + } + + XFree(data); + } else { + printf("Failed to fetch vnc clipboard\n"); + } + } + break; + case SelectionClear: + cutbuf[0] = '\0'; + XSetSelectionOwner(appdisp, XA_PRIMARY, None, + CurrentTime); + break; + default: + printf("Unexpected event type %u\n", ev.type); + break; + } + } + + // App-side events + while (XPending(appdisp)) { + XEvent ev; + XNextEvent(appdisp, &ev); + + if (ev.type == xfixesbase + XFixesSelectionNotify) { + XFixesSelectionNotifyEvent *xfe = + (XFixesSelectionNotifyEvent *) &ev; + //printf("app disp did a copy, owner %lu\n", xfe->owner); + if (xfe->owner == None || xfe->owner == approot) + continue; + + XConvertSelection(appdisp, XA_PRIMARY, XA_STRING, XA_STRING, + selwin, CurrentTime); + } else switch (ev.type) { + case SelectionNotify: + { + Atom realtype; + int fmt; + unsigned long nitems, bytes_rem; + unsigned char *data; + if (XGetWindowProperty(appdisp, selwin, + XA_STRING, + 0, CUT_MAX / 4, + False, AnyPropertyType, + &realtype, &fmt, + &nitems, &bytes_rem, + &data) == Success) { + + if (bytes_rem) { + printf("Clipboard too large, ignoring\n"); + } else { + const uint32_t len = nitems * (fmt / 8); + //printf("realtype %lu, fmt %u, nitems %lu\n", + // realtype, fmt, nitems); + memcpy(cutbuf, data, len); + if (len < CUT_MAX) + cutbuf[len] = 0; + else + cutbuf[CUT_MAX - 1] = 0; + + // Send it to the VNC screen + XSetSelectionOwner(vncdisp, XA_PRIMARY, + vncroot, + CurrentTime); + } + + XFree(data); + } else { + printf("Failed to fetch app clipboard\n"); + } + } + break; + case SelectionRequest: + supplyselection(appdisp, &ev, xa_targets_app); + break; + default: + printf("Unexpected app event type %u\n", ev.type); + break; + } + } + + // Cursors + cursor = XFixesGetCursorImage(appdisp); + uint64_t newhash = XXH64(cursor->pixels, + cursor->width * cursor->height * sizeof(unsigned long), + 0); + if (cursorhash != newhash) { + if (cursorhash) + XFreeCursor(vncdisp, xcursor); + + XcursorImage *converted = XcursorImageCreate(cursor->width, cursor->height); + + converted->xhot = cursor->xhot; + converted->yhot = cursor->yhot; + unsigned i; + for (i = 0; i < cursor->width * cursor->height; i++) { + converted->pixels[i] = cursor->pixels[i]; + } + + xcursor = XcursorImageLoadCursor(vncdisp, converted); + XDefineCursor(vncdisp, vncroot, xcursor); + + XcursorImageDestroy(converted); + + cursorhash = newhash; + } + + usleep(sleeptime); + } + + XCloseDisplay(appdisp); + XCloseDisplay(vncdisp); + + return 0; +} diff --git a/unix/kasmxproxy/kasmxproxy.man b/unix/kasmxproxy/kasmxproxy.man new file mode 100644 index 0000000..a4a016d --- /dev/null +++ b/unix/kasmxproxy/kasmxproxy.man @@ -0,0 +1,46 @@ +.TH kasmxproxy 1 "" "KasmVNC" "Virtual Network Computing" +.SH NAME +kasmxproxy \- proxy an existing X11 display to a KasmVNC display +.SH SYNOPSIS +.B kasmxproxy +.RB [ \-a|\-\-app\-display +.IR source\-display ] +.RB [ \-v|\-\-vnc\-display +.IR destination\-display ] +.RB [ \-f|\-\-fps +.IR FPS ] +.RB [ \-r|\-\-resize ] +.br + +.SH DESCRIPTION +.B kasmxproxy +is used to proxy an x display, usually attached to a physical GPU, to KasmVNC display. This is usually used in the context of providing GPU acceleration to a KasmVNC session. + +.SH OPTIONS +.TP +.B \-a, \-\-app\-display \fIsource-display\fP +Existing X display to proxy. +Defaults to :0. + +.TP +.B \-v, \-\-vnc\-display \fIdestination-display\fP +X display, where source display will be available on. +Defaults to :1. + +.TP +.B \-f, \-\-fps \fIframes-per-second\fP +X display, where the source display will be available on. +Defaults to 30 frames per second. + +.TP +.B \-r|\-\-resize +Enable resizing. WARNING: DO NOT ENABLE IF PHYSICAL DISPLAY IS ATTACHED. +Disabled by default. + +.SH EXAMPLES +.TP +.BI "kasmxproxy -a :1 -v :10 -r" +.B Proxy display :1 to display :10, with resizing on. + +.SH AUTHOR +Kasm Technologies http://kasmweb.com diff --git a/unix/kasmxproxy/xxhash.c b/unix/kasmxproxy/xxhash.c new file mode 120000 index 0000000..d70891c --- /dev/null +++ b/unix/kasmxproxy/xxhash.c @@ -0,0 +1 @@ +../../common/rfb/xxhash.c \ No newline at end of file diff --git a/unix/kasmxproxy/xxhash.h b/unix/kasmxproxy/xxhash.h new file mode 120000 index 0000000..52b76ef --- /dev/null +++ b/unix/kasmxproxy/xxhash.h @@ -0,0 +1 @@ +../../common/rfb/xxhash.h \ No newline at end of file