diff --git a/.dockerignore b/.dockerignore new file mode 100755 index 0000000..04204c7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +config diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..04204c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..18646ce --- /dev/null +++ b/Dockerfile @@ -0,0 +1,363 @@ +FROM debian:bullseye-slim + +# Update package repos +ARG DEBIAN_FRONTEND=noninteractive +RUN \ + echo "**** Update apt database ****" \ + && sed -i '/^.*main/ s/$/ contrib non-free/' /etc/apt/sources.list \ + && \ + echo + +# Update locale +RUN \ + echo "**** Update apt database ****" \ + && apt-get update \ + && \ + echo "**** Install and configure locals ****" \ + && apt-get -y install --no-install-recommends \ + locales \ + procps \ + && echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen \ + && locale-gen \ + && \ + echo "**** Section cleanup ****" \ + && apt-get clean autoclean -y \ + && apt-get autoremove -y \ + && rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* \ + && \ + echo +ENV \ + LANG=en_US.UTF-8 \ + LANGUAGE=en_US:en \ + LC_ALL=en_US.UTF-8 + +# Re-install certificates +RUN \ + echo "**** Update apt database ****" \ + && apt-get update \ + && \ + echo "**** Install certificates ****" \ + && apt-get -y install --reinstall \ + ca-certificates \ + && \ + echo "**** Section cleanup ****" \ + && apt-get clean autoclean -y \ + && apt-get autoremove -y \ + && rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* \ + && \ + echo + +# Install core packages +RUN \ + echo "**** Update apt database ****" \ + && apt-get update \ + && \ + echo "**** Install tools ****" \ + && apt-get -y install \ + bash \ + bash-completion \ + gcc \ + git \ + kmod \ + less \ + make \ + nano \ + sudo \ + vim \ + wget \ + && \ + echo "**** Section cleanup ****" \ + && apt-get clean autoclean -y \ + && apt-get autoremove -y \ + && rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* \ + && \ + echo + +# Install desktop requirements +RUN \ + echo "**** Update apt database ****" \ + && apt-get update \ + && \ + echo "**** Install desktop requirements ****" \ + && apt-get -y install --no-install-recommends \ + dbus-x11 \ + libxcomposite-dev \ + libxcursor1 \ + man-db \ + mlocate \ + net-tools \ + pciutils \ + pkg-config \ + python3 \ + python3-numpy \ + python3-setuptools \ + rsync \ + x11vnc \ + xauth \ + xorg \ + xserver-xorg-legacy \ + xvfb \ + && \ + echo "**** Section cleanup ****" \ + && apt-get clean autoclean -y \ + && apt-get autoremove -y \ + && rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* \ + && \ + echo + +# Install supervisor +RUN \ + echo "**** Update apt database ****" \ + && apt-get update \ + && \ + echo "**** Install supervisor ****" \ + && apt-get -y install \ + supervisor \ + && \ + echo "**** Section cleanup ****" \ + && apt-get clean autoclean -y \ + && apt-get autoremove -y \ + && rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* \ + && \ + echo + +# Install openssh server +RUN \ + echo "**** Update apt database ****" \ + && apt-get update \ + && \ + echo "**** Install openssh server ****" \ + && apt-get -y install \ + openssh-server \ + && echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config \ + && \ + echo "**** Section cleanup ****" \ + && apt-get clean autoclean -y \ + && apt-get autoremove -y \ + && rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* \ + && \ + echo + +# Install noVNC +ARG NOVNC_VERSION=1.2.0 +RUN \ + echo "**** Fetch noVNC ****" \ + && cd /tmp \ + && wget -O /tmp/novnc.tar.gz https://github.com/novnc/noVNC/archive/v${NOVNC_VERSION}.tar.gz \ + && \ + echo "**** Extract noVNC ****" \ + && cd /tmp \ + && tar -xvf /tmp/novnc.tar.gz \ + && \ + echo "**** Configure noVNC ****" \ + && cd /tmp/noVNC-${NOVNC_VERSION} \ + && sed -i 's/credentials: { password: password } });/credentials: { password: password },\n wsProtocols: ["'"binary"'"] });/g' app/ui.js \ + && mkdir -p /opt \ + && rm -rf /opt/noVNC \ + && cd /opt \ + && mv -f /tmp/noVNC-${NOVNC_VERSION} /opt/noVNC \ + && cd /opt/noVNC \ + && ln -s vnc.html index.html \ + && chmod -R 755 /opt/noVNC \ + && \ + echo "**** Modify noVNC title ****" \ + && sed -i '/ document.title =/c\ document.title = "Steam Headless - noVNC";' \ + /opt/noVNC/app/ui.js \ + && \ + echo "**** Section cleanup ****" \ + && rm -rf \ + /tmp/noVNC* \ + /tmp/novnc.tar.gz + +# Install Websockify +ARG WEBSOCKETIFY_VERSION=0.10.0 +RUN \ + echo "**** Fetch Websockify ****" \ + && cd /tmp \ + && wget -O /tmp/websockify.tar.gz https://github.com/novnc/websockify/archive/v${WEBSOCKETIFY_VERSION}.tar.gz \ + && \ + echo "**** Extract Websockify ****" \ + && cd /tmp \ + && tar -xvf /tmp/websockify.tar.gz \ + && \ + echo "**** Install Websockify to main ****" \ + && cd /tmp/websockify-${WEBSOCKETIFY_VERSION} \ + && python3 ./setup.py install \ + && \ + echo "**** Install Websockify to noVNC path ****" \ + && cd /tmp \ + && mv -v /tmp/websockify-${WEBSOCKETIFY_VERSION} /opt/noVNC/utils/websockify \ + && \ + echo "**** Section cleanup ****" \ + && rm -rf \ + /tmp/websockify-* \ + /tmp/websockify.tar.gz + +# Install desktop environment +RUN \ + echo "**** Update apt database ****" \ + && apt-get update \ + && \ + echo "**** Install desktop environment ****" \ + && apt-get -y install \ + xfce4 \ + xfce4-terminal \ + msttcorefonts \ + fonts-vlgothic \ + gedit \ + && \ + echo "**** Section cleanup ****" \ + && apt-get clean autoclean -y \ + && apt-get autoremove -y \ + && rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* \ + && \ + echo + +# Install firefox +RUN \ + echo "**** Update apt database ****" \ + && apt-get update \ + && \ + echo "**** Install firefox ****" \ + && apt-get -y install \ + firefox-esr \ + && \ + echo "**** Section cleanup ****" \ + && apt-get clean autoclean -y \ + && apt-get autoremove -y \ + && rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* \ + && \ + echo + +# Install Steam +RUN \ + echo "**** Install steam ****" \ + && dpkg --add-architecture i386 \ + && apt-get update \ + && echo steam steam/question select "I AGREE" | debconf-set-selections \ + && echo steam steam/license note '' | debconf-set-selections \ + && apt-get -y install \ + steam \ + steam-devices \ + vulkan-tools \ + mesa-utils \ + mesa-vulkan-drivers \ + libglx-mesa0:i386 \ + mesa-vulkan-drivers:i386 \ + libgl1-mesa-dri:i386 \ + && \ + echo "**** Section cleanup ****" \ + && apt-get clean autoclean -y \ + && apt-get autoremove -y \ + && rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* \ + && \ + echo + +# Setup browser audio streaming deps +RUN \ + echo "**** Update apt database ****" \ + && apt-get update \ + && \ + echo "**** Install audio streaming deps ****" \ + && apt-get -y install --no-install-recommends \ + bzip2 \ + gstreamer1.0-alsa \ + gstreamer1.0-gl \ + gstreamer1.0-gtk3 \ + gstreamer1.0-libav \ + gstreamer1.0-plugins-base \ + gstreamer1.0-plugins-good \ + gstreamer1.0-pulseaudio \ + gstreamer1.0-qt5 \ + gstreamer1.0-tools \ + gstreamer1.0-x \ + libglu1-mesa \ + libgstreamer1.0-0 \ + libgtk2.0-0 \ + libncursesw5 \ + libopenal1 \ + libsdl-image1.2 \ + libsdl-ttf2.0-0 \ + libsdl1.2debian \ + libsndfile1 \ + novnc \ + pulseaudio \ + ucspi-tcp \ + && \ + echo "**** Section cleanup ****" \ + && apt-get clean autoclean -y \ + && apt-get autoremove -y \ + && rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* \ + && \ + echo + +# Configure default user and set env +ENV \ + USER="default" \ + USER_PASSWORD="password" \ + USER_HOME="/home/default" \ + TZ="Pacific/Auckland" \ + USER_LOCALES="en_US.UTF-8 UTF-8" +RUN \ + echo "**** Configure default user '${USER}' ****" \ + && mkdir -p \ + ${USER_HOME} \ + /games \ + && useradd -d ${USER_HOME} -s /bin/bash ${USER} \ + && chown -R ${USER} \ + ${USER_HOME} \ + /games \ + && echo "${USER} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + +# Add FS overlay +COPY overlay / + +# Set display environment variables +ENV \ + DISPLAY_CDEPTH="24" \ + DISPLAY_DPI="96" \ + DISPLAY_REFRESH="60" \ + DISPLAY_SIZEH="900" \ + DISPLAY_SIZEW="1600" \ + DISPLAY_VIDEO_PORT="DFP" \ + DISPLAY=":0" \ + NVIDIA_DRIVER_CAPABILITIES="all" \ + NVIDIA_VISIBLE_DEVICES="all" + +# Be sure that the noVNC port is exposed +EXPOSE 8083 +EXPOSE 32123 + +# Set entrypoint +RUN chmod +x /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md index 71f5035..16b7856 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,41 @@ -# docker-steam-headless -Headless Steam supporting NVIDIA GPU and accessible via NoVNC +# Headless Steam Service + +Play your games in the browser with audio. Connect another device and use it with Steam Remote Play. + +## Features: +- Full video/audio noVNC web access to a Xfce4 Desktop +- NVIDIA GPU support +- Root access +- SSH server for remote terminal access + +--- +## Notes: + +### ADDITIONAL SOFTWARE: +If you wish to install additional applications, you can generate a +script inside the `~/init.d` directory ending with ".sh". This will be executed on the container startup. + +### STORAGE PATHS: +Everything that you wish to save in this container should be stored in the home directory or a docker container mount that you have specified. All files that are store outside your home directory are not persistent and will be wiped if there is an update of the container or you change something in the template. + +### GAMES LIBRARY: +It is recommended that you mount your games library to `/games` and configure Steam to add that path. + +### AUTO START APPLICATIONS: +In this container, Steam is configured to automatically start. If you wish to add additional services to automatically start, add them under **Applications > Settings > Session and Startup** in the WebUI. + +### NETWORK MODE: +If you want to use the container as a Steam Remote Play (previously "In Home Streaming") host device you should create a custom network and assign this container it's own IP, if you don't do this the traffic will be routed through the internet since Steam thinks you are on a different network. + +--- +## Running: + +For a development environment, I have created a script in the devops directory. + + +--- +## TODO: +- Configure xorg.conf with no NVIDIA GPU +- Lock SSH access to user only (remove root access) +- Require user to enter password for sudo +- Document how to run this container diff --git a/devops/run_server.sh b/devops/run_server.sh new file mode 100755 index 0000000..1d0fda2 --- /dev/null +++ b/devops/run_server.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +### +# File: run.sh +# Project: docker-steamos +# File Created: Saturday, 8th January 2022 2:34:23 pm +# Author: Josh.5 (jsunnex@gmail.com) +# ----- +# Last Modified: Monday, 10th January 2022 11:04:51 pm +# Modified By: Josh.5 (jsunnex@gmail.com) +### + +script_path=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ); +project_base_path=$(realpath ${script_path}/..); + + +if [[ ${1} == "stop" ]]; then + docker stop steam-headless + docker rm steam-headless + exit $? +elif [[ ${1} == "tail" ]]; then + docker logs -f steam-headless + exit $? +elif [[ ${1} == "user" ]]; then + docker exec -ti --user default steam-headless bash + exit $? +elif [[ ${1} == "root" ]]; then + docker exec -ti --user 0 steam-headless bash + exit $? +fi + + +docker stop steam-headless +docker rm steam-headless +sleep 1 + + +docker run -d --name='steam-headless' \ + --privileged=true \ + --net='br0' --ip='192.168.1.208' \ + --cpuset-cpus='3,9,4,10,5,11' \ + -e PUID="99" \ + -e PGID="100" \ + -e UMASK='000' \ + -e USER_PASSWORD="password" \ + -e USER="default" \ + -e HOME="/home/test" \ + -e USER_HOME="/home/default" \ + -e TZ="Pacific/Auckland" \ + -e USER_LOCALES="en_US.UTF-8 UTF-8" \ + -e DISPLAY_CDEPTH="24" \ + -e DISPLAY_DPI="96" \ + -e DISPLAY_REFRESH="60" \ + -e DISPLAY_SIZEH="720" \ + -e DISPLAY_SIZEW="1280" \ + -e DISPLAY_VIDEO_PORT="DFP" \ + -e DISPLAY=":0" \ + -e NVIDIA_DRIVER_CAPABILITIES="all" \ + -e NVIDIA_VISIBLE_DEVICES="all" \ + -v "${project_base_path}/config/home/default":'/home/default':'rw' \ + --hostname='steam-headless' \ + --shm-size=2G \ + --runtime=nvidia \ + josh5/steam-headless:latest + + +docker logs -f steam-headless diff --git a/images/steam-icon.png b/images/steam-icon.png new file mode 100644 index 0000000..b7d71a5 Binary files /dev/null and b/images/steam-icon.png differ diff --git a/overlay/entrypoint.sh b/overlay/entrypoint.sh new file mode 100644 index 0000000..bc7ac4a --- /dev/null +++ b/overlay/entrypoint.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +### +# File: entrypoint.sh +# Project: docker-steamos +# File Created: Saturday, 8th January 2022 7:08:46 pm +# Author: Josh.5 (jsunnex@gmail.com) +# ----- +# Last Modified: Tuesday, 11th January 2022 1:02:16 am +# Modified By: Josh.5 (jsunnex@gmail.com) +### + +set -e + +# If a command was passed, run that instead of the usual init process +if [ ! -z "$@" ]; then + exec $@ + exit $? +fi + +# Execute all init scripts +for init_script in /scripts/*.sh ; do + echo + echo "[ ${init_script}: executing... ]" + sed -i 's/\r$//' "${init_script}" + source "${init_script}" +done + +# Execute any user generated init scripts +mkdir -p ${USER_HOME}/init.d +chown -R ${USER} ${USER_HOME}/init.d +for user_init_script in ${USER_HOME}/init.d/*.sh ; do + + # Check that a file was found + # (If no files exist in this directory, then user_init_script will be empty) + if [[ -e "${user_init_script}" ]]; then + + echo + echo "[ USER:${user_init_script}: executing... ]" + sed -i 's/\r$//' "${user_init_script}" + source "${user_init_script}" + + fi + +done + +echo "**** Starting supervisord ****"; +exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf --nodaemon diff --git a/overlay/etc/home_directory_template/.bashrc b/overlay/etc/home_directory_template/.bashrc new file mode 100644 index 0000000..a355b0c --- /dev/null +++ b/overlay/etc/home_directory_template/.bashrc @@ -0,0 +1,9 @@ +# +# ~/.bashrc +# + +# If not running interactively, don't do anything +[[ $- != *i* ]] && return + +alias ls='ls --color=auto' +PS1='[\u@\h \W]\$ ' diff --git a/overlay/etc/home_directory_template/.cache/log/.placeholder b/overlay/etc/home_directory_template/.cache/log/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/overlay/etc/home_directory_template/.config/autostart/Steam.desktop b/overlay/etc/home_directory_template/.config/autostart/Steam.desktop new file mode 100644 index 0000000..7319c5e --- /dev/null +++ b/overlay/etc/home_directory_template/.config/autostart/Steam.desktop @@ -0,0 +1,14 @@ +[Desktop Entry] +Encoding=UTF-8 +Version=0.9.4 +Type=Application +Name=Steam +Comment=launch steam on login +Exec=/usr/games/steam %U +Icon=steam +OnlyShowIn=XFCE; +RunHook=0 +StartupNotify=false +Terminal=false +Hidden=false + diff --git a/overlay/etc/home_directory_template/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-desktop.xml b/overlay/etc/home_directory_template/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-desktop.xml new file mode 100644 index 0000000..9bcabd3 --- /dev/null +++ b/overlay/etc/home_directory_template/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-desktop.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/overlay/etc/home_directory_template/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-panel.xml b/overlay/etc/home_directory_template/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-panel.xml new file mode 100644 index 0000000..d0a0d9f --- /dev/null +++ b/overlay/etc/home_directory_template/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-panel.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/overlay/etc/home_directory_template/.config/xfce4/xfconf/xfce-perchannel-xml/xsettings.xml b/overlay/etc/home_directory_template/.config/xfce4/xfconf/xfce-perchannel-xml/xsettings.xml new file mode 100644 index 0000000..cd0228c --- /dev/null +++ b/overlay/etc/home_directory_template/.config/xfce4/xfconf/xfce-perchannel-xml/xsettings.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/overlay/etc/home_directory_template/.local/share/applications/x11vnc.desktop b/overlay/etc/home_directory_template/.local/share/applications/x11vnc.desktop new file mode 100644 index 0000000..6e110dc --- /dev/null +++ b/overlay/etc/home_directory_template/.local/share/applications/x11vnc.desktop @@ -0,0 +1,12 @@ +# Hide application from menu +[Desktop Entry] +Name=X11VNC Server +Comment=Share this desktop by VNC +Exec=x11vnc -gui tray=setpass -rfbport PROMPT -bg -o %%HOME/.x11vnc.log.%%VNCDISPLAY +Icon=computer +Terminal=false +Type=Application +StartupNotify=false +#StartupWMClass=x11vnc_port_prompt +Categories=Network;RemoteAccess; +Hidden=true diff --git a/overlay/etc/supervisor/conf.d/services.conf b/overlay/etc/supervisor/conf.d/services.conf new file mode 100644 index 0000000..a82f082 --- /dev/null +++ b/overlay/etc/supervisor/conf.d/services.conf @@ -0,0 +1,130 @@ +[supervisord] +user=root +nodaemon=true + +[program:ssh] +autostart=true +priority=10 +directory=/ +command=/usr/sbin/sshd -D +user=root +autorestart=true +stopsignal=QUIT + +[program:dbus] +autostart=true +priority=10 +directory=/ +command=dbus-daemon --config-file=/usr/share/dbus-1/system.conf --nofork --nopidfile +user=%(ENV_USER)s +environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s" +autorestart=true +stopsignal=QUIT + +[program:pulseaudio] +autostart=true +priority=10 +directory=/ +command=/usr/bin/pulseaudio -vvvv --disallow-module-loading --disallow-exit --exit-idle-time=-1 +user=%(ENV_USER)s +environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s" +autorestart=true +stopsignal=QUIT +stdout_logfile=/home/%(ENV_USER)s/.cache/log/pulseaudio.log +stderr_logfile=/home/%(ENV_USER)s/.cache/log/pulseaudio.err + +[program:audiostream] +autostart=true +priority=10 +command=tcpserver localhost 5901 gst-launch-1.0 -q pulsesrc server=/tmp/pulseaudio.socket ! audio/x-raw, channels=2, rate=24000 ! cutter ! opusenc ! webmmux ! fdsink fd=1 +autorestart=true +stopsignal=QUIT +stdout_logfile=/home/%(ENV_USER)s/.cache/log/audiostream.log +stderr_logfile=/home/%(ENV_USER)s/.cache/log/audiostream.err + +[program:audiowebsock] +autostart=true +priority=10 +command=/usr/local/bin/websockify 32123 localhost:5901 +autorestart=true +stopsignal=QUIT +stdout_logfile=/home/%(ENV_USER)s/.cache/log/audiowebsock.log +stderr_logfile=/home/%(ENV_USER)s/.cache/log/audiowebsock.err + +[program:xorg] +autostart=true +priority=20 +directory=/ +command=/usr/bin/Xorg vt7 -novtswitch -sharevts -dpi "%(ENV_DISPLAY_DPI)s" +extension "MIT-SHM" %(ENV_DISPLAY)s +#command=/usr/bin/Xorg -novtswitch -sharevts -dpi "%(ENV_DISPLAY_DPI)s" +extension "MIT-SHM" "%(ENV_DISPLAY)s" +user=%(ENV_USER)s +environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s",XDG_RUNTIME_DIR="/tmp/xdg",RUNLEVEL="3" +autorestart=true +stopsignal=QUIT +stdout_logfile=/home/%(ENV_USER)s/.cache/log/xorg.log +stderr_logfile=/home/%(ENV_USER)s/.cache/log/xorg.err + +# Dont use xvfb as it does not work with nvidia X11 configuration +# [program:xvfb] +# autostart=false +# priority=20 +# directory=/ +# command=/usr/bin/Xvfb %(ENV_DISPLAY)s -screen 0 1920x1080x24 +# user=%(ENV_USER)s +# environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s" +# autorestart=true +# stopsignal=QUIT +# stdout_logfile=/home/%(ENV_USER)s/.cache/log/xvfb.log +# stderr_logfile=/home/%(ENV_USER)s/.cache/log/xvfb.err + +[program:x11vnc] +autostart=true +priority=30 +directory=/ +#command=/usr/bin/x11vnc -display %(ENV_DISPLAY)s -xkb +command=/usr/bin/x11vnc -display %(ENV_DISPLAY)s -rfbport 5900 -shared -forever +user=%(ENV_USER)s +autorestart=true +stopsignal=QUIT +stdout_logfile=/home/%(ENV_USER)s/.cache/log/x11vnc.log +stderr_logfile=/home/%(ENV_USER)s/.cache/log/x11vnc.err + +[program:de] +autostart=true +priority=50 +directory=/home/%(ENV_USER)s +command=/usr/bin/startxfce4 +#command=/usr/bin/mate-session +#command=/usr/bin/startlxqt +#command=/usr/bin/dbus-launch startxfce4 +#command=/usr/bin/dbus-launch xfce4-session +#command=/usr/bin/dbus-launch startplasma-x11 +user=%(ENV_USER)s +autorestart=true +stopsignal=QUIT +environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s" +stdout_logfile=/home/%(ENV_USER)s/.cache/log/de.log +stderr_logfile=/home/%(ENV_USER)s/.cache/log/de.err + +## Experimental launch directly to steam big picture... (this is cool, but not ideal for headless) +# [program:steam] +# autostart=true +# priority=50 +# directory=/home/%(ENV_USER)s +# command=/usr/bin/steam -bigpicture +# user=%(ENV_USER)s +# autorestart=true +# stopsignal=QUIT +# environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s" +# stdout_logfile=/home/%(ENV_USER)s/.cache/log/steam.log +# stderr_logfile=/home/%(ENV_USER)s/.cache/log/steam.err + +[program:novnc] +autostart=true +priority=100 +command=/opt/noVNC/utils/launch.sh --vnc localhost:5900 --listen 8083 +#command=/usr/share/novnc/utils/launch.sh --vnc localhost:5900 --listen 8083 +#command=/usr/local/bin/websockify -D --web=/usr/share/novnc/ --cert=/etc/ssl/novnc.pem 8083 localhost:5900 +user=%(ENV_USER)s +environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s" +autorestart=true diff --git a/overlay/opt/noVNC/audio.patch b/overlay/opt/noVNC/audio.patch new file mode 100644 index 0000000..8f31b12 --- /dev/null +++ b/overlay/opt/noVNC/audio.patch @@ -0,0 +1,416 @@ +diff --git a/app/styles/base.css b/app/styles/base.css +index adad415..dfefa6a 100644 +--- a/app/styles/base.css ++++ b/app/styles/base.css +@@ -683,7 +683,7 @@ select:active { + #noVNC_setting_port { + width: 80px; + } +-#noVNC_setting_path { ++#noVNC_setting_path #noVNC_setting_apath { + width: 100px; + } + +diff --git a/app/ui.js b/app/ui.js +index cb6a9fd..4d599e1 100644 +--- a/app/ui.js ++++ b/app/ui.js +@@ -15,6 +15,7 @@ import KeyTable from "../core/input/keysym.js"; + import keysyms from "../core/input/keysymdef.js"; + import Keyboard from "../core/input/keyboard.js"; + import RFB from "../core/rfb.js"; ++import WebAudio from "../core/webaudio.js"; + import * as WebUtil from "./webutil.js"; + + const PAGE_TITLE = "noVNC"; +@@ -40,6 +41,7 @@ const UI = { + inhibitReconnect: true, + reconnectCallback: null, + reconnectPassword: null, ++ webaudio: null, + + prime() { + return WebUtil.initSettings().then(() => { +@@ -171,6 +173,7 @@ const UI = { + UI.initSetting('compression', 2); + UI.initSetting('shared', true); + UI.initSetting('view_only', false); ++ UI.initSetting('audio', true); + UI.initSetting('show_dot', false); + UI.initSetting('path', 'websockify'); + UI.initSetting('repeaterID', ''); +@@ -200,6 +203,20 @@ const UI = { + } + }, + ++ toggleAudio() { ++ console.log('here'); ++ const audio = UI.getSetting('audio'); ++ if (audio) { ++ UI.webaudio.start(); ++ } else { ++ if(UI.webaudio !== null) { ++ if (UI.webaudio.connected) { ++ UI.webaudio.stop(); ++ } ++ } ++ } ++ }, ++ + /* ------^------- + * /INIT + * ============== +@@ -356,6 +373,8 @@ const UI = { + UI.addSettingChangeHandler('shared'); + UI.addSettingChangeHandler('view_only'); + UI.addSettingChangeHandler('view_only', UI.updateViewOnly); ++ UI.addSettingChangeHandler('audio'); ++ UI.addSettingChangeHandler('audio', UI.updateEnableAudio); + UI.addSettingChangeHandler('show_dot'); + UI.addSettingChangeHandler('show_dot', UI.updateShowDotCursor); + UI.addSettingChangeHandler('host'); +@@ -841,6 +860,7 @@ const UI = { + UI.updateSetting('compression'); + UI.updateSetting('shared'); + UI.updateSetting('view_only'); ++ UI.updateSetting('audio'); + UI.updateSetting('path'); + UI.updateSetting('repeaterID'); + UI.updateSetting('logging'); +@@ -1041,6 +1061,7 @@ const UI = { + UI.rfb.resizeSession = UI.getSetting('resize') === 'remote'; + UI.rfb.qualityLevel = parseInt(UI.getSetting('quality')); + UI.rfb.compressionLevel = parseInt(UI.getSetting('compression')); ++ UI.rfb.enableAudio = UI.getSetting('audio'); + UI.rfb.showDotCursor = UI.getSetting('show_dot'); + + UI.updateViewOnly(); // requires UI.rfb +@@ -1056,6 +1077,10 @@ const UI = { + + UI.updateVisualState('disconnecting'); + ++ if(UI.webaudio !== null && UI.webaudio.socket !== null) { ++ UI.webaudio.socket.close(); ++ } ++ + // Don't display the connection settings until we're actually disconnected + }, + +@@ -1097,6 +1122,19 @@ const UI = { + + // Do this last because it can only be used on rendered elements + UI.rfb.focus(); ++ ++ let audio_url; ++ let host = window.location.hostname; ++ let port = 32123; ++ if (window.location.protocol === "https:") { ++ audio_url = 'wss'; ++ } else { ++ audio_url = 'ws'; ++ } ++ audio_url += '://' + host + ':' + port; ++ ++ UI.webaudio = new WebAudio(audio_url); ++ UI.toggleAudio(); + }, + + disconnectFinished(e) { +@@ -1647,6 +1685,12 @@ const UI = { + } + }, + ++ updateEnableAudio() { ++ if (!UI.rfb) return; ++ UI.rfb.enableAudio = UI.getSetting('audio'); ++ UI.toggleAudio(); ++ }, ++ + updateShowDotCursor() { + if (!UI.rfb) return; + UI.rfb.showDotCursor = UI.getSetting('show_dot'); +diff --git a/core/webaudio.js b/core/webaudio.js +new file mode 100644 +index 0000000..28c71ac +--- /dev/null ++++ b/core/webaudio.js +@@ -0,0 +1,142 @@ ++export default class WebAudio { ++ constructor(url) { ++ this.url = url ++ ++ this.connected = false; ++ ++ //constants for audio behavoir ++ this.maximumAudioLag = 1.5; //amount of seconds we can potentially be behind the server audio stream ++ this.syncLagInterval = 5000; //check every x milliseconds if we are behind the server audio stream ++ this.updateBufferEvery = 20; //add recieved data to the player buffer every x milliseconds ++ this.reduceBufferInterval = 500; //trim the output audio stream buffer every x milliseconds so we don't overflow ++ this.maximumSecondsOfBuffering = 1; //maximum amount of data to store in the play buffer ++ this.connectionCheckInterval = 500; //check the connection every x milliseconds ++ ++ //register all our background timers. these need to be created only once - and will run independent of the object's streams/properties ++ this.updateCheck = null; ++ this.syncCheck = null; ++ this.reduceCheck = null; ++ this.ConnCheck = null; ++ ++ } ++ ++ //registers all the event handlers for when this stream is closed - or when data arrives. ++ registerHandlers() { ++ this.mediaSource.addEventListener('sourceended', e => this.socketDisconnected(e)) ++ this.mediaSource.addEventListener('sourceclose', e => this.socketDisconnected(e)) ++ this.mediaSource.addEventListener('error', e => this.socketDisconnected(e)) ++ this.buffer.addEventListener('error', e => this.socketDisconnected(e)) ++ this.buffer.addEventListener('abort', e => this.socketDisconnected(e)) ++ } ++ ++ //starts the web audio stream. only call this method on button click. ++ start() { ++ if (!!this.connected) return; ++ if (!!this.audio) this.audio.remove(); ++ this.queue = null; ++ ++ if (this.updateCheck === null) this.updateCheck = setInterval(() => this.updateQueue(), this.updateBufferEvery); ++ if (this.syncCheck === null) this.syncCheck = setInterval(() => this.syncInterval(), this.syncLagInterval); ++ if (this.reduceCheck === null) this.reduceCheck = setInterval(() => this.reduceBuffer(), this.reduceBufferInterval); ++ if (this.ConnCheck === null) this.ConnCheck = setInterval(() => this.tryLastPacket(), this.connectionCheckInterval); ++ ++ this.mediaSource = new MediaSource() ++ this.mediaSource.addEventListener('sourceopen', e => this.onSourceOpen()) ++ //first we need a media source - and an audio object that contains it. ++ this.audio = document.createElement('audio'); ++ this.audio.src = window.URL.createObjectURL(this.mediaSource); ++ ++ //start our stream - we can only do this on user input ++ this.audio.play(); ++ } ++ ++ stop() { ++ // Clear all interval timers ++ clearInterval(this.updateCheck); ++ clearInterval(this.syncCheck); ++ clearInterval(this.reduceCheck); ++ clearInterval(this.ConnCheck); ++ // Close the socket ++ this.socket.close(); ++ this.connected = false; ++ // Reset timers to null ++ this.updateCheck = null; ++ this.syncCheck = null; ++ this.reduceCheck = null; ++ this.ConnCheck = null; ++ } ++ ++ wsConnect() { ++ if (!!this.socket) this.socket.close(); ++ ++ this.socket = new WebSocket(this.url, ['binary', 'base64']) ++ this.socket.binaryType = 'arraybuffer' ++ this.socket.addEventListener('message', e => this.websocketDataArrived(e), false); ++ } ++ ++ //this is called when the media source contains data ++ onSourceOpen(e) { ++ this.buffer = this.mediaSource.addSourceBuffer('audio/webm; codecs="opus"') ++ this.registerHandlers(); ++ this.wsConnect(); ++ } ++ ++ //whenever data arrives in our websocket this is called. ++ websocketDataArrived(e) { ++ this.lastPacket = Date.now(); ++ this.connected = true; ++ this.queue = this.queue == null ? e.data : this.concat(this.queue, e.data); ++ } ++ ++ //whenever a disconnect happens this is called. ++ socketDisconnected(e) { ++ console.log(e); ++ this.connected = false; ++ } ++ ++ tryLastPacket() { ++ if (this.lastPacket == null) return; ++ if ((Date.now() - this.lastPacket) > 1000) { ++ this.socketDisconnected('timeout'); ++ } ++ } ++ ++ //this updates the buffer with the data from our queue ++ updateQueue() { ++ if (!(!!this.queue && !!this.buffer && !this.buffer.updating)) { ++ return; ++ } ++ ++ this.buffer.appendBuffer(this.queue); ++ this.queue = null; ++ } ++ ++ //reduces the stream buffer to the minimal size that we need for streaming ++ reduceBuffer() { ++ if (!(this.buffer && !this.buffer.updating && !!this.audio && !!this.audio.currentTime && this.audio.currentTime > 1)) { ++ return; ++ } ++ ++ this.buffer.remove(0, this.audio.currentTime - 1); ++ } ++ ++ //synchronizes the current time of the stream with the server ++ syncInterval() { ++ if (!(this.audio && this.audio.currentTime && this.audio.currentTime > 1 && this.buffer && this.buffer.buffered && this.buffer.buffered.length > 1)) { ++ return; ++ } ++ ++ var currentTime = this.audio.currentTime; ++ var targetTime = this.buffer.buffered.end(this.buffer.buffered.length - 1); ++ ++ if (targetTime > (currentTime + this.maximumAudioLag)) this.audio.fastSeek(targetTime); ++ } ++ ++ //joins two data arrays - helper function ++ concat(buffer1, buffer2) { ++ var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); ++ tmp.set(new Uint8Array(buffer1), 0); ++ tmp.set(new Uint8Array(buffer2), buffer1.byteLength); ++ return tmp.buffer; ++ }; ++} +diff --git a/vnc.html b/vnc.html +index c678c2a..d1652a0 100644 +--- a/vnc.html ++++ b/vnc.html +@@ -170,6 +170,10 @@ + + +

  • ++
  • ++ ++
  • ++

  • +
  • + +
  • +diff --git a/vnc_lite.html b/vnc_lite.html +index 8e2f5cb..d3cf6ab 100644 +--- a/vnc_lite.html ++++ b/vnc_lite.html +@@ -41,6 +41,15 @@ + #status { + text-align: center; + } ++ #toggleAudioButton { ++ position: fixed; ++ top: 0px; ++ left: 0px; ++ border: 1px outset; ++ padding: 5px 5px 4px 5px; ++ cursor: pointer; ++ display: none; ++ } + #sendCtrlAltDelButton { + position: fixed; + top: 0px; +@@ -60,18 +69,38 @@ +