22 Commits

Author SHA1 Message Date
0dd9597519 create pulseaudio sink, fixes #267. 2023-03-26 18:59:10 +02:00
334fcef407 update changelog. 2023-03-25 22:21:00 +01:00
e868ad4061 Merge branch 'pu/autoplay' 2023-03-25 22:19:43 +01:00
b41d0bf956 lint fix. 2023-03-25 22:19:01 +01:00
b62fa6ab8b kde disable autolock, fixes #266. 2023-03-25 22:09:17 +01:00
8dba9cff44 revert extra controls usecase. 2023-03-25 21:53:17 +01:00
217cc451ea add autoplay audio if possible 2023-03-24 21:33:32 +01:00
1f81bd3efc hide buttons in cast mode 2023-03-24 21:15:51 +01:00
50e5483661 turn on vaapi hwenc in intel images by default. 2023-03-19 19:13:11 +01:00
Bad
d2765c30fd readd -> read 2023-03-19 18:13:06 +01:00
76fc892823 fix video state sync, fixes #250. 2023-03-18 13:31:40 +01:00
ea99ce7753 fix log. 2023-03-18 13:11:42 +01:00
646eaced29 fix candidate error. 2023-03-18 13:11:02 +01:00
9104953ad9 upgrade node version in serve script. 2023-03-18 12:59:44 +01:00
c40243635f improved chinese and korean characters support, fixes #252. 2023-03-18 12:58:19 +01:00
8059002475 forgotten mobile styles for cast, fixes #254. 2023-03-18 12:48:18 +01:00
9daf83cc52 fix webrtc client gathering, #259. 2023-03-18 00:49:25 +01:00
0cebe465a2 fix build. 2023-03-17 20:47:53 +01:00
d9403d9c14 add gpu acceleration to docs. 2023-03-17 19:04:57 +01:00
8604a30744 add gpu flag to start server. 2023-03-17 18:28:13 +01:00
957e893cf6 fix nvidia chromium. 2023-03-17 18:27:34 +01:00
55005a6f8d Add nvidia docker gpu acceleration (#238)
* add nvidia dockerfile.

* add nvidia docker to build.

* remove vaapi.

* add google chrome and brave.

* upgrade to virtualgl 3.1.

* add disable-seccomp-filter-sandbox to chrome.

* use vgl display in vglrun.

* Revert "use vgl display in vglrun."

This reverts commit 0cd556b5d8.

* update chrome params.

* update changelog.

* update brave.

* update CI.
2023-03-17 01:12:35 +01:00
39 changed files with 905 additions and 59 deletions

View File

@ -79,7 +79,9 @@ RUN set -eux; \
# Japanese fonts # Japanese fonts
fonts-takao-mincho \ fonts-takao-mincho \
# Chinese fonts # Chinese fonts
fonts-wqy-zenhei; \ fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
# Korean fonts
fonts-wqy-microhei; \
# #
# create a non-root user # create a non-root user
groupadd --gid $USER_GID $USERNAME; \ groupadd --gid $USER_GID $USERNAME; \

View File

@ -85,7 +85,9 @@ RUN set -eux; \
# Japanese fonts # Japanese fonts
fonts-takao-mincho \ fonts-takao-mincho \
# Chinese fonts # Chinese fonts
fonts-wqy-zenhei; \ fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
# Korean fonts
fonts-wqy-microhei; \
# #
# create a non-root user # create a non-root user
groupadd --gid $USER_GID $USERNAME; \ groupadd --gid $USER_GID $USERNAME; \

View File

@ -88,7 +88,9 @@ RUN set -eux; \
# Japanese fonts # Japanese fonts
fonts-takao-mincho \ fonts-takao-mincho \
# Chinese fonts # Chinese fonts
fonts-wqy-zenhei; \ fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
# Korean fonts
fonts-wqy-microhei; \
# #
# create a non-root user # create a non-root user
groupadd --gid $USER_GID $USERNAME; \ groupadd --gid $USER_GID $USERNAME; \
@ -131,6 +133,7 @@ ENV DISPLAY=:99.0
ENV NEKO_PASSWORD=neko ENV NEKO_PASSWORD=neko
ENV NEKO_PASSWORD_ADMIN=admin ENV NEKO_PASSWORD_ADMIN=admin
ENV NEKO_BIND=:8080 ENV NEKO_BIND=:8080
ENV NEKO_HWENC=VAAPI
ENV RENDER_GID= ENV RENDER_GID=
# #

View File

@ -0,0 +1,197 @@
ARG UBUNTU_RELEASE=20.04
ARG CUDA_VERSION=11.2.2
#
# STAGE 1: SERVER
#
FROM golang:1.18-bullseye as server
WORKDIR /src
#
# install dependencies
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends git cmake make libx11-dev libxrandr-dev libxtst-dev \
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly; \
#
# install libclipboard
set -eux; \
cd /tmp; \
git clone --depth=1 https://github.com/jtanx/libclipboard; \
cd libclipboard; \
cmake .; \
make -j4; \
make install; \
rm -rf /tmp/libclipboard; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# build server
COPY server/ .
RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
#
# STAGE 2: CLIENT
#
FROM node:14-bullseye-slim as client
WORKDIR /src
#
# install dependencies
COPY client/package*.json ./
RUN npm install
#
# build client
COPY client/ .
RUN npm run build
#
# STAGE 3: RUNTIME
#
FROM nvcr.io/nvidia/cudagl:${CUDA_VERSION}-runtime-ubuntu${UBUNTU_RELEASE} as runtime
ARG UBUNTU_RELEASE
ARG CUDA_VERSION
# Make all NVIDIA GPUs visible by default
ENV NVIDIA_VISIBLE_DEVICES=all
# All NVIDIA driver capabilities should preferably be used, check `NVIDIA_DRIVER_CAPABILITIES` inside the container if things do not work
ENV NVIDIA_DRIVER_CAPABILITIES all
#
# set vgl-display to headless 3d gpu card/// correct values are egl[n] or /dev/dri/card0:if this is passed into container
ENV VGL_DISPLAY egl
#
# avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive
#
# set custom user
ARG USERNAME=neko
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN set -eux; \
apt-get update; \
#
# install dependencies
apt-get install -y --no-install-recommends wget ca-certificates supervisor; \
apt-get install -y --no-install-recommends pulseaudio dbus-x11 xserver-xorg-video-dummy; \
apt-get install -y --no-install-recommends libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx6; \
#
# hardware acclerations utilities
apt-get install -y --no-install-recommends libgtk-3-bin mesa-utils mesa-utils-extra mesa-va-drivers mesa-vulkan-drivers libvulkan-dev libvulkan-dev:i386 vdpauinfo; \
#
# gst
apt-get install -y --no-install-recommends libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-pulseaudio; \
#
# install fonts
apt-get install -y --no-install-recommends \
# Google emojis
fonts-noto-color-emoji \
# Japanese fonts
fonts-takao-mincho \
# Chinese fonts
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
# Korean fonts
fonts-wqy-microhei; \
#
# create a non-root user
groupadd --gid $USER_GID $USERNAME; \
useradd --uid $USER_UID --gid $USERNAME --shell /bin/bash --create-home $USERNAME; \
adduser $USERNAME audio; \
adduser $USERNAME video; \
adduser $USERNAME pulse; \
#
# setup pulseaudio
mkdir -p /home/$USERNAME/.config/pulse/; \
echo "default-server=unix:/tmp/pulseaudio.socket" > /home/$USERNAME/.config/pulse/client.conf; \
#
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
mkdir /tmp/.X11-unix; \
chmod 1777 /tmp/.X11-unix; \
chown $USERNAME /tmp/.X11-unix/; \
#
# make directories for neko
mkdir -p /etc/neko /var/www /var/log/neko; \
chmod 1777 /var/log/neko; \
chown $USERNAME /var/log/neko/; \
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# install and configure Vulkan manually
RUN set -eux; \
apt-get update; \
if [ "${UBUNTU_RELEASE}" = "18.04" ]; then apt-get install -y --no-install-recommends vulkan-utils; else apt-get install -y --no-install-recommends vulkan-tools; fi; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*; \
#
# configure vulkan
VULKAN_API_VERSION=$(dpkg -s libvulkan1 | grep -oP 'Version: [0-9|\.]+' | grep -oP '[0-9]+(\.[0-9]+)(\.[0-9]+)'); \
mkdir -p /etc/vulkan/icd.d/; \
echo "{\n\
\"file_format_version\" : \"1.0.0\",\n\
\"ICD\": {\n\
\"library_path\": \"libGLX_nvidia.so.0\",\n\
\"api_version\" : \"${VULKAN_API_VERSION}\"\n\
}\n\
}" > /etc/vulkan/icd.d/nvidia_icd.json
#
# install VirtualGL and make libraries available for preload
ARG VIRTUALGL_VERSION=3.1
RUN set -eux; \
apt-get update; \
wget "https://sourceforge.net/projects/virtualgl/files/virtualgl_${VIRTUALGL_VERSION}_amd64.deb"; \
wget "https://sourceforge.net/projects/virtualgl/files/virtualgl32_${VIRTUALGL_VERSION}_amd64.deb"; \
apt-get install -y --no-install-recommends ./virtualgl_${VIRTUALGL_VERSION}_amd64.deb ./virtualgl32_${VIRTUALGL_VERSION}_amd64.deb; \
rm -f "virtualgl_${VIRTUALGL_VERSION}_amd64.deb" "virtualgl32_${VIRTUALGL_VERSION}_amd64.deb"; \
chmod u+s /usr/lib/libvglfaker.so; \
chmod u+s /usr/lib/libdlfaker.so; \
chmod u+s /usr/lib32/libvglfaker.so; \
chmod u+s /usr/lib32/libdlfaker.so; \
chmod u+s /usr/lib/i386-linux-gnu/libvglfaker.so; \
chmod u+s /usr/lib/i386-linux-gnu/libdlfaker.so; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*;
#
# copy config files
COPY .docker/base/dbus /usr/bin/dbus
COPY .docker/base/default.pa /etc/pulse/default.pa
COPY .docker/base/supervisord.conf /etc/neko/supervisord.conf
COPY .docker/base/xorg.conf /etc/neko/xorg.conf
COPY .docker/base/nvidia/entrypoint.sh /bin/entrypoint.sh
#
# set default envs
ENV USER=$USERNAME
ENV DISPLAY=:99.0
ENV NEKO_PASSWORD=neko
ENV NEKO_PASSWORD_ADMIN=admin
ENV NEKO_BIND=:8080
#
# copy static files from previous stages
COPY --from=server /src/bin/neko /usr/bin/neko
COPY --from=client /src/dist/ /var/www
HEALTHCHECK --interval=10s --timeout=5s --retries=8 \
CMD wget -O - http://localhost:${NEKO_BIND#*:}/health || exit 1
#
# run neko
CMD ["/usr/bin/supervisord", "-c", "/etc/neko/supervisord.conf"]

View File

@ -1,5 +1,8 @@
#!/usr/bin/pulseaudio -nF #!/usr/bin/pulseaudio -nF
### Create virtual output device sink
load-module module-null-sink sink_name=audio_output sink_properties=device.description="Virtual\ Audio\ Output"
# Allow pulse audio to be accessed via TCP (from localhost only), to allow other users to access the virtual devices # Allow pulse audio to be accessed via TCP (from localhost only), to allow other users to access the virtual devices
load-module module-native-protocol-unix socket=/tmp/pulseaudio.socket auth-anonymous=1 load-module module-native-protocol-unix socket=/tmp/pulseaudio.socket auth-anonymous=1

View File

@ -0,0 +1,12 @@
#!/bin/bash -e
# Add VirtualGL directories to path
export PATH="${PATH}:/opt/VirtualGL/bin"
# Use VirtualGL to run wine with OpenGL if the GPU is available, otherwise use barebone wine
if [ -n "$(nvidia-smi --query-gpu=uuid --format=csv | sed -n 2p)" ]; then
exec vglrun "$@"
else
echo "No GPU detected"
exec "$@"
fi

View File

@ -0,0 +1,23 @@
ARG BASE_IMAGE=m1k1o/neko:nvidia-base
FROM $BASE_IMAGE
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends apt-transport-https curl openbox; \
#
# install brave browser
curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg; \
echo "deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg arch=amd64] https://brave-browser-apt-release.s3.brave.com/ stable main" \
| tee /etc/apt/sources.list.d/brave-browser-release.list; \
apt-get update; \
apt-get install -y --no-install-recommends brave-browser; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.nvidia.conf /etc/neko/supervisord/brave.conf
COPY --chown=neko preferences.json /home/neko/.config/brave/Default/Preferences
COPY policies.json /etc/brave/policies/managed/policies.json
COPY openbox.xml /etc/neko/openbox.xml

View File

@ -1,6 +1,17 @@
[program:brave] [program:brave]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s" environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/brave-browser --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/brave --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage command=/usr/bin/brave-browser
--window-position=0,0
--display=%(ENV_DISPLAY)s
--user-data-dir=/home/neko/.config/brave
--no-first-run
--start-maximized
--bwsi
--force-dark-mode
--disable-file-system
--disable-gpu
--disable-software-rasterizer
--disable-dev-shm-usage
stopsignal=INT stopsignal=INT
autorestart=true autorestart=true
priority=800 priority=800

View File

@ -0,0 +1,36 @@
[program:brave]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/bin/entrypoint.sh /usr/bin/brave-browser
--window-position=0,0
--display=%(ENV_DISPLAY)s
--user-data-dir=/home/neko/.config/brave
--no-first-run
--start-maximized
--bwsi
--force-dark-mode
--disable-file-system
--enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization
--ignore-gpu-blocklist
--disable-seccomp-filter-sandbox
--use-gl=egl
--disable-software-rasterizer
--disable-dev-shm-usage
stopsignal=INT
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/brave.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true
[program:openbox]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
autorestart=true
priority=300
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/openbox.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true

View File

@ -92,6 +92,21 @@ build_intel() {
fi fi
} }
build_nvidia() {
if [ "$1" = "base" ]
then
# build nvidia base
docker build -t "${BUILD_IMAGE}:nvidia-base" -f base/Dockerfile.nvidia "${BASE}"
elif [ -f "$1/Dockerfile.nvidia" ]
then
# build dedicated nvidia image
docker build -t "${BUILD_IMAGE}:nvidia-$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:nvidia-base" -f "$1/Dockerfile.nvidia" "$1/"
else
# try to build nvidia image with common Dockerfile
docker build -t "${BUILD_IMAGE}:nvidia-$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:nvidia-base" -f "$1/Dockerfile" "$1/"
fi
}
case $1 in case $1 in
client) build_client;; client) build_client;;
server) build_server;; server) build_server;;
@ -102,6 +117,9 @@ case $1 in
# build intel- images # build intel- images
intel-*) build_intel "${1#intel-}";; intel-*) build_intel "${1#intel-}";;
# build nvidia- images
nvidia-*) build_nvidia "${1#nvidia-}";;
# build images # build images
*) build "$1";; *) build "$1";;
esac esac

View File

@ -0,0 +1,37 @@
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
#
# install neko chromium
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends software-properties-common; \
# chromium-browser from default repo needs snap to be installed
# and nvidia base is ubuntu not debian
add-apt-repository ppa:system76/pop; \
apt-get update; \
apt-get install -y --no-install-recommends unzip chromium openbox; \
#
# install widevine module
CHROMIUM_DIR="/usr/lib/chromium"; \
WIDEVINE_VERSION=$(wget --quiet -O - https://dl.google.com/widevine-cdm/versions.txt | tail -n 1); \
wget -O /tmp/widevine.zip "https://dl.google.com/widevine-cdm/${WIDEVINE_VERSION}-linux-x64.zip"; \
mkdir -p "${CHROMIUM_DIR}/WidevineCdm/_platform_specific/linux_x64"; \
unzip -p /tmp/widevine.zip LICENSE.txt > "${CHROMIUM_DIR}/WidevineCdm/LICENSE"; \
unzip -p /tmp/widevine.zip manifest.json > "${CHROMIUM_DIR}/WidevineCdm/manifest.json"; \
unzip -p /tmp/widevine.zip libwidevinecdm.so > "${CHROMIUM_DIR}/WidevineCdm/_platform_specific/linux_x64/libwidevinecdm.so"; \
find "${CHROMIUM_DIR}/WidevineCdm" -type d -exec chmod 0755 '{}' \;; \
find "${CHROMIUM_DIR}/WidevineCdm" -type f -exec chmod 0644 '{}' \;; \
rm /tmp/widevine.zip; \
#
# clean up
apt-get --purge autoremove -y unzip; \
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.nvidia.conf /etc/neko/supervisord/chromium.conf
COPY --chown=neko preferences.json /home/neko/.config/chromium/Default/Preferences
COPY policies.json /etc/chromium/policies/managed/policies.json
COPY openbox.xml /etc/neko/openbox.xml

View File

@ -1,6 +1,17 @@
[program:chromium] [program:chromium]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s" environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/chromium --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/chromium --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage command=/usr/bin/chromium
--window-position=0,0
--display=%(ENV_DISPLAY)s
--user-data-dir=/home/neko/.config/chromium
--no-first-run
--start-maximized
--bwsi
--force-dark-mode
--disable-file-system
--disable-gpu
--disable-software-rasterizer
--disable-dev-shm-usage
stopsignal=INT stopsignal=INT
autorestart=true autorestart=true
priority=800 priority=800

View File

@ -0,0 +1,36 @@
[program:chromium]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/bin/entrypoint.sh /usr/bin/chromium
--window-position=0,0
--display=%(ENV_DISPLAY)s
--user-data-dir=/home/neko/.config/chromium
--no-first-run
--start-maximized
--bwsi
--force-dark-mode
--disable-file-system
--enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization
--ignore-gpu-blocklist
--disable-seccomp-filter-sandbox
--use-gl=egl
--disable-software-rasterizer
--disable-dev-shm-usage
stopsignal=INT
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/chromium.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true
[program:openbox]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
autorestart=true
priority=300
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/openbox.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true

View File

@ -0,0 +1,21 @@
ARG BASE_IMAGE=m1k1o/neko:nvidia-base
FROM $BASE_IMAGE
ARG SRC_URL="https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb"
#
# install google chrome
RUN set -eux; apt-get update; \
wget -O /tmp/google-chrome.deb "${SRC_URL}"; \
apt-get install -y --no-install-recommends openbox /tmp/google-chrome.deb; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.nvidia.conf /etc/neko/supervisord/google-chrome.conf
COPY --chown=neko preferences.json /home/neko/.config/google-chrome/Default/Preferences
COPY policies.json /etc/opt/chrome/policies/managed/policies.json
COPY openbox.xml /etc/neko/openbox.xml

View File

@ -1,6 +1,17 @@
[program:google-chrome] [program:google-chrome]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s" environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/google-chrome --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/google-chrome --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage command=/usr/bin/google-chrome
--window-position=0,0
--display=%(ENV_DISPLAY)s
--user-data-dir=/home/neko/.config/google-chrome
--no-first-run
--start-maximized
--bwsi
--force-dark-mode
--disable-file-system
--disable-gpu
--disable-software-rasterizer
--disable-dev-shm-usage
stopsignal=INT stopsignal=INT
autorestart=true autorestart=true
priority=800 priority=800

View File

@ -0,0 +1,36 @@
[program:google-chrome]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/bin/entrypoint.sh /usr/bin/google-chrome
--window-position=0,0
--display=%(ENV_DISPLAY)s
--user-data-dir=/home/neko/.config/chromium
--no-first-run
--start-maximized
--bwsi
--force-dark-mode
--disable-file-system
--enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization
--ignore-gpu-blocklist
--disable-seccomp-filter-sandbox
--use-gl=egl
--disable-software-rasterizer
--disable-dev-shm-usage
stopsignal=INT
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/google-chrome.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true
[program:openbox]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
autorestart=true
priority=300
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/openbox.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true

View File

@ -15,6 +15,11 @@ RUN set -eux; apt-get update; \
apt-get clean -y; \ apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/* rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# disable autolock
RUN kwriteconfig5 --file /home/neko/.config/kscreenlockerrc --group Daemon --key Autolock false; \
chown neko:neko /home/neko/.config/kscreenlockerrc
# #
# copy configuation files # copy configuation files
COPY supervisord.conf /etc/neko/supervisord/kde.conf COPY supervisord.conf /etc/neko/supervisord/kde.conf

View File

@ -0,0 +1,24 @@
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
ARG API_URL="https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-stable/"
#
# install microsoft edge
RUN set -eux; apt-get update; \
#
# fetch latest release
SRC_URL="${API_URL}$(wget -O - "${API_URL}" 2>/dev/null | sed -n 's/.*href="\([^"]*\).*/\1/p' | tail -1)"; \
wget -O /tmp/microsoft-edge.deb "${SRC_URL}"; \
apt-get install -y --no-install-recommends openbox /tmp/microsoft-edge.deb; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.nvidia.conf /etc/neko/supervisord/microsoft-edge.conf
COPY --chown=neko preferences.json /home/neko/.config/microsoft-edge/Default/Preferences
COPY policies.json /etc/opt/edge/policies/managed/policies.json
COPY openbox.xml /etc/neko/openbox.xml

View File

@ -1,6 +1,17 @@
[program:microsoft-edge] [program:microsoft-edge]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s" environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/microsoft-edge --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/microsoft-edge --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage command=/usr/bin/microsoft-edge
--window-position=0,0
--display=%(ENV_DISPLAY)s
--user-data-dir=/home/neko/.config/microsoft-edge
--no-first-run
--start-maximized
--bwsi
--force-dark-mode
--disable-file-system
--disable-gpu
--disable-software-rasterizer
--disable-dev-shm-usage
stopsignal=INT stopsignal=INT
autorestart=true autorestart=true
priority=800 priority=800

View File

@ -0,0 +1,36 @@
[program:microsoft-edge]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/bin/entrypoint.sh /usr/bin/microsoft-edge
--window-position=0,0
--display=%(ENV_DISPLAY)s
--user-data-dir=/home/neko/.config/microsoft-edge
--no-first-run
--start-maximized
--bwsi
--force-dark-mode
--disable-file-system
--enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization
--ignore-gpu-blocklist
--disable-seccomp-filter-sandbox
--use-gl=egl
--disable-software-rasterizer
--disable-dev-shm-usage
stopsignal=INT
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/microsoft-edge.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true
[program:openbox]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
autorestart=true
priority=300
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/openbox.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true

View File

@ -17,5 +17,9 @@ fi
docker run --rm -it \ docker run --rm -it \
-v "${PWD}/../server:/src" \ -v "${PWD}/../server:/src" \
--entrypoint="go" \ -e GIT_COMMIT=`git rev-parse --short HEAD` \
neko_dev_server build -o "bin/neko" "cmd/neko/main.go" -e GIT_BRANCH=`git rev-parse --symbolic-full-name --abbrev-ref HEAD` \
-e GIT_DIRTY=`git diff-index --quiet HEAD -- || echo "✗-"` \
--entrypoint="bash" \
--workdir="/src" \
neko_dev_server ./build

View File

@ -16,7 +16,7 @@ if [ ! -d "${PWD}/../client/node_modules" ] || [ "$1" == "-i" ]; then
-v "${PWD}/../client:/app" \ -v "${PWD}/../client:/app" \
--workdir="/app" \ --workdir="/app" \
--entrypoint="npm" \ --entrypoint="npm" \
node:14-buster-slim install node:18-bullseye-slim install
fi fi
docker run --rm -it \ docker run --rm -it \
@ -25,5 +25,5 @@ docker run --rm -it \
-e "VUE_APP_SERVER_PORT=${SERVER_PORT}" \ -e "VUE_APP_SERVER_PORT=${SERVER_PORT}" \
--workdir="/app" \ --workdir="/app" \
--entrypoint="npm" \ --entrypoint="npm" \
node:14-buster-slim run serve node:18-bullseye-slim run serve

View File

@ -17,8 +17,22 @@ if [ ! -f "${BINARY_PATH}" ] || [ "$1" == "-r" ]; then
./rebuild-server ./rebuild-server
fi fi
# if image starts with nvidia- add --gpus all
if [[ "${SERVER_TAG}" == "nvidia-"* ]]; then
GPU_FLAG="--gpus all"
echo "Nvidia GPU acceleration enabled"
fi
# if image starts with intel- add --device /dev/dri
if [[ "${SERVER_TAG}" == "intel-"* ]]; then
GPU_FLAG="--device /dev/dri"
echo "Intel GPU acceleration enabled"
fi
# use --gpus all to enable GPU acceleration
docker run --rm -it \ docker run --rm -it \
--name "neko_dev" \ --name "neko_dev" \
$GPU_FLAG \
-p "${SERVER_PORT}:8080" \ -p "${SERVER_PORT}:8080" \
-p "${SERVER_EPR}:${SERVER_EPR}/udp" \ -p "${SERVER_EPR}:${SERVER_EPR}/udp" \
-e "NEKO_SCREEN=1920x1080@60" \ -e "NEKO_SCREEN=1920x1080@60" \

122
.github/workflows/ghcr-nvidia.yml vendored Normal file
View File

@ -0,0 +1,122 @@
name: "nvidia gpu supported images"
on:
push:
tags:
- 'v*'
env:
REGISTRY: ghcr.io
IMAGE_NAME: m1k1o/neko
TAG_PREFIX: nvidia-
BASE_DOCKERFILE: Dockerfile.nvidia
PLATFORMS: linux/amd64
jobs:
build-base:
runs-on: ubuntu-latest
#
# do not run on forks
#
if: github.repository_owner == 'm1k1o'
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Extract metadata (tags, labels) for Docker
uses: docker/metadata-action@v3
id: meta
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ env.TAG_PREFIX }}base
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,format=long
-
name: Log in to the Container registry
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GHCR_ACCESS_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: ./
file: .docker/base/${{ env.BASE_DOCKERFILE }}
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build:
runs-on: ubuntu-latest
#
# do not run on forks
#
if: github.repository_owner == 'm1k1o'
needs: [ build-base ]
strategy:
# Will build all images even if some fail.
matrix:
include:
- tag: brave
dockerfile: Dockerfile.nvidia
- tag: chromium
dockerfile: Dockerfile.nvidia
- tag: google-chrome
dockerfile: Dockerfile.nvidia
- tag: microsoft-edge
dockerfile: Dockerfile.nvidia
env:
TAG_NAME: ${{ matrix.tag }}
DOCKERFILE: ${{ matrix.dockerfile }}
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Extract metadata (tags, labels) for Docker
uses: docker/metadata-action@v3
id: meta
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ env.TAG_PREFIX }}${{ env.TAG_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,format=long
-
name: Log in to the Container registry
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GHCR_ACCESS_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .docker/${{ env.TAG_NAME }}
file: .docker/${{ env.TAG_NAME }}/${{ env.DOCKERFILE }}
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
BASE_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ env.TAG_PREFIX }}base:sha-${{ github.sha }}

View File

@ -1,17 +1,22 @@
<template> <template>
<div id="neko" :class="[side ? 'expanded' : '']"> <div id="neko" :class="[!videoOnly && side ? 'expanded' : '']">
<template v-if="!$client.supported"> <template v-if="!$client.supported">
<neko-unsupported /> <neko-unsupported />
</template> </template>
<template v-else> <template v-else>
<main class="neko-main"> <main class="neko-main">
<div v-if="!hideControls" class="header-container"> <div v-if="!videoOnly" class="header-container">
<neko-header /> <neko-header />
</div> </div>
<div class="video-container"> <div class="video-container">
<neko-video ref="video" :hideControls="hideControls" @control-attempt="controlAttempt" /> <neko-video
ref="video"
:hideControls="hideControls"
:extraControls="isEmbedMode"
@control-attempt="controlAttempt"
/>
</div> </div>
<div v-if="!hideControls" class="room-container"> <div v-if="!videoOnly" class="room-container">
<neko-members /> <neko-members />
<div class="room-menu"> <div class="room-menu">
<div class="settings"> <div class="settings">
@ -26,11 +31,11 @@
</div> </div>
</div> </div>
</main> </main>
<neko-side v-if="!hideControls && side" /> <neko-side v-if="!videoOnly && side" />
<neko-connect v-if="!connected" /> <neko-connect v-if="!connected" />
<neko-about v-if="about" /> <neko-about v-if="about" />
<notifications <notifications
v-if="!hideControls" v-if="!videoOnly"
group="neko" group="neko"
position="top left" position="top left"
style="top: 50px; pointer-events: none" style="top: 50px; pointer-events: none"
@ -176,10 +181,22 @@
shakeKbd = false shakeKbd = false
get hideControls() { get isCastMode() {
return !!new URL(location.href).searchParams.get('cast') return !!new URL(location.href).searchParams.get('cast')
} }
get isEmbedMode() {
return !!new URL(location.href).searchParams.get('embed')
}
get hideControls() {
return this.isCastMode
}
get videoOnly() {
return this.isCastMode || this.isEmbedMode
}
@Watch('hideControls', { immediate: true }) @Watch('hideControls', { immediate: true })
onHideControls(enabled: boolean) { onHideControls(enabled: boolean) {
if (enabled) { if (enabled) {

View File

@ -22,25 +22,25 @@
@mouseenter.stop.prevent="onMouseEnter" @mouseenter.stop.prevent="onMouseEnter"
@mouseleave.stop.prevent="onMouseLeave" @mouseleave.stop.prevent="onMouseLeave"
/> />
<div v-if="!playing && playable" class="player-overlay" @click.stop.prevent="toggle"> <div v-if="!playing && playable" class="player-overlay" @click.stop.prevent="playAndUnmute">
<i class="fas fa-play-circle" /> <i class="fas fa-play-circle" />
</div> </div>
<div v-if="mutedOverlay && muted" class="player-overlay" @click.stop.prevent="unmute"> <div v-else-if="mutedOverlay && muted" class="player-overlay" @click.stop.prevent="unmute">
<i class="fas fa-volume-up" /> <i class="fas fa-volume-up" />
</div> </div>
<div ref="aspect" class="player-aspect" /> <div ref="aspect" class="player-aspect" />
</div> </div>
<ul v-if="!fullscreen" class="video-menu top"> <ul v-if="!fullscreen && !hideControls" class="video-menu top">
<li><i @click.stop.prevent="requestFullscreen" class="fas fa-expand"></i></li> <li><i @click.stop.prevent="requestFullscreen" class="fas fa-expand"></i></li>
<li v-if="admin"><i @click.stop.prevent="openResolution" class="fas fa-desktop"></i></li> <li v-if="admin"><i @click.stop.prevent="openResolution" class="fas fa-desktop"></i></li>
<li :class="hideControls || 'request-control'"> <li v-if="!implicitHosting" :class="extraControls || 'extra-control'">
<i <i
:class="[hosted && !hosting ? 'disabled' : '', !hosted && !hosting ? 'faded' : '', 'fas', 'fa-keyboard']" :class="[hosted && !hosting ? 'disabled' : '', !hosted && !hosting ? 'faded' : '', 'fas', 'fa-keyboard']"
@click.stop.prevent="toggleControl" @click.stop.prevent="toggleControl"
/> />
</li> </li>
</ul> </ul>
<ul v-if="!fullscreen" class="video-menu bottom"> <ul v-if="!fullscreen && !hideControls" class="video-menu bottom">
<li v-if="hosting && (!clipboard_read_available || !clipboard_write_available)"> <li v-if="hosting && (!clipboard_read_available || !clipboard_write_available)">
<i @click.stop.prevent="openClipboard" class="fas fa-clipboard"></i> <i @click.stop.prevent="openClipboard" class="fas fa-clipboard"></i>
</li> </li>
@ -106,12 +106,12 @@
} }
} }
&.request-control { /* usually extra controls are only shown on mobile */
&.extra-control {
display: none; display: none;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
&.request-control { &.extra-control {
display: inline-block; display: inline-block;
} }
} }
@ -223,13 +223,15 @@
@Ref('resolution') readonly _resolution!: Resolution @Ref('resolution') readonly _resolution!: Resolution
@Ref('clipboard') readonly _clipboard!: Clipboard @Ref('clipboard') readonly _clipboard!: Clipboard
// all controls are hidden (e.g. for cast mode)
@Prop(Boolean) readonly hideControls!: boolean @Prop(Boolean) readonly hideControls!: boolean
// extra controls are shown (e.g. for embed mode)
@Prop(Boolean) readonly extraControls!: boolean
private keyboard = GuacamoleKeyboard() private keyboard = GuacamoleKeyboard()
private observer = new ResizeObserver(this.onResize.bind(this)) private observer = new ResizeObserver(this.onResize.bind(this))
private focused = false private focused = false
private fullscreen = false private fullscreen = false
private startsMuted = true
private mutedOverlay = true private mutedOverlay = true
get admin() { get admin() {
@ -361,7 +363,6 @@
onMutedChanged(muted: boolean) { onMutedChanged(muted: boolean) {
if (this._video && this._video.muted != muted) { if (this._video && this._video.muted != muted) {
this._video.muted = muted this._video.muted = muted
this.startsMuted = muted
if (!muted) { if (!muted) {
this.mutedOverlay = false this.mutedOverlay = false
@ -384,9 +385,29 @@
} }
@Watch('playing') @Watch('playing')
onPlayingChanged(playing: boolean) { async onPlayingChanged(playing: boolean) {
if (this._video && this._video.paused && playing) { if (this._video && this._video.paused && playing) {
this.play() // if autoplay is disabled, play() will throw an error
// and we need to properly save the state otherwise we
// would be thinking we're playing when we're not
try {
await this._video.play()
} catch (err: any) {
if (!this._video.muted) {
// video.play() can fail if audio is set due restrictive
// browsers autoplay policy -> retry with muted audio
try {
this.$accessor.video.setMuted(true)
this._video.muted = true
await this._video.play()
} catch (err: any) {
// if it still fails, we're not playing anything
this.$accessor.video.pause()
}
} else {
this.$accessor.video.pause()
}
}
} }
if (this._video && !this._video.paused && !playing) { if (this._video && !this._video.paused && !playing) {
@ -424,11 +445,6 @@
this._video.addEventListener('canplaythrough', () => { this._video.addEventListener('canplaythrough', () => {
this.$accessor.video.setPlayable(true) this.$accessor.video.setPlayable(true)
if (this.autoplay) { if (this.autoplay) {
// start as muted due to restrictive browsers autoplay policy
if (this.startsMuted && (!document.hasFocus() || !this.$accessor.active)) {
this.$accessor.video.setMuted(true)
}
this.$nextTick(() => { this.$nextTick(() => {
this.$accessor.video.play() this.$accessor.video.play()
}) })
@ -560,6 +576,11 @@
} }
} }
playAndUnmute() {
this.$accessor.video.play()
this.$accessor.video.setMuted(false)
}
unmute() { unmute() {
this.$accessor.video.setMuted(false) this.$accessor.video.setMuted(false)
} }

View File

@ -203,11 +203,12 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
return return
} }
this._peer = new RTCPeerConnection()
if (lite !== true) { if (lite !== true) {
this._peer = new RTCPeerConnection({ this._peer = new RTCPeerConnection({
iceServers: servers, iceServers: servers,
}) })
} else {
this._peer = new RTCPeerConnection()
} }
this._peer.onconnectionstatechange = () => { this._peer.onconnectionstatechange = () => {
@ -251,11 +252,28 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
this._peer.ontrack = this.onTrack.bind(this) this._peer.ontrack = this.onTrack.bind(this)
this._peer.onicecandidate = (event: RTCPeerConnectionIceEvent) => {
if (!event.candidate) {
this.emit('debug', `sent all local ICE candidates`)
return
}
const init = event.candidate.toJSON()
this.emit('debug', `sending local ICE candidate`, init)
this._ws!.send(
JSON.stringify({
event: EVENT.SIGNAL.CANDIDATE,
data: JSON.stringify(init),
}),
)
}
this._peer.onnegotiationneeded = async () => { this._peer.onnegotiationneeded = async () => {
this.emit('warn', `negotiation is needed`) this.emit('warn', `negotiation is needed`)
const d = await this._peer!.createOffer() const d = await this._peer!.createOffer()
this._peer!.setLocalDescription(d) await this._peer!.setLocalDescription(d)
this._ws!.send( this._ws!.send(
JSON.stringify({ JSON.stringify({
@ -277,10 +295,10 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
return return
} }
this._peer.setRemoteDescription({ type: 'offer', sdp }) await this._peer.setRemoteDescription({ type: 'offer', sdp })
for (const candidate of this._candidates) { for (const candidate of this._candidates) {
this._peer.addIceCandidate(candidate) await this._peer.addIceCandidate(candidate)
} }
this._candidates = [] this._candidates = []
@ -310,7 +328,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
return return
} }
this._peer.setRemoteDescription({ type: 'answer', sdp }) await this._peer.setRemoteDescription({ type: 'answer', sdp })
} }
private async onMessage(e: MessageEvent) { private async onMessage(e: MessageEvent) {

View File

@ -5,16 +5,24 @@
### New Features ### New Features
- Added AV1 tag, metadata and pipeline. Unfortunately does not work yet, since the encoding is way too slow (by @mbattista). - Added AV1 tag, metadata and pipeline. Unfortunately does not work yet, since the encoding is way too slow (by @mbattista).
- Added `m1k1o/neko:kde` tag as an alternative to `m1k1o/neko:xfce`. - Added `m1k1o/neko:kde` tag as an alternative to `m1k1o/neko:xfce`.
- New VirtualGL version 3.1 was released, adding support for Chromium browsers to use Nvidia GPU acceleration!
- Added `?embed=1` parameter to the URL, which will hide the sidebar and the top bar, so that it can be embedded in other websites.
### Bugs ### Bugs
- Fixed TCP mux occasional freeze by adding write buffer to it. - Fixed TCP mux occasional freeze by adding write buffer to it.
- Fixed stereo problem in chromium-based browsers, where it was only as mono by adding `stereo=1` to opus SDP to clients answer. - Fixed stereo problem in chromium-based browsers, where it was only as mono by adding `stereo=1` to opus SDP to clients answer.
- Fixed keysym mapping for unknown keycodes, which was causing some key combinations to not work on some keyboards. - Fixed keysym mapping for unknown keycodes, which was causing some key combinations to not work on some keyboards.
- Fixed a bug where `max_fps=0` would lead to an invalid pipeline. - Fixed a bug where `max_fps=0` would lead to an invalid pipeline.
- Fixed client side webrtc ICE gathering, so that neko can be used without exposed ports, only with STUN and TURN servers.
- Fixed play state synchronization, when autoplay is disabled.
### Misc ### Misc
- Updated to go 1.19 and Node 18, removed go-events as dependency (by @mbattista). - Updated to go 1.19 and Node 18, removed go-events as dependency (by @mbattista).
- Added adaptive framerate which now streams in the framerate you selected from the dropdown. - Added adaptive framerate which now streams in the framerate you selected from the dropdown.
- Improved chinese and korean characters support.
- Disabled autolock for kde, so that it does not lock the screen when you are not using it.
- Refactored autoplay, so that it will start playing audio, if it's allowed by the browser (by @urbanekpj).
- Renamed pulseaudio sink from `auto_null` to `audio_output`, because it was ignored by KDE.
## [n.eko v2.7](https://github.com/m1k1o/neko/releases/tag/v2.7) ## [n.eko v2.7](https://github.com/m1k1o/neko/releases/tag/v2.7)

View File

@ -74,6 +74,13 @@ For images with VAAPI GPU hardware acceleration using intel drivers use:
- `ghcr.io/m1k1o/neko/intel-xfce:latest` - `ghcr.io/m1k1o/neko/intel-xfce:latest`
- `ghcr.io/m1k1o/neko/intel-kde:latest` - `ghcr.io/m1k1o/neko/intel-kde:latest`
For images with Nvidia GPU hardware acceleration using EGL use:
- `ghcr.io/m1k1o/neko/nvidia-chromium:latest`
- `ghcr.io/m1k1o/neko/nvidia-google-chrome:latest`
- `ghcr.io/m1k1o/neko/nvidia-microsoft-edge:latest`
- `ghcr.io/m1k1o/neko/nvidia-brave:latest`
GHCR images are built using GitHub actions for every tag. GHCR images are built using GitHub actions for every tag.
### Networking: ### Networking:
@ -155,6 +162,57 @@ services:
"RestoreOnStartup": 1, "RestoreOnStartup": 1,
``` ```
### Nvidia GPU acceleration
You need to have nvidia-docker installed, start the container with `--gpus all` flag and use images built for nvidia (see above).
```bash
docker run -d --gpus all \
-p 8080:8080 \
-p 56000-56100:56000-56100/udp \
-e NEKO_SCREEN=1920x1080@30 \
-e NEKO_PASSWORD=neko \
-e NEKO_PASSWORD_ADMIN=admin \
-e NEKO_EPR=56000-56100 \
-e NEKO_NAT1TO1=192.168.1.10 \
-e NEKO_ICELITE=1 \
--shm-size=2gb \
--cap-add=SYS_ADMIN \
--name neko \
ghcr.io/m1k1o/neko/nvidia-google-chrome:latest
```
If you want to use docker-compose, you can use this example:
```yaml
version: "3.4"
services:
neko:
image: "ghcr.io/m1k1o/neko/nvidia-google-chrome:latest"
restart: "unless-stopped"
shm_size: "2gb"
ports:
- "8080:8080"
- "56000-56100:56000-56100/udp"
cap_add:
- SYS_ADMIN
environment:
NEKO_SCREEN: '1920x1080@30'
NEKO_PASSWORD: neko
NEKO_PASSWORD_ADMIN: admin
NEKO_EPR: 56000-56100
NEKO_NAT1TO1: 192.168.1.10
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
```
Note, currently only browser GPU acceleration is supported, not encoding.
### Want to use VPN for your n.eko browsing? ### Want to use VPN for your n.eko browsing?
- Check this out: https://github.com/m1k1o/neko-vpn - Check this out: https://github.com/m1k1o/neko-vpn
@ -172,6 +230,7 @@ services:
- Adding `?pwd=<password>` will prefill password. - Adding `?pwd=<password>` will prefill password.
- Adding `?usr=<display-name>` will prefill username. - Adding `?usr=<display-name>` will prefill username.
- Adding `?cast=1` will hide all control and show only video. - Adding `?cast=1` will hide all control and show only video.
- Adding `?embed=1` will hide most additional components and show only video.
- e.g. `http(s)://<URL:Port>/?pwd=neko&usr=guest&cast=1` - e.g. `http(s)://<URL:Port>/?pwd=neko&usr=guest&cast=1`
### Screen size ### Screen size

View File

@ -25,7 +25,7 @@ nat1to1: <ip>
- Control protection means, users can gain control only if at least one admin is in the room. - Control protection means, users can gain control only if at least one admin is in the room.
- e.g. `false` - e.g. `false`
#### `NEKO_IMPLICIT_CONTROL`: #### `NEKO_IMPLICIT_CONTROL`:
- If enabled members can gain control implicitly, they don't needd to request control. - If enabled members can gain control implicitly, they don't need to request control.
- e.g. `false` - e.g. `false`
#### `NEKO_LOCKS`: #### `NEKO_LOCKS`:
- Resources, that will be locked when starting, separated by whitespace. - Resources, that will be locked when starting, separated by whitespace.
@ -167,7 +167,7 @@ Flags:
--cert string path to the SSL cert used to secure the neko server --cert string path to the SSL cert used to secure the neko server
--control_protection control protection means, users can gain control only if at least one admin is in the room --control_protection control protection means, users can gain control only if at least one admin is in the room
--cors strings list of allowed origins for CORS (default [*]) --cors strings list of allowed origins for CORS (default [*])
--device string audio device to capture (default "auto_null.monitor") --device string audio device to capture (default "audio_output.monitor")
--display string XDisplay to capture (default ":99.0") --display string XDisplay to capture (default ":99.0")
--epr string limits the pool of ephemeral ports that ICE UDP connections can allocate from (default "59000-59100") --epr string limits the pool of ephemeral ports that ICE UDP connections can allocate from (default "59000-59100")
--file_transfer_enabled enable file transfer feature (default false) --file_transfer_enabled enable file transfer feature (default false)

View File

@ -3,8 +3,22 @@
set -ex set -ex
BUILD_TIME=`date -u +'%Y-%m-%dT%H:%M:%SZ'` BUILD_TIME=`date -u +'%Y-%m-%dT%H:%M:%SZ'`
GIT_COMMIT=`git rev-parse --short HEAD`
GIT_BRANCH=`git rev-parse --symbolic-full-name --abbrev-ref HEAD`
GIT_DIRTY=`git diff-index --quiet HEAD -- || echo "✗-"`
go build -o bin/neko -ldflags "-s -X 'm1k1o/neko.buildDate=${BUILD_TIME}' -X 'm1k1o/neko.gitCommit=${GIT_DIRTY}${GIT_COMMIT}' -X 'm1k1o/neko.gitBranch=${GIT_BRANCH}'" -i cmd/neko/main.go #
# set git build variables if git exists
if git status > /dev/null 2>&1 && [ -z $GIT_COMMIT ] && [ -z $GIT_BRANCH ] && [ -z $GIT_DIRTY ];
then
GIT_COMMIT=`git rev-parse --short HEAD`
GIT_BRANCH=`git rev-parse --symbolic-full-name --abbrev-ref HEAD`
GIT_DIRTY=`git diff-index --quiet HEAD -- || echo "✗-"`
fi
go build \
-o bin/neko \
-ldflags "
-s -w
-X 'm1k1o/neko.buildDate=${BUILD_TIME}'
-X 'm1k1o/neko.gitCommit=${GIT_DIRTY}${GIT_COMMIT}'
-X 'm1k1o/neko.gitBranch=${GIT_BRANCH}'
" \
cmd/neko/main.go;

View File

@ -92,7 +92,7 @@ func (Capture) Init(cmd *cobra.Command) error {
// audio // audio
// //
cmd.PersistentFlags().String("device", "auto_null.monitor", "audio device to capture") cmd.PersistentFlags().String("device", "audio_output.monitor", "audio device to capture")
if err := viper.BindPFlag("device", cmd.PersistentFlags().Lookup("device")); err != nil { if err := viper.BindPFlag("device", cmd.PersistentFlags().Lookup("device")); err != nil {
return err return err
} }

View File

@ -131,6 +131,17 @@ func (session *Session) SignalLocalAnswer(sdp string) error {
}) })
} }
func (session *Session) SignalLocalCandidate(data string) error {
if session.socket == nil {
return nil
}
session.logger.Info().Msg("signal update - LocalCandidate")
return session.socket.Send(&message.SignalCandidate{
Event: event.SIGNAL_CANDIDATE,
Data: data,
})
}
func (session *Session) SignalRemoteOffer(sdp string) error { func (session *Session) SignalRemoteOffer(sdp string) error {
if session.peer == nil { if session.peer == nil {
return nil return nil
@ -154,14 +165,12 @@ func (session *Session) SignalRemoteAnswer(sdp string) error {
return session.peer.SetAnswer(sdp) return session.peer.SetAnswer(sdp)
} }
func (session *Session) SignalCandidate(data string) error { func (session *Session) SignalRemoteCandidate(data string) error {
if session.socket == nil { if session.socket == nil {
return nil return nil
} }
return session.socket.Send(&message.SignalCandidate{ session.logger.Info().Msg("signal update - RemoteCandidate")
Event: event.SIGNAL_CANDIDATE, return session.peer.SetCandidate(data)
Data: data,
})
} }
func (session *Session) destroy() error { func (session *Session) destroy() error {

View File

@ -40,9 +40,10 @@ type Session interface {
Send(v interface{}) error Send(v interface{}) error
SignalLocalOffer(sdp string) error SignalLocalOffer(sdp string) error
SignalLocalAnswer(sdp string) error SignalLocalAnswer(sdp string) error
SignalLocalCandidate(data string) error
SignalRemoteOffer(sdp string) error SignalRemoteOffer(sdp string) error
SignalRemoteAnswer(sdp string) error SignalRemoteAnswer(sdp string) error
SignalCandidate(data string) error SignalRemoteCandidate(data string) error
} }
type SessionManager interface { type SessionManager interface {

View File

@ -21,6 +21,7 @@ type Peer interface {
CreateAnswer() (string, error) CreateAnswer() (string, error)
SetOffer(sdp string) error SetOffer(sdp string) error
SetAnswer(sdp string) error SetAnswer(sdp string) error
SetCandidate(candidateString string) error
WriteData(v interface{}) error WriteData(v interface{}) error
Destroy() error Destroy() error
} }

View File

@ -1,6 +1,7 @@
package webrtc package webrtc
import ( import (
"encoding/json"
"sync" "sync"
"github.com/pion/webrtc/v3" "github.com/pion/webrtc/v3"
@ -49,6 +50,16 @@ func (peer *Peer) SetAnswer(sdp string) error {
return peer.connection.SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer}) return peer.connection.SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer})
} }
func (peer *Peer) SetCandidate(candidateString string) error {
var candidate webrtc.ICECandidateInit
err := json.Unmarshal([]byte(candidateString), &candidate)
if err != nil {
return err
}
return peer.connection.AddICECandidate(candidate)
}
func (peer *Peer) WriteData(v interface{}) error { func (peer *Peer) WriteData(v interface{}) error {
peer.mu.Lock() peer.mu.Lock()
defer peer.mu.Unlock() defer peer.mu.Unlock()

View File

@ -123,7 +123,6 @@ func (manager *WebRTCManager) initAPI() error {
LoggerFactory: logger, LoggerFactory: logger,
} }
_ = settings.SetEphemeralUDPPortRange(manager.config.EphemeralMin, manager.config.EphemeralMax)
settings.SetNAT1To1IPs(manager.config.NAT1To1IPs, webrtc.ICECandidateTypeHost) settings.SetNAT1To1IPs(manager.config.NAT1To1IPs, webrtc.ICECandidateTypeHost)
settings.SetICETimeouts(6*time.Second, 6*time.Second, 3*time.Second) settings.SetICETimeouts(6*time.Second, 6*time.Second, 3*time.Second)
settings.SetSRTPReplayProtectionWindow(512) settings.SetSRTPReplayProtectionWindow(512)
@ -168,12 +167,15 @@ func (manager *WebRTCManager) initAPI() error {
networkType = append(networkType, webrtc.NetworkTypeUDP4) networkType = append(networkType, webrtc.NetworkTypeUDP4)
manager.logger.Info().Int("port", manager.config.UDPMUX).Msg("using UDP MUX") manager.logger.Info().Int("port", manager.config.UDPMUX).Msg("using UDP MUX")
} else if manager.config.EphemeralMax != 0 {
_ = settings.SetEphemeralUDPPortRange(manager.config.EphemeralMin, manager.config.EphemeralMax)
networkType = append(networkType,
webrtc.NetworkTypeUDP4,
webrtc.NetworkTypeUDP6,
)
} }
// Enable support for TCP and UDP ICE candidates settings.SetNetworkTypes(networkType)
if len(networkType) > 0 {
settings.SetNetworkTypes(networkType)
}
// Create MediaEngine with selected codecs // Create MediaEngine with selected codecs
engine := webrtc.MediaEngine{} engine := webrtc.MediaEngine{}
@ -299,7 +301,7 @@ func (manager *WebRTCManager) CreatePeer(id string, session types.Session) (type
return return
} }
if err := session.SignalCandidate(string(candidateString)); err != nil { if err := session.SignalLocalCandidate(string(candidateString)); err != nil {
manager.logger.Warn().Err(err).Msg("sending SignalCandidate failed") manager.logger.Warn().Err(err).Msg("sending SignalCandidate failed")
return return
} }

View File

@ -87,6 +87,12 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
utils.Unmarshal(payload, raw, func() error { utils.Unmarshal(payload, raw, func() error {
return h.signalRemoteAnswer(id, session, payload) return h.signalRemoteAnswer(id, session, payload)
}), "%s failed", header.Event) }), "%s failed", header.Event)
case event.SIGNAL_CANDIDATE:
payload := &message.SignalCandidate{}
return errors.Wrapf(
utils.Unmarshal(payload, raw, func() error {
return h.signalRemoteCandidate(id, session, payload)
}), "%s failed", header.Event)
// Control Events // Control Events
case event.CONTROL_RELEASE: case event.CONTROL_RELEASE:

View File

@ -45,3 +45,7 @@ func (h *MessageHandler) signalRemoteAnswer(id string, session types.Session, pa
return nil return nil
} }
func (h *MessageHandler) signalRemoteCandidate(id string, session types.Session, payload *message.SignalCandidate) error {
return session.SignalRemoteCandidate(payload.Data)
}