diff --git a/build.rs b/build.rs index 9b77e064..3e9eb38c 100644 --- a/build.rs +++ b/build.rs @@ -20,14 +20,13 @@ fn main() { // Compile protobuf files for RustDesk protocol compile_protos(); - // Generate secrets module from secrets.toml + // Generate minimal secrets module generate_secrets(); // Rerun if the script itself changes println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=protos/rendezvous.proto"); println!("cargo:rerun-if-changed=protos/message.proto"); - println!("cargo:rerun-if-changed=secrets.toml"); } /// Compile protobuf files using protobuf-codegen (same as RustDesk server) @@ -52,123 +51,59 @@ pub mod message; std::fs::write(protos_dir.join("mod.rs"), mod_content).unwrap(); } -/// Generate secrets module from secrets.toml -/// -/// This reads the secrets.toml file and generates a Rust module with -/// compile-time constants for sensitive configuration values. +/// Generate minimal secrets module with Google STUN server hardcoded fn generate_secrets() { let out_dir = std::env::var("OUT_DIR").unwrap(); let dest_path = Path::new(&out_dir).join("secrets_generated.rs"); - // Default values if secrets.toml doesn't exist - let mut rustdesk_public_server = String::new(); - let mut rustdesk_public_key = String::new(); - let mut rustdesk_relay_key = String::new(); - let mut ice_stun_server = String::new(); - let mut ice_turn_urls = String::new(); - let mut ice_turn_username = String::new(); - let mut ice_turn_password = String::new(); - - // Try to read secrets.toml - if let Ok(content) = fs::read_to_string("secrets.toml") { - if let Ok(value) = content.parse::() { - // RustDesk section - if let Some(rustdesk) = value.get("rustdesk") { - if let Some(v) = rustdesk.get("public_server").and_then(|v| v.as_str()) { - rustdesk_public_server = v.to_string(); - } - if let Some(v) = rustdesk.get("public_key").and_then(|v| v.as_str()) { - rustdesk_public_key = v.to_string(); - } - if let Some(v) = rustdesk.get("relay_key").and_then(|v| v.as_str()) { - rustdesk_relay_key = v.to_string(); - } - } - - // ICE section (for WebRTC) - if let Some(ice) = value.get("ice") { - if let Some(v) = ice.get("stun_server").and_then(|v| v.as_str()) { - ice_stun_server = v.to_string(); - } - if let Some(v) = ice.get("turn_urls").and_then(|v| v.as_str()) { - ice_turn_urls = v.to_string(); - } - if let Some(v) = ice.get("turn_username").and_then(|v| v.as_str()) { - ice_turn_username = v.to_string(); - } - if let Some(v) = ice.get("turn_password").and_then(|v| v.as_str()) { - ice_turn_password = v.to_string(); - } - } - } else { - println!("cargo:warning=Failed to parse secrets.toml"); - } - } else { - println!("cargo:warning=secrets.toml not found, using empty defaults"); - } - - // Generate the secrets module - let code = format!( - r#"// Auto-generated secrets module -// DO NOT EDIT - This file is generated by build.rs from secrets.toml - -/// RustDesk public server configuration -pub mod rustdesk {{ - /// Public RustDesk ID server address (used when user leaves field empty) - pub const PUBLIC_SERVER: &str = "{}"; - - /// Public key for the RustDesk server (for client connection) - pub const PUBLIC_KEY: &str = "{}"; - - /// Relay server authentication key (licence_key for relay server) - pub const RELAY_KEY: &str = "{}"; - - /// Check if public server is configured - pub const fn has_public_server() -> bool {{ - !PUBLIC_SERVER.is_empty() - }} -}} + // Generate the secrets module - no public servers provided + let code = r#"// Auto-generated secrets module +// DO NOT EDIT - This file is generated by build.rs /// ICE server configuration (for WebRTC NAT traversal) -pub mod ice {{ - /// Public STUN server URL - pub const STUN_SERVER: &str = "{}"; +pub mod ice { + /// Google public STUN server URL (hardcoded) + pub const STUN_SERVER: &str = "stun:stun.l.google.com:19302"; - /// Public TURN server URLs (comma-separated) - pub const TURN_URLS: &str = "{}"; + /// TURN server URLs - not provided, users must configure their own + pub const TURN_URLS: &str = ""; /// TURN authentication username - pub const TURN_USERNAME: &str = "{}"; + pub const TURN_USERNAME: &str = ""; /// TURN authentication password - pub const TURN_PASSWORD: &str = "{}"; + pub const TURN_PASSWORD: &str = ""; - /// Check if public ICE servers are configured - pub const fn is_configured() -> bool {{ - !STUN_SERVER.is_empty() || !TURN_URLS.is_empty() - }} + /// Always returns true since we have STUN + pub const fn is_configured() -> bool { + true + } - /// Check if TURN servers are configured (requires credentials) - pub const fn has_turn() -> bool {{ - !TURN_URLS.is_empty() && !TURN_USERNAME.is_empty() && !TURN_PASSWORD.is_empty() - }} -}} -"#, - escape_string(&rustdesk_public_server), - escape_string(&rustdesk_public_key), - escape_string(&rustdesk_relay_key), - escape_string(&ice_stun_server), - escape_string(&ice_turn_urls), - escape_string(&ice_turn_username), - escape_string(&ice_turn_password), - ); - - fs::write(&dest_path, code).expect("Failed to write secrets_generated.rs"); + /// Always returns false since TURN is not provided + pub const fn has_turn() -> bool { + false + } } -/// Escape special characters in a string for use in Rust string literals -fn escape_string(s: &str) -> String { - s.replace('\\', "\\\\").replace('"', "\\\"") +/// RustDesk public server configuration - NOT PROVIDED +pub mod rustdesk { + /// Public RustDesk ID server - NOT PROVIDED + pub const PUBLIC_SERVER: &str = ""; + + /// Public key for the RustDesk server - NOT PROVIDED + pub const PUBLIC_KEY: &str = ""; + + /// Relay server authentication key - NOT PROVIDED + pub const RELAY_KEY: &str = ""; + + /// Always returns false + pub const fn has_public_server() -> bool { + false + } +} +"#; + + fs::write(&dest_path, code).expect("Failed to write secrets_generated.rs"); } /// Convert days since Unix epoch to year-month-day diff --git a/build/Dockerfile.runtime b/build/Dockerfile.runtime index 478993e9..fc91b443 100644 --- a/build/Dockerfile.runtime +++ b/build/Dockerfile.runtime @@ -8,40 +8,28 @@ FROM debian:12-slim ARG TARGETPLATFORM -# Install runtime dependencies and create directories -RUN apt-get update && apt-get install -y --no-install-recommends \ - # Runtime libraries - libasound2 \ - libv4l-0 \ - libudev1 \ - zlib1g \ - libjpeg62-turbo \ - libyuv0 \ - # FFmpeg runtime - libavcodec59 \ - libavformat59 \ - libavutil57 \ - libswscale6 \ - libswresample4 \ - # Video codecs - libx264-164 \ - libx265-199 \ - libvpx7 \ - # Audio codec - libopus0 \ - # Hardware acceleration - libva2 \ - libva-drm2 \ - libdrm2 \ - # X11 (for VAAPI) - libx11-6 \ - libxcb1 \ - # Utilities - ca-certificates \ - # Intel Media SDK (x86_64 only, ignored on other archs) - $([ "$TARGETPLATFORM" = "linux/amd64" ] && echo "libmfx1") \ - && rm -rf /var/lib/apt/lists/* \ - && mkdir -p /etc/one-kvm/ventoy +# Install runtime dependencies in a single layer +# Static linked: FFmpeg core, libyuv, libvpx, libjpeg-turbo +# Dynamic linked: hardware acceleration drivers, GPL codecs (x264/x265) +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + # Core runtime (all platforms) + libasound2 \ + libv4l-0 \ + libudev1 \ + libdrm2 \ + libopus0 \ + ca-certificates \ + # GPL codecs (must be dynamic for license compliance) + libx264-164 \ + libx265-199 && \ + # Platform-specific hardware acceleration + if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ + apt-get install -y --no-install-recommends \ + libva2 libva-drm2 libva-x11-2 libx11-6 libxcb1 libmfx1; \ + fi && \ + rm -rf /var/lib/apt/lists/* && \ + mkdir -p /etc/one-kvm/ventoy # Copy init script COPY --chmod=755 init.sh /init.sh diff --git a/build/cross/Dockerfile.aarch64 b/build/cross/Dockerfile.aarch64 index 37b9a2e4..89730253 100644 --- a/build/cross/Dockerfile.aarch64 +++ b/build/cross/Dockerfile.aarch64 @@ -28,37 +28,83 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ git \ libclang-dev \ llvm \ - protobuf-compiler \ mold \ meson \ ninja-build \ wget \ + file \ gcc-aarch64-linux-gnu \ g++-aarch64-linux-gnu \ libc6-dev-arm64-cross \ && rm -rf /var/lib/apt/lists/* -# Install ARM64 development libraries (without system ffmpeg) +# Install ARM64 development libraries (without VAAPI/X11 for ARM) RUN apt-get update && apt-get install -y --no-install-recommends \ - libssl-dev:arm64 \ libasound2-dev:arm64 \ libv4l-dev:arm64 \ libudev-dev:arm64 \ zlib1g-dev:arm64 \ - libjpeg62-turbo-dev:arm64 \ - libyuv-dev:arm64 \ + # Note: libjpeg-turbo, libyuv, libvpx are built from source below for static linking libx264-dev:arm64 \ libx265-dev:arm64 \ - libvpx-dev:arm64 \ libopus-dev:arm64 \ - libva-dev:arm64 \ libdrm-dev:arm64 \ - libx11-dev:arm64 \ - libxcb1-dev:arm64 \ - libxau-dev:arm64 \ - libxdmcp-dev:arm64 \ && rm -rf /var/lib/apt/lists/* +# Build static libjpeg-turbo from source (cross-compile for ARM64) +RUN git clone --depth 1 https://github.com/libjpeg-turbo/libjpeg-turbo /tmp/libjpeg-turbo \ + && cd /tmp/libjpeg-turbo \ + && cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_SYSTEM_NAME=Linux \ + -DCMAKE_SYSTEM_PROCESSOR=aarch64 \ + -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc \ + -DCMAKE_INSTALL_PREFIX=/opt/one-kvm-libs/aarch64-linux-gnu \ + -DENABLE_SHARED=OFF -DENABLE_STATIC=ON \ + && cmake --build build -j$(nproc) \ + && cmake --install build \ + && rm -rf /tmp/libjpeg-turbo + +# Build static libyuv from source (cross-compile for ARM64) +RUN git clone --depth 1 https://github.com/lemenkov/libyuv /tmp/libyuv \ + && cd /tmp/libyuv \ + && mkdir build && cd build \ + && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_SYSTEM_NAME=Linux \ + -DCMAKE_SYSTEM_PROCESSOR=aarch64 \ + -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc \ + -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ \ + -DCMAKE_PREFIX_PATH=/opt/one-kvm-libs/aarch64-linux-gnu \ + -DCMAKE_INSTALL_PREFIX=/opt/one-kvm-libs/aarch64-linux-gnu \ + && make -j$(nproc) \ + && mkdir -p /opt/one-kvm-libs/aarch64-linux-gnu/lib \ + && cp libyuv.a /opt/one-kvm-libs/aarch64-linux-gnu/lib/ \ + && cp -r ../include /opt/one-kvm-libs/aarch64-linux-gnu/ \ + && rm -rf /tmp/libyuv + +# Build static libvpx from source (cross-compile for ARM64) +# CC/CXX/LD/AR must be environment variables, not configure arguments +RUN git clone --depth 1 https://github.com/webmproject/libvpx /tmp/libvpx \ + && cd /tmp/libvpx \ + && echo "=== libvpx: Configuring for ARM64 ===" \ + && export CC=aarch64-linux-gnu-gcc \ + && export CXX=aarch64-linux-gnu-g++ \ + && export LD=aarch64-linux-gnu-ld \ + && export AR=aarch64-linux-gnu-ar \ + && export CROSS=aarch64-linux-gnu- \ + && ./configure --prefix=/opt/one-kvm-libs/aarch64-linux-gnu \ + --target=arm64-linux-gcc \ + --enable-static --disable-shared --enable-pic \ + --disable-examples --disable-tools --disable-docs \ + --disable-unit-tests \ + && echo "=== libvpx: Building ===" \ + && make -j$(nproc) \ + && echo "=== libvpx: Checking architecture ===" \ + && file libvpx.a \ + && make install \ + && echo "=== libvpx: Verifying installed library ===" \ + && file /opt/one-kvm-libs/aarch64-linux-gnu/lib/libvpx.a \ + && rm -rf /tmp/libvpx + # Download and build FFmpeg with RKMPP support RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ && wget -q https://files.mofeng.run/src/image/other/ffmpeg.tar.gz \ @@ -73,7 +119,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ \ -DCMAKE_INSTALL_PREFIX=/usr/aarch64-linux-gnu \ -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=ON \ + -DBUILD_SHARED_LIBS=OFF \ -DBUILD_TEST=OFF \ && make -j$(nproc) \ && make install \ @@ -94,6 +140,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ && meson setup build --prefix=/usr/aarch64-linux-gnu --libdir=lib \ --cross-file=/tmp/aarch64-cross.txt \ --buildtype=release \ + --default-library=static \ -Dcpp_args=-fpermissive \ -Dlibdrm=false \ -Dlibrga_demo=false \ @@ -102,7 +149,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ && cd .. \ # Create pkg-config wrapper for cross-compilation && echo '#!/bin/sh' > /tmp/aarch64-pkg-config \ - && echo 'export PKG_CONFIG_LIBDIR=/usr/aarch64-linux-gnu/lib/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig' >> /tmp/aarch64-pkg-config \ + && echo 'export PKG_CONFIG_LIBDIR=/opt/one-kvm-libs/aarch64-linux-gnu/lib/pkgconfig:/usr/aarch64-linux-gnu/lib/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig' >> /tmp/aarch64-pkg-config \ && echo 'export PKG_CONFIG_PATH=""' >> /tmp/aarch64-pkg-config \ && echo 'export PKG_CONFIG_SYSROOT_DIR=""' >> /tmp/aarch64-pkg-config \ && echo 'exec pkg-config "$@"' >> /tmp/aarch64-pkg-config \ @@ -110,7 +157,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ # Build FFmpeg with RKMPP (minimal build for encoding only) && cd ffmpeg-rockchip \ && ./configure \ - --prefix=/usr/aarch64-linux-gnu \ + --prefix=/opt/one-kvm-libs/aarch64-linux-gnu \ --cross-prefix=aarch64-linux-gnu- \ --arch=aarch64 \ --target-os=linux \ @@ -118,13 +165,14 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ --pkg-config=/tmp/aarch64-pkg-config \ --enable-gpl \ --enable-version3 \ - --enable-shared \ - --disable-static \ - # Hardware acceleration + --disable-shared \ + --enable-static \ + --enable-pic \ + # Hardware acceleration (ARM: RKMPP + V4L2, no VAAPI) --enable-libdrm \ --enable-rkmpp \ --enable-rkrga \ - --enable-vaapi \ + --disable-vaapi \ --enable-v4l2-m2m \ # Software encoding libraries --enable-libx264 \ @@ -151,10 +199,6 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ --disable-decoders \ # Disable all encoders, enable only needed ones --disable-encoders \ - --enable-encoder=h264_vaapi \ - --enable-encoder=hevc_vaapi \ - --enable-encoder=vp8_vaapi \ - --enable-encoder=vp9_vaapi \ --enable-encoder=h264_rkmpp \ --enable-encoder=hevc_rkmpp \ --enable-encoder=h264_v4l2m2m \ @@ -194,14 +238,22 @@ RUN rustup target add aarch64-unknown-linux-gnu # Create symlink for mold to work with cross-compiler RUN ln -s /usr/bin/mold /usr/bin/aarch64-linux-gnu-ld.mold +# Copy entrypoint script +COPY build/cross/entrypoint.sh /usr/local/bin/cross-entrypoint.sh +RUN chmod +x /usr/local/bin/cross-entrypoint.sh + # Configure environment for cross-compilation -# Use PKG_CONFIG_LIBDIR to completely replace default search paths -# This ensures we use our custom-built FFmpeg instead of system FFmpeg ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \ CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \ CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ \ AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-ar \ - PKG_CONFIG_LIBDIR=/usr/aarch64-linux-gnu/lib/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig \ + PKG_CONFIG_LIBDIR=/opt/one-kvm-libs/aarch64-linux-gnu/lib/pkgconfig:/usr/aarch64-linux-gnu/lib/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig \ PKG_CONFIG_PATH="" \ PKG_CONFIG_ALLOW_CROSS=1 \ + LIBRARY_PATH="/opt/one-kvm-libs/aarch64-linux-gnu/lib" \ + CPATH="/opt/one-kvm-libs/aarch64-linux-gnu/include" \ + FFMPEG_STATIC=1 \ + LIBYUV_STATIC=1 \ RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc -C link-arg=-fuse-ld=mold" + +ENTRYPOINT ["/usr/local/bin/cross-entrypoint.sh"] diff --git a/build/cross/Dockerfile.armv7 b/build/cross/Dockerfile.armv7 index a8dbafb3..9d521515 100644 --- a/build/cross/Dockerfile.armv7 +++ b/build/cross/Dockerfile.armv7 @@ -28,37 +28,79 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ git \ libclang-dev \ llvm \ - protobuf-compiler \ mold \ meson \ ninja-build \ wget \ + file \ gcc-arm-linux-gnueabihf \ g++-arm-linux-gnueabihf \ libc6-dev-armhf-cross \ && rm -rf /var/lib/apt/lists/* -# Install ARMv7 development libraries (without system ffmpeg) +# Install ARMv7 development libraries (without VAAPI/X11 for ARM) RUN apt-get update && apt-get install -y --no-install-recommends \ - libssl-dev:armhf \ libasound2-dev:armhf \ libv4l-dev:armhf \ libudev-dev:armhf \ zlib1g-dev:armhf \ - libjpeg62-turbo-dev:armhf \ - libyuv-dev:armhf \ + # Note: libjpeg-turbo, libyuv, libvpx are built from source below for static linking libx264-dev:armhf \ libx265-dev:armhf \ - libvpx-dev:armhf \ libopus-dev:armhf \ - libva-dev:armhf \ libdrm-dev:armhf \ - libx11-dev:armhf \ - libxcb1-dev:armhf \ - libxau-dev:armhf \ - libxdmcp-dev:armhf \ && rm -rf /var/lib/apt/lists/* +# Build static libjpeg-turbo from source (cross-compile for ARMv7) +RUN git clone --depth 1 https://github.com/libjpeg-turbo/libjpeg-turbo /tmp/libjpeg-turbo \ + && cd /tmp/libjpeg-turbo \ + && cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_SYSTEM_NAME=Linux \ + -DCMAKE_SYSTEM_PROCESSOR=arm \ + -DCMAKE_C_COMPILER=arm-linux-gnueabihf-gcc \ + -DCMAKE_INSTALL_PREFIX=/opt/one-kvm-libs/armv7-linux-gnueabihf \ + -DENABLE_SHARED=OFF -DENABLE_STATIC=ON \ + && cmake --build build -j$(nproc) \ + && cmake --install build \ + && rm -rf /tmp/libjpeg-turbo + +# Build static libyuv from source (cross-compile for ARMv7) +RUN git clone --depth 1 https://github.com/lemenkov/libyuv /tmp/libyuv \ + && cd /tmp/libyuv \ + && mkdir build && cd build \ + && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_SYSTEM_NAME=Linux \ + -DCMAKE_SYSTEM_PROCESSOR=arm \ + -DCMAKE_C_COMPILER=arm-linux-gnueabihf-gcc \ + -DCMAKE_CXX_COMPILER=arm-linux-gnueabihf-g++ \ + -DCMAKE_PREFIX_PATH=/opt/one-kvm-libs/armv7-linux-gnueabihf \ + -DCMAKE_INSTALL_PREFIX=/opt/one-kvm-libs/armv7-linux-gnueabihf \ + && make -j$(nproc) \ + && mkdir -p /opt/one-kvm-libs/armv7-linux-gnueabihf/lib \ + && cp libyuv.a /opt/one-kvm-libs/armv7-linux-gnueabihf/lib/ \ + && cp -r ../include /opt/one-kvm-libs/armv7-linux-gnueabihf/ \ + && rm -rf /tmp/libyuv + +# Build static libvpx from source (cross-compile for ARMv7) +# CC/CXX/LD/AR must be environment variables, not configure arguments +RUN git clone --depth 1 https://github.com/webmproject/libvpx /tmp/libvpx \ + && cd /tmp/libvpx \ + && export CC=arm-linux-gnueabihf-gcc \ + && export CXX=arm-linux-gnueabihf-g++ \ + && export LD=arm-linux-gnueabihf-ld \ + && export AR=arm-linux-gnueabihf-ar \ + && export CROSS=arm-linux-gnueabihf- \ + && ./configure --prefix=/opt/one-kvm-libs/armv7-linux-gnueabihf \ + --target=armv7-linux-gcc \ + --enable-static --disable-shared --enable-pic \ + --disable-examples --disable-tools --disable-docs \ + --disable-unit-tests \ + && make -j$(nproc) \ + && echo "=== libvpx: Checking architecture ===" \ + && file libvpx.a \ + && make install \ + && rm -rf /tmp/libvpx + # Download and build FFmpeg with RKMPP support RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ && wget -q https://files.mofeng.run/src/image/other/ffmpeg.tar.gz \ @@ -73,7 +115,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ -DCMAKE_CXX_COMPILER=arm-linux-gnueabihf-g++ \ -DCMAKE_INSTALL_PREFIX=/usr/arm-linux-gnueabihf \ -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=ON \ + -DBUILD_SHARED_LIBS=OFF \ -DBUILD_TEST=OFF \ && make -j$(nproc) \ && make install \ @@ -94,6 +136,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ && meson setup build --prefix=/usr/arm-linux-gnueabihf --libdir=lib \ --cross-file=/tmp/armhf-cross.txt \ --buildtype=release \ + --default-library=static \ -Dcpp_args=-fpermissive \ -Dlibdrm=false \ -Dlibrga_demo=false \ @@ -102,7 +145,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ && cd .. \ # Create pkg-config wrapper for cross-compilation && echo '#!/bin/sh' > /tmp/armhf-pkg-config \ - && echo 'export PKG_CONFIG_LIBDIR=/usr/arm-linux-gnueabihf/lib/pkgconfig:/usr/lib/arm-linux-gnueabihf/pkgconfig' >> /tmp/armhf-pkg-config \ + && echo 'export PKG_CONFIG_LIBDIR=/opt/one-kvm-libs/armv7-linux-gnueabihf/lib/pkgconfig:/usr/arm-linux-gnueabihf/lib/pkgconfig:/usr/lib/arm-linux-gnueabihf/pkgconfig' >> /tmp/armhf-pkg-config \ && echo 'export PKG_CONFIG_PATH=""' >> /tmp/armhf-pkg-config \ && echo 'export PKG_CONFIG_SYSROOT_DIR=""' >> /tmp/armhf-pkg-config \ && echo 'exec pkg-config "$@"' >> /tmp/armhf-pkg-config \ @@ -110,7 +153,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ # Build FFmpeg with RKMPP (minimal build for encoding only) && cd ffmpeg-rockchip \ && ./configure \ - --prefix=/usr/arm-linux-gnueabihf \ + --prefix=/opt/one-kvm-libs/armv7-linux-gnueabihf \ --cross-prefix=arm-linux-gnueabihf- \ --arch=arm \ --target-os=linux \ @@ -118,13 +161,14 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ --pkg-config=/tmp/armhf-pkg-config \ --enable-gpl \ --enable-version3 \ - --enable-shared \ - --disable-static \ - # Hardware acceleration + --disable-shared \ + --enable-static \ + --enable-pic \ + # Hardware acceleration (ARM: RKMPP + V4L2, no VAAPI) --enable-libdrm \ --enable-rkmpp \ --enable-rkrga \ - --enable-vaapi \ + --disable-vaapi \ --enable-v4l2-m2m \ # Software encoding libraries --enable-libx264 \ @@ -151,10 +195,6 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ --disable-decoders \ # Disable all encoders, enable only needed ones --disable-encoders \ - --enable-encoder=h264_vaapi \ - --enable-encoder=hevc_vaapi \ - --enable-encoder=vp8_vaapi \ - --enable-encoder=vp9_vaapi \ --enable-encoder=h264_rkmpp \ --enable-encoder=hevc_rkmpp \ --enable-encoder=h264_v4l2m2m \ @@ -194,14 +234,22 @@ RUN rustup target add armv7-unknown-linux-gnueabihf # Create symlink for mold to work with cross-compiler RUN ln -s /usr/bin/mold /usr/bin/arm-linux-gnueabihf-ld.mold +# Copy entrypoint script +COPY build/cross/entrypoint.sh /usr/local/bin/cross-entrypoint.sh +RUN chmod +x /usr/local/bin/cross-entrypoint.sh + # Configure environment for cross-compilation -# Use PKG_CONFIG_LIBDIR to completely replace default search paths -# This ensures we use our custom-built FFmpeg instead of system FFmpeg ENV CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc \ CC_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc \ CXX_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-g++ \ AR_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-ar \ - PKG_CONFIG_LIBDIR=/usr/arm-linux-gnueabihf/lib/pkgconfig:/usr/lib/arm-linux-gnueabihf/pkgconfig \ + PKG_CONFIG_LIBDIR=/opt/one-kvm-libs/armv7-linux-gnueabihf/lib/pkgconfig:/usr/arm-linux-gnueabihf/lib/pkgconfig:/usr/lib/arm-linux-gnueabihf/pkgconfig \ PKG_CONFIG_PATH="" \ PKG_CONFIG_ALLOW_CROSS=1 \ + LIBRARY_PATH="/opt/one-kvm-libs/armv7-linux-gnueabihf/lib" \ + CPATH="/opt/one-kvm-libs/armv7-linux-gnueabihf/include" \ + FFMPEG_STATIC=1 \ + LIBYUV_STATIC=1 \ RUSTFLAGS="-C linker=arm-linux-gnueabihf-gcc -C link-arg=-fuse-ld=mold" + +ENTRYPOINT ["/usr/local/bin/cross-entrypoint.sh"] diff --git a/build/cross/Dockerfile.x86_64 b/build/cross/Dockerfile.x86_64 index 239970e7..b7fe2731 100644 --- a/build/cross/Dockerfile.x86_64 +++ b/build/cross/Dockerfile.x86_64 @@ -17,6 +17,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ENV PATH="/root/.cargo/bin:${PATH}" # Install build dependencies +# Note: libyuv, libvpx are built from source below for static linking RUN apt-get update && apt-get install -y --no-install-recommends \ # Build tools build-essential \ @@ -27,8 +28,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ git \ libclang-dev \ llvm \ - protobuf-compiler \ - libssl-dev \ mold \ wget \ # Core system libraries @@ -36,37 +35,69 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libv4l-dev \ libudev-dev \ zlib1g-dev \ - # Video/image processing - libjpeg62-turbo-dev \ - libyuv-dev \ - # Video codec libraries (for FFmpeg build) + # Note: libjpeg-turbo is built from source below for static linking + # Video codec libraries (dynamic, for software fallback) libx264-dev \ libx265-dev \ - libvpx-dev \ # Audio codec libopus-dev \ # Hardware acceleration libva-dev \ libdrm-dev \ libmfx-dev \ - # X11 libraries + # X11 libraries (for VAAPI) libx11-dev \ libxcb1-dev \ libxau-dev \ libxdmcp-dev \ && rm -rf /var/lib/apt/lists/* +# Build static libjpeg-turbo from source (needed by libyuv) +RUN git clone --depth 1 https://github.com/libjpeg-turbo/libjpeg-turbo /tmp/libjpeg-turbo \ + && cd /tmp/libjpeg-turbo \ + && cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_INSTALL_PREFIX=/opt/one-kvm-libs/x86_64-linux-gnu \ + -DENABLE_SHARED=OFF -DENABLE_STATIC=ON \ + && cmake --build build -j$(nproc) \ + && cmake --install build \ + && rm -rf /tmp/libjpeg-turbo + +# Build static libyuv from source (uses libjpeg-turbo headers) +RUN git clone --depth 1 https://github.com/lemenkov/libyuv /tmp/libyuv \ + && cd /tmp/libyuv \ + && mkdir build && cd build \ + && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_PREFIX_PATH=/opt/one-kvm-libs/x86_64-linux-gnu \ + -DCMAKE_INSTALL_PREFIX=/opt/one-kvm-libs/x86_64-linux-gnu \ + && make -j$(nproc) \ + && mkdir -p /opt/one-kvm-libs/x86_64-linux-gnu/lib \ + && cp libyuv.a /opt/one-kvm-libs/x86_64-linux-gnu/lib/ \ + && cp -r ../include /opt/one-kvm-libs/x86_64-linux-gnu/ \ + && rm -rf /tmp/libyuv + +# Build static libvpx from source +RUN git clone --depth 1 https://github.com/webmproject/libvpx /tmp/libvpx \ + && cd /tmp/libvpx \ + && ./configure --prefix=/opt/one-kvm-libs/x86_64-linux-gnu \ + --enable-static --disable-shared --enable-pic \ + --disable-examples --disable-tools --disable-docs \ + && make -j$(nproc) \ + && make install \ + && rm -rf /tmp/libvpx + # Download and build FFmpeg with minimal configuration for encoding only RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ && wget -q https://files.mofeng.run/src/image/other/ffmpeg.tar.gz \ && tar -xzf ffmpeg.tar.gz \ && cd ffmpeg/ffmpeg-rockchip \ + && export PKG_CONFIG_PATH="/opt/one-kvm-libs/x86_64-linux-gnu/lib/pkgconfig:$PKG_CONFIG_PATH" \ && ./configure \ - --prefix=/usr/local \ + --prefix=/opt/one-kvm-libs/x86_64-linux-gnu \ --enable-gpl \ --enable-version3 \ - --enable-shared \ - --disable-static \ + --disable-shared \ + --enable-static \ + --enable-pic \ # Hardware acceleration --enable-libdrm \ --enable-vaapi \ @@ -128,13 +159,22 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ --disable-debug \ && make -j$(nproc) \ && make install \ - && ldconfig \ && cd / \ && rm -rf /tmp/ffmpeg-build # Add Rust target RUN rustup target add x86_64-unknown-linux-gnu -# Configure mold as the linker and use custom FFmpeg +# Copy entrypoint script +COPY build/cross/entrypoint.sh /usr/local/bin/cross-entrypoint.sh +RUN chmod +x /usr/local/bin/cross-entrypoint.sh + +# Configure environment for static linking ENV RUSTFLAGS="-C link-arg=-fuse-ld=mold" \ - PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:${PKG_CONFIG_PATH}" + PKG_CONFIG_PATH="/opt/one-kvm-libs/x86_64-linux-gnu/lib/pkgconfig" \ + LIBRARY_PATH="/opt/one-kvm-libs/x86_64-linux-gnu/lib" \ + CPATH="/opt/one-kvm-libs/x86_64-linux-gnu/include" \ + FFMPEG_STATIC=1 \ + LIBYUV_STATIC=1 + +ENTRYPOINT ["/usr/local/bin/cross-entrypoint.sh"] diff --git a/docs/modules/rustdesk.md b/docs/modules/rustdesk.md index 2ab10a35..01611b64 100644 --- a/docs/modules/rustdesk.md +++ b/docs/modules/rustdesk.md @@ -14,7 +14,6 @@ RustDesk 模块实现 RustDesk 协议集成,允许使用标准 RustDesk 客户 - 视频/音频/HID 转换 - 端到端加密 (Curve25519 + XSalsa20-Poly1305) - 签名认证 (Ed25519) -- 公共服务器支持 (通过 secrets.toml) - 动态编码器协商 (H264/H265/VP8/VP9) - 输入节流 (防止 HID EAGAIN) - CapsLock 状态同步 @@ -660,7 +659,7 @@ pub struct RustDeskConfig { /// Rendezvous 服务器地址 (hbbs) /// 格式: "rs.example.com" 或 "192.168.1.100:21116" - /// 如果为空,使用 secrets.toml 中配置的公共服务器 + /// 必填项 - 不再提供公共服务器,需自行配置 pub rendezvous_server: String, /// 中继服务器地址 (hbbr),默认与 rendezvous 同主机 @@ -703,15 +702,9 @@ impl RustDeskConfig { /// 检查配置是否有效 pub fn is_valid(&self) -> bool; - /// 检查是否使用公共服务器 - pub fn is_using_public_server(&self) -> bool; - /// 获取有效的 Rendezvous 服务器地址 pub fn effective_rendezvous_server(&self) -> &str; - /// 获取公共服务器信息 (如果配置了) - pub fn public_server_info() -> Option; - /// 获取带默认端口的 Rendezvous 地址 pub fn rendezvous_addr(&self) -> String; @@ -721,14 +714,6 @@ impl RustDeskConfig { /// 确保 UUID 存在 (自动生成并标记需要保存) pub fn ensure_uuid(&mut self) -> ([u8; 16], bool); } - -/// 公共服务器信息 -#[derive(Serialize, Deserialize)] -#[typeshare] -pub struct PublicServerInfo { - pub server: String, // 服务器地址 - pub public_key: String, // 公钥 (Base64) -} ``` ### 配置文件示例 @@ -744,23 +729,7 @@ device_password = "mypassword" # 密钥和 UUID 由程序自动生成和保存 ``` -**使用公共服务器:** -```toml -[rustdesk] -enabled = true -rendezvous_server = "" # 留空使用 secrets.toml 中的公共服务器 -device_id = "123456789" -device_password = "mypassword" -``` - -**secrets.toml 公共服务器配置:** -```toml -[rustdesk] -# 公共服务器配置 (可选) -public_server = "rs-ny.rustdesk.com" -public_key = "xxx...base64...xxx" -relay_key = "xxx...key...xxx" -``` +**注意**: 不再提供公共服务器,需自行配置 RustDesk 服务器。 --- @@ -868,7 +837,7 @@ pub enum RustDeskError { ```rust let config = RustDeskConfig { enabled: true, - rendezvous_server: "".to_string(), // 使用公共服务器 + rendezvous_server: "hbbs.example.com:21116".to_string(), // 必填 - 配置您的服务器 device_id: "123456789".to_string(), device_password: "mypassword".to_string(), ..Default::default() @@ -1271,7 +1240,7 @@ docker run -d --name hbbr \ | 协议 | RustDesk Protocol | 同 | | P2P | 支持 | 支持 | | 中继 | 支持 | 提供中继服务 | -| 公共服务器 | 可配置 (secrets.toml) | N/A | +| 公共服务器 | 不提供,需自建 | N/A | | 多连接 | 支持 | N/A | | 输入节流 | 60Hz 限流 | 无限制 | diff --git a/libs/hwcodec/build.rs b/libs/hwcodec/build.rs index 978b4e3c..0b2727f1 100644 --- a/libs/hwcodec/build.rs +++ b/libs/hwcodec/build.rs @@ -97,10 +97,76 @@ mod ffmpeg { build_ffmpeg_ram(builder); } - /// Link system FFmpeg using pkg-config (for Linux development) + /// Link system FFmpeg using pkg-config or custom path + /// Supports both static and dynamic linking based on FFMPEG_STATIC env var fn link_system_ffmpeg(builder: &mut Build) { use std::process::Command; + // Check if static linking is requested + let use_static = std::env::var("FFMPEG_STATIC").map(|v| v == "1").unwrap_or(false); + let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); + + // Try custom library path first: + // 1. Check ONE_KVM_LIBS_PATH environment variable (explicit override) + // 2. Fall back to architecture-based detection + let custom_lib_path = if let Ok(path) = std::env::var("ONE_KVM_LIBS_PATH") { + path + } else { + match target_arch.as_str() { + "x86_64" => "/opt/one-kvm-libs/x86_64-linux-gnu", + "aarch64" => "/opt/one-kvm-libs/aarch64-linux-gnu", + "arm" => "/opt/one-kvm-libs/armv7-linux-gnueabihf", + _ => "", + } + .to_string() + }; + + // Check if our custom FFmpeg exists + if !custom_lib_path.is_empty() { + let lib_dir = Path::new(&custom_lib_path).join("lib"); + let include_dir = Path::new(&custom_lib_path).join("include"); + let avcodec_lib = lib_dir.join("libavcodec.a"); + + if avcodec_lib.exists() { + println!("cargo:info=Using custom FFmpeg from {}", custom_lib_path); + println!("cargo:rustc-link-search=native={}", lib_dir.display()); + builder.include(&include_dir); + + // Static link FFmpeg core libraries + println!("cargo:rustc-link-lib=static=avcodec"); + println!("cargo:rustc-link-lib=static=avutil"); + + // Link hardware acceleration dependencies (dynamic) + // These vary by architecture + if target_arch == "x86_64" { + // VAAPI for x86_64 + println!("cargo:rustc-link-lib=va"); + println!("cargo:rustc-link-lib=va-drm"); + println!("cargo:rustc-link-lib=va-x11"); // Required for vaGetDisplay + println!("cargo:rustc-link-lib=mfx"); + } else { + // RKMPP for ARM + println!("cargo:rustc-link-lib=rockchip_mpp"); + println!("cargo:rustc-link-lib=rga"); + } + + // Software codec dependencies (dynamic - GPL) + println!("cargo:rustc-link-lib=x264"); + println!("cargo:rustc-link-lib=x265"); + + // VPX - check if static version exists in our custom path + let vpx_static = lib_dir.join("libvpx.a"); + if vpx_static.exists() { + println!("cargo:rustc-link-lib=static=vpx"); + } else { + println!("cargo:rustc-link-lib=vpx"); + } + + return; + } + } + + // Fallback to pkg-config // Only need libavcodec and libavutil for encoding let libs = ["libavcodec", "libavutil"]; @@ -120,9 +186,15 @@ mod ffmpeg { } } - // Get libs - always use dynamic linking on Linux + // Get libs - use --static flag for static linking + let pkg_config_args = if use_static { + vec!["--static", "--libs", lib] + } else { + vec!["--libs", lib] + }; + if let Ok(output) = Command::new("pkg-config") - .args(["--libs", lib]) + .args(&pkg_config_args) .output() { if output.status.success() { @@ -131,7 +203,18 @@ mod ffmpeg { if flag.starts_with("-L") { println!("cargo:rustc-link-search=native={}", &flag[2..]); } else if flag.starts_with("-l") { - println!("cargo:rustc-link-lib={}", &flag[2..]); + let lib_name = &flag[2..]; + if use_static { + // For static linking, link FFmpeg libs statically, others dynamically + if lib_name.starts_with("av") || lib_name == "swresample" { + println!("cargo:rustc-link-lib=static={}", lib_name); + } else { + // Runtime libraries (va, drm, etc.) must be dynamic + println!("cargo:rustc-link-lib={}", lib_name); + } + } else { + println!("cargo:rustc-link-lib={}", lib_name); + } } } } else { @@ -142,7 +225,11 @@ mod ffmpeg { } } - println!("cargo:info=Using system FFmpeg via pkg-config (dynamic linking)"); + if use_static { + println!("cargo:info=Using system FFmpeg via pkg-config (static linking)"); + } else { + println!("cargo:info=Using system FFmpeg via pkg-config (dynamic linking)"); + } } fn link_vcpkg(builder: &mut Build, mut path: PathBuf) -> PathBuf { @@ -203,14 +290,15 @@ mod ffmpeg { let dyn_libs: Vec<&str> = if target_os == "windows" { ["User32", "bcrypt", "ole32", "advapi32"].to_vec() } else if target_os == "linux" { - // Note: VA-API libraries (va, va-drm, va-x11) must remain dynamic - // as they load drivers at runtime. Same for libmfx. - // All dependencies use dynamic linking on Linux. - let mut v = vec!["drm", "X11", "stdc++"]; + // Base libraries for all Linux platforms + let mut v = vec!["drm", "stdc++"]; + // x86_64: needs X11 for VAAPI and zlib if target_arch == "x86_64" { + v.push("X11"); v.push("z"); } + // ARM (aarch64, arm): no X11 needed, uses RKMPP/V4L2 v } else { panic!("Unsupported OS: {}. Only Windows and Linux are supported.", target_os); diff --git a/res/vcpkg/libyuv/build.rs b/res/vcpkg/libyuv/build.rs index 318da693..bc3000bd 100644 --- a/res/vcpkg/libyuv/build.rs +++ b/res/vcpkg/libyuv/build.rs @@ -148,40 +148,31 @@ fn link_vcpkg(mut path: PathBuf) -> bool { println!("cargo:rustc-link-search=native={}", lib_path.display()); - // For Linux: use dynamic linking - #[cfg(target_os = "linux")] - { - println!("cargo:rustc-link-lib=yuv"); - println!("cargo:rustc-link-lib=jpeg"); - println!("cargo:rustc-link-lib=stdc++"); - } + // Check if static linking is requested via environment variable + let use_static = env::var("LIBYUV_STATIC").map(|v| v == "1").unwrap_or(false); - // For Windows/macOS: keep static linking - #[cfg(not(target_os = "linux"))] - { - let static_lib = lib_path.join("libyuv.a"); - let static_lib_win = lib_path.join("yuv.lib"); - - if static_lib.exists() || static_lib_win.exists() { - println!("cargo:rustc-link-lib=static=yuv"); - } else { - println!("cargo:rustc-link-lib=yuv"); - } - - let jpeg_static = lib_path.join("libjpeg.a"); - let jpeg_static_win = lib_path.join("jpeg.lib"); - let turbojpeg_static = lib_path.join("libturbojpeg.a"); + let static_lib = lib_path.join("libyuv.a"); + let jpeg_static = lib_path.join("libjpeg.a"); + let turbojpeg_static = lib_path.join("libturbojpeg.a"); + if use_static && static_lib.exists() { + // Static linking (for deb packaging) + println!("cargo:rustc-link-lib=static=yuv"); if turbojpeg_static.exists() { println!("cargo:rustc-link-lib=static=turbojpeg"); - } else if jpeg_static.exists() || jpeg_static_win.exists() { + } else if jpeg_static.exists() { println!("cargo:rustc-link-lib=static=jpeg"); } else { println!("cargo:rustc-link-lib=jpeg"); } - - #[cfg(target_os = "windows")] println!("cargo:rustc-link-lib=stdc++"); + println!("cargo:info=Using libyuv from vcpkg (static linking)"); + } else { + // Dynamic linking (default for development) + println!("cargo:rustc-link-lib=yuv"); + println!("cargo:rustc-link-lib=jpeg"); + println!("cargo:rustc-link-lib=stdc++"); + println!("cargo:info=Using libyuv from vcpkg (dynamic linking)"); } println!( @@ -238,21 +229,72 @@ fn link_pkg_config() -> bool { } fn link_system() -> bool { - // Try common system library paths (dynamic linking only) - let lib_paths = [ + // Check if static linking is requested + let use_static = env::var("LIBYUV_STATIC").map(|v| v == "1").unwrap_or(false); + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); + + // Build custom library paths based on target architecture: + // 1. Check ONE_KVM_LIBS_PATH environment variable (explicit override) + // 2. Fall back to architecture-based detection + let custom_lib_path = if let Ok(path) = env::var("ONE_KVM_LIBS_PATH") { + format!("{}/lib", path) + } else { + match target_arch.as_str() { + "x86_64" => "/opt/one-kvm-libs/x86_64-linux-gnu/lib", + "aarch64" => "/opt/one-kvm-libs/aarch64-linux-gnu/lib", + "arm" => "/opt/one-kvm-libs/armv7-linux-gnueabihf/lib", + _ => "", + } + .to_string() + }; + + // Try common system library paths (custom paths first) + let mut lib_paths: Vec = Vec::new(); + + // Add custom build path first (highest priority) + if !custom_lib_path.is_empty() { + lib_paths.push(custom_lib_path); + } + + // Then standard paths + lib_paths.extend([ + "/usr/local/lib", // Custom builds + "/usr/local/lib64", "/usr/lib", "/usr/lib64", - "/usr/local/lib", - "/usr/local/lib64", "/usr/lib/x86_64-linux-gnu", // Debian/Ubuntu x86_64 "/usr/lib/aarch64-linux-gnu", // Debian/Ubuntu ARM64 "/usr/lib/arm-linux-gnueabihf", // Debian/Ubuntu ARMv7 - ]; + ].iter().map(|s| s.to_string())); for path in &lib_paths { let lib_path = Path::new(path); + let libyuv_static = lib_path.join("libyuv.a"); let libyuv_so = lib_path.join("libyuv.so"); + // Prefer static library if LIBYUV_STATIC=1 + if use_static && libyuv_static.exists() { + println!("cargo:rustc-link-search=native={}", path); + println!("cargo:rustc-link-lib=static=yuv"); + + // Check for static libjpeg-turbo in the same directory + let turbojpeg_static = lib_path.join("libturbojpeg.a"); + let jpeg_static = lib_path.join("libjpeg.a"); + if turbojpeg_static.exists() { + println!("cargo:rustc-link-lib=static=turbojpeg"); + } else if jpeg_static.exists() { + println!("cargo:rustc-link-lib=static=jpeg"); + } else { + // Fall back to dynamic jpeg + println!("cargo:rustc-link-lib=jpeg"); + } + + println!("cargo:rustc-link-lib=stdc++"); + println!("cargo:info=Using system libyuv from {} (static linking)", path); + return true; + } + + // Fall back to dynamic linking if libyuv_so.exists() { println!("cargo:rustc-link-search=native={}", path); println!("cargo:rustc-link-lib=yuv"); diff --git a/src/rustdesk/config.rs b/src/rustdesk/config.rs index 4f9d0906..1532f986 100644 --- a/src/rustdesk/config.rs +++ b/src/rustdesk/config.rs @@ -5,8 +5,6 @@ use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::secrets; - /// RustDesk configuration #[typeshare] #[derive(Debug, Clone, Serialize, Deserialize)] @@ -15,9 +13,8 @@ pub struct RustDeskConfig { /// Enable RustDesk protocol pub enabled: bool, - /// Rendezvous server address (hbbs), e.g., "rs.example.com" or "192.168.1.100" - /// Port defaults to 21116 if not specified - /// If empty, uses the public server from secrets.toml + /// Rendezvous server address (hbbs), e.g., "rs.example.com" or "192.168.1.100:21116" + /// Required for RustDesk to function pub rendezvous_server: String, /// Relay server address (hbbr), if different from rendezvous server @@ -79,39 +76,17 @@ impl Default for RustDeskConfig { impl RustDeskConfig { /// Check if the configuration is valid for starting the service - /// Returns true if enabled and has a valid server (user-configured or public) + /// Returns true if enabled and has a valid server pub fn is_valid(&self) -> bool { self.enabled - && !self.effective_rendezvous_server().is_empty() + && !self.rendezvous_server.is_empty() && !self.device_id.is_empty() && !self.device_password.is_empty() } - /// Check if using the public server (user left rendezvous_server empty) - pub fn is_using_public_server(&self) -> bool { - self.rendezvous_server.is_empty() && secrets::rustdesk::has_public_server() - } - - /// Get the effective rendezvous server (user-configured or public fallback) + /// Get the rendezvous server (user-configured) pub fn effective_rendezvous_server(&self) -> &str { - if self.rendezvous_server.is_empty() { - secrets::rustdesk::PUBLIC_SERVER - } else { - &self.rendezvous_server - } - } - - /// Get public server info for display (server address and public key) - /// Returns None if no public server is configured - pub fn public_server_info() -> Option { - if secrets::rustdesk::has_public_server() { - Some(PublicServerInfo { - server: secrets::rustdesk::PUBLIC_SERVER.to_string(), - public_key: secrets::rustdesk::PUBLIC_KEY.to_string(), - }) - } else { - None - } + &self.rendezvous_server } /// Generate a new random device ID @@ -148,8 +123,10 @@ impl RustDeskConfig { /// Get the rendezvous server address with default port pub fn rendezvous_addr(&self) -> String { - let server = self.effective_rendezvous_server(); - if server.contains(':') { + let server = &self.rendezvous_server; + if server.is_empty() { + String::new() + } else if server.contains(':') { server.to_string() } else { format!("{}:21116", server) @@ -165,8 +142,8 @@ impl RustDeskConfig { format!("{}:21117", s) } }).or_else(|| { - // Default: same host as effective rendezvous server - let server = self.effective_rendezvous_server(); + // Default: same host as rendezvous server + let server = &self.rendezvous_server; if !server.is_empty() { let host = server.split(':').next().unwrap_or(""); if !host.is_empty() { @@ -181,16 +158,6 @@ impl RustDeskConfig { } } -/// Public server information for display to users -#[derive(Debug, Clone, Serialize, Deserialize)] -#[typeshare] -pub struct PublicServerInfo { - /// Public server address - pub server: String, - /// Public key for client connection - pub public_key: String, -} - /// Generate a random 9-digit device ID pub fn generate_device_id() -> String { use rand::Rng; @@ -239,6 +206,10 @@ mod tests { config.rendezvous_server = "example.com:21116".to_string(); assert_eq!(config.rendezvous_addr(), "example.com:21116"); + + // Empty server returns empty string + config.rendezvous_server = String::new(); + assert_eq!(config.rendezvous_addr(), ""); } #[test] @@ -252,6 +223,10 @@ mod tests { // Explicit relay server config.relay_server = Some("relay.example.com".to_string()); assert_eq!(config.relay_addr(), Some("relay.example.com:21117".to_string())); + + // No rendezvous server, relay is None + config.rendezvous_server = String::new(); + assert_eq!(config.relay_addr(), None); } #[test] @@ -262,10 +237,8 @@ mod tests { config.rendezvous_server = "custom.example.com".to_string(); assert_eq!(config.effective_rendezvous_server(), "custom.example.com"); - // When empty, falls back to public server (if configured) + // When empty, returns empty config.rendezvous_server = String::new(); - // This will return PUBLIC_SERVER from secrets - let effective = config.effective_rendezvous_server(); - assert!(!effective.is_empty() || !secrets::rustdesk::has_public_server()); + assert_eq!(config.effective_rendezvous_server(), ""); } } diff --git a/src/rustdesk/mod.rs b/src/rustdesk/mod.rs index 2f098ed1..b262632f 100644 --- a/src/rustdesk/mod.rs +++ b/src/rustdesk/mod.rs @@ -239,16 +239,10 @@ impl RustDeskService { let config = service_config_punch.clone(); tokio::spawn(async move { - // Get relay_key from config, or use public server's relay_key if using public server + // Get relay_key from config (no public server fallback) let relay_key = { let cfg = config.read(); - cfg.relay_key.clone().unwrap_or_else(|| { - if cfg.is_using_public_server() { - crate::secrets::rustdesk::RELAY_KEY.to_string() - } else { - String::new() - } - }) + cfg.relay_key.clone().unwrap_or_default() }; // Try P2P direct connection first @@ -295,16 +289,10 @@ impl RustDeskService { let config = service_config.clone(); tokio::spawn(async move { - // Get relay_key from config, or use public server's relay_key if using public server + // Get relay_key from config (no public server fallback) let relay_key = { let cfg = config.read(); - cfg.relay_key.clone().unwrap_or_else(|| { - if cfg.is_using_public_server() { - crate::secrets::rustdesk::RELAY_KEY.to_string() - } else { - String::new() - } - }) + cfg.relay_key.clone().unwrap_or_default() }; if let Err(e) = handle_relay_request( diff --git a/src/web/handlers/config/rustdesk.rs b/src/web/handlers/config/rustdesk.rs index 3183bf49..1b291461 100644 --- a/src/web/handlers/config/rustdesk.rs +++ b/src/web/handlers/config/rustdesk.rs @@ -4,7 +4,7 @@ use axum::{extract::State, Json}; use std::sync::Arc; use crate::error::Result; -use crate::rustdesk::config::{PublicServerInfo, RustDeskConfig}; +use crate::rustdesk::config::RustDeskConfig; use crate::state::AppState; use super::apply::apply_rustdesk_config; @@ -23,8 +23,6 @@ pub struct RustDeskConfigResponse { pub has_keypair: bool, /// 是否已设置 relay key pub has_relay_key: bool, - /// 是否使用公共服务器(用户留空时) - pub using_public_server: bool, } impl From<&RustDeskConfig> for RustDeskConfigResponse { @@ -37,7 +35,6 @@ impl From<&RustDeskConfig> for RustDeskConfigResponse { has_password: !config.device_password.is_empty(), has_keypair: config.public_key.is_some() && config.private_key.is_some(), has_relay_key: config.relay_key.is_some(), - using_public_server: config.is_using_public_server(), } } } @@ -48,8 +45,6 @@ pub struct RustDeskStatusResponse { pub config: RustDeskConfigResponse, pub service_status: String, pub rendezvous_status: Option, - /// 公共服务器信息(仅当有公共服务器配置时返回) - pub public_server: Option, } /// 获取 RustDesk 配置 @@ -73,14 +68,10 @@ pub async fn get_rustdesk_status(State(state): State>) -> Json>) -> Json { + use crate::webrtc::config::public_ice; + let config = state.config.get(); let mut ice_servers = Vec::new(); - // Add STUN server - if let Some(ref stun) = config.stream.stun_server { - if !stun.is_empty() { + // Check if user has configured custom ICE servers + let has_custom_stun = config + .stream + .stun_server + .as_ref() + .map(|s| !s.is_empty()) + .unwrap_or(false); + let has_custom_turn = config + .stream + .turn_server + .as_ref() + .map(|s| !s.is_empty()) + .unwrap_or(false); + + if has_custom_stun || has_custom_turn { + // Use user-configured ICE servers + if let Some(ref stun) = config.stream.stun_server { + if !stun.is_empty() { + ice_servers.push(IceServerInfo { + urls: vec![stun.clone()], + username: None, + credential: None, + }); + } + } + + if let Some(ref turn) = config.stream.turn_server { + if !turn.is_empty() { + let username = config.stream.turn_username.clone(); + let credential = config.stream.turn_password.clone(); + if username.is_some() && credential.is_some() { + ice_servers.push(IceServerInfo { + urls: vec![turn.clone()], + username, + credential, + }); + } + } + } + } else { + // No custom servers configured - use Google STUN as default + if let Some(stun) = public_ice::stun_server() { ice_servers.push(IceServerInfo { - urls: vec![stun.clone()], + urls: vec![stun], username: None, credential: None, }); } - } - - // Add TURN server (with credentials) - if let Some(ref turn) = config.stream.turn_server { - if !turn.is_empty() { - let username = config.stream.turn_username.clone(); - let credential = config.stream.turn_password.clone(); - // Only add TURN if credentials are provided - if username.is_some() && credential.is_some() { - ice_servers.push(IceServerInfo { - urls: vec![turn.clone()], - username, - credential, - }); - } - } + // Note: TURN servers are not provided - users must configure their own } Json(IceServersResponse { ice_servers }) diff --git a/src/webrtc/config.rs b/src/webrtc/config.rs index a71b9b0c..3b5c79c3 100644 --- a/src/webrtc/config.rs +++ b/src/webrtc/config.rs @@ -4,21 +4,21 @@ use serde::{Deserialize, Serialize}; use crate::secrets; -/// Public ICE server utilities +/// ICE server utilities - Only provides Google STUN, no TURN pub mod public_ice { use super::*; - /// Check if public ICE servers are configured (at compile time) + /// Always returns true (we have Google STUN) pub fn is_configured() -> bool { secrets::ice::is_configured() } - /// Check if public TURN servers are configured (requires credentials) + /// Always returns false (TURN not provided) pub fn has_turn() -> bool { secrets::ice::has_turn() } - /// Get the public STUN server URL + /// Get the Google STUN server URL pub fn stun_server() -> Option { let server = secrets::ice::STUN_SERVER; if server.is_empty() { @@ -28,33 +28,15 @@ pub mod public_ice { } } - /// Get public TURN servers as TurnServer structs + /// Always returns empty vector (TURN not provided) pub fn turn_servers() -> Vec { - if !secrets::ice::has_turn() { - return vec![]; - } - - let urls: Vec = secrets::ice::TURN_URLS - .split(',') - .map(|s| s.trim().to_string()) - .filter(|s| !s.is_empty()) - .collect(); - - if urls.is_empty() { - return vec![]; - } - - vec![TurnServer { - urls, - username: secrets::ice::TURN_USERNAME.to_string(), - credential: secrets::ice::TURN_PASSWORD.to_string(), - }] + vec![] } - /// Get all public ICE servers (STUN + TURN) for WebRTC configuration + /// Get all public ICE servers (STUN only, no TURN) pub fn get_all_servers() -> (Vec, Vec) { let stun_servers = stun_server().into_iter().collect(); - let turn_servers = turn_servers(); + let turn_servers = vec![]; (stun_servers, turn_servers) } } diff --git a/src/webrtc/peer.rs b/src/webrtc/peer.rs index a1a7dde5..49636676 100644 --- a/src/webrtc/peer.rs +++ b/src/webrtc/peer.rs @@ -199,46 +199,56 @@ impl PeerConnection { pc.on_data_channel(Box::new(move |dc: Arc| { let data_channel = data_channel.clone(); let hid = hid_clone.clone(); + let label = dc.label().to_string(); Box::pin(async move { - info!("Data channel opened with HID support: {}", dc.label()); + // Handle both reliable (hid) and unreliable (hid-unreliable) channels + let is_hid_channel = label == "hid" || label == "hid-unreliable"; - // Store data channel - *data_channel.write().await = Some(dc.clone()); + if is_hid_channel { + info!("HID DataChannel opened: {} (unreliable: {})", label, label == "hid-unreliable"); - // Set up message handler with HID processing - // Immediately spawn task in tokio runtime for low latency - dc.on_message(Box::new(move |msg: DataChannelMessage| { - let hid = hid.clone(); + // Store the reliable data channel for sending responses + if label == "hid" { + *data_channel.write().await = Some(dc.clone()); + } - tokio::spawn(async move { - debug!("DataChannel HID message: {} bytes", msg.data.len()); + // Set up message handler with HID processing + // Both channels use the same HID processing logic + dc.on_message(Box::new(move |msg: DataChannelMessage| { + let hid = hid.clone(); - // Parse and process HID message - if let Some(event) = parse_hid_message(&msg.data) { - match event { - HidChannelEvent::Keyboard(kb_event) => { - if let Err(e) = hid.send_keyboard(kb_event).await { - debug!("Failed to send keyboard event: {}", e); + tokio::spawn(async move { + debug!("DataChannel HID message: {} bytes", msg.data.len()); + + // Parse and process HID message + if let Some(event) = parse_hid_message(&msg.data) { + match event { + HidChannelEvent::Keyboard(kb_event) => { + if let Err(e) = hid.send_keyboard(kb_event).await { + debug!("Failed to send keyboard event: {}", e); + } } - } - HidChannelEvent::Mouse(ms_event) => { - if let Err(e) = hid.send_mouse(ms_event).await { - debug!("Failed to send mouse event: {}", e); + HidChannelEvent::Mouse(ms_event) => { + if let Err(e) = hid.send_mouse(ms_event).await { + debug!("Failed to send mouse event: {}", e); + } } - } - HidChannelEvent::Consumer(consumer_event) => { - if let Err(e) = hid.send_consumer(consumer_event).await { - debug!("Failed to send consumer event: {}", e); + HidChannelEvent::Consumer(consumer_event) => { + if let Err(e) = hid.send_consumer(consumer_event).await { + debug!("Failed to send consumer event: {}", e); + } } } } - } - }); + }); - // Return empty future (actual work is spawned above) - Box::pin(async {}) - })); + // Return empty future (actual work is spawned above) + Box::pin(async {}) + })); + } else { + info!("Non-HID DataChannel opened: {}", label); + } }) })); diff --git a/web/src/api/config.ts b/web/src/api/config.ts index 2869db33..4f25d4f1 100644 --- a/web/src/api/config.ts +++ b/web/src/api/config.ts @@ -256,12 +256,6 @@ export const extensionsApi = { // ===== RustDesk 配置 API ===== -/** 公共服务器信息 */ -export interface PublicServerInfo { - server: string - public_key: string -} - /** RustDesk 配置响应 */ export interface RustDeskConfigResponse { enabled: boolean @@ -271,7 +265,6 @@ export interface RustDeskConfigResponse { has_password: boolean has_keypair: boolean has_relay_key: boolean - using_public_server: boolean } /** RustDesk 状态响应 */ @@ -279,7 +272,6 @@ export interface RustDeskStatusResponse { config: RustDeskConfigResponse service_status: string rendezvous_status: string | null - public_server: PublicServerInfo | null } /** RustDesk 配置更新 */ diff --git a/web/src/i18n/en-US.ts b/web/src/i18n/en-US.ts index fabbc87d..fb88baec 100644 --- a/web/src/i18n/en-US.ts +++ b/web/src/i18n/en-US.ts @@ -570,18 +570,17 @@ export default { // WebRTC / ICE webrtcSettings: 'WebRTC Settings', webrtcSettingsDesc: 'Configure STUN/TURN servers for NAT traversal', - usingPublicIceServers: 'Using public ICE servers', - publicIceServersHint: 'Leave empty to use built-in public STUN/TURN servers for NAT traversal', + publicIceServersHint: 'Empty uses Google public STUN, configure your own TURN for production', stunServer: 'STUN Server', stunServerPlaceholder: 'stun:stun.l.google.com:19302', - stunServerHint: 'Custom STUN server (leave empty to use public server)', + stunServerHint: 'Custom STUN server (leave empty to use Google public server)', turnServer: 'TURN Server', turnServerPlaceholder: 'turn:turn.example.com:3478', - turnServerHint: 'Custom TURN relay server (leave empty to use public server)', + turnServerHint: 'Custom TURN relay server (required for production)', turnUsername: 'TURN Username', turnPassword: 'TURN Password', turnPasswordConfigured: 'Password already configured. Leave empty to keep current password.', - turnCredentialsHint: 'Credentials for TURN server authentication (only needed for custom servers)', + turnCredentialsHint: 'Credentials for TURN server authentication', iceConfigNote: 'Note: Changes require reconnecting the WebRTC session to take effect.', }, virtualKeyboard: { @@ -703,7 +702,7 @@ export default { serverSettings: 'Server Settings', rendezvousServer: 'ID Server', rendezvousServerPlaceholder: 'hbbs.example.com:21116', - rendezvousServerHint: 'Leave empty to use public server', + rendezvousServerHint: 'Configure your RustDesk server address', relayServer: 'Relay Server', relayServerPlaceholder: 'hbbr.example.com:21117', relayServerHint: 'Relay server address, auto-derived from ID server if empty', @@ -711,10 +710,6 @@ export default { relayKeyPlaceholder: 'Enter relay server key', relayKeySet: '••••••••', relayKeyHint: 'Authentication key for relay server (if server uses -k option)', - publicServerInfo: 'Public Server Info', - publicServerAddress: 'Server Address', - publicServerKey: 'Connection Key', - usingPublicServer: 'Using public server', deviceInfo: 'Device Info', deviceId: 'Device ID', deviceIdHint: 'Use this ID in RustDesk client to connect', diff --git a/web/src/i18n/zh-CN.ts b/web/src/i18n/zh-CN.ts index 54c6b8a8..58fc8f22 100644 --- a/web/src/i18n/zh-CN.ts +++ b/web/src/i18n/zh-CN.ts @@ -570,18 +570,17 @@ export default { // WebRTC / ICE webrtcSettings: 'WebRTC 设置', webrtcSettingsDesc: '配置 STUN/TURN 服务器以实现 NAT 穿透', - usingPublicIceServers: '正在使用公共 ICE 服务器', - publicIceServersHint: '留空以使用内置的公共 STUN/TURN 服务器进行 NAT 穿透', + publicIceServersHint: '留空将使用 Google 公共 STUN 服务器,TURN 服务器需自行配置', stunServer: 'STUN 服务器', stunServerPlaceholder: 'stun:stun.l.google.com:19302', - stunServerHint: '自定义 STUN 服务器(留空则使用公共服务器)', + stunServerHint: '自定义 STUN 服务器(留空则使用 Google 公共服务器)', turnServer: 'TURN 服务器', turnServerPlaceholder: 'turn:turn.example.com:3478', - turnServerHint: '自定义 TURN 中继服务器(留空则使用公共服务器)', + turnServerHint: '自定义 TURN 中继服务器(生产环境必须配置)', turnUsername: 'TURN 用户名', turnPassword: 'TURN 密码', turnPasswordConfigured: '密码已配置。留空则保持当前密码。', - turnCredentialsHint: '用于 TURN 服务器认证的凭据(仅自定义服务器需要)', + turnCredentialsHint: '用于 TURN 服务器认证的凭据', iceConfigNote: '注意:更改后需要重新连接 WebRTC 会话才能生效。', }, virtualKeyboard: { @@ -703,7 +702,7 @@ export default { serverSettings: '服务器设置', rendezvousServer: 'ID 服务器', rendezvousServerPlaceholder: 'hbbs.example.com:21116', - rendezvousServerHint: '留空则使用公共服务器', + rendezvousServerHint: '请配置您的 RustDesk 服务器地址', relayServer: '中继服务器', relayServerPlaceholder: 'hbbr.example.com:21117', relayServerHint: '中继服务器地址,留空则自动从 ID 服务器推导', @@ -711,10 +710,6 @@ export default { relayKeyPlaceholder: '输入中继服务器密钥', relayKeySet: '••••••••', relayKeyHint: '中继服务器认证密钥(如果服务器使用 -k 选项)', - publicServerInfo: '公共服务器信息', - publicServerAddress: '服务器地址', - publicServerKey: '连接密钥', - usingPublicServer: '正在使用公共服务器', deviceInfo: '设备信息', deviceId: '设备 ID', deviceIdHint: '此 ID 用于 RustDesk 客户端连接', diff --git a/web/src/types/generated.ts b/web/src/types/generated.ts index 4a6dd7b8..ff4e168b 100644 --- a/web/src/types/generated.ts +++ b/web/src/types/generated.ts @@ -314,9 +314,8 @@ export interface RustDeskConfig { /** Enable RustDesk protocol */ enabled: boolean; /** - * Rendezvous server address (hbbs), e.g., "rs.example.com" or "192.168.1.100" - * Port defaults to 21116 if not specified - * If empty, uses the public server from secrets.toml + * Rendezvous server address (hbbs), e.g., "rs.example.com" or "192.168.1.100:21116" + * Required for RustDesk to function */ rendezvous_server: string; /** @@ -517,14 +516,6 @@ export interface MsdConfigUpdate { virtual_drive_size_mb?: number; } -/** Public server information for display to users */ -export interface PublicServerInfo { - /** Public server address */ - server: string; - /** Public key for client connection */ - public_key: string; -} - export interface RustDeskConfigUpdate { enabled?: boolean; rendezvous_server?: string; diff --git a/web/src/views/ConsoleView.vue b/web/src/views/ConsoleView.vue index 8e837ace..5eaff896 100644 --- a/web/src/views/ConsoleView.vue +++ b/web/src/views/ConsoleView.vue @@ -105,6 +105,12 @@ const mousePosition = ref({ x: 0, y: 0 }) const lastMousePosition = ref({ x: 0, y: 0 }) // Track last position for relative mode const isPointerLocked = ref(false) // Track pointer lock state +// Mouse move throttling (60 Hz = ~16.67ms interval) +const MOUSE_SEND_INTERVAL_MS = 16 +let mouseSendTimer: ReturnType | null = null +let pendingMouseMove: { type: 'move' | 'move_abs'; x: number; y: number } | null = null +let accumulatedDelta = { x: 0, y: 0 } // For relative mode: accumulate deltas between sends + // Cursor visibility (from localStorage, updated via storage event) const cursorVisible = ref(localStorage.getItem('hidShowCursor') !== 'false') @@ -1479,20 +1485,21 @@ function handleMouseMove(e: MouseEvent) { const y = Math.round((e.clientY - rect.top) / rect.height * 32767) mousePosition.value = { x, y } - sendMouseEvent({ type: 'move_abs', x, y }) + // Queue for throttled sending (absolute mode: just update pending position) + pendingMouseMove = { type: 'move_abs', x, y } + ensureMouseSendTimer() } else { // Relative mode: use movementX/Y when pointer is locked if (isPointerLocked.value) { const dx = e.movementX const dy = e.movementY - // Only send if there's actual movement + // Only accumulate if there's actual movement if (dx !== 0 || dy !== 0) { - // Clamp to i8 range (-127 to 127) - const clampedDx = Math.max(-127, Math.min(127, dx)) - const clampedDy = Math.max(-127, Math.min(127, dy)) - - sendMouseEvent({ type: 'move', x: clampedDx, y: clampedDy }) + // Accumulate deltas for throttled sending + accumulatedDelta.x += dx + accumulatedDelta.y += dy + ensureMouseSendTimer() } // Update display position (accumulated delta for display only) @@ -1504,6 +1511,50 @@ function handleMouseMove(e: MouseEvent) { } } +// Start the mouse send timer if not already running +function ensureMouseSendTimer() { + if (mouseSendTimer !== null) return + + // Send immediately on first event, then throttle + flushMouseMove() + + mouseSendTimer = setInterval(() => { + if (!flushMouseMove()) { + // No pending data, stop the timer + if (mouseSendTimer !== null) { + clearInterval(mouseSendTimer) + mouseSendTimer = null + } + } + }, MOUSE_SEND_INTERVAL_MS) +} + +// Flush pending mouse move data, returns true if data was sent +function flushMouseMove(): boolean { + if (mouseMode.value === 'absolute') { + if (pendingMouseMove) { + sendMouseEvent(pendingMouseMove) + pendingMouseMove = null + return true + } + } else { + // Relative mode: send accumulated delta + if (accumulatedDelta.x !== 0 || accumulatedDelta.y !== 0) { + // Clamp to i8 range (-127 to 127) + const clampedDx = Math.max(-127, Math.min(127, accumulatedDelta.x)) + const clampedDy = Math.max(-127, Math.min(127, accumulatedDelta.y)) + + sendMouseEvent({ type: 'move', x: clampedDx, y: clampedDy }) + + // Subtract sent amount (keep remainder for next send if clamped) + accumulatedDelta.x -= clampedDx + accumulatedDelta.y -= clampedDy + return true + } + } + return false +} + // Track pressed mouse button for window-level mouseup handling const pressedMouseButton = ref<'left' | 'right' | 'middle' | null>(null) @@ -1746,6 +1797,12 @@ onUnmounted(() => { // Reset initial device info flag initialDeviceInfoReceived = false + // Clear mouse send timer + if (mouseSendTimer !== null) { + clearInterval(mouseSendTimer) + mouseSendTimer = null + } + // Clear ttyd poll interval if (ttydPollInterval) { clearInterval(ttydPollInterval) diff --git a/web/src/views/SettingsView.vue b/web/src/views/SettingsView.vue index 309a9fe2..087289e6 100644 --- a/web/src/views/SettingsView.vue +++ b/web/src/views/SettingsView.vue @@ -47,12 +47,6 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog' -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from '@/components/ui/tooltip' import { Monitor, Keyboard, @@ -82,7 +76,6 @@ import { ExternalLink, Copy, ScreenShare, - CircleHelp, } from 'lucide-vue-next' const { t, locale } = useI18n() @@ -2094,28 +2087,7 @@ onMounted(async () => { v-model="rustdeskLocalConfig.rendezvous_server" :placeholder="t('extensions.rustdesk.rendezvousServerPlaceholder')" /> -
-

{{ t('extensions.rustdesk.rendezvousServerHint') }}

- - - - - - -
-

{{ t('extensions.rustdesk.publicServerInfo') }}

-
-

{{ t('extensions.rustdesk.publicServerAddress') }}: {{ rustdeskStatus.public_server.server }}

-

{{ t('extensions.rustdesk.publicServerKey') }}: {{ rustdeskStatus.public_server.public_key }}

-
-
-
-
-
-
-

- {{ t('extensions.rustdesk.usingPublicServer') }} -

+

{{ t('extensions.rustdesk.rendezvousServerHint') }}