Bladeren bron

ub docker

orbitzs 4 jaren geleden
bovenliggende
commit
de4d5784c4
100 gewijzigde bestanden met toevoegingen van 5328 en 0 verwijderingen
  1. 1 0
      ubuntu/docker-ubuntu-vnc-desktop/.dockerignore
  2. 41 0
      ubuntu/docker-ubuntu-vnc-desktop/ARCHITECTURE.md
  3. 43 0
      ubuntu/docker-ubuntu-vnc-desktop/DEVELOPMENT.md
  4. 1 0
      ubuntu/docker-ubuntu-vnc-desktop/Dockerfile
  5. 129 0
      ubuntu/docker-ubuntu-vnc-desktop/Dockerfile.amd64
  6. 129 0
      ubuntu/docker-ubuntu-vnc-desktop/Dockerfile.arm64
  7. 124 0
      ubuntu/docker-ubuntu-vnc-desktop/Dockerfile.armhf
  8. 137 0
      ubuntu/docker-ubuntu-vnc-desktop/Dockerfile.j2
  9. 137 0
      ubuntu/docker-ubuntu-vnc-desktop/Dockerfile.my
  10. 201 0
      ubuntu/docker-ubuntu-vnc-desktop/LICENSE
  11. 61 0
      ubuntu/docker-ubuntu-vnc-desktop/Makefile
  12. 164 0
      ubuntu/docker-ubuntu-vnc-desktop/README.md
  13. 2 0
      ubuntu/docker-ubuntu-vnc-desktop/TODO
  14. 8 0
      ubuntu/docker-ubuntu-vnc-desktop/flavors/lxde.yml
  15. 8 0
      ubuntu/docker-ubuntu-vnc-desktop/flavors/lxqt.yml
  16. 6 0
      ubuntu/docker-ubuntu-vnc-desktop/flavors/xfce4.yml
  17. 4 0
      ubuntu/docker-ubuntu-vnc-desktop/hooks/pre_build
  18. 52 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/etc/nginx/sites-enabled/default
  19. 62 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/etc/supervisor/conf.d/supervisord.conf
  20. 86 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/etc/supervisor/conf.d/supervisord.conf.j2
  21. 4 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/root/.asoundrc
  22. 20 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/root/.gtkrc-2.0
  23. 75 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/startup.sh
  24. 8 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/bin/chromium-browser-sound.sh
  25. 3 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/bin/xvfb.sh
  26. 11 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/config/__init__.py
  27. 0 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/log/__init__.py
  28. 90 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/log/config.py
  29. 19 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/requirements.txt
  30. 122 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/run.py
  31. 0 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/vnc/__init__.py
  32. 203 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/vnc/app.py
  33. 2 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/vnc/log.py
  34. 40 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/vnc/response.py
  35. 133 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/vnc/state.py
  36. 15 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/vnc/util.py
  37. BIN
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/share/doro-lxde-wallpapers/bg1.jpg
  38. BIN
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/share/doro-lxde-wallpapers/bg2.jpg
  39. BIN
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/share/doro-lxde-wallpapers/bg3.jpg
  40. BIN
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/share/doro-lxde-wallpapers/bg4.jpg
  41. 18 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/share/doro-lxde-wallpapers/desktop-items-0.conf
  42. 221 0
      ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/share/applications/chromium-browser-sound.desktop
  43. BIN
      ubuntu/docker-ubuntu-vnc-desktop/screenshots/lxde.png
  44. 17 0
      ubuntu/docker-ubuntu-vnc-desktop/web/.babelrc
  45. 9 0
      ubuntu/docker-ubuntu-vnc-desktop/web/.editorconfig
  46. 5 0
      ubuntu/docker-ubuntu-vnc-desktop/web/.eslintignore
  47. 29 0
      ubuntu/docker-ubuntu-vnc-desktop/web/.eslintrc.js
  48. 10 0
      ubuntu/docker-ubuntu-vnc-desktop/web/.postcssrc.js
  49. 30 0
      ubuntu/docker-ubuntu-vnc-desktop/web/README.md
  50. 41 0
      ubuntu/docker-ubuntu-vnc-desktop/web/build/build.js
  51. 54 0
      ubuntu/docker-ubuntu-vnc-desktop/web/build/check-versions.js
  52. BIN
      ubuntu/docker-ubuntu-vnc-desktop/web/build/logo.png
  53. 101 0
      ubuntu/docker-ubuntu-vnc-desktop/web/build/utils.js
  54. 22 0
      ubuntu/docker-ubuntu-vnc-desktop/web/build/vue-loader.conf.js
  55. 92 0
      ubuntu/docker-ubuntu-vnc-desktop/web/build/webpack.base.conf.js
  56. 95 0
      ubuntu/docker-ubuntu-vnc-desktop/web/build/webpack.dev.conf.js
  57. 149 0
      ubuntu/docker-ubuntu-vnc-desktop/web/build/webpack.prod.conf.js
  58. 8 0
      ubuntu/docker-ubuntu-vnc-desktop/web/config/dev.env.js
  59. 89 0
      ubuntu/docker-ubuntu-vnc-desktop/web/config/index.js
  60. 5 0
      ubuntu/docker-ubuntu-vnc-desktop/web/config/prod.env.js
  61. 7 0
      ubuntu/docker-ubuntu-vnc-desktop/web/config/test.env.js
  62. 12 0
      ubuntu/docker-ubuntu-vnc-desktop/web/index.html
  63. 13 0
      ubuntu/docker-ubuntu-vnc-desktop/web/novnc-armhf-1.patch
  64. 83 0
      ubuntu/docker-ubuntu-vnc-desktop/web/package.json
  65. 22 0
      ubuntu/docker-ubuntu-vnc-desktop/web/src/App.vue
  66. 217 0
      ubuntu/docker-ubuntu-vnc-desktop/web/src/components/Vnc.vue
  67. 17 0
      ubuntu/docker-ubuntu-vnc-desktop/web/src/main.js
  68. 17 0
      ubuntu/docker-ubuntu-vnc-desktop/web/src/router/index.js
  69. 13 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/AUTHORS
  70. 62 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/LICENSE.txt
  71. 214 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/README.md
  72. 66 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/error-handler.js
  73. 92 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/alt.svg
  74. 106 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/clipboard.svg
  75. 96 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/connect.svg
  76. 96 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/ctrl.svg
  77. 100 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/ctrlaltdel.svg
  78. 94 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/disconnect.svg
  79. 71 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/drag.svg
  80. 81 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/error.svg
  81. 92 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/esc.svg
  82. 69 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/expander.svg
  83. 93 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/fullscreen.svg
  84. 82 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/handle.svg
  85. 172 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/handle_bg.svg
  86. 42 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/Makefile
  87. BIN
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-120x120.png
  88. BIN
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-144x144.png
  89. BIN
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-152x152.png
  90. BIN
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-16x16.png
  91. BIN
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-192x192.png
  92. BIN
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-24x24.png
  93. BIN
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-32x32.png
  94. BIN
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-48x48.png
  95. BIN
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-60x60.png
  96. BIN
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-64x64.png
  97. BIN
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-72x72.png
  98. BIN
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-76x76.png
  99. BIN
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-96x96.png
  100. 163 0
      ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-icon-sm.svg

+ 1 - 0
ubuntu/docker-ubuntu-vnc-desktop/.dockerignore

@@ -0,0 +1 @@
+web/node_modules

+ 41 - 0
ubuntu/docker-ubuntu-vnc-desktop/ARCHITECTURE.md

@@ -0,0 +1,41 @@
+# Architecture of the container #
+
+Components
+============
+
+The container contains the following components :
+- An Ubuntu base system
+- The tini + supervisord startup and daemon control system
+- Nginx Web server
+- A backend ("novnc2") Python Web app providing an API (written with
+  Flask) on port 6079
+- A frontend VueJS Web app displayed to the user, which will wrap noVNC
+- noVNC + WebSockify providing the Web VNC client in an HTML5 canvas
+- Xvfb running the X11 server in memory
+- x11vnc exporting the X11 display through VNC
+- and all regular X applications, like the LXDE desktop and apps
+
+Wiring them all
+------------------
+
+Internally, Xvfb will be started in DISPLAY :1, then x11vnc will
+provide access to it on the default VNC port (5900).
+
+noVNC will be started listening to HTTP requests on port 6081.
+It is possible to connect directly to port 6081 of the container, to
+only use the regular noVNC Web interface (provided it is exported by
+the container).
+
+Above noVNC stands the VueJS frontend Web app provided by nginx, which
+will proxy the noVNC canvas, and will add some useful features over
+noVNC.
+
+User-oriented features
+==========================
+
+The Web frontend adds the following features :
+- upon display of the Web page, the app will detect the size of the
+  Web browser's window, and will invoke the backend API so as to make
+  sure the noVNC rendering ajusts to that size
+- provide a flash video rendering transporting the sound (???)
+

+ 43 - 0
ubuntu/docker-ubuntu-vnc-desktop/DEVELOPMENT.md

@@ -0,0 +1,43 @@
+# Get code
+
+```
+git clone --recursive https://github.com/fcwu/docker-ubuntu-vnc-desktop
+```
+
+or, if you have already cloned it, get submodules contents :
+```
+git submodule init; git submodule update
+```
+
+# Test local code
+
+## Test-run in container rebuilt from local repo
+
+You may edit the code in your local copy of the repo, rebuild the
+container, and test the changes:
+
+```
+make clean
+FLAVOR=lxqt ARCH=amd64 IMAGE=ubuntu:18.04 make build
+make run
+```
+
+## develop backend
+
+You may wish to work on the backend app. As the "make run" makes sure
+to mount the current dir contents under /src in the container, you can
+proceed as such (no compilation of the Python code):
+```
+make shell
+supervisorctl -c /etc/supervisor/supervisord.conf stop web
+cd /src/image/usr/local/lib/web/backend
+./run.py --debug
+```
+
+## develop frontend
+
+```
+cd web
+yarn add
+BACKEND=http://127.0.0.1:6080 npm run dev
+```

+ 1 - 0
ubuntu/docker-ubuntu-vnc-desktop/Dockerfile

@@ -0,0 +1 @@
+Dockerfile.my

+ 129 - 0
ubuntu/docker-ubuntu-vnc-desktop/Dockerfile.amd64

@@ -0,0 +1,129 @@
+# Built with arch: amd64 flavor: lxde image: ubuntu:20.04
+#
+################################################################################
+# base system
+################################################################################
+
+FROM ubuntu:20.04 as system
+
+
+
+RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list;
+
+
+# built-in packages
+ENV DEBIAN_FRONTEND noninteractive
+RUN apt update \
+    && apt install -y --no-install-recommends software-properties-common curl apache2-utils \
+    && apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        supervisor nginx sudo net-tools zenity xz-utils \
+        dbus-x11 x11-utils alsa-utils \
+        mesa-utils libgl1-mesa-dri \
+    && apt autoclean -y \
+    && apt autoremove -y \
+    && rm -rf /var/lib/apt/lists/*
+# install debs error if combine together
+RUN apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        xvfb x11vnc \
+        vim-tiny firefox ttf-ubuntu-font-family ttf-wqy-zenhei  \
+    && apt autoclean -y \
+    && apt autoremove -y \
+    && rm -rf /var/lib/apt/lists/*
+
+RUN apt update \
+    && apt install -y gpg-agent \
+    && curl -LO https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \
+    && (dpkg -i ./google-chrome-stable_current_amd64.deb || apt-get install -fy) \
+    && curl -sSL https://dl.google.com/linux/linux_signing_key.pub | apt-key add \
+    && rm google-chrome-stable_current_amd64.deb \
+    && rm -rf /var/lib/apt/lists/*
+
+RUN apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        lxde gtk2-engines-murrine gnome-themes-standard gtk2-engines-pixbuf gtk2-engines-murrine arc-theme \
+    && apt autoclean -y \
+    && apt autoremove -y \
+    && rm -rf /var/lib/apt/lists/*
+
+
+# Additional packages require ~600MB
+# libreoffice  pinta language-pack-zh-hant language-pack-gnome-zh-hant firefox-locale-zh-hant libreoffice-l10n-zh-tw
+
+# tini to fix subreap
+ARG TINI_VERSION=v0.18.0
+ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /bin/tini
+RUN chmod +x /bin/tini
+
+# ffmpeg
+RUN apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        ffmpeg \
+    && rm -rf /var/lib/apt/lists/* \
+    && mkdir /usr/local/ffmpeg \
+    && ln -s /usr/bin/ffmpeg /usr/local/ffmpeg/ffmpeg
+
+# python library
+COPY rootfs/usr/local/lib/web/backend/requirements.txt /tmp/
+RUN apt-get update \
+    && dpkg-query -W -f='${Package}\n' > /tmp/a.txt \
+    && apt-get install -y python3-pip python3-dev build-essential \
+	&& pip3 install setuptools wheel && pip3 install -r /tmp/requirements.txt \
+    && ln -s /usr/bin/python3 /usr/local/bin/python \
+    && dpkg-query -W -f='${Package}\n' > /tmp/b.txt \
+    && apt-get remove -y `diff --changed-group-format='%>' --unchanged-group-format='' /tmp/a.txt /tmp/b.txt | xargs` \
+    && apt-get autoclean -y \
+    && apt-get autoremove -y \
+    && rm -rf /var/lib/apt/lists/* \
+    && rm -rf /var/cache/apt/* /tmp/a.txt /tmp/b.txt
+
+
+################################################################################
+# builder
+################################################################################
+FROM ubuntu:20.04 as builder
+
+
+RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list;
+
+
+RUN apt-get update \
+    && apt-get install -y --no-install-recommends curl ca-certificates gnupg patch
+
+# nodejs
+RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \
+    && apt-get install -y nodejs
+
+# yarn
+RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
+    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
+    && apt-get update \
+    && apt-get install -y yarn
+
+# build frontend
+COPY web /src/web
+RUN cd /src/web \
+    && yarn \
+    && yarn build
+RUN sed -i 's#app/locale/#novnc/app/locale/#' /src/web/dist/static/novnc/app/ui.js
+
+
+
+################################################################################
+# merge
+################################################################################
+FROM system
+LABEL maintainer="fcwu.tw@gmail.com"
+
+COPY --from=builder /src/web/dist/ /usr/local/lib/web/frontend/
+COPY rootfs /
+RUN ln -sf /usr/local/lib/web/frontend/static/websockify /usr/local/lib/web/frontend/static/novnc/utils/websockify && \
+	chmod +x /usr/local/lib/web/frontend/static/websockify/run
+
+EXPOSE 80
+WORKDIR /root
+ENV HOME=/home/ubuntu \
+    SHELL=/bin/bash
+HEALTHCHECK --interval=30s --timeout=5s CMD curl --fail http://127.0.0.1:6079/api/health
+ENTRYPOINT ["/startup.sh"]

+ 129 - 0
ubuntu/docker-ubuntu-vnc-desktop/Dockerfile.arm64

@@ -0,0 +1,129 @@
+# Built with arch: arm64 flavor: lxde image: ubuntu:18.04
+#
+################################################################################
+# base system
+################################################################################
+
+# qemu helper for arm build
+FROM ubuntu:20.04 as amd64
+RUN apt update && apt install -y qemu-user-static
+FROM arm64v8/ubuntu:20.04 as system
+COPY --from=amd64 /usr/bin/qemu-aarch64-static /usr/bin/
+
+
+
+RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list;
+
+
+# built-in packages
+ENV DEBIAN_FRONTEND noninteractive
+RUN apt update \
+    && apt install -y --no-install-recommends software-properties-common curl apache2-utils \
+    && apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        supervisor nginx sudo net-tools zenity xz-utils \
+        dbus-x11 x11-utils alsa-utils \
+        mesa-utils libgl1-mesa-dri \
+    && apt autoclean -y \
+    && apt autoremove -y \
+    && rm -rf /var/lib/apt/lists/*
+# install debs error if combine together
+RUN apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        xvfb x11vnc \
+        vim-tiny firefox chromium-browser ttf-ubuntu-font-family ttf-wqy-zenhei  \
+    && add-apt-repository -r ppa:fcwu-tw/apps \
+    && apt autoclean -y \
+    && apt autoremove -y \
+    && rm -rf /var/lib/apt/lists/*
+
+RUN apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        lxde gtk2-engines-murrine gnome-themes-standard gtk2-engines-pixbuf gtk2-engines-murrine arc-theme \
+    && apt autoclean -y \
+    && apt autoremove -y \
+    && rm -rf /var/lib/apt/lists/*
+
+
+# Additional packages require ~600MB
+# libreoffice  pinta language-pack-zh-hant language-pack-gnome-zh-hant firefox-locale-zh-hant libreoffice-l10n-zh-tw
+
+# tini for subreap
+ARG TINI_VERSION=v0.18.0
+ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-arm64 /bin/tini
+RUN chmod +x /bin/tini
+
+# ffmpeg
+RUN apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        ffmpeg \
+    && rm -rf /var/lib/apt/lists/* \
+    && mkdir /usr/local/ffmpeg \
+    && ln -s /usr/bin/ffmpeg /usr/local/ffmpeg/ffmpeg
+
+# python library
+COPY rootfs/usr/local/lib/web/backend/requirements.txt /tmp/
+RUN apt-get update \
+    && dpkg-query -W -f='${Package}\n' > /tmp/a.txt \
+    && apt-get install -y python3-pip python3-dev build-essential \
+	&& pip3 install setuptools wheel && pip3 install -r /tmp/requirements.txt \
+    && ln -s /usr/bin/python3 /usr/local/bin/python \
+    && dpkg-query -W -f='${Package}\n' > /tmp/b.txt \
+    && apt-get remove -y `diff --changed-group-format='%>' --unchanged-group-format='' /tmp/a.txt /tmp/b.txt | xargs` \
+    && apt-get autoclean -y \
+    && apt-get autoremove -y \
+    && rm -rf /var/lib/apt/lists/* \
+    && rm -rf /var/cache/apt/* /tmp/a.txt /tmp/b.txt
+
+
+################################################################################
+# builder
+################################################################################
+FROM ubuntu:20.04 as builder
+
+
+RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list;
+
+
+RUN apt-get update \
+    && apt-get install -y --no-install-recommends curl ca-certificates gnupg patch
+
+# nodejs
+RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \
+    && apt-get install -y nodejs
+
+# yarn
+RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
+    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
+    && apt-get update \
+    && apt-get install -y yarn
+
+# build frontend
+COPY web /src/web
+RUN cd /src/web \
+    && yarn \
+    && yarn build
+RUN sed -i 's#app/locale/#novnc/app/locale/#' /src/web/dist/static/novnc/app/ui.js
+
+
+RUN cd /src/web/dist/static/novnc && patch -p0 < /src/web/novnc-armhf-1.patch
+
+
+################################################################################
+# merge
+################################################################################
+FROM system
+LABEL maintainer="fcwu.tw@gmail.com"
+
+COPY --from=builder /src/web/dist/ /usr/local/lib/web/frontend/
+COPY rootfs /
+RUN ln -sf /usr/local/lib/web/frontend/static/websockify" "/usr/local/lib/web/frontend/static/novnc/utils/websockify && chmod +x /usr/local/lib/web/frontend/static/websockify/run
+RUN ln -sf /usr/local/lib/web/frontend/static/websockify /usr/local/lib/web/frontend/static/novnc/utils/websockify && \
+	chmod +x /usr/local/lib/web/frontend/static/websockify/run
+
+EXPOSE 80
+WORKDIR /root
+ENV HOME=/home/ubuntu \
+    SHELL=/bin/bash
+HEALTHCHECK --interval=30s --timeout=5s CMD curl --fail http://127.0.0.1:6079/api/health
+ENTRYPOINT ["/startup.sh"]

+ 124 - 0
ubuntu/docker-ubuntu-vnc-desktop/Dockerfile.armhf

@@ -0,0 +1,124 @@
+# Built with arch: armhf flavor: lxde image: ubuntu:18.04
+#
+################################################################################
+# base system
+################################################################################
+
+# qemu helper for arm build
+FROM ubuntu:18.04 as amd64
+RUN apt update && apt install -y qemu-user-static
+FROM arm32v7/ubuntu:18.04 as system
+COPY --from=amd64 /usr/bin/qemu-arm-static /usr/bin/
+
+
+
+RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list;
+
+
+# built-in packages
+ENV DEBIAN_FRONTEND noninteractive
+RUN apt update \
+    && apt install -y --no-install-recommends software-properties-common curl apache2-utils \
+    && apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        supervisor nginx sudo net-tools zenity xz-utils \
+        dbus-x11 x11-utils alsa-utils \
+        mesa-utils libgl1-mesa-dri \
+    && apt autoclean -y \
+    && apt autoremove -y \
+    && rm -rf /var/lib/apt/lists/*
+# install debs error if combine together
+RUN add-apt-repository -y ppa:fcwu-tw/apps \
+    && apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        xvfb x11vnc=0.9.16-1 \
+        vim-tiny firefox chromium-browser ttf-ubuntu-font-family ttf-wqy-zenhei  \
+    && add-apt-repository -r ppa:fcwu-tw/apps \
+    && apt autoclean -y \
+    && apt autoremove -y \
+    && rm -rf /var/lib/apt/lists/*
+
+RUN apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        lxde gtk2-engines-murrine gnome-themes-standard gtk2-engines-pixbuf gtk2-engines-murrine arc-theme \
+    && apt autoclean -y \
+    && apt autoremove -y \
+    && rm -rf /var/lib/apt/lists/*
+
+
+# Additional packages require ~600MB
+# libreoffice  pinta language-pack-zh-hant language-pack-gnome-zh-hant firefox-locale-zh-hant libreoffice-l10n-zh-tw
+
+# tini for subreap
+ARG TINI_VERSION=v0.18.0
+ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-armhf /bin/tini
+RUN chmod +x /bin/tini
+
+# ffmpeg
+RUN mkdir -p /usr/local/ffmpeg \
+    && curl -sSL https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz | tar xJvf - -C /usr/local/ffmpeg/ --strip 1
+
+# python library
+COPY rootfs/usr/local/lib/web/backend/requirements.txt /tmp/
+RUN apt-get update \
+    && dpkg-query -W -f='${Package}\n' > /tmp/a.txt \
+    && apt-get install -y python-pip python-dev build-essential \
+	&& pip install setuptools wheel && pip install -r /tmp/requirements.txt \
+    && dpkg-query -W -f='${Package}\n' > /tmp/b.txt \
+    && apt-get remove -y `diff --changed-group-format='%>' --unchanged-group-format='' /tmp/a.txt /tmp/b.txt | xargs` \
+    && apt-get autoclean -y \
+    && apt-get autoremove -y \
+    && rm -rf /var/lib/apt/lists/* \
+    && rm -rf /var/cache/apt/* /tmp/a.txt /tmp/b.txt
+
+
+################################################################################
+# builder
+################################################################################
+FROM ubuntu:18.04 as builder
+
+
+RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list;
+
+
+RUN apt-get update \
+    && apt-get install -y --no-install-recommends curl ca-certificates gnupg patch
+
+# nodejs
+RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \
+    && apt-get install -y nodejs
+
+# yarn
+RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
+    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
+    && apt-get update \
+    && apt-get install -y yarn
+
+# build frontend
+COPY web /src/web
+RUN cd /src/web \
+    && yarn \
+    && yarn run build
+RUN sed -i 's#app/locale/#novnc/app/locale/#' /src/web/dist/static/novnc/app/ui.js
+
+RUN cd /src/web/dist/static/novnc && patch -p0 < /src/web/novnc-armhf-1.patch
+
+
+################################################################################
+# merge
+################################################################################
+FROM system
+LABEL maintainer="fcwu.tw@gmail.com"
+
+COPY --from=builder /src/web/dist/ /usr/local/lib/web/frontend/
+COPY rootfs /
+RUN ln -sf /usr/local/lib/web/frontend/static/websockify" "/usr/local/lib/web/frontend/static/novnc/utils/websockify && chmod +x /usr/local/lib/web/frontend/static/websockify/run
+RUN ln -sf /usr/local/lib/web/frontend/static/websockify /usr/local/lib/web/frontend/static/novnc/utils/websockify && \
+	chmod +x /usr/local/lib/web/frontend/static/websockify/run
+
+EXPOSE 80
+WORKDIR /root
+ENV HOME=/home/ubuntu \
+    SHELL=/bin/bash
+HEALTHCHECK --interval=30s --timeout=5s CMD curl --fail http://127.0.0.1:6079/api/health
+ENTRYPOINT ["/startup.sh"]

+ 137 - 0
ubuntu/docker-ubuntu-vnc-desktop/Dockerfile.j2

@@ -0,0 +1,137 @@
+# Built with arch: {{ arch }} flavor: {{ flavor }} image: {{ image }}
+#
+################################################################################
+# base system
+################################################################################
+{%if arch == "amd64"%}
+FROM {{image}} as system
+{%elif arch == "armhf"%}
+# qemu helper for arm build
+FROM {{image}} as amd64
+RUN apt update && apt install -y qemu-user-static
+FROM arm32v7/{{image}} as system
+COPY --from=amd64 /usr/bin/qemu-arm-static /usr/bin/
+{%endif%}
+
+RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list;
+
+# built-in packages
+ENV DEBIAN_FRONTEND noninteractive
+RUN apt update \
+    && apt install -y --no-install-recommends software-properties-common curl apache2-utils \
+    && apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        supervisor nginx sudo net-tools zenity xz-utils \
+        dbus-x11 x11-utils alsa-utils \
+        mesa-utils libgl1-mesa-dri \
+    && apt autoclean -y \
+    && apt autoremove -y \
+    && rm -rf /var/lib/apt/lists/*
+# install debs error if combine together
+RUN apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        xvfb x11vnc \
+        vim-tiny firefox ttf-ubuntu-font-family ttf-wqy-zenhei  \
+    && apt autoclean -y \
+    && apt autoremove -y \
+    && rm -rf /var/lib/apt/lists/*
+RUN apt update \
+    && apt install -y gpg-agent \
+    && curl -LO https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \
+    && (dpkg -i ./google-chrome-stable_current_amd64.deb || apt-get install -fy) \
+    && curl -sSL https://dl.google.com/linux/linux_signing_key.pub | apt-key add \
+    && rm google-chrome-stable_current_amd64.deb \
+    && rm -rf /var/lib/apt/lists/*
+{%if desktop == "lxde" %}
+{%endif%}
+{%if desktop == "lxqt" %}
+{%endif%}
+{%if desktop == "xfce4" %}
+{%endif%}
+RUN apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        lxde gtk2-engines-murrine gnome-themes-standard gtk2-engines-pixbuf gtk2-engines-murrine arc-theme \
+    && apt autoclean -y \
+    && apt autoremove -y \
+    && rm -rf /var/lib/apt/lists/*
+# Additional packages require ~600MB
+# libreoffice  pinta language-pack-zh-hant language-pack-gnome-zh-hant firefox-locale-zh-hant libreoffice-l10n-zh-tw
+
+# tini to fix subreap
+ARG TINI_VERSION=v0.18.0
+ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /bin/tini
+RUN chmod +x /bin/tini
+
+# ffmpeg
+RUN apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        ffmpeg \
+    && rm -rf /var/lib/apt/lists/* \
+    && mkdir /usr/local/ffmpeg \
+    && ln -s /usr/bin/ffmpeg /usr/local/ffmpeg/ffmpeg
+
+# python library
+COPY rootfs/usr/local/lib/web/backend/requirements.txt /tmp/
+RUN apt-get update \
+    && dpkg-query -W -f='${Package}\n' > /tmp/a.txt \
+    && apt-get install -y python3-pip python3-dev build-essential \
+	&& pip3 install setuptools wheel && pip3 install -r /tmp/requirements.txt \
+    && ln -s /usr/bin/python3 /usr/local/bin/python \
+    && dpkg-query -W -f='${Package}\n' > /tmp/b.txt \
+    && apt-get remove -y `diff --changed-group-format='%>' --unchanged-group-format='' /tmp/a.txt /tmp/b.txt | xargs` \
+    && apt-get autoclean -y \
+    && apt-get autoremove -y \
+    && rm -rf /var/lib/apt/lists/* \
+    && rm -rf /var/cache/apt/* /tmp/a.txt /tmp/b.txt
+
+
+################################################################################
+# builder
+################################################################################
+FROM {{image}} as builder
+
+{% if localbuild == 1 %}
+RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list;
+{% endif %}
+
+RUN apt-get update \
+    && apt-get install -y --no-install-recommends curl ca-certificates gnupg patch
+
+# nodejs
+RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \
+    && apt-get install -y nodejs
+
+# yarn
+RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
+    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
+    && apt-get update \
+    && apt-get install -y yarn
+
+# build frontend
+COPY web /src/web
+RUN cd /src/web \
+    && yarn \
+    && yarn build
+RUN sed -i 's#app/locale/#novnc/app/locale/#' /src/web/dist/static/novnc/app/ui.js
+
+{%if arch == "armhf"%}
+RUN cd /src/web/dist/static/novnc && patch -p0 < /src/web/novnc-armhf-1.patch
+{%endif%}
+
+################################################################################
+# merge
+################################################################################
+FROM system
+LABEL maintainer="fcwu.tw@gmail.com"
+
+COPY --from=builder /src/web/dist/ /usr/local/lib/web/frontend/
+COPY rootfs /
+RUN ln -sf /usr/local/lib/web/frontend/static/websockify /usr/local/lib/web/frontend/static/novnc/utils/websockify && \
+	chmod +x /usr/local/lib/web/frontend/static/websockify/run
+
+EXPOSE 80
+WORKDIR /root
+ENV HOME=/home/ubuntu \
+    SHELL=/bin/bash
+HEALTHCHECK --interval=30s --timeout=5s CMD curl --fail http://127.0.0.1:6079/api/health
+ENTRYPOINT ["/startup.sh"]

+ 137 - 0
ubuntu/docker-ubuntu-vnc-desktop/Dockerfile.my

@@ -0,0 +1,137 @@
+# Built with arch: amd64 flavor: lxde image: ubuntu:20.04
+#
+################################################################################
+# base system
+################################################################################
+
+FROM ubuntu:20.04 as system
+
+
+
+RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list;
+
+
+# built-in packages
+ENV DEBIAN_FRONTEND noninteractive
+RUN apt update \
+    && apt install -y --no-install-recommends software-properties-common curl apache2-utils \
+    && apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        supervisor nginx sudo net-tools zenity xz-utils \
+        dbus-x11 x11-utils alsa-utils \
+        mesa-utils libgl1-mesa-dri \
+    && apt autoclean -y \
+    && apt autoremove -y \
+    && rm -rf /var/lib/apt/lists/*
+# install debs error if combine together
+RUN apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        xvfb x11vnc \
+        vim-tiny firefox ttf-ubuntu-font-family ttf-wqy-zenhei  \
+    && apt autoclean -y \
+    && apt autoremove -y \
+    && rm -rf /var/lib/apt/lists/*
+
+RUN apt update \
+    && apt install -y gpg-agent \
+    && curl -LO https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \
+    && (dpkg -i ./google-chrome-stable_current_amd64.deb || apt-get install -fy) \
+    && curl -sSL https://dl.google.com/linux/linux_signing_key.pub | apt-key add \
+    && rm google-chrome-stable_current_amd64.deb \
+    && rm -rf /var/lib/apt/lists/*
+
+RUN apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        lxde gtk2-engines-murrine gnome-themes-standard gtk2-engines-pixbuf gtk2-engines-murrine arc-theme \
+        iputils-ping net-tools screen chromium-browser \
+    && apt autoclean -y \
+    && apt autoremove -y \
+    && rm -rf /var/lib/apt/lists/* 
+
+
+# Additional packages require ~600MB
+# libreoffice  pinta language-pack-zh-hant language-pack-gnome-zh-hant firefox-locale-zh-hant libreoffice-l10n-zh-tw
+
+# tini to fix subreap
+ARG TINI_VERSION=v0.18.0
+ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /bin/tini
+RUN chmod +x /bin/tini
+
+# ffmpeg
+RUN apt update \
+    && apt install -y --no-install-recommends --allow-unauthenticated \
+        ffmpeg \
+    && rm -rf /var/lib/apt/lists/* \
+    && mkdir /usr/local/ffmpeg \
+    && ln -s /usr/bin/ffmpeg /usr/local/ffmpeg/ffmpeg
+
+# python library
+COPY rootfs/usr/local/lib/web/backend/requirements.txt /tmp/
+RUN apt-get update \
+    && dpkg-query -W -f='${Package}\n' > /tmp/a.txt \
+    && apt-get install -y python3-pip python3-dev build-essential \
+	&& pip3 install setuptools wheel && pip3 install -r /tmp/requirements.txt \
+    && ln -s /usr/bin/python3 /usr/local/bin/python \
+    && dpkg-query -W -f='${Package}\n' > /tmp/b.txt \
+    && apt-get remove -y `diff --changed-group-format='%>' --unchanged-group-format='' /tmp/a.txt /tmp/b.txt | xargs` \
+    && apt-get autoclean -y \
+    && apt-get autoremove -y \
+    && rm -rf /var/lib/apt/lists/* \
+    && rm -rf /var/cache/apt/* /tmp/a.txt /tmp/b.txt
+
+
+RUN apt update && apt-get install -y snapd squashfuse fuse
+RUN systemctl enable snapd
+#CMD [ "/sbin/init" ]
+#RUN snap install chromium
+
+
+################################################################################
+# builder
+################################################################################
+FROM ubuntu:20.04 as builder
+
+
+RUN sed -i 's#http://archive.ubuntu.com/ubuntu/#mirror://mirrors.ubuntu.com/mirrors.txt#' /etc/apt/sources.list;
+
+
+RUN apt-get update \
+    && apt-get install -y --no-install-recommends curl ca-certificates gnupg patch
+
+# nodejs
+RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \
+    && apt-get install -y nodejs
+
+# yarn
+RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
+    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
+    && apt-get update \
+    && apt-get install -y yarn
+
+# build frontend
+COPY web /src/web
+RUN cd /src/web \
+    && yarn \
+    && yarn build
+RUN sed -i 's#app/locale/#novnc/app/locale/#' /src/web/dist/static/novnc/app/ui.js
+
+
+
+################################################################################
+# merge
+################################################################################
+FROM system
+LABEL maintainer="fcwu.tw@gmail.com"
+
+COPY --from=builder /src/web/dist/ /usr/local/lib/web/frontend/
+COPY rootfs /
+RUN ln -sf /usr/local/lib/web/frontend/static/websockify /usr/local/lib/web/frontend/static/novnc/utils/websockify && \
+	chmod +x /usr/local/lib/web/frontend/static/websockify/run
+
+EXPOSE 80
+WORKDIR /root
+ENV HOME=/home/ubuntu \
+    SHELL=/bin/bash
+HEALTHCHECK --interval=30s --timeout=5s CMD curl --fail http://127.0.0.1:6079/api/health
+
+ENTRYPOINT ["/startup.sh"]

+ 201 - 0
ubuntu/docker-ubuntu-vnc-desktop/LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 61 - 0
ubuntu/docker-ubuntu-vnc-desktop/Makefile

@@ -0,0 +1,61 @@
+.PHONY: build run
+
+# Default values for variables
+REPO  ?= dorowu/ubuntu-desktop-lxde-vnc
+TAG   ?= latest
+# you can choose other base image versions
+IMAGE ?= ubuntu:20.04
+# IMAGE ?= nvidia/cuda:10.1-cudnn7-devel-ubuntu18.04
+# choose from supported flavors (see available ones in ./flavors/*.yml)
+FLAVOR ?= lxde
+# armhf or amd64
+ARCH ?= amd64
+
+# These files will be generated from teh Jinja templates (.j2 sources)
+templates = Dockerfile rootfs/etc/supervisor/conf.d/supervisord.conf
+
+# Rebuild the container image
+build: $(templates)
+	docker build -t $(REPO):$(TAG) .
+
+# Test run the container
+# the local dir will be mounted under /src read-only
+run:
+	docker run --privileged --rm \
+		-p 6080:80 -p 6081:443 \
+		-v ${PWD}:/src:ro \
+		-e USER=doro -e PASSWORD=mypassword \
+		-e ALSADEV=hw:2,0 \
+		-e SSL_PORT=443 \
+		-e RELATIVE_URL_ROOT=approot \
+		-e OPENBOX_ARGS="--startup /usr/bin/galculator" \
+		-v ${PWD}/ssl:/etc/nginx/ssl \
+		--device /dev/snd \
+		--name ubuntu-desktop-lxde-test \
+		$(REPO):$(TAG)
+
+# Connect inside the running container for debugging
+shell:
+	docker exec -it ubuntu-desktop-lxde-test bash
+
+# Generate the SSL/TLS config for HTTPS
+gen-ssl:
+	mkdir -p ssl
+	openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
+		-keyout ssl/nginx.key -out ssl/nginx.crt
+
+clean:
+	rm -f $(templates)
+
+extra-clean:
+	docker rmi $(REPO):$(TAG)
+	docker image prune -f
+
+# Run jinja2cli to parse Jinja template applying rules defined in the flavors definitions
+%: %.j2 flavors/$(FLAVOR).yml
+	docker run -v $(shell pwd):/data vikingco/jinja2cli \
+		-D flavor=$(FLAVOR) \
+		-D image=$(IMAGE) \
+		-D localbuild=$(LOCALBUILD) \
+		-D arch=$(ARCH) \
+		$< flavors/$(FLAVOR).yml > $@ || rm $@

+ 164 - 0
ubuntu/docker-ubuntu-vnc-desktop/README.md

@@ -0,0 +1,164 @@
+# docker-ubuntu-vnc-desktop
+
+[![Docker Pulls](https://img.shields.io/docker/pulls/dorowu/ubuntu-desktop-lxde-vnc.svg)](https://hub.docker.com/r/dorowu/ubuntu-desktop-lxde-vnc/)
+[![Docker Stars](https://img.shields.io/docker/stars/dorowu/ubuntu-desktop-lxde-vnc.svg)](https://hub.docker.com/r/dorowu/ubuntu-desktop-lxde-vnc/)
+
+docker-ubuntu-vnc-desktop is a Docker image to provide web VNC interface to access Ubuntu LXDE/LxQT desktop environment.
+
+<!-- @import "[TOC]" {cmd="toc" depthFrom=2 depthTo=2 orderedList=false} -->
+
+<!-- code_chunk_output -->
+
+- [Quick Start](#quick-start)
+- [VNC Viewer](#vnc-viewer)
+- [HTTP Base Authentication](#http-base-authentication)
+- [SSL](#ssl)
+- [Screen Resolution](#screen-resolution)
+- [Default Desktop User](#default-desktop-user)
+- [Deploy to a subdirectory (relative url root)](#deploy-to-a-subdirectory-relative-url-root)
+- [Sound (Preview version and Linux only)](#sound-preview-version-and-linux-only)
+- [Generate Dockerfile from jinja template](#generate-dockerfile-from-jinja-template)
+- [Troubleshooting and FAQ](#troubleshooting-and-faq)
+- [License](#license)
+
+<!-- /code_chunk_output -->
+
+## Quick Start
+
+Run the docker container and access with port `6080`
+
+```shell
+docker run -p 6080:80 -v /dev/shm:/dev/shm dorowu/ubuntu-desktop-lxde-vnc
+```
+
+Browse http://127.0.0.1:6080/
+
+<img src="https://raw.github.com/fcwu/docker-ubuntu-vnc-desktop/master/screenshots/lxde.png?v1" width=700/>
+
+### Ubuntu Flavors
+
+Choose your favorite Ubuntu version with [tags](https://hub.docker.com/r/dorowu/ubuntu-desktop-lxde-vnc/tags/)
+
+- focal: Ubuntu 20.04 (latest)
+- focal-lxqt: Ubuntu 20.04 LXQt
+- bionic: Ubuntu 18.04
+- bionic-lxqt: Ubuntu 18.04 LXQt
+- xenial: Ubuntu 16.04 (deprecated)
+- trusty: Ubuntu 14.04 (deprecated)
+
+## VNC Viewer
+
+Forward VNC service port 5900 to host by
+
+```shell
+docker run -p 6080:80 -p 5900:5900 -v /dev/shm:/dev/shm dorowu/ubuntu-desktop-lxde-vnc
+```
+
+Now, open the vnc viewer and connect to port 5900. If you would like to protect vnc service by password, set environment variable `VNC_PASSWORD`, for example
+
+```shell
+docker run -p 6080:80 -p 5900:5900 -e VNC_PASSWORD=mypassword -v /dev/shm:/dev/shm dorowu/ubuntu-desktop-lxde-vnc
+```
+
+A prompt will ask password either in the browser or vnc viewer.
+
+## HTTP Base Authentication
+
+This image provides base access authentication of HTTP via `HTTP_PASSWORD`
+
+```shell
+docker run -p 6080:80 -e HTTP_PASSWORD=mypassword -v /dev/shm:/dev/shm dorowu/ubuntu-desktop-lxde-vnc
+```
+
+## SSL
+
+To connect with SSL, generate self signed SSL certificate first if you don't have it
+
+```shell
+mkdir -p ssl
+openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ssl/nginx.key -out ssl/nginx.crt
+```
+
+Specify SSL port by `SSL_PORT`, certificate path to `/etc/nginx/ssl`, and forward it to 6081
+
+```shell
+docker run -p 6081:443 -e SSL_PORT=443 -v ${PWD}/ssl:/etc/nginx/ssl -v /dev/shm:/dev/shm dorowu/ubuntu-desktop-lxde-vnc
+```
+
+## Screen Resolution
+
+The Resolution of virtual desktop adapts browser window size when first connecting the server. You may choose a fixed resolution by passing `RESOLUTION` environment variable, for example
+
+```shell
+docker run -p 6080:80 -e RESOLUTION=1920x1080 -v /dev/shm:/dev/shm dorowu/ubuntu-desktop-lxde-vnc
+```
+
+## Default Desktop User
+
+The default user is `root`. You may change the user and password respectively by `USER` and `PASSWORD` environment variable, for example,
+
+```shell
+docker run -p 6080:80 -e USER=doro -e PASSWORD=password -v /dev/shm:/dev/shm dorowu/ubuntu-desktop-lxde-vnc
+```
+
+## Deploy to a subdirectory (relative url root)
+
+You may deploy this application to a subdirectory, for example `/some-prefix/`. You then can access application by `http://127.0.0.1:6080/some-prefix/`. This can be specified using the `RELATIVE_URL_ROOT` configuration option like this
+
+```shell
+docker run -p 6080:80 -e RELATIVE_URL_ROOT=some-prefix dorowu/ubuntu-desktop-lxde-vnc
+```
+
+NOTE: this variable should not have any leading and trailing splash (/)
+
+## Sound (Preview version and Linux only)
+
+It only works in Linux. 
+
+First of all, insert kernel module `snd-aloop` and specify `2` as the index of sound loop device
+
+```shell
+sudo modprobe snd-aloop index=2
+```
+
+Start the container
+
+```shell
+docker run -it --rm -p 6080:80 --device /dev/snd -e ALSADEV=hw:2,0 dorowu/ubuntu-desktop-lxde-vnc
+```
+
+where `--device /dev/snd -e ALSADEV=hw:2,0` means to grant sound device to container and set basic ASLA config to use card 2.
+
+Launch a browser with URL http://127.0.0.1:6080/#/?video, where `video` means to start with video mode. Now you can start Chromium in start menu (Internet -> Chromium Web Browser Sound) and try to play some video.
+
+Following is the screen capture of these operations. Turn on your sound at the end of video!
+
+[![demo video](http://img.youtube.com/vi/Kv9FGClP1-k/0.jpg)](http://www.youtube.com/watch?v=Kv9FGClP1-k)
+
+
+## Generate Dockerfile from jinja template
+
+WARNING: Deprecated
+
+Dockerfile and configuration can be generated by template. 
+
+- arch: one of `amd64` or `armhf`
+- flavor: refer to file in flavor/`flavor`.yml
+- image: base image
+- desktop: desktop environment which is set in flavor
+- addon_package: Debian package to be installed which is set in flavor
+
+Dockerfile and configuration are re-generate if they do not exist. Or you may force to re-generate by removing them with the command `make clean`.
+
+## Troubleshooting and FAQ
+
+1. boot2docker connection issue, https://github.com/fcwu/docker-ubuntu-vnc-desktop/issues/2
+2. Multi-language supports, https://github.com/fcwu/docker-ubuntu-vnc-desktop/issues/80
+3. Autostart, https://github.com/fcwu/docker-ubuntu-vnc-desktop/issues/85#issuecomment-466778407
+4. x11vnc arguments(multiptr), https://github.com/fcwu/docker-ubuntu-vnc-desktop/issues/101
+5. firefox/chrome crash (/dev/shm), https://github.com/fcwu/docker-ubuntu-vnc-desktop/issues/112
+6. resize display size without destroying container, https://github.com/fcwu/docker-ubuntu-vnc-desktop/issues/115#issuecomment-522426037
+
+## License
+
+See the LICENSE file for details.

+ 2 - 0
ubuntu/docker-ubuntu-vnc-desktop/TODO

@@ -0,0 +1,2 @@
+- upgrade frontend packages
+- rewrite backend by golang

+ 8 - 0
ubuntu/docker-ubuntu-vnc-desktop/flavors/lxde.yml

@@ -0,0 +1,8 @@
+---
+addon_packages:
+  - vim-tiny
+  - firefox
+  - chromium-browser
+  - ttf-ubuntu-font-family
+  - ttf-wqy-zenhei
+desktop: lxde

+ 8 - 0
ubuntu/docker-ubuntu-vnc-desktop/flavors/lxqt.yml

@@ -0,0 +1,8 @@
+---
+addon_packages:
+  - vim-tiny
+  - firefox
+  - chromium-browser
+  - ttf-ubuntu-font-family
+  - ttf-wqy-zenhei
+desktop: lxqt

+ 6 - 0
ubuntu/docker-ubuntu-vnc-desktop/flavors/xfce4.yml

@@ -0,0 +1,6 @@
+---
+addon_packages:
+  - vim-tiny
+  - firefox
+  - xfce4-terminal
+desktop: xfce4

+ 4 - 0
ubuntu/docker-ubuntu-vnc-desktop/hooks/pre_build

@@ -0,0 +1,4 @@
+#!/bin/bash
+# Register qemu-*-static for all supported processors except the 
+# current one, but also remove all registered binfmt_misc before
+docker run --rm --privileged multiarch/qemu-user-static:register --reset

+ 52 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/etc/nginx/sites-enabled/default

@@ -0,0 +1,52 @@
+server {
+	listen 80 default_server;
+	# listen [::]:80 default_server ipv6only=on;
+
+	#_SSL_PORT_#listen 443 ssl default_server;
+	#_SSL_PORT_#listen [::]:443 ssl default_server ipv6only=on;
+	#_SSL_PORT_#ssl_certificate /etc/nginx/ssl/nginx.crt;
+	#_SSL_PORT_#ssl_certificate_key /etc/nginx/ssl/nginx.key;
+	
+	#_HTTP_PASSWORD_#auth_basic "Private Property";
+	#_HTTP_PASSWORD_#auth_basic_user_file /etc/nginx/.htpasswd;
+
+	root /usr/local/lib/web/frontend/;
+	index index.html index.htm;
+
+	#_RELATIVE_URL_ROOT_location /_RELATIVE_URL_ROOT_/ {
+	#_RELATIVE_URL_ROOT_	rewrite /_RELATIVE_URL_ROOT_/(.*) /$1 break;
+	#_RELATIVE_URL_ROOT_	root /usr/local/lib/web/frontend/;
+	#_RELATIVE_URL_ROOT_}
+
+	location ~ .*/(api/.*|websockify) {
+		try_files $uri @api$http_upgrade;
+	}
+
+	location / {
+		rewrite /approot/(.*) /$1 break;
+		root /usr/local/lib/web/frontend/;
+	}
+
+	location @apiwebsocket {
+		#_RELATIVE_URL_ROOT_rewrite /_RELATIVE_URL_ROOT_/(.*) $1 break;
+		proxy_connect_timeout       7d;
+		proxy_send_timeout          7d;
+		proxy_read_timeout          7d;
+		proxy_buffering                         off;
+
+		proxy_http_version 1.1;
+		proxy_set_header Upgrade $http_upgrade;
+		proxy_set_header Connection "upgrade";
+		proxy_pass http://127.0.0.1:6081;
+	}
+
+	location @api {
+		#_RELATIVE_URL_ROOT_rewrite /_RELATIVE_URL_ROOT_/(.*) $1 break;
+		proxy_set_header X-Real-IP  $remote_addr;
+		proxy_set_header X-Forwarded-For $remote_addr;
+		proxy_set_header Host $host;
+		max_ranges 0;
+		proxy_pass http://127.0.0.1:6079;
+	}
+}
+

+ 62 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/etc/supervisor/conf.d/supervisord.conf

@@ -0,0 +1,62 @@
+[supervisord]
+redirect_stderr=true
+stopsignal=QUIT
+autorestart=true
+directory=/root
+
+[program:nginx]
+priority=10
+command=nginx -c /etc/nginx/nginx.conf -g 'daemon off;'
+
+[program:web]
+priority=10
+directory=/usr/local/lib/web/backend
+command=/usr/local/lib/web/backend/run.py
+stdout_logfile=/dev/fd/1
+stdout_logfile_maxbytes=0
+stderr_logfile=/dev/fd/1
+stderr_logfile_maxbytes=0
+
+
+[group:x]
+programs=xvfb,wm,lxpanel,pcmanfm,x11vnc,novnc
+
+[program:wm]
+priority=15
+command=/usr/bin/openbox
+environment=DISPLAY=":1",HOME="/root",USER="root"
+
+[program:lxpanel]
+priority=15
+directory=%HOME%
+command=/usr/bin/lxpanel --profile LXDE
+user=%USER%
+environment=DISPLAY=":1",HOME="%HOME%",USER="%USER%"
+
+[program:pcmanfm]
+priority=15
+directory=%HOME%
+command=/usr/bin/pcmanfm --desktop --profile LXDE
+user=%USER%
+stopwaitsecs=3
+environment=DISPLAY=":1",HOME="%HOME%",USER="%USER%"
+
+
+
+
+
+
+[program:xvfb]
+priority=10
+command=/usr/local/bin/xvfb.sh
+stopsignal=KILL
+
+[program:x11vnc]
+priority=20
+command=x11vnc -display :1 -xkb -forever -shared -repeat -capslock
+
+[program:novnc]
+priority=25
+directory=/usr/local/lib/web/frontend/static/novnc
+command=bash /usr/local/lib/web/frontend/static/novnc/utils/launch.sh --listen 6081
+stopasgroup=true

+ 86 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/etc/supervisor/conf.d/supervisord.conf.j2

@@ -0,0 +1,86 @@
+[supervisord]
+redirect_stderr=true
+stopsignal=QUIT
+autorestart=true
+directory=/root
+
+[program:nginx]
+priority=10
+command=nginx -c /etc/nginx/nginx.conf -g 'daemon off;'
+
+[program:web]
+priority=10
+directory=/usr/local/lib/web/backend
+command=/usr/local/lib/web/backend/run.py
+stdout_logfile=/dev/fd/1
+stdout_logfile_maxbytes=0
+stderr_logfile=/dev/fd/1
+stderr_logfile_maxbytes=0
+
+{% if desktop == "lxde" %}
+[group:x]
+programs=xvfb,wm,lxpanel,pcmanfm,x11vnc,novnc
+
+[program:wm]
+priority=15
+command=/usr/bin/openbox
+environment=DISPLAY=":1",HOME="/root",USER="root"
+
+[program:lxpanel]
+priority=15
+directory=%HOME%
+command=/usr/bin/lxpanel --profile LXDE
+user=%USER%
+environment=DISPLAY=":1",HOME="%HOME%",USER="%USER%"
+
+[program:pcmanfm]
+priority=15
+directory=%HOME%
+command=/usr/bin/pcmanfm --desktop --profile LXDE
+user=%USER%
+environment=DISPLAY=":1",HOME="%HOME%",USER="%USER%"
+{% endif %}
+
+{% if desktop == "lxqt" %}
+[group:x]
+programs=xvfb,wm,lxpanel,x11vnc,novnc
+
+[program:wm]
+priority=15
+command=/usr/bin/openbox
+environment=DISPLAY=":1",HOME="/root",USER="root"
+
+[program:lxpanel]
+priority=15
+directory=%HOME%
+command=/usr/bin/startlxqt
+user=%USER%
+environment=DISPLAY=":1",HOME="%HOME%",USER="%USER%"
+{% endif %}
+
+{% if desktop == "xfce4" %}
+[group:x]
+programs=xvfb,lxpanel,x11vnc,novnc
+
+[program:lxpanel]
+priority=15
+directory=%HOME%
+command=/usr/bin/startxfce4
+user=%USER%
+environment=DISPLAY=":1",HOME="%HOME%",USER="%USER%"
+{% endif %}
+
+[program:xvfb]
+priority=10
+command=/usr/local/bin/xvfb.sh
+stopsignal=KILL
+
+[program:x11vnc]
+priority=20
+command=x11vnc -display :1 -xkb -forever -shared -repeat -capslock
+
+[program:novnc]
+priority=25
+directory=/usr/local/lib/web/frontend/static/novnc
+command=bash /usr/local/lib/web/frontend/static/novnc/utils/launch.sh --listen 6081
+stopasgroup=true

+ 4 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/root/.asoundrc

@@ -0,0 +1,4 @@
+pcm.loop {
+    type plug
+    slave.pcm "hw:Loopback,2,0"
+}

+ 20 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/root/.gtkrc-2.0

@@ -0,0 +1,20 @@
+# DO NOT EDIT! This file will be overwritten by LXAppearance.
+# Any customization should be done in ~/.gtkrc-2.0.mine instead.
+include "/usr/share/themes/Arc/gtk-2.0/gtkrc"
+
+gtk-theme-name="Arc"
+gtk-icon-theme-name="nuoveXT2"
+gtk-font-name="Sans 10"
+gtk-cursor-theme-name="DMZ-White"
+gtk-cursor-theme-size=18
+gtk-toolbar-style=GTK_TOOLBAR_BOTH_HORIZ
+gtk-toolbar-icon-size=GTK_ICON_SIZE_LARGE_TOOLBAR
+gtk-button-images=1
+gtk-menu-images=1
+gtk-enable-event-sounds=1
+gtk-enable-input-feedback-sounds=1
+gtk-xft-antialias=1
+gtk-xft-hinting=1
+gtk-xft-hintstyle="hintslight"
+gtk-xft-rgba="rgb"
+include "/root/.gtkrc-2.0.mine"

+ 75 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/startup.sh

@@ -0,0 +1,75 @@
+#!/bin/bash
+
+if [ -n "$VNC_PASSWORD" ]; then
+    echo -n "$VNC_PASSWORD" > /.password1
+    x11vnc -storepasswd $(cat /.password1) /.password2
+    chmod 400 /.password*
+    sed -i 's/^command=x11vnc.*/& -rfbauth \/.password2/' /etc/supervisor/conf.d/supervisord.conf
+    export VNC_PASSWORD=
+fi
+
+if [ -n "$X11VNC_ARGS" ]; then
+    sed -i "s/^command=x11vnc.*/& ${X11VNC_ARGS}/" /etc/supervisor/conf.d/supervisord.conf
+fi
+
+if [ -n "$OPENBOX_ARGS" ]; then
+    sed -i "s#^command=/usr/bin/openbox\$#& ${OPENBOX_ARGS}#" /etc/supervisor/conf.d/supervisord.conf
+fi
+
+if [ -n "$RESOLUTION" ]; then
+    sed -i "s/1024x768/$RESOLUTION/" /usr/local/bin/xvfb.sh
+fi
+
+USER=${USER:-root}
+HOME=/root
+if [ "$USER" != "root" ]; then
+    echo "* enable custom user: $USER"
+    useradd --create-home --shell /bin/bash --user-group --groups adm,sudo $USER
+    if [ -z "$PASSWORD" ]; then
+        echo "  set default password to \"ubuntu\""
+        PASSWORD=ubuntu
+    fi
+    HOME=/home/$USER
+    echo "$USER:$PASSWORD" | chpasswd
+    cp -r /root/{.config,.gtkrc-2.0,.asoundrc} ${HOME}
+    chown -R $USER:$USER ${HOME}
+    [ -d "/dev/snd" ] && chgrp -R adm /dev/snd
+fi
+sed -i -e "s|%USER%|$USER|" -e "s|%HOME%|$HOME|" /etc/supervisor/conf.d/supervisord.conf
+
+# home folder
+if [ ! -x "$HOME/.config/pcmanfm/LXDE/" ]; then
+    mkdir -p $HOME/.config/pcmanfm/LXDE/
+    ln -sf /usr/local/share/doro-lxde-wallpapers/desktop-items-0.conf $HOME/.config/pcmanfm/LXDE/
+    chown -R $USER:$USER $HOME
+fi
+
+# nginx workers
+sed -i 's|worker_processes .*|worker_processes 1;|' /etc/nginx/nginx.conf
+
+# nginx ssl
+if [ -n "$SSL_PORT" ] && [ -e "/etc/nginx/ssl/nginx.key" ]; then
+    echo "* enable SSL"
+	sed -i 's|#_SSL_PORT_#\(.*\)443\(.*\)|\1'$SSL_PORT'\2|' /etc/nginx/sites-enabled/default
+	sed -i 's|#_SSL_PORT_#||' /etc/nginx/sites-enabled/default
+fi
+
+# nginx http base authentication
+if [ -n "$HTTP_PASSWORD" ]; then
+    echo "* enable HTTP base authentication"
+    htpasswd -bc /etc/nginx/.htpasswd $USER $HTTP_PASSWORD
+	sed -i 's|#_HTTP_PASSWORD_#||' /etc/nginx/sites-enabled/default
+fi
+
+# dynamic prefix path renaming
+if [ -n "$RELATIVE_URL_ROOT" ]; then
+    echo "* enable RELATIVE_URL_ROOT: $RELATIVE_URL_ROOT"
+	sed -i 's|#_RELATIVE_URL_ROOT_||' /etc/nginx/sites-enabled/default
+	sed -i 's|_RELATIVE_URL_ROOT_|'$RELATIVE_URL_ROOT'|' /etc/nginx/sites-enabled/default
+fi
+
+# clearup
+PASSWORD=
+HTTP_PASSWORD=
+
+exec /bin/tini -- supervisord -n -c /etc/supervisor/supervisord.conf

+ 8 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/bin/chromium-browser-sound.sh

@@ -0,0 +1,8 @@
+#!/bin/sh
+
+if [ -z "$ALSADEV" ]; then
+    zenity --error --text "To support audio, please read README.md and run container with --device /dev/snd -e ALSADEV=..."
+    exit 1
+fi
+
+exec /usr/bin/google-chrome --no-sandbox --alsa-output-device="$ALSADEV" "$@"

+ 3 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/bin/xvfb.sh

@@ -0,0 +1,3 @@
+#!/bin/sh
+
+exec /usr/bin/Xvfb :1 -screen 0 1024x768x24

+ 11 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/config/__init__.py

@@ -0,0 +1,11 @@
+class Default(object):
+    DEBUG = True
+
+
+class Development(Default):
+    PHASE = 'development'
+
+
+class Production(Default):
+    PHASE = 'production'
+    DEBUG = False

+ 0 - 0
ubuntu/a → ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/log/__init__.py


+ 90 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/log/config.py

@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+import sys
+import logging
+import logging.handlers
+
+
+# The terminal has 8 colors with codes from 0 to 7
+BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
+
+# These are the sequences need to get colored ouput
+RESET_SEQ = "\033[0m"
+COLOR_SEQ = "\033[1;%dm"
+BOLD_SEQ = "\033[1m"
+
+# The background is set with 40 plus the number of the color,
+# and the foreground with 30
+COLORS = {
+    'WARNING':  COLOR_SEQ % (30 + YELLOW) + 'WARN ' + RESET_SEQ,
+    'INFO':     COLOR_SEQ % (30 + WHITE) + 'INFO ' + RESET_SEQ,
+    'DEBUG':    COLOR_SEQ % (30 + BLUE) + 'DEBUG' + RESET_SEQ,
+    'CRITICAL': COLOR_SEQ % (30 + YELLOW) + 'CRITI' + RESET_SEQ,
+    'ERROR':    COLOR_SEQ % (30 + RED) + 'ERROR' + RESET_SEQ,
+}
+
+
+class ColoredFormatter(logging.Formatter):
+    def __init__(self, msg, use_color=True):
+        logging.Formatter.__init__(self, msg)
+        self.use_color = use_color
+
+    def format(self, record):
+        if self.use_color:
+            record.levelname = COLORS.get(record.levelname, record.levelname)
+        return logging.Formatter.format(self, record)
+
+
+class LoggingConfiguration(object):
+    COLOR_FORMAT = "%(asctime)s" + \
+                   " %(levelname)s %(message)s " + \
+                   "(" + BOLD_SEQ + "%(filename)s" + RESET_SEQ + ":%(lineno)d)"
+    NO_COLOR_FORMAT = "%(asctime)s %(levelname)s " + \
+                      "%(message)s " + \
+                      "(%(filename)s:%(lineno)d)"
+    FILE_FORMAT = "%(asctime)s %(levelname)s " + \
+                  "%(message)s "
+
+    @classmethod
+    def set(cls, log_level, log_filename, append=None, **kwargs):
+        """ Configure a rotating file logging
+        """
+        logger = logging.getLogger()
+        logger.setLevel(log_level)
+
+        COLOR_FORMAT = cls.COLOR_FORMAT
+        NO_COLOR_FORMAT = cls.NO_COLOR_FORMAT
+        FILE_FORMAT = cls.FILE_FORMAT
+        if 'name' in kwargs:
+            COLOR_FORMAT = COLOR_FORMAT.replace('%(threadName)-22s',
+                                                '%-22s' % (kwargs['name']))
+            NO_COLOR_FORMAT = NO_COLOR_FORMAT.replace(
+                '%(threadName)-22s', '%-22s' % (kwargs['name']))
+            FILE_FORMAT = FILE_FORMAT.replace(
+                '%(threadName)-22s', '%s' % (kwargs['name']))
+
+        # Log to rotating file
+        try:
+            fh = logging.handlers.RotatingFileHandler(
+                log_filename,
+                mode='a+',
+                backupCount=3
+            )
+            fh.setFormatter(ColoredFormatter(FILE_FORMAT, False))
+            fh.setLevel(log_level)
+            logger.addHandler(fh)
+            if not append:
+                # Create a new log file on every new
+                fh.doRollover()
+        except IOError as e:
+            print('ignore to log to {}: {}'.format(log_filename, e))
+
+        # Log to sys.stderr using log level passed through command line
+        if log_level != logging.NOTSET:
+            log_handler = logging.StreamHandler(sys.stdout)
+            if sys.platform.find('linux') >= 0:
+                formatter = ColoredFormatter(COLOR_FORMAT)
+            else:
+                formatter = ColoredFormatter(NO_COLOR_FORMAT, False)
+            log_handler.setFormatter(formatter)
+            log_handler.setLevel(log_level)
+            logger.addHandler(log_handler)

+ 19 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/requirements.txt

@@ -0,0 +1,19 @@
+backports.ssl-match-hostname==3.7.0.1
+certifi==2019.9.11
+chardet==3.0.4
+Click==7.0
+Flask==1.1.1
+Flask-Login==0.4.1
+gevent==1.4.0
+gevent-websocket==0.10.1
+greenlet==0.4.15
+idna==2.8
+itsdangerous==1.1.0
+Jinja2==2.11.3
+MarkupSafe==1.1.1
+meld3==2.0.0
+requests==2.22.0
+six==1.12.0
+urllib3==1.25.6
+websocket-client==0.47.0
+Werkzeug==0.16.0

+ 122 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/run.py

@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+from __future__ import (
+    absolute_import, division, print_function, with_statement
+)
+import os
+import time
+import sys
+import subprocess
+from vnc.util import ignored
+
+
+def main():
+    def run_with_reloader(main_func, extra_files=None, interval=3):
+        """Run the given function in an independent python interpreter."""
+        def find_files(directory="./"):
+            for root, dirs, files in os.walk(directory):
+                for basename in files:
+                    if basename.endswith('.py'):
+                        filename = os.path.join(root, basename)
+                        yield filename
+
+        if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
+            try:
+                main_func()
+            except KeyboardInterrupt:
+                pass
+            return
+
+        proc = None
+        try:
+            while True:
+                log.info('Restarting with reloader {} {}'.format(
+                    sys.executable,
+                    ' '.join(sys.argv))
+                )
+                args = [sys.executable] + sys.argv
+                new_environ = os.environ.copy()
+                new_environ['WERKZEUG_RUN_MAIN'] = 'true'
+
+                proc = subprocess.Popen(
+                    args,
+                    env=new_environ,
+                    close_fds=True,
+                    preexec_fn=os.setsid
+                )
+                mtimes = {}
+                restart = False
+                while not restart:
+                    for filename in find_files():
+                        try:
+                            mtime = os.stat(filename).st_mtime
+                        except OSError:
+                            continue
+
+                        old_time = mtimes.get(filename)
+                        if old_time is None:
+                            mtimes[filename] = mtime
+                            continue
+                        elif mtime > old_time:
+                            log.info(
+                                'Detected change in {}, reloading'.format(
+                                    filename
+                                )
+                            )
+                            restart = True
+                            proc.terminate()
+                            break
+                    time.sleep(interval)
+        except KeyboardInterrupt:
+            pass
+        finally:
+            with ignored(Exception):
+                proc.terminate()
+
+    def run_server():
+        import socket
+        from gevent.pywsgi import WSGIServer
+        from vnc.app import app
+
+        # websocket conflict: WebSocketHandler
+        if DEBUG:
+            # from werkzeug.debug import DebuggedApplication
+            app.debug = True
+            # app = DebuggedApplication(app, evalex=True)
+
+        try:
+            log.info('Listening on http://localhost:{}'.format(PORT))
+            http_server = WSGIServer(('localhost', PORT), app)
+            http_server.serve_forever()
+            # app.run(host='localhost', port=PORT)
+        except socket.error as e:
+            log.exception(e)
+        except KeyboardInterrupt:
+            pass
+        finally:
+            http_server.stop(timeout=10)
+            log.info('shutdown gracefully')
+
+    PORT = 6079
+    DEBUG = False
+    os.environ['CONFIG'] = 'config.Production'
+    entrypoint = run_server
+    if '--debug' in sys.argv:
+        DEBUG = True
+        os.environ['CONFIG'] = 'config.Development'
+        entrypoint = lambda: run_with_reloader(run_server)
+
+    # logging
+    import logging
+    from log.config import LoggingConfiguration
+    LoggingConfiguration.set(
+        logging.DEBUG if DEBUG else logging.INFO,
+        '/var/log/web.log'
+    )
+    logging.getLogger("werkzeug").setLevel(logging.WARNING)
+    log = logging.getLogger('novnc2')
+
+    entrypoint()
+
+
+if __name__ == "__main__":
+    main()

+ 0 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/vnc/__init__.py


+ 203 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/vnc/app.py

@@ -0,0 +1,203 @@
+from __future__ import (
+    absolute_import, division, print_function, with_statement
+)
+import re
+import os
+from flask import (
+    Flask,
+    request,
+    Response,
+    jsonify,
+    abort,
+)
+from gevent import subprocess as gsp, spawn, sleep
+from geventwebsocket.exceptions import WebSocketError
+from .response import httperror
+from .util import ignored
+from .state import state
+from .log import log
+
+
+# Flask app
+app = Flask('novnc2')
+app.config.from_object('config.Default')
+app.config.from_object(os.environ.get('CONFIG') or 'config.Development')
+
+
+@app.route('/api/state')
+@httperror
+def apistate():
+    state.wait(int(request.args.get('id', -1)), 30)
+    state.switch_video(request.args.get('video', 'false') == 'true')
+    mystate = state.to_dict()
+    return jsonify({
+        'code': 200,
+        'data': mystate,
+    })
+
+
+@app.route('/api/health')
+def apihealth():
+    if state.health:
+        return 'success'
+    abort(503, 'unhealthy')
+
+
+@app.route('/api/reset')
+def reset():
+    if 'w' in request.args and 'h' in request.args:
+        args = {
+            'w': int(request.args.get('w')),
+            'h': int(request.args.get('h')),
+        }
+        state.set_size(args['w'], args['h'])
+
+    state.apply_and_restart()
+
+    # check all running
+    for i in range(40):
+        if state.health:
+            break
+        sleep(1)
+        log.info('wait services is ready...')
+    else:
+        return jsonify({
+            'code': 500,
+            'errorMessage': 'service is not ready, please restart container'
+        })
+    return jsonify({'code': 200})
+
+
+@app.route('/resize')
+@httperror
+def apiresize():
+    state.reset_size()
+    return '<html><head><script type = "text/javascript">var h=window.location.href;window.location.href=h.substring(0,h.length-6);</script></head></html>'
+
+
+@app.route('/api/live.flv')
+@httperror
+def liveflv():
+    def generate():
+        xenvs = {
+            'DISPLAY': ':1',
+        }
+        bufsize = 1024 * 1
+        framerate = 20
+
+        # sound
+        sound_cmd_input = []
+        sound_cmd_parameters = []
+        zero_latency_make_sound_not_good = [
+            '-tune', 'zerolatency',
+        ]
+
+        xenvs['X_WIDTH'] = state.w
+        xenvs['X_HEIGHT'] = state.h
+        xenvs['X_WIDTH'] -= state.w % 2
+        xenvs['X_HEIGHT'] -= state.h % 2
+
+        pixels_count = xenvs['X_WIDTH'] * xenvs['X_HEIGHT']
+        # factor (720p)
+        #    383: 2400k
+        #    300: 3000k
+        #    230: 4000k
+        factor = 265
+        maxbitrate_cmd = [
+            '-maxrate', str(int(pixels_count / factor)) + 'k',
+            '-bufsize', str(int(pixels_count / factor / 3)) + 'k'
+        ]
+
+        # TODO move to global
+        # get default source
+        sound_cmd_input = [
+            '-f', 'alsa',
+            '-i', 'hw:2,1',
+        ]
+        sound_cmd_parameters = [
+            '-ar', '44100',
+            '-c:a', 'mp3',
+        ]
+        # flv.js report error if enabling hw acceleration
+        # hwaccel_dev = ['-vaapi_device', '/dev/dri/renderD128']
+        # hwaccel_if = ['-vf', 'format=nv12,hwupload']
+        # vcodec = 'h264_vaapi'
+        hwaccel_dev = []
+        hwaccel_if = []
+        vcodec = 'libx264'
+        # zero_latency_make_sound_not_good = []
+        # sound_cmd_parameters = []
+        # sound_cmd_input = []
+        cmd = ['/usr/local/ffmpeg/ffmpeg'] + sound_cmd_input + hwaccel_dev + [
+            '-video_size', '{X_WIDTH}x{X_HEIGHT}'.format(**xenvs),
+            '-framerate', '{}'.format(framerate),
+            '-f', 'x11grab', '-draw_mouse', '1',
+            '-i', '{DISPLAY}'.format(**xenvs),
+        ] + hwaccel_if + [
+            '-r', '{}'.format(framerate),
+            '-g', '{}'.format(framerate),
+            '-flags:v', '+global_header',
+            '-vcodec', vcodec,
+            '-preset', 'ultrafast',
+            '-b_strategy', '0',
+            '-pix_fmt', 'yuv420p',
+            '-bsf:v', 'dump_extra=freq=e',
+        ] + maxbitrate_cmd \
+            + sound_cmd_parameters + zero_latency_make_sound_not_good + [
+            '-f', 'flv', 'pipe:1',
+        ]
+        log.info('command: ' + ' '.join(cmd))
+        pobj = gsp.Popen(
+            cmd,
+            stdout=gsp.PIPE,
+            stderr=gsp.PIPE,
+            env={k: str(v) for k, v in xenvs.items()},
+        )
+
+        def readerr(f):
+            reobj = re.compile(r'bitrate=(\S+)')
+            global av_bitrate
+            try:
+                while True:
+                    buf = f.read(bufsize)
+                    if len(buf) == 0:
+                        break
+                    patterns = reobj.findall(buf.decode('utf-8', 'ignore'))
+                    if len(patterns) > 0:
+                        av_bitrate = patterns[-1]
+                    # log.info(str(buf))
+            except Exception as e:
+                log.exception(e)
+
+        preaderr = None
+        try:
+            preaderr = spawn(readerr, pobj.stderr)
+            try:
+                while True:
+                    buf = pobj.stdout.read(bufsize)
+                    if len(buf) == 0:
+                        break
+                    # ws.send(buf)
+                    yield buf
+            except WebSocketError:
+                pass
+            except Exception as e:
+                log.exception(e)
+            finally:
+                with ignored(Exception):
+                    pobj.kill()
+            preaderr.join()
+        except Exception as e:
+            log.exception(e)
+        finally:
+            log.info('exiting')
+            with ignored(Exception):
+                pobj.kill()
+            with ignored(Exception):
+                preaderr.kill()
+            log.info('exited')
+    return Response(generate(), mimetype='video/x-flv')
+
+
+if __name__ == '__main__':
+    app.run(host=app.config['ADDRESS'], port=app.config['PORT'])

+ 2 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/vnc/log.py

@@ -0,0 +1,2 @@
+import logging
+log = logging.getLogger('novnc2')

+ 40 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/vnc/response.py

@@ -0,0 +1,40 @@
+from __future__ import (
+    absolute_import, division, print_function, with_statement
+)
+from functools import wraps
+import logging
+from flask import jsonify
+
+
+log = logging.getLogger()
+
+
+class PermissionDenied(Exception):
+    pass
+
+
+class BadRequest(Exception):
+    pass
+
+
+def httperror(f):
+    @wraps(f)
+    def func(*args, **kwargs):
+        result = {
+            'code': 400,
+            'errorMessage': '',
+        }
+        try:
+            return f(*args, **kwargs)
+        except PermissionDenied as e:
+            result['code'] = 403
+            result['errorMessage'] = str(e)
+        except BadRequest as e:
+            result['code'] = 400
+            result['errorMessage'] = str(e)
+        except Exception as e:
+            logging.exception(e)
+            result['code'] = 500
+            result['errorMessage'] = str(e)
+        return jsonify(result)
+    return func

+ 133 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/vnc/state.py

@@ -0,0 +1,133 @@
+from __future__ import (
+    absolute_import, division, print_function, with_statement
+)
+from os import environ
+from gevent.event import Event
+from gevent import subprocess as gsp
+from re import search as research
+from .log import log
+
+
+class State(object):
+    def __init__(self):
+        self._eid = 0
+        self._event = Event()
+        self._w = self._h = self._health = None
+        self.size_changed_count = 0
+
+    def wait(self, eid, timeout=5):
+        if eid < self._eid:
+            return
+        self._event.clear()
+        self._event.wait(timeout)
+        return self._eid
+
+    def notify(self):
+        self._eid += 1
+        self._event.set()
+
+    def _update_health(self):
+        health = True
+        output = gsp.check_output([
+            'supervisorctl', '-c', '/etc/supervisor/supervisord.conf',
+            'status'
+        ], encoding='UTF-8')
+        for line in output.strip().split('\n'):
+            if not line.startswith('web') and line.find('RUNNING') < 0:
+                health = False
+                break
+        if self._health != health:
+            self._health = health
+            self.notify()
+        return self._health
+
+    def to_dict(self):
+        self._update_health()
+
+        state = {
+            'id': self._eid,
+            'config': {
+                'fixedResolution': 'RESOLUTION' in environ,
+                'sizeChangedCount': self.size_changed_count
+            }
+        }
+
+        self._update_size()
+        state.update({
+            'width': self.w,
+            'height': self.h,
+        })
+
+        return state
+
+    def set_size(self, w, h):
+        gsp.check_call((
+            'sed -i \'s#'
+            '^exec /usr/bin/Xvfb.*$'
+            '#'
+            'exec /usr/bin/Xvfb :1 -screen 0 {}x{}x24'
+            '#\' /usr/local/bin/xvfb.sh'
+        ).format(w, h), shell=True)
+        self.size_changed_count += 1
+
+    def apply_and_restart(self):
+        gsp.check_call([
+            'supervisorctl', '-c', '/etc/supervisor/supervisord.conf',
+            'restart', 'x:'
+        ])
+        self._w = self._h = self._health = None
+        self.notify()
+
+    def switch_video(self, onoff):
+        xenvs = {
+            'DISPLAY': ':1',
+        }
+        try:
+            cmd = 'nofb' if onoff else 'fb'
+            gsp.check_output(['x11vnc', '-remote', cmd], env=xenvs)
+        except gsp.CalledProcessError as e:
+            log.warn('failed to set x11vnc fb: ' + str(e))
+
+    def _update_size(self):
+        if self._w is not None and self._h is not None:
+            return
+        xenvs = {
+            'DISPLAY': ':1',
+        }
+        try:
+            output = gsp.check_output([
+                'x11vnc', '-query', 'dpy_x,dpy_y'
+            ], env=xenvs).decode('utf-8')
+            mobj = research(r'dpy_x:(\d+).*dpy_y:(\d+)', output)
+            if mobj is not None:
+                w, h = int(mobj.group(1)), int(mobj.group(2))
+                changed = False
+                if self._w != w:
+                    changed = True
+                    self._w = w
+                if self._h != h:
+                    changed = True
+                    self._h = h
+                if changed:
+                    self.notify()
+        except gsp.CalledProcessError as e:
+            log.warn('failed to get dispaly size: ' + str(e))
+
+    def reset_size(self):
+        self.size_changed_count = 0
+
+    @property
+    def w(self):
+        return self._w
+
+    @property
+    def h(self):
+        return self._h
+
+    @property
+    def health(self):
+        self._update_health()
+        return self._health
+
+
+state = State()

+ 15 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/lib/web/backend/vnc/util.py

@@ -0,0 +1,15 @@
+from __future__ import (
+    absolute_import, division, print_function, with_statement
+)
+from contextlib import contextmanager
+from gevent import GreenletExit
+
+
+@contextmanager
+def ignored(*exceptions):
+    try:
+        yield
+    except GreenletExit as e:
+        raise e
+    except exceptions:
+        pass

BIN
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/share/doro-lxde-wallpapers/bg1.jpg


BIN
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/share/doro-lxde-wallpapers/bg2.jpg


BIN
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/share/doro-lxde-wallpapers/bg3.jpg


BIN
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/share/doro-lxde-wallpapers/bg4.jpg


+ 18 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/local/share/doro-lxde-wallpapers/desktop-items-0.conf

@@ -0,0 +1,18 @@
+[*]
+wallpaper_mode=stretch
+wallpaper_common=0
+wallpapers_configured=4
+wallpaper0=/usr/local/share/doro-lxde-wallpapers/bg1.jpg
+wallpaper1=/usr/local/share/doro-lxde-wallpapers/bg2.jpg
+wallpaper2=/usr/local/share/doro-lxde-wallpapers/bg3.jpg
+wallpaper3=/usr/local/share/doro-lxde-wallpapers/bg4.jpg
+desktop_bg=#000000
+desktop_fg=#ffffff
+desktop_shadow=#000000
+desktop_font=Sans 12
+show_wm_menu=0
+sort=mtime;ascending;mingle;
+show_documents=0
+show_trash=0
+show_mounts=0
+

+ 221 - 0
ubuntu/docker-ubuntu-vnc-desktop/rootfs/usr/share/applications/chromium-browser-sound.desktop

@@ -0,0 +1,221 @@
+[Desktop Entry]
+Version=1.0
+Name=Google Chrome Sound
+# Only KDE 4 seems to use GenericName, so we reuse the KDE strings.
+# From Ubuntu's language-pack-kde-XX-base packages, version 9.04-20090413.
+GenericName=Web Browser
+GenericName[ar]=متصفح الشبكة
+GenericName[bg]=Уеб браузър
+GenericName[ca]=Navegador web
+GenericName[cs]=WWW prohlížeč
+GenericName[da]=Browser
+GenericName[de]=Web-Browser
+GenericName[el]=Περιηγητής ιστού
+GenericName[en_GB]=Web Browser
+GenericName[es]=Navegador web
+GenericName[et]=Veebibrauser
+GenericName[fi]=WWW-selain
+GenericName[fr]=Navigateur Web
+GenericName[gu]=વેબ બ્રાઉઝર
+GenericName[he]=דפדפן אינטרנט
+GenericName[hi]=वेब ब्राउज़र
+GenericName[hu]=Webböngésző
+GenericName[it]=Browser Web
+GenericName[ja]=ウェブブラウザ
+GenericName[kn]=ಜಾಲ ವೀಕ್ಷಕ
+GenericName[ko]=웹 브라우저
+GenericName[lt]=Žiniatinklio naršyklė
+GenericName[lv]=Tīmekļa pārlūks
+GenericName[ml]=വെബ് ബ്രൌസര്‍
+GenericName[mr]=वेब ब्राऊजर
+GenericName[nb]=Nettleser
+GenericName[nl]=Webbrowser
+GenericName[pl]=Przeglądarka WWW
+GenericName[pt]=Navegador Web
+GenericName[pt_BR]=Navegador da Internet
+GenericName[ro]=Navigator de Internet
+GenericName[ru]=Веб-браузер
+GenericName[sl]=Spletni brskalnik
+GenericName[sv]=Webbläsare
+GenericName[ta]=இணைய உலாவி
+GenericName[th]=เว็บเบราว์เซอร์
+GenericName[tr]=Web Tarayıcı
+GenericName[uk]=Навігатор Тенет
+GenericName[zh_CN]=网页浏览器
+GenericName[zh_HK]=網頁瀏覽器
+GenericName[zh_TW]=網頁瀏覽器
+# Not translated in KDE, from Epiphany 2.26.1-0ubuntu1.
+GenericName[bn]=ওয়েব ব্রাউজার
+GenericName[fil]=Web Browser
+GenericName[hr]=Web preglednik
+GenericName[id]=Browser Web
+GenericName[or]=ଓ୍ବେବ ବ୍ରାଉଜର
+GenericName[sk]=WWW prehliadač
+GenericName[sr]=Интернет прегледник
+GenericName[te]=మహాతల అన్వేషి
+GenericName[vi]=Bộ duyệt Web
+# Gnome and KDE 3 uses Comment.
+Comment=Access the Internet
+Comment[ar]=الدخول إلى الإنترنت
+Comment[bg]=Достъп до интернет
+Comment[bn]=ইন্টারনেটটি অ্যাক্সেস করুন
+Comment[ca]=Accedeix a Internet
+Comment[cs]=Přístup k internetu
+Comment[da]=Få adgang til internettet
+Comment[de]=Internetzugriff
+Comment[el]=Πρόσβαση στο Διαδίκτυο
+Comment[en_GB]=Access the Internet
+Comment[es]=Accede a Internet.
+Comment[et]=Pääs Internetti
+Comment[fi]=Käytä internetiä
+Comment[fil]=I-access ang Internet
+Comment[fr]=Accéder à Internet
+Comment[gu]=ઇંટરનેટ ઍક્સેસ કરો
+Comment[he]=גישה אל האינטרנט
+Comment[hi]=इंटरनेट तक पहुंच स्थापित करें
+Comment[hr]=Pristup Internetu
+Comment[hu]=Internetelérés
+Comment[id]=Akses Internet
+Comment[it]=Accesso a Internet
+Comment[ja]=インターネットにアクセス
+Comment[kn]=ಇಂಟರ್ನೆಟ್ ಅನ್ನು ಪ್ರವೇಶಿಸಿ
+Comment[ko]=인터넷 연결
+Comment[lt]=Interneto prieiga
+Comment[lv]=Piekļūt internetam
+Comment[ml]=ഇന്റര്‍‌നെറ്റ് ആക്‌സസ് ചെയ്യുക
+Comment[mr]=इंटरनेटमध्ये प्रवेश करा
+Comment[nb]=Gå til Internett
+Comment[nl]=Verbinding maken met internet
+Comment[or]=ଇଣ୍ଟର୍ନେଟ୍ ପ୍ରବେଶ କରନ୍ତୁ
+Comment[pl]=Skorzystaj z internetu
+Comment[pt]=Aceder à Internet
+Comment[pt_BR]=Acessar a internet
+Comment[ro]=Accesaţi Internetul
+Comment[ru]=Доступ в Интернет
+Comment[sk]=Prístup do siete Internet
+Comment[sl]=Dostop do interneta
+Comment[sr]=Приступите Интернету
+Comment[sv]=Gå ut på Internet
+Comment[ta]=இணையத்தை அணுகுதல்
+Comment[te]=ఇంటర్నెట్‌ను ఆక్సెస్ చెయ్యండి
+Comment[th]=เข้าถึงอินเทอร์เน็ต
+Comment[tr]=İnternet'e erişin
+Comment[uk]=Доступ до Інтернету
+Comment[vi]=Truy cập Internet
+Comment[zh_CN]=访问互联网
+Comment[zh_HK]=連線到網際網路
+Comment[zh_TW]=連線到網際網路
+Exec=/usr/local/bin/chromium-browser-sound.sh %U
+StartupNotify=true
+Terminal=false
+Icon=google-chrome
+Type=Application
+Categories=Network;WebBrowser;
+MimeType=application/pdf;application/rdf+xml;application/rss+xml;application/xhtml+xml;application/xhtml_xml;application/xml;image/gif;image/jpeg;image/png;image/webp;text/html;text/xml;x-scheme-handler/ftp;x-scheme-handler/http;x-scheme-handler/https;
+Actions=new-window;new-private-window;
+
+[Desktop Action new-window]
+Name=New Window
+Name[am]=አዲስ መስኮት
+Name[ar]=نافذة جديدة
+Name[bg]=Нов прозорец
+Name[bn]=নতুন উইন্ডো
+Name[ca]=Finestra nova
+Name[cs]=Nové okno
+Name[da]=Nyt vindue
+Name[de]=Neues Fenster
+Name[el]=Νέο Παράθυρο
+Name[en_GB]=New Window
+Name[es]=Nueva ventana
+Name[et]=Uus aken
+Name[fa]=پنجره جدید
+Name[fi]=Uusi ikkuna
+Name[fil]=New Window
+Name[fr]=Nouvelle fenêtre
+Name[gu]=નવી વિંડો
+Name[hi]=नई विंडो
+Name[hr]=Novi prozor
+Name[hu]=Új ablak
+Name[id]=Jendela Baru
+Name[it]=Nuova finestra
+Name[iw]=חלון חדש
+Name[ja]=新規ウインドウ
+Name[kn]=ಹೊಸ ವಿಂಡೊ
+Name[ko]=새 창
+Name[lt]=Naujas langas
+Name[lv]=Jauns logs
+Name[ml]=പുതിയ വിന്‍ഡോ
+Name[mr]=नवीन विंडो
+Name[nl]=Nieuw venster
+Name[no]=Nytt vindu
+Name[pl]=Nowe okno
+Name[pt]=Nova janela
+Name[pt_BR]=Nova janela
+Name[ro]=Fereastră nouă
+Name[ru]=Новое окно
+Name[sk]=Nové okno
+Name[sl]=Novo okno
+Name[sr]=Нови прозор
+Name[sv]=Nytt fönster
+Name[sw]=Dirisha Jipya
+Name[ta]=புதிய சாளரம்
+Name[te]=క్రొత్త విండో
+Name[th]=หน้าต่างใหม่
+Name[tr]=Yeni Pencere
+Name[uk]=Нове вікно
+Name[vi]=Cửa sổ Mới
+Name[zh_CN]=新建窗口
+Name[zh_TW]=開新視窗
+Exec=/usr/bin/google-chrome-stable
+
+[Desktop Action new-private-window]
+Name=New Incognito Window
+Name[ar]=نافذة جديدة للتصفح المتخفي
+Name[bg]=Нов прозорец „инкогнито“
+Name[bn]=নতুন ছদ্মবেশী উইন্ডো
+Name[ca]=Finestra d'incògnit nova
+Name[cs]=Nové anonymní okno
+Name[da]=Nyt inkognitovindue
+Name[de]=Neues Inkognito-Fenster
+Name[el]=Νέο παράθυρο για ανώνυμη περιήγηση
+Name[en_GB]=New Incognito window
+Name[es]=Nueva ventana de incógnito
+Name[et]=Uus inkognito aken
+Name[fa]=پنجره جدید حالت ناشناس
+Name[fi]=Uusi incognito-ikkuna
+Name[fil]=Bagong Incognito window
+Name[fr]=Nouvelle fenêtre de navigation privée
+Name[gu]=નવી છુપી વિંડો
+Name[hi]=नई गुप्त विंडो
+Name[hr]=Novi anoniman prozor
+Name[hu]=Új Inkognitóablak
+Name[id]=Jendela Penyamaran baru
+Name[it]=Nuova finestra di navigazione in incognito
+Name[iw]=חלון חדש לגלישה בסתר
+Name[ja]=新しいシークレット ウィンドウ
+Name[kn]=ಹೊಸ ಅಜ್ಞಾತ ವಿಂಡೋ
+Name[ko]=새 시크릿 창
+Name[lt]=Naujas inkognito langas
+Name[lv]=Jauns inkognito režīma logs
+Name[ml]=പുതിയ വേഷ പ്രച്ഛന്ന വിന്‍ഡോ
+Name[mr]=नवीन गुप्त विंडो
+Name[nl]=Nieuw incognitovenster
+Name[no]=Nytt inkognitovindu
+Name[pl]=Nowe okno incognito
+Name[pt]=Nova janela de navegação anónima
+Name[pt_BR]=Nova janela anônima
+Name[ro]=Fereastră nouă incognito
+Name[ru]=Новое окно в режиме инкогнито
+Name[sk]=Nové okno inkognito
+Name[sl]=Novo okno brez beleženja zgodovine
+Name[sr]=Нови прозор за прегледање без архивирања
+Name[sv]=Nytt inkognitofönster
+Name[ta]=புதிய மறைநிலைச் சாளரம்
+Name[te]=క్రొత్త అజ్ఞాత విండో
+Name[th]=หน้าต่างใหม่ที่ไม่ระบุตัวตน
+Name[tr]=Yeni Gizli pencere
+Name[uk]=Нове вікно в режимі анонімного перегляду
+Name[vi]=Cửa sổ ẩn danh mới
+Name[zh_CN]=新建隐身窗口
+Name[zh_TW]=新增無痕式視窗
+Exec=/usr/bin/google-chrome-stable --incognito

BIN
ubuntu/docker-ubuntu-vnc-desktop/screenshots/lxde.png


+ 17 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/.babelrc

@@ -0,0 +1,17 @@
+{
+  "presets": [
+    ["env", {
+      "modules": false,
+      "targets": {
+        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
+      }
+    }],
+    "stage-2"
+  ],
+  "plugins": ["transform-vue-jsx", "transform-runtime"],
+  "env": {
+    "test": {
+      "presets": ["env", "stage-2"]
+    }
+  }
+}

+ 9 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/.editorconfig

@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true

+ 5 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/.eslintignore

@@ -0,0 +1,5 @@
+/build/
+/config/
+/dist/
+/*.js
+/test/unit/coverage/

+ 29 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/.eslintrc.js

@@ -0,0 +1,29 @@
+// https://eslint.org/docs/user-guide/configuring
+
+module.exports = {
+  root: true,
+  parserOptions: {
+    parser: 'babel-eslint'
+  },
+  env: {
+    browser: true,
+  },
+  extends: [
+    // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
+    // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
+    'plugin:vue/essential', 
+    // https://github.com/standard/standard/blob/master/docs/RULES-en.md
+    'standard'
+  ],
+  // required to lint *.vue files
+  plugins: [
+    'vue'
+  ],
+  // add your custom rules here
+  rules: {
+    // allow async-await
+    'generator-star-spacing': 'off',
+    // allow debugger during development
+    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
+  }
+}

+ 10 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/.postcssrc.js

@@ -0,0 +1,10 @@
+// https://github.com/michael-ciniawsky/postcss-load-config
+
+module.exports = {
+  "plugins": {
+    "postcss-import": {},
+    "postcss-url": {},
+    // to edit target browsers: use "browserslist" field in package.json
+    "autoprefixer": {}
+  }
+}

+ 30 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/README.md

@@ -0,0 +1,30 @@
+# novnc2
+
+> extended novnc
+
+## Build Setup
+
+``` bash
+# install dependencies
+npm install
+
+# serve with hot reload at localhost:8080
+npm run dev
+
+# build for production with minification
+npm run build
+
+# build for production and view the bundle analyzer report
+npm run build --report
+
+# run unit tests
+npm run unit
+
+# run e2e tests
+npm run e2e
+
+# run all tests
+npm test
+```
+
+For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).

+ 41 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/build/build.js

@@ -0,0 +1,41 @@
+'use strict'
+require('./check-versions')()
+
+process.env.NODE_ENV = 'production'
+
+const ora = require('ora')
+const rm = require('rimraf')
+const path = require('path')
+const chalk = require('chalk')
+const webpack = require('webpack')
+const config = require('../config')
+const webpackConfig = require('./webpack.prod.conf')
+
+const spinner = ora('building for production...')
+spinner.start()
+
+rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
+  if (err) throw err
+  webpack(webpackConfig, (err, stats) => {
+    spinner.stop()
+    if (err) throw err
+    process.stdout.write(stats.toString({
+      colors: true,
+      modules: false,
+      children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
+      chunks: false,
+      chunkModules: false
+    }) + '\n\n')
+
+    if (stats.hasErrors()) {
+      console.log(chalk.red('  Build failed with errors.\n'))
+      process.exit(1)
+    }
+
+    console.log(chalk.cyan('  Build complete.\n'))
+    console.log(chalk.yellow(
+      '  Tip: built files are meant to be served over an HTTP server.\n' +
+      '  Opening index.html over file:// won\'t work.\n'
+    ))
+  })
+})

+ 54 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/build/check-versions.js

@@ -0,0 +1,54 @@
+'use strict'
+const chalk = require('chalk')
+const semver = require('semver')
+const packageConfig = require('../package.json')
+const shell = require('shelljs')
+
+function exec (cmd) {
+  return require('child_process').execSync(cmd).toString().trim()
+}
+
+const versionRequirements = [
+  {
+    name: 'node',
+    currentVersion: semver.clean(process.version),
+    versionRequirement: packageConfig.engines.node
+  }
+]
+
+if (shell.which('npm')) {
+  versionRequirements.push({
+    name: 'npm',
+    currentVersion: exec('npm --version'),
+    versionRequirement: packageConfig.engines.npm
+  })
+}
+
+module.exports = function () {
+  const warnings = []
+
+  for (let i = 0; i < versionRequirements.length; i++) {
+    const mod = versionRequirements[i]
+
+    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
+      warnings.push(mod.name + ': ' +
+        chalk.red(mod.currentVersion) + ' should be ' +
+        chalk.green(mod.versionRequirement)
+      )
+    }
+  }
+
+  if (warnings.length) {
+    console.log('')
+    console.log(chalk.yellow('To use this template, you must update following to modules:'))
+    console.log()
+
+    for (let i = 0; i < warnings.length; i++) {
+      const warning = warnings[i]
+      console.log('  ' + warning)
+    }
+
+    console.log()
+    process.exit(1)
+  }
+}

BIN
ubuntu/docker-ubuntu-vnc-desktop/web/build/logo.png


+ 101 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/build/utils.js

@@ -0,0 +1,101 @@
+'use strict'
+const path = require('path')
+const config = require('../config')
+const ExtractTextPlugin = require('extract-text-webpack-plugin')
+const packageConfig = require('../package.json')
+
+exports.assetsPath = function (_path) {
+  const assetsSubDirectory = process.env.NODE_ENV === 'production'
+    ? config.build.assetsSubDirectory
+    : config.dev.assetsSubDirectory
+
+  return path.posix.join(assetsSubDirectory, _path)
+}
+
+exports.cssLoaders = function (options) {
+  options = options || {}
+
+  const cssLoader = {
+    loader: 'css-loader',
+    options: {
+      sourceMap: options.sourceMap
+    }
+  }
+
+  const postcssLoader = {
+    loader: 'postcss-loader',
+    options: {
+      sourceMap: options.sourceMap
+    }
+  }
+
+  // generate loader string to be used with extract text plugin
+  function generateLoaders (loader, loaderOptions) {
+    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
+
+    if (loader) {
+      loaders.push({
+        loader: loader + '-loader',
+        options: Object.assign({}, loaderOptions, {
+          sourceMap: options.sourceMap
+        })
+      })
+    }
+
+    // Extract CSS when that option is specified
+    // (which is the case during production build)
+    if (options.extract) {
+      return ExtractTextPlugin.extract({
+        use: loaders,
+        fallback: 'vue-style-loader'
+      })
+    } else {
+      return ['vue-style-loader'].concat(loaders)
+    }
+  }
+
+  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
+  return {
+    css: generateLoaders(),
+    postcss: generateLoaders(),
+    less: generateLoaders('less'),
+    sass: generateLoaders('sass', { indentedSyntax: true }),
+    scss: generateLoaders('sass'),
+    stylus: generateLoaders('stylus'),
+    styl: generateLoaders('stylus')
+  }
+}
+
+// Generate loaders for standalone style files (outside of .vue)
+exports.styleLoaders = function (options) {
+  const output = []
+  const loaders = exports.cssLoaders(options)
+
+  for (const extension in loaders) {
+    const loader = loaders[extension]
+    output.push({
+      test: new RegExp('\\.' + extension + '$'),
+      use: loader
+    })
+  }
+
+  return output
+}
+
+exports.createNotifierCallback = () => {
+  const notifier = require('node-notifier')
+
+  return (severity, errors) => {
+    if (severity !== 'error') return
+
+    const error = errors[0]
+    const filename = error.file && error.file.split('!').pop()
+
+    notifier.notify({
+      title: packageConfig.name,
+      message: severity + ': ' + error.name,
+      subtitle: filename || '',
+      icon: path.join(__dirname, 'logo.png')
+    })
+  }
+}

+ 22 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/build/vue-loader.conf.js

@@ -0,0 +1,22 @@
+'use strict'
+const utils = require('./utils')
+const config = require('../config')
+const isProduction = process.env.NODE_ENV === 'production'
+const sourceMapEnabled = isProduction
+  ? config.build.productionSourceMap
+  : config.dev.cssSourceMap
+
+module.exports = {
+  loaders: utils.cssLoaders({
+    sourceMap: sourceMapEnabled,
+    extract: isProduction
+  }),
+  cssSourceMap: sourceMapEnabled,
+  cacheBusting: config.dev.cacheBusting,
+  transformToRequire: {
+    video: ['src', 'poster'],
+    source: 'src',
+    img: 'src',
+    image: 'xlink:href'
+  }
+}

+ 92 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/build/webpack.base.conf.js

@@ -0,0 +1,92 @@
+'use strict'
+const path = require('path')
+const utils = require('./utils')
+const config = require('../config')
+const vueLoaderConfig = require('./vue-loader.conf')
+
+function resolve (dir) {
+  return path.join(__dirname, '..', dir)
+}
+
+const createLintingRule = () => ({
+  test: /\.(js|vue)$/,
+  loader: 'eslint-loader',
+  enforce: 'pre',
+  include: [resolve('src'), resolve('test')],
+  options: {
+    formatter: require('eslint-friendly-formatter'),
+    emitWarning: !config.dev.showEslintErrorsInOverlay
+  }
+})
+
+module.exports = {
+  context: path.resolve(__dirname, '../'),
+  entry: {
+    app: './src/main.js'
+  },
+  output: {
+    path: config.build.assetsRoot,
+    filename: '[name].js',
+    publicPath: process.env.NODE_ENV === 'production'
+      ? config.build.assetsPublicPath
+      : config.dev.assetsPublicPath
+  },
+  resolve: {
+    extensions: ['.js', '.vue', '.json'],
+    alias: {
+      'vue$': 'vue/dist/vue.esm.js',
+      '@': resolve('src'),
+    }
+  },
+  module: {
+    rules: [
+      ...(config.dev.useEslint ? [createLintingRule()] : []),
+      {
+        test: /\.vue$/,
+        loader: 'vue-loader',
+        options: vueLoaderConfig
+      },
+      {
+        test: /\.js$/,
+        loader: 'babel-loader',
+        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
+      },
+      {
+        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
+        loader: 'url-loader',
+        options: {
+          limit: 10000,
+          name: utils.assetsPath('img/[name].[hash:7].[ext]')
+        }
+      },
+      {
+        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
+        loader: 'url-loader',
+        options: {
+          limit: 10000,
+          name: utils.assetsPath('media/[name].[hash:7].[ext]')
+        }
+      },
+      {
+        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
+        loader: 'url-loader',
+        options: {
+          limit: 10000,
+          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
+        }
+      }
+    ]
+  },
+  node: {
+    // prevent webpack from injecting useless setImmediate polyfill because Vue
+    // source contains it (although only uses it if it's native).
+    setImmediate: false,
+    // prevent webpack from injecting mocks to Node native modules
+    // that does not make sense for the client
+    dgram: 'empty',
+    fs: 'empty',
+    net: 'empty',
+    tls: 'empty',
+    child_process: 'empty'
+  }
+}

+ 95 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/build/webpack.dev.conf.js

@@ -0,0 +1,95 @@
+'use strict'
+const utils = require('./utils')
+const webpack = require('webpack')
+const config = require('../config')
+const merge = require('webpack-merge')
+const path = require('path')
+const baseWebpackConfig = require('./webpack.base.conf')
+const CopyWebpackPlugin = require('copy-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
+const portfinder = require('portfinder')
+
+const HOST = process.env.HOST
+const PORT = process.env.PORT && Number(process.env.PORT)
+
+const devWebpackConfig = merge(baseWebpackConfig, {
+  module: {
+    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
+  },
+  // cheap-module-eval-source-map is faster for development
+  devtool: config.dev.devtool,
+
+  // these devServer options should be customized in /config/index.js
+  devServer: {
+    clientLogLevel: 'warning',
+    historyApiFallback: {
+      rewrites: [
+        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
+      ],
+    },
+    hot: true,
+    contentBase: false, // since we use CopyWebpackPlugin.
+    compress: true,
+    host: HOST || config.dev.host,
+    port: PORT || config.dev.port,
+    open: config.dev.autoOpenBrowser,
+    overlay: config.dev.errorOverlay
+      ? { warnings: false, errors: true }
+      : false,
+    publicPath: config.dev.assetsPublicPath,
+    proxy: config.dev.proxyTable,
+    quiet: true, // necessary for FriendlyErrorsPlugin
+    watchOptions: {
+      poll: config.dev.poll,
+    }
+  },
+  plugins: [
+    new webpack.DefinePlugin({
+      'process.env': require('../config/dev.env')
+    }),
+    new webpack.HotModuleReplacementPlugin(),
+    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
+    new webpack.NoEmitOnErrorsPlugin(),
+    // https://github.com/ampedandwired/html-webpack-plugin
+    new HtmlWebpackPlugin({
+      filename: 'index.html',
+      template: 'index.html',
+      inject: true
+    }),
+    // copy custom static assets
+    new CopyWebpackPlugin([
+      {
+        from: path.resolve(__dirname, '../static'),
+        to: config.dev.assetsSubDirectory,
+        ignore: ['.*']
+      }
+    ])
+  ]
+})
+
+module.exports = new Promise((resolve, reject) => {
+  portfinder.basePort = process.env.PORT || config.dev.port
+  portfinder.getPort((err, port) => {
+    if (err) {
+      reject(err)
+    } else {
+      // publish the new Port, necessary for e2e tests
+      process.env.PORT = port
+      // add port to devServer config
+      devWebpackConfig.devServer.port = port
+
+      // Add FriendlyErrorsPlugin
+      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
+        compilationSuccessInfo: {
+          messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
+        },
+        onErrors: config.dev.notifyOnErrors
+        ? utils.createNotifierCallback()
+        : undefined
+      }))
+
+      resolve(devWebpackConfig)
+    }
+  })
+})

+ 149 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/build/webpack.prod.conf.js

@@ -0,0 +1,149 @@
+'use strict'
+const path = require('path')
+const utils = require('./utils')
+const webpack = require('webpack')
+const config = require('../config')
+const merge = require('webpack-merge')
+const baseWebpackConfig = require('./webpack.base.conf')
+const CopyWebpackPlugin = require('copy-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const ExtractTextPlugin = require('extract-text-webpack-plugin')
+const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
+const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
+
+const env = process.env.NODE_ENV === 'testing'
+  ? require('../config/test.env')
+  : require('../config/prod.env')
+
+const webpackConfig = merge(baseWebpackConfig, {
+  module: {
+    rules: utils.styleLoaders({
+      sourceMap: config.build.productionSourceMap,
+      extract: true,
+      usePostCSS: true
+    })
+  },
+  devtool: config.build.productionSourceMap ? config.build.devtool : false,
+  output: {
+    path: config.build.assetsRoot,
+    filename: utils.assetsPath('js/[name].[chunkhash].js'),
+    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
+  },
+  plugins: [
+    // http://vuejs.github.io/vue-loader/en/workflow/production.html
+    new webpack.DefinePlugin({
+      'process.env': env
+    }),
+    new UglifyJsPlugin({
+      uglifyOptions: {
+        compress: {
+          warnings: false
+        }
+      },
+      sourceMap: config.build.productionSourceMap,
+      parallel: true
+    }),
+    // extract css into its own file
+    new ExtractTextPlugin({
+      filename: utils.assetsPath('css/[name].[contenthash].css'),
+      // Setting the following option to `false` will not extract CSS from codesplit chunks.
+      // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
+      // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 
+      // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
+      allChunks: true,
+    }),
+    // Compress extracted CSS. We are using this plugin so that possible
+    // duplicated CSS from different components can be deduped.
+    new OptimizeCSSPlugin({
+      cssProcessorOptions: config.build.productionSourceMap
+        ? { safe: true, map: { inline: false } }
+        : { safe: true }
+    }),
+    // generate dist index.html with correct asset hash for caching.
+    // you can customize output by editing /index.html
+    // see https://github.com/ampedandwired/html-webpack-plugin
+    new HtmlWebpackPlugin({
+      filename: process.env.NODE_ENV === 'testing'
+        ? 'index.html'
+        : config.build.index,
+      template: 'index.html',
+      inject: true,
+      minify: {
+        removeComments: true,
+        collapseWhitespace: true,
+        removeAttributeQuotes: true
+        // more options:
+        // https://github.com/kangax/html-minifier#options-quick-reference
+      },
+      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
+      chunksSortMode: 'dependency'
+    }),
+    // keep module.id stable when vendor modules does not change
+    new webpack.HashedModuleIdsPlugin(),
+    // enable scope hoisting
+    new webpack.optimize.ModuleConcatenationPlugin(),
+    // split vendor js into its own file
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'vendor',
+      minChunks (module) {
+        // any required modules inside node_modules are extracted to vendor
+        return (
+          module.resource &&
+          /\.js$/.test(module.resource) &&
+          module.resource.indexOf(
+            path.join(__dirname, '../node_modules')
+          ) === 0
+        )
+      }
+    }),
+    // extract webpack runtime and module manifest to its own file in order to
+    // prevent vendor hash from being updated whenever app bundle is updated
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'manifest',
+      minChunks: Infinity
+    }),
+    // This instance extracts shared chunks from code splitted chunks and bundles them
+    // in a separate chunk, similar to the vendor chunk
+    // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'app',
+      async: 'vendor-async',
+      children: true,
+      minChunks: 3
+    }),
+
+    // copy custom static assets
+    new CopyWebpackPlugin([
+      {
+        from: path.resolve(__dirname, '../static'),
+        to: config.build.assetsSubDirectory,
+        ignore: ['.*']
+      }
+    ])
+  ]
+})
+
+if (config.build.productionGzip) {
+  const CompressionWebpackPlugin = require('compression-webpack-plugin')
+
+  webpackConfig.plugins.push(
+    new CompressionWebpackPlugin({
+      asset: '[path].gz[query]',
+      algorithm: 'gzip',
+      test: new RegExp(
+        '\\.(' +
+        config.build.productionGzipExtensions.join('|') +
+        ')$'
+      ),
+      threshold: 10240,
+      minRatio: 0.8
+    })
+  )
+}
+
+if (config.build.bundleAnalyzerReport) {
+  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
+  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
+}
+
+module.exports = webpackConfig

+ 8 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/config/dev.env.js

@@ -0,0 +1,8 @@
+'use strict'
+const merge = require('webpack-merge')
+const prodEnv = require('./prod.env')
+
+module.exports = merge(prodEnv, {
+  NODE_ENV: '"development"',
+  PREFIX_PATH: `"${process.env.PREFIX_PATH}"`
+})

+ 89 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/config/index.js

@@ -0,0 +1,89 @@
+'use strict'
+// Template version: 1.3.1
+// see http://vuejs-templates.github.io/webpack for documentation.
+
+const path = require('path')
+const BACKEND = process.env.BACKEND || 'http://127.0.0.1:6080'
+
+module.exports = {
+  dev: {
+
+    // Paths
+    assetsSubDirectory: 'static',
+    assetsPublicPath: './',
+    proxyTable: {
+      '/api': {
+        target: BACKEND,
+        changeOrigin: true,
+        secure: false
+      },
+      '/websockify': {
+        target: BACKEND,
+        // logLevel: 'debug',
+        ws: true,
+        changeOrigin: true
+      }
+    },
+
+    // Various Dev Server settings
+    host: 'localhost', // can be overwritten by process.env.HOST
+    port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
+    autoOpenBrowser: false,
+    errorOverlay: true,
+    notifyOnErrors: true,
+    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
+
+    // Use Eslint Loader?
+    // If true, your code will be linted during bundling and
+    // linting errors and warnings will be shown in the console.
+    useEslint: true,
+    // If true, eslint errors and warnings will also be shown in the error overlay
+    // in the browser.
+    showEslintErrorsInOverlay: false,
+
+    /**
+     * Source Maps
+     */
+
+    // https://webpack.js.org/configuration/devtool/#development
+    devtool: 'cheap-module-eval-source-map',
+
+    // If you have problems debugging vue-files in devtools,
+    // set this to false - it *may* help
+    // https://vue-loader.vuejs.org/en/options.html#cachebusting
+    cacheBusting: true,
+
+    cssSourceMap: true
+  },
+
+  build: {
+    // Template for index.html
+    index: path.resolve(__dirname, '../dist/index.html'),
+
+    // Paths
+    assetsRoot: path.resolve(__dirname, '../dist'),
+    assetsSubDirectory: 'static',
+    assetsPublicPath: './',
+
+    /**
+     * Source Maps
+     */
+
+    productionSourceMap: true,
+    // https://webpack.js.org/configuration/devtool/#production
+    devtool: '#source-map',
+
+    // Gzip off by default as many popular static hosts such as
+    // Surge or Netlify already gzip all static assets for you.
+    // Before setting to `true`, make sure to:
+    // npm install --save-dev compression-webpack-plugin
+    productionGzip: false,
+    productionGzipExtensions: ['js', 'css'],
+
+    // Run the build command with an extra argument to
+    // View the bundle analyzer report after build finishes:
+    // `npm run build --report`
+    // Set to `true` or `false` to always turn it on or off
+    bundleAnalyzerReport: process.env.npm_config_report
+  }
+}

+ 5 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/config/prod.env.js

@@ -0,0 +1,5 @@
+'use strict'
+module.exports = {
+  NODE_ENV: '"production"',
+  PREFIX_PATH: `"${process.env.PREFIX_PATH}"`
+}

+ 7 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/config/test.env.js

@@ -0,0 +1,7 @@
+'use strict'
+const merge = require('webpack-merge')
+const devEnv = require('./dev.env')
+
+module.exports = merge(devEnv, {
+  NODE_ENV: '"testing"'
+})

+ 12 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/index.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <title>novnc2</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 13 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/novnc-armhf-1.patch

@@ -0,0 +1,13 @@
+--- core/rfb.js	2019-02-24 21:17:04.402944311 +0800
++++ core/rfb.js	2019-02-24 21:17:34.782484107 +0800
+@@ -1256,8 +1256,8 @@
+         encs.push(encodings.encodingCopyRect);
+         // Only supported with full depth support
+         if (this._fb_depth == 24) {
+-            encs.push(encodings.encodingTight);
+-            encs.push(encodings.encodingTightPNG);
++            // encs.push(encodings.encodingTight);
++            // encs.push(encodings.encodingTightPNG);
+             encs.push(encodings.encodingHextile);
+             encs.push(encodings.encodingRRE);
+         }

+ 83 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/package.json

@@ -0,0 +1,83 @@
+{
+  "name": "novnc2",
+  "version": "1.0.0",
+  "description": "extended novnc",
+  "author": "Doro Wu <fcwu.tw@gmail.com>",
+  "private": true,
+  "scripts": {
+    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
+    "start": "npm run dev",
+    "e2e": "node test/e2e/runner.js",
+    "test": "npm run unit && npm run e2e",
+    "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs",
+    "build": "node build/build.js"
+  },
+  "dependencies": {
+    "axios": "^0.21.1",
+    "vue": "^2.5.2",
+    "vue-material": "^1.0.0-beta-10.2",
+    "vue-router": "^3.0.1"
+  },
+  "devDependencies": {
+    "autoprefixer": "^7.1.2",
+    "babel-core": "^6.22.1",
+    "babel-eslint": "^8.2.1",
+    "babel-helper-vue-jsx-merge-props": "^2.0.3",
+    "babel-loader": "^7.1.1",
+    "babel-plugin-syntax-jsx": "^6.18.0",
+    "babel-plugin-transform-runtime": "^6.22.0",
+    "babel-plugin-transform-vue-jsx": "^3.5.0",
+    "babel-preset-env": "^1.3.2",
+    "babel-preset-stage-2": "^6.22.0",
+    "babel-register": "^6.22.0",
+    "chalk": "^2.0.1",
+    "chromedriver": "^2.27.2",
+    "copy-webpack-plugin": "^4.0.1",
+    "cross-spawn": "^5.0.1",
+    "css-loader": "^0.28.0",
+    "eslint": "^4.15.0",
+    "eslint-config-standard": "^10.2.1",
+    "eslint-friendly-formatter": "^3.0.0",
+    "eslint-loader": "^1.7.1",
+    "eslint-plugin-import": "^2.7.0",
+    "eslint-plugin-node": "^5.2.0",
+    "eslint-plugin-promise": "^3.4.0",
+    "eslint-plugin-standard": "^3.0.1",
+    "eslint-plugin-vue": "^4.0.0",
+    "extract-text-webpack-plugin": "^3.0.0",
+    "file-loader": "^1.1.4",
+    "friendly-errors-webpack-plugin": "^1.6.1",
+    "html-webpack-plugin": "^2.30.1",
+    "nightwatch": "^0.9.12",
+    "node-notifier": "^8.0.1",
+    "optimize-css-assets-webpack-plugin": "^3.2.0",
+    "ora": "^1.2.0",
+    "portfinder": "^1.0.13",
+    "postcss-import": "^11.0.0",
+    "postcss-loader": "^2.0.8",
+    "postcss-url": "^7.2.1",
+    "rimraf": "^2.6.0",
+    "selenium-server": "^3.0.1",
+    "semver": "^5.3.0",
+    "shelljs": "^0.7.6",
+    "uglifyjs-webpack-plugin": "^1.1.1",
+    "url-loader": "^0.5.8",
+    "vue-loader": "^13.3.0",
+    "vue-style-loader": "^3.0.1",
+    "vue-template-compiler": "^2.5.2",
+    "webpack": "^3.6.0",
+    "webpack-bundle-analyzer": "^2.9.0",
+    "webpack-cli": "^3.3.7",
+    "webpack-dev-server": "^3.2.1",
+    "webpack-merge": "^4.1.0"
+  },
+  "engines": {
+    "node": ">= 8.0.0",
+    "npm": ">= 3.0.0"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not ie <= 8"
+  ]
+}

+ 22 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/src/App.vue

@@ -0,0 +1,22 @@
+<template>
+  <div id="app">
+    <router-view/>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'App'
+}
+</script>
+
+<style>
+#app {
+  font-family: 'Avenir', Helvetica, Arial, sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  text-align: center;
+  color: #2c3e50;
+  margin-top: 60px;
+}
+</style>

+ 217 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/src/components/Vnc.vue

@@ -0,0 +1,217 @@
+<template>
+  <div style="width: 100%; height: 100%; background-color: #000;">
+    <iframe id="videoFrame" ref="videoFrame" class="frame" frameBorder="0" v-show="config.mode === 'video'" scrolling="no"></iframe>
+    <iframe id="vncFrame" ref="vncFrame" class="frame" v-bind:class="{hiddenvnc: config.mode === 'video'}" frameBorder="0" v-show="true" scrolling="no"></iframe>
+  </div>
+</template>
+
+<script>
+
+export default {
+  name: 'Vnc',
+  components: {
+  },
+  data () {
+    return {
+      // stopped -> connected -> disconnected
+      vncState: 'stopped',
+      videoState: 'stopped',
+      // toolbar
+      config: {
+        mode: 'vnc'
+      },
+      stateID: -1,
+      // retry
+      errorMessage: '',
+      // vnc canvas size
+      width: 0,
+      height: 0,
+      //
+      videoCurrentTime: 0,
+      stateErrorCount: 0,
+      timerState: null
+    }
+  },
+  created: function () {
+    window.addEventListener('message', this.onMessage)
+    if ('video' in this.$route.query) {
+      this.config.mode = 'video'
+    }
+  },
+  mounted: function () {
+    this.update_status()
+  },
+  beforeDestroy: function () {
+    clearTimeout(this.timerState)
+    window.removeEventListener('message', this.onMessage)
+  },
+  methods: {
+    update_status: async function () {
+      const w = this.$refs.vncFrame.clientWidth
+      const h = this.$refs.vncFrame.clientHeight
+      const params = {
+        'video': this.config.mode === 'video',
+        'id': this.stateID,
+        'w': w,
+        'h': h
+      }
+      try {
+        const response = await this.$http.get('api/state', {params: params})
+        const body = response.data
+        if (body.code !== 200) {
+          this.stateErrorCount += 1
+          if (this.stateErrorCount > 10) {
+            this.errorMessage = this.$t('serviceIsUnavailable')
+            throw this.errorMessage
+          }
+        }
+
+        // long polling
+        this.stateID = body.data.id
+
+        // adaptive resolution
+        if (!body.data.config.fixedResolution && body.data.config.sizeChangedCount === 0) {
+          const response = await this.$http.get('api/reset', {params: params})
+          const body = response.data
+          if (body.code !== 200) {
+            this.stateErrorCount += 1
+            if (this.stateErrorCount > 10) {
+              this.errorMessage = this.$t('serviceIsUnavailable')
+              throw this.errorMessage
+            }
+          }
+        }
+
+        if (this.vncState === 'stopped') {
+          this.reconnect(false)
+        }
+
+        // video
+        // try {
+        //   let flvPlayer = this.$refs.videoFrame.contentWindow.flvPlayer
+        //   let readyState = 0
+        //   readyState = flvPlayer._mediaElement.readyState
+        //   if (readyState >= 3) {
+        //     this.videoState = 'running'
+        //     if (this.videoCurrentTime !== flvPlayer.currentTime * 1000) {
+        //       // playing
+        //       let diff = (flvPlayer._mediaElement.buffered.end(0) - flvPlayer.currentTime) * 1000
+        //       // console.log('player diff=' + diff)
+        //       if (diff >= 2000) {
+        //         // seek to nearest
+        //         console.log('seek to nearest')
+        //         flvPlayer._mediaElement.currentTime = flvPlayer._mediaElement.buffered.end(0)
+        //       }
+        //       this.videoCurrentTime = flvPlayer.currentTime * 1000
+        //     } else {
+        //       // stall
+        //       console.log('stall, restart')
+        //       this.videoState = 'stopped'
+        //     }
+        //   }
+        // } catch (e) {
+        //   // mediaElement TypeError
+        // }
+
+        this.schedule_next_update_status()
+      } catch (error) {
+        this.stateErrorCount += 1
+        if (this.stateErrorCount > 10) {
+          this.errorMessage = this.$t('serviceIsUnavailable')
+        } else {
+          this.schedule_next_update_status()
+        }
+      }
+    },
+    schedule_next_update_status: function (afterMseconds = 1000) {
+      if (this.timerState !== null) {
+        return
+      }
+      this.timerState = setTimeout(() => {
+        this.timerState = null
+        this.update_status()
+      }, afterMseconds)
+    },
+    reconnect: function (force = false) {
+      // console.trace()
+      console.log(`connecting...`)
+      this.errorMessage = ''
+      let websockifyPath = location.pathname.substr(1) + 'websockify'
+      if (force || this.vncState === 'stopped') {
+        this.vncState = 'connecting'
+        let hostname = window.location.hostname
+        let port = window.location.port
+        if (!port) {
+          port = window.location.protocol[4] === 's' ? 443 : 80
+        }
+        let url = 'static/vnc.html?'
+        url += 'autoconnect=1&'
+        url += `host=${hostname}&port=${port}&`
+        url += `path=${websockifyPath}&title=novnc2&`
+        url += `logging=warn`
+        this.$refs.vncFrame.setAttribute('src', url)
+      }
+      if (this.config.mode === 'video') {
+        if (force || this.videoState === 'stopped') {
+          const w = this.$refs.vncFrame.clientWidth
+          const h = this.$refs.vncFrame.clientHeight
+          let url = `static/video.html?width=${w}&height=${h}&base=${window.location.host}`
+          this.$refs.videoFrame.setAttribute('src', url)
+          this.videoState = 'connecting'
+        }
+      } else {
+        if (this.videoState !== 'stopped') {
+          this.$refs.videoFrame.setAttribute('src', '')
+          this.videoState = 'stopped'
+        }
+      }
+    },
+    onMessage: function (message) {
+      try {
+        let data = JSON.parse(message.data)
+        if (data.from === 'flvjs') {
+          if (data.type === 'event') {
+            console.log(data.eventName)
+            if (data.eventName === 'onSourceOpen') {
+              this.videoState = 'running'
+            } else if (data.eventName === 'onSourceEnded') {
+            } else if (data.eventName === 'onSourceClose') {
+              this.videoState = 'stopped'
+            }
+          }
+        }
+        if (data.from === 'novnc') {
+          if (data.state) {
+            this.vncState = data.state
+          }
+        }
+      } catch (exc) {
+        // SyntaxError if JSON pasrse error
+      }
+    }
+  },
+  computed: {
+  },
+  watch: {
+  }
+}
+</script>
+
+<style scoped>
+body {
+    margin: 0px;
+}
+
+iframe {
+    border-width: 0px;
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    left: 0px;
+    top: 0px;
+}
+
+.hiddenvnc {
+  opacity: 0;
+}
+</style>

+ 17 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/src/main.js

@@ -0,0 +1,17 @@
+// The Vue build version to load with the `import` command
+// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
+import Vue from 'vue'
+import App from './App'
+import router from './router'
+import axios from 'axios'
+
+Vue.config.productionTip = false
+Vue.prototype.$http = axios
+
+/* eslint-disable no-new */
+new Vue({
+  el: '#app',
+  router,
+  components: { App },
+  template: '<App/>'
+})

+ 17 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/src/router/index.js

@@ -0,0 +1,17 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+import Vnc from '@/components/Vnc'
+
+Vue.use(Router)
+
+export default new Router({
+  mode: 'history',
+  base: window.location.pathname,
+  routes: [
+    {
+      path: '/',
+      name: 'Vnc',
+      component: Vnc
+    }
+  ]
+})

+ 13 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/AUTHORS

@@ -0,0 +1,13 @@
+maintainers:
+- Joel Martin (@kanaka)
+- Solly Ross (@directxman12)
+- Samuel Mannehed for Cendio AB (@samhed)
+- Pierre Ossman for Cendio AB (@CendioOssman)
+maintainersEmeritus:
+- @astrand 
+contributors:
+# There are a bunch of people that should be here.
+# If you want to be on this list, feel free send a PR
+# to add yourself.
+- jalf <git@jalf.dk>
+- NTT corp.

+ 62 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/LICENSE.txt

@@ -0,0 +1,62 @@
+noVNC is Copyright (C) 2019 The noVNC Authors
+(./AUTHORS)
+
+The noVNC core library files are licensed under the MPL 2.0 (Mozilla
+Public License 2.0). The noVNC core library is composed of the
+Javascript code necessary for full noVNC operation. This includes (but
+is not limited to):
+
+    core/**/*.js
+    app/*.js
+    test/playback.js
+
+The HTML, CSS, font and images files that included with the noVNC
+source distibution (or repository) are not considered part of the
+noVNC core library and are licensed under more permissive licenses.
+The intent is to allow easy integration of noVNC into existing web
+sites and web applications.
+
+The HTML, CSS, font and image files are licensed as follows:
+
+    *.html                     : 2-Clause BSD license
+
+    app/styles/*.css           : 2-Clause BSD license
+
+    app/styles/Orbitron*       : SIL Open Font License 1.1
+                                 (Copyright 2009 Matt McInerney)
+
+    app/images/                : Creative Commons Attribution-ShareAlike
+                                 http://creativecommons.org/licenses/by-sa/3.0/
+
+Some portions of noVNC are copyright to their individual authors.
+Please refer to the individual source files and/or to the noVNC commit
+history: https://github.com/novnc/noVNC/commits/master
+
+The are several files and projects that have been incorporated into
+the noVNC core library. Here is a list of those files and the original
+licenses (all MPL 2.0 compatible):
+
+    core/base64.js          : MPL 2.0
+
+    core/des.js             : Various BSD style licenses
+
+    vendor/pako/            : MIT
+
+Any other files not mentioned above are typically marked with
+a copyright/license header at the top of the file. The default noVNC
+license is MPL-2.0.
+
+The following license texts are included:
+
+    docs/LICENSE.MPL-2.0
+    docs/LICENSE.OFL-1.1
+    docs/LICENSE.BSD-3-Clause (New BSD)
+    docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD)
+    vendor/pako/LICENSE (MIT)
+
+Or alternatively the license texts may be found here:
+
+    http://www.mozilla.org/MPL/2.0/
+    http://scripts.sil.org/OFL
+    http://en.wikipedia.org/wiki/BSD_licenses
+    https://opensource.org/licenses/MIT

+ 214 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/README.md

@@ -0,0 +1,214 @@
+## noVNC: HTML VNC Client Library and Application
+
+[![Test Status](https://github.com/novnc/noVNC/workflows/Test/badge.svg)](https://github.com/novnc/noVNC/actions?query=workflow%3ATest)
+[![Lint Status](https://github.com/novnc/noVNC/workflows/Lint/badge.svg)](https://github.com/novnc/noVNC/actions?query=workflow%3ALint)
+
+### Description
+
+noVNC is both a HTML VNC client JavaScript library and an application built on
+top of that library. noVNC runs well in any modern browser including mobile
+browsers (iOS and Android).
+
+Many companies, projects and products have integrated noVNC including
+[OpenStack](http://www.openstack.org),
+[OpenNebula](http://opennebula.org/),
+[LibVNCServer](http://libvncserver.sourceforge.net), and
+[ThinLinc](https://cendio.com/thinlinc). See
+[the Projects and Companies wiki page](https://github.com/novnc/noVNC/wiki/Projects-and-companies-using-noVNC)
+for a more complete list with additional info and links.
+
+### Table of Contents
+
+- [News/help/contact](#newshelpcontact)
+- [Features](#features)
+- [Screenshots](#screenshots)
+- [Browser Requirements](#browser-requirements)
+- [Server Requirements](#server-requirements)
+- [Quick Start](#quick-start)
+- [Installation from Snap Package](#installation-from-snap-package)
+- [Integration and Deployment](#integration-and-deployment)
+- [Authors/Contributors](#authorscontributors)
+
+### News/help/contact
+
+The project website is found at [novnc.com](http://novnc.com).
+Notable commits, announcements and news are posted to
+[@noVNC](http://www.twitter.com/noVNC).
+
+If you are a noVNC developer/integrator/user (or want to be) please join the
+[noVNC discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc).
+
+Bugs and feature requests can be submitted via
+[github issues](https://github.com/novnc/noVNC/issues). If you have questions
+about using noVNC then please first use the
+[discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc).
+We also have a [wiki](https://github.com/novnc/noVNC/wiki/) with lots of
+helpful information.
+
+If you are looking for a place to start contributing to noVNC, a good place to
+start would be the issues that are marked as
+["patchwelcome"](https://github.com/novnc/noVNC/issues?labels=patchwelcome).
+Please check our
+[contribution guide](https://github.com/novnc/noVNC/wiki/Contributing) though.
+
+If you want to show appreciation for noVNC you could donate to a great non-
+profits such as:
+[Compassion International](http://www.compassion.com/),
+[SIL](http://www.sil.org),
+[Habitat for Humanity](http://www.habitat.org),
+[Electronic Frontier Foundation](https://www.eff.org/),
+[Against Malaria Foundation](http://www.againstmalaria.com/),
+[Nothing But Nets](http://www.nothingbutnets.net/), etc.
+Please tweet [@noVNC](http://www.twitter.com/noVNC) if you do.
+
+
+### Features
+
+* Supports all modern browsers including mobile (iOS, Android)
+* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG
+* Supports scaling, clipping and resizing the desktop
+* Local cursor rendering
+* Clipboard copy/paste
+* Translations
+* Touch gestures for emulating common mouse actions
+* Licensed mainly under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/), see
+  [the license document](LICENSE.txt) for details
+
+### Screenshots
+
+Running in Firefox before and after connecting:
+
+<img src="http://novnc.com/img/noVNC-1-login.png" width=400>&nbsp;
+<img src="http://novnc.com/img/noVNC-3-connected.png" width=400>
+
+See more screenshots
+[here](http://novnc.com/screenshots.html).
+
+
+### Browser Requirements
+
+noVNC uses many modern web technologies so a formal requirement list is
+not available. However these are the minimum versions we are currently
+aware of:
+
+* Chrome 64, Firefox 79, Safari 13.4, Opera 51, Edge 79
+
+
+### Server Requirements
+
+noVNC follows the standard VNC protocol, but unlike other VNC clients it does
+require WebSockets support. Many servers include support (e.g.
+[x11vnc/libvncserver](http://libvncserver.sourceforge.net/),
+[QEMU](http://www.qemu.org/), and
+[MobileVNC](http://www.smartlab.at/mobilevnc/)), but for the others you need to
+use a WebSockets to TCP socket proxy. noVNC has a sister project
+[websockify](https://github.com/novnc/websockify) that provides a simple such
+proxy.
+
+
+### Quick Start
+
+* Use the `novnc_proxy` script to automatically download and start websockify, which
+  includes a mini-webserver and the WebSockets proxy. The `--vnc` option is
+  used to specify the location of a running VNC server:
+
+    `./utils/novnc_proxy --vnc localhost:5901`
+
+* Point your browser to the cut-and-paste URL that is output by the `novnc_proxy`
+  script. Hit the Connect button, enter a password if the VNC server has one
+  configured, and enjoy!
+
+### Installation from Snap Package
+Running the command below will install the latest release of noVNC from Snap:
+
+`sudo snap install novnc`
+
+#### Running noVNC
+
+You can run the Snap-package installed novnc directly with, for example:
+
+`novnc --listen 6081 --vnc localhost:5901 # /snap/bin/novnc if /snap/bin is not in your PATH`
+
+#### Running as a Service (Daemon)
+The Snap package also has the capability to run a 'novnc' service which can be 
+configured to listen on multiple ports connecting to multiple VNC servers 
+(effectively a service runing multiple instances of novnc).
+Instructions (with example values):
+
+List current services (out-of-box this will be blank):
+
+```
+sudo snap get novnc services
+Key             Value
+services.n6080  {...}
+services.n6081  {...}
+```
+
+Create a new service that listens on port 6082 and connects to the VNC server 
+running on port 5902 on localhost:
+
+`sudo snap set novnc services.n6082.listen=6082 services.n6082.vnc=localhost:5902`
+
+(Any services you define with 'snap set' will be automatically started)
+Note that the name of the service, 'n6082' in this example, can be anything 
+as long as it doesn't start with a number or contain spaces/special characters.
+
+View the configuration of the service just created:
+
+```
+sudo snap get novnc services.n6082
+Key                    Value
+services.n6082.listen  6082
+services.n6082.vnc     localhost:5902
+```
+
+Disable a service (note that because of a limitation in  Snap it's currently not 
+possible to unset config variables, setting them to blank values is the way 
+to disable a service):
+
+`sudo snap set novnc services.n6082.listen='' services.n6082.vnc=''`
+
+(Any services you set to blank with 'snap set' like this will be automatically stopped)
+
+Verify that the service is disabled (blank values):
+
+```
+sudo snap get novnc services.n6082
+Key                    Value
+services.n6082.listen  
+services.n6082.vnc
+```
+
+### Integration and Deployment
+
+Please see our other documents for how to integrate noVNC in your own software,
+or deploying the noVNC application in production environments:
+
+* [Embedding](docs/EMBEDDING.md) - For the noVNC application
+* [Library](docs/LIBRARY.md) - For the noVNC JavaScript library
+
+
+### Authors/Contributors
+
+See [AUTHORS](AUTHORS) for a (full-ish) list of authors.  If you're not on
+that list and you think you should be, feel free to send a PR to fix that.
+
+* Core team:
+    * [Joel Martin](https://github.com/kanaka)
+    * [Samuel Mannehed](https://github.com/samhed) (Cendio)
+    * [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack)
+    * [Pierre Ossman](https://github.com/CendioOssman) (Cendio)
+
+* Notable contributions:
+    * UI and Icons : Pierre Ossman, Chris Gordon
+    * Original Logo : Michael Sersen
+    * tight encoding : Michael Tinglof (Mercuri.ca)
+
+* Included libraries:
+    * base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net)
+    * DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs)
+    * Pako : Vitaly Puzrin (https://github.com/nodeca/pako)
+
+Do you want to be on this list? Check out our
+[contribution guide](https://github.com/novnc/noVNC/wiki/Contributing) and
+start hacking!

+ 66 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/error-handler.js

@@ -0,0 +1,66 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2019 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+// NB: this should *not* be included as a module until we have
+// native support in the browsers, so that our error handler
+// can catch script-loading errors.
+
+// No ES6 can be used in this file since it's used for the translation
+/* eslint-disable prefer-arrow-callback */
+
+(function _scope() {
+    "use strict";
+
+    // Fallback for all uncought errors
+    function handleError(event, err) {
+        try {
+            const msg = document.getElementById('noVNC_fallback_errormsg');
+
+            // Only show the initial error
+            if (msg.hasChildNodes()) {
+                return false;
+            }
+
+            let div = document.createElement("div");
+            div.classList.add('noVNC_message');
+            div.appendChild(document.createTextNode(event.message));
+            msg.appendChild(div);
+
+            if (event.filename) {
+                div = document.createElement("div");
+                div.className = 'noVNC_location';
+                let text = event.filename;
+                if (event.lineno !== undefined) {
+                    text += ":" + event.lineno;
+                    if (event.colno !== undefined) {
+                        text += ":" + event.colno;
+                    }
+                }
+                div.appendChild(document.createTextNode(text));
+                msg.appendChild(div);
+            }
+
+            if (err && err.stack) {
+                div = document.createElement("div");
+                div.className = 'noVNC_stack';
+                div.appendChild(document.createTextNode(err.stack));
+                msg.appendChild(div);
+            }
+
+            document.getElementById('noVNC_fallback_error')
+                .classList.add("noVNC_open");
+        } catch (exc) {
+            document.write("noVNC encountered an error.");
+        }
+        // Don't return true since this would prevent the error
+        // from being printed to the browser console.
+        return false;
+    }
+    window.addEventListener('error', function onerror(evt) { handleError(evt, evt.error); });
+    window.addEventListener('unhandledrejection', function onreject(evt) { handleError(evt.reason, evt.reason); });
+})();

+ 92 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/alt.svg

@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="alt.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="16"
+     inkscape:cx="18.205425"
+     inkscape:cy="17.531398"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <g
+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:48px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       id="text5290">
+      <path
+         d="m 9.9560547,1042.3329 -2.9394531,0 -0.4638672,1.3281 -1.8896485,0 2.7001953,-7.29 2.241211,0 2.7001958,7.29 -1.889649,0 -0.4589843,-1.3281 z m -2.4707031,-1.3526 1.9970703,0 -0.9960938,-2.9003 -1.0009765,2.9003 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5340" />
+      <path
+         d="m 13.188477,1036.0634 1.748046,0 0,7.5976 -1.748046,0 0,-7.5976 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5342" />
+      <path
+         d="m 18.535156,1036.6395 0,1.5528 1.801758,0 0,1.25 -1.801758,0 0,2.3193 q 0,0.3809 0.151367,0.5176 0.151368,0.1318 0.600586,0.1318 l 0.898438,0 0,1.25 -1.499024,0 q -1.035156,0 -1.469726,-0.4297 -0.429688,-0.4345 -0.429688,-1.4697 l 0,-2.3193 -0.86914,0 0,-1.25 0.86914,0 0,-1.5528 1.748047,0 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5344" />
+    </g>
+  </g>
+</svg>

+ 106 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/clipboard.svg

@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="clipboard.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="15.366606"
+     inkscape:cy="16.42981"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <path
+       style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="M 9,6 6,6 C 5.4459889,6 5,6.4459889 5,7 l 0,13 c 0,0.554011 0.4459889,1 1,1 l 13,0 c 0.554011,0 1,-0.445989 1,-1 L 20,7 C 20,6.4459889 19.554011,6 19,6 l -3,0"
+       transform="translate(0,1027.3622)"
+       id="rect6083"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cssssssssc" />
+    <rect
+       style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6085"
+       width="7"
+       height="4"
+       x="9"
+       y="1031.3622"
+       ry="1.00002" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.50196081"
+       d="m 8.5071212,1038.8622 7.9999998,0"
+       id="path6087"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.50196081"
+       d="m 8.5071212,1041.8622 3.9999998,0"
+       id="path6089"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.50196081"
+       d="m 8.5071212,1044.8622 5.9999998,0"
+       id="path6091"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>

+ 96 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/connect.svg

@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="connect.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="37.14834"
+     inkscape:cy="1.9525926"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <g
+       id="g5103"
+       transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,-729.15757,315.8823)">
+      <path
+         sodipodi:nodetypes="cssssc"
+         inkscape:connector-curvature="0"
+         id="rect5096"
+         d="m 11,1040.3622 -5,0 c -1.108,0 -2,-0.892 -2,-2 l 0,-4 c 0,-1.108 0.892,-2 2,-2 l 5,0"
+         style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+      <path
+         style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+         d="m 14,1032.3622 5,0 c 1.108,0 2,0.892 2,2 l 0,4 c 0,1.108 -0.892,2 -2,2 l -5,0"
+         id="path5099"
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cssssc" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path5101"
+         d="m 9,1036.3622 7,0"
+         style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    </g>
+  </g>
+</svg>

+ 96 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/ctrl.svg

@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="ctrl.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="16"
+     inkscape:cx="18.205425"
+     inkscape:cy="17.531398"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <g
+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:48px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       id="text5290">
+      <path
+         d="m 9.1210938,1043.1898 q -0.5175782,0.2686 -1.0791016,0.4053 -0.5615235,0.1367 -1.171875,0.1367 -1.8212891,0 -2.8857422,-1.0156 -1.0644531,-1.0205 -1.0644531,-2.7637 0,-1.748 1.0644531,-2.7637 1.0644531,-1.0205 2.8857422,-1.0205 0.6103515,0 1.171875,0.1368 0.5615234,0.1367 1.0791016,0.4052 l 0,1.5088 q -0.522461,-0.3564 -1.0302735,-0.5224 -0.5078125,-0.1661 -1.0693359,-0.1661 -1.0058594,0 -1.5820313,0.6446 -0.5761719,0.6445 -0.5761719,1.7773 0,1.1279 0.5761719,1.7725 0.5761719,0.6445 1.5820313,0.6445 0.5615234,0 1.0693359,-0.166 0.5078125,-0.166 1.0302735,-0.5225 l 0,1.5088 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5370" />
+      <path
+         d="m 12.514648,1036.5687 0,1.5528 1.801758,0 0,1.25 -1.801758,0 0,2.3193 q 0,0.3809 0.151368,0.5176 0.151367,0.1318 0.600586,0.1318 l 0.898437,0 0,1.25 -1.499023,0 q -1.035157,0 -1.469727,-0.4297 -0.429687,-0.4345 -0.429687,-1.4697 l 0,-2.3193 -0.8691411,0 0,-1.25 0.8691411,0 0,-1.5528 1.748046,0 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5372" />
+      <path
+         d="m 19.453125,1039.6107 q -0.229492,-0.1074 -0.458984,-0.1562 -0.22461,-0.054 -0.454102,-0.054 -0.673828,0 -1.040039,0.4345 -0.361328,0.4297 -0.361328,1.2354 l 0,2.5195 -1.748047,0 0,-5.4687 1.748047,0 0,0.8984 q 0.336914,-0.5371 0.771484,-0.7813 0.439453,-0.249 1.049805,-0.249 0.08789,0 0.19043,0.01 0.102539,0 0.297851,0.029 l 0.0049,1.582 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5374" />
+      <path
+         d="m 20.332031,1035.9926 1.748047,0 0,7.5976 -1.748047,0 0,-7.5976 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5376" />
+    </g>
+  </g>
+</svg>

+ 100 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/ctrlaltdel.svg

@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="ctrlaltdel.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="8"
+     inkscape:cx="11.135667"
+     inkscape:cy="16.407428"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <rect
+       style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect5253"
+       width="5"
+       height="5.0000172"
+       x="16"
+       y="1031.3622"
+       ry="1.0000174" />
+    <rect
+       y="1043.3622"
+       x="4"
+       height="5.0000172"
+       width="5"
+       id="rect5255"
+       style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       ry="1.0000174" />
+    <rect
+       style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect5257"
+       width="5"
+       height="5.0000172"
+       x="13"
+       y="1043.3622"
+       ry="1.0000174" />
+  </g>
+</svg>

+ 94 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/disconnect.svg

@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="disconnect.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="16"
+     inkscape:cx="25.05707"
+     inkscape:cy="11.594858"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="false">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <g
+       id="g5171"
+       transform="translate(-24.062499,-6.15775e-4)">
+      <path
+         id="path5110"
+         transform="translate(0,1027.3622)"
+         d="m 39.744141,3.4960938 c -0.769923,0 -1.539607,0.2915468 -2.121094,0.8730468 l -2.566406,2.5664063 1.414062,1.4140625 2.566406,-2.5664063 c 0.403974,-0.404 1.010089,-0.404 1.414063,0 l 2.828125,2.828125 c 0.40398,0.4039 0.403907,1.0101621 0,1.4140629 l -2.566406,2.566406 1.414062,1.414062 2.566406,-2.566406 c 1.163041,-1.1629 1.162968,-3.0791874 0,-4.2421874 L 41.865234,4.3691406 C 41.283747,3.7876406 40.514063,3.4960937 39.744141,3.4960938 Z M 39.017578,9.015625 a 1.0001,1.0001 0 0 0 -0.6875,0.3027344 l -0.445312,0.4453125 1.414062,1.4140621 0.445313,-0.445312 A 1.0001,1.0001 0 0 0 39.017578,9.015625 Z m -6.363281,0.7070312 a 1.0001,1.0001 0 0 0 -0.6875,0.3027348 L 28.431641,13.5625 c -1.163042,1.163 -1.16297,3.079187 0,4.242188 l 2.828125,2.828124 c 1.162974,1.163101 3.079213,1.163101 4.242187,0 l 3.535156,-3.535156 a 1.0001,1.0001 0 1 0 -1.414062,-1.414062 l -3.535156,3.535156 c -0.403974,0.404 -1.010089,0.404 -1.414063,0 l -2.828125,-2.828125 c -0.403981,-0.404 -0.403908,-1.010162 0,-1.414063 l 3.535156,-3.537109 A 1.0001,1.0001 0 0 0 32.654297,9.7226562 Z m 3.109375,2.1621098 -2.382813,2.384765 a 1.0001,1.0001 0 1 0 1.414063,1.414063 l 2.382812,-2.384766 -1.414062,-1.414062 z"
+         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         inkscape:connector-curvature="0" />
+      <rect
+         transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,0,0)"
+         y="752.29541"
+         x="-712.31262"
+         height="18.000017"
+         width="3"
+         id="rect5116"
+         style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+    </g>
+  </g>
+</svg>

File diff suppressed because it is too large
+ 71 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/drag.svg


+ 81 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/error.svg

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="error.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="14.00357"
+     inkscape:cy="12.443398"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <path
+       style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="M 7 3 C 4.7839905 3 3 4.7839905 3 7 L 3 18 C 3 20.21601 4.7839905 22 7 22 L 18 22 C 20.21601 22 22 20.21601 22 18 L 22 7 C 22 4.7839905 20.21601 3 18 3 L 7 3 z M 7.6992188 6 A 1.6916875 1.6924297 0 0 1 8.9121094 6.5117188 L 12.5 10.101562 L 16.087891 6.5117188 A 1.6916875 1.6924297 0 0 1 17.251953 6 A 1.6916875 1.6924297 0 0 1 18.480469 8.90625 L 14.892578 12.496094 L 18.480469 16.085938 A 1.6916875 1.6924297 0 1 1 16.087891 18.478516 L 12.5 14.888672 L 8.9121094 18.478516 A 1.6916875 1.6924297 0 1 1 6.5214844 16.085938 L 10.109375 12.496094 L 6.5214844 8.90625 A 1.6916875 1.6924297 0 0 1 7.6992188 6 z "
+       transform="translate(0,1027.3622)"
+       id="rect4135" />
+  </g>
+</svg>

+ 92 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/esc.svg

@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="esc.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="16"
+     inkscape:cx="18.205425"
+     inkscape:cy="17.531398"
+     inkscape:document-units="px"
+     inkscape:current-layer="text5290"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <g
+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:48px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       id="text5290">
+      <path
+         d="m 3.9331055,1036.1464 5.0732422,0 0,1.4209 -3.1933594,0 0,1.3574 3.0029297,0 0,1.4209 -3.0029297,0 0,1.6699 3.3007812,0 0,1.4209 -5.180664,0 0,-7.29 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5314" />
+      <path
+         d="m 14.963379,1038.1385 0,1.3282 q -0.561524,-0.2344 -1.083984,-0.3516 -0.522461,-0.1172 -0.986329,-0.1172 -0.498046,0 -0.742187,0.127 -0.239258,0.122 -0.239258,0.3808 0,0.21 0.180664,0.3223 0.185547,0.1123 0.65918,0.166 l 0.307617,0.044 q 1.342773,0.1709 1.806641,0.5615 0.463867,0.3906 0.463867,1.2256 0,0.874 -0.644531,1.3134 -0.644532,0.4395 -1.923829,0.4395 -0.541992,0 -1.123046,-0.088 -0.576172,-0.083 -1.186524,-0.2539 l 0,-1.3281 q 0.522461,0.2539 1.069336,0.3808 0.551758,0.127 1.118164,0.127 0.512695,0 0.771485,-0.1416 0.258789,-0.1416 0.258789,-0.4199 0,-0.2344 -0.180664,-0.3467 -0.175782,-0.1172 -0.708008,-0.1807 l -0.307617,-0.039 q -1.166993,-0.1465 -1.635743,-0.542 -0.46875,-0.3955 -0.46875,-1.2012 0,-0.8691 0.595703,-1.2891 0.595704,-0.4199 1.826172,-0.4199 0.483399,0 1.015625,0.073 0.532227,0.073 1.157227,0.2294 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5316" />
+      <path
+         d="m 21.066895,1038.1385 0,1.4258 q -0.356446,-0.2441 -0.717774,-0.3613 -0.356445,-0.1172 -0.742187,-0.1172 -0.732422,0 -1.142579,0.4297 -0.405273,0.4248 -0.405273,1.1914 0,0.7666 0.405273,1.1963 0.410157,0.4248 1.142579,0.4248 0.410156,0 0.776367,-0.1221 0.371094,-0.122 0.683594,-0.3613 l 0,1.4307 q -0.410157,0.1513 -0.834961,0.2246 -0.419922,0.078 -0.844727,0.078 -1.479492,0 -2.314453,-0.7568 -0.834961,-0.7618 -0.834961,-2.1143 0,-1.3525 0.834961,-2.1094 0.834961,-0.7617 2.314453,-0.7617 0.429688,0 0.844727,0.078 0.419921,0.073 0.834961,0.2246 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5318" />
+    </g>
+  </g>
+</svg>

+ 69 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/expander.svg

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="9"
+   height="10"
+   viewBox="0 0 9 10"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="expander.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="45.254834"
+     inkscape:cx="9.8737281"
+     inkscape:cy="6.4583132"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     units="px"
+     inkscape:snap-object-midpoints="false"
+     inkscape:object-nodes="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1042.3622)">
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="M 2.0800781,1042.3633 A 2.0002,2.0002 0 0 0 0,1044.3613 l 0,6 a 2.0002,2.0002 0 0 0 3.0292969,1.7168 l 5,-3 a 2.0002,2.0002 0 0 0 0,-3.4316 l -5,-3 a 2.0002,2.0002 0 0 0 -0.9492188,-0.2832 z"
+       id="path4138"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>

+ 93 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/fullscreen.svg

@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="fullscreen.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="16.400723"
+     inkscape:cy="15.083758"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="false"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="false">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <rect
+       style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect5006"
+       width="17"
+       height="17.000017"
+       x="4"
+       y="1031.3622"
+       ry="3.0000174" />
+    <path
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+       d="m 7.5,1044.8622 4,0 -1.5,-1.5 1.5,-1.5 -1,-1 -1.5,1.5 -1.5,-1.5 0,4 z"
+       id="path5017"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path5025"
+       d="m 17.5,1034.8622 -4,0 1.5,1.5 -1.5,1.5 1,1 1.5,-1.5 1.5,1.5 0,-4 z"
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+  </g>
+</svg>

+ 82 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/handle.svg

@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="5"
+   height="6"
+   viewBox="0 0 5 6"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="handle.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="32"
+     inkscape:cx="1.3551778"
+     inkscape:cy="8.7800329"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="false"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1046.3622)">
+    <path
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 4.0000803,1049.3622 -3,-2 0,4 z"
+       id="path4247"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cccc" />
+  </g>
+</svg>

+ 172 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/handle_bg.svg

@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="15"
+   height="50"
+   viewBox="0 0 15 50"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="handle_bg.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="16"
+     inkscape:cx="-10.001409"
+     inkscape:cy="24.512566"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="false"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1002.3622)">
+    <rect
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect4249"
+       width="1"
+       height="1.0000174"
+       x="9.5"
+       y="1008.8622"
+       ry="1.7382812e-05" />
+    <rect
+       ry="1.7382812e-05"
+       y="1013.8622"
+       x="9.5"
+       height="1.0000174"
+       width="1"
+       id="rect4255"
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+    <rect
+       ry="1.7382812e-05"
+       y="1008.8622"
+       x="4.5"
+       height="1.0000174"
+       width="1"
+       id="rect4261"
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+    <rect
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect4263"
+       width="1"
+       height="1.0000174"
+       x="4.5"
+       y="1013.8622"
+       ry="1.7382812e-05" />
+    <rect
+       ry="1.7382812e-05"
+       y="1039.8622"
+       x="9.5"
+       height="1.0000174"
+       width="1"
+       id="rect4265"
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+    <rect
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect4267"
+       width="1"
+       height="1.0000174"
+       x="9.5"
+       y="1044.8622"
+       ry="1.7382812e-05" />
+    <rect
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect4269"
+       width="1"
+       height="1.0000174"
+       x="4.5"
+       y="1039.8622"
+       ry="1.7382812e-05" />
+    <rect
+       ry="1.7382812e-05"
+       y="1044.8622"
+       x="4.5"
+       height="1.0000174"
+       width="1"
+       id="rect4271"
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+    <rect
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect4273"
+       width="1"
+       height="1.0000174"
+       x="9.5"
+       y="1018.8622"
+       ry="1.7382812e-05" />
+    <rect
+       ry="1.7382812e-05"
+       y="1018.8622"
+       x="4.5"
+       height="1.0000174"
+       width="1"
+       id="rect4275"
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+    <rect
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect4277"
+       width="1"
+       height="1.0000174"
+       x="9.5"
+       y="1034.8622"
+       ry="1.7382812e-05" />
+    <rect
+       ry="1.7382812e-05"
+       y="1034.8622"
+       x="4.5"
+       height="1.0000174"
+       width="1"
+       id="rect4279"
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+  </g>
+</svg>

+ 42 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/Makefile

@@ -0,0 +1,42 @@
+ICONS := \
+	novnc-16x16.png \
+	novnc-24x24.png \
+	novnc-32x32.png \
+	novnc-48x48.png \
+	novnc-64x64.png
+
+ANDROID_LAUNCHER := \
+	novnc-48x48.png \
+	novnc-72x72.png \
+	novnc-96x96.png \
+	novnc-144x144.png \
+	novnc-192x192.png
+
+IPHONE_LAUNCHER := \
+	novnc-60x60.png \
+	novnc-120x120.png
+
+IPAD_LAUNCHER := \
+	novnc-76x76.png \
+	novnc-152x152.png
+
+ALL_ICONS := $(ICONS) $(ANDROID_LAUNCHER) $(IPHONE_LAUNCHER) $(IPAD_LAUNCHER)
+
+all: $(ALL_ICONS)
+
+novnc-16x16.png: novnc-icon-sm.svg
+	convert -density 90 \
+		-background transparent "$<" "$@"
+novnc-24x24.png: novnc-icon-sm.svg
+	convert -density 135 \
+		-background transparent "$<" "$@"
+novnc-32x32.png: novnc-icon-sm.svg
+	convert -density 180 \
+		-background transparent "$<" "$@"
+
+novnc-%.png: novnc-icon.svg
+	convert -density $$[`echo $* | cut -d x -f 1` * 90 / 48] \
+		-background transparent "$<" "$@"
+
+clean:
+	rm -f *.png

BIN
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-120x120.png


BIN
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-144x144.png


BIN
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-152x152.png


BIN
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-16x16.png


BIN
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-192x192.png


BIN
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-24x24.png


BIN
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-32x32.png


BIN
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-48x48.png


BIN
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-60x60.png


BIN
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-64x64.png


BIN
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-72x72.png


BIN
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-76x76.png


BIN
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-96x96.png


+ 163 - 0
ubuntu/docker-ubuntu-vnc-desktop/web/static/novnc/app/images/icons/novnc-icon-sm.svg

@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16"
+   height="16"
+   viewBox="0 0 16 16"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="novnc-icon-sm.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="45.254834"
+     inkscape:cx="9.722703"
+     inkscape:cy="5.5311896"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:object-nodes="true"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:snap-midpoints="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4169" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1036.3621)">
+    <rect
+       style="opacity:1;fill:#494949;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect4167"
+       width="16"
+       height="15.999992"
+       x="0"
+       y="1036.3622"
+       ry="2.6666584" />
+    <path
+       style="opacity:1;fill:#313131;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="M 2.6666667,1036.3621 C 1.1893373,1036.3621 0,1037.5515 0,1039.0288 l 0,10.6666 c 0,1.4774 1.1893373,2.6667 2.6666667,2.6667 l 4,0 C 11.837333,1052.3621 16,1046.7128 16,1039.6955 l 0,-0.6667 c 0,-1.4773 -1.189337,-2.6667 -2.666667,-2.6667 l -10.6666663,0 z"
+       id="rect4173"
+       inkscape:connector-curvature="0" />
+    <g
+       id="g4381">
+      <g
+         transform="translate(0.25,0.25)"
+         style="fill:#000000;fill-opacity:1"
+         id="g4365">
+        <g
+           style="fill:#000000;fill-opacity:1"
+           id="g4367">
+          <path
+             inkscape:connector-curvature="0"
+             id="path4369"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 4.3289754,1039.3621 c 0.1846149,0 0.3419956,0.071 0.4716623,0.2121 C 4.933546,1039.7121 5,1039.8793 5,1040.0759 l 0,3.2862 -1,0 0,-2.964 c 0,-0.024 -0.011592,-0.036 -0.034038,-0.036 l -1.931924,0 C 2.011349,1040.3621 2,1040.3741 2,1040.3981 l 0,2.964 -1,0 0,-4 z"
+             sodipodi:nodetypes="scsccsssscccs" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path4371"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 6.6710244,1039.3621 2.6579513,0 c 0.184775,0 0.3419957,0.071 0.471662,0.2121 C 9.933546,1039.7121 10,1039.8793 10,1040.0759 l 0,2.5724 c 0,0.1966 -0.066454,0.3655 -0.1993623,0.5069 -0.1296663,0.1379 -0.286887,0.2069 -0.471662,0.2069 l -2.6579513,0 c -0.184775,0 -0.3436164,-0.069 -0.4765247,-0.2069 C 6.0648334,1043.0138 6,1042.8449 6,1042.6483 l 0,-2.5724 c 0,-0.1966 0.064833,-0.3638 0.1944997,-0.5017 0.1329083,-0.1414 0.2917497,-0.2121 0.4765247,-0.2121 z m 2.2949386,1 -1.931926,0 C 7.011344,1040.3621 7,1040.3741 7,1040.3981 l 0,1.928 c 0,0.024 0.011347,0.036 0.034037,0.036 l 1.931926,0 c 0.02269,0 0.034037,-0.012 0.034037,-0.036 l 0,-1.928 c 0,-0.024 -0.011347,-0.036 -0.034037,-0.036 z"
+             sodipodi:nodetypes="sscsscsscsscssssssssss" />
+        </g>
+        <g
+           style="fill:#000000;fill-opacity:1"
+           id="g4373">
+          <path
+             inkscape:connector-curvature="0"
+             id="path4375"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 3,1047.1121 1,-2.75 1,0 -1.5,4 -1,0 -1.5,-4 1,0 z"
+             sodipodi:nodetypes="cccccccc" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path4377"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 9,1046.8621 0,-2.5 1,0 0,4 -1,0 -2,-2.5 0,2.5 -1,0 0,-4 1,0 z"
+             sodipodi:nodetypes="ccccccccccc" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path4379"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 15,1045.3621 -2.96596,0 c -0.02269,0 -0.03404,0.012 -0.03404,0.036 l 0,1.928 c 0,0.024 0.01135,0.036 0.03404,0.036 l 2.96596,0 0,1 -3.324113,0 c -0.188017,0 -0.348479,-0.068 -0.481388,-0.2037 C 11.064833,1048.0192 11,1047.8511 11,1047.6542 l 0,-2.5842 c 0,-0.1969 0.06483,-0.3633 0.194499,-0.4991 0.132909,-0.1392 0.293371,-0.2088 0.481388,-0.2088 l 3.324113,0 z"
+             sodipodi:nodetypes="cssssccscsscscc" />
+        </g>
+      </g>
+      <g
+         id="g4356">
+        <g
+           id="g4347">
+          <path
+             sodipodi:nodetypes="scsccsssscccs"
+             d="m 4.3289754,1039.3621 c 0.1846149,0 0.3419956,0.071 0.4716623,0.2121 C 4.933546,1039.7121 5,1039.8793 5,1040.0759 l 0,3.2862 -1,0 0,-2.964 c 0,-0.024 -0.011592,-0.036 -0.034038,-0.036 l -1.931924,0 c -0.022689,0 -0.034038,0.012 -0.034038,0.036 l 0,2.964 -1,0 0,-4 z"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             id="path4143"
+             inkscape:connector-curvature="0" />
+          <path
+             sodipodi:nodetypes="sscsscsscsscssssssssss"
+             d="m 6.6710244,1039.3621 2.6579513,0 c 0.184775,0 0.3419957,0.071 0.471662,0.2121 C 9.933546,1039.7121 10,1039.8793 10,1040.0759 l 0,2.5724 c 0,0.1966 -0.066454,0.3655 -0.1993623,0.5069 -0.1296663,0.1379 -0.286887,0.2069 -0.471662,0.2069 l -2.6579513,0 c -0.184775,0 -0.3436164,-0.069 -0.4765247,-0.2069 C 6.0648334,1043.0138 6,1042.8449 6,1042.6483 l 0,-2.5724 c 0,-0.1966 0.064833,-0.3638 0.1944997,-0.5017 0.1329083,-0.1414 0.2917497,-0.2121 0.4765247,-0.2121 z m 2.2949386,1 -1.931926,0 C 7.011344,1040.3621 7,1040.3741 7,1040.3981 l 0,1.928 c 0,0.024 0.011347,0.036 0.034037,0.036 l 1.931926,0 c 0.02269,0 0.034037,-0.012 0.034037,-0.036 l 0,-1.928 c 0,-0.024 -0.011347,-0.036 -0.034037,-0.036 z"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             id="path4145"
+             inkscape:connector-curvature="0" />
+        </g>
+        <g
+           id="g4351">
+          <path
+             sodipodi:nodetypes="cccccccc"
+             d="m 3,1047.1121 1,-2.75 1,0 -1.5,4 -1,0 -1.5,-4 1,0 z"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             id="path4147"
+             inkscape:connector-curvature="0" />
+          <path
+             sodipodi:nodetypes="ccccccccccc"
+             d="m 9,1046.8621 0,-2.5 1,0 0,4 -1,0 -2,-2.5 0,2.5 -1,0 0,-4 1,0 z"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             id="path4149"
+             inkscape:connector-curvature="0" />
+          <path
+             sodipodi:nodetypes="cssssccscsscscc"
+             d="m 15,1045.3621 -2.96596,0 c -0.02269,0 -0.03404,0.012 -0.03404,0.036 l 0,1.928 c 0,0.024 0.01135,0.036 0.03404,0.036 l 2.96596,0 0,1 -3.324113,0 c -0.188017,0 -0.348479,-0.068 -0.481388,-0.2037 C 11.064833,1048.0192 11,1047.8511 11,1047.6542 l 0,-2.5842 c 0,-0.1969 0.06483,-0.3633 0.194499,-0.4991 0.132909,-0.1392 0.293371,-0.2088 0.481388,-0.2088 l 3.324113,0 z"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             id="path4151"
+             inkscape:connector-curvature="0" />
+        </g>
+      </g>
+    </g>
+  </g>
+</svg>

Some files were not shown because too many files changed in this diff