initial commit

This commit is contained in:
Josh.5
2022-01-11 01:03:30 +13:00
parent d1daefe07d
commit 51e965de8e
23 changed files with 1489 additions and 2 deletions

47
overlay/entrypoint.sh Normal file
View 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

View 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]\$ '

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View 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

View 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>

View 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"

View 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"

View 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"

View 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"

View 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"

View 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"

View 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"