Compare commits

...

7 Commits

Author SHA1 Message Date
ReenigneArcher a00f7985f1 Allow pull requests on any branch
Removed branch restriction for pull requests.
2025-10-28 15:14:31 -04:00
copilot-swe-agent[bot] 8e4fccdfc6 build: apply ENet FreeBSD patch during cmake configuration
- Create proper patch file for ENet FreeBSD support
- Apply patch automatically during CMake configuration on FreeBSD
- Avoid modifying submodule files directly

Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
2025-10-28 19:10:58 +00:00
copilot-swe-agent[bot] 4353158d60 docs: add FreeBSD additional requirements section
Add notes about required packages for gaming on FreeBSD:
- linux-steam-utils for Steam gaming
- nvidia-driver for NVIDIA GPU support

Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
2025-10-28 19:08:37 +00:00
copilot-swe-agent[bot] 69751d1943 fix: add FreeBSD support for local address handling
- Add FreeBSD-specific packet info handling in ENet unix.c
- Use IP_RECVDSTADDR/IP_SENDSRCADDR for FreeBSD instead of IP_PKTINFO
- Enable IP_RECVDSTADDR socket option on video/audio sockets for FreeBSD
- Remove incorrect IP_PKTINFO macro redefinition from misc.cpp
- Add FreeBSD-specific send code in Sunshine's UDP streaming functions

Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
2025-10-28 19:06:45 +00:00
copilot-swe-agent[bot] 6300ea0641 Initial plan 2025-10-28 18:51:17 +00:00
ReenigneArcher 31801ad4ea debug stream session 2025-10-06 17:30:22 -04:00
ReenigneArcher d3a73d47c9 build: add freebsd support 2025-10-06 17:30:21 -04:00
28 changed files with 702 additions and 31 deletions
+256
View File
@@ -0,0 +1,256 @@
---
name: CI-FreeBSD
permissions:
contents: read
on:
workflow_call:
inputs:
release_commit:
required: true
type: string
release_version:
required: true
type: string
env:
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@v1.2.3
with:
arch: ${{ matrix.arch }}
cpu: ${{ steps.processor_count.outputs.PROCESSOR_COUNT }}
# 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/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
- name: Configure
env:
BRANCH: ${{ github.head_ref || github.ref_name }}
BUILD_VERSION: ${{ inputs.release_version }}
COMMIT: ${{ inputs.release_commit }}
shell: freebsd {0}
run: |
set -e
cd "${GITHUB_WORKSPACE}"
export BRANCH="${BRANCH}"
export BUILD_VERSION="${BUILD_VERSION}"
export COMMIT="${COMMIT}"
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 +MANIFEST
if: always()
shell: bash
working-directory: build/cpack_artifacts
run: cat ./_CPack_Packages/FreeBSD/FREEBSD/Sunshine/+MANIFEST
- 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@v4
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
+35 -3
View File
@@ -5,8 +5,6 @@ permissions:
on:
pull_request:
branches:
- master
types:
- opened
- synchronize
@@ -69,6 +67,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
@@ -128,6 +134,7 @@ jobs:
!cancelled() &&
startsWith(github.repository, 'LizardByte/')
needs:
- build-freebsd
- build-linux
- build-linux-flatpak
- build-homebrew
@@ -137,29 +144,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@v5
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
@@ -170,8 +201,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
@@ -188,6 +219,7 @@ jobs:
needs:
- release-setup
- build-docker
- build-freebsd
- build-linux
- build-linux-flatpak
- build-homebrew
+4
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")
+8 -1
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)
+33
View File
@@ -7,6 +7,36 @@ include(dependencies/Boost_Sunshine)
# submodules
# moonlight common library
set(ENET_NO_INSTALL ON CACHE BOOL "Don't install any libraries built for enet")
# Apply FreeBSD patch to ENet if on FreeBSD
if(FREEBSD)
set(ENET_PATCH_FILE "${CMAKE_SOURCE_DIR}/patches/enet-freebsd-support.patch")
set(ENET_SOURCE_FILE "${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/enet/unix.c")
# Check if patch needs to be applied
execute_process(
COMMAND patch -p0 -N --dry-run -i ${ENET_PATCH_FILE}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/enet
RESULT_VARIABLE PATCH_DRY_RUN_RESULT
OUTPUT_QUIET
ERROR_QUIET
)
# Apply patch if it hasn't been applied yet
if(PATCH_DRY_RUN_RESULT EQUAL 0)
message(STATUS "Applying FreeBSD patch to ENet")
execute_process(
COMMAND patch -p0 -N -i ${ENET_PATCH_FILE}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/enet
RESULT_VARIABLE PATCH_RESULT
)
if(NOT PATCH_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to apply FreeBSD patch to ENet")
endif()
endif()
endif()
add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/enet")
# web server
@@ -30,6 +60,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()
+24
View File
@@ -34,6 +34,14 @@ 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")
@@ -77,6 +85,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 +108,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, doesn't work with the current config
@@ -141,6 +161,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
+27
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 \|
\|-------------------\|------------------------------------------------------------------------\|
+38
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
+13 -12
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>
+42 -1
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,42 @@ 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
```
#### Additional Requirements
For gaming on FreeBSD, you'll need to install additional packages:
**For Steam gaming:**
```sh
sudo pkg install linux-steam-utils
```
**For NVIDIA GPU support:**
```sh
sudo pkg install nvidia-driver
```
##### Uninstall
```sh
sudo pkg delete Sunshine
```
### Linux
**CUDA Compatibility**
CUDA is used for NVFBC capture.
@@ -522,6 +557,12 @@ All shortcuts start with `Ctrl+Alt+Shift`, just like Moonlight.
* The "Desktop" app works the same as any other application except it has no commands. It does not start an application,
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.
* 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
* DualSense5 emulation is limited due to missing uhid features
* 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.
+63
View File
@@ -0,0 +1,63 @@
diff --git a/unix.c b/unix.c
index 3d57e9d..c2318ca 100644
--- a/unix.c
+++ b/unix.c
@@ -373,6 +373,14 @@ enet_socket_create (int af, ENetSocketType type)
}
#endif
+#if defined(__FreeBSD__) && defined(IP_RECVDSTADDR)
+ {
+ // Enable IP_RECVDSTADDR for FreeBSD to receive destination address
+ int on = 1;
+ setsockopt(sock, IPPROTO_IP, IP_RECVDSTADDR, (char *)&on, sizeof(on));
+ }
+#endif
+
#ifdef IP_PKTINFO
{
// We turn this on for all sockets because it may be required for IPv4
@@ -617,6 +625,21 @@ enet_socket_send (ENetSocket socket,
// from this peer to ensure it correctly recognizes our responses as
// coming from the expected host.
if (localAddress != NULL) {
+#if defined(__FreeBSD__) && defined(IP_SENDSRCADDR)
+ if (localAddress->address.ss_family == AF_INET) {
+ struct in_addr srcAddr = ((struct sockaddr_in*)&localAddress->address)->sin_addr;
+
+ msgHdr.msg_control = controlBufData;
+ msgHdr.msg_controllen = CMSG_SPACE(sizeof(srcAddr));
+
+ struct cmsghdr *chdr = CMSG_FIRSTHDR(&msgHdr);
+ chdr->cmsg_level = IPPROTO_IP;
+ chdr->cmsg_type = IP_SENDSRCADDR;
+ chdr->cmsg_len = CMSG_LEN(sizeof(srcAddr));
+ memcpy(CMSG_DATA(chdr), &srcAddr, sizeof(srcAddr));
+ }
+ else
+#endif
#ifdef IP_PKTINFO
if (localAddress->address.ss_family == AF_INET) {
struct in_pktinfo pktInfo;
@@ -744,6 +767,21 @@ enet_socket_receive (ENetSocket socket,
// to ensure we respond from the correct address/interface.
if (localAddress != NULL) {
for (struct cmsghdr *chdr = CMSG_FIRSTHDR(&msgHdr); chdr != NULL; chdr = CMSG_NXTHDR(&msgHdr, chdr)) {
+#if defined(__FreeBSD__) && defined(IP_RECVDSTADDR)
+ if (chdr->cmsg_level == IPPROTO_IP && chdr->cmsg_type == IP_RECVDSTADDR) {
+ struct sockaddr_in *localAddr = (struct sockaddr_in*)&localAddress->address;
+
+ localAddr->sin_family = AF_INET;
+ localAddr->sin_addr = *(struct in_addr*)CMSG_DATA(chdr);
+ // FreeBSD doesn't populate sin_port in RECVDSTADDR, but we don't need it
+ // since we only use the address part for routing decisions
+ localAddr->sin_port = 0;
+
+ localAddress->addressLength = sizeof(*localAddr);
+ break;
+ }
+ else
+#endif
#ifdef IP_PKTINFO
if (chdr->cmsg_level == IPPROTO_IP && chdr->cmsg_type == IP_PKTINFO) {
struct sockaddr_in *localAddr = (struct sockaddr_in*)&localAddress->address;
+56 -2
View File
@@ -35,6 +35,21 @@
#include "src/platform/common.h"
#include "vaapi.h"
#ifdef __FreeBSD__
#include <netinet/in.h>
#include <sys/socket.h>
// Define constants that are missing in FreeBSD
#ifndef SOL_IP // socket level for IPv4
#define SOL_IP IPPROTO_IP
#endif
#ifndef SOL_IPV6 // socket level for IPv6
#define SOL_IPV6 IPPROTO_IPV6
#endif
#ifndef SO_PRIORITY // socket option for priority, disabled for FreeBSD
#define SO_PRIORITY -1
#endif
#endif
#ifdef __GNUC__
#define SUNSHINE_GNUC_EXTENSION __extension__
#else
@@ -377,7 +392,11 @@ namespace platf {
}
union {
#ifdef __FreeBSD__
char buf[CMSG_SPACE(sizeof(uint16_t)) + std::max(CMSG_SPACE(sizeof(struct in_addr)), CMSG_SPACE(sizeof(struct in6_pktinfo)))];
#else
char buf[CMSG_SPACE(sizeof(uint16_t)) + std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))];
#endif
struct cmsghdr alignment;
} cmbuf = {}; // Must be zeroed for CMSG_NXTHDR()
@@ -403,6 +422,18 @@ namespace platf {
pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo));
memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo));
} else {
#ifdef __FreeBSD__
// FreeBSD uses IP_SENDSRCADDR instead of IP_PKTINFO
struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0);
struct in_addr srcAddr = saddr_v4.sin_addr;
cmbuflen += CMSG_SPACE(sizeof(srcAddr));
pktinfo_cm->cmsg_level = IPPROTO_IP;
pktinfo_cm->cmsg_type = IP_SENDSRCADDR;
pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(srcAddr));
memcpy(CMSG_DATA(pktinfo_cm), &srcAddr, sizeof(srcAddr));
#else
struct in_pktinfo pktInfo;
struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0);
@@ -415,6 +446,7 @@ namespace platf {
pktinfo_cm->cmsg_type = IP_PKTINFO;
pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo));
memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo));
#endif
}
auto const max_iovs_per_msg = send_info.payload_buffers.size() + (send_info.headers ? 1 : 0);
@@ -507,8 +539,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 +616,11 @@ namespace platf {
}
union {
#ifdef __FreeBSD__
char buf[std::max(CMSG_SPACE(sizeof(struct in_addr)), CMSG_SPACE(sizeof(struct in6_pktinfo)))];
#else
char buf[std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))];
#endif
struct cmsghdr alignment;
} cmbuf;
@@ -608,6 +644,18 @@ namespace platf {
pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo));
memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo));
} else {
#ifdef __FreeBSD__
// FreeBSD uses IP_SENDSRCADDR instead of IP_PKTINFO
struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0);
struct in_addr srcAddr = saddr_v4.sin_addr;
cmbuflen += CMSG_SPACE(sizeof(srcAddr));
pktinfo_cm->cmsg_level = IPPROTO_IP;
pktinfo_cm->cmsg_type = IP_SENDSRCADDR;
pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(srcAddr));
memcpy(CMSG_DATA(pktinfo_cm), &srcAddr, sizeof(srcAddr));
#else
struct in_pktinfo pktInfo;
struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0);
@@ -620,6 +668,7 @@ namespace platf {
pktinfo_cm->cmsg_type = IP_PKTINFO;
pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo));
memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo));
#endif
}
struct iovec iovs[2];
@@ -753,6 +802,10 @@ namespace platf {
// reset SO_PRIORITY back to 0.
//
// 6 is the highest priority that can be used without SYS_CAP_ADMIN.
#ifdef __FreeBSD__
// FreeBSD doesn't support SO_PRIORITY, so we skip this
BOOST_LOG(debug) << "SO_PRIORITY not supported on FreeBSD, 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 +813,7 @@ namespace platf {
} else {
BOOST_LOG(error) << "Failed to set SO_PRIORITY: "sv << errno;
}
#endif
return std::make_unique<qos_t>(sockfd, reset_options);
}
+42
View File
@@ -8,6 +8,11 @@
#include <future>
#include <queue>
#ifdef __FreeBSD__
#include <netinet/in.h>
#include <sys/socket.h>
#endif
// lib includes
#include <boost/endian/arithmetic.hpp>
#include <openssl/err.h>
@@ -520,6 +525,9 @@ 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);
if (local_address.empty()) {
BOOST_LOG(warning) << "Couldn't get local address for control connection"sv;
}
session_p->localAddress = boost::asio::ip::make_address(local_address);
BOOST_LOG(debug) << "Control local address ["sv << local_address << ']';
@@ -1709,6 +1717,23 @@ namespace stream {
return -1;
}
#ifdef __FreeBSD__
// Enable IP_RECVDSTADDR for FreeBSD to receive destination address for IPv4
{
int on = 1;
if (setsockopt(ctx.video_sock.native_handle(), IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on)) < 0) {
BOOST_LOG(warning) << "Failed to set IP_RECVDSTADDR on video socket";
}
}
// Enable IPV6_RECVPKTINFO for IPv6
if (address_family == net::af_e::BOTH) {
int on = 1;
if (setsockopt(ctx.video_sock.native_handle(), IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)) < 0) {
BOOST_LOG(warning) << "Failed to set IPV6_RECVPKTINFO on video socket";
}
}
#endif
// Set video socket send buffer size (SO_SENDBUF) to 1MB
try {
ctx.video_sock.set_option(boost::asio::socket_base::send_buffer_size(1024 * 1024));
@@ -1730,6 +1755,23 @@ namespace stream {
return -1;
}
#ifdef __FreeBSD__
// Enable IP_RECVDSTADDR for FreeBSD to receive destination address for IPv4
{
int on = 1;
if (setsockopt(ctx.audio_sock.native_handle(), IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on)) < 0) {
BOOST_LOG(warning) << "Failed to set IP_RECVDSTADDR on audio socket";
}
}
// Enable IPV6_RECVPKTINFO for IPv6
if (address_family == net::af_e::BOTH) {
int on = 1;
if (setsockopt(ctx.audio_sock.native_handle(), IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)) < 0) {
BOOST_LOG(warning) << "Failed to set IPV6_RECVPKTINFO on audio socket";
}
}
#endif
ctx.audio_sock.bind(udp::endpoint(protocol, audio_port), ec);
if (ec) {
BOOST_LOG(fatal) << "Couldn't bind Audio server to port ["sv << audio_port << "]: "sv << ec.message();
+1 -1
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"
+2 -2
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__
+1 -1
View File
@@ -221,7 +221,7 @@ namespace video {
extern encoder_t quicksync;
#endif
#ifdef __linux__
#if defined(__linux__) || defined(linux) || defined(__linux) || defined(__FreeBSD__)
extern encoder_t vaapi;
#endif
@@ -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>
+3 -3
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();
+1 -1
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";
});
@@ -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>
@@ -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>
@@ -28,12 +28,18 @@ const config = ref(props.config)
<option value="auto">{{ $t('_common.auto') }}</option>
<PlatformLayout :platform="platform">
<template #freebsd>
<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 #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>
@@ -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>
@@ -241,6 +241,8 @@ function addRemappingEntry() {
</div>
</div>
</template>
<template #freebsd>
</template>
<template #linux>
</template>
<template #macos>
@@ -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
+6
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);
+2 -2
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,
+1 -1
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__