mirror of
https://github.com/Steam-Headless/docker-steam-headless.git
synced 2023-08-28 13:41:56 +03:00
initial commit
This commit is contained in:
1
.dockerignore
Executable file
1
.dockerignore
Executable file
@@ -0,0 +1 @@
|
||||
config
|
||||
1
.gitignore
vendored
Executable file
1
.gitignore
vendored
Executable file
@@ -0,0 +1 @@
|
||||
config
|
||||
363
Dockerfile
Normal file
363
Dockerfile
Normal file
@@ -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"]
|
||||
43
README.md
43
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
|
||||
|
||||
66
devops/run_server.sh
Executable file
66
devops/run_server.sh
Executable file
@@ -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
|
||||
BIN
images/steam-icon.png
Normal file
BIN
images/steam-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
47
overlay/entrypoint.sh
Normal file
47
overlay/entrypoint.sh
Normal file
@@ -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
|
||||
9
overlay/etc/home_directory_template/.bashrc
Normal file
9
overlay/etc/home_directory_template/.bashrc
Normal file
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# ~/.bashrc
|
||||
#
|
||||
|
||||
# If not running interactively, don't do anything
|
||||
[[ $- != *i* ]] && return
|
||||
|
||||
alias ls='ls --color=auto'
|
||||
PS1='[\u@\h \W]\$ '
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<channel name="xfce4-desktop" version="1.0">
|
||||
<property name="backdrop" type="empty">
|
||||
<property name="screen0" type="empty">
|
||||
<property name="monitor0" type="empty">
|
||||
<property name="brightness" type="empty"/>
|
||||
<property name="color1" type="empty"/>
|
||||
<property name="color2" type="empty"/>
|
||||
<property name="color-style" type="empty"/>
|
||||
<property name="image-path" type="empty"/>
|
||||
<property name="image-show" type="empty"/>
|
||||
<property name="last-image" type="empty"/>
|
||||
<property name="last-single-image" type="empty"/>
|
||||
</property>
|
||||
<property name="monitor1" type="empty">
|
||||
<property name="brightness" type="empty"/>
|
||||
<property name="color1" type="empty"/>
|
||||
<property name="color2" type="empty"/>
|
||||
<property name="color-style" type="empty"/>
|
||||
<property name="image-path" type="empty"/>
|
||||
<property name="image-show" type="empty"/>
|
||||
<property name="last-image" type="empty"/>
|
||||
<property name="last-single-image" type="empty"/>
|
||||
</property>
|
||||
<property name="monitorDVI-D-0" type="empty">
|
||||
<property name="workspace0" type="empty">
|
||||
<property name="color-style" type="int" value="1"/>
|
||||
<property name="image-style" type="int" value="5"/>
|
||||
<property name="last-image" type="string" value="/usr/share/images/desktop-base/default"/>
|
||||
</property>
|
||||
<property name="workspace1" type="empty">
|
||||
<property name="color-style" type="int" value="1"/>
|
||||
<property name="image-style" type="int" value="5"/>
|
||||
<property name="last-image" type="string" value="/usr/share/images/desktop-base/default"/>
|
||||
</property>
|
||||
<property name="workspace2" type="empty">
|
||||
<property name="color-style" type="int" value="1"/>
|
||||
<property name="image-style" type="int" value="5"/>
|
||||
<property name="last-image" type="string" value="/usr/share/images/desktop-base/default"/>
|
||||
</property>
|
||||
<property name="workspace3" type="empty">
|
||||
<property name="color-style" type="int" value="1"/>
|
||||
<property name="image-style" type="int" value="5"/>
|
||||
<property name="last-image" type="string" value="/usr/share/images/desktop-base/default"/>
|
||||
</property>
|
||||
</property>
|
||||
</property>
|
||||
</property>
|
||||
</channel>
|
||||
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<channel name="xfce4-panel" version="1.0">
|
||||
<property name="configver" type="int" value="2"/>
|
||||
<property name="panels" type="array">
|
||||
<value type="int" value="1"/>
|
||||
<property name="dark-mode" type="bool" value="true"/>
|
||||
<property name="panel-1" type="empty">
|
||||
<property name="position" type="string" value="p=6;x=0;y=0"/>
|
||||
<property name="length" type="uint" value="100"/>
|
||||
<property name="position-locked" type="bool" value="true"/>
|
||||
<property name="icon-size" type="uint" value="16"/>
|
||||
<property name="size" type="uint" value="26"/>
|
||||
<property name="plugin-ids" type="array">
|
||||
<value type="int" value="1"/>
|
||||
<value type="int" value="2"/>
|
||||
<value type="int" value="3"/>
|
||||
<value type="int" value="4"/>
|
||||
<value type="int" value="5"/>
|
||||
<value type="int" value="6"/>
|
||||
<value type="int" value="8"/>
|
||||
<value type="int" value="10"/>
|
||||
<value type="int" value="11"/>
|
||||
<value type="int" value="12"/>
|
||||
<value type="int" value="13"/>
|
||||
<value type="int" value="14"/>
|
||||
</property>
|
||||
<property name="background-style" type="uint" value="0"/>
|
||||
</property>
|
||||
</property>
|
||||
<property name="plugins" type="empty">
|
||||
<property name="plugin-1" type="string" value="applicationsmenu"/>
|
||||
<property name="plugin-2" type="string" value="tasklist">
|
||||
<property name="grouping" type="uint" value="1"/>
|
||||
</property>
|
||||
<property name="plugin-3" type="string" value="separator">
|
||||
<property name="expand" type="bool" value="true"/>
|
||||
<property name="style" type="uint" value="0"/>
|
||||
</property>
|
||||
<property name="plugin-4" type="string" value="pager"/>
|
||||
<property name="plugin-5" type="string" value="separator">
|
||||
<property name="style" type="uint" value="0"/>
|
||||
</property>
|
||||
<property name="plugin-6" type="string" value="systray">
|
||||
<property name="square-icons" type="bool" value="true"/>
|
||||
</property>
|
||||
<property name="plugin-8" type="string" value="pulseaudio">
|
||||
<property name="enable-keyboard-shortcuts" type="bool" value="true"/>
|
||||
<property name="show-notifications" type="bool" value="true"/>
|
||||
</property>
|
||||
<property name="plugin-9" type="string" value="power-manager-plugin"/>
|
||||
<property name="plugin-10" type="string" value="notification-plugin"/>
|
||||
<property name="plugin-11" type="string" value="separator">
|
||||
<property name="style" type="uint" value="0"/>
|
||||
</property>
|
||||
<property name="plugin-12" type="string" value="clock"/>
|
||||
<property name="plugin-13" type="string" value="separator">
|
||||
<property name="style" type="uint" value="0"/>
|
||||
</property>
|
||||
<property name="plugin-14" type="string" value="actions"/>
|
||||
</property>
|
||||
</channel>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<channel name="xsettings" version="1.0">
|
||||
<property name="Net" type="empty">
|
||||
<property name="ThemeName" type="string" value="Adwaita-dark"/>
|
||||
<property name="IconThemeName" type="string" value="Adwaita"/>
|
||||
<property name="DoubleClickTime" type="empty"/>
|
||||
<property name="DoubleClickDistance" type="empty"/>
|
||||
<property name="DndDragThreshold" type="empty"/>
|
||||
<property name="CursorBlink" type="empty"/>
|
||||
<property name="CursorBlinkTime" type="empty"/>
|
||||
<property name="SoundThemeName" type="empty"/>
|
||||
<property name="EnableEventSounds" type="empty"/>
|
||||
<property name="EnableInputFeedbackSounds" type="empty"/>
|
||||
</property>
|
||||
<property name="Xft" type="empty">
|
||||
<property name="DPI" type="empty"/>
|
||||
<property name="Antialias" type="empty"/>
|
||||
<property name="Hinting" type="empty"/>
|
||||
<property name="HintStyle" type="empty"/>
|
||||
<property name="RGBA" type="empty"/>
|
||||
</property>
|
||||
<property name="Gtk" type="empty">
|
||||
<property name="CanChangeAccels" type="empty"/>
|
||||
<property name="ColorPalette" type="empty"/>
|
||||
<property name="FontName" type="empty"/>
|
||||
<property name="MonospaceFontName" type="empty"/>
|
||||
<property name="IconSizes" type="empty"/>
|
||||
<property name="KeyThemeName" type="empty"/>
|
||||
<property name="ToolbarStyle" type="empty"/>
|
||||
<property name="ToolbarIconSize" type="empty"/>
|
||||
<property name="MenuImages" type="empty"/>
|
||||
<property name="ButtonImages" type="empty"/>
|
||||
<property name="MenuBarAccel" type="empty"/>
|
||||
<property name="CursorThemeName" type="empty"/>
|
||||
<property name="CursorThemeSize" type="empty"/>
|
||||
<property name="DecorationLayout" type="empty"/>
|
||||
<property name="DialogsUseHeader" type="empty"/>
|
||||
<property name="TitlebarMiddleClick" type="empty"/>
|
||||
</property>
|
||||
<property name="Gdk" type="empty">
|
||||
<property name="WindowScalingFactor" type="empty"/>
|
||||
</property>
|
||||
</channel>
|
||||
@@ -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
|
||||
130
overlay/etc/supervisor/conf.d/services.conf
Normal file
130
overlay/etc/supervisor/conf.d/services.conf
Normal file
@@ -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
|
||||
416
overlay/opt/noVNC/audio.patch
Normal file
416
overlay/opt/noVNC/audio.patch
Normal file
@@ -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 @@
|
||||
<label><input id="noVNC_setting_view_only" type="checkbox"> View Only</label>
|
||||
</li>
|
||||
<li><hr></li>
|
||||
+ <li>
|
||||
+ <label><input id="noVNC_setting_audio" type="checkbox"> Enable Audio</label>
|
||||
+ </li>
|
||||
+ <li><hr></li>
|
||||
<li>
|
||||
<label><input id="noVNC_setting_view_clip" type="checkbox"> Clip to Window</label>
|
||||
</li>
|
||||
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 @@
|
||||
<script type="module" crossorigin="anonymous">
|
||||
// RFB holds the API to connect and communicate with a VNC server
|
||||
import RFB from './core/rfb.js';
|
||||
+ import WebAudio from './core/webaudio.js';
|
||||
|
||||
let rfb;
|
||||
let desktopName;
|
||||
+ let wa;
|
||||
|
||||
// When this function is called we have
|
||||
// successfully connected to a server
|
||||
function connectedToServer(e) {
|
||||
+ 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;
|
||||
+
|
||||
+ wa = new WebAudio(audio_url);
|
||||
+ document.getElementById('toggleAudioButton').style.display = "block";
|
||||
+
|
||||
status("Connected to " + desktopName);
|
||||
}
|
||||
|
||||
// This function is called when we are disconnected
|
||||
function disconnectedFromServer(e) {
|
||||
+ if(wa !== null) {
|
||||
+ wa.stop();
|
||||
+ wa = null;
|
||||
+ document.getElementById('toggleAudioButton').style.display = "none";
|
||||
+ }
|
||||
if (e.detail.clean) {
|
||||
status("Disconnected");
|
||||
} else {
|
||||
@@ -100,6 +129,19 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
+ // Enable the audio websocket
|
||||
+ function toggleAudio() {
|
||||
+ if (wa.connected) {
|
||||
+ console.log('Stopping audio...')
|
||||
+ wa.stop();
|
||||
+ document.getElementById('toggleAudioButton').innerText = "Enable Audio (requires access to port 32123)";
|
||||
+ } else {
|
||||
+ console.log('Starting audio websocket on: ' + audio_url)
|
||||
+ wa.start();
|
||||
+ document.getElementById('toggleAudioButton').innerText = "Disable Audio";
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
// Show a status text in the top bar
|
||||
function status(text) {
|
||||
document.getElementById('status').textContent = text;
|
||||
@@ -135,12 +177,16 @@
|
||||
document.getElementById('sendCtrlAltDelButton')
|
||||
.onclick = sendCtrlAltDel;
|
||||
|
||||
+ document.getElementById('toggleAudioButton')
|
||||
+ .onclick = toggleAudio;
|
||||
+
|
||||
// Read parameters specified in the URL query string
|
||||
// By default, use the host and port of server that served this file
|
||||
const host = readQueryVariable('host', window.location.hostname);
|
||||
let port = readQueryVariable('port', window.location.port);
|
||||
const password = readQueryVariable('password');
|
||||
const path = readQueryVariable('path', 'websockify');
|
||||
+ const audio_path = readQueryVariable('audio_path', '');
|
||||
|
||||
// | | | | | |
|
||||
// | | | Connect | | |
|
||||
@@ -150,6 +196,7 @@
|
||||
|
||||
// Build the websocket URL used to connect
|
||||
let url;
|
||||
+ let audio_url;
|
||||
if (window.location.protocol === "https:") {
|
||||
url = 'wss';
|
||||
} else {
|
||||
@@ -159,6 +206,9 @@
|
||||
if(port) {
|
||||
url += ':' + port;
|
||||
}
|
||||
+ if(audio_path !== '') {
|
||||
+ audio_url = url + '/' + audio_path;
|
||||
+ }
|
||||
url += '/' + path;
|
||||
|
||||
// Creating a new RFB object will start a new connection
|
||||
@@ -179,6 +229,7 @@
|
||||
|
||||
<body>
|
||||
<div id="top_bar">
|
||||
+ <div id="toggleAudioButton">Enable Audio (requires access to port 32123)</div>
|
||||
<div id="status">Loading</div>
|
||||
<div id="sendCtrlAltDelButton">Send CtrlAltDel</div>
|
||||
</div>
|
||||
31
overlay/scripts/10-setup_user.sh
Normal file
31
overlay/scripts/10-setup_user.sh
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
PUID=${PUID:-99}
|
||||
PGID=${PGID:-100}
|
||||
UMASK=${UMASK:-000}
|
||||
USER_PASSWORD=${USER_PASSWORD:-password}
|
||||
|
||||
echo "**** Configure default user ****"
|
||||
|
||||
echo "Setting run user uid=${PGID}(${USER}) gid=${PUID}(${USER})"
|
||||
groupmod -o -g "${PGID}" ${USER}
|
||||
usermod -o -u "${PUID}" ${USER}
|
||||
|
||||
|
||||
echo "Setting umask to ${UMASK}";
|
||||
umask ${UMASK}
|
||||
|
||||
|
||||
# Setup home directory and permissions
|
||||
echo "Adding default home directory template"
|
||||
mkdir -p ${USER_HOME}
|
||||
chown -R ${PUID}:${PGID} /etc/home_directory_template
|
||||
rsync -aq /etc/home_directory_template/ ${USER_HOME}/
|
||||
|
||||
|
||||
# Set the root and user password
|
||||
echo "Setting root password"
|
||||
echo "root:${USER_PASSWORD}" | chpasswd
|
||||
echo "Setting user password"
|
||||
echo "${USER}:${USER_PASSWORD}" | chpasswd
|
||||
|
||||
echo "DONE"
|
||||
10
overlay/scripts/20-configre_sshd.sh
Normal file
10
overlay/scripts/20-configre_sshd.sh
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
# Generate new SSH host keys if they dont exist
|
||||
if [[ ! -f /etc/ssh/ssh_host_rsa_key ]]; then
|
||||
echo "**** Generating SSH keys ****";
|
||||
/usr/bin/ssh-keygen -A
|
||||
fi
|
||||
mkdir -p /run/sshd
|
||||
chmod 744 /run/sshd
|
||||
|
||||
echo "DONE"
|
||||
32
overlay/scripts/30-configure_system_paths.sh
Normal file
32
overlay/scripts/30-configure_system_paths.sh
Normal file
@@ -0,0 +1,32 @@
|
||||
# Configure system paths
|
||||
echo "**** Configure system paths ****";
|
||||
sed -i "/ <user>/c\ <user>${USER}</user>" /usr/share/dbus-1/system.conf
|
||||
if [ ! -d /tmp/xdg ]; then
|
||||
mkdir -p /tmp/xdg
|
||||
fi
|
||||
|
||||
echo "Configure dbus";
|
||||
# Remove old dbus session
|
||||
rm -rf ${USER_HOME}/.dbus/session-bus/* 2> /dev/null
|
||||
# Remove old dbus pids
|
||||
mkdir -p /var/run/dbus
|
||||
chown -R ${PUID}:${PGID} /var/run/dbus/
|
||||
chmod -R 770 /var/run/dbus/
|
||||
# Generate a dbus machine ID
|
||||
dbus-uuidgen > /var/lib/dbus/machine-id
|
||||
|
||||
echo "Configure X Windows context"
|
||||
chown -R ${PUID}:${PGID} /tmp/xdg
|
||||
chmod -R 0700 /tmp/xdg
|
||||
|
||||
echo "Configure X Windows session"
|
||||
rm -rfv /tmp/.ICE-unix*
|
||||
mkdir -p /tmp/.ICE-unix
|
||||
chown root:root /tmp/.ICE-unix/
|
||||
chmod 1777 /tmp/.ICE-unix/
|
||||
|
||||
echo "Remove old lockfiles"
|
||||
find /var/run/dbus -name "pid" -exec rm -f {} \;
|
||||
find /tmp -name ".X99*" -exec rm -f {} \;
|
||||
|
||||
echo "DONE"
|
||||
19
overlay/scripts/40-setup_locale.sh
Normal file
19
overlay/scripts/40-setup_locale.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
current_local=$(head -n 1 /etc/locale.gen)
|
||||
user_local=$(echo ${USER_LOCALES} | cut -d ' ' -f 1)
|
||||
|
||||
if [ "${current_local}" != "${USER_LOCALES}" ]; then
|
||||
echo "**** Configuring Locales to ${USER_LOCALES} ****";
|
||||
rm /etc/locale.gen
|
||||
echo -e "${USER_LOCALES}\nen_US.UTF-8 UTF-8" > "/etc/locale.gen"
|
||||
export LANGUAGE="${user_local}"
|
||||
export LANG="${user_local}"
|
||||
export LC_ALL="${user_local}" 2> /dev/null
|
||||
sleep 2
|
||||
locale-gen
|
||||
update-locale LC_ALL="${user_local}"
|
||||
else
|
||||
echo "**** Locales already set correctly to ${USER_LOCALES} ****";
|
||||
fi
|
||||
|
||||
echo "DONE"
|
||||
20
overlay/scripts/50-configure_audio.sh
Normal file
20
overlay/scripts/50-configure_audio.sh
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
echo "**** Configure pulseaudio socket ****"
|
||||
sed -i 's|^; default-server.*$|default-server = unix:/tmp/pulseaudio.socket|' /etc/pulse/client.conf
|
||||
sed -i 's|^load-module module-native-protocol-unix.*$|load-module module-native-protocol-unix socket=/tmp/pulseaudio.socket auth-anonymous=1|' \
|
||||
/etc/pulse/default.pa
|
||||
chown -R ${USER} /etc/pulse
|
||||
|
||||
# Credits for this audio patch:
|
||||
# - https://github.com/novnc/noVNC/issues/302
|
||||
# - https://github.com/vexingcodes/dwarf-fortress-docker
|
||||
# - https://github.com/calebj/noVNC
|
||||
if [ -f /opt/noVNC/audio.patch ]; then
|
||||
echo "**** Patching noVNC with audio websocket ****"
|
||||
pushd /opt/noVNC/ &> /dev/null
|
||||
patch -p1 --input=/opt/noVNC/audio.patch --batch --quiet
|
||||
popd &> /dev/null
|
||||
rm /opt/noVNC/audio.patch
|
||||
fi
|
||||
|
||||
echo "DONE"
|
||||
64
overlay/scripts/80-configure_nvidia_driver.sh
Normal file
64
overlay/scripts/80-configure_nvidia_driver.sh
Normal file
@@ -0,0 +1,64 @@
|
||||
|
||||
# Fech NVIDIA GPU device (if one exists)
|
||||
if [ "$NVIDIA_VISIBLE_DEVICES" == "all" ]; then
|
||||
export gpu_select=$(nvidia-smi --format=csv --query-gpu=uuid 2> /dev/null | sed -n 2p)
|
||||
elif [ -z "$NVIDIA_VISIBLE_DEVICES" ]; then
|
||||
export gpu_select=$(nvidia-smi --format=csv --query-gpu=uuid 2> /dev/null | sed -n 2p)
|
||||
else
|
||||
export gpu_select=$(nvidia-smi --format=csv --id=$(echo "$NVIDIA_VISIBLE_DEVICES" | cut -d ',' -f1) --query-gpu=uuid | sed -n 2p)
|
||||
if [ -z "$gpu_select" ]; then
|
||||
export gpu_select=$(nvidia-smi --format=csv --query-gpu=uuid 2> /dev/null | sed -n 2p)
|
||||
fi
|
||||
fi
|
||||
|
||||
export nvidia_pci_address="$(nvidia-smi --format=csv --query-gpu=pci.bus_id --id="${gpu_select}" 2> /dev/null | sed -n 2p | cut -d ':' -f2,3)"
|
||||
export nvidia_gpu_name=$(nvidia-smi --format=csv --query-gpu=name --id="${gpu_select}" | sed -n 2p 2> /dev/null)
|
||||
export nvidia_host_driver_version="$(nvidia-smi 2> /dev/null | grep NVIDIA-SMI | cut -d ' ' -f3)"
|
||||
|
||||
function download_driver {
|
||||
mkdir -p ${USER_HOME}/.cache/nvidia
|
||||
|
||||
if [[ ! -f "${USER_HOME}/.cache/nvidia/NVIDIA_${nvidia_host_driver_version}.run" ]]; then
|
||||
echo "Downloading driver"
|
||||
wget -q --show-progress --progress=bar:force:noscroll \
|
||||
-O /tmp/NVIDIA.run \
|
||||
http://download.nvidia.com/XFree86/Linux-x86_64/${nvidia_host_driver_version}/NVIDIA-Linux-x86_64-${nvidia_host_driver_version}.run
|
||||
[[ $? -gt 0 ]] && echo "Error downloading driver. Exit!" && return 1
|
||||
|
||||
mv /tmp/NVIDIA.run ${USER_HOME}/.cache/nvidia/NVIDIA_${nvidia_host_driver_version}.run
|
||||
fi
|
||||
}
|
||||
|
||||
function install_driver {
|
||||
# Check here if the currently installed version matches using nvidia-settings
|
||||
nvidia_settings_version=$(nvidia-settings --version 2> /dev/null | grep version | cut -d ' ' -f 4)
|
||||
[[ "${nvidia_settings_version}x" == "${nvidia_host_driver_version}x" ]] && return 0
|
||||
|
||||
# Download the driver (if it does not yet exist locally)
|
||||
download_driver
|
||||
|
||||
echo "Installing driver"
|
||||
chmod +x ${USER_HOME}/.cache/nvidia/NVIDIA_${nvidia_host_driver_version}.run
|
||||
${USER_HOME}/.cache/nvidia/NVIDIA_${nvidia_host_driver_version}.run \
|
||||
--silent \
|
||||
--accept-license \
|
||||
--no-kernel-module \
|
||||
--install-compat32-libs \
|
||||
--no-nouveau-check \
|
||||
--no-nvidia-modprobe \
|
||||
--no-rpms \
|
||||
--no-backup \
|
||||
--no-check-for-alternate-installs \
|
||||
--no-libglx-indirect \
|
||||
--no-install-libglvnd \
|
||||
> ${USER_HOME}/.cache/nvidia/last_install.log 2>&1
|
||||
}
|
||||
|
||||
if [[ -z ${nvidia_pci_address} ]]; then
|
||||
echo "**** No NVIDIA device found ****";
|
||||
else
|
||||
echo "**** Found NVIDIA device '${nvidia_gpu_name}' ****";
|
||||
install_driver
|
||||
fi
|
||||
|
||||
echo "DONE"
|
||||
57
overlay/scripts/90-configure_xorg.sh
Normal file
57
overlay/scripts/90-configure_xorg.sh
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
# Fech NVIDIA GPU device (if one exists)
|
||||
if [ "$NVIDIA_VISIBLE_DEVICES" == "all" ]; then
|
||||
export gpu_select=$(nvidia-smi --format=csv --query-gpu=uuid 2> /dev/null | sed -n 2p)
|
||||
elif [ -z "$NVIDIA_VISIBLE_DEVICES" ]; then
|
||||
export gpu_select=$(nvidia-smi --format=csv --query-gpu=uuid 2> /dev/null | sed -n 2p)
|
||||
else
|
||||
export gpu_select=$(nvidia-smi --format=csv --id=$(echo "$NVIDIA_VISIBLE_DEVICES" | cut -d ',' -f1) --query-gpu=uuid | sed -n 2p)
|
||||
if [ -z "$gpu_select" ]; then
|
||||
export gpu_select=$(nvidia-smi --format=csv --query-gpu=uuid 2> /dev/null | sed -n 2p)
|
||||
fi
|
||||
fi
|
||||
|
||||
export nvidia_gpu_hex_id=$(nvidia-smi --format=csv --query-gpu=pci.bus_id --id="${gpu_select}" 2> /dev/null | sed -n 2p)
|
||||
|
||||
# Fech current configuration (if modified in UI)
|
||||
if [ -f "${USER_HOME}/.config/xfce4/xfconf/xfce-perchannel-xml/displays.xml" ]; then
|
||||
new_display_sizew=$(cat ${USER_HOME}/.config/xfce4/xfconf/xfce-perchannel-xml/displays.xml | grep Resolution | head -n1 | grep -oP '(?<=value=").*?(?=")' | cut -d'x' -f1)
|
||||
new_display_sizeh=$(cat ${USER_HOME}/.config/xfce4/xfconf/xfce-perchannel-xml/displays.xml | grep Resolution | head -n1 | grep -oP '(?<=value=").*?(?=")' | cut -d'x' -f2)
|
||||
if [ "${new_display_sizew}x" != "x" ] && [ "${new_display_sizeh}x" != "x" ]; then
|
||||
export DISPLAY_SIZEW="${new_display_sizew}"
|
||||
export DISPLAY_SIZEH="${new_display_sizeh}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Configure a NVIDIA X11 config
|
||||
function configure_nvidia_x_server {
|
||||
# Configure x to be run by anyone
|
||||
if grep -Fxq "allowed_users=console" /etc/X11/Xwrapper.config; then
|
||||
echo "Configure Xwrapper.config"
|
||||
sed -i "s/allowed_users=console/allowed_users=anybody/" /etc/X11/Xwrapper.config
|
||||
fi
|
||||
|
||||
# Configure xorg for NVIDIA
|
||||
echo "Configuring X11 with GPU ID: '${gpu_select}'"
|
||||
nvidia_gpu_hex_id=$(nvidia-smi --format=csv --query-gpu=pci.bus_id --id="${gpu_select}" 2> /dev/null | sed -n 2p)
|
||||
IFS=":." ARR_ID=(${nvidia_gpu_hex_id})
|
||||
unset IFS
|
||||
bus_id=PCI:$((16#${ARR_ID[1]})):$((16#${ARR_ID[2]})):$((16#${ARR_ID[3]}))
|
||||
echo "Configuring X11 with PCI bus ID: '${bus_id}'"
|
||||
export MODELINE=$(cvt -r "${DISPLAY_SIZEW}" "${DISPLAY_SIZEH}" "${DISPLAY_REFRESH}" | sed -n 2p)
|
||||
echo "Writing X11 config with ${MODELINE}"
|
||||
nvidia-xconfig --virtual="${DISPLAY_SIZEW}x${DISPLAY_SIZEH}" --depth="${DISPLAY_CDEPTH}" --mode=$(echo "${MODELINE}" | awk '{print $2}' | tr -d '"') --allow-empty-initial-configuration --no-probe-all-gpus --busid="${bus_id}" --only-one-x-screen --connected-monitor="${DISPLAY_VIDEO_PORT}"
|
||||
sed -i '/Driver\s\+"nvidia"/a\ Option "ModeValidation" "NoMaxPClkCheck, NoEdidMaxPClkCheck, NoMaxSizeCheck, NoHorizSyncCheck, NoVertRefreshCheck, NoVirtualSizeCheck, NoExtendedGpuCapabilitiesCheck, NoTotalSizeCheck, NoDualLinkDVICheck, NoDisplayPortBandwidthCheck, AllowNon3DVisionModes, AllowNonHDMI3DModes, AllowNonEdidModes, NoEdidHDMI2Check, AllowDpInterlaced"' /etc/X11/xorg.conf
|
||||
sed -i '/Section\s\+"Monitor"/a\ '"${MODELINE}" /etc/X11/xorg.conf
|
||||
}
|
||||
|
||||
|
||||
if [[ -z ${nvidia_gpu_hex_id} ]]; then
|
||||
echo "**** Generate default xorg.conf ****";
|
||||
# TODO: Configure xorg.conf with no NVIDIA GPU
|
||||
else
|
||||
echo "**** Generate NVIDIA xorg.conf ****";
|
||||
configure_nvidia_x_server
|
||||
fi
|
||||
|
||||
echo "DONE"
|
||||
Reference in New Issue
Block a user