diff --git a/.github/workflows/ci-freebsd.yml b/.github/workflows/ci-freebsd.yml new file mode 100644 index 000000000..ab3f56010 --- /dev/null +++ b/.github/workflows/ci-freebsd.yml @@ -0,0 +1,272 @@ +--- +name: CI-FreeBSD +permissions: + contents: read + +on: + workflow_call: + inputs: + release_commit: + required: true + type: string + release_version: + required: true + type: string + +env: + BRANCH: ${{ github.head_ref || github.ref_name }} + BUILD_VERSION: ${{ inputs.release_version }} + COMMIT: ${{ inputs.release_commit }} + FREEBSD_CLANG_VERSION: 19 + +jobs: + setup-matrix: + name: Setup Build Matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.generate-matrix.outputs.matrix }} + steps: + - name: Generate Matrix + id: generate-matrix + shell: bash + run: | + # Base matrix with amd64 build + matrix='{ + "include": [ + { + "bsd_release": "14.3", + "arch": "x86_64", + "cmake_processor": "amd64", + "runner": "ubuntu-latest" + } + ] + }' + + # Add aarch64 build only if not a pull request event + if [[ "${{ github.event_name }}" != "pull_request" ]]; then + matrix=$(echo "$matrix" | jq '.include += [{ + "bsd_release": "14.3", + "arch": "aarch64", + "cmake_processor": "aarch64", + "runner": "ubuntu-latest" + }]') + fi + + # Use heredoc for multiline JSON output + { + echo "matrix<> "${GITHUB_OUTPUT}" + + echo "Generated matrix:" + echo "$matrix" | jq . + + build_freebsd: + name: ${{ matrix.cmake_processor }}-${{ matrix.bsd_release }} + runs-on: ubuntu-latest + needs: setup-matrix + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.setup-matrix.outputs.matrix) }} + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + submodules: recursive + + - name: Get Processor Count + id: processor_count + shell: bash + run: | + PROCESSOR_COUNT=$(nproc) + echo "PROCESSOR_COUNT=${PROCESSOR_COUNT}" >> "${GITHUB_OUTPUT}" + echo "PROCESSOR_COUNT: $PROCESSOR_COUNT" + + - name: Setup FreeBSD + uses: vmactions/freebsd-vm@783ae15c0393f8a2582a139f76cc55f2d887b4a6 # v1.2.6 + with: + arch: ${{ matrix.arch }} + cpu: ${{ steps.processor_count.outputs.PROCESSOR_COUNT }} + envs: 'BRANCH BUILD_VERSION COMMIT' + # TODO: there is no libcap for freebsd... we need graphics/libdrm if we find a way to use libcap + # TODO: docs are off because doxygen is too old: https://www.freshports.org/devel/doxygen/ must be >= 1.10 + prepare: | + set -e + + pkg update + pkg upgrade -y + pkg install -y \ + audio/opus \ + audio/pulseaudio \ + devel/boost-all \ + devel/cmake-core \ + devel/evdev-proto \ + devel/git \ + devel/libayatana-appindicator \ + devel/libevdev \ + devel/libnotify \ + devel/llvm${{ env.FREEBSD_CLANG_VERSION }} \ + devel/ninja \ + devel/pkgconf \ + ftp/curl \ + graphics/libdrm \ + graphics/wayland \ + lang/python312 \ + multimedia/libva \ + net/miniupnpc \ + ports-mgmt/pkg \ + security/openssl \ + shells/bash \ + www/npm \ + x11/libX11 \ + x11/libxcb \ + x11/libXfixes \ + x11/libXrandr \ + x11/libXtst \ + x11-servers/xorg-server + + # create symlink for shebang bash compatibility + ln -s /usr/local/bin/bash /bin/bash + + # setup python + ln -s /usr/local/bin/python3.12 /usr/local/bin/python + python -m ensurepip + release: ${{ matrix.bsd_release }} + run: | + set -e + # install gcvor + python -m pip install gcovr + + # fix git safe.directory issues + git config --global --add safe.directory "*" + sync: nfs # sshfs is used for build-deps; however it's much slower than nfs + + - name: Configure + shell: freebsd {0} + run: | + set -e + cd "${GITHUB_WORKSPACE}" + + cc_path="$(which clang${{ env.FREEBSD_CLANG_VERSION }})" + cxx_path="$(which clang++${{ env.FREEBSD_CLANG_VERSION }})" + + export CC="${cc_path}" + export CXX="${cxx_path}" + + mkdir -p build + cmake \ + -B build \ + -G Ninja \ + -S . \ + -DBUILD_DOCS=OFF \ + -DBUILD_WERROR=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/local \ + -DSUNSHINE_ASSETS_DIR=share/assets \ + -DSUNSHINE_EXECUTABLE_PATH=/usr/local/bin/sunshine \ + -DSUNSHINE_ENABLE_CUDA=OFF \ + -DSUNSHINE_ENABLE_DRM=OFF \ + -DSUNSHINE_ENABLE_WAYLAND=ON \ + -DSUNSHINE_ENABLE_X11=ON \ + -DSUNSHINE_PUBLISHER_NAME='${{ github.repository_owner }}' \ + -DSUNSHINE_PUBLISHER_WEBSITE='https://app.lizardbyte.dev' \ + -DSUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support' + + - name: Build + shell: freebsd {0} + run: | + set -e + cd "${GITHUB_WORKSPACE}" + ninja -C build + + - name: Package + shell: freebsd {0} + run: | + set -e + cd "${GITHUB_WORKSPACE}" + + mkdir -p artifacts + + cd build + cpack -G FREEBSD + + # move compiled files to artifacts + mv ./cpack_artifacts/Sunshine.pkg \ + ../artifacts/Sunshine-FreeBSD-${{ matrix.bsd_release }}-${{ matrix.cmake_processor }}.pkg + + - name: Debug + if: always() + shell: bash + working-directory: build/cpack_artifacts/_CPack_Packages/FreeBSD/FREEBSD/Sunshine + run: | + echo "FreeBSD CPack Debug" + echo "===== Staging Directory Contents =====" + ls -la + echo "" + echo "===== +MANIFEST Content =====" + cat +MANIFEST + echo "" + + # use tar to print the contents of the pkg + cd "${GITHUB_WORKSPACE}/artifacts" + echo "===== Package Contents =====" + tar -tvf Sunshine-FreeBSD-${{ matrix.bsd_release }}-${{ matrix.cmake_processor }}.pkg + echo "" + echo "===== Package Statistics =====" + echo -n "Total files in package: " + tar -tf Sunshine-FreeBSD-${{ matrix.bsd_release }}-${{ matrix.cmake_processor }}.pkg 2>&1 | wc -l + echo "" + echo "Package file size:" + ls -lh Sunshine-FreeBSD-${{ matrix.bsd_release }}-${{ matrix.cmake_processor }}.pkg + + - name: Test + id: test + shell: freebsd {0} + run: | + set -e + cd "${GITHUB_WORKSPACE}/build/tests" + + export DISPLAY=:1 + Xvfb ${DISPLAY} -screen 0 1024x768x24 & + XVFB_PID=$! + + ./test_sunshine --gtest_color=yes --gtest_output=xml:test_results.xml + + kill ${XVFB_PID} + + - name: Generate gcov report + id: test_report + # any except canceled or skipped + if: >- + always() && + (steps.test.outcome == 'success' || steps.test.outcome == 'failure') + shell: freebsd {0} + run: | + cd "${GITHUB_WORKSPACE}/build" + python -m gcovr . -r ../src \ + --exclude-noncode-lines \ + --exclude-throw-branches \ + --exclude-unreachable-branches \ + --verbose \ + --xml-pretty \ + -o coverage.xml + + - name: Upload coverage artifact + if: >- + always() && + (steps.test_report.outcome == 'success') + uses: actions/upload-artifact@v5 + with: + name: coverage-FreeBSD-${{ matrix.bsd_release }}-${{ matrix.cmake_processor }} + path: | + build/coverage.xml + build/tests/test_results.xml + if-no-files-found: error + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: build-FreeBSD-${{ matrix.bsd_release }}-${{ matrix.cmake_processor }} + path: artifacts/ + if-no-files-found: error diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c36592565..f7c8b99ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,6 +69,14 @@ jobs: GH_BOT_TOKEN: ${{ secrets.GH_BOT_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + build-freebsd: + name: FreeBSD + needs: release-setup + uses: ./.github/workflows/ci-freebsd.yml + with: + release_commit: ${{ needs.release-setup.outputs.release_commit }} + release_version: ${{ needs.release-setup.outputs.release_version }} + build-homebrew: name: Homebrew needs: release-setup @@ -133,6 +141,7 @@ jobs: !cancelled() && startsWith(github.repository, 'LizardByte/') needs: + - build-freebsd - build-linux - build-linux-flatpak - build-homebrew @@ -142,29 +151,53 @@ jobs: fail-fast: false matrix: include: + - name: FreeBSD-14.3-amd64 + coverage: true + pr: true + - name: FreeBSD-14.3-aarch64 + coverage: true + pr: false - name: Linux-AppImage coverage: true + pr: true - name: Homebrew-macos-14 coverage: false + pr: true - name: Homebrew-macos-15 coverage: false + pr: true - name: Homebrew-macos-26 coverage: false + pr: true - name: Homebrew-ubuntu-latest coverage: false + pr: true - name: Windows-AMD64 coverage: true + pr: true steps: + - name: Should run + id: should_run + run: | + if [ ${{ github.event_name }} != 'pull_request' ] || [ ${{ matrix.pr }} == 'true' ]; then + echo "SHOULD_RUN=true" >> "${GITHUB_OUTPUT}" + else + echo "SHOULD_RUN=false" >> "${GITHUB_OUTPUT}" + fi + - name: Checkout + if: steps.should_run.outputs.SHOULD_RUN == 'true' uses: actions/checkout@v5 - name: Download coverage artifact + if: steps.should_run.outputs.SHOULD_RUN == 'true' uses: actions/download-artifact@v6 with: name: coverage-${{ matrix.name }} path: _coverage - name: Upload test results + if: steps.should_run.outputs.SHOULD_RUN == 'true' uses: codecov/test-results-action@v1 with: disable_search: true @@ -175,8 +208,8 @@ jobs: verbose: true - name: Upload coverage + if: steps.should_run.outputs.SHOULD_RUN == 'true' && matrix.coverage != false uses: codecov/codecov-action@v5 - if: matrix.coverage != false with: disable_search: true fail_ci_if_error: true @@ -193,6 +226,7 @@ jobs: needs: - release-setup - build-docker + - build-freebsd - build-linux - build-linux-flatpak - build-homebrew diff --git a/CMakeLists.txt b/CMakeLists.txt index 5cedee558..08c0b1ba3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,10 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) endif() +if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + set(FREEBSD ON) +endif() + # set the module path, used for includes set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") diff --git a/README.md b/README.md index 108d11c90..da3b61b62 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,166 @@ LizardByte has the full documentation hosted on [Read the Docs](https://docs.liz * [Stable Docs](https://docs.lizardbyte.dev/projects/sunshine/latest/) * [Beta Docs](https://docs.lizardbyte.dev/projects/sunshine/master/) +## 🎮 Feature Compatibility + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Platform Feature Support
FeatureFreeBSDLinuxmacOSWindows
Gamepad Emulation
+ What type of gamepads can be emulated on the host.
+ Clients may support other gamepads. +
DualShock / DS4 (PlayStation 4)
DualSense / DS5 (PlayStation 5)
Nintendo Switch Pro
Xbox 360
Xbox One/Series
GPU Encoding
AMD/AMF✅ (vaapi)✅ (vaapi)✅ (Video Toolbox)
Intel QuickSync✅ (vaapi)✅ (vaapi)✅ (Video Toolbox)
NVIDIA NVENC✅ (vaapi)✅ (vaapi)✅ (Video Toolbox)
Screen Capture
DXGI
KMS
NVIDIA NvFBC🟡
  ↳ X11 Support
  ↳ Wayland Support
Video Toolbox
Wayland
Windows.Graphics.Capture🟡
  ↳ Portable
  ↳ Service
X11
+ +**Legend:** ✅ Supported | 🟡 Partial Support | ❌ Not Yet Supported | ➖ Not Applicable + ## 🖥️ System Requirements > [!WARNING] @@ -50,7 +210,7 @@ LizardByte has the full documentation hosted on [Read the Docs](https://docs.liz Intel:
-   Linux: VAAPI-compatible, see: VAAPI hardware support
+   FreeBSD/Linux: VAAPI-compatible, see: VAAPI hardware support
  Windows: Skylake or newer with QuickSync encoding support @@ -69,11 +229,8 @@ LizardByte has the full documentation hosted on [Read the Docs](https://docs.liz 4GB or more - OS - Windows: 10+ (Windows Server does not support virtual gamepads) - - - macOS: 14+ + OS + FreeBSD: 14.3+ Linux/Debian: 13+ (trixie) @@ -84,6 +241,12 @@ LizardByte has the full documentation hosted on [Read the Docs](https://docs.liz Linux/Ubuntu: 22.04+ (jammy) + + macOS: 14+ + + + Windows: 11+ (Windows Server does not support virtual gamepads) + Network Host: 5GHz, 802.11ac @@ -106,14 +269,14 @@ LizardByte has the full documentation hosted on [Read the Docs](https://docs.liz Intel:
-   Linux: HD Graphics 510 or higher
+   FreeBSD/Linux: HD Graphics 510 or higher
  Windows: Skylake or newer with QuickSync encoding support Nvidia:
-   Linux: GeForce RTX 2000 series or higher
+   FreeBSD/Linux: GeForce RTX 2000 series or higher
  Windows: Geforce GTX 1080 or higher diff --git a/cmake/compile_definitions/linux.cmake b/cmake/compile_definitions/linux.cmake index e4ed683f9..6f98c3555 100644 --- a/cmake/compile_definitions/linux.cmake +++ b/cmake/compile_definitions/linux.cmake @@ -1,6 +1,10 @@ # linux specific compile definitions -add_compile_definitions(SUNSHINE_PLATFORM="linux") +if(FREEBSD) + add_compile_definitions(SUNSHINE_PLATFORM="freebsd") +else() + add_compile_definitions(SUNSHINE_PLATFORM="linux") +endif() # AppImage if(${SUNSHINE_BUILD_APPIMAGE}) @@ -211,6 +215,9 @@ endif() # These need to be set before adding the inputtino subdirectory in order for them to be picked up set(LIBEVDEV_CUSTOM_INCLUDE_DIR "${EVDEV_INCLUDE_DIR}") set(LIBEVDEV_CUSTOM_LIBRARY "${EVDEV_LIBRARY}") +if(FREEBSD) + set(USE_UHID OFF) +endif() add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/inputtino") list(APPEND SUNSHINE_EXTERNAL_LIBRARIES inputtino::libinputtino) diff --git a/cmake/dependencies/common.cmake b/cmake/dependencies/common.cmake index 97319be6f..24a808837 100644 --- a/cmake/dependencies/common.cmake +++ b/cmake/dependencies/common.cmake @@ -30,6 +30,9 @@ include_directories(SYSTEM ${MINIUPNP_INCLUDE_DIRS}) if(NOT DEFINED FFMPEG_PREPARED_BINARIES) if(WIN32) set(FFMPEG_PLATFORM_LIBRARIES mfplat ole32 strmiids mfuuid vpl) + elseif(FREEBSD) + # numa is not available on FreeBSD + set(FFMPEG_PLATFORM_LIBRARIES va va-drm va-x11 X11) elseif(UNIX AND NOT APPLE) set(FFMPEG_PLATFORM_LIBRARIES numa va va-drm va-x11 X11) endif() diff --git a/cmake/packaging/freebsd_custom_cpack.cmake b/cmake/packaging/freebsd_custom_cpack.cmake new file mode 100644 index 000000000..8446789c3 --- /dev/null +++ b/cmake/packaging/freebsd_custom_cpack.cmake @@ -0,0 +1,138 @@ +# FreeBSD post-build script to fix +POST_INSTALL and +PRE_DEINSTALL scripts +# in the generated .pkg file. +# +# This script runs AFTER CPack creates the .pkg file. We need to: +# 1. Extract the .pkg file (which is a tar.xz archive) +# 2. Add our install/deinstall scripts to the root +# 3. Remove script entries from the +MANIFEST files section +# 4. Repack the .pkg file using pkg-static + +if(NOT CPACK_GENERATOR STREQUAL "FREEBSD") + return() +endif() + +message(STATUS "FreeBSD post-build: Processing install/deinstall scripts") + +# Get script paths from the list we set +if(NOT DEFINED CPACK_FREEBSD_PACKAGE_SCRIPTS) + message(FATAL_ERROR "FreeBSD post-build: CPACK_FREEBSD_PACKAGE_SCRIPTS not defined") +endif() + +list(LENGTH CPACK_FREEBSD_PACKAGE_SCRIPTS _script_count) +if(_script_count EQUAL 0) + message(FATAL_ERROR "FreeBSD post-build: CPACK_FREEBSD_PACKAGE_SCRIPTS is empty") +endif() + +# Find the package file in CPACK_TOPLEVEL_DIRECTORY +file(GLOB _pkg_files "${CPACK_TOPLEVEL_DIRECTORY}/*.pkg") + +if(NOT _pkg_files) + message(FATAL_ERROR "FreeBSD post-build: No .pkg file found in ${CPACK_TOPLEVEL_DIRECTORY}") +endif() + +list(GET _pkg_files 0 _pkg_file) +message(STATUS "FreeBSD post-build: Found package: ${_pkg_file}") + +# Create a temporary directory for extraction +get_filename_component(_pkg_dir "${_pkg_file}" DIRECTORY) +set(_tmp_dir "${_pkg_dir}/pkg_repack_tmp") +file(REMOVE_RECURSE "${_tmp_dir}") +file(MAKE_DIRECTORY "${_tmp_dir}") + +# Extract the package using tar (pkg files are tar.xz archives) +message(STATUS "FreeBSD post-build: Extracting package...") +find_program(TAR_EXECUTABLE tar REQUIRED) +find_program(PKG_STATIC_EXECUTABLE pkg-static REQUIRED) + +execute_process( + COMMAND ${TAR_EXECUTABLE} -xf ${_pkg_file} --no-same-owner --numeric-owner + WORKING_DIRECTORY "${_tmp_dir}" + RESULT_VARIABLE _extract_result + ERROR_VARIABLE _extract_error +) + +if(NOT _extract_result EQUAL 0) + message(FATAL_ERROR "FreeBSD post-build: Failed to extract package: ${_extract_error}") +endif() + +# Debug: Check what was extracted +file(GLOB_RECURSE _extracted_files RELATIVE "${_tmp_dir}" "${_tmp_dir}/*") +list(LENGTH _extracted_files _file_count) +message(STATUS "FreeBSD post-build: Extracted ${_file_count} files") + +# Copy the install/deinstall scripts to the extracted package root +message(STATUS "FreeBSD post-build: Adding install/deinstall scripts...") + +foreach(script_path ${CPACK_FREEBSD_PACKAGE_SCRIPTS}) + if(EXISTS "${script_path}") + get_filename_component(_script_name "${script_path}" NAME) + file(COPY "${script_path}" + DESTINATION "${_tmp_dir}/" + FILE_PERMISSIONS + OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) + message(STATUS " Added: ${_script_name}") + else() + message(FATAL_ERROR "FreeBSD post-build: Script not found: ${script_path}") + endif() +endforeach() + +# Repack the package using pkg-static create +message(STATUS "FreeBSD post-build: Repacking package...") + +# Debug: Verify files before repacking +file(GLOB_RECURSE _files_before_repack RELATIVE "${_tmp_dir}" "${_tmp_dir}/*") +list(LENGTH _files_before_repack _count_before_repack) +message(STATUS "FreeBSD post-build: About to repack ${_count_before_repack} files") + +# Debug: Check directory structure +if(EXISTS "${_tmp_dir}/usr") + message(STATUS "FreeBSD post-build: Found usr directory in extracted package") + file(GLOB_RECURSE _usr_files RELATIVE "${_tmp_dir}/usr" "${_tmp_dir}/usr/*") + list(LENGTH _usr_files _usr_file_count) + message(STATUS "FreeBSD post-build: usr directory contains ${_usr_file_count} files") +endif() + +# Create metadata directory separate from rootdir +set(_metadata_dir "${_tmp_dir}/metadata") +file(MAKE_DIRECTORY "${_metadata_dir}") + +# Move manifest and scripts to metadata directory +file(GLOB _metadata_files "${_tmp_dir}/+*") +foreach(meta_file ${_metadata_files}) + get_filename_component(_meta_name "${meta_file}" NAME) + file(RENAME "${meta_file}" "${_metadata_dir}/${_meta_name}") + message(STATUS "FreeBSD post-build: Moved ${_meta_name} to metadata directory") +endforeach() + +# Use pkg-static create to rebuild the package +# pkg create -r rootdir -m manifestdir -o outdir +# The rootdir should contain the actual files (usr/local/...) +# The manifestdir should contain +MANIFEST and install scripts +execute_process( + COMMAND ${PKG_STATIC_EXECUTABLE} create -r ${_tmp_dir} -m ${_metadata_dir} -o ${_pkg_dir} + RESULT_VARIABLE _pack_result + OUTPUT_VARIABLE _pack_output + ERROR_VARIABLE _pack_error +) + +if(NOT _pack_result EQUAL 0) + message(FATAL_ERROR "FreeBSD post-build: Failed to repack package: ${_pack_error}") +endif() + +# Find the generated package file (pkg create generates its own name based on manifest) +file(GLOB _new_pkg_files "${_pkg_dir}/Sunshine-*.pkg") +if(NOT _new_pkg_files) + message(FATAL_ERROR "FreeBSD post-build: pkg-static create succeeded but no package file was generated") +endif() + +list(GET _new_pkg_files 0 _generated_pkg) + +# Replace the original package with the newly created one +file(REMOVE "${_pkg_file}") +file(RENAME "${_generated_pkg}" "${_pkg_file}") +message(STATUS "FreeBSD post-build: Successfully processed package") + +# Clean up +file(REMOVE_RECURSE "${_tmp_dir}") diff --git a/cmake/packaging/linux.cmake b/cmake/packaging/linux.cmake index 617bf375b..3be299765 100644 --- a/cmake/packaging/linux.cmake +++ b/cmake/packaging/linux.cmake @@ -34,10 +34,34 @@ else() endif() endif() +# RPM specific +set(CPACK_RPM_PACKAGE_LICENSE "GPLv3") + +# FreeBSD specific +set(CPACK_FREEBSD_PACKAGE_MAINTAINER "${CPACK_PACKAGE_VENDOR}") +set(CPACK_FREEBSD_PACKAGE_ORIGIN "misc/${CPACK_PACKAGE_NAME}") +set(CPACK_FREEBSD_PACKAGE_LICENSE "GPLv3") + # Post install set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst") set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst") +# FreeBSD post install/deinstall scripts +if(FREEBSD) + # Note: CPack's FreeBSD generator does NOT natively support install/deinstall scripts + # like CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA or CPACK_RPM_POST_INSTALL_SCRIPT_FILE. + # This is a known limitation of the CPack FREEBSD generator. + # + # Workaround: Use CPACK_POST_BUILD_SCRIPTS to extract the generated .pkg file, + # add the install/deinstall scripts, and repack the package. This ensures they are + # recognized as package control scripts rather than installed files. + set(CPACK_FREEBSD_PACKAGE_SCRIPTS + "${SUNSHINE_SOURCE_ASSETS_DIR}/bsd/misc/+POST_INSTALL" + "${SUNSHINE_SOURCE_ASSETS_DIR}/bsd/misc/+PRE_DEINSTALL" + ) + list(APPEND CPACK_POST_BUILD_SCRIPTS "${CMAKE_MODULE_PATH}/packaging/freebsd_custom_cpack.cmake") +endif() + # Apply setcap for RPM # https://github.com/coreos/rpm-ostree/discussions/5036#discussioncomment-10291071 set(CPACK_RPM_USER_FILELIST "%caps(cap_sys_admin+p) ${SUNSHINE_EXECUTABLE_PATH}") @@ -77,6 +101,15 @@ set(CPACK_RPM_PACKAGE_REQUIRES "\ openssl >= 3.0.2, \ pulseaudio-libs >= 10.0, \ which >= 2.21") +list(APPEND CPACK_FREEBSD_PACKAGE_DEPS + audio/opus + ftp/curl + devel/libevdev + net/avahi + x11/libX11 + net/miniupnpc + security/openssl +) if(NOT BOOST_USE_STATIC) set(CPACK_DEBIAN_PACKAGE_DEPENDS "\ @@ -91,6 +124,9 @@ if(NOT BOOST_USE_STATIC) boost-locale >= ${Boost_VERSION}, \ boost-log >= ${Boost_VERSION}, \ boost-program-options >= ${Boost_VERSION}") + list(APPEND CPACK_FREEBSD_PACKAGE_DEPS + devel/boost-libs + ) endif() # This should automatically figure out dependencies on packages @@ -142,6 +178,10 @@ if(${SUNSHINE_TRAY} STREQUAL 1) set(CPACK_RPM_PACKAGE_REQUIRES "\ ${CPACK_RPM_PACKAGE_REQUIRES}, \ libappindicator-gtk3 >= 12.10.0") + list(APPEND CPACK_FREEBSD_PACKAGE_DEPS + devel/libayatana-appindicator + devel/libnotify + ) endif() # desktop file diff --git a/docs/app_examples.md b/docs/app_examples.md index 85f703374..e6e251b68 100644 --- a/docs/app_examples.md +++ b/docs/app_examples.md @@ -26,6 +26,14 @@ and applications to Sunshine. > process is killed. @tabs{ + @tab{FreeBSD | + \| Field \| Value \| + \|------------------------------\|------------------------------------------------------\| + \| Application Name \| @code{}Steam Big Picture@endcode \| + \| Command Preporations -> Undo \| @code{}setsid steam steam://close/bigpicture@endcode \| + \| Detached Commands \| @code{}setsid steam steam://open/bigpicture@endcode \| + \| Image \| @code{}steam.png@endcode \| + } @tab{Linux | \| Field \| Value \| \|------------------------------\|------------------------------------------------------\| @@ -97,6 +105,12 @@ and applications to Sunshine. #### URI @tabs{ + @tab{FreeBSD | + \| Field \| Value \| + \|-------------------\|------------------------------------------------------\| + \| Application Name \| @code{}Surviving Mars@endcode \| + \| Detached Commands \| @code{}setsid steam steam://rungameid/464920@endcode \| + } @tab{Linux | \| Field \| Value \| \|-------------------\|------------------------------------------------------\| @@ -119,6 +133,13 @@ and applications to Sunshine. #### Binary (w/ working directory @tabs{ + @tab{FreeBSD | + \| Field \| Value \| + \|-------------------\|--------------------------------------------------------------\| + \| Application Name \| @code{}Surviving Mars@endcode \| + \| Command \| @code{}MarsSteam@endcode \| + \| Working Directory \| @code{}~/.steam/steam/SteamApps/common/Survivng Mars@endcode \| + } @tab{Linux | \| Field \| Value \| \|-------------------\|--------------------------------------------------------------\| @@ -144,6 +165,12 @@ and applications to Sunshine. #### Binary (w/o working directory) @tabs{ + @tab{FreeBSD | + \| Field \| Value \| + \|-------------------\|------------------------------------------------------------------------\| + \| Application Name \| @code{}Surviving Mars@endcode \| + \| Command \| @code{}~/.steam/steam/SteamApps/common/Survivng Mars/MarsSteam@endcode \| + } @tab{Linux | \| Field \| Value \| \|-------------------\|------------------------------------------------------------------------\| diff --git a/docs/building.md b/docs/building.md index 5a1766227..56febbce8 100644 --- a/docs/building.md +++ b/docs/building.md @@ -14,6 +14,39 @@ It is recommended to use one of the following compilers: ### Dependencies +#### FreeBSD +> [!CAUTION] +> Sunshine support for FreeBSD is experimental and may be incomplete or not work as expected + +##### Install dependencies +```sh +pkg install -y \ + audio/opus \ + audio/pulseaudio \ + devel/cmake \ + devel/evdev-proto \ + devel/git \ + devel/libayatana-appindicator \ + devel/libevdev \ + devel/libnotify \ + devel/ninja \ + devel/pkgconf \ + ftp/curl \ + graphics/libdrm \ + graphics/wayland \ + multimedia/libva \ + net/miniupnpc \ + ports-mgmt/pkg \ + security/openssl \ + shells/bash \ + www/npm \ + x11/libX11 \ + x11/libxcb \ + x11/libXfixes \ + x11/libXrandr \ + x11/libXtst +``` + #### Linux Dependencies vary depending on the distribution. You can reference our [linux_build.sh](https://github.com/LizardByte/Sunshine/blob/master/scripts/linux_build.sh) script for a list of @@ -135,6 +168,11 @@ ninja -C build ### Package @tabs{ + @tab{FreeBSD | @tabs{ + @tab{pkg | ```bash + cpack -G FREEBSD --config ./build/CPackConfig.cmake + ```} + }} @tab{Linux | @tabs{ @tab{deb | ```bash cpack -G DEB --config ./build/CPackConfig.cmake diff --git a/docs/configuration.md b/docs/configuration.md index 2264aa8d4..d060c4e04 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -26,6 +26,7 @@ location by modifying the configuration file. | OS | Location | |---------|-------------------------------------------------| | Docker | @code{}/config@endcode | +| FreeBSD | @code{}~/.config/sunshine@endcode | | Linux | @code{}~/.config/sunshine@endcode | | macOS | @code{}~/.config/sunshine@endcode | | Windows | @code{}%ProgramFiles%\\Sunshine\\config@endcode | @@ -339,12 +340,12 @@ editing the `conf` file in a text editor. Use the examples as reference. ds5 DualShock 5 controller (PS5) - @note{This option applies to Linux only.} + @note{This option applies to FreeBSD and Linux only.} switch Switch Pro controller - @note{This option applies to Linux only.} + @note{This option applies to FreeBSD and Linux only.} x360 @@ -354,7 +355,7 @@ editing the `conf` file in a text editor. Use the examples as reference. xone Xbox One controller - @note{This option applies to Linux only.} + @note{This option applies to FreeBSD and Linux only.} @@ -735,14 +736,14 @@ editing the `conf` file in a text editor. Use the examples as reference. @tip{To find the name of the audio sink follow these instructions.

- **Linux + pulseaudio:** + **FreeBSD/Linux + pulseaudio:**
@code{} pacmd list-sinks | grep "name:" @endcode

- **Linux + pipewire:** + **FreeBSD/Linux + pipewire:**
@code{} pactl info | grep Source @@ -776,7 +777,7 @@ editing the `conf` file in a text editor. Use the examples as reference. Sunshine will select the default audio device. - Example (Linux) + Example (FreeBSD/Linux) @code{} audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo @endcode @@ -883,7 +884,7 @@ editing the `conf` file in a text editor. Use the examples as reference. @tip{To find the appropriate values follow these instructions.

- **Linux + VA-API:** + **FreeBSD/Linux + VA-API:**
Unlike with *amdvce* and *nvenc*, it doesn't matter if video encoding is done on a different GPU. @code{} @@ -913,7 +914,7 @@ editing the `conf` file in a text editor. Use the examples as reference. Sunshine will select the default video card. - Example (Linux) + Example (FreeBSD/Linux) @code{} adapter_name = /dev/dri/renderD128 @endcode @@ -936,7 +937,7 @@ editing the `conf` file in a text editor. Use the examples as reference. @tip{To find the appropriate values follow these instructions.

- **Linux:** + **FreeBSD/Linux:**
During Sunshine startup, you should see the list of detected displays: @code{} @@ -1021,7 +1022,7 @@ editing the `conf` file in a text editor. Use the examples as reference. Sunshine will select the default display. - Example (Linux) + Example (FreeBSD/Linux) @code{} output_name = 0 @endcode @@ -2034,7 +2035,7 @@ editing the `conf` file in a text editor. Use the examples as reference. x11 Uses XCB. This is the slowest and most CPU intensive so should be avoided if possible. - @note{Applies to Linux only.} + @note{Applies to FreeBSD and Linux only.} ddx @@ -2083,7 +2084,7 @@ editing the `conf` file in a text editor. Use the examples as reference. vaapi - Use Linux VA-API (AMD, Intel) + Use VA-API (AMD, Intel) software diff --git a/docs/getting_started.md b/docs/getting_started.md index 0ffc38ae4..c0d40752c 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -8,7 +8,7 @@ and release artifacts may be missing when merging changes on a faster cadence. ## Binaries -Binaries of Sunshine are created for each release. They are available for Linux, macOS, and Windows. +Binaries of Sunshine are created for each release. They are available for FreeBSD, Linux, macOS, and Windows. Binaries can be found in the [latest release][latest-release]. > [!NOTE] @@ -28,7 +28,28 @@ and [ghcr.io](https://github.com/orgs/LizardByte/packages?repo_name=sunshine). See [Docker](../DOCKER_README.md) for more information. +### FreeBSD + +#### Install +1. Download the appropriate package for your architecture + + | Architecture | Package | + |---------------|----------------------------------------------------------------------------------------------------------------------------------------| + | amd64/x86_64 | [Sunshine-FreeBSD-14.3-amd64.pkg](https://github.com/LizardByte/Sunshine/releases/latest/download/Sunshine-FreeBSD-14.3-amd64.pkg) | + | arm64/aarch64 | [Sunshine-FreeBSD-14.3-aarch64.pkg](https://github.com/LizardByte/Sunshine/releases/latest/download/Sunshine-FreeBSD-14.3-aarch64.pkg) | + +2. Open terminal and run the following command. + ```sh + sudo pkg install ./Sunshine-FreeBSD-14.3-{arch}.pkg + ``` + +#### Uninstall +```sh +sudo pkg delete Sunshine +``` + ### Linux + **CUDA Compatibility** CUDA is used for NVFBC capture. @@ -380,6 +401,22 @@ overflow menu. Different versions of Windows may provide slightly different step ## Initial Setup After installation, some initial setup is required. +### FreeBSD + +#### Virtual Input Devices + +> [!IMPORTANT] +> To use virtual input devices (keyboard, mouse, gamepads), you must add your user to the `input` group. + +The installation process creates the `input` group and configures permissions for `/dev/uinput`. +To allow your user to create virtual input devices, run: + +```bash +pw groupmod input -m $USER +``` + +After adding yourself to the group, log out and log back in for the changes to take effect. + ### Linux #### KMS Capture @@ -542,7 +579,16 @@ All shortcuts start with `Ctrl+Alt+Shift`, just like Moonlight. instead it simply starts a stream. If you removed it and would like to get it back, just add a new application with the name "Desktop" and "desktop.png" as the image path. * For the Linux flatpak you must prepend commands with `flatpak-spawn --host`. -* If inputs (mouse, keyboard, gamepads...) aren't working after connecting, add the user running sunshine to the `input` group. +* If inputs (mouse, keyboard, gamepads...) aren't working after connecting: + + * On FreeBSD/Linux, add the user running sunshine to the `input` group. + +* The FreeBSD version of Sunshine is missing some features that are present on Linux. + The following are known limitations. + + * Only X11 and Wayland capture are supported + * DualSense/DS5 emulation is not available due to missing uhid features + ### HDR Support Streaming HDR content is officially supported on Windows hosts and experimentally supported for Linux hosts. diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 6fd18cd79..30ca8e08e 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -10,14 +10,22 @@ // standard includes #include +#include #include +#include // platform includes #include #include #include +#include #include #include +#include + +#ifdef __FreeBSD__ + #include // For sockaddr_dl, LLADDR, and AF_LINK +#endif // lib includes #include @@ -41,6 +49,16 @@ #define SUNSHINE_GNUC_EXTENSION #endif +#ifndef SOL_IP + #define SOL_IP IPPROTO_IP +#endif +#ifndef SOL_IPV6 + #define SOL_IPV6 IPPROTO_IPV6 +#endif +#ifndef SOL_UDP + #define SOL_UDP IPPROTO_UDP +#endif + using namespace std::literals; namespace fs = std::filesystem; namespace bp = boost::process::v1; @@ -214,6 +232,40 @@ namespace platf { std::string get_mac_address(const std::string_view &address) { auto ifaddrs = get_ifaddrs(); + +#ifdef __FreeBSD__ + // On FreeBSD, we need to find the interface name first, then look for its AF_LINK entry + std::string interface_name; + for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) { + if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) { + interface_name = pos->ifa_name; + break; + } + } + + if (!interface_name.empty()) { + // Find the AF_LINK entry for this interface to get MAC address + for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) { + if (pos->ifa_addr && pos->ifa_addr->sa_family == AF_LINK && + interface_name == pos->ifa_name) { + auto sdl = (struct sockaddr_dl *) pos->ifa_addr; + auto mac = (unsigned char *) LLADDR(sdl); + + // Format MAC address as XX:XX:XX:XX:XX:XX + std::ostringstream mac_stream; + mac_stream << std::hex << std::setfill('0'); + for (int i = 0; i < sdl->sdl_alen; i++) { + if (i > 0) { + mac_stream << ':'; + } + mac_stream << std::setw(2) << (int) mac[i]; + } + return mac_stream.str(); + } + } + } +#else + // On Linux, read MAC address from sysfs for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) { if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) { std::ifstream mac_file("/sys/class/net/"s + pos->ifa_name + "/address"); @@ -224,6 +276,7 @@ namespace platf { } } } +#endif BOOST_LOG(warning) << "Unable to find MAC address for "sv << address; return "00:00:00:00:00:00"s; @@ -377,7 +430,12 @@ namespace platf { } union { +#ifdef IP_PKTINFO char buf[CMSG_SPACE(sizeof(uint16_t)) + std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))]; +#elif defined(IP_SENDSRCADDR) + // FreeBSD uses IP_SENDSRCADDR with struct in_addr instead of IP_PKTINFO with struct in_pktinfo + char buf[CMSG_SPACE(sizeof(uint16_t)) + std::max(CMSG_SPACE(sizeof(struct in_addr)), CMSG_SPACE(sizeof(struct in6_pktinfo)))]; +#endif struct cmsghdr alignment; } cmbuf = {}; // Must be zeroed for CMSG_NXTHDR() @@ -403,6 +461,7 @@ namespace platf { pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo)); memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo)); } else { +#ifdef IP_PKTINFO struct in_pktinfo pktInfo; struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0); @@ -415,6 +474,18 @@ namespace platf { pktinfo_cm->cmsg_type = IP_PKTINFO; pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo)); memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo)); +#elif defined(IP_SENDSRCADDR) + // FreeBSD uses IP_SENDSRCADDR with struct in_addr instead of IP_PKTINFO + struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0); + struct in_addr src_addr = saddr_v4.sin_addr; + + cmbuflen += CMSG_SPACE(sizeof(src_addr)); + + pktinfo_cm->cmsg_level = IPPROTO_IP; + pktinfo_cm->cmsg_type = IP_SENDSRCADDR; + pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(src_addr)); + memcpy(CMSG_DATA(pktinfo_cm), &src_addr, sizeof(src_addr)); +#endif } auto const max_iovs_per_msg = send_info.payload_buffers.size() + (send_info.headers ? 1 : 0); @@ -507,8 +578,8 @@ namespace platf { { // If GSO is not supported, use sendmmsg() instead. - struct mmsghdr msgs[send_info.block_count]; - struct iovec iovs[send_info.block_count * (send_info.headers ? 2 : 1)]; + std::vector msgs(send_info.block_count); + std::vector iovs(send_info.block_count * (send_info.headers ? 2 : 1)); int iov_idx = 0; for (size_t i = 0; i < send_info.block_count; i++) { msgs[i].msg_len = 0; @@ -584,7 +655,12 @@ namespace platf { } union { +#ifdef IP_PKTINFO char buf[std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))]; +#elif defined(IP_SENDSRCADDR) + // FreeBSD uses IP_SENDSRCADDR with struct in_addr instead of IP_PKTINFO with struct in_pktinfo + char buf[std::max(CMSG_SPACE(sizeof(struct in_addr)), CMSG_SPACE(sizeof(struct in6_pktinfo)))]; +#endif struct cmsghdr alignment; } cmbuf; @@ -608,6 +684,7 @@ namespace platf { pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo)); memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo)); } else { +#ifdef IP_PKTINFO struct in_pktinfo pktInfo; struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0); @@ -620,6 +697,18 @@ namespace platf { pktinfo_cm->cmsg_type = IP_PKTINFO; pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo)); memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo)); +#elif defined(IP_SENDSRCADDR) + // FreeBSD uses IP_SENDSRCADDR with struct in_addr instead of IP_PKTINFO + struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0); + struct in_addr src_addr = saddr_v4.sin_addr; + + cmbuflen += CMSG_SPACE(sizeof(src_addr)); + + pktinfo_cm->cmsg_level = IPPROTO_IP; + pktinfo_cm->cmsg_type = IP_SENDSRCADDR; + pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(src_addr)); + memcpy(CMSG_DATA(pktinfo_cm), &src_addr, sizeof(src_addr)); +#endif } struct iovec iovs[2]; @@ -753,6 +842,10 @@ namespace platf { // reset SO_PRIORITY back to 0. // // 6 is the highest priority that can be used without SYS_CAP_ADMIN. +#ifndef SO_PRIORITY + // FreeBSD doesn't support SO_PRIORITY, so we skip this + BOOST_LOG(debug) << "SO_PRIORITY not supported on this platform, skipping traffic priority setting"; +#else int priority = data_type == qos_data_type_e::audio ? 6 : 5; if (setsockopt(sockfd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)) == 0) { // Reset SO_PRIORITY to 0 when QoS is disabled @@ -760,6 +853,7 @@ namespace platf { } else { BOOST_LOG(error) << "Failed to set SO_PRIORITY: "sv << errno; } +#endif return std::make_unique(sockfd, reset_options); } diff --git a/src/stream.cpp b/src/stream.cpp index e6bb5ebfd..6bfd5d977 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -520,7 +520,12 @@ namespace stream { // for other communications to the client. This is necessary to ensure // proper routing on multi-homed hosts. auto local_address = platf::from_sockaddr((sockaddr *) &peer->localAddress.address); - session_p->localAddress = boost::asio::ip::make_address(local_address); + try { + session_p->localAddress = boost::asio::ip::make_address(local_address); + } catch (const boost::system::system_error &e) { + BOOST_LOG(error) << "boost::system::system_error in address parsing: " << e.what() << " (code: " << e.code() << ")"sv; + throw; + } BOOST_LOG(debug) << "Control local address ["sv << local_address << ']'; BOOST_LOG(debug) << "Control peer address ["sv << peer_addr << ':' << peer_port << ']'; diff --git a/src/system_tray.cpp b/src/system_tray.cpp index 812483560..e2bfa239e 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -13,7 +13,7 @@ #define TRAY_ICON_PLAYING WEB_DIR "images/sunshine-playing.ico" #define TRAY_ICON_PAUSING WEB_DIR "images/sunshine-pausing.ico" #define TRAY_ICON_LOCKED WEB_DIR "images/sunshine-locked.ico" - #elif defined(__linux__) || defined(linux) || defined(__linux) + #elif defined(__linux__) || defined(linux) || defined(__linux) || defined(__FreeBSD__) #define TRAY_ICON SUNSHINE_TRAY_PREFIX "-tray" #define TRAY_ICON_PLAYING SUNSHINE_TRAY_PREFIX "-playing" #define TRAY_ICON_PAUSING SUNSHINE_TRAY_PREFIX "-pausing" diff --git a/src/video.cpp b/src/video.cpp index 6603d6d04..55bd322ad 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -897,7 +897,7 @@ namespace video { H264_ONLY | PARALLEL_ENCODING | ALWAYS_REPROBE | YUV444_SUPPORT }; -#ifdef __linux__ +#if defined(__linux__) || defined(linux) || defined(__linux) || defined(__FreeBSD__) encoder_t vaapi { "vaapi"sv, std::make_unique( @@ -1032,7 +1032,7 @@ namespace video { &quicksync, &amdvce, #endif -#ifdef __linux__ +#if defined(__linux__) || defined(linux) || defined(__linux) || defined(__FreeBSD__) &vaapi, #endif #ifdef __APPLE__ diff --git a/src/video.h b/src/video.h index c4b9554f2..8dbf76e27 100644 --- a/src/video.h +++ b/src/video.h @@ -222,7 +222,7 @@ namespace video { extern encoder_t quicksync; #endif -#ifdef __linux__ +#if defined(__linux__) || defined(linux) || defined(__linux) || defined(__FreeBSD__) extern encoder_t vaapi; #endif diff --git a/src_assets/bsd/misc/+POST_INSTALL b/src_assets/bsd/misc/+POST_INSTALL new file mode 100644 index 000000000..239343d29 --- /dev/null +++ b/src_assets/bsd/misc/+POST_INSTALL @@ -0,0 +1,64 @@ +#!/bin/sh + +# FreeBSD post-install script for Sunshine +# This script sets up the necessary permissions for virtual input devices + +echo "Configuring permissions for virtual input devices..." + +# Create the 'input' group if it doesn't exist +if ! pw groupshow input >/dev/null 2>&1; then + echo "Creating 'input' group..." + pw groupadd input + if [ $? -eq 0 ]; then + echo "Successfully created 'input' group." + else + echo "Warning: Failed to create 'input' group. You may need to create it manually." + fi +else + echo "'input' group already exists." +fi + +# Set permissions on /dev/uinput if it exists +if [ -e /dev/uinput ]; then + echo "Setting permissions on /dev/uinput..." + chown root:input /dev/uinput + chmod 660 /dev/uinput + echo "Permissions set on /dev/uinput." +else + echo "Note: /dev/uinput does not exist. It will be created when needed." +fi + +# Create devfs rules for persistent permissions +echo "Creating devfs rules for persistent permissions..." +DEVFS_RULESET_FILE="/etc/devfs.rules" +RULESET_NUM=47989 + +# Check if our rules already exist +if ! grep -q "\[sunshine=$RULESET_NUM\]" "$DEVFS_RULESET_FILE" 2>/dev/null; then + cat >> "$DEVFS_RULESET_FILE" << EOF + +[sunshine=$RULESET_NUM] +add path 'uinput' mode 0660 group input +EOF + echo "Devfs rules added to $DEVFS_RULESET_FILE" +else + echo "Devfs rules already exist in $DEVFS_RULESET_FILE" +fi + +# Apply the devfs ruleset immediately (without waiting for reboot) +echo "Applying devfs ruleset to current system..." +if [ -e /dev/uinput ]; then + devfs -m /dev rule -s $RULESET_NUM apply +fi + +echo "" +echo "Post-installation configuration complete!" +echo "" +echo "IMPORTANT: To use virtual input devices (keyboard, mouse, gamepads)," +echo "you must add your user to the 'input' group:" +echo "" +echo " pw groupmod input -m \$USER" +echo "" +echo "After adding yourself to the group, log out and log back in for the" +echo "changes to take effect." +echo "" diff --git a/src_assets/bsd/misc/+PRE_DEINSTALL b/src_assets/bsd/misc/+PRE_DEINSTALL new file mode 100644 index 000000000..232e7c8b8 --- /dev/null +++ b/src_assets/bsd/misc/+PRE_DEINSTALL @@ -0,0 +1,32 @@ +#!/bin/sh + +# FreeBSD pre-deinstall script for Sunshine +# This script cleans up configuration added during installation + +echo "Cleaning up Sunshine configuration..." + +# Remove devfs rules +DEVFS_RULESET_FILE="/etc/devfs.rules" +RULESET_NUM=47989 + +# Remove rules from /etc/devfs.rules +if [ -f "$DEVFS_RULESET_FILE" ]; then + if grep -q "\[sunshine=$RULESET_NUM\]" "$DEVFS_RULESET_FILE"; then + echo "Removing devfs rules from $DEVFS_RULESET_FILE..." + # Remove the [sunshine=47989] section and its rules (match the section header and the next line) + sed -i.bak '/^\[sunshine='"$RULESET_NUM"'\]$/,/^add path.*uinput/d' "$DEVFS_RULESET_FILE" + echo "Devfs rules removed from file." + fi +fi + +echo "Removing devfs ruleset from memory..." +devfs rule -s $RULESET_NUM delset 2>/dev/null || true + +# Note: We intentionally do NOT: +# - Remove the 'input' group (other software may use it) + +echo "Cleanup complete." +echo "" +echo "NOTE: The 'input' group has not been removed as other software may use it." +echo "If you wish to remove it manually, run: pw groupdel input" +echo "" diff --git a/src_assets/common/assets/web/PlatformLayout.vue b/src_assets/common/assets/web/PlatformLayout.vue index 31064fec6..49cbacc0f 100644 --- a/src_assets/common/assets/web/PlatformLayout.vue +++ b/src_assets/common/assets/web/PlatformLayout.vue @@ -12,6 +12,10 @@ const props = defineProps({ + + diff --git a/src_assets/common/assets/web/apps.html b/src_assets/common/assets/web/apps.html index 8b0cb517c..e1f7c83dd 100644 --- a/src_assets/common/assets/web/apps.html +++ b/src_assets/common/assets/web/apps.html @@ -331,7 +331,7 @@
{{ $t('apps.env_qres_example') }}
cmd /C <{{ $t('apps.env_qres_path') }}>\QRes.exe /X:%SUNSHINE_CLIENT_WIDTH% /Y:%SUNSHINE_CLIENT_HEIGHT% /R:%SUNSHINE_CLIENT_FPS%
-
{{ $t('apps.env_xrandr_example') }} +
{{ $t('apps.env_xrandr_example') }}
sh -c "xrandr --output HDMI-1 --mode \"${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}\" --rate ${SUNSHINE_CLIENT_FPS}"
{{ $t('apps.env_displayplacer_example') }} @@ -442,8 +442,8 @@ if (resp) { fetch("./api/apps/" + id, { method: "DELETE", - headers: { - "Content-Type": "application/json" + headers: { + "Content-Type": "application/json" }, }).then((r) => { if (r.status === 200) document.location.reload(); diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index 62a37906e..887062d6c 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -305,7 +305,7 @@ return el.id !== "vt" && el.id !== "vaapi"; }); } - if (this.platform === "linux") { + if (this.platform === "freebsd" || this.platform === "linux") { this.tabs = this.tabs.filter((el) => { return el.id !== "amd" && el.id !== "qsv" && el.id !== "vt"; }); diff --git a/src_assets/common/assets/web/configs/tabs/Advanced.vue b/src_assets/common/assets/web/configs/tabs/Advanced.vue index bd11adf28..37191c7e1 100644 --- a/src_assets/common/assets/web/configs/tabs/Advanced.vue +++ b/src_assets/common/assets/web/configs/tabs/Advanced.vue @@ -64,6 +64,10 @@ const config = ref(props.config)