From 536df759ae2033f7c383677249729199961be0db Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 18 Apr 2022 14:53:28 -0400 Subject: [PATCH 01/13] Initial version of sphinx documentation and... - remove ubuntu 21.04 from CI (end of life) - adjust matrix strategy for clang.yml - Use lessons learned from RetroArcher on localize.yml, crowdin.yml, and locale.py - Add end of life comments to Dockerfiles - Adjust dependency order in Dockerfiles --- .github/workflows/CI.yml | 2 +- .github/workflows/clang.yml | 8 +- .github/workflows/localize.yml | 45 +- .readthedocs.yaml | 26 + DOCKER_README.md | 113 ++ README.rst | 65 ++ assets/sunshine.conf | 319 +----- crowdin.yml | 4 + docs/Makefile | 20 + docs/make.bat | 35 + docs/source/about/advanced_usage.rst | 1087 ++++++++++++++++++++ docs/source/about/docker.rst | 5 + docs/source/about/installation.rst | 84 ++ docs/source/about/overview.rst | 1 + docs/source/about/third_party_packages.rst | 65 ++ docs/source/about/usage.rst | 163 +++ docs/source/building/build.rst | 31 + docs/source/building/linux.rst | 241 +++++ docs/source/building/macos.rst | 41 + docs/source/building/windows.rst | 22 + docs/source/conf.py | 88 ++ docs/source/contributing/contributing.rst | 32 + docs/source/contributing/localization.rst | 80 ++ docs/source/contributing/testing.rst | 42 + docs/source/index.rst | 7 + docs/source/toc.rst | 27 + scripts/Dockerfile-debian | 4 +- scripts/Dockerfile-fedora_33 | 3 + scripts/Dockerfile-fedora_35 | 4 +- scripts/Dockerfile-ubuntu_18_04 | 2 + scripts/Dockerfile-ubuntu_20_04 | 2 + scripts/Dockerfile-ubuntu_21_04 | 3 + scripts/Dockerfile-ubuntu_21_10 | 4 +- scripts/_locale.py | 14 +- scripts/requirements.txt | 3 + 35 files changed, 2350 insertions(+), 342 deletions(-) create mode 100644 .readthedocs.yaml create mode 100644 DOCKER_README.md create mode 100644 README.rst create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/source/about/advanced_usage.rst create mode 100644 docs/source/about/docker.rst create mode 100644 docs/source/about/installation.rst create mode 100644 docs/source/about/overview.rst create mode 100644 docs/source/about/third_party_packages.rst create mode 100644 docs/source/about/usage.rst create mode 100644 docs/source/building/build.rst create mode 100644 docs/source/building/linux.rst create mode 100644 docs/source/building/macos.rst create mode 100644 docs/source/building/windows.rst create mode 100644 docs/source/conf.py create mode 100644 docs/source/contributing/contributing.rst create mode 100644 docs/source/contributing/localization.rst create mode 100644 docs/source/contributing/testing.rst create mode 100644 docs/source/index.rst create mode 100644 docs/source/toc.rst diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 60c91f8d..da3cc6ed 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -150,7 +150,7 @@ jobs: strategy: fail-fast: true # false to test all, true to fail entire job if any fail matrix: - distro: [ debian, ubuntu_18_04, ubuntu_20_04, ubuntu_21_04, ubuntu_21_10 ] + distro: [ debian, ubuntu_18_04, ubuntu_20_04, ubuntu_21_10 ] package: [ -p ] extension: [ deb ] include: # package these differently diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index 2ab2f600..f096c5f9 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -9,10 +9,6 @@ jobs: lint: name: Clang Format Lint runs-on: ubuntu-latest - strategy: - fail-fast: false # false to test all, true to fail entire job if any fail - matrix: - inplace: [ true, false ] # removed ubuntu_18_04 for now steps: - name: Checkout @@ -25,10 +21,10 @@ jobs: extensions: 'cpp,h,m,mm' clangFormatVersion: 13 style: file - inplace: ${{ matrix.inplace }} + inplace: false - name: Upload Artifacts - if: ${{ matrix.inplace == true }} + if: failure() uses: actions/upload-artifact@v3 with: name: sunshine diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index 57db544a..b860ad64 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -3,15 +3,16 @@ name: localize on: push: branches: [nightly] - paths: # prevents workflow from running unless files in these directories change - - 'sunshine/**' # only localizing files inside sunshine directory + paths: # prevents workflow from running unless these files change + - 'sunshine/**' + - 'locale/sunshine.po' workflow_dispatch: jobs: localize: name: Update Localization - if: ${{ github.event.pull_request.merged }} runs-on: ubuntu-latest + steps: - name: Checkout uses: actions/checkout@v3 @@ -37,9 +38,37 @@ jobs: run: | python ./scripts/_locale.py --extract - - name: GitHub Commit & Push # push changes back into nightly - uses: actions-js/push@v1.3 + - name: git diff + run: | + # print the git diff + git diff --exit-code locale/sunshine.po + + # set the variable with minimal output + OUTPUT=$(git diff --exit-code --numstat locale/sunshine.po) + echo "git_diff=${OUTPUT}" >> $GITHUB_ENV + + - name: git reset + if: ${{ env.git_diff != '1 1 locale/sunshine.po' }} # only run if more than 1 line changed + run: | + git reset --hard + + - name: Create/Update Pull Request + uses: peter-evans/create-pull-request@v4 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: nightly - message: localization updated by localize workflow + add-paths: | + locale/*.po + token: ${{ secrets.GH_PAT }} # must trigger PR tests + commit-message: New localization template + branch: localize/update + delete-branch: true + base: nightly + title: New Babel Updates + body: | + Update report + - Updated with *today's* date + - Auto-generated by [create-pull-request][1] + + [1]: https://github.com/peter-evans/create-pull-request + labels: | + babel + l10n diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..78c304f8 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,26 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python +build: + os: ubuntu-20.04 + tools: + python: "3.9" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + builder: html + configuration: docs/source/conf.py + fail_on_warning: true + +# Using Sphinx, build docs in additional formats +formats: all + +python: + install: + - requirements: ./scripts/requirements.txt + system_packages: true diff --git a/DOCKER_README.md b/DOCKER_README.md new file mode 100644 index 00000000..8691e01b --- /dev/null +++ b/DOCKER_README.md @@ -0,0 +1,113 @@ +# Docker + +## Using docker run +Create and run the container (substitute your ``): + +```bash +docker run -d \ + --name=sunshine \ + --restart=unless-stopped + -v :/config \ + -e PUID= \ + -e PGID= \ + -e TZ= \ + -p 47990:47990 \ + -p 47984:47984 \ + -p 47989:47989 \ + -p 48010:48010 \ + -p 47998:47998 \ + -p 47999:47999 \ + -p 48000:48000 \ + -p 48002:48002 \ + -p 48010:48010 \ + sunshinestream/sunshine +``` + +To update the container it must be removed and recreated: + +```bash +# Stop the container +docker stop sunshine +# Remove the container +docker rm sunshine +# Pull the latest update +docker pull sunshinestream/sunshine +# Run the container with the same parameters as before +docker run -d ... +``` + +## Using docker-compose + +Create a `docker-compose.yml` file with the following contents (substitute your ``): + +```yaml +version: '3' +services: + retroarcher: + image: sunshinestream/sunshine + container_name: sunshine + restart: unless-stopped + volumes: + - :/config + environment: + - PUID= + - PGID= + - TZ= + ports: + - "47990:47990" + - "47984:47984" + - "47989:47989" + - "48010:48010" + - "47998:47998" + - "47999:47999" + - "48000:48000" + - "48002:48002" + - "48010:48010" +``` + +Create and start the container (run the command from the same folder as your `docker-compose.yml` file): + +```bash +docker-compose up -d +``` + +To update the container: +```bash +# Pull the latest update +docker-compose pull +# Update and restart the container +docker-compose up -d +``` + +## Parameters +You must substitute the `` with your own settings. + +Parameters are split into two halves separated by a colon. The left side represents the host and the right side the +container. + +**Example:** `-p external:internal` - This shows the port mapping from internal to external of the container. +Therefore `-p 47990:47990` would expose port `47990` from inside the container to be accessible from the host's IP on +port `47990` (e.g. `http://:47990`). The internal port must be `47990`, but the external port may be changed +(e.g. `-p 8080:47990`). + + +| Parameter | Function | Example Value | Required | +| --------------------------- | -------------------- | ------------------- | -------- | +| `-p :47990` | Web UI Port | `47990` | True | +| `-v :/config` | Volume mapping | `/home/sunshine` | True | +| `-e PUID=` | User ID | `1001` | False | +| `-e PGID=` | Group ID | `1001` | False | +| `-e TZ=` | Lookup TZ value [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) | `America/New_York` | True | + +### User / Group Identifiers: + +When using data volumes (-v flags) permissions issues can arise between the host OS and the container. To avoid this +issue you can specify the user PUID and group PGID. Ensure the data volume directory on the host is owned by the same +user you specify. + +In this instance `PUID=1001` and `PGID=1001`. To find yours use id user as below: + +```bash +$ id dockeruser +uid=1001(dockeruser) gid=1001(dockergroup) groups=1001(dockergroup) +``` diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..8fb29b6b --- /dev/null +++ b/README.rst @@ -0,0 +1,65 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/README.rst + +Overview +======== + +About +----- +Sunshine is a Game stream host for Moonlight. It is an open source version of GeForce Experience (GFE). + +These are the advantages of Sunshine over GFE. + + - FOSS (Free and Open Source Software) + - Multi-platform + + - Linux (deb, rpm, and AppImage packages) + - MacOS (Portfile) + - Windows (portable binary) + + - Pair over web ui + - Supports AMD and Nvidia GPUs for encoding + - Supports software encoding + - Supports streaming to multiple clients + - Web UI for configuration + +Integrations +------------ + +.. image:: https://img.shields.io/github/workflow/status/sunshinestream/sunshine/CI/master?label=CI%20build&logo=github&style=for-the-badge + :alt: GitHub Workflow Status (CI) + :target: https://github.com/SunshineStream/Sunshine/actions/workflows/CI.yml?query=branch%3Amaster + +.. image:: https://img.shields.io/github/workflow/status/sunshinestream/sunshine/localize/nightly?label=localize%20build&logo=github&style=for-the-badge + :alt: GitHub Workflow Status (localize) + :target: https://github.com/SunshineStream/Sunshine/actions/workflows/localize.yml?query=branch%3Anightly + +.. image:: https://img.shields.io/readthedocs/sunshinestream?label=Docs&style=for-the-badge&logo=readthedocs + :alt: Read the Docs + :target: http://sunshinestream.readthedocs.io/ + +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=localized&style=for-the-badge&query=%24.progress..data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json&logo=crowdin + :alt: CrowdIn + :target: https://crowdin.com/project/sunshinestream + +Support +--------- + +.. image:: https://img.shields.io/discord/938534566107418705?label=Discord&style=for-the-badge&color=blue&logo=discord + :alt: Discord + :target: https://sunshinestream.github.io/discord + +.. image:: https://img.shields.io/github/discussions/sunshinestream/sunshine?logo=github&style=for-the-badge + :alt: GitHub Discussions + :target: https://github.com/SunshineStream/Sunshine/discussions + +Downloads +--------- + +.. image:: https://img.shields.io/github/downloads/sunshinestream/sunshine/total?style=for-the-badge&logo=github + :alt: GitHub Releases + :target: https://github.com/SunshineStream/Sunshine/releases/latest + +.. comment + image:: https://img.shields.io/docker/pulls/sunshinestream/sunshine?style=for-the-badge&logo=docker + :alt: Docker + :target: https://hub.docker.com/r/sunshinestream/sunshine diff --git a/assets/sunshine.conf b/assets/sunshine.conf index a1e020f0..509ff52a 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -1,318 +1 @@ -# If no external IP address is given, Sunshine will attempt to automatically detect external ip-address -# external_ip = 123.456.789.12 - -# Set the familly of ports used by Sunshine -# port = 47989 - -# The private key must be 2048 bits -# pkey = /dir/pkey.pem - -# The certificate must be signed with a 2048 bit key -# cert = /dir/cert.pem - -# The name displayed by Moonlight -# If not specified, the PC's hostname is used -# sunshine_name = Sunshine - -# The minimum log level printed to standard out -# -# none -> no logs are printed to standard out -# -# verbose = [0] -# debug = [1] -# info = [2] -# warning = [3] -# error = [4] -# fatal = [5] -# none = [6] -# -# min_log_level = info - -# The origin of the remote endpoint address that is not denied for HTTP method /pin -# Could be any of the following values: -# pc|lan|wan -# pc: Only localhost may access /pin -# lan: Only those in LAN may access /pin -# wan: Anyone may access /pin -# -# origin_pin_allowed = pc - -# The origin of the remote endpoint address that is not denied for HTTPS Web UI -# Could be any of the following values: -# pc|lan|wan -# pc: Only localhost may access the Web Manager -# lan: Only those in LAN may access the Web Manager -# wan: Anyone may access the Web Manager -# -# origin_web_ui_allowed = lan - -# If UPnP is enabled, Sunshine will attempt to open ports for streaming over the internet -# To enable it, uncomment the following line: -# upnp = on - -# The file where current state of Sunshine is stored -# file_state = sunshine_state.json - -# The file where user credentials for the UI are stored -# By default, credentials are stored in `file_state` -# credentials_file = sunshine_state.json - -# The display modes advertised by Sunshine -# -# Some versions of Moonlight, such as Moonlight-nx (Switch), -# rely on this list to ensure that the requested resolutions and fps -# are supported. -# -# fps = [10, 30, 60, 90, 120] -# resolutions = [ -# 352x240, -# 480x360, -# 858x480, -# 1280x720, -# 1920x1080, -# 2560x1080, -# 3440x1440, -# 1920x1200, -# 3860x2160, -# 3840x1600, -# ] - -# Sometimes it may be usefull to map keybindings. -# Wayland won't allow clients to capture the Win Key for example -# -# See https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes -# -# Note: -# keybindings needs to have a multiple of two elements -# keybindings = [ -# 0x10, 0xA0, -# 0x11, 0xA2, -# 0x12, 0xA4, -# 0x4A, 0x4B -# ] - -# It may be possible that you cannot send the Windows Key from Moonlight directly. -# In those cases it may be useful to make Sunshine think the Right Alt key is the Windows key -# key_rightalt_to_key_win = enabled - -# How long to wait in milliseconds for data from moonlight before shutting down the stream -# ping_timeout = 10000 - -# The file where configuration for the different applications that Sunshine can run during a stream -# file_apps = apps.json - -# Percentage of error correcting packets per data packet in each video frame -# Higher values can correct for more network packet loss, but at the cost of increasing bandwidth usage -# The default value of 20 is what GeForce Experience uses -# -# The value must be greater than 0 and lower than or equal to 255 -# fec_percentage = 20 - -# When multicasting, it could be usefull to have different configurations for each connected Client. -# For example: -# Clients connected through WAN and LAN have different bitrate contstraints. -# Decoders may require different settings for color -# -# Unlike simply broadcasting to multiple Client, this will generate distinct video streams. -# Note, CPU usage increases for each distinct video stream generated -# channels = 1 - -# The back/select button on the controller -# On the Shield, the home and powerbutton are not passed to Moonlight -# If, after the timeout, the back button is still pressed down, Home/Guide button press is emulated. -# If back_button_timeout < 0, then the Home/Guide button will not be emulated -# back_button_timeout = 2000 - -# !! Windows only !! -# Gamepads supported by Sunshine -# Possible values: -# x360 -- xbox 360 controller -# ds4 -- dualshock controller (PS4) -# gamepad = x360 - -# Control how fast keys will repeat themselves -# The initial delay in milliseconds before repeating keys -# key_repeat_delay = 500 -# -# How often keys repeat every second -# This configurable option supports decimals -# key_repeat_frequency = 24.9 - -# The name of the audio sink used for Audio Loopback -# If you do not specify this variable, pulseaudio will select the default monitor device. -# -# You can find the name of the audio sink using the following command: -# !! Linux only !! -# pacmd list-sinks | grep "name:" if running vanilla pulseaudio -# pPipewire: Use `pactl info | grep Source`. In some causes you'd need to use the `sink` device. Try `pactl info | grep Sink`, if _Source_ doesn't work -# audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo -# -# !! Windows only !! -# tools\audio-info.exe -# audio_sink = {0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B} -# -# The virtual sink, is the audio device that's virtual (Like Steam Streaming Speakers), it allows Sunshine -# to stream audio, while muting the speakers. -# virtual_sink = {0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4} - -# -# !! MacOS only !! -# audio_sink = BlackHole 2ch - -# !! Windows only !! -# You can select the video card you want to stream: -# The appropriate values can be found using the following command: -# tools\dxgi-info.exe -# adapter_name = Radeon RX 580 Series -# output_name = \\.\DISPLAY1 - -# !! Linux only !! -# Set the display number to stream. -# You can find them by the following command: -# xrandr --listmonitors -# Example output: "0: +HDMI-1 1920/518x1200/324+0+0 HDMI-1" -# ^ <-- You need this. -# output_name = 0 - -############################################### -# FFmpeg software encoding parameters -# Honestly, I have no idea what the optimal values would be. -# Play around with this :) - -# Quantitization Parameter -# Some devices don't support Constant Bit Rate. For those devices, QP is used instead -# Higher value means more compression, but less quality -# qp = 28 - -# Minimum number of threads used by ffmpeg to encode the video. -# Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually -# worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest -# value that can reliably encode at your desired streaming settings on your hardware. -# min_threads = 1 - -# Allows the client to request HEVC Main or HEVC Main10 video streams. -# HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding. -# If set to 0 (default), Sunshine will specify support for HEVC based on encoder -# If set to 1, Sunshine will not advertise support for HEVC -# If set to 2, Sunshine will advertise support for HEVC Main profile -# If set to 3, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles -# hevc_mode = 2 - -# Force a specific encoder, otherwise Sunshine will use the first encoder that is available -# supported encoders: -# nvenc -# amdvce # NOTE: alpha stage. The cursor is not yet displayed -# software -# -# encoder = nvenc -##################################### Software ##################################### -# See x264 --fullhelp for the different presets -# sw_preset = superfast -# sw_tune = zerolatency -# - -##################################### NVENC ##################################### -###### presets ########### -# default -# hp -- high performance -# hq -- high quality -# slow -- hq 2 passes -# medium -- hq 1 pass -# fast -- hp 1 pass -# bd -# ll -- low latency -# llhq -# llhp -# lossless -# losslesshp -########################## -# nv_preset = llhq -# -####### rate control ##### -# auto -- let ffmpeg decide rate control -# constqp -- constant QP mode -# vbr -- variable bitrate -# cbr -- constant bitrate -# cbr_hq -- cbr high quality -# cbr_ld_hq -- cbr low delay high quality -# vbr_hq -- vbr high quality -########################## -# nv_rc = auto - -###### h264 entropy ###### -# auto -- let ffmpeg nvenc decide the entropy encoding -# cabac -# cavlc -########################## -# nv_coder = auto - -##################################### AMD ##################################### -###### presets ########### -# default -# speed -# balanced -########################## -# amd_quality = balanced -# -####### rate control ##### -# auto -- let ffmpeg decide rate control -# constqp -- constant QP mode -# vbr_latency -- Latency Constrained Variable Bitrate -# vbr_peak -- Peak Contrained Variable Bitrate -# cbr -- constant bitrate -########################## -# amd_rc = auto - -###### h264 entropy ###### -# auto -- let ffmpeg nvenc decide the entropy encoding -# cabac -# cavlc -########################## -# amd_coder = auto - -#################################### VAAPI ################################### -####### adapter ########## -# Unlike with `amdvce` and `nvenc`, it doesn't matter if video encoding is done -# on a different GPU. -# Run the following commands: -# 1. ls /dev/dri/renderD* -# to find all devices capable of VAAPI -# 2. vainfo --display drm --device /dev/dri/renderD129 | grep -E "((VAProfileH264High|VAProfileHEVCMain|VAProfileHEVCMain10).*VAEntrypointEncSlice)|Driver version" -# Lists the name and capabilities of the device. -# To be supported by Sunshine, it needs to have at the very minimum: -# VAProfileH264High : VAEntrypointEncSlice -# adapter_name = /dev/dri/renderD128 - -################################# VideoToolbox ############################### -####### software encoding ########## -# Video Toolbox can be allowed/required to use software encoding instead of -# hardware accelerated encoding. -# auto -- let sunshine decide on encoding -# disabled -- disable software encoding -# allowed -- allow software encoding -# forced -- force software encoding -########################## -# vt_software = auto -# -####### realtime encoding ########## -# Disabling realtime encoding might result in a delayed frame encoding or frame drop -########################## -# vt_realtime = enabled -# -###### h264/hevc entropy ###### -# auto -- let ffmpeg decide the entropy encoding -# cabac -# cavlc -########################## -# vt_coder = auto - - -############################################## -# Some configurable parameters, are merely toggles for specific features -# The first occurrence turns it on, the second occurence turns it off, the third occurence turns it on again, etc, etc -# Here, you change the default state of any flag -# -# To set the initial state of flags -0 and -1 to on, set the following flags: -# flags = 012 -# -# See: sunshine --help for all options under the header: flags +# See our documentation at https://sunshinestream.readthedocs.io/en/latest/about/advanced_usage.html diff --git a/crowdin.yml b/crowdin.yml index d014a00c..ca9c8c5c 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,6 +1,10 @@ "base_path": "." "base_url": "https://api.crowdin.com" # optional (for Crowdin Enterprise only) "preserve_hierarchy": false # flatten tree on crowdin +"pull_request_labels": [ + "crowdin", + "l10n" +] "files" : [ { diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..d0c3cbf1 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..dc1312ab --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst new file mode 100644 index 00000000..e01de0aa --- /dev/null +++ b/docs/source/about/advanced_usage.rst @@ -0,0 +1,1087 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/about/advanced_usage.rst + +Advanced Usage +============== +Sunshine will work with the default settings for most users. In some cases you may want to configure Sunshine further. + +Configuration +------------- +The default location for the configuration file is ``./assets/sunshine.conf``. You can use another location if you +choose, by passing in the full configuration file path as the first argument when you start Sunshine. + +** Default File Location** + +.. table:: + :widths: auto + + ======= =========== + Value Description + ======= =========== + Linux ./assets/sunshine.conf + MacOS /opt/local/etc/sunshine.conf + Windows ./assets/sunshine.conf + ======= =========== + +Example + .. code-block:: bash + + sunshine ~/sunshine_config.conf + +To manually configure sunshine you may edit the `conf` file in a text editor. Use the examples as reference. + +.. Note:: Some settings are not available within the web ui. + +General +------- + +sunshine_name +^^^^^^^^^^^^^ + +Description + The name displayed by Moonlight + +Default + PC hostname + +Example + .. code-block:: text + + sunshine_name = Sunshine + +min_log_level +^^^^^^^^^^^^^ + +Description + The minimum log level printed to standard out. + +**Choices** + +.. table:: + :widths: auto + + ======= =========== + Value Description + ======= =========== + verbose verbose logging + debug debug logging + info info logging + warning warning logging + error error logging + fatal fatal logging + none no logging + ======= =========== + +Default + ``info`` + +Example + .. code-block:: text + + min_log_level = info + +Controls +-------- + +gamepad +^^^^^^^ + +Description + The type of gamepad to emulate on the host. + + .. Caution:: Applies to Windows only. + +**Choices** + +.. table:: + :widths: auto + + ===== =========== + Value Description + ===== =========== + x360 xbox 360 controller + ds4 dualshock controller (PS4) + ===== =========== + +Default + ``x360`` + +Example + .. code-block:: text + + gamepad = x360 + +back_button_timeout +^^^^^^^^^^^^^^^^^^^ + +Description + If, after the timeout, the back/select button is still pressed down, Home/Guide button press is emulated. + + On Nvidia Shield, the home and power button are not passed to Moonlight. + + .. Tip:: If back_button_timeout < 0, then the Home/Guide button will not be emulated. + +Default + ``2000`` + +Example + .. code-block:: text + + back_button_timeout = 2000 + +key_repeat_delay +^^^^^^^^^^^^^^^^ + +Description + The initial delay in milliseconds before repeating keys. Controls how fast keys will repeat themselves. + +Default + ``500`` + +Example + .. code-block:: text + + key_repeat_delay = 500 + +key_repeat_frequency +^^^^^^^^^^^^^^^^^^^^ + +Description + How often keys repeat every second. + + .. Tip:: This configurable option supports decimals. + +Default + .. Todo:: Unknown + +Example + .. code-block:: text + + key_repeat_frequency = 24.9 + +keybindings +^^^^^^^^^^^ + +Description + Sometimes it may be useful to map keybindings. Wayland won't allow clients to capture the Win Key for example. + + .. Tip:: See `virtual key codes `_ + + .. Note:: keybindings needs to have a multiple of two elements. + +Default + None + +Example + .. code-block:: text + + keybindings = [ + 0x10, 0xA0, + 0x11, 0xA2, + 0x12, 0xA4, + 0x4A, 0x4B + ] + +key_rightalt_to_key_win +^^^^^^^^^^^^^^^^^^^^^^^ + +Description + It may be possible that you cannot send the Windows Key from Moonlight directly. In those cases it may be useful to + make Sunshine think the Right Alt key is the Windows key. + +Default + None + +Example + .. code-block:: text + + key_rightalt_to_key_win = enabled + +Display +------- + +adapter_name +^^^^^^^^^^^^ + +Description + Select the video card you want to stream. + + .. Tip:: To find the name of the appropriate values follow these instructions. + + Linux + VA-API + Unlike with `amdvce` and `nvenc`, it doesn't matter if video encoding is done on a different GPU. + + .. code-block:: bash + + ls /dev/dri/renderD* # to find all devices capable of VAAPI + + # replace ``renderD129`` with the device from above to lists the name and capabilities of the device + vainfo --display drm --device /dev/dri/renderD129 | \ + grep -E "((VAProfileH264High|VAProfileHEVCMain|VAProfileHEVCMain10).*VAEntrypointEncSlice)|Driver version" + + To be supported by Sunshine, it needs to have at the very minimum: + ``VAProfileH264High : VAEntrypointEncSlice`` + + .. Todo:: MacOS + + Windows + .. code-block:: batch + + tools\dxgi-info.exe + +Default + Sunshine will select the default video card. + +Examples + Linux + .. code-block:: text + + adapter_name = /dev/dri/renderD128 + + .. Todo:: MacOS + + Windows + .. code-block:: text + + adapter_name = Radeon RX 580 Series + +output_name +^^^^^^^^^^^ + +Description + Select the display number you want to stream. + + .. Tip:: To find the name of the appropriate values follow these instructions. + + Linux + .. code-block:: bash + + xrandr --listmonitors + + Example output: ``0: +HDMI-1 1920/518x1200/324+0+0 HDMI-1`` + + You need to use the value before the colon in the output, e.g. ``0``. + + .. Todo:: MacOS + + Windows + .. code-block:: batch + + tools\dxgi-info.exe + +Default + Sunshine will select the default display. + +Examples + Linux + .. code-block:: text + + output_name = 0 + + .. Todo:: MacOS + + Windows + .. code-block:: text + + output_name = \\.\DISPLAY1 + +fps +^^^ + +Description + The fps modes advertised by Sunshine. + + .. Note:: Some versions of Moonlight, such as Moonlight-nx (Switch), rely on this list to ensure that the requested + fps is supported. + +Default + .. Todo:: Unknown + +Example + .. code-block:: text + + fps = [10, 30, 60, 90, 120] + +resolutions +^^^^^^^^^^^ + +Description + The resolutions advertised by Sunshine. + + .. Note:: Some versions of Moonlight, such as Moonlight-nx (Switch), rely on this list to ensure that the requested + resolution is supported. + +Default + .. Todo:: Unknown + +Example + .. code-block:: text + + resolutions = [ + 352x240, + 480x360, + 858x480, + 1280x720, + 1920x1080, + 2560x1080, + 3440x1440, + 1920x1200, + 3860x2160, + 3840x1600, + ] + +Audio +----- + +audio_sink +^^^^^^^^^^ + +Description + The name of the audio sink used for audio loopback. + + .. Tip:: To find the name of the audio sink follow these instructions. + + Linux + pulseaudio + .. code-block:: bash + + pacmd list-sinks | grep "name:" + + Linux + pipewire + .. code-block:: bash + + pactl info | grep Source + # in some causes you'd need to use the `Sink` device, if `Source` doesn't work, so try: + pactl info | grep Sink + + MacOS + Sunshine can only access microphones on MacOS due to system limitations. To stream system audio use + `Soundflower `_ or + `BlackHole `_. + + Windows + .. code-block:: batch + + tools\audio-info.exe + +Default + Sunshine will select the default audio device. + +Examples + Linux + .. code-block:: text + + audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo + + MacOS + .. code-block:: text + + audio_sink = BlackHole 2ch + + Windows + .. code-block:: text + + audio_sink = {0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B} + +virtual_sink +^^^^^^^^^^^^ + +Description + The audio device that's virtual, like Steam Streaming Speakers. This allows Sunshine to stream audio, while muting + the speakers. + + .. Tip:: See `audio_sink`_! + +Default + .. Todo:: Unknown + +Example + .. code-block:: text + + virtual_sink = {0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4} + +Network +------- + +external_ip +^^^^^^^^^^^ + +Description + If no external IP address is given, Sunshine will attempt to automatically detect external ip-address. + +Default + Automatic + +Example + .. code-block:: text + + external_ip = 123.456.789.12 + +port +^^^^ + +Description + Set the family of ports used by Sunshine. + +Default + ``47989`` + +Example + .. code-block:: text + + port = 47989 + +pkey +^^^^ + +Description + The private key. This must be 2048 bits. + +Default + .. Todo:: Unknown + +Example + .. code-block:: text + + pkey = /dir/pkey.pem + +cert +^^^^ + +Description + The certificate. Must be signed with a 2048 bit key. + +Default + .. Todo:: Unknown + +Example + .. code-block:: text + + cert = /dir/cert.pem + +origin_pin_allowed +^^^^^^^^^^^^^^^^^^ + +Description + The origin of the remote endpoint address that is not denied for HTTP method /pin. + +**Choices** + +.. table:: + :widths: auto + + ===== =========== + Value Description + ===== =========== + pc Only localhost may access /pin + lan Only LAN devices may access /pin + wan Anyone may access /pin + ===== =========== + +Default + ``pc`` + +Example + .. code-block:: text + + origin_pin_allowed = pc + +origin_web_ui_allowed +^^^^^^^^^^^^^^^^^^^^^ + +Description + The origin of the remote endpoint address that is not denied for HTTPS Web UI. + +**Choices** + +.. table:: + :widths: auto + + ===== =========== + Value Description + ===== =========== + pc Only localhost may access the web ui + lan Only LAN devices may access the web ui + wan Anyone may access the web ui + ===== =========== + +Default + ``lan`` + +Example + .. code-block:: text + + origin_web_ui_allowed = lan + +upnp +^^^^ + +Description + Sunshine will attempt to open ports for streaming over the internet. + +**Choices** + +.. table:: + :widths: auto + + ===== =========== + Value Description + ===== =========== + on enable UPnP + off disable UPnP + ===== =========== + +Default + ``off`` + +Example + .. code-block:: text + + upnp = on + +ping_timeout +^^^^^^^^^^^^ + +Description + How long to wait in milliseconds for data from Moonlight before shutting down the stream. + +Default + ``10000`` + +Example + .. code-block:: text + + ping_timeout = 10000 + +Encoding +-------- + +channels +^^^^^^^^ + +Description + This will generate distinct video streams, unlike simply broadcasting to multiple Clients. + + When multicasting, it could be useful to have different configurations for each connected Client. + + For instance: + + - Clients connected through WAN and LAN have different bitrate constraints. + - Decoders may require different settings for color. + + .. Warning:: CPU usage increases for each distinct video stream generated. + +Default + ``1`` + +Example + .. code-block:: text + + channels = 1 + +fec_percentage +^^^^^^^^^^^^^^ + +Description + Percentage of error correcting packets per data packet in each video frame. + + .. Warning:: Higher values can correct for more network packet loss, but at the cost of increasing bandwidth usage. + +Default + ``20`` + +Range + ``1-255`` + +Example + .. code-block:: text + + fec_percentage = 20 + +qp +^^ + +Description + Quantitization Parameter. Some devices don't support Constant Bit Rate. For those devices, QP is used instead. + + .. Warning:: Higher value means more compression, but less quality. + +Default + ``28`` + +Example + .. code-block:: text + + qp = 28 + +min_threads +^^^^^^^^^^^ + +Description + Minimum number of threads used by ffmpeg to encode the video. + + .. Note:: Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually worth it to gain + the use of more CPU cores for encoding. The ideal value is the lowest value that can reliably encode at your + desired streaming settings on your hardware. + +Default + ``1`` + +Example + .. code-block:: text + + min_threads = 1 + +hevc_mode +^^^^^^^^^ + +Description + Allows the client to request HEVC Main or HEVC Main10 video streams. + + .. Warning:: HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software + encoding. + +**Choices** + +.. table:: + :widths: auto + + ===== =========== + Value Description + ===== =========== + 0 advertise support for HEVC based on encoder + 1 do not advertise support for HEVC + 2 advertise support for HEVC Main profile + 3 advertise support for HEVC Main and Main10 (HDR) profiles + ===== =========== + +Default + ``0`` + +Example + .. code-block:: text + + hevc_mode = 2 + +encoder +^^^^^^^ + +Description + Force a specific encoder. + +**Choices** + +.. table:: + :widths: auto + + ======== =========== + Value Description + ======== =========== + nvenc For Nvidia graphics cards + amdvce For AMD graphics cards + software Encoding occurs on the CPU + ======== =========== + +Default + Sunshine will use the first encoder that is available. + +Example + .. code-block:: text + + encoder = nvenc + +sw_preset +^^^^^^^^^ + +Description + The encoder preset to use. + + .. Note:: This option only applies when using software `encoder`_. + + .. Note:: From `FFmpeg `_. + + A preset is a collection of options that will provide a certain encoding speed to compression ratio. A slower + preset will provide better compression (compression is quality per filesize). This means that, for example, if + you target a certain file size or constant bit rate, you will achieve better quality with a slower preset. + Similarly, for constant quality encoding, you will simply save bitrate by choosing a slower preset. + + Use the slowest preset that you have patience for. + +**Choices** + +.. table:: + :widths: auto + + ========= =========== + Value Description + ========= =========== + ultrafast fastest + superfast + veryfast + superfast + faster + fast + medium + slow + slow + slower + veryslow slowest + ========= =========== + +Default + ``superfast`` + +Example + .. code-block:: text + + sw_preset = superfast + +sw_tune +^^^^^^^ + +Description + The tuning preset to use. + + .. Note:: This option only applies when using software `encoder`_. + + .. Note:: From `FFmpeg `_. + + You can optionally use -tune to change settings based upon the specifics of your input. + +**Choices** + +.. table:: + :widths: auto + + =========== =========== + Value Description + =========== =========== + film use for high quality movie content; lowers deblocking + animation good for cartoons; uses higher deblocking and more reference frames + grain preserves the grain structure in old, grainy film material + stillimage good for slideshow-like content + fastdecode allows faster decoding by disabling certain filters + zerolatency good for fast encoding and low-latency streaming + =========== =========== + +Default + ``zerolatency`` + +Example + .. code-block:: text + + sw_tune = zerolatency + +nv_preset +^^^^^^^^^ + +Description + The encoder preset to use. + + .. Note:: This option only applies when using nvenc `encoder`_. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + default let ffmpeg decide + hp high performance + hq high quality + slow high quality, 2 passes + medium high quality, 1 pass + fast high performance, 1 pass + bd + ll low latency + llhq low latency, high quality + llhp low latency, high performance + lossless lossless + losslesshp lossless, high performance + ========== =========== + +Default + ``llhq`` + +Example + .. code-block:: text + + nv_preset = llhq + +nv_rc +^^^^^ + +Description + The encoder rate control. + + .. Note:: This option only applies when using nvenc `encoder`_. + + .. Note:: Moonlight does not currently support variable bitrate, although it can still be selected here. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + auto let ffmpeg decide + constqp constant QP mode + cbr constant bitrate + cbr_hq constant bitrate, high quality + cbr_ld_hq constant bitrate, low delay, high quality + vbr variable bitrate + vbr_hq variable bitrate, high quality + ========== =========== + +Default + ``auto`` + +Example + .. code-block:: text + + nv_rc = auto + +nv_coder +^^^^^^^^ + +Description + The entropy encoding to use. + + .. Note:: This option only applies when using nvenc `encoder`_. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + auto let ffmpeg decide + cabac + cavlc + ========== =========== + +Default + ``auto`` + +Example + .. code-block:: text + + nv_coder = auto + +amd_quality +^^^^^^^^^^^ + +Description + The encoder preset to use. + + .. Note:: This option only applies when using amdvce `encoder`_. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + default let ffmpeg decide + speed fast + balanced balance performance and speed + ========== =========== + +Default + ``balanced`` + +Example + .. code-block:: text + + amd_quality = balanced + +amd_rc +^^^^^^ + +Description + The encoder rate control. + + .. Note:: This option only applies when using amdvce `encoder`_. + + .. Note:: Moonlight does not currently support variable bitrate, although it can still be selected here. + +**Choices** + +.. table:: + :widths: auto + + =========== =========== + Value Description + =========== =========== + auto let ffmpeg decide + constqp constant QP mode + cbr constant bitrate + vbr_latency variable bitrate, latency constrained + vbr_peak variable bitrate, peak constrained + =========== =========== + +Default + ``auto`` + +Example + .. code-block:: text + + amd_rc = auto + +amd_coder +^^^^^^^^^ + +Description + The entropy encoding to use. + + .. Note:: This option only applies when using nvenc `encoder`_. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + auto let ffmpeg decide + cabac + cavlc + ========== =========== + +Default + ``auto`` + +Example + .. code-block:: text + + amd_coder = auto + +vt_software +^^^^^^^^^^^ + +Description + Force Video Toolbox to use software encoding. + + .. Note:: This option only applies when using MacOS. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + auto let ffmpeg decide + disabled disable software encoding + allowed allow software encoding + forced force software encoding + ========== =========== + +Default + ``auto`` + +Example + .. code-block:: text + + vt_software = auto + +vt_realtime +^^^^^^^^^^^ + +Description + Realtime encoding. + + .. Note:: This option only applies when using MacOS. + + .. Warning:: Disabling realtime encoding might result in a delayed frame encoding or frame drop. + +Default + ``enabled`` + +Example + .. code-block:: text + + vt_realtime = enabled + +vt_coder +^^^^^^^^ + +Description + The entropy encoding to use. + + .. Note:: This option only applies when using MacOS. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + auto let ffmpeg decide + cabac + cavlc + ========== =========== + +Default + ``auto`` + +Example + .. code-block:: text + + vt_coder = auto + +Advanced +-------- + +file_apps +^^^^^^^^^ + +Description + The application configuration file path. The file contains a json formatted list of applications that can be started + by Moonlight. + +Default + OS dependent + +Example + .. code-block:: text + + file_apps = apps.json + +file_state +^^^^^^^^^^ + +Description + The file where current state of Sunshine is stored. + +Default + ``sunshine_state.json`` + +Example + .. code-block:: text + + file_state = sunshine_state.json + +credentials_file +^^^^^^^^^^^^^^^^ + +Description + The file where user credentials for the UI are stored. + +Default + ``sunshine_state.json`` + +Example + .. code-block:: text + + credentials_file = sunshine_state.json diff --git a/docs/source/about/docker.rst b/docs/source/about/docker.rst new file mode 100644 index 00000000..f9afa251 --- /dev/null +++ b/docs/source/about/docker.rst @@ -0,0 +1,5 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/DOCKER_README.md + +.. Todo:: This is a planned feature. Currently no Dockerfile or image exists for Sunshine. + +.. mdinclude:: ../../../DOCKER_README.md diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst new file mode 100644 index 00000000..14ee37d2 --- /dev/null +++ b/docs/source/about/installation.rst @@ -0,0 +1,84 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/about/installation.rst + +Installation +============ +The recommended method for running Sunshine is to use the `binaries`_ bundled with the `latest release`_. + +Binaries +-------- +Binaries of Sunshine are created for each release. They are available for Linux, and Windows. +Binaries can be found in the `latest release`_. + +.. Todo:: Create binary package(s) for MacOS. See `here `_. + +.. Tip:: Some third party packages also exist. See + :ref:`Third Party Packages `. + +Docker +------ +.. Todo:: Docker images of Sunshine are planned to be included in the future. + They will be available on `Dockerhub.io`_ and `ghcr.io`_. + +Linux +----- + +AppImage +^^^^^^^^ +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:appimage?logo=github&style=for-the-badge' + :alt: GitHub issues by-label + +#. Download and extract `sunshine-appimage.zip` + +Debian Packages +^^^^^^^^^^^^^^^ +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:debian?logo=github&style=for-the-badge' + :alt: GitHub issues by-label + +#. Download the corresponding `.deb` file, e.g. ``sunshine-ubuntu_20_04.deb`` +#. ``sudo apt install -f ``, e.g. ``sudo apt install -f ./sunshine-ubuntu_20_04.deb`` + +Red Hat Packages +^^^^^^^^^^^^^^^^ +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:fedora?logo=github&style=for-the-badge' + :alt: GitHub issues by-label + +#. Download the corresponding `.rpm` file, e.g. ``sunshine-fedora_35.rpm`` +#. ``sudo rpm -i ``, e.g. ``sudo rpm -i ./sunshine-fedora_35.rpm`` + +.. Note:: If this is the first time installing. + + .. code-block:: bash + + sudo usermod -a -G input $USER + sudo reboot now + +.. Tip:: Optionally, run Sunshine in the background. + + .. code-block:: bash + + systemctl --user start sunshine + +MacOS +----- +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:macos?logo=github&style=for-the-badge' + :alt: GitHub issues by-label + +#. Install `MacPorts `_ +#. Download the `Portfile `_ from this repository to + ``/tmp`` +#. In a terminal run ``cd /tmp && sudo port install`` +#. The first time you start Sunshine, you will be asked to grant access to screen recording and your microphone. + +Windows +------- +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:10?logo=github&style=for-the-badge' + :alt: GitHub issues by-label + +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:11?logo=github&style=for-the-badge' + :alt: GitHub issues by-label + +#. Download and extract ``sunshine-windows.zip`` + +.. _latest release: https://github.com/SunshineStream/Sunshine/releases/latest +.. _Dockerhub.io: https://hub.docker.com/repository/docker/sunshinestream/sunshine +.. _ghcr.io: https://github.com/orgs/SunshineStream/packages?repo_name=sunshine diff --git a/docs/source/about/overview.rst b/docs/source/about/overview.rst new file mode 100644 index 00000000..e4a3ad51 --- /dev/null +++ b/docs/source/about/overview.rst @@ -0,0 +1 @@ +.. include:: ../../../README.rst diff --git a/docs/source/about/third_party_packages.rst b/docs/source/about/third_party_packages.rst new file mode 100644 index 00000000..4f349abe --- /dev/null +++ b/docs/source/about/third_party_packages.rst @@ -0,0 +1,65 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/about/third_party_packages.rst + +Third Party Packages +==================== + +.. Warning:: These packages are not maintained by SunshineStream. Use at your own risk. + +AUR (Arch Linux User Repository) +-------------------------------- + +.. image:: https://img.shields.io/aur/version/sunshine?style=for-the-badge&logo=archlinux + :alt: AUR version + +.. image:: https://img.shields.io/aur/last-modified/sunshine?style=for-the-badge&logo=archlinux + :alt: AUR last modified + +.. image:: https://img.shields.io/aur/votes/sunshine?style=for-the-badge&logo=archlinux + :alt: AUR votes + +.. image:: https://img.shields.io/aur/maintainer/sunshine?style=for-the-badge&logo=archlinux + :alt: AUR maintainer + +.. image:: https://img.shields.io/static/v1?label=maintainer&message=hadogenes&color=blue&style=for-the-badge&logo=github + :alt: GitHub Maintainer + :target: https://github.com/hadogenes + +Chocolatey +---------- + +.. image:: https://img.shields.io/chocolatey/v/Sunshine?style=for-the-badge&logo=chocolatey + :alt: Chocolatey Version + +.. image:: https://img.shields.io/chocolatey/dt/sunshine?style=for-the-badge&logo=chocolatey + :alt: Chocolatey + +.. image:: https://img.shields.io/static/v1?label=maintainer&message=AeliusSaionji&color=blue&style=for-the-badge&logo=github + :alt: GitHub Maintainer + :target: https://github.com/AeliusSaionji + +Scoop +----- + +.. image:: https://img.shields.io/scoop/v/sunshine?bucket=extras&style=for-the-badge + :alt: Scoop Version (extras bucket) + +.. image:: https://img.shields.io/static/v1?label=maintainer&message=sitiom&color=blue&style=for-the-badge&logo=github + :alt: GitHub Maintainer + :target: https://github.com/sitiom + + +Legacy GitHub Repo +------------------ + +.. image:: https://img.shields.io/github/last-commit/loki-47-6F-64/sunshine?style=for-the-badge&logo=github + :alt: GitHub last commit + +.. image:: https://img.shields.io/github/release-date/loki-47-6F-64/sunshine?style=for-the-badge&logo=github + :alt: GitHub Release Date + +.. image:: https://img.shields.io/github/downloads/loki-47-6F-64/sunshine/total?style=for-the-badge&logo=github + :alt: GitHub Releases + +.. image:: https://img.shields.io/static/v1?label=maintainer&message=loki-47-6F-64&color=blue&style=for-the-badge&logo=github + :alt: GitHub Maintainer + :target: https://github.com/loki-47-6F-64 diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst new file mode 100644 index 00000000..6eafe4b9 --- /dev/null +++ b/docs/source/about/usage.rst @@ -0,0 +1,163 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/about/usage.rst + +Usage +===== +#. See the `setup`_ section for your specific OS. +#. Run ``sunshine /sunshine.conf``. + + .. Note:: The configuration file specified will be created if it doesn't exist. + + .. Tip:: If using the Linux AppImage, replace ``sunshine`` with ``./sunshine.AppImage`` + +#. Configure Sunshine in the web ui + The web ui is available on `https://localhost:47990 `_ by default. You may replace + `localhost` with your internal ip address. + + .. Tip:: Ignore any warning given by your browser about "insecure website". + + .. Caution:: If running for the first time, make sure to note the username and password Sunshine showed to you, + since you cannot get back later! + + Add games and applications. + This can be configured in the web ui. + + .. Note:: Additionally, apps can be configured manually. `assets/apps_.json` is an example of a list of + applications that are started just before running a stream. + + .. Note:: Application list is not fully supported on MacOS + +#. In Moonlight, you may need to add the PC manually. +#. When Moonlight request you insert the correct pin on sunshine: + + - Login to the web ui + - Go to "PIN" in the Header + - Type in your PIN and press Enter, you should get a Success Message + - In Moonlight, select one of the Applications listed + +Network +------- +Sunshine will be available on port 47990 by default. + +.. Warning:: Do not expose port 47990, or the web ui, to the internet! + +Arguments +--------- +To get a list of available arguments run the following: + + .. code-block:: bash + + sunshine --help + +Setup +----- + +Linux +^^^^^ +Sunshine needs access to `uinput` to create mouse and gamepad events. + +Add user to group `input`. + .. code-block:: bash + + usermod -a -G input $USER + +Create `udev` rules. + .. code-block:: bash + + nano /etc/udev/rules.d/85-sunshine-input.rules + + Input the following contents: + ``KERNEL=="uinput", GROUP="input", MODE="0660"`` + + Save the file and exit: + + #. ``CTRL+X`` to start exit. + #. ``Y`` to save modifications. + +Configure autostart service + `path/to/build/dir/sunshine.service` is used to start sunshine in the background. To use it, do the following: + + #. Copy it to the users systemd: ``cp sunshine.service ~/.config/systemd/user/`` + #. Starting + + - One time: ``systemctl --user start sunshine`` + - Always on boot: ``systemctl --user enable sunshine`` + +Additional Setup for KMS + .. Note:: ``cap_sys_admin`` may as well be root, except you don't need to be root to run it. + + It is necessary to allow Sunshine to use KMS: ``sudo setcap cap_sys_admin+p sunshine`` + +MacOS +^^^^^ +Sunshine can only access microphones on macOS due to system limitations. To stream system audio use +`Soundflower `_ or +`BlackHole `_ and +select their sink as audio device in `sunshine.conf`. + +.. Note:: Command Keys are not forwarded by Moonlight. Right Option-Key is mapped to CMD-Key. + +.. Caution:: Gamepads are not supported. + +Windows +^^^^^^^ +For gamepad support, install `ViGEmBus `_ + +Shortcuts +--------- +All shortcuts start with CTRL + ALT + SHIFT, just like Moonlight + + - ``CTRL + ALT + SHIFT + N`` - Hide/Unhide the cursor (This may be useful for Remote Desktop Mode for Moonlight) + - ``CTRL + ALT + SHIFT + F1/F13`` - Switch to different monitor for Streaming + +Application List +---------------- +- You can use Environment variables in place of values +- ``$(HOME)` will be replaced by the value of ``$HOME`` +- ``$$`` will be replaced by ``$``, e.g. ``$$(HOME)`` will be replaced by ``$(HOME)`` +- ``env`` - Adds or overwrites Environment variables for the commands/applications run by Sunshine +- ``"Variable name":"Variable value"`` +- ``apps`` - The list of applications +- Example application: + + .. code-block:: json + + { + "name":"An App", + "cmd":"command to open app", + "prep-cmd":[ + { + "do":"some-command", + "undo":"undo-that-command" + } + ], + "detached":[ + "some-command", + "another-command" + ] + } + + - ``name`` - The name of the application/game + - ``output`` - The file where the output of the command is stored + - ``detached`` - A list of commands to be run and forgotten about + - ``prep-cmd`` - A list of commands to be run before/after the application + + - If any of the prep-commands fail, starting the application is aborted + - ``do`` - Run before the application + + - If it fails, all ``undo`` commands of the previously succeeded ``do`` commands are run + + - ``undo`` - Run after the application has terminated + + - This should not fail considering it is supposed to undo the ``do`` commands + - If it fails, Sunshine is terminated + + - ``cmd`` - The main application + + - If not specified, a process is started that sleeps indefinitely + +Considerations +-------------- +- When an application is started, if there is an application already running, it will be terminated. +- When the application has been shutdown, the stream shuts down as well. +- In addition to the apps listed, one app "Desktop" is hardcoded into Sunshine. It does not start an application, + instead it simply starts a stream. diff --git a/docs/source/building/build.rst b/docs/source/building/build.rst new file mode 100644 index 00000000..556725fc --- /dev/null +++ b/docs/source/building/build.rst @@ -0,0 +1,31 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/build.rst + +Build +===== +Sunshine binaries are built using `CMake `_. Cross compilation is not +supported. That means the binaries must be built on the target operating system and architecture. + +Building Locally +---------------- + +Clone +^^^^^ +Ensure `git `_ is installed and run the following: + + .. code-block:: bash + + git clone https://github.com/sunshinestream/sunshine.git --recurse-submodules + cd sunshine && mkdir build && cd build + +Build +^^^^^ +See the section specific to your OS. + +Remote Build +------------ +It may be beneficial to build remotely in some cases. This will enable easier building on different operating systems. + +#. Fork the project +#. Activate workflows +#. Trigger the `CI` workflow manually +#. Download the artifacts/binaries from the workflow run summary diff --git a/docs/source/building/linux.rst b/docs/source/building/linux.rst new file mode 100644 index 00000000..9f87d588 --- /dev/null +++ b/docs/source/building/linux.rst @@ -0,0 +1,241 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/linux.rst + +Linux +===== + +Requirements +------------ +.. Warning:: Installing these dependencies may break your distribution. It is recommended to build in a virtual machine + or to use the Dockerfile builds located in the `./scripts` directory. + +Debian Bullseye +""""""""""""""" +End of Life: TBD + +Install Requirements + .. code-block:: bash + + sudo apt update && sudo apt install \ + build-essential \ + cmake \ + git \ + libavdevice-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libboost-thread-dev \ + libcap-dev \ # KMS + libdrm-dev \ # KMS + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ # Wayland + libx11-dev \ # X11 + libxcb-shm0-dev \ # X11 + libxcb-xfixes0-dev \ # X11 + libxcb1-dev \ # X11 + libxfixes-dev \ # X11 + libxrandr-dev \ # X11 + libxtst-dev \ # X11 + nvidia-cuda-dev \ # Cuda, NvFBC + nvidia-cuda-toolkit \ # Cuda, NvFBC + +Fedora 35 +""""""""" +End of Life: TBD + +Install Repositories + .. code-block:: bash + + sudo dnf update && \ + sudo dnf group install "Development Tools" && \ + sudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm + +Install Requirements + .. code-block:: bash + + sudo dnf install \ + boost-devel \ + boost-static.x86_64 \ + cmake \ + ffmpeg-devel \ + gcc-c++ \ + libevdev-devel \ + libX11-devel \ # X11 + libxcb-devel \ # X11 + libXcursor-devel \ # X11 + libXfixes-devel \ # X11 + libXinerama-devel \ # X11 + libXi-devel \ # X11 + libXrandr-devel \ # X11 + libXtst-devel \ # X11 + mesa-libGL-devel \ + openssl-devel \ + opus-devel \ + pulseaudio-libs-devel \ + rpm-build \ # if you want to build an RPM binary package + +Ubuntu 18.04 +"""""""""""" +End of Life: April 2028 + +Install Repositories + .. code-block:: bash + + sudo apt update && sudo apt install \ + software-properties-common \ + && add-apt-repository ppa:savoury1/graphics && \ + add-apt-repository ppa:savoury1/multimedia && \ + add-apt-repository ppa:savoury1/ffmpeg4 && \ + add-apt-repository ppa:savoury1/boost-defaults-1.71 && \ + add-apt-repository ppa:ubuntu-toolchain-r/test && \ + +Install Requirements + .. code-block:: bash + + sudo apt install \ + build-essential \ + cmake \ + gcc-10 \ + git \ + g++-10 \ + libavdevice-dev \ + libboost-filesystem1.71-dev \ + libboost-log1.71-dev \ + libboost-regex1.71-dev \ + libboost-thread1.71-dev \ + libcap-dev \ # KMS + libdrm-dev \ # KMS + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ # Wayland + libx11-dev \ # X11 + libxcb-shm0-dev \ # X11 + libxcb-xfixes0-dev \ # X11 + libxcb1-dev \ # X11 + libxfixes-dev \ # X11 + libxrandr-dev \ # X11 + libxtst-dev \ # X11 + wget \ + +Update gcc alias + .. code-block:: bash + + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 + +Install CuDA + .. code-block:: bash + + wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O ./cuda.run && chmod a+x ./cuda.run + ./cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm ./cuda.run + +Install CMake + .. code-block:: bash + + wget https://cmake.org/files/v3.22/cmake-3.22.2-linux-x86_64.sh + mkdir /opt/cmake + sh /cmake-3.22.2-linux-x86_64.sh --prefix=/opt/cmake --skip-license + ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake + cmake --version + +Ubuntu 20.04 +"""""""""""" +End of Life: April 2030 + +Install Requirements + .. code-block:: bash + + sudo apt update && sudo apt install \ + build-essential \ + cmake \ + git \ + g++-10 \ + libavdevice-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libboost-thread-dev \ + libcap-dev \ # KMS + libdrm-dev \ # KMS + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ # Wayland + libx11-dev \ # X11 + libxcb-shm0-dev \ # X11 + libxcb-xfixes0-dev \ # X11 + libxcb1-dev \ # X11 + libxfixes-dev \ # X11 + libxrandr-dev \ # X11 + libxtst-dev \ # X11 + wget \ + +Update gcc alias + .. code-block:: bash + + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 + +Install CuDA + .. code-block:: bash + + wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O ./cuda.run && chmod a+x ./cuda.run + ./cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm ./cuda.run + +Ubuntu 21.10 +"""""""""""" +End of Life: July 2022 + +Install Requirements + .. code-block:: bash + + sudo apt update && sudo apt install \ + build-essential \ + cmake \ + git \ + libavdevice-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libboost-thread-dev \ + libcap-dev \ # KMS + libdrm-dev \ # KMS + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ # Wayland + libx11-dev \ # X11 + libxcb-shm0-dev \ # X11 + libxcb-xfixes0-dev \ # X11 + libxcb1-dev \ # X11 + libxfixes-dev \ # X11 + libxrandr-dev \ # X11 + libxtst-dev \ # X11 + nvidia-cuda-dev \ # Cuda, NvFBC + nvidia-cuda-toolkit \ # Cuda, NvFBC + +Ubuntu 22.04 +"""""""""""" +End of Life: April 2027 + +.. Todo:: Create Ubuntu 22.04 Dockerfile and complete this documentation. + +Build +----- +.. Caution:: Ensure you are in the build directory created during the clone step earlier before continuing. + +Debian based OSes + .. code-block:: bash + + cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 .. + +Red Hat based Oses + .. code-block:: bash + + cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ .. + +Finally + .. code-block:: bash + + make -j ${nproc} diff --git a/docs/source/building/macos.rst b/docs/source/building/macos.rst new file mode 100644 index 00000000..c763ad12 --- /dev/null +++ b/docs/source/building/macos.rst @@ -0,0 +1,41 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/macos.rst + +MacOS +===== + +Requirements +------------ +MacOS Big Sur and Xcode 12.5+ + +Use either `MacPorts `_ or `Homebrew `_ + +MacPorts +"""""""" +Install Requirements + .. code-block:: bash + + sudo port install cmake boost libopus ffmpeg + +Homebrew +"""""""" +Install Requirements + .. code-block:: bash + + brew install boost cmake ffmpeg libopusenc + # if there are issues with an SSL header that is not found: + cd /usr/local/include + ln -s ../opt/openssl/include/openssl . + +Build +----- +.. Caution:: Ensure you are in the build directory created during the clone step earlier before continuing. + + .. code-block:: bash + + cmake .. + make -j ${nproc} + +If cmake fails complaining to find Boost, try to set the path explicitly. + + ``cmake -DBOOST_ROOT=[boost path] ..``, e.g., ``cmake -DBOOST_ROOT=/opt/local/libexec/boost/1.76 ..`` + diff --git a/docs/source/building/windows.rst b/docs/source/building/windows.rst new file mode 100644 index 00000000..6a7c1e16 --- /dev/null +++ b/docs/source/building/windows.rst @@ -0,0 +1,22 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/building/windows.rst + +Windows +======= + +Requirements +------------ +First you need to install `MSYS2 `_, then startup "MSYS2 MinGW 64-bit" and install the +following packages using: + +.. code-block:: batch + + pacman -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git mingw-w64-x86_64-make cmake make gcc + +Build +----- +.. Caution:: Ensure you are in the build directory created during the clone step earlier before continuing. + + .. code-block:: batch + + cmake -G"Unix Makefiles" .. + mingw32-make diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 00000000..0375d42b --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,88 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# standard imports +from datetime import datetime + + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +import os +# import sys + +script_dir = os.path.dirname(os.path.abspath(__file__)) # the directory of this file +source_dir = os.path.dirname(script_dir) # the source folder directory +root_dir = os.path.dirname(source_dir) # the root folder directory + +# -- Project information ----------------------------------------------------- +project = 'Sunshine' +copyright = f'{datetime.now ().year}, {project}' +author = 'ReenigneArcher' + +# The full version, including alpha/beta/rc tags +# version = '0.13.0' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'm2r2', # enable markdown files + 'sphinx.ext.autosectionlabel', + 'sphinx.ext.todo', # enable to-do sections + 'sphinx.ext.viewcode' # add links to view source code +] + +# Add any paths that contain templates here, relative to this directory. +# templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['toc.rst'] + +# Extensions to include. +source_suffix = ['.rst', '.md'] + + +# -- Options for HTML output ------------------------------------------------- + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +# html_static_path = ['_static'] + +html_logo = os.path.join(root_dir, 'sunshine.ico') + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'sphinx_rtd_theme' + +html_theme_options = { + # 'analytics_id': 'G-XXXXXXXXXX', # Provided by Google in your dashboard + # 'analytics_anonymize_ip': False, + 'logo_only': False, + 'display_version': False, + 'prev_next_buttons_location': 'bottom', + 'style_external_links': True, + 'vcs_pageview_mode': 'blob', + # 'style_nav_header_background': 'white', + # Toc options + 'collapse_navigation': True, + 'sticky_navigation': True, + 'navigation_depth': 4, + 'includehidden': True, + 'titles_only': False, +} + +# extension config options +autosectionlabel_prefix_document = True # Make sure the target is unique +todo_include_todos = True diff --git a/docs/source/contributing/contributing.rst b/docs/source/contributing/contributing.rst new file mode 100644 index 00000000..d9f5106f --- /dev/null +++ b/docs/source/contributing/contributing.rst @@ -0,0 +1,32 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/contributing/contributing.rst + +Contributing +============ +#. Fork the repo on GitHub +#. Create a new branch for the feature you are adding or the issue you are fixing + + .. Tip:: Base the new branch off the `nightly` branch. It will make your life easier when you submit the PR! + +#. Make changes, push commits, etc. +#. Files should contain an empty line at the end. +#. Document your code! +#. Test your code! +#. When ready create a PR to this repo on the `nightly` branch. + + .. Hint:: If you accidentally make your PR against a different branch, a bot will comment letting you know it's on + the wrong branch. Don't worry. You can edit the PR to change the target branch. There is no reason to close the + PR! + + .. Note:: Draft PRs are also welcome as you work through issues. The benefit of creating a draft PR is that an + automated build can run in a github runner. + + .. Attention:: Do not expect partially complete PRs to be merged. These topics will be considered before merging. + + - Does the code follows the style guidelines of this project? + + .. Tip:: Look at examples of existing code in the project! + + - Is the code well commented? + - Were documentation blocks updated for new or modified components? + + .. Note:: Developers and maintainers will attempt to assist with challenging issues. diff --git a/docs/source/contributing/localization.rst b/docs/source/contributing/localization.rst new file mode 100644 index 00000000..6d0f6556 --- /dev/null +++ b/docs/source/contributing/localization.rst @@ -0,0 +1,80 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/contributing/localization.rst + +Localization +============ +Sunshine is being localized into various languages. The default language is `en` (English) and is highlighted green. + +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=de&style=for-the-badge&query=%24.progress.0.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json +.. image:: https://img.shields.io/badge/dynamic/json?color=green&label=en&style=for-the-badge&query=%24.progress.1.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=en-GB&style=for-the-badge&query=%24.progress.2.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=en-US&style=for-the-badge&query=%24.progress.3.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=es-ES&style=for-the-badge&query=%24.progress.4.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=fr&style=for-the-badge&query=%24.progress.5.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=it&style=for-the-badge&query=%24.progress.6.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json +.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=ru&style=for-the-badge&query=%24.progress.7.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json + +Graph + .. image:: https://badges.awesome-crowdin.com/translation-15178612-503956.png + +CrowdIn +------- +The translations occur on +`CrowdIn `_. Feel free to contribute to localization there. +Only elements of the API are planned to be translated. + +.. Note:: The rest API has not yet been implemented. + +Translations Basics + - The brand names `SunshineStream` and `Sunshine` should never be translated. + - Other brand names should never be translated. + Examples: + + - AMD + - Nvidia + +CrowdIn Integration + How does it work? + + When a change is made to sunshine source code, a workflow generates new translation templates + that get pushed to CrowdIn automatically. + + When translations are updated on CrowdIn, a push gets made to the `l10n_nightly` branch and a PR is made against the + `nightly` branch. Once PR is merged, all updated translations are part of the project and will be included in the + next release. + +Extraction +---------- +There should be minimal cases where strings need to be extracted from source code; however it may be necessary in some +situations. For example if a system tray icon is added it should be localized as it is user interfacing. + + - Wrap the string to be extracted in a function as shown. + + .. code-block:: cpp + + #include + boost::locale::translate("Hello world!") + +.. Warning:: This is for information only. Contributors should never include manually updated template files, or + manually compiled language files in Pull Requests. + +Strings are automatically extracted from the code to the `locale/sunshine.po` template file. The generated file is +used by CrowdIn to generate language specific template files. The file is generated using the +`.github/workflows/localize.yml` workflow and is run on any push event into the `nightly` branch. Jobs are only run if +any of the following paths are modified. + + .. code-block:: yaml + + - 'sunshine/**' + +When testing locally it may be desirable to manually extract, initialize, update, and compile strings. Python is +required for this, along with the dependencies in the `./scripts/requirements.txt` file. + + Extract, initialize, and update + .. code-block:: bash + + python ./scripts/_locale.py --extract --init --update + + Compile + .. code-block:: bash + + python ./scripts/_locale.py --compile diff --git a/docs/source/contributing/testing.rst b/docs/source/contributing/testing.rst new file mode 100644 index 00000000..a08c6f9c --- /dev/null +++ b/docs/source/contributing/testing.rst @@ -0,0 +1,42 @@ +:github_url: https://github.com/RetroArcher/RetroArcher/tree/nightly/docs/source/contributing/testing.rst + +Testing +======= + +Clang Format +------------ +Source code is tested against the `.clang-format` file for linting errors. The workflow file responsible for clang +format testing is `.github/workflows/clang.yml`. + +Test clang-format locally. + .. Todo:: This documentation needs to be improved. + + .. code-block:: bash + + clang-format ... + +Sphinx +------ +Sunshine uses `Sphinx `_ for documentation building. Sphinx is included +in the `./scripts/requirements.txt` file. Python is required to build sphinx docs. Installation and setup of python +will not be covered here. + +The config file for Sphinx is `docs/source/conf.py`. This is already included in the repo and should not be modified. + +Test with Sphinx + .. code-block:: bash + + cd docs + make html + + Alternatively + + .. code-block:: bash + + cd docs + sphinx-build -b html source build + +Unit Testing +------------ +.. Todo:: Sunshine does not currently have any unit tests. If you would like to help us improve please get in contact + with us, or make a PR with suggested changes. diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 00000000..5384c8e2 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,7 @@ +:github_url: https://github.com/SunshineStream/Sunshine/tree/nightly/docs/source/index.rst + +SunshineStream has this documentation hosted on `Read the Docs `_. + +Table of Contents +================= +.. include:: toc.rst diff --git a/docs/source/toc.rst b/docs/source/toc.rst new file mode 100644 index 00000000..9c0989ee --- /dev/null +++ b/docs/source/toc.rst @@ -0,0 +1,27 @@ +.. toctree:: + :maxdepth: 2 + :caption: About + + about/overview + about/installation + about/docker + about/third_party_packages + about/usage + about/advanced_usage + +.. toctree:: + :maxdepth: 2 + :caption: Build + + building/build + building/linux + building/macos + building/windows + +.. toctree:: + :maxdepth: 2 + :caption: Contributing + + contributing/contributing + contributing/localization + contributing/testing diff --git a/scripts/Dockerfile-debian b/scripts/Dockerfile-debian index 3a53fd9f..fc77eb8e 100644 --- a/scripts/Dockerfile-debian +++ b/scripts/Dockerfile-debian @@ -1,5 +1,7 @@ FROM debian:bullseye AS sunshine-debian +# Debian Bullseye end of life is TBD + ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" @@ -11,9 +13,9 @@ RUN apt-get update -y && \ cmake \ git \ libavdevice-dev \ - libboost-thread-dev \ libboost-filesystem-dev \ libboost-log-dev \ + libboost-thread-dev \ libcap-dev \ libdrm-dev \ libevdev-dev \ diff --git a/scripts/Dockerfile-fedora_33 b/scripts/Dockerfile-fedora_33 index 50a4e3db..136058c3 100644 --- a/scripts/Dockerfile-fedora_33 +++ b/scripts/Dockerfile-fedora_33 @@ -1,5 +1,8 @@ FROM fedora:33 AS sunshine-fedora_33 +# Fedora 33 end of life is November 2021 +# This file remains for reference only + SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN dnf -y update && \ dnf -y group install "Development Tools" && \ diff --git a/scripts/Dockerfile-fedora_35 b/scripts/Dockerfile-fedora_35 index d4f5d843..18b04377 100644 --- a/scripts/Dockerfile-fedora_35 +++ b/scripts/Dockerfile-fedora_35 @@ -1,5 +1,7 @@ FROM fedora:35 AS sunshine-fedora_35 +# Fedora 35 end of life is TBD + SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN dnf -y update && \ dnf -y group install "Development Tools" && \ @@ -11,8 +13,8 @@ RUN dnf -y update && \ ffmpeg-devel \ gcc-c++ \ libevdev-devel \ - libxcb-devel \ libX11-devel \ + libxcb-devel \ libXcursor-devel \ libXfixes-devel \ libXinerama-devel \ diff --git a/scripts/Dockerfile-ubuntu_18_04 b/scripts/Dockerfile-ubuntu_18_04 index 7225c0c7..6ad3e8c0 100644 --- a/scripts/Dockerfile-ubuntu_18_04 +++ b/scripts/Dockerfile-ubuntu_18_04 @@ -1,5 +1,7 @@ FROM ubuntu:18.04 AS sunshine-ubuntu_18_04 +# Ubuntu 18.04 end of life is April 2028 + ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" diff --git a/scripts/Dockerfile-ubuntu_20_04 b/scripts/Dockerfile-ubuntu_20_04 index 9ae7dfa9..44e897a7 100644 --- a/scripts/Dockerfile-ubuntu_20_04 +++ b/scripts/Dockerfile-ubuntu_20_04 @@ -1,5 +1,7 @@ FROM ubuntu:20.04 AS sunshine-ubuntu_20_04 +# Ubuntu 20.04 end of life is April 2030 + ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" diff --git a/scripts/Dockerfile-ubuntu_21_04 b/scripts/Dockerfile-ubuntu_21_04 index 00801c55..012845a5 100644 --- a/scripts/Dockerfile-ubuntu_21_04 +++ b/scripts/Dockerfile-ubuntu_21_04 @@ -1,5 +1,8 @@ FROM ubuntu:21.04 AS sunshine-ubuntu_21_04 +# Ubuntu 21.04 end of life is January 2022 +# This file remains for reference only + ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" diff --git a/scripts/Dockerfile-ubuntu_21_10 b/scripts/Dockerfile-ubuntu_21_10 index 83b3e7f8..6be49dbe 100644 --- a/scripts/Dockerfile-ubuntu_21_10 +++ b/scripts/Dockerfile-ubuntu_21_10 @@ -1,5 +1,7 @@ FROM ubuntu:21.10 AS sunshine-ubuntu_21_10 +# Ubuntu 21.10 end of life is July 2022 + ARG DEBIAN_FRONTEND=noninteractive ARG TZ="Europe/London" @@ -10,9 +12,9 @@ RUN apt-get update -y && \ cmake \ git \ libavdevice-dev \ - libboost-thread-dev \ libboost-filesystem-dev \ libboost-log-dev \ + libboost-thread-dev \ libcap-dev \ libdrm-dev \ libevdev-dev \ diff --git a/scripts/_locale.py b/scripts/_locale.py index 82e172cc..e47887cc 100644 --- a/scripts/_locale.py +++ b/scripts/_locale.py @@ -1,4 +1,6 @@ -"""_locale.py +""" +.. + _locale.py Functions related to building, initializing, updating, and compiling localization translations. @@ -62,7 +64,7 @@ def x_extract(): commands.append(filename) print(commands) - proc = subprocess.run(args=commands, cwd=root_dir) + subprocess.check_output(args=commands, cwd=root_dir) # fix header body = "" @@ -98,7 +100,7 @@ def babel_init(locale_code: str): ] print(commands) - proc = subprocess.run(args=commands, cwd=root_dir) + subprocess.check_output(args=commands, cwd=root_dir) def babel_update(): @@ -113,7 +115,7 @@ def babel_update(): ] print(commands) - proc = subprocess.run(args=commands, cwd=root_dir) + subprocess.check_output(args=commands, cwd=root_dir) def babel_compile(): @@ -126,13 +128,13 @@ def babel_compile(): ] print(commands) - proc = subprocess.run(args=commands, cwd=root_dir) + subprocess.check_output(args=commands, cwd=root_dir) if __name__ == '__main__': # Set up and gather command line arguments parser = argparse.ArgumentParser( - description='Script helps update locale_id translations. Translations must be done manually.') + description='Script helps update locale translations. Translations must be done manually.') parser.add_argument('--extract', action='store_true', help='Extract messages from c++ files.') parser.add_argument('--init', action='store_true', help='Initialize any new locales specified in target locales.') diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 9d236e72..c089efd9 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -1 +1,4 @@ Babel==2.9.1 +m2r2==0.3.2 +Sphinx==4.5.0 +sphinx-rtd-theme==1.0.0 From 293ee266afdb7a881b94c04c918b4a435a2ea19c Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 18 Apr 2022 15:26:53 -0400 Subject: [PATCH 02/13] Add docker file build instructions and... - Remove readme.md files --- README.md | 334 --------------------------------- docs/source/building/build.rst | 8 +- docs/source/building/linux.rst | 73 ++++++- scripts/README.md | 53 ------ 4 files changed, 72 insertions(+), 396 deletions(-) delete mode 100644 README.md delete mode 100644 scripts/README.md diff --git a/README.md b/README.md deleted file mode 100644 index f062769e..00000000 --- a/README.md +++ /dev/null @@ -1,334 +0,0 @@ -# Introduction -Sunshine is a Gamestream host for Moonlight - -[![CI](https://github.com/SunshineStream/Sunshine/actions/workflows/CI.yml/badge.svg?branch=master)](https://github.com/SunshineStream/Sunshine/actions/workflows/CI.yml) -[![Downloads](https://img.shields.io/github/downloads/sunshinestream/sunshine/total)](https://github.com/sunshinestream/sunshine/releases) -[![Crowdin](https://badges.crowdin.net/sunshinestream/localized.svg)](https://crowdin.com/project/sunshinestream) - -- [Building](README.md#building) -- [Credits](README.md#credits) - -# Building -- [Linux](README.md#linux) -- [MacOS](README.md#macos) -- [Windows](README.md#windows-10) - -## Linux - -If you do not wish to clutter your PC with development files, yet you want the very latest version... -You can use these [build scripts](scripts/README.md) -They make use of docker to handle building Sunshine automatically - -### Requirements: - -#### Ubuntu 20.04: -Install the following: - -#### Common -``` -sudo apt install cmake gcc-10 g++-10 libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libevdev-dev -``` -#### X11 -``` -sudo apt install libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev -``` - -#### KMS -This requires additional [setup](README.md#Setup). -``` -sudo apt install libdrm-dev libcap-dev -``` - -#### Wayland -This is for wlroots based compositores, such as Sway -``` -sudo apt install libwayland-dev -``` - -#### Cuda + NvFBC -This requires proprietary software -On Ubuntu 20.04, the cuda compiler will fail since it's version is too old, it's recommended you compile the sources with the [build scripts](scripts/README.md) -``` -sudo apt install nvidia-cuda-dev nvidia-cuda-toolkit -``` - -#### Fedora 35: - -You will need some things in the RPMFusion repo, nost notably ffmpeg. -``` -sudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm -``` -#### Development tools and libraries -``` -sudo dnf install \ - boost-devel \ - boost-static.x86_64 \ - cmake \ - ffmpeg-devel \ - gcc-c++ \ - libevdev-devel \ - libxcb-devel \ - libX11-devel \ - libXcursor-devel \ - libXfixes-devel \ - libXinerama-devel \ - libXi-devel \ - libXrandr-devel \ - libXtst-devel \ - mesa-libGL-devel \ - openssl-devel \ - opus-devel \ - pulseaudio-libs-devel -``` -#### If you need to build an RPM binary package: -``` -sudo dnf install rpmbuild -``` - -#### Warning: -You might require ffmpeg version >= 4.3. Check the troubleshooting section for more information. - -### Compilation: - -#### Ubuntu -- `git clone https://github.com/SunshineStream/Sunshine.git --recurse-submodules` -- `cd sunshine && mkdir build && cd build` -- `cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 ..` -- `make -j ${nproc}` - -#### Fedora -- `git clone https://github.com/SunshineStream/Sunshine.git --recurse-submodules` -- `cd sunshine && mkdir build && cd build` -- `cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ ..` -- `make -j ${nproc}` - -### Setup: -sunshine needs access to uinput to create mouse and gamepad events: - -- Add user to group 'input': - `usermod -a -G input $USER` -- Create udev rules: - - Run the following command: - `nano /etc/udev/rules.d/85-sunshine-input.rules` - - Input the following contents: - `KERNEL=="uinput", GROUP="input", MODE="0660"` - - Save the file and exit - 1. `CTRL+X` to start exit - 2. `Y` to save modifications -- `assets/sunshine.conf` is an example configuration file. Modify it as you see fit, then use it by running: - `sunshine path/to/sunshine.conf` -- Configure autostart service - `path/to/build/dir/sunshine.service` is used to start sunshine in the background. To use it, do the following: - 1. Copy it to the users systemd, `cp sunshine.service ~/.config/systemd/user/` - 2. Starting - - Onetime: - `systemctl --user start sunshine` - - Always on boot: - `systemctl --user enable sunshine` - -- `assets/apps.json` is an [example](README.md#application-list) of a list of applications that are started just before running a stream - -#### Additional Setup for KMS: -Please note that `cap_sys_admin` may as well be root, except you don't need to be root to run it. -It's necessary to allow Sunshine to use KMS -- `sudo setcap cap_sys_admin+p sunshine` - -### Trouleshooting: -- If you get "Could not create Sunshine Gamepad: Permission Denied", ensure you are part of the group "input": - - `groups $USER` - -- If Sunshine sends audio from the microphone instead of the speaker, try the following steps: - 1. Check whether you're using Pulseaudio or Pipewire - - Pulseaudio: Use `pacmd list-sources | grep "name:"` - - Pipewire: Use `pactl info | grep Source`. In some causes you'd need to use the `sink` device. Try `pactl info | grep Sink`, if _Source_ doesn't work. - 2. Copy the name to the configuration option "audio_sink" - 3. Restart sunshine - -- If you get "Error: Failed to create client: Daemon not running", ensure that your avahi-daemon is running: - - `systemctl status avahi-daemon` - -- If you use hardware acceleration on Linux using an Intel or an AMD GPU (with VAAPI), you will get tons of [graphical issues](https://github.com/loki-47-6F-64/sunshine/issues/228) if your ffmpeg version is < 4.3. If it is not available in your distribution's repositories, consider using a newer version of your distribution. - - Ubuntu started to ship ffmpeg 4.3 starting with groovy (20.10). If you're using an older version, you could use [this PPA](https://launchpad.net/%7Esavoury1/+archive/ubuntu/ffmpeg4) instead of upgrading. **Using PPAs is dangerous and may break your system. Use it at your own risk.** - -## macOS - -### Quickstart - -- Install [MacPorts](https://www.macports.org) -- Download the `Portfile` from this repository to `/tmp` -- In a Terminal run `cd /tmp && sudo port install` -- Sunshine configuration is in `/opt/local/etc` -- Run `sunshine` to start the Sunshine server -- You will be asked to grant access to screen recording and your microphone to be able to stream it - -### Manuel Build - -#### Requirements: -macOS Big Sur and Xcode 12.5+: - -Either, using [MacPorts](https://www.macports.org), install the following -``` -sudo port install cmake boost libopus ffmpeg -``` - -Or, using [Homebrew](https://brew.sh), install the follwoing: -``` -brew install boost cmake ffmpeg libopusenc -# if there are issues with an SSL header that is not found: -cd /usr/local/include -ln -s ../opt/openssl/include/openssl . -``` - -#### Compilation: -- `git clone https://github.com/SunshineStream/Sunshine.git --recurse-submodules` -- `cd sunshine && mkdir build && cd build` -- `cmake ..` -- `make -j ${nproc}` - -If cmake fails complaining to find Boost, try to set the path explicitly: `cmake -DBOOST_ROOT=[boost path] ..`, e.g., `cmake -DBOOST_ROOT=/opt/local/libexec/boost/1.76 ..` - -### Setup: -- Sunshine can only access microphones on macOS due to system limitations. To stream system audio use [Soundflower](https://github.com/mattingalls/Soundflower) or [BlackHole](https://github.com/ExistentialAudio/BlackHole) and select their sink as audio device in `sunshine.conf` -- `assets/sunshine.conf` is an example configuration file. Modify it as you see fit, then use it by running: - `sunshine path/to/sunshine.conf` -- `assets/apps.json` is an [example](README.md#application-list) of a list of applications that are started just before running a stream - -### Usage & Limitations: -- Command Keys are not forwarded by Moonlight. Right Option-Key is mapped to CMD-Key. -- Gamepads are not supported - -## Windows 10 - -### Requirements: - -First you need to install [MSYS2](https://www.msys2.org), then startup "MSYS2 MinGW 64-bit" and install the following packages using `pacman -S`: - - mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git mingw-w64-x86_64-make cmake make gcc - -### Compilation: -- `git clone https://github.com/loki-47-6F-64/sunshine.git --recursive` -- `cd sunshine && mkdir build && cd build` -- `cmake -G"Unix Makefiles" ..` -- `mingw32-make` - -### Setup: -- **OPTIONAL** Gamepad support: Download and run 'ViGEmBus_Setup_1.16.116.exe' from [https://github.com/ViGEm/ViGEmBus/releases] - - - -# Common - -## Usage: -- run "sunshine path/to/sunshine.conf" -- If running for the first time, make sure to note the username and password Sunshine showed to you, since you **cannot get back later**! -- In Moonlight: Add PC manually -- When Moonlight request you insert the correct pin on sunshine: - - Type in the URL bar of your browser: `https://xxx.xxx.xxx.xxx:47990` where `xxx.xxx.xxx.xxx` is the IP address of your computer - - Ignore any warning given by your browser about "insecure website" - - You should compile the next page with a new username and a password, needed to login into the next step - - Press "Save" and log in using the credentials given above - - Go to "PIN" in the Header - - Type in your PIN and press Enter, you should get a Success Message -- Click on one of the Applications listed -- Have fun :) - -## Shortcuts: - -All shortcuts start with CTRL + ALT + SHIFT, just like Moonlight -- CTRL + ALT + SHIFT + N --> Hide/Unhide the cursor (This may be usefull for Remote Desktop Mode for Moonlight) -- CTRL + ALT + SHIFT + F1/F13 --> Switch to different monitor for Streaming - -## Credits: -- [loki-47-6F-64/sunshine](https://github.com/loki-47-6F-64/sunshine) (For all the hard work put in to create sunshine in the first place!) -- [Simple-Web-Server](https://gitlab.com/eidheim/Simple-Web-Server) -- [Moonlight](https://github.com/moonlight-stream) -- [Looking-Glass](https://github.com/gnif/LookingGlass) (For showing me how to properly capture frames on Windows, saving me a lot of time :) -- [Eretik](http://eretik.omegahg.com/) (For creating PolicyConfig.h, allowing me to change the default audio device on Windows programmatically) - -## Application List: -**Note:** You can change the Application List in the "Apps" section of the User Interface `https://xxx.xxx.xxx.xxx:47990/` -- You can use Environment variables in place of values - - $(HOME) will be replaced by the value of $HOME - - $$ will be replaced by $ --> $$(HOME) will be replaced by $(HOME) -- env: Adds or overwrites Environment variables for the commands/applications run by Sunshine. - - "Variable name":"Variable value" -- apps: The list of applications - - Example: - ```json - { - "name":"An App", - "cmd":"command to open app", - "prep-cmd":[ - { - "do":"some-command", - "undo":"undo-that-command" - } - ], - "detached":[ - "some-command", - "another-command" - ] - } - ``` - - name: Self explanatory - - output : The file where the output of the command is stored - - If it is not specified, the output is ignored - - detached: A list of commands to be run and forgotten about - - prep-cmd: A list of commands to be run before/after the application - - If any of the prep-commands fail, starting the application is aborted - - do: Run before the application - - If it fails, all 'undo' commands of the previously succeeded 'do' commands are run - - undo : Run after the application has terminated - - This should not fail considering it is supposed to undo the 'do' commands. - - If it fails, Sunshine is terminated - - cmd : The main application - - If not specified, a processs is started that sleeps indefinitely - -1. When an application is started, if there is an application already running, it will be terminated. -2. When the application has been shutdown, the stream shuts down as well. -3. In addition to the apps listed, one app "Desktop" is hardcoded into Sunshine. It does not start an application, instead it simply starts a stream. - -Linux -```json -{ - "env":{ - "DISPLAY":":0", - "DRI_PRIME":"1", - "XAUTHORITY":"$(HOME)/.Xauthority", - "PATH":"$(PATH):$(HOME)/.local/bin" - }, - "apps":[ - { - "name":"Low Res Desktop", - "prep-cmd":[ - { "do":"xrandr --output HDMI-1 --mode 1920x1080", "undo":"xrandr --output HDMI-1 --mode 1920x1200" } - ] - }, - { - "name":"Steam BigPicture", - - "output":"steam.txt", - "cmd":"steam -bigpicture", - "prep-cmd":[] - } - ] -} -``` -Windows -```json -{ - "env":{ - "PATH":"$(PATH);C:\\Program Files (x86)\\Steam" - }, - "apps":[ - { - "name":"Steam BigPicture", - - "output":"steam.txt", - "prep-cmd":[ - {"do":"steam \"steam://open/bigpicture\""} - ] - } - ] -} -``` diff --git a/docs/source/building/build.rst b/docs/source/building/build.rst index 556725fc..cd83cb14 100644 --- a/docs/source/building/build.rst +++ b/docs/source/building/build.rst @@ -17,10 +17,14 @@ Ensure `git `_ is installed and run the following: git clone https://github.com/sunshinestream/sunshine.git --recurse-submodules cd sunshine && mkdir build && cd build -Build -^^^^^ +Compile +^^^^^^^ See the section specific to your OS. +- :ref:`Linux ` +- :ref:`MacOS ` +- :ref:`Windows ` + Remote Build ------------ It may be beneficial to build remotely in some cases. This will enable easier building on different operating systems. diff --git a/docs/source/building/linux.rst b/docs/source/building/linux.rst index 9f87d588..15ff0001 100644 --- a/docs/source/building/linux.rst +++ b/docs/source/building/linux.rst @@ -6,10 +6,10 @@ Linux Requirements ------------ .. Warning:: Installing these dependencies may break your distribution. It is recommended to build in a virtual machine - or to use the Dockerfile builds located in the `./scripts` directory. + or to use the `Dockerfile builds`_ located in the `./scripts` directory. Debian Bullseye -""""""""""""""" +^^^^^^^^^^^^^^^ End of Life: TBD Install Requirements @@ -41,7 +41,7 @@ Install Requirements nvidia-cuda-toolkit \ # Cuda, NvFBC Fedora 35 -""""""""" +^^^^^^^^^ End of Life: TBD Install Repositories @@ -76,7 +76,7 @@ Install Requirements rpm-build \ # if you want to build an RPM binary package Ubuntu 18.04 -"""""""""""" +^^^^^^^^^^^^ End of Life: April 2028 Install Repositories @@ -141,7 +141,7 @@ Install CMake cmake --version Ubuntu 20.04 -"""""""""""" +^^^^^^^^^^^^ End of Life: April 2030 Install Requirements @@ -184,7 +184,7 @@ Install CuDA ./cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm ./cuda.run Ubuntu 21.10 -"""""""""""" +^^^^^^^^^^^^ End of Life: July 2022 Install Requirements @@ -216,7 +216,7 @@ Install Requirements nvidia-cuda-toolkit \ # Cuda, NvFBC Ubuntu 22.04 -"""""""""""" +^^^^^^^^^^^^ End of Life: April 2027 .. Todo:: Create Ubuntu 22.04 Dockerfile and complete this documentation. @@ -239,3 +239,62 @@ Finally .. code-block:: bash make -j ${nproc} + +Dockerfile Builds +----------------- +You may wish to simply build sunshine from source, without bloating your OS with development files. +There are scripts located in the ``./scripts`` directory that will create docker images that have the necessary +packages. As a result, removing the development files after you're done is a single command away. +These scripts use docker under the hood, as such, they can only be used to compile the Linux version + +.. Todo:: Publish the Dockerfiles to Dockerhub and ghcr. + +Requirements + Install `Docker `_ + +Instructions + #. :ref:`Clone `. Sunshine. + #. Select the desired Dockerfile from the ``./scripts`` directory. + + Available Files: + .. code-block:: text + + Dockerfile-debian + Dockerfile-fedora_33 # end of life + Dockerfile-fedora_35 + Dockerfile-ubuntu_18_04 + Dockerfile-ubuntu_20_04 + Dockerfile-ubuntu_21_04 # end of life + Dockerfile-ubuntu_21_10 + + #. Execute + + .. code-block:: bash + + cd scripts # move to the scripts directory + ./build-container.sh -f Dockerfile- # create the container (replace the "") + ./build-sunshine.sh -p -s .. # compile and build sunshine + + #. Updating + + .. code-block:: bash + + git pull # pull the latest changes from github + ./build-sunshine.sh -p -s .. # compile and build sunshine + + #. Optionally, delete the container + .. code-block:: bash + + ./build-container.sh -c delete + + #. Install the resulting package + + Debian + .. code-block:: bash + + sudo apt install -f sunshine-build/sunshine.deb + + Red Hat + .. code-block:: bash + + sudo rpm -i -f sunshine-build/sunshine.rpm diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100644 index 16e99ac8..00000000 --- a/scripts/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# Introduction -Sunshine is a Gamestream host for Moonlight - -[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/cgrtw2g3fq9b0b70/branch/master?svg=true)](https://ci.appveyor.com/project/loki-47-6F-64/sunshine/branch/master) -[![Downloads](https://img.shields.io/github/downloads/Loki-47-6F-64/sunshine/total)](https://github.com/Loki-47-6F-64/sunshine/releases) - -You may wish to simply build sunshine from source, without bloating your OS with development files. -These scripts will create a docker images that have the necessary packages. As a result, removing the development files after you're done is a single command away. -These scripts use docker under the hood, as such, they can only be used to compile the Linux version - - -#### Requirements - -``` -sudo apt install docker -``` - -#### instructions - -You'll require one of the following Dockerfiles: -* Dockerfile-2004 --> Ubuntu 20.04 -* Dockerfile-2104 --> Ubuntu 21.04 -* Dockerfile-debian --> Debian Bullseye - -Depending on your system, the build-* scripts may need root privilleges - -First, the docker container needs to be created: -``` -cd scripts -./build-container.sh -f Dockerfile- -``` - -Then, the sources will be compiled and the debian package generated: -``` -./build-sunshine.sh -p -s .. -``` -You can run `build-sunshine -p -s ..` again as long as the docker container exists. - -``` -git pull -./build-sunshine.sh -p -s .. -``` - -Optionally, the docker container can be removed after you're finished: -``` -./build-container.sh -c delete -``` - -Finally install the resulting package: -``` -sudo apt install -f sunshine-build/sunshine.deb -``` - From 56cf3e4ede97006e1aff652c61d42a82586e7b62 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 18 Apr 2022 16:05:17 -0400 Subject: [PATCH 03/13] Update admonitions --- docs/source/about/advanced_usage.rst | 4 ++-- docs/source/about/installation.rst | 2 +- docs/source/about/third_party_packages.rst | 4 +++- docs/source/about/usage.rst | 10 ++++++---- docs/source/building/linux.rst | 4 ++-- docs/source/building/macos.rst | 2 +- docs/source/building/windows.rst | 2 +- docs/source/contributing/localization.rst | 2 +- 8 files changed, 17 insertions(+), 13 deletions(-) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index e01de0aa..2a23e1bc 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -29,7 +29,7 @@ Example To manually configure sunshine you may edit the `conf` file in a text editor. Use the examples as reference. -.. Note:: Some settings are not available within the web ui. +.. Hint:: Some settings are not available within the web ui. General ------- @@ -166,7 +166,7 @@ Description .. Tip:: See `virtual key codes `_ - .. Note:: keybindings needs to have a multiple of two elements. + .. Hint:: keybindings needs to have a multiple of two elements. Default None diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 14ee37d2..70fd28c2 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -45,7 +45,7 @@ Red Hat Packages #. Download the corresponding `.rpm` file, e.g. ``sunshine-fedora_35.rpm`` #. ``sudo rpm -i ``, e.g. ``sudo rpm -i ./sunshine-fedora_35.rpm`` -.. Note:: If this is the first time installing. +.. Hint:: If this is the first time installing. .. code-block:: bash diff --git a/docs/source/about/third_party_packages.rst b/docs/source/about/third_party_packages.rst index 4f349abe..6069258a 100644 --- a/docs/source/about/third_party_packages.rst +++ b/docs/source/about/third_party_packages.rst @@ -3,7 +3,7 @@ Third Party Packages ==================== -.. Warning:: These packages are not maintained by SunshineStream. Use at your own risk. +.. Danger:: These packages are not maintained by SunshineStream. Use at your own risk. AUR (Arch Linux User Repository) -------------------------------- @@ -51,6 +51,8 @@ Scoop Legacy GitHub Repo ------------------ +.. Attention:: This repo is no longer maintained. Thank you to Loki for bringing this amazing project to life! + .. image:: https://img.shields.io/github/last-commit/loki-47-6F-64/sunshine?style=for-the-badge&logo=github :alt: GitHub last commit diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index 6eafe4b9..4bb81bc5 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -5,7 +5,9 @@ Usage #. See the `setup`_ section for your specific OS. #. Run ``sunshine /sunshine.conf``. - .. Note:: The configuration file specified will be created if it doesn't exist. + .. Note:: You do not need to specify a config file. If no config file is entered the default location will be used. + + .. Attention:: The configuration file specified will be created if it doesn't exist. .. Tip:: If using the Linux AppImage, replace ``sunshine`` with ``./sunshine.AppImage`` @@ -13,7 +15,7 @@ Usage The web ui is available on `https://localhost:47990 `_ by default. You may replace `localhost` with your internal ip address. - .. Tip:: Ignore any warning given by your browser about "insecure website". + .. Attention:: Ignore any warning given by your browser about "insecure website". .. Caution:: If running for the first time, make sure to note the username and password Sunshine showed to you, since you cannot get back later! @@ -24,7 +26,7 @@ Usage .. Note:: Additionally, apps can be configured manually. `assets/apps_.json` is an example of a list of applications that are started just before running a stream. - .. Note:: Application list is not fully supported on MacOS + .. Attention:: Application list is not fully supported on MacOS #. In Moonlight, you may need to add the PC manually. #. When Moonlight request you insert the correct pin on sunshine: @@ -38,7 +40,7 @@ Network ------- Sunshine will be available on port 47990 by default. -.. Warning:: Do not expose port 47990, or the web ui, to the internet! +.. Danger:: Do not expose port 47990, or the web ui, to the internet! Arguments --------- diff --git a/docs/source/building/linux.rst b/docs/source/building/linux.rst index 15ff0001..a519761a 100644 --- a/docs/source/building/linux.rst +++ b/docs/source/building/linux.rst @@ -5,7 +5,7 @@ Linux Requirements ------------ -.. Warning:: Installing these dependencies may break your distribution. It is recommended to build in a virtual machine +.. Danger:: Installing these dependencies may break your distribution. It is recommended to build in a virtual machine or to use the `Dockerfile builds`_ located in the `./scripts` directory. Debian Bullseye @@ -223,7 +223,7 @@ End of Life: April 2027 Build ----- -.. Caution:: Ensure you are in the build directory created during the clone step earlier before continuing. +.. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing. Debian based OSes .. code-block:: bash diff --git a/docs/source/building/macos.rst b/docs/source/building/macos.rst index c763ad12..97a58a23 100644 --- a/docs/source/building/macos.rst +++ b/docs/source/building/macos.rst @@ -28,7 +28,7 @@ Install Requirements Build ----- -.. Caution:: Ensure you are in the build directory created during the clone step earlier before continuing. +.. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing. .. code-block:: bash diff --git a/docs/source/building/windows.rst b/docs/source/building/windows.rst index 6a7c1e16..36cea881 100644 --- a/docs/source/building/windows.rst +++ b/docs/source/building/windows.rst @@ -14,7 +14,7 @@ following packages using: Build ----- -.. Caution:: Ensure you are in the build directory created during the clone step earlier before continuing. +.. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing. .. code-block:: batch diff --git a/docs/source/contributing/localization.rst b/docs/source/contributing/localization.rst index 6d0f6556..721ebc61 100644 --- a/docs/source/contributing/localization.rst +++ b/docs/source/contributing/localization.rst @@ -22,7 +22,7 @@ The translations occur on `CrowdIn `_. Feel free to contribute to localization there. Only elements of the API are planned to be translated. -.. Note:: The rest API has not yet been implemented. +.. Attention:: The rest API has not yet been implemented. Translations Basics - The brand names `SunshineStream` and `Sunshine` should never be translated. From eacae3954e21eaf8a7349ddc8223cd117c117828 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 18 Apr 2022 16:21:05 -0400 Subject: [PATCH 04/13] Fix typos --- DOCKER_README.md | 2 +- docs/source/about/advanced_usage.rst | 2 +- docs/source/about/installation.rst | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/DOCKER_README.md b/DOCKER_README.md index 8691e01b..aa2f1017 100644 --- a/DOCKER_README.md +++ b/DOCKER_README.md @@ -43,7 +43,7 @@ Create a `docker-compose.yml` file with the following contents (substitute your ```yaml version: '3' services: - retroarcher: + sunshine: image: sunshinestream/sunshine container_name: sunshine restart: unless-stopped diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index 2a23e1bc..4f427dcb 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -9,7 +9,7 @@ Configuration The default location for the configuration file is ``./assets/sunshine.conf``. You can use another location if you choose, by passing in the full configuration file path as the first argument when you start Sunshine. -** Default File Location** +**Default File Location** .. table:: :widths: auto diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 70fd28c2..12813cef 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -24,14 +24,14 @@ Linux AppImage ^^^^^^^^ -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:appimage?logo=github&style=for-the-badge' +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/pkg:appimage?logo=github&style=for-the-badge :alt: GitHub issues by-label #. Download and extract `sunshine-appimage.zip` Debian Packages ^^^^^^^^^^^^^^^ -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:debian?logo=github&style=for-the-badge' +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:debian?logo=github&style=for-the-badge :alt: GitHub issues by-label #. Download the corresponding `.deb` file, e.g. ``sunshine-ubuntu_20_04.deb`` @@ -39,7 +39,7 @@ Debian Packages Red Hat Packages ^^^^^^^^^^^^^^^^ -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:fedora?logo=github&style=for-the-badge' +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:linux:fedora?logo=github&style=for-the-badge :alt: GitHub issues by-label #. Download the corresponding `.rpm` file, e.g. ``sunshine-fedora_35.rpm`` @@ -60,7 +60,7 @@ Red Hat Packages MacOS ----- -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:macos?logo=github&style=for-the-badge' +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:macos?logo=github&style=for-the-badge :alt: GitHub issues by-label #. Install `MacPorts `_ @@ -71,10 +71,10 @@ MacOS Windows ------- -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:10?logo=github&style=for-the-badge' +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:10?logo=github&style=for-the-badge :alt: GitHub issues by-label -.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:11?logo=github&style=for-the-badge' +.. image:: https://img.shields.io/github/issues/sunshinestream/sunshine/os:windows:11?logo=github&style=for-the-badge :alt: GitHub issues by-label #. Download and extract ``sunshine-windows.zip`` From f36d81954baf724ea8d4f0b5f9d963ef03b971f9 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 19 Apr 2022 20:38:06 -0400 Subject: [PATCH 05/13] Update rpm install commands --- docs/source/about/installation.rst | 2 +- docs/source/building/linux.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index 12813cef..d2b9d877 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -43,7 +43,7 @@ Red Hat Packages :alt: GitHub issues by-label #. Download the corresponding `.rpm` file, e.g. ``sunshine-fedora_35.rpm`` -#. ``sudo rpm -i ``, e.g. ``sudo rpm -i ./sunshine-fedora_35.rpm`` +#. ``sudo dnf install ``, e.g. ``sudo dnf install ./sunshine-fedora_35.rpm`` .. Hint:: If this is the first time installing. diff --git a/docs/source/building/linux.rst b/docs/source/building/linux.rst index a519761a..a6e7eefb 100644 --- a/docs/source/building/linux.rst +++ b/docs/source/building/linux.rst @@ -297,4 +297,4 @@ Instructions Red Hat .. code-block:: bash - sudo rpm -i -f sunshine-build/sunshine.rpm + sudo dnf install sunshine-build/sunshine.rpm From 7a1e5f43d9e4fb622585af9809d8acf8e05bacdc Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 23 Apr 2022 12:35:39 -0400 Subject: [PATCH 06/13] Workflow updates - Do not re-run PR tests on edited PRs - Close added/fixed issues on published release - Issues stale after 60 days instead of 30, close after 10 days instead of 5 - Use Vankka/pr-target-branch-action for checking that PR is made to proper branch - Add version number to sphinx config, must use cmake to configure the file - Add jobs to readthedocs.yaml configuration --- .github/workflows/CI.yml | 2 +- .github/workflows/clang.yml | 2 +- .github/workflows/issues-closer.yml | 21 ++++++++++++++++++ .github/workflows/issues-stale.yml | 4 ++-- .github/workflows/pull-requests.yml | 34 +++++++++-------------------- .readthedocs.yaml | 5 +++++ CMakeLists.txt | 1 + docs/source/{conf.py => conf.py.in} | 4 ++-- 8 files changed, 43 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/issues-closer.yml rename docs/source/{conf.py => conf.py.in} (97%) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index da3cc6ed..b0be1130 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -3,7 +3,7 @@ name: CI on: pull_request: branches: [master, nightly] - types: [opened, synchronize, edited, reopened] + types: [opened, synchronize, reopened] push: branches: [master] workflow_dispatch: diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index f096c5f9..fd62d261 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -3,7 +3,7 @@ name: clang-format-lint on: pull_request: branches: [master, nightly] - types: [opened, synchronize, edited, reopened] + types: [opened, synchronize, reopened] jobs: lint: diff --git a/.github/workflows/issues-closer.yml b/.github/workflows/issues-closer.yml new file mode 100644 index 00000000..943bae37 --- /dev/null +++ b/.github/workflows/issues-closer.yml @@ -0,0 +1,21 @@ +name: Close Added/Fixed Issues + +on: + release: + types: [published] + +jobs: + close_issues: + name: Check Issues / PRs + runs-on: ubuntu-latest + steps: + - name: Close Issues (added/fixed) + uses: actions/stale@v3 + with: + only-issues-labels: 'added,fixed' + close-issue-message: > + This is now available in the latest release. + close-issue-label: 'released' + days-before-issue-stale: 0 + days-before-issue-close: 0 + ignore-updates: true diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml index 1c40c45d..233582bd 100644 --- a/.github/workflows/issues-stale.yml +++ b/.github/workflows/issues-stale.yml @@ -26,8 +26,8 @@ jobs: This PR was closed because it has been stalled for 5 days with no activity. stale-pr-label: 'stale' exempt-pr-labels: 'status:in-progress' - days-before-stale: 30 - days-before-close: 5 + days-before-stale: 60 + days-before-close: 10 - name: Invalid Template uses: actions/stale@v5 diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index ad014e6d..36f597b9 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -5,31 +5,17 @@ on: types: [opened, synchronize, edited, reopened] jobs: - check-branch: + check-pull-request: name: Check Pull Request runs-on: ubuntu-latest steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Branch check - if: ( github.head_ref == 'repo-sync/common-repo-files/default' && github.base_ref == 'master' ) || ( github.head_ref == 'nightly' && github.base_ref == 'master' ) - run: | - echo Base: "$GITHUB_BASE_REF" - echo Head: "$GITHUB_HEAD_REF" - echo "branch=True" >> $GITHUB_ENV - - - name: Comment on Pull Request - uses: mshick/add-pr-comment@v1 - if: github.base_ref != 'nightly' && env.branch != 'True' + - uses: Vankka/pr-target-branch-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - message: Pull requests must be made to the `nightly` branch. Thanks. - repo-token: ${{ secrets.GITHUB_TOKEN }} - repo-token-user-login: 'github-actions[bot]' - - - name: Fail Workflow - if: github.base_ref != 'nightly' && env.branch != 'True' - run: | - echo Base: "$GITHUB_BASE_REF" - echo Head: "$GITHUB_HEAD_REF" - exit 1 + target: master + exclude: nightly # Don't prevent going from nightly -> master + change-to: nightly + comment: | + Your PR was set to `master`, PRs should be sent to `nightly` + The base branch of this PR has been automatically changed to `nightly`, please check that there are no merge conflicts diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 78c304f8..5f62d86b 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,6 +10,11 @@ build: os: ubuntu-20.04 tools: python: "3.9" + jobs: + post_system_dependencies: + - apt-get install cmake + pre_create_environment: + - cmake .. # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/CMakeLists.txt b/CMakeLists.txt index 862e4f1a..dcbd6942 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -319,6 +319,7 @@ else() endif() configure_file(version.h.in version.h @ONLY) +configure_file(docs/source/conf.py.in "${CMAKE_CURRENT_SOURCE_DIR}/docs/source/conf.py" @ONLY) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(SUNSHINE_TARGET_FILES diff --git a/docs/source/conf.py b/docs/source/conf.py.in similarity index 97% rename from docs/source/conf.py rename to docs/source/conf.py.in index 0375d42b..99ce7143 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py.in @@ -26,7 +26,7 @@ copyright = f'{datetime.now ().year}, {project}' author = 'ReenigneArcher' # The full version, including alpha/beta/rc tags -# version = '0.13.0' +version = '@PROJECT_VERSION@' # -- General configuration --------------------------------------------------- @@ -60,7 +60,7 @@ source_suffix = ['.rst', '.md'] # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] -html_logo = os.path.join(root_dir, 'sunshine.ico') +html_logo = os.path.join(root_dir, 'sunshine.png') # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. From 4cd1014bacdf7f147d77cca5de2ebd374992b7cd Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 23 Apr 2022 12:40:54 -0400 Subject: [PATCH 07/13] Use apt_packages to install cmake --- .readthedocs.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 5f62d86b..c7d0ed50 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,11 +10,11 @@ build: os: ubuntu-20.04 tools: python: "3.9" + apt_packages: + - cmake jobs: - post_system_dependencies: - - apt-get install cmake - pre_create_environment: - - cmake .. + pre_build: + - cmake -Wno-dev . # Build documentation in the docs/ directory with Sphinx sphinx: From b332633b074c9d7ec02b227438af7bcc2f4e4483 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 23 Apr 2022 13:09:18 -0400 Subject: [PATCH 08/13] Add submodules --- .readthedocs.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index c7d0ed50..ad158de4 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -14,7 +14,12 @@ build: - cmake jobs: pre_build: - - cmake -Wno-dev . + - cmake . + +# Include the submodules, required for cmake +submodules: + include: all + recursive: true # Build documentation in the docs/ directory with Sphinx sphinx: From 780339d91bddf923130ea80396fa8739307f369a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 23 Apr 2022 13:16:21 -0400 Subject: [PATCH 09/13] Add boost dependencies --- .readthedocs.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index ad158de4..87870397 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -12,6 +12,9 @@ build: python: "3.9" apt_packages: - cmake + - libboost-filesystem-dev + - libboost-log-dev + - libboost-thread-dev jobs: pre_build: - cmake . From 521335c387d68e58c85572278c95ed1798be0fe5 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 23 Apr 2022 16:20:06 -0400 Subject: [PATCH 10/13] Add ffmpeg dependency --- .readthedocs.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 87870397..a5997812 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -12,6 +12,7 @@ build: python: "3.9" apt_packages: - cmake + - ffmpeg - libboost-filesystem-dev - libboost-log-dev - libboost-thread-dev From b286c061449715aef215ad8b47d895bfe612ef2e Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 23 Apr 2022 20:50:42 -0400 Subject: [PATCH 11/13] Fix localize `git diff` and `git reset` steps --- .github/workflows/localize.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index b860ad64..a017efab 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -4,6 +4,7 @@ on: push: branches: [nightly] paths: # prevents workflow from running unless these files change + - '.github/workflows/localize.yml' - 'sunshine/**' - 'locale/sunshine.po' workflow_dispatch: @@ -40,15 +41,18 @@ jobs: - name: git diff run: | + # disable the pager + git config --global pager.diff false + # print the git diff - git diff --exit-code locale/sunshine.po + git diff locale/sunshine.po # set the variable with minimal output - OUTPUT=$(git diff --exit-code --numstat locale/sunshine.po) + OUTPUT=$(git diff --numstat locale/sunshine.po) echo "git_diff=${OUTPUT}" >> $GITHUB_ENV - name: git reset - if: ${{ env.git_diff != '1 1 locale/sunshine.po' }} # only run if more than 1 line changed + if: ${{ env.git_diff == '1 1 locale/sunshine.po' }} # only run if more than 1 line changed run: | git reset --hard From ef9abf2f159917c55e3f87ec54709351cdd77525 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 28 Apr 2022 18:06:55 -0400 Subject: [PATCH 12/13] Get version number from CMakeLists --- .github/workflows/issues-closer.yml | 21 -------------------- .readthedocs.yaml | 30 ++++++++++++++++------------- CMakeLists.txt | 1 - docs/source/{conf.py.in => conf.py} | 16 +++++++++++---- 4 files changed, 29 insertions(+), 39 deletions(-) delete mode 100644 .github/workflows/issues-closer.yml rename docs/source/{conf.py.in => conf.py} (84%) diff --git a/.github/workflows/issues-closer.yml b/.github/workflows/issues-closer.yml deleted file mode 100644 index 943bae37..00000000 --- a/.github/workflows/issues-closer.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Close Added/Fixed Issues - -on: - release: - types: [published] - -jobs: - close_issues: - name: Check Issues / PRs - runs-on: ubuntu-latest - steps: - - name: Close Issues (added/fixed) - uses: actions/stale@v3 - with: - only-issues-labels: 'added,fixed' - close-issue-message: > - This is now available in the latest release. - close-issue-label: 'released' - days-before-issue-stale: 0 - days-before-issue-close: 0 - ignore-updates: true diff --git a/.readthedocs.yaml b/.readthedocs.yaml index a5997812..762371f8 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,20 +10,24 @@ build: os: ubuntu-20.04 tools: python: "3.9" - apt_packages: - - cmake - - ffmpeg - - libboost-filesystem-dev - - libboost-log-dev - - libboost-thread-dev - jobs: - pre_build: - - cmake . -# Include the submodules, required for cmake -submodules: - include: all - recursive: true +## apt packages required packages to run cmake on sunshine, note that additional packages are required +# apt_packages: +# - cmake +# - ffmpeg +# - libboost-filesystem-dev +# - libboost-log-dev +# - libboost-thread-dev + +## run cmake +# jobs: +# pre_build: +# - cmake . + +## Include the submodules, required for cmake +#submodules: +# include: all +# recursive: true # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/CMakeLists.txt b/CMakeLists.txt index dcbd6942..862e4f1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -319,7 +319,6 @@ else() endif() configure_file(version.h.in version.h @ONLY) -configure_file(docs/source/conf.py.in "${CMAKE_CURRENT_SOURCE_DIR}/docs/source/conf.py" @ONLY) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(SUNSHINE_TARGET_FILES diff --git a/docs/source/conf.py.in b/docs/source/conf.py similarity index 84% rename from docs/source/conf.py.in rename to docs/source/conf.py index 99ce7143..86b7b805 100644 --- a/docs/source/conf.py.in +++ b/docs/source/conf.py @@ -6,6 +6,8 @@ # standard imports from datetime import datetime +import os +import re # -- Path setup -------------------------------------------------------------- @@ -13,8 +15,6 @@ from datetime import datetime # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -import os -# import sys script_dir = os.path.dirname(os.path.abspath(__file__)) # the directory of this file source_dir = os.path.dirname(script_dir) # the source folder directory @@ -26,8 +26,16 @@ copyright = f'{datetime.now ().year}, {project}' author = 'ReenigneArcher' # The full version, including alpha/beta/rc tags -version = '@PROJECT_VERSION@' - +with open(os.path.join(root_dir, 'CMakeLists.txt'), 'r') as f: + version = re.search(r"project\(Sunshine VERSION ((\d+)\.(\d+)\.(\d+))\)", str(f.read())).group(1) +""" +To use cmake method for obtaining version instead of regex, +1. Within CMakeLists.txt add the following line without backticks: + ``configure_file(docs/source/conf.py.in "${CMAKE_CURRENT_SOURCE_DIR}/docs/source/conf.py" @ONLY)`` +2. Rename this file to ``conf.py.in`` +3. Uncomment the next line +""" +# version = '@PROJECT_VERSION@' # use this for cmake configure_file method # -- General configuration --------------------------------------------------- From 734400dc779de3ff8c2595e192b73d478f0e2289 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 28 Apr 2022 18:20:53 -0400 Subject: [PATCH 13/13] Remove white background from png logo --- sunshine.png | Bin 14699 -> 20640 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/sunshine.png b/sunshine.png index 77f4795673e5bc0e5c828e4b37f26a6d8c05105d..785bc479b9655ce9dd158be2f045c25d9e53f137 100644 GIT binary patch literal 20640 zcmZ5nRa{h08(vaa>29Q?Q@XpQyIVR0X;`|uOF&9eLZo5I1w^E!8Fffeb&267Yu@qSI1Li)3f9-=8zEzyrsxq!s{-J zz{bN%dkh<`6S_vg@9zBXk513ldxPvHyVlM&db>HV>#hrpg{g32hqgWce@JPIxw52A zdE2}wy~;g}gmy4yFgLS_jYZbY3jQU zn5gLqdN!sR`jObQdpp_?phtI{T1chVPf(u$@O39RX|yQ~xPwh|t@J3vaV53yi24<< z!rFTEnMF_OFHu-H0!3nnPe;9ADPzri4#slg|>5|xTD+BBs3jv`qqWNqemP%EWSkQcd!O-E5< zB+F7j>qP zCX%KcQCs00!1Fm093=7CAa&{67|K~y=}MqC{*9{#RYRF9iD0O;#xz_UZ>z1k<>(ix zB}=qlEyrWJAd`!FAZ~F2J|b`Y#?p7WQSDk^v**HO+(S*8h`gs5o+? z4LP$zbWLwpT5(K{_4n$b&W20#woUm+rKT)uF)~aF_>8=4z#BY%>&GHrEM14=FIAW` zOBH$QkGFaOO=!m7b;C#Z_M!oOni9S{+GR58qlHp*1faxFJmp+Jn!4I_3go;3HLc4q!v z3Z(uKGg{5oRKbzyLBiIaj8;@@($AvYUzxFW@r?oZ%acuksSd18UX<=HA7g3h@xYHY55> zeI88^`!NK>7A3*56vDbHAb=@@Xa9yA)0pB4h&4o>46aL&pcxt~x77dlJBKDlunwaL z+0lM_uIzXPxeee@J3VA{*Y{Y<@9=)%>*>t~M@)BNR zITd4A;RCo&wRu4w}^$F z6B4ZGg7cPh7N1>znBqN3Ne4mRkaf(BBIs;b6L9eZDt!(iz&=}O8{BG)Nq z+iRSK1&ion=35x(_4=cf_Qk)tjil4pm)7QL9ym&d>iq);vD1iSdr6 z`+Kv6%7j3Up93DH8&b4|PLQS61ww`NMx+bMT_<9}I%V+d3l?FQ6|)%5=78W5THqAJwH(82)nU`Tiw1=n=sT#de zTi-yILuucol8^bzh@p8%0xknpuu98puDde#d

Ham7aEv9?}_6 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 tMkvIoS9c?+KHU?(UL