build: add freebsd support (#4049)
Some checks failed
CodeQL / CodeQL (push) Has been cancelled
CI / GitHub Env Debug (push) Has been cancelled
CI / Release Setup (push) Has been cancelled
CI / Docker (push) Has been cancelled
CI / FreeBSD (push) Has been cancelled
CI / Homebrew (push) Has been cancelled
CI / Linux (push) Has been cancelled
CI / Linux Copr (push) Has been cancelled
CI / Linux Flatpak (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI / Bundle Analysis (push) Has been cancelled
CI / Coverage-Homebrew-macos-14 (push) Has been cancelled
CI / Coverage-Homebrew-macos-15 (push) Has been cancelled
CI / Coverage-Homebrew-macos-26 (push) Has been cancelled
CI / Coverage-Homebrew-ubuntu-latest (push) Has been cancelled
CI / Coverage-FreeBSD-14.3-aarch64 (push) Has been cancelled
CI / Coverage-FreeBSD-14.3-amd64 (push) Has been cancelled
CI / Coverage-Linux-AppImage (push) Has been cancelled
CI / Coverage-Windows-AMD64 (push) Has been cancelled
CI / Release (push) Has been cancelled
CI / Release Homebrew Beta (push) Has been cancelled
localize / Update Localization (push) Has been cancelled
Build GH-Pages / call-jekyll-build (push) Has been cancelled
Build GH-Pages / prep (push) Has been cancelled

This commit is contained in:
David Lane
2025-11-11 23:46:11 -05:00
committed by GitHub
parent 2dbe837ebc
commit 1d6d916b7a
31 changed files with 1055 additions and 39 deletions

272
.github/workflows/ci-freebsd.yml vendored Normal file
View File

@@ -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<<EOF"
echo "$matrix"
echo "EOF"
} >> "${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

View File

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

View File

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

179
README.md
View File

@@ -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
<table>
<caption id="feature_compatibility">Platform Feature Support</caption>
<tr>
<th>Feature</th>
<th>FreeBSD</th>
<th>Linux</th>
<th>macOS</th>
<th>Windows</th>
</tr>
<tr>
<td colspan="5" align="center"><b>Gamepad Emulation</b><br>
What type of gamepads can be emulated on the host.<br>
Clients may support other gamepads.
</td>
</tr>
<tr>
<td>DualShock / DS4 (PlayStation 4)</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>DualSense / DS5 (PlayStation 5)</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Nintendo Switch Pro</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Xbox 360</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Xbox One/Series</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td colspan="5" align="center"><b>GPU Encoding</b></td>
</tr>
<tr>
<td>AMD/AMF</td>
<td>✅ (vaapi)</td>
<td>✅ (vaapi)</td>
<td>✅ (Video Toolbox)</td>
<td></td>
</tr>
<tr>
<td>Intel QuickSync</td>
<td>✅ (vaapi)</td>
<td>✅ (vaapi)</td>
<td>✅ (Video Toolbox)</td>
<td></td>
</tr>
<tr>
<td>NVIDIA NVENC</td>
<td>✅ (vaapi)</td>
<td>✅ (vaapi)</td>
<td>✅ (Video Toolbox)</td>
<td></td>
</tr>
<tr>
<td colspan="5" align="center"><b>Screen Capture</b></td>
</tr>
<tr>
<td>DXGI</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>KMS</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>NVIDIA NvFBC</td>
<td></td>
<td>🟡</td>
<td></td>
<td></td>
</tr>
<tr>
<td>&nbsp;&nbsp;↳ X11 Support</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>&nbsp;&nbsp;↳ Wayland Support</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Video Toolbox</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Wayland</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Windows.Graphics.Capture</td>
<td></td>
<td></td>
<td></td>
<td>🟡</td>
</tr>
<tr>
<td>&nbsp;&nbsp;↳ Portable</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>&nbsp;&nbsp;↳ Service</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>X11</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
**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
<tr>
<td>
Intel:<br>
&nbsp;&nbsp;Linux: VAAPI-compatible, see: <a href="https://www.intel.com/content/www/us/en/developer/articles/technical/linuxmedia-vaapi.html">VAAPI hardware support</a><br>
&nbsp;&nbsp;FreeBSD/Linux: VAAPI-compatible, see: <a href="https://www.intel.com/content/www/us/en/developer/articles/technical/linuxmedia-vaapi.html">VAAPI hardware support</a><br>
&nbsp;&nbsp;Windows: Skylake or newer with QuickSync encoding support
</td>
</tr>
@@ -69,11 +229,8 @@ LizardByte has the full documentation hosted on [Read the Docs](https://docs.liz
<td>4GB or more</td>
</tr>
<tr>
<td rowspan="5">OS</td>
<td>Windows: 10+ (Windows Server does not support virtual gamepads)</td>
</tr>
<tr>
<td>macOS: 14+</td>
<td rowspan="6">OS</td>
<td>FreeBSD: 14.3+</td>
</tr>
<tr>
<td>Linux/Debian: 13+ (trixie)</td>
@@ -84,6 +241,12 @@ LizardByte has the full documentation hosted on [Read the Docs](https://docs.liz
<tr>
<td>Linux/Ubuntu: 22.04+ (jammy)</td>
</tr>
<tr>
<td>macOS: 14+</td>
</tr>
<tr>
<td>Windows: 11+ (Windows Server does not support virtual gamepads)</td>
</tr>
<tr>
<td rowspan="2">Network</td>
<td>Host: 5GHz, 802.11ac</td>
@@ -106,14 +269,14 @@ LizardByte has the full documentation hosted on [Read the Docs](https://docs.liz
<tr>
<td>
Intel:<br>
&nbsp;&nbsp;Linux: HD Graphics 510 or higher<br>
&nbsp;&nbsp;FreeBSD/Linux: HD Graphics 510 or higher<br>
&nbsp;&nbsp;Windows: Skylake or newer with QuickSync encoding support
</td>
</tr>
<tr>
<td>
Nvidia:<br>
&nbsp;&nbsp;Linux: GeForce RTX 2000 series or higher<br>
&nbsp;&nbsp;FreeBSD/Linux: GeForce RTX 2000 series or higher<br>
&nbsp;&nbsp;Windows: Geforce GTX 1080 or higher
</td>
</tr>

View File

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

View File

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

View File

@@ -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}")

View File

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

View File

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

View File

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

View File

@@ -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.
<tr>
<td>ds5</td>
<td>DualShock 5 controller (PS5)
@note{This option applies to Linux only.}</td>
@note{This option applies to FreeBSD and Linux only.}</td>
</tr>
<tr>
<td>switch</td>
<td>Switch Pro controller
@note{This option applies to Linux only.}</td>
@note{This option applies to FreeBSD and Linux only.}</td>
</tr>
<tr>
<td>x360</td>
@@ -354,7 +355,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
<tr>
<td>xone</td>
<td>Xbox One controller
@note{This option applies to Linux only.}</td>
@note{This option applies to FreeBSD and Linux only.}</td>
</tr>
</table>
@@ -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.
<br>
<br>
**Linux + pulseaudio:**
**FreeBSD/Linux + pulseaudio:**
<br>
@code{}
pacmd list-sinks | grep "name:"
@endcode
<br>
<br>
**Linux + pipewire:**
**FreeBSD/Linux + pipewire:**
<br>
@code{}
pactl info | grep Source
@@ -776,7 +777,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
<td colspan="2">Sunshine will select the default audio device.</td>
</tr>
<tr>
<td>Example (Linux)</td>
<td>Example (FreeBSD/Linux)</td>
<td colspan="2">@code{}
audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo
@endcode</td>
@@ -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.
<br>
<br>
**Linux + VA-API:**
**FreeBSD/Linux + VA-API:**
<br>
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.
<td colspan="2">Sunshine will select the default video card.</td>
</tr>
<tr>
<td>Example (Linux)</td>
<td>Example (FreeBSD/Linux)</td>
<td colspan="2">@code{}
adapter_name = /dev/dri/renderD128
@endcode</td>
@@ -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.
<br>
<br>
**Linux:**
**FreeBSD/Linux:**
<br>
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.
<td colspan="2">Sunshine will select the default display.</td>
</tr>
<tr>
<td>Example (Linux)</td>
<td>Example (FreeBSD/Linux)</td>
<td colspan="2">@code{}
output_name = 0
@endcode</td>
@@ -2034,7 +2035,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
<tr>
<td>x11</td>
<td>Uses XCB. This is the slowest and most CPU intensive so should be avoided if possible.
@note{Applies to Linux only.}</td>
@note{Applies to FreeBSD and Linux only.}</td>
</tr>
<tr>
<td>ddx</td>
@@ -2083,7 +2084,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
<tr>
<td>vaapi</td>
<td>Use Linux VA-API (AMD, Intel)</td>
<td>Use VA-API (AMD, Intel)</td>
</tr>
<tr>
<td>software</td>

View File

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

View File

@@ -10,14 +10,22 @@
// standard includes
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
// platform includes
#include <arpa/inet.h>
#include <dlfcn.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <pwd.h>
#include <sys/socket.h>
#ifdef __FreeBSD__
#include <net/if_dl.h> // For sockaddr_dl, LLADDR, and AF_LINK
#endif
// lib includes
#include <boost/asio/ip/address.hpp>
@@ -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<struct mmsghdr> msgs(send_info.block_count);
std::vector<struct iovec> 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<qos_t>(sockfd, reset_options);
}

View File

@@ -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 << ']';

View File

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

View File

@@ -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<encoder_platform_formats_avcodec>(
@@ -1032,7 +1032,7 @@ namespace video {
&quicksync,
&amdvce,
#endif
#ifdef __linux__
#if defined(__linux__) || defined(linux) || defined(__linux) || defined(__FreeBSD__)
&vaapi,
#endif
#ifdef __APPLE__

View File

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

View File

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

View File

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

View File

@@ -12,6 +12,10 @@ const props = defineProps({
<slot name="windows"></slot>
</template>
<template v-if="$slots.freebsd && platform === 'freebsd'">
<slot name="freebsd"></slot>
</template>
<template v-if="$slots.linux && platform === 'linux'">
<slot name="linux"></slot>
</template>

View File

@@ -331,7 +331,7 @@
<div class="form-text" v-if="platform === 'windows'"><b>{{ $t('apps.env_qres_example') }}</b>
<pre>cmd /C &lt;{{ $t('apps.env_qres_path') }}&gt;\QRes.exe /X:%SUNSHINE_CLIENT_WIDTH% /Y:%SUNSHINE_CLIENT_HEIGHT% /R:%SUNSHINE_CLIENT_FPS%</pre>
</div>
<div class="form-text" v-else-if="platform === 'linux'"><b>{{ $t('apps.env_xrandr_example') }}</b>
<div class="form-text" v-else-if="platform === 'freebsd' || platform === 'linux'"><b>{{ $t('apps.env_xrandr_example') }}</b>
<pre>sh -c "xrandr --output HDMI-1 --mode \"${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}\" --rate ${SUNSHINE_CLIENT_FPS}"</pre>
</div>
<div class="form-text" v-else-if="platform === 'macos'"><b>{{ $t('apps.env_displayplacer_example') }}</b>
@@ -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();

View File

@@ -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";
});

View File

@@ -64,6 +64,10 @@ const config = ref(props.config)
<select id="capture" class="form-select" v-model="config.capture">
<option value="">{{ $t('_common.autodetect') }}</option>
<PlatformLayout :platform="platform">
<template #freebsd>
<option value="wlr">wlroots</option>
<option value="x11">X11</option>
</template>
<template #linux>
<option value="nvfbc">NvFBC</option>
<option value="wlr">wlroots</option>
@@ -90,6 +94,9 @@ const config = ref(props.config)
<option value="quicksync">Intel QuickSync</option>
<option value="amdvce">AMD AMF/VCE</option>
</template>
<template #freebsd>
<option value="vaapi">VA-API</option>
</template>
<template #linux>
<option value="nvenc">NVIDIA NVENC</option>
<option value="vaapi">VA-API</option>

View File

@@ -30,6 +30,10 @@ const config = ref(props.config)
<template #windows>
<pre>tools\audio-info.exe</pre>
</template>
<template #freebsd>
<pre>pacmd list-sinks | grep "name:"</pre>
<pre>pactl info | grep Source</pre>
</template>
<template #linux>
<pre>pacmd list-sinks | grep "name:"</pre>
<pre>pactl info | grep Source</pre>

View File

@@ -28,12 +28,17 @@ const config = ref(props.config)
<option value="auto">{{ $t('_common.auto') }}</option>
<PlatformLayout :platform="platform">
<template #freebsd>
<option value="switch">{{ $t("config.gamepad_switch") }}</option>
<option value="xone">{{ $t("config.gamepad_xone") }}</option>
</template>
<template #linux>
<option value="ds5">{{ $t("config.gamepad_ds5") }}</option>
<option value="switch">{{ $t("config.gamepad_switch") }}</option>
<option value="xone">{{ $t("config.gamepad_xone") }}</option>
</template>
<template #windows>
<option value="ds4">{{ $t('config.gamepad_ds4') }}</option>
<option value="x360">{{ $t('config.gamepad_x360') }}</option>

View File

@@ -23,6 +23,16 @@ const config = ref(props.config)
{{ $t('config.adapter_name_desc_windows') }}<br>
<pre>tools\dxgi-info.exe</pre>
</template>
<template #freebsd>
{{ $t('config.adapter_name_desc_linux_1') }}<br>
<pre>ls /dev/dri/renderD* # {{ $t('config.adapter_name_desc_linux_2') }}</pre>
<pre>
vainfo --display drm --device /dev/dri/renderD129 | \
grep -E "((VAProfileH264High|VAProfileHEVCMain|VAProfileHEVCMain10).*VAEntrypointEncSlice)|Driver version"
</pre>
{{ $t('config.adapter_name_desc_linux_3') }}<br>
<i>VAProfileH264High : VAEntrypointEncSlice</i>
</template>
<template #linux>
{{ $t('config.adapter_name_desc_linux_1') }}<br>
<pre>ls /dev/dri/renderD* # {{ $t('config.adapter_name_desc_linux_2') }}</pre>

View File

@@ -241,6 +241,8 @@ function addRemappingEntry() {
</div>
</div>
</template>
<template #freebsd>
</template>
<template #linux>
</template>
<template #macos>

View File

@@ -30,6 +30,16 @@ const outputNamePlaceholder = (props.platform === 'windows') ? '{de9bb7e2-186e-5
<b>&nbsp;&nbsp;}</b>
</pre>
</template>
<template #freebsd>
<pre style="white-space: pre-line;">
Info: Detecting displays
Info: Detected display: DVI-D-0 (id: 0) connected: false
Info: Detected display: HDMI-0 (id: 1) connected: true
Info: Detected display: DP-0 (id: 2) connected: true
Info: Detected display: DP-1 (id: 3) connected: false
Info: Detected display: DVI-D-1 (id: 4) connected: false
</pre>
</template>
<template #linux>
<pre style="white-space: pre-line;">
Info: Detecting displays

View File

@@ -126,6 +126,12 @@ namespace test_utils {
#define IS_MACOS false
#endif
#ifdef __FreeBSD__
#define IS_FREEBSD true
#else
#define IS_FREEBSD false
#endif
struct PlatformTestSuite: testing::Test {
static void SetUpTestSuite() {
ASSERT_FALSE(platf_deinit);

View File

@@ -13,7 +13,7 @@ struct MouseHIDTest: PlatformTestSuite, testing::WithParamInterface<util::point_
// the alternative `platf::abs_mouse` method seem to work better during tests,
// but I'm not sure about real work
GTEST_SKIP() << "TODO Windows";
#elif __linux__
#elif defined(__linux__) || defined(__FreeBSD__)
// TODO: Inputtino waiting https://github.com/games-on-whales/inputtino/issues/6 is resolved.
GTEST_SKIP() << "TODO Inputtino";
#endif
@@ -88,7 +88,7 @@ TEST_P(MouseHIDTest, AbsMoveInputTest) {
65535,
65535
};
#elif __linux__
#elif defined(__linux__) || defined(__FreeBSD__)
platf::touch_port_t abs_port {
0,
0,

View File

@@ -32,7 +32,7 @@ INSTANTIATE_TEST_SUITE_P(
&video::amdvce,
&video::quicksync,
#endif
#ifdef __linux__
#if defined(__linux__) || defined(__FreeBSD__)
&video::vaapi,
#endif
#ifdef __APPLE__