mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 08:45:38 +02:00
🐜 fix(runner): dbus directory and mesa install (#163)
should fix the missing dbus runtime directory issue, using mesa-git as it's less cumbersome to deal with package wise than normal mesa --------- Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com> Co-authored-by: Wanjohi <elviswanjohi47@gmail.com>
This commit is contained in:
committed by
GitHub
parent
9b8d187887
commit
fb77dc9572
17
.github/workflows/runner.yml
vendored
17
.github/workflows/runner.yml
vendored
@@ -46,6 +46,11 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Setup Docker Buildx
|
name: Setup Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
-
|
||||||
|
name: Set Swap Space
|
||||||
|
uses: pierotofy/set-swap-space@master
|
||||||
|
with:
|
||||||
|
swap-size-gb: 10
|
||||||
-
|
-
|
||||||
name: Cache Docker layers
|
name: Cache Docker layers
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -63,13 +68,13 @@ jobs:
|
|||||||
push: false
|
push: false
|
||||||
load: true
|
load: true
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
cache-from: type=local,src=/tmp/.buildx-cache
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
|
cache-to: type=local,dest=/tmp/.buildx-cache,mode=max
|
||||||
tags: nestri:runner
|
tags: nestri:runner
|
||||||
-
|
# -
|
||||||
name: Move cache
|
# name: Move cache
|
||||||
run: |
|
# run: |
|
||||||
rm -rf /tmp/.buildx-cache
|
# rm -rf /tmp/.buildx-cache
|
||||||
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
# mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
||||||
|
|
||||||
build-docker-main:
|
build-docker-main:
|
||||||
name: Build image on main
|
name: Build image on main
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ export default component$(() => {
|
|||||||
window.nestrikeyboard.dispose();
|
window.nestrikeyboard.dispose();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.nestrikeyboard = undefined;
|
window.nestrikeyboard = undefined;
|
||||||
|
// @ts-ignore
|
||||||
|
window.nestriLock = undefined;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -147,7 +149,7 @@ export default component$(() => {
|
|||||||
ref={canvas}
|
ref={canvas}
|
||||||
onClick$={async () => {
|
onClick$={async () => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (canvas.value && window.hasstream) {
|
if (canvas.value && window.hasstream && !window.nestriLock) {
|
||||||
// Do not use - unadjustedMovement: true - breaks input on linux
|
// Do not use - unadjustedMovement: true - breaks input on linux
|
||||||
await canvas.value.requestPointerLock();
|
await canvas.value.requestPointerLock();
|
||||||
await canvas.value.requestFullscreen()
|
await canvas.value.requestFullscreen()
|
||||||
@@ -168,14 +170,20 @@ export default component$(() => {
|
|||||||
window.navigator.keyboard.lock(keys).then(
|
window.navigator.keyboard.lock(keys).then(
|
||||||
() => {
|
() => {
|
||||||
console.log("keyboard lock success");
|
console.log("keyboard lock success");
|
||||||
|
// @ts-ignore
|
||||||
|
window.nestriLock = true;
|
||||||
}
|
}
|
||||||
).catch(
|
).catch(
|
||||||
(e: any) => {
|
(e: any) => {
|
||||||
console.log("keyboard lock failed: ", e);
|
console.log("keyboard lock failed: ", e);
|
||||||
|
// @ts-ignore
|
||||||
|
window.nestriLock = false;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
console.log("keyboard lock not supported, navigator is: ", window.navigator, navigator);
|
console.log("keyboard lock not supported, navigator is: ", window.navigator, navigator);
|
||||||
|
// @ts-ignore
|
||||||
|
window.nestriLock = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ FROM ${BASE_IMAGE} AS gst-builder
|
|||||||
WORKDIR /builder/
|
WORKDIR /builder/
|
||||||
|
|
||||||
# Grab build and rust packages #
|
# Grab build and rust packages #
|
||||||
RUN pacman -Syu --noconfirm meson pkgconf cmake git gcc make rustup \
|
RUN pacman -Sy --noconfirm meson pkgconf cmake git gcc make rustup \
|
||||||
gstreamer gst-plugins-base gst-plugins-good gst-plugin-rswebrtc
|
gstreamer gst-plugins-base gst-plugins-good gst-plugin-rswebrtc
|
||||||
|
|
||||||
# Setup stable rust toolchain #
|
# Setup stable rust toolchain #
|
||||||
@@ -34,7 +34,7 @@ FROM ${BASE_IMAGE} AS gstwayland-builder
|
|||||||
WORKDIR /builder/
|
WORKDIR /builder/
|
||||||
|
|
||||||
# Grab build and rust packages #
|
# Grab build and rust packages #
|
||||||
RUN pacman -Syu --noconfirm meson pkgconf cmake git gcc make rustup \
|
RUN pacman -Sy --noconfirm meson pkgconf cmake git gcc make rustup \
|
||||||
libxkbcommon wayland gstreamer gst-plugins-base gst-plugins-good libinput
|
libxkbcommon wayland gstreamer gst-plugins-base gst-plugins-good libinput
|
||||||
|
|
||||||
# Setup stable rust toolchain #
|
# Setup stable rust toolchain #
|
||||||
@@ -71,11 +71,19 @@ FROM ${BASE_IMAGE} AS runtime
|
|||||||
## Install Graphics, Media, and Audio packages ##
|
## Install Graphics, Media, and Audio packages ##
|
||||||
RUN sed -i '/#\[multilib\]/,/#Include = \/etc\/pacman.d\/mirrorlist/ s/#//' /etc/pacman.conf && \
|
RUN sed -i '/#\[multilib\]/,/#Include = \/etc\/pacman.d\/mirrorlist/ s/#//' /etc/pacman.conf && \
|
||||||
sed -i "s/#Color/Color/" /etc/pacman.conf && \
|
sed -i "s/#Color/Color/" /etc/pacman.conf && \
|
||||||
pacman --noconfirm -Syu archlinux-keyring && \
|
pacman --noconfirm -Sy archlinux-keyring && \
|
||||||
dirmngr </dev/null > /dev/null 2>&1 && \
|
dirmngr </dev/null > /dev/null 2>&1 && \
|
||||||
|
# Install mesa-git before Steam for simplicity
|
||||||
|
pacman --noconfirm -Sy mesa-git && \
|
||||||
# Install Steam
|
# Install Steam
|
||||||
pacman --noconfirm -S steam && \
|
pacman --noconfirm -Sy steam steam-native-runtime && \
|
||||||
pacman -Syu --noconfirm --needed \
|
# Clean up pacman cache
|
||||||
|
paccache -rk1 && \
|
||||||
|
rm -rf /usr/share/info/* && \
|
||||||
|
rm -rf /usr/share/man/* && \
|
||||||
|
rm -rf /usr/share/doc/
|
||||||
|
|
||||||
|
RUN pacman -Sy --noconfirm --needed \
|
||||||
# Graphics packages
|
# Graphics packages
|
||||||
sudo xorg-xwayland labwc wlr-randr mangohud \
|
sudo xorg-xwayland labwc wlr-randr mangohud \
|
||||||
# GStreamer and plugins
|
# GStreamer and plugins
|
||||||
@@ -84,6 +92,8 @@ RUN sed -i '/#\[multilib\]/,/#Include = \/etc\/pacman.d\/mirrorlist/ s/#//' /et
|
|||||||
gst-plugin-rswebrtc gst-plugin-rsrtp \
|
gst-plugin-rswebrtc gst-plugin-rsrtp \
|
||||||
# Audio packages
|
# Audio packages
|
||||||
pipewire pipewire-pulse pipewire-alsa wireplumber \
|
pipewire pipewire-pulse pipewire-alsa wireplumber \
|
||||||
|
# Non-latin fonts
|
||||||
|
noto-fonts-cjk \
|
||||||
# Other requirements
|
# Other requirements
|
||||||
supervisor jq chwd lshw pacman-contrib && \
|
supervisor jq chwd lshw pacman-contrib && \
|
||||||
# Clean up pacman cache
|
# Clean up pacman cache
|
||||||
@@ -93,11 +103,14 @@ RUN sed -i '/#\[multilib\]/,/#Include = \/etc\/pacman.d\/mirrorlist/ s/#//' /et
|
|||||||
rm -rf /usr/share/doc/*
|
rm -rf /usr/share/doc/*
|
||||||
|
|
||||||
|
|
||||||
|
# Regenerate locale
|
||||||
|
RUN locale-gen
|
||||||
|
|
||||||
## User ##
|
## User ##
|
||||||
# Create and setup user #
|
# Create and setup user #
|
||||||
ENV USER="nestri" \
|
ENV USER="nestri" \
|
||||||
UID=99 \
|
UID=1000 \
|
||||||
GID=100 \
|
GID=1000 \
|
||||||
USER_PWD="nestri1234"
|
USER_PWD="nestri1234"
|
||||||
|
|
||||||
RUN mkdir -p /home/${USER} && \
|
RUN mkdir -p /home/${USER} && \
|
||||||
@@ -114,7 +127,8 @@ RUN mkdir -p /run/user/${UID} && \
|
|||||||
# Groups #
|
# Groups #
|
||||||
RUN usermod -aG input root && usermod -aG input ${USER} && \
|
RUN usermod -aG input root && usermod -aG input ${USER} && \
|
||||||
usermod -aG video root && usermod -aG video ${USER} && \
|
usermod -aG video root && usermod -aG video ${USER} && \
|
||||||
usermod -aG render root && usermod -aG render ${USER}
|
usermod -aG render root && usermod -aG render ${USER} && \
|
||||||
|
usermod -aG seat root && usermod -aG seat ${USER}
|
||||||
|
|
||||||
## Copy files from builders ##
|
## Copy files from builders ##
|
||||||
# this is done here at end to not trigger full rebuild on changes to builder
|
# this is done here at end to not trigger full rebuild on changes to builder
|
||||||
@@ -139,6 +153,9 @@ ENV XDG_RUNTIME_DIR=/run/user/${UID} \
|
|||||||
# Required for NVIDIA.. they want to be special like that #
|
# Required for NVIDIA.. they want to be special like that #
|
||||||
ENV NVIDIA_DRIVER_CAPABILITIES=all
|
ENV NVIDIA_DRIVER_CAPABILITIES=all
|
||||||
|
|
||||||
|
# DBus run directory creation #
|
||||||
|
RUN mkdir -p /run/dbus
|
||||||
|
|
||||||
# Wireplumber disable suspend #
|
# Wireplumber disable suspend #
|
||||||
# Remove suspend node
|
# Remove suspend node
|
||||||
RUN sed -z -i 's/{[[:space:]]*name = node\/suspend-node\.lua,[[:space:]]*type = script\/lua[[:space:]]*provides = hooks\.node\.suspend[[:space:]]*}[[:space:]]*//g' /usr/share/wireplumber/wireplumber.conf
|
RUN sed -z -i 's/{[[:space:]]*name = node\/suspend-node\.lua,[[:space:]]*type = script\/lua[[:space:]]*provides = hooks\.node\.suspend[[:space:]]*}[[:space:]]*//g' /usr/share/wireplumber/wireplumber.conf
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ if [ ! -e "$PIPEWIRE_SOCKET" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Update system packages before proceeding
|
|
||||||
echo "Upgrading system packages..."
|
|
||||||
pacman -Syu --noconfirm
|
|
||||||
|
|
||||||
echo "Detecting GPU vendor and installing necessary GStreamer plugins..."
|
echo "Detecting GPU vendor and installing necessary GStreamer plugins..."
|
||||||
source /etc/nestri/gpu_helpers.sh
|
source /etc/nestri/gpu_helpers.sh
|
||||||
|
|
||||||
@@ -43,14 +39,14 @@ get_gpu_info
|
|||||||
# Identify vendor
|
# Identify vendor
|
||||||
if [[ "${vendor_full_map[0],,}" =~ "intel" ]]; then
|
if [[ "${vendor_full_map[0],,}" =~ "intel" ]]; then
|
||||||
echo "Intel GPU detected, installing required packages..."
|
echo "Intel GPU detected, installing required packages..."
|
||||||
chwd -a
|
#chwd -a
|
||||||
pacman -Syu --noconfirm gstreamer-vaapi gst-plugin-va gst-plugin-qsv
|
pacman -Sy --noconfirm gstreamer-vaapi gst-plugin-va gst-plugin-qsv
|
||||||
# chwd missed a thing
|
# chwd missed a thing
|
||||||
pacman -Syu --noconfirm vpl-gpu-rt
|
pacman -Sy --noconfirm vpl-gpu-rt
|
||||||
elif [[ "${vendor_full_map[0],,}" =~ "amd" ]]; then
|
elif [[ "${vendor_full_map[0],,}" =~ "amd" ]]; then
|
||||||
echo "AMD GPU detected, installing required packages..."
|
echo "AMD GPU detected, installing required packages..."
|
||||||
chwd -a
|
#chwd -a
|
||||||
pacman -Syu --noconfirm gstreamer-vaapi gst-plugin-va
|
pacman -Sy --noconfirm gstreamer-vaapi gst-plugin-va
|
||||||
elif [[ "${vendor_full_map[0],,}" =~ "nvidia" ]]; then
|
elif [[ "${vendor_full_map[0],,}" =~ "nvidia" ]]; then
|
||||||
echo "NVIDIA GPU detected. Assuming drivers are linked"
|
echo "NVIDIA GPU detected. Assuming drivers are linked"
|
||||||
else
|
else
|
||||||
@@ -62,4 +58,6 @@ echo "Cleaning up old package cache..."
|
|||||||
paccache -rk1
|
paccache -rk1
|
||||||
|
|
||||||
echo "Switching to nestri user for application startup..."
|
echo "Switching to nestri user for application startup..."
|
||||||
|
# Make sure user home dir is owned properly
|
||||||
|
chown ${USER}:${USER} /home/${USER}
|
||||||
exec sudo -E -u nestri /etc/nestri/entrypoint_nestri.sh
|
exec sudo -E -u nestri /etc/nestri/entrypoint_nestri.sh
|
||||||
|
|||||||
@@ -15,26 +15,20 @@ MAX_RETRIES=3
|
|||||||
|
|
||||||
# Helper function to restart the chain
|
# Helper function to restart the chain
|
||||||
restart_chain() {
|
restart_chain() {
|
||||||
echo "Restarting nestri-server, labwc, and wlr-randr..."
|
echo "Restarting nestri-server, compositor..."
|
||||||
|
|
||||||
# Kill all child processes safely (if running)
|
|
||||||
if [[ -n "${NESTRI_PID:-}" ]] && kill -0 "${NESTRI_PID}" 2 >/dev/null; then
|
|
||||||
kill "${NESTRI_PID}"
|
|
||||||
fi
|
|
||||||
if [[ -n "${LABWC_PID:-}" ]] && kill -0 "${LABWC_PID}" 2 >/dev/null; then
|
|
||||||
kill "${LABWC_PID}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
wait || true
|
|
||||||
|
|
||||||
# Start nestri-server
|
# Start nestri-server
|
||||||
start_nestri_server
|
start_nestri_server
|
||||||
RETRY_COUNT=0
|
RETRY_COUNT=0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Function to start nestri-server
|
# Function to start nestri-server
|
||||||
start_nestri_server() {
|
start_nestri_server() {
|
||||||
|
if [[ -n "${NESTRI_PID:-}" ]] && kill -0 "${NESTRI_PID}" 2 >/dev/null; then
|
||||||
|
echo "Killing existing nestri-server process..."
|
||||||
|
kill "${NESTRI_PID}"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Starting nestri-server..."
|
echo "Starting nestri-server..."
|
||||||
nestri-server $(echo $NESTRI_PARAMS) &
|
nestri-server $(echo $NESTRI_PARAMS) &
|
||||||
NESTRI_PID=$!
|
NESTRI_PID=$!
|
||||||
@@ -45,7 +39,8 @@ start_nestri_server() {
|
|||||||
for _ in {1..15}; do # Wait up to 15 seconds
|
for _ in {1..15}; do # Wait up to 15 seconds
|
||||||
if [ -e "$WAYLAND_SOCKET" ]; then
|
if [ -e "$WAYLAND_SOCKET" ]; then
|
||||||
echo "Wayland display 'wayland-1' is ready."
|
echo "Wayland display 'wayland-1' is ready."
|
||||||
start_labwc
|
sleep 1 # necessary sleep - reduces chance that non-ready socket is used
|
||||||
|
start_compositor
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
sleep 1
|
sleep 1
|
||||||
@@ -60,37 +55,44 @@ start_nestri_server() {
|
|||||||
restart_chain
|
restart_chain
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to start labwc
|
# Function to start compositor (labwc)
|
||||||
start_labwc() {
|
start_compositor() {
|
||||||
echo "Starting labwc..."
|
if [[ -n "${COMPOSITOR_PID:-}" ]] && kill -0 "${COMPOSITOR_PID}" 2 >/dev/null; then
|
||||||
|
echo "Killing existing compositor process..."
|
||||||
|
kill "${COMPOSITOR_PID}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting compositor..."
|
||||||
rm -rf /tmp/.X11-unix && mkdir -p /tmp/.X11-unix && chown nestri:nestri /tmp/.X11-unix
|
rm -rf /tmp/.X11-unix && mkdir -p /tmp/.X11-unix && chown nestri:nestri /tmp/.X11-unix
|
||||||
WAYLAND_DISPLAY=wayland-1 WLR_BACKENDS=wayland labwc &
|
WAYLAND_DISPLAY=wayland-1 WLR_BACKENDS=wayland labwc &
|
||||||
LABWC_PID=$!
|
COMPOSITOR_PID=$!
|
||||||
|
|
||||||
# Wait for labwc to initialize (using `wlr-randr` as an indicator)
|
# Wait for compositor to initialize
|
||||||
echo "Waiting for labwc to initialize..."
|
echo "Waiting for compositor to initialize..."
|
||||||
|
COMPOSITOR_SOCKET="/run/user/${UID}/wayland-0"
|
||||||
for _ in {1..15}; do
|
for _ in {1..15}; do
|
||||||
if wlr-randr --json | jq -e '.[] | select(.enabled == true)' >/dev/null; then
|
if [ -e "$COMPOSITOR_SOCKET" ]; then
|
||||||
echo "labwc is initialized and Wayland outputs are ready."
|
echo "compositor is initialized, wayland-0 output ready."
|
||||||
|
sleep 1 # necessary sleep - reduces chance that non-ready socket is used
|
||||||
start_wlr_randr
|
start_wlr_randr
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "Error: labwc did not initialize correctly. Incrementing retry count..."
|
echo "Error: compositor did not initialize correctly. Incrementing retry count..."
|
||||||
((RETRY_COUNT++))
|
((RETRY_COUNT++))
|
||||||
if [ "$RETRY_COUNT" -ge "$MAX_RETRIES" ]; then
|
if [ "$RETRY_COUNT" -ge "$MAX_RETRIES" ]; then
|
||||||
echo "Max retries reached for labwc. Exiting."
|
echo "Max retries reached for compositor. Exiting."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
restart_chain
|
start_compositor
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to run wlr-randr
|
# Function to run wlr-randr
|
||||||
start_wlr_randr() {
|
start_wlr_randr() {
|
||||||
echo "Configuring resolution with wlr-randr..."
|
echo "Configuring resolution with wlr-randr..."
|
||||||
OUTPUT_NAME=$(wlr-randr --json | jq -r '.[] | select(.enabled == true) | .name' | head -n 1)
|
OUTPUT_NAME=$(WAYLAND_DISPLAY=wayland-0 wlr-randr --json | jq -r '.[] | select(.enabled == true) | .name' | head -n 1)
|
||||||
if [ -z "$OUTPUT_NAME" ]; then
|
if [ -z "$OUTPUT_NAME" ]; then
|
||||||
echo "Error: No enabled outputs detected. Skipping wlr-randr."
|
echo "Error: No enabled outputs detected. Skipping wlr-randr."
|
||||||
return
|
return
|
||||||
@@ -116,8 +118,8 @@ main_loop() {
|
|||||||
if [[ -n "${NESTRI_PID:-}" ]] && kill -0 "${NESTRI_PID}" 2>/dev/null; then
|
if [[ -n "${NESTRI_PID:-}" ]] && kill -0 "${NESTRI_PID}" 2>/dev/null; then
|
||||||
kill "${NESTRI_PID}"
|
kill "${NESTRI_PID}"
|
||||||
fi
|
fi
|
||||||
if [[ -n "${LABWC_PID:-}" ]] && kill -0 "${LABWC_PID}" 2>/dev/null; then
|
if [[ -n "${COMPOSITOR_PID:-}" ]] && kill -0 "${COMPOSITOR_PID}" 2>/dev/null; then
|
||||||
kill "${LABWC_PID}"
|
kill "${COMPOSITOR_PID}"
|
||||||
fi
|
fi
|
||||||
exit 0' SIGINT SIGTERM
|
exit 0' SIGINT SIGTERM
|
||||||
|
|
||||||
@@ -134,14 +136,14 @@ fi
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
restart_chain
|
restart_chain
|
||||||
elif ! kill -0 ${LABWC_PID:-} 2 >/dev/null; then
|
elif ! kill -0 ${COMPOSITOR_PID:-} 2 >/dev/null; then
|
||||||
echo "labwc crashed. Restarting labwc and wlr-randr..."
|
echo "compositor crashed. Restarting compositor..."
|
||||||
((RETRY_COUNT++))
|
((RETRY_COUNT++))
|
||||||
if [ "$RETRY_COUNT" -ge "$MAX_RETRIES" ]; then
|
if [ "$RETRY_COUNT" -ge "$MAX_RETRIES" ]; then
|
||||||
echo "Max retries reached for labwc. Exiting."
|
echo "Max retries reached for compositor. Exiting."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
start_labwc
|
start_compositor
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -331,6 +331,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Optimize latency of pipeline
|
// Optimize latency of pipeline
|
||||||
|
video_source.sync_state_with_parent().expect("failed to sync with parent");
|
||||||
video_source.set_property("do-timestamp", &true);
|
video_source.set_property("do-timestamp", &true);
|
||||||
audio_source.set_property("do-timestamp", &true);
|
audio_source.set_property("do-timestamp", &true);
|
||||||
pipeline.set_property("latency", &0u64);
|
pipeline.set_property("latency", &0u64);
|
||||||
|
|||||||
Reference in New Issue
Block a user