Compare commits

...

22 Commits

Author SHA1 Message Date
ReenigneArcher
5b863f760b feat(api): add openapi specification 2025-02-01 10:07:42 -05:00
ReenigneArcher
257a102127 fix(api): return proper json objects (#3544)
Some checks failed
CI / GitHub Env Debug (push) Waiting to run
CI / Setup Release (push) Waiting to run
CI / Setup Flatpak Matrix (push) Waiting to run
CI / Linux Flatpak (push) Blocked by required conditions
CI / Linux ${{ matrix.type }} (--appimage-build, 22.04, AppImage) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 13) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 14) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest, true) (push) Blocked by required conditions
CI / Windows (push) Blocked by required conditions
CI Docker / Check Dockerfiles (push) Waiting to run
CI Docker / Setup Release (push) Blocked by required conditions
CI Docker / Docker${{ matrix.tag }} (push) Blocked by required conditions
CodeQL / Get language matrix (push) Waiting to run
CodeQL / Analyze (${{ matrix.name }}) (push) Blocked by required conditions
Build GH-Pages / prep (push) Waiting to run
Build GH-Pages / call-jekyll-build (push) Blocked by required conditions
localize / Update Localization (push) Has been cancelled
2025-01-31 23:38:22 -05:00
VMFortress
5b36357133 feat(display): Add revert display config on disconnect option (#3613)
Some checks failed
CI / GitHub Env Debug (push) Has been cancelled
CI / Setup Release (push) Has been cancelled
CI / Setup Flatpak Matrix (push) Has been cancelled
CI Docker / Check Dockerfiles (push) Has been cancelled
CodeQL / Get language matrix (push) Has been cancelled
localize / Update Localization (push) Has been cancelled
Build GH-Pages / prep (push) Has been cancelled
CI / Linux Flatpak (push) Has been cancelled
CI / Linux ${{ matrix.type }} (--appimage-build, 22.04, AppImage) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 13) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 14) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest, true) (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI Docker / Setup Release (push) Has been cancelled
CI Docker / Docker${{ matrix.tag }} (push) Has been cancelled
CodeQL / Analyze (${{ matrix.name }}) (push) Has been cancelled
Build GH-Pages / call-jekyll-build (push) Has been cancelled
2025-01-30 16:02:22 -05:00
dependabot[bot]
ce28e36a47 build(deps): bump third-party/wayland-protocols from 6bcf87d to c7b582c (#3611)
build(deps): bump third-party/wayland-protocols

Bumps third-party/wayland-protocols from `6bcf87d` to `c7b582c`.

---
updated-dependencies:
- dependency-name: third-party/wayland-protocols
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-30 14:05:30 -05:00
Lukas Senionis
23e131439f fix(video): allow encoder probing when there are no devices at all (#3594)
Some checks failed
CI / GitHub Env Debug (push) Has been cancelled
CI / Setup Release (push) Has been cancelled
CI / Setup Flatpak Matrix (push) Has been cancelled
CI Docker / Check Dockerfiles (push) Has been cancelled
CodeQL / Get language matrix (push) Has been cancelled
localize / Update Localization (push) Has been cancelled
Build GH-Pages / prep (push) Has been cancelled
CI / Linux Flatpak (push) Has been cancelled
CI / Linux ${{ matrix.type }} (--appimage-build, 22.04, AppImage) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 13) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 14) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest, true) (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI Docker / Setup Release (push) Has been cancelled
CI Docker / Docker${{ matrix.tag }} (push) Has been cancelled
CodeQL / Analyze (${{ matrix.name }}) (push) Has been cancelled
Build GH-Pages / call-jekyll-build (push) Has been cancelled
2025-01-27 12:19:47 -05:00
dependabot[bot]
24cce3a666 build(deps): bump third-party/libdisplaydevice from 2ac3386 to 53a0ea5 (#3597)
build(deps): bump third-party/libdisplaydevice

Bumps [third-party/libdisplaydevice](https://github.com/LizardByte/libdisplaydevice) from `2ac3386` to `53a0ea5`.
- [Release notes](https://github.com/LizardByte/libdisplaydevice/releases)
- [Commits](2ac338626c...53a0ea5317)

---
updated-dependencies:
- dependency-name: third-party/libdisplaydevice
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-27 08:37:18 -05:00
Lukas Senionis
0631472533 fix(video): prevent encoder probing with no active displays (#3592)
Some checks are pending
CI / GitHub Env Debug (push) Waiting to run
CI / Setup Release (push) Waiting to run
CI / Setup Flatpak Matrix (push) Waiting to run
CI / Linux Flatpak (push) Blocked by required conditions
CI / Linux ${{ matrix.type }} (--appimage-build, 22.04, AppImage) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 13) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 14) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest, true) (push) Blocked by required conditions
CI / Windows (push) Blocked by required conditions
CI Docker / Check Dockerfiles (push) Waiting to run
CI Docker / Setup Release (push) Blocked by required conditions
CI Docker / Docker${{ matrix.tag }} (push) Blocked by required conditions
CodeQL / Get language matrix (push) Waiting to run
CodeQL / Analyze (${{ matrix.name }}) (push) Blocked by required conditions
localize / Update Localization (push) Waiting to run
Build GH-Pages / prep (push) Waiting to run
Build GH-Pages / call-jekyll-build (push) Blocked by required conditions
2025-01-26 18:21:15 -05:00
ReenigneArcher
a5c791658e build(deps): bump wayland-protocols from 1.38 to 1.39 (#3482)
Some checks failed
CI / GitHub Env Debug (push) Has been cancelled
CI / Setup Release (push) Has been cancelled
CI / Setup Flatpak Matrix (push) Has been cancelled
CI Docker / Check Dockerfiles (push) Has been cancelled
CodeQL / Get language matrix (push) Has been cancelled
Build GH-Pages / prep (push) Has been cancelled
CI / Linux Flatpak (push) Has been cancelled
CI / Linux ${{ matrix.type }} (--appimage-build, 22.04, AppImage) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 13) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 14) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest, true) (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI Docker / Setup Release (push) Has been cancelled
CI Docker / Docker${{ matrix.tag }} (push) Has been cancelled
CodeQL / Analyze (${{ matrix.name }}) (push) Has been cancelled
Build GH-Pages / call-jekyll-build (push) Has been cancelled
2025-01-24 16:37:17 -05:00
ReenigneArcher
a513acc16b ci(packaging): fix flatpak metainfo (#3588)
Some checks are pending
CI / GitHub Env Debug (push) Waiting to run
CI / Setup Release (push) Waiting to run
CI / Setup Flatpak Matrix (push) Waiting to run
CI / Linux Flatpak (push) Blocked by required conditions
CI / Linux ${{ matrix.type }} (--appimage-build, 22.04, AppImage) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 13) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 14) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest, true) (push) Blocked by required conditions
CI / Windows (push) Blocked by required conditions
CI Docker / Check Dockerfiles (push) Waiting to run
CI Docker / Setup Release (push) Blocked by required conditions
CI Docker / Docker${{ matrix.tag }} (push) Blocked by required conditions
CodeQL / Get language matrix (push) Waiting to run
CodeQL / Analyze (${{ matrix.name }}) (push) Blocked by required conditions
Build GH-Pages / prep (push) Waiting to run
Build GH-Pages / call-jekyll-build (push) Blocked by required conditions
2025-01-24 15:36:50 +00:00
Cody Maness
64544e7960 fix(httpcommon): sonarqube warning cleanup (#3558)
Some checks failed
CI / GitHub Env Debug (push) Has been cancelled
CI / Setup Release (push) Has been cancelled
CI / Setup Flatpak Matrix (push) Has been cancelled
CI Docker / Check Dockerfiles (push) Has been cancelled
CodeQL / Get language matrix (push) Has been cancelled
localize / Update Localization (push) Has been cancelled
Build GH-Pages / prep (push) Has been cancelled
CI / Linux Flatpak (push) Has been cancelled
CI / Linux ${{ matrix.type }} (--appimage-build, 22.04, AppImage) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 13) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 14) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest, true) (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI Docker / Setup Release (push) Has been cancelled
CI Docker / Docker${{ matrix.tag }} (push) Has been cancelled
CodeQL / Analyze (${{ matrix.name }}) (push) Has been cancelled
Build GH-Pages / call-jekyll-build (push) Has been cancelled
Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
2025-01-22 09:16:14 -05:00
LizardByte-bot
2a31ee5422 chore(l10n): update translations (#3563)
Some checks are pending
CI / GitHub Env Debug (push) Waiting to run
CI / Setup Release (push) Waiting to run
CI / Setup Flatpak Matrix (push) Waiting to run
CI / Linux Flatpak (push) Blocked by required conditions
CI / Linux ${{ matrix.type }} (--appimage-build, 22.04, AppImage) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 13) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 14) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest, true) (push) Blocked by required conditions
CI / Windows (push) Blocked by required conditions
CI Docker / Check Dockerfiles (push) Waiting to run
CI Docker / Setup Release (push) Blocked by required conditions
CI Docker / Docker${{ matrix.tag }} (push) Blocked by required conditions
CodeQL / Get language matrix (push) Waiting to run
CodeQL / Analyze (${{ matrix.name }}) (push) Blocked by required conditions
Build GH-Pages / prep (push) Waiting to run
Build GH-Pages / call-jekyll-build (push) Blocked by required conditions
2025-01-22 02:45:44 +00:00
dependabot[bot]
8263d8976f build(deps): bump packaging/linux/flatpak/deps/shared-modules from f5d368a to 26def5f (#3568)
build(deps): bump packaging/linux/flatpak/deps/shared-modules

Bumps [packaging/linux/flatpak/deps/shared-modules](https://github.com/flathub/shared-modules) from `f5d368a` to `26def5f`.
- [Commits](f5d368a31d...26def5f1d2)

---
updated-dependencies:
- dependency-name: packaging/linux/flatpak/deps/shared-modules
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-21 20:38:20 -05:00
ReenigneArcher
eb6916ef34 fix(web-ui): fix new version notification conditions (#3577)
Some checks are pending
CI / GitHub Env Debug (push) Waiting to run
CI / Setup Release (push) Waiting to run
CI / Setup Flatpak Matrix (push) Waiting to run
CI / Linux Flatpak (push) Blocked by required conditions
CI / Linux ${{ matrix.type }} (--appimage-build, 22.04, AppImage) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 13) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 14) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest, true) (push) Blocked by required conditions
CI / Windows (push) Blocked by required conditions
CI Docker / Check Dockerfiles (push) Waiting to run
CI Docker / Setup Release (push) Blocked by required conditions
CI Docker / Docker${{ matrix.tag }} (push) Blocked by required conditions
CodeQL / Get language matrix (push) Waiting to run
CodeQL / Analyze (${{ matrix.name }}) (push) Blocked by required conditions
Build GH-Pages / prep (push) Waiting to run
Build GH-Pages / call-jekyll-build (push) Blocked by required conditions
2025-01-22 00:46:04 +00:00
dependabot[bot]
5af21bde88 build(deps): bump third-party/tray from e80058b to d45306e (#3574)
Some checks are pending
CI / GitHub Env Debug (push) Waiting to run
CI / Setup Release (push) Waiting to run
CI / Setup Flatpak Matrix (push) Waiting to run
CI / Linux Flatpak (push) Blocked by required conditions
CI / Linux ${{ matrix.type }} (--appimage-build, 22.04, AppImage) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 13) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 14) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest, true) (push) Blocked by required conditions
CI / Windows (push) Blocked by required conditions
CI Docker / Check Dockerfiles (push) Waiting to run
CI Docker / Setup Release (push) Blocked by required conditions
CI Docker / Docker${{ matrix.tag }} (push) Blocked by required conditions
CodeQL / Get language matrix (push) Waiting to run
CodeQL / Analyze (${{ matrix.name }}) (push) Blocked by required conditions
Build GH-Pages / prep (push) Waiting to run
Build GH-Pages / call-jekyll-build (push) Blocked by required conditions
Bumps [third-party/tray](https://github.com/LizardByte/tray) from `e80058b` to `d45306e`.
- [Commits](e80058b9a9...d45306e686)

---
updated-dependencies:
- dependency-name: third-party/tray
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-21 09:15:26 -05:00
dependabot[bot]
31866fde35 build(deps): bump third-party/libdisplaydevice from 63599b0 to 1975f75 (#3576)
build(deps): bump third-party/libdisplaydevice

Bumps [third-party/libdisplaydevice](https://github.com/LizardByte/libdisplaydevice) from `63599b0` to `1975f75`.
- [Release notes](https://github.com/LizardByte/libdisplaydevice/releases)
- [Commits](63599b0765...1975f75add)

---
updated-dependencies:
- dependency-name: third-party/libdisplaydevice
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-21 09:14:58 -05:00
dependabot[bot]
299b12347f build(deps): bump third-party/doxyconfig from 4c94524 to 4501c7b (#3573)
Some checks are pending
CI / GitHub Env Debug (push) Waiting to run
CI / Setup Release (push) Waiting to run
CI / Setup Flatpak Matrix (push) Waiting to run
CI / Linux Flatpak (push) Blocked by required conditions
CI / Linux ${{ matrix.type }} (--appimage-build, 22.04, AppImage) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 13) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 14) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest, true) (push) Blocked by required conditions
CI / Windows (push) Blocked by required conditions
CI Docker / Check Dockerfiles (push) Waiting to run
CI Docker / Setup Release (push) Blocked by required conditions
CI Docker / Docker${{ matrix.tag }} (push) Blocked by required conditions
CodeQL / Get language matrix (push) Waiting to run
CodeQL / Analyze (${{ matrix.name }}) (push) Blocked by required conditions
Build GH-Pages / prep (push) Waiting to run
Build GH-Pages / call-jekyll-build (push) Blocked by required conditions
Bumps [third-party/doxyconfig](https://github.com/LizardByte/doxyconfig) from `4c94524` to `4501c7b`.
- [Commits](4c9452482b...4501c7b191)

---
updated-dependencies:
- dependency-name: third-party/doxyconfig
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-21 05:06:24 +00:00
LizardByte-bot
2ac87fdc36 chore: update global workflows (#3570) 2025-01-21 02:30:06 +00:00
dependabot[bot]
a88c01f3b8 build(deps): bump third-party/tray from ebbd14f to e80058b (#3567)
Bumps [third-party/tray](https://github.com/LizardByte/tray) from `ebbd14f` to `e80058b`.
- [Commits](ebbd14fe6a...e80058b9a9)

---
updated-dependencies:
- dependency-name: third-party/tray
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-20 20:19:36 -05:00
ReenigneArcher
f5b923c406 fix(flatpak): fix broken desktop file, icons, and service (#3561)
Some checks failed
CI / GitHub Env Debug (push) Waiting to run
CI / Setup Release (push) Waiting to run
CI / Setup Flatpak Matrix (push) Waiting to run
CI / Linux Flatpak (push) Blocked by required conditions
CI / Linux ${{ matrix.type }} (--appimage-build, 22.04, AppImage) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 13) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 14) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest, true) (push) Blocked by required conditions
CI / Windows (push) Blocked by required conditions
CI Docker / Check Dockerfiles (push) Waiting to run
CI Docker / Setup Release (push) Blocked by required conditions
CI Docker / Docker${{ matrix.tag }} (push) Blocked by required conditions
CodeQL / Get language matrix (push) Waiting to run
CodeQL / Analyze (${{ matrix.name }}) (push) Blocked by required conditions
Build GH-Pages / prep (push) Waiting to run
Build GH-Pages / call-jekyll-build (push) Blocked by required conditions
localize / Update Localization (push) Has been cancelled
2025-01-21 00:15:23 +00:00
ReenigneArcher
3048e6fe20 docs(guides): fix blog url (#3564)
Some checks are pending
CI / GitHub Env Debug (push) Waiting to run
CI / Setup Release (push) Waiting to run
CI / Setup Flatpak Matrix (push) Waiting to run
CI / Linux Flatpak (push) Blocked by required conditions
CI / Linux ${{ matrix.type }} (--appimage-build, 22.04, AppImage) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 13) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 14) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest, true) (push) Blocked by required conditions
CI / Windows (push) Blocked by required conditions
CI Docker / Check Dockerfiles (push) Waiting to run
CI Docker / Setup Release (push) Blocked by required conditions
CI Docker / Docker${{ matrix.tag }} (push) Blocked by required conditions
CodeQL / Get language matrix (push) Waiting to run
CodeQL / Analyze (${{ matrix.name }}) (push) Blocked by required conditions
localize / Update Localization (push) Waiting to run
Build GH-Pages / prep (push) Waiting to run
Build GH-Pages / call-jekyll-build (push) Blocked by required conditions
2025-01-20 04:35:57 +00:00
ReenigneArcher
c2420427b1 style: adjust clang-format rules (#2186)
Co-authored-by: Vithorio Polten <reach@vithor.io>
2025-01-19 22:34:47 -05:00
ReenigneArcher
f57aee9025 build(linux)!: remove legacy input option (#3562)
Some checks are pending
CI / GitHub Env Debug (push) Waiting to run
CI / Setup Release (push) Waiting to run
CI / Setup Flatpak Matrix (push) Waiting to run
CI / Linux Flatpak (push) Blocked by required conditions
CI / Linux ${{ matrix.type }} (--appimage-build, 22.04, AppImage) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 13) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 14) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest) (push) Blocked by required conditions
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest, true) (push) Blocked by required conditions
CI / Windows (push) Blocked by required conditions
CI Docker / Check Dockerfiles (push) Waiting to run
CI Docker / Setup Release (push) Blocked by required conditions
CI Docker / Docker${{ matrix.tag }} (push) Blocked by required conditions
CodeQL / Get language matrix (push) Waiting to run
CodeQL / Analyze (${{ matrix.name }}) (push) Blocked by required conditions
localize / Update Localization (push) Waiting to run
Build GH-Pages / prep (push) Waiting to run
Build GH-Pages / call-jekyll-build (push) Blocked by required conditions
2025-01-19 12:18:08 -05:00
207 changed files with 9817 additions and 13121 deletions

View File

@@ -6,27 +6,34 @@
# Generated from CLion C/C++ Code Style settings
BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: false
AlignAfterOpenBracket: BlockIndent
AlignConsecutiveAssignments: None
AlignEscapedNewlines: DontAlign
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLambdasOnASingleLine: All
AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: None
AllowShortLoopsOnASingleLine: true
AlignTrailingComments: false
AlwaysBreakAfterReturnType: All
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: MultiLine
BreakBeforeBraces: Custom
BinPackArguments: false
BinPackParameters: false
BracedInitializerIndentWidth: 2
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: true
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
@@ -36,39 +43,75 @@ BraceWrapping:
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
BreakArrays: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: AfterColon
BreakInheritanceList: AfterColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 2
Cpp11BracedListStyle: true
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: Always
ExperimentalAutoDetectBinPacking: true
FixNamespaceComments: true
IncludeBlocks: Regroup
IndentAccessModifiers: false
IndentCaseBlocks: true
IndentCaseLabels: true
IndentExternBlock: Indent
IndentGotoLabels: true
IndentPPDirectives: BeforeHash
IndentWidth: 2
IndentWrappedFunctionNames: true
InsertBraces: true
InsertNewlineAtEOF: true
KeepEmptyLinesAtTheStartOfBlocks: false
LineEnding: LF
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
ObjCBinPackProtocolList: Never
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: Never
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 1
PenaltyBreakString: 1
PenaltyBreakFirstLessLess: 0
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 100000000
PointerAlignment: Right
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RemoveSemicolon: false
SeparateDefinitionBlocks: Always
SortIncludes: CaseInsensitive
SortUsingDeclarations: Lexicographic
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: false
SpaceBeforeInheritanceColon: false
SpaceBeforeJsonColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: Never
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInLineCommentPrefix:
Maximum: 3
Minimum: 1
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 2
Cpp11BracedListStyle: false
UseTab: Never

View File

@@ -887,7 +887,6 @@ jobs:
mingw-w64-ucrt-x86_64-graphviz
mingw-w64-ucrt-x86_64-MinHook
mingw-w64-ucrt-x86_64-miniupnpc
mingw-w64-ucrt-x86_64-nlohmann-json
mingw-w64-ucrt-x86_64-nodejs
mingw-w64-ucrt-x86_64-nsis
mingw-w64-ucrt-x86_64-onevpl

View File

@@ -80,13 +80,14 @@ jobs:
month_day=$(printf "%04d" "$month_day")
# create the filename
file_name="_posts/releases/${year}-${month_day:0:2}-${month_day:2:2}-v${semver}.md"
file_name="_posts/releases/${repo_lower}/${year}-${month_day:0:2}-${month_day:2:2}-v${semver}.md"
mkdir -p "$(dirname "${file_name}")"
# create jekyll blog post
echo "---" > "${file_name}"
echo "layout: release" >> "${file_name}"
echo "title: ${{ github.event.repository.name }} ${tag_name} Released" >> "${file_name}"
echo "release-tag: ${tag_name}" >> "${file_name}"
echo "gh-repo: ${{ github.repository }}" >> "${file_name}"
echo "gh-badge: [follow, fork, star]" >> "${file_name}"
echo "tags: [release, ${repo_lower}]" >> "${file_name}"

View File

@@ -81,9 +81,16 @@ jobs:
--arg description "${{ github.event.repository.description }}" \
'{default_branch: $default_branch}')
# change the default branch to the latest release
curl \
-X PATCH \
-H "Authorization: Token ${RTD_TOKEN}" \
https://readthedocs.org/api/v3/projects/${RTD_SLUG}/ \
-H "Content-Type: application/json" \
https://readthedocs.org/api/v3/projects/${RTD_SLUG}/ \
-d "$json_body"
# trigger a build for the latest version
curl \
-X POST \
-H "Authorization: Token ${RTD_TOKEN}" \
https://readthedocs.org/api/v3/projects/${RTD_SLUG}/versions/latest/builds/

View File

@@ -143,9 +143,9 @@ jobs:
BEGIN { replaced = 0 }
/<release version=.*>/ {
if (!replaced) {
print "<release version=\"" version "\" date=\"" date "\">"
print "<description><p>" changelog "</p></description>"
print "</release>"
print " <release version=\"" version "\" date=\"" date "\">"
print " <description><p>" changelog "</p></description>"
print " </release>"
replaced = 1
}
}

View File

@@ -0,0 +1,17 @@
---
description: Bad Request - A parameter was not specified, or was specified incorrectly.
content:
application/json:
schema:
type: object
properties:
status_code:
type: string
status:
type: string
error:
type: string
example:
status_code: 400
status: false
error: "Bad Request"

View File

@@ -0,0 +1,17 @@
---
description: Unauthorized - The request requires user authentication.
content:
application/json:
schema:
type: object
properties:
status_code:
type: string
status:
type: string
error:
type: string
example:
status_code: 401
status: false
error: "Unauthorized"

View File

@@ -0,0 +1,7 @@
---
description: Forbidden - The server understood the request, but is refusing to fulfill it.
content:
# TODO: return JSON response.
text/plain:
schema:
type: string

View File

@@ -0,0 +1,15 @@
---
description: Not Found - The requested resource could not be found.
content:
application/json:
schema:
type: object
properties:
status_code:
type: integer
format: int32
error:
type: string
example:
status_code: 404
error: "Not Found"

View File

@@ -0,0 +1,48 @@
---
type: object
required:
- name
properties:
name:
type: string
description: Application Name, as shown on Moonlight
output:
type: string
description: The file where the output of the command is stored, if it is not specified, the output is ignored
cmd:
$ref: "./cmd.yml"
description: The main application to start. If blank, no application will be started.
exclude-global-prep-cmd:
type: boolean
description: Enable/Disable the execution of Global Prep Commands for this application.
elevated:
type: boolean
description: Run the application as an elevated process.
auto-detach:
type: boolean
description: Continue streaming if the application exits quickly
wait-all:
type: boolean
description: Continue streaming until all app processes exit
exit-timeout:
type: integer
description: Number of seconds to wait for all app processes to gracefully exit when requested to quit.
image-path:
type: string
description: |
Application icon/picture/image path that will be sent to client. Image must be a PNG file.
If not set, Sunshine will send default box image.
working-dir:
type: string
description: |
The working directory that should be passed to the process.
For example, some applications use the working directory to search for configuration files.
If not set, Sunshine will default to the parent directory of the command
prep-cmd:
type: array
items:
$ref: "./prep-cmd.yml"
detached:
type: array
items:
$ref: "./cmd.yml"

View File

@@ -0,0 +1,3 @@
---
type: string
description: Command to execute

View File

@@ -0,0 +1,16 @@
---
type: object
required:
- do
- undo
- elevated
properties:
do:
$ref: "./cmd.yml"
description: Command to run before the application starts.
undo:
$ref: "./cmd.yml"
description: Command to run after the application exits.
elevated:
type: boolean
description: Run the command as an elevated process.

44
api/openapi.yml Normal file
View File

@@ -0,0 +1,44 @@
---
# https://openapi.tools
openapi: 3.1.0
info:
title: Sunshine
summary: Self-hosted game stream host for Moonlight.
version: 0.0.0
contact:
name: LizardByte
url: https://app.lizardbyte.dev/support
license:
name: GNU General Public License v3.0 only
url: https://github.com/LizardByte/Sunshine/blob/master/LICENSE
servers:
- url: "https://{host}:{ui-port}"
description: Sunshine server
variables:
host:
default: "localhost"
ui-port:
default: 47990
security:
- basicAuth: []
components:
securitySchemes:
# TODO: update when JWT is implemented (https://github.com/LizardByte/Sunshine/pull/2995)
# https://swagger.io/specification/#security-scheme-object-examples
basicAuth:
description: HTTP Basic authentication
type: http
scheme: basic
paths:
/api/apps:
$ref: "./paths/confighttp/apps/apps.yml"
/api/apps/{index}:
$ref: "./paths/confighttp/apps/apps-by-index.yml"
/api/logs:
$ref: "./paths/confighttp/logs/logs.yml"

View File

@@ -0,0 +1,37 @@
---
delete:
summary: Delete an application.
description: |
Delete an application.
operationId: deleteApps
tags:
- Apps
parameters:
- name: index
in: path
description: The index of the application to delete.
required: true
schema:
type: integer
format: int32
responses:
'200':
description: The application was deleted successfully.
content:
application/json:
schema:
type: object
properties:
status:
type: string
result:
type: string
example:
status: true
result: "application 9999 deleted"
'400':
$ref: "../../../components/responses/400.yml"
'401':
$ref: "../../../components/responses/401.yml"
'403':
$ref: "../../../components/responses/403.yml"

View File

@@ -0,0 +1,151 @@
---
get:
summary: Get the list of available applications.
description: |
Get the list of available applications.
operationId: getApps
tags:
- Apps
responses:
'200':
description: A list of available applications.
content:
application/json:
schema:
type: array
items:
$ref: "../../../components/schemas/app.yml"
example:
- name: "Example App"
output: "/path/to/output.log"
cmd: "example-command"
exclude-global-prep-cmd: false
elevated: false
auto-detach: true
wait-all: false
exit-timeout: 30
image-path: "/path/to/image.png"
working-dir: "/path/to/working-dir"
prep-cmd:
- do: "prep-command-1"
undo: "undo-command-1"
elevated: false
detached:
- "detached-command-1"
'401':
$ref: "../../../components/responses/401.yml"
'403':
$ref: "../../../components/responses/403.yml"
post:
summary: Save an application.
description: |
Save an application.
To save a new application the index must be `-1`.
To update an existing application, you must provide the current index of the application.
operationId: postApps
tags:
- Apps
parameters:
- name: index
in: query
description: The index of the application to update. If the index is -1, a new application will be created.
required: true
schema:
type: integer
format: int32
- name: name
in: query
description: Application Name
required: false
schema:
type: string
- name: output
in: query
description: Log Output Path
required: false
schema:
type: string
- name: cmd
in: query
description: Command to run the application
required: false
schema:
$ref: "../../../components/schemas/cmd.yml"
- name: exclude-global-prep-cmd
in: query
description: Enable/Disable the execution of Global Prep Commands for this application.
required: false
schema:
type: boolean
- name: elevated
in: query
description: Run the application as an elevated process.
required: false
schema:
type: boolean
- name: auto-detach
in: query
description: Continue streaming if the application exits quickly
required: false
schema:
type: boolean
- name: wait-all
in: query
description: Continue streaming until all app processes exit
required: false
schema:
type: boolean
- name: exit-timeout
in: query
description: Number of seconds to wait for all app processes to gracefully exit when requested to quit.
required: false
schema:
type: integer
format: int32
- name: prep-cmd
in: query
description: Commands to run before the main application
required: false
schema:
type: array
items:
$ref: "../../../components/schemas/prep-cmd.yml"
- name: detached
in: query
description: Commands to run in detached processes
required: false
schema:
type: array
items:
$ref: "../../../components/schemas/cmd.yml"
- name: image-path
in: query
description: Full path to the application image. Must be a png file.
required: false
schema:
type: string
- name: working-dir
in: query
description: The working directory that should be passed to the process.
required: false
schema:
type: string
responses:
'200':
description: The application was saved successfully.
content:
application/json:
schema:
type: object
properties:
status:
type: string
example:
status: true
'400':
$ref: "../../../components/responses/400.yml"
'401':
$ref: "../../../components/responses/401.yml"
'403':
$ref: "../../../components/responses/403.yml"

View File

@@ -0,0 +1,20 @@
---
get:
summary: Get the logs from the log file.
description: |
Get the logs from the log file.
operationId: getLogs
tags:
- Logs
responses:
'200':
description: The contents of the log file.
content:
text/plain:
schema:
type: string
example: '[2025-01-15 17:07:58.131]: Info: Sunshine version: v...'
'401':
$ref: "../../../components/responses/401.yml"
'403':
$ref: "../../../components/responses/403.yml"

View File

@@ -149,6 +149,7 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
${CMAKE_THREAD_LIBS_INIT}
enet
libdisplaydevice::display_device
nlohmann_json::nlohmann_json
opus
${FFMPEG_LIBRARIES}
${Boost_LIBRARIES}

View File

@@ -198,29 +198,33 @@ if(${SUNSHINE_ENABLE_TRAY})
list(APPEND PLATFORM_TARGET_FILES "${CMAKE_SOURCE_DIR}/third-party/tray/src/tray_linux.c")
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${APPINDICATOR_LIBRARIES} ${LIBNOTIFY_LIBRARIES})
endif()
# flatpak icons must be prefixed with the app id or they will not be included in the flatpak
if(${SUNSHINE_BUILD_FLATPAK})
set(SUNSHINE_TRAY_PREFIX "${PROJECT_FQDN}")
else()
set(SUNSHINE_TRAY_PREFIX "sunshine")
endif()
list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_TRAY_PREFIX="${SUNSHINE_TRAY_PREFIX}")
else()
set(SUNSHINE_TRAY 0)
message(STATUS "Tray icon disabled")
endif()
if(${SUNSHINE_USE_LEGACY_INPUT}) # TODO: Remove this legacy option after the next stable release
list(APPEND PLATFORM_TARGET_FILES "${CMAKE_SOURCE_DIR}/src/platform/linux/input/legacy_input.cpp")
else()
# These need to be set before adding the inputtino subdirectory in order for them to be picked up
set(LIBEVDEV_CUSTOM_INCLUDE_DIR "${EVDEV_INCLUDE_DIR}")
set(LIBEVDEV_CUSTOM_LIBRARY "${EVDEV_LIBRARY}")
# These need to be set before adding the inputtino subdirectory in order for them to be picked up
set(LIBEVDEV_CUSTOM_INCLUDE_DIR "${EVDEV_INCLUDE_DIR}")
set(LIBEVDEV_CUSTOM_LIBRARY "${EVDEV_LIBRARY}")
add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/inputtino")
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES inputtino::libinputtino)
file(GLOB_RECURSE INPUTTINO_SOURCES
${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.h
${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.cpp)
list(APPEND PLATFORM_TARGET_FILES ${INPUTTINO_SOURCES})
add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/inputtino")
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES inputtino::libinputtino)
file(GLOB_RECURSE INPUTTINO_SOURCES
${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.h
${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.cpp)
list(APPEND PLATFORM_TARGET_FILES ${INPUTTINO_SOURCES})
# build libevdev before the libinputtino target
if(EXTERNAL_PROJECT_LIBEVDEV_USED)
add_dependencies(libinputtino libevdev)
endif()
# build libevdev before the libinputtino target
if(EXTERNAL_PROJECT_LIBEVDEV_USED)
add_dependencies(libinputtino libevdev)
endif()
# AppImage and Flatpak

View File

@@ -76,7 +76,6 @@ list(PREPEND PLATFORM_LIBRARIES
libstdc++.a
libwinpthread.a
minhook::minhook
nlohmann_json::nlohmann_json
ntdll
setupapi
shlwapi

View File

@@ -16,6 +16,7 @@ add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/Simple-Web-Server")
add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/libdisplaydevice")
# common dependencies
include("${CMAKE_MODULE_PATH}/dependencies/nlohmann_json.cmake")
find_package(OpenSSL REQUIRED)
find_package(PkgConfig REQUIRED)
find_package(Threads REQUIRED)

View File

@@ -0,0 +1,18 @@
#
# Loads the nlohmann_json library giving the priority to the system package first, with a fallback to FetchContent.
#
include_guard(GLOBAL)
find_package(nlohmann_json 3.11 QUIET GLOBAL)
if(NOT nlohmann_json_FOUND)
message(STATUS "nlohmann_json v3.11.x package not found in the system. Falling back to FetchContent.")
include(FetchContent)
FetchContent_Declare(
json
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
URL_HASH MD5=c23a33f04786d85c29fda8d16b5f0efd
DOWNLOAD_EXTRACT_TIMESTAMP
)
FetchContent_MakeAvailable(json)
endif()

View File

@@ -1,8 +1,5 @@
# windows specific dependencies
# nlohmann_json
find_package(nlohmann_json CONFIG 3.11 REQUIRED)
# Make sure MinHook is installed
find_library(MINHOOK_LIBRARY libMinHook.a REQUIRED)
find_path(MINHOOK_INCLUDE_DIR MinHook.h PATH_SUFFIXES include REQUIRED)

View File

@@ -100,15 +100,31 @@ endif()
# tray icon
if(${SUNSHINE_TRAY} STREQUAL 1)
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
RENAME "sunshine-tray.svg")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-playing.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-pausing.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-locked.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
if(NOT ${SUNSHINE_BUILD_FLATPAK})
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
RENAME "sunshine-tray.svg")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-playing.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-pausing.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-locked.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
else()
# flatpak icons must be prefixed with the app id or they will not be included in the flatpak
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
RENAME "${PROJECT_FQDN}-tray.svg")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-playing.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
RENAME "${PROJECT_FQDN}-playing.svg")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-pausing.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
RENAME "${PROJECT_FQDN}-pausing.svg")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-locked.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
RENAME "${PROJECT_FQDN}-locked.svg")
endif()
set(CPACK_DEBIAN_PACKAGE_DEPENDS "\
${CPACK_DEBIAN_PACKAGE_DEPENDS}, \
@@ -128,15 +144,8 @@ else()
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.desktop"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications"
RENAME "${PROJECT_FQDN}.desktop")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine_kms.desktop"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications"
RENAME "${PROJECT_FQDN}_kms.desktop")
endif()
if(${SUNSHINE_BUILD_FLATPAK})
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine_terminal.desktop"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications"
RENAME "${PROJECT_FQDN}_terminal.desktop")
elseif(NOT ${SUNSHINE_BUILD_APPIMAGE})
if(NOT ${SUNSHINE_BUILD_APPIMAGE} AND NOT ${SUNSHINE_BUILD_FLATPAK})
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine_terminal.desktop"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
endif()

View File

@@ -8,10 +8,10 @@ elseif (UNIX)
endif()
if(SUNSHINE_BUILD_FLATPAK)
set(SUNSHINE_SERVICE_START_COMMAND "ExecStart=${PROJECT_FQDN}")
set(SUNSHINE_SERVICE_START_COMMAND "ExecStart=flatpak run --command=sunshine ${PROJECT_FQDN}")
set(SUNSHINE_SERVICE_STOP_COMMAND "ExecStop=flatpak kill ${PROJECT_FQDN}")
else()
set(SUNSHINE_SERVICE_START_COMMAND "ExecStart=${SUNSHINE_EXECUTABLE_PATH}")
set(SUNSHINE_SERVICE_STOP_COMMAND "")
endif()
endif ()
endif()

View File

@@ -65,6 +65,4 @@ elseif(UNIX) # Linux
"Enable building wayland specific code." ON)
option(SUNSHINE_ENABLE_X11
"Enable X11 grab if available." ON)
option(SUNSHINE_USE_LEGACY_INPUT # TODO: Remove this legacy option after the next stable release
"Use the legacy virtual input implementation." OFF)
endif()

View File

@@ -10,14 +10,12 @@ if(APPLE)
endif()
elseif(UNIX)
# configure the .desktop file
set(SUNSHINE_DESKTOP_ICON "sunshine.svg")
set(SUNSHINE_DESKTOP_ICON "sunshine")
if(${SUNSHINE_BUILD_APPIMAGE})
configure_file(packaging/linux/AppImage/sunshine.desktop sunshine.desktop @ONLY)
elseif(${SUNSHINE_BUILD_FLATPAK})
set(SUNSHINE_DESKTOP_ICON "${PROJECT_FQDN}.svg")
set(SUNSHINE_DESKTOP_ICON "${PROJECT_FQDN}")
configure_file(packaging/linux/flatpak/sunshine.desktop sunshine.desktop @ONLY)
configure_file(packaging/linux/flatpak/sunshine_kms.desktop sunshine_kms.desktop @ONLY)
configure_file(packaging/linux/sunshine_terminal.desktop sunshine_terminal.desktop @ONLY)
configure_file(packaging/linux/flatpak/${PROJECT_FQDN}.metainfo.xml
${PROJECT_FQDN}.metainfo.xml @ONLY)
else()

View File

@@ -12,17 +12,23 @@ basic authentication with the admin username and password.
## GET /api/apps
@copydoc confighttp::getApps()
## GET /api/logs
@copydoc confighttp::getLogs()
## POST /api/apps
@copydoc confighttp::saveApp()
## POST /api/apps/close
@copydoc confighttp::closeApp()
## DELETE /api/apps/{index}
@copydoc confighttp::deleteApp()
## POST /api/covers/upload
@copydoc confighttp::uploadCover()
## GET /api/clients/list
@copydoc confighttp::getClients()
## POST /api/clients/unpair
@copydoc confighttp::unpair()
## POST /api/clients/unpair-all
@copydoc confighttp::unpairAll()
## GET /api/config
@copydoc confighttp::getConfig()
@@ -33,11 +39,11 @@ basic authentication with the admin username and password.
## POST /api/config
@copydoc confighttp::saveConfig()
## POST /api/restart
@copydoc confighttp::restart()
## POST /api/covers/upload
@copydoc confighttp::uploadCover()
## POST /api/reset-display-device-persistence
@copydoc confighttp::resetDisplayDevicePersistence()
## GET /api/logs
@copydoc confighttp::getLogs()
## POST /api/password
@copydoc confighttp::savePassword()
@@ -45,17 +51,11 @@ basic authentication with the admin username and password.
## POST /api/pin
@copydoc confighttp::savePin()
## POST /api/clients/unpair-all
@copydoc confighttp::unpairAll()
## POST /api/reset-display-device-persistence
@copydoc confighttp::resetDisplayDevicePersistence()
## POST /api/clients/unpair
@copydoc confighttp::unpair()
## GET /api/clients/list
@copydoc confighttp::listClients()
## POST /api/apps/close
@copydoc confighttp::closeApp()
## POST /api/restart
@copydoc confighttp::restart()
<div class="section_buttons">

View File

@@ -301,22 +301,19 @@ administrative privileges. Simply enable the elevated option in the WEB UI, or a
This is an option for both prep-cmd and regular commands and will launch the process with the current user without a
UAC prompt.
@note{It is important to write the values "true" and "false" as string values, not as the typical true/false
values in most JSON.}
**Example**
```json
{
"name": "Game With AntiCheat that Requires Admin",
"output": "",
"cmd": "ping 127.0.0.1",
"exclude-global-prep-cmd": "false",
"elevated": "true",
"exclude-global-prep-cmd": false,
"elevated": true,
"prep-cmd": [
{
"do": "powershell.exe -command \"Start-Streaming\"",
"undo": "powershell.exe -command \"Stop-Streaming\"",
"elevated": "false"
"elevated": false
}
],
"image-path": ""

View File

@@ -92,7 +92,6 @@ dependencies=(
"mingw-w64-ucrt-x86_64-graphviz" # Optional, for docs
"mingw-w64-ucrt-x86_64-MinHook"
"mingw-w64-ucrt-x86_64-miniupnpc"
"mingw-w64-ucrt-x86_64-nlohmann-json"
"mingw-w64-ucrt-x86_64-nodejs"
"mingw-w64-ucrt-x86_64-nsis"
"mingw-w64-ucrt-x86_64-onevpl"

View File

@@ -225,7 +225,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
<tr>
<td>Example</td>
<td colspan="2">@code{}
global_prep_cmd = [{"do":"nircmd.exe setdisplay 1280 720 32 144","undo":"nircmd.exe setdisplay 2560 1440 32 144"}]
global_prep_cmd = [{"do":"nircmd.exe setdisplay 1280 720 32 144","elevated":true,"undo":"nircmd.exe setdisplay 2560 1440 32 144"}]
@endcode</td>
</tr>
</table>
@@ -1203,6 +1203,31 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
</table>
### dd_config_revert_on_disconnect
<table>
<tr>
<td>Description</td>
<td colspan="2">
When enabled, display configuration is reverted upon disconnect of all clients instead of app close or last session termination.
This can be useful for returning to physical usage of the host machine without closing the active app.
@warning{Some applications may not function properly when display configuration is changed while active.}
@note{Applies to Windows only.}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}disabled@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
dd_config_revert_on_disconnect = enabled
@endcode</td>
</tr>
</table>
### dd_mode_remapping
<table>

View File

@@ -1,7 +1,7 @@
# Guides
@admonition{Community | A collection of guides written by the community is available on our
[blog](https://lizardbyte.com/blog).
[blog](https://app.lizardbyte.dev/blog).
Feel free to contribute your own tips and trips by making a PR to
[LizardByte.github.io](https://github.com/LizardByte/LizardByte.github.io).}

View File

@@ -29,14 +29,18 @@
</p>
<p>NOTE: Sunshine requires additional installation steps.</p>
<p>flatpak run --command=additional-install.sh @PROJECT_FQDN@</p>
<p>
<code>flatpak run --command=additional-install.sh @PROJECT_FQDN@</code>
</p>
<p>NOTE: Sunshine uses a self-signed certificate. The web browser will report it as not secure, but it is safe.</p>
<p>NOTE: KMS Grab (Optional)</p>
<p>sudo -i PULSE_SERVER=unix:/run/user/$(id -u $whoami)/pulse/native flatpak run @PROJECT_FQDN@</p>
<p>
<code>sudo -i PULSE_SERVER=unix:/run/user/$(id -u $whoami)/pulse/native flatpak run @PROJECT_FQDN@</code>
</p>
</description>
<releases>
<release version="@PROJECT_VERSION@" date="1970-01-01"></release>
<release version="@PROJECT_VERSION@" date="1990-01-01"></release>
</releases>
<developer_name>LizardByte</developer_name>

View File

@@ -0,0 +1,11 @@
#!/bin/sh
PORT=47990
if ! curl -k https://localhost:$PORT > /dev/null 2>&1; then
(sleep 3 && xdg-open https://localhost:$PORT) &
exec sunshine "$@"
else
echo "Sunshine is already running, opening the web interface..."
xdg-open https://localhost:$PORT
fi

View File

@@ -1,20 +1,9 @@
[Desktop Entry]
Type=Application
Name=@PROJECT_NAME@
Exec=@PROJECT_FQDN@
Version=1.0
Categories=AudioVideo;Network;RemoteAccess;
Comment=@PROJECT_DESCRIPTION@
Exec=sunshine.sh
Icon=@SUNSHINE_DESKTOP_ICON@
Keywords=gamestream;stream;moonlight;remote play;
Categories=AudioVideo;Network;RemoteAccess;
Actions=RunInTerminal;KMS;
[Desktop Action RunInTerminal]
Name=Run in Terminal
Icon=application-x-executable
Exec=gio launch @CMAKE_INSTALL_FULL_DATAROOTDIR@/applications/@PROJECT_FQDN@_terminal.desktop
[Desktop Action KMS]
Name=Run in Terminal (KMS)
Icon=application-x-executable
Exec=gio launch @CMAKE_INSTALL_FULL_DATAROOTDIR@/applications/@PROJECT_FQDN@_kms.desktop
Name=@PROJECT_NAME@
Type=Application
Version=1.0

View File

@@ -1,6 +0,0 @@
[Desktop Entry]
Name=@PROJECT_NAME@ (KMS)
Exec=sudo -i PULSE_SERVER=unix:$(pactl info | awk '/Server String/{print$3}') flatpak run @PROJECT_FQDN@
Terminal=true
Type=Application
NoDisplay=true

View File

@@ -7,12 +7,12 @@ directories = [
'src',
'tests',
'tools',
os.path.join('third-party', 'glad'),
os.path.join('third-party', 'nvfbc'),
]
file_types = [
'cpp',
'cu',
'h',
'hpp',
'm',
'mm'
]

View File

@@ -2,16 +2,18 @@
* @file src/audio.cpp
* @brief Definitions for audio capture and encoding.
*/
// standard includes
#include <thread>
// lib includes
#include <opus/opus_multistream.h>
#include "platform/common.h"
// local includes
#include "audio.h"
#include "config.h"
#include "globals.h"
#include "logging.h"
#include "platform/common.h"
#include "thread_safe.h"
#include "utility.h"
@@ -20,15 +22,11 @@ namespace audio {
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<float>>>;
static int
start_audio_control(audio_ctx_t &ctx);
static void
stop_audio_control(audio_ctx_t &);
static void
apply_surround_params(opus_stream_config_t &stream, const stream_params_t &params);
static int start_audio_control(audio_ctx_t &ctx);
static void stop_audio_control(audio_ctx_t &);
static void apply_surround_params(opus_stream_config_t &stream, const stream_params_t &params);
int
map_stream(int channels, bool quality);
int map_stream(int channels, bool quality);
constexpr auto SAMPLE_RATE = 48000;
@@ -85,8 +83,7 @@ namespace audio {
},
};
void
encodeThread(sample_queue_t samples, config_t config, void *channel_data) {
void encodeThread(sample_queue_t samples, config_t config, void *channel_data) {
auto packets = mail::man->queue<packet_t>(mail::audio_packets);
auto stream = stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
if (config.flags[config_t::CUSTOM_SURROUND_PARAMS]) {
@@ -96,14 +93,15 @@ namespace audio {
// Encoding takes place on this thread
platf::adjust_thread_priority(platf::thread_priority_e::high);
opus_t opus { opus_multistream_encoder_create(
opus_t opus {opus_multistream_encoder_create(
stream.sampleRate,
stream.channelCount,
stream.streams,
stream.coupledStreams,
stream.mapping,
OPUS_APPLICATION_RESTRICTED_LOWDELAY,
nullptr) };
nullptr
)};
opus_multistream_encoder_ctl(opus.get(), OPUS_SET_BITRATE(stream.bitrate));
opus_multistream_encoder_ctl(opus.get(), OPUS_SET_VBR(0));
@@ -114,7 +112,7 @@ namespace audio {
auto frame_size = config.packetDuration * stream.sampleRate / 1000;
while (auto sample = samples->pop()) {
buffer_t packet { 1400 };
buffer_t packet {1400};
int bytes = opus_multistream_encode_float(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size());
if (bytes < 0) {
@@ -129,8 +127,7 @@ namespace audio {
}
}
void
capture(safe::mail_t mail, config_t config, void *channel_data) {
void capture(safe::mail_t mail, config_t config, void *channel_data) {
auto shutdown_event = mail->event<bool>(mail::shutdown);
auto stream = stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
if (config.flags[config_t::CUSTOM_SURROUND_PARAMS]) {
@@ -204,7 +201,7 @@ namespace audio {
platf::adjust_thread_priority(platf::thread_priority_e::critical);
auto samples = std::make_shared<sample_queue_t::element_type>(30);
std::thread thread { encodeThread, samples, config, channel_data };
std::thread thread {encodeThread, samples, config, channel_data};
auto fg = util::fail_guard([&]() {
samples->stop();
@@ -243,14 +240,12 @@ namespace audio {
}
}
audio_ctx_ref_t
get_audio_ctx_ref() {
static auto control_shared { safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control) };
audio_ctx_ref_t get_audio_ctx_ref() {
static auto control_shared {safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control)};
return control_shared.ref();
}
bool
is_audio_ctx_sink_available(const audio_ctx_t &ctx) {
bool is_audio_ctx_sink_available(const audio_ctx_t &ctx) {
if (!ctx.control) {
return false;
}
@@ -263,8 +258,7 @@ namespace audio {
return ctx.control->is_sink_available(sink);
}
int
map_stream(int channels, bool quality) {
int map_stream(int channels, bool quality) {
int shift = quality ? 1 : 0;
switch (channels) {
case 2:
@@ -277,8 +271,7 @@ namespace audio {
return STEREO;
}
int
start_audio_control(audio_ctx_t &ctx) {
int start_audio_control(audio_ctx_t &ctx) {
auto fg = util::fail_guard([]() {
BOOST_LOG(warning) << "There will be no audio"sv;
});
@@ -305,8 +298,7 @@ namespace audio {
return 0;
}
void
stop_audio_control(audio_ctx_t &ctx) {
void stop_audio_control(audio_ctx_t &ctx) {
// restore audio-sink if applicable
if (!ctx.restore_sink) {
return;
@@ -320,8 +312,7 @@ namespace audio {
}
}
void
apply_surround_params(opus_stream_config_t &stream, const stream_params_t &params) {
void apply_surround_params(opus_stream_config_t &stream, const stream_params_t &params) {
stream.channelCount = params.channelCount;
stream.streams = params.streams;
stream.coupledStreams = params.coupledStreams;

View File

@@ -71,8 +71,7 @@ namespace audio {
using packet_t = std::pair<void *, buffer_t>;
using audio_ctx_ref_t = safe::shared_t<audio_ctx_t>::ptr_t;
void
capture(safe::mail_t mail, config_t config, void *channel_data);
void capture(safe::mail_t mail, config_t config, void *channel_data);
/**
* @brief Get the reference to the audio context.
@@ -84,8 +83,7 @@ namespace audio {
* audio_ctx_ref_t audio = get_audio_ctx_ref()
* @examples_end
*/
audio_ctx_ref_t
get_audio_ctx_ref();
audio_ctx_ref_t get_audio_ctx_ref();
/**
* @brief Check if the audio sink held by audio context is available.
@@ -101,6 +99,5 @@ namespace audio {
* return false;
* @examples_end
*/
bool
is_audio_ctx_sink_available(const audio_ctx_t &ctx);
bool is_audio_ctx_sink_available(const audio_ctx_t &ctx);
} // namespace audio

View File

@@ -3,6 +3,7 @@
* @brief Definitions for FFmpeg Coded Bitstream API.
*/
extern "C" {
// lib includes
#include <libavcodec/avcodec.h>
#include <libavcodec/cbs_h264.h>
#include <libavcodec/cbs_h265.h>
@@ -10,14 +11,15 @@ extern "C" {
#include <libavutil/pixdesc.h>
}
// local includes
#include "cbs.h"
#include "logging.h"
#include "utility.h"
using namespace std::literals;
namespace cbs {
void
close(CodedBitstreamContext *c) {
void close(CodedBitstreamContext *c) {
ff_cbs_close(&c);
}
@@ -36,8 +38,7 @@ namespace cbs {
std::fill_n((std::uint8_t *) this, sizeof(*this), 0);
}
frag_t &
operator=(frag_t &&o) {
frag_t &operator=(frag_t &&o) {
std::copy((std::uint8_t *) &o, (std::uint8_t *) (&o + 1), (std::uint8_t *) this);
o.data = nullptr;
@@ -53,12 +54,11 @@ namespace cbs {
}
};
util::buffer_t<std::uint8_t>
write(cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) {
util::buffer_t<std::uint8_t> write(cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) {
cbs::frag_t frag;
auto err = ff_cbs_insert_unit_content(&frag, -1, nal, uh, nullptr);
if (err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
BOOST_LOG(error) << "Could not insert NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {};
@@ -66,29 +66,27 @@ namespace cbs {
err = ff_cbs_write_fragment_data(cbs_ctx.get(), &frag);
if (err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
BOOST_LOG(error) << "Could not write fragment data: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {};
}
// frag.data_size * 8 - frag.data_bit_padding == bits in fragment
util::buffer_t<std::uint8_t> data { frag.data_size };
util::buffer_t<std::uint8_t> data {frag.data_size};
std::copy_n(frag.data, frag.data_size, std::begin(data));
return data;
}
util::buffer_t<std::uint8_t>
write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
util::buffer_t<std::uint8_t> write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
cbs::ctx_t cbs_ctx;
ff_cbs_init(&cbs_ctx, codec_id, nullptr);
return write(cbs_ctx, nal, uh, codec_id);
}
h264_t
make_sps_h264(const AVCodecContext *avctx, const AVPacket *packet) {
h264_t make_sps_h264(const AVCodecContext *avctx, const AVPacket *packet) {
cbs::ctx_t ctx;
if (ff_cbs_init(&ctx, AV_CODEC_ID_H264, nullptr)) {
return {};
@@ -98,7 +96,7 @@ namespace cbs {
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
if (err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {};
@@ -144,8 +142,7 @@ namespace cbs {
};
}
hevc_t
make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) {
hevc_t make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) {
cbs::ctx_t ctx;
if (ff_cbs_init(&ctx, AV_CODEC_ID_H265, nullptr)) {
return {};
@@ -155,7 +152,7 @@ namespace cbs {
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
if (err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {};
@@ -222,8 +219,7 @@ namespace cbs {
* It then checks if the SPS->VUI (Video Usability Information) is present in the active SPS of the packet.
* This is done for both H264 and H265 codecs.
*/
bool
validate_sps(const AVPacket *packet, int codec_id) {
bool validate_sps(const AVPacket *packet, int codec_id) {
cbs::ctx_t ctx;
if (ff_cbs_init(&ctx, (AVCodecID) codec_id, nullptr)) {
return false;
@@ -233,7 +229,7 @@ namespace cbs {
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
if (err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return false;

View File

@@ -4,6 +4,7 @@
*/
#pragma once
// local includes
#include "utility.h"
struct AVPacket;
@@ -25,10 +26,8 @@ namespace cbs {
nal_t sps;
};
hevc_t
make_sps_hevc(const AVCodecContext *ctx, const AVPacket *packet);
h264_t
make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet);
hevc_t make_sps_hevc(const AVCodecContext *ctx, const AVPacket *packet);
h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet);
/**
* @brief Validates the Sequence Parameter Set (SPS) of a given packet.
@@ -36,6 +35,5 @@ namespace cbs {
* @param codec_id The ID of the codec used (either AV_CODEC_ID_H264 or AV_CODEC_ID_H265).
* @return True if the SPS->VUI is present in the active SPS of the packet, false otherwise.
*/
bool
validate_sps(const AVPacket *packet, int codec_id);
bool validate_sps(const AVPacket *packet, int codec_id);
} // namespace cbs

View File

@@ -2,6 +2,7 @@
* @file src/config.cpp
* @brief Definitions for the configuration of Sunshine.
*/
// standard includes
#include <algorithm>
#include <filesystem>
#include <fstream>
@@ -11,21 +12,22 @@
#include <unordered_map>
#include <utility>
// lib includes
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
// local includes
#include "config.h"
#include "entry_handler.h"
#include "file_handler.h"
#include "logging.h"
#include "nvhttp.h"
#include "platform/common.h"
#include "rtsp.h"
#include "utility.h"
#include "platform/common.h"
#ifdef _WIN32
#include <shellapi.h>
#endif
@@ -43,15 +45,21 @@ using namespace std::literals;
#define CERTIFICATE_FILE CA_DIR "/cacert.pem"
#define APPS_JSON_PATH platf::appdata().string() + "/apps.json"
namespace config {
namespace nv {
nvenc::nvenc_two_pass
twopass_from_view(const std::string_view &preset) {
if (preset == "disabled") return nvenc::nvenc_two_pass::disabled;
if (preset == "quarter_res") return nvenc::nvenc_two_pass::quarter_resolution;
if (preset == "full_res") return nvenc::nvenc_two_pass::full_resolution;
nvenc::nvenc_two_pass twopass_from_view(const std::string_view &preset) {
if (preset == "disabled") {
return nvenc::nvenc_two_pass::disabled;
}
if (preset == "quarter_res") {
return nvenc::nvenc_two_pass::quarter_resolution;
}
if (preset == "full_res") {
return nvenc::nvenc_two_pass::full_resolution;
}
BOOST_LOG(warning) << "config: unknown nvenc_twopass value: " << preset;
return nvenc::nvenc_two_pass::quarter_resolution;
}
@@ -178,11 +186,11 @@ namespace config {
cavlc = AMF_VIDEO_ENCODER_CALV ///< CAVLC
};
template <class T>
std::optional<int>
quality_from_view(const std::string_view &quality_type, const std::optional<int>(&original)) {
template<class T>
std::optional<int> quality_from_view(const std::string_view &quality_type, const std::optional<int>(&original)) {
#define _CONVERT_(x) \
if (quality_type == #x##sv) return (int) T::x
if (quality_type == #x##sv) \
return (int) T::x
_CONVERT_(balanced);
_CONVERT_(quality);
_CONVERT_(speed);
@@ -190,11 +198,11 @@ namespace config {
return original;
}
template <class T>
std::optional<int>
rc_from_view(const std::string_view &rc, const std::optional<int>(&original)) {
template<class T>
std::optional<int> rc_from_view(const std::string_view &rc, const std::optional<int>(&original)) {
#define _CONVERT_(x) \
if (rc == #x##sv) return (int) T::x
if (rc == #x##sv) \
return (int) T::x
_CONVERT_(cbr);
_CONVERT_(cqp);
_CONVERT_(vbr_latency);
@@ -203,11 +211,11 @@ namespace config {
return original;
}
template <class T>
std::optional<int>
usage_from_view(const std::string_view &usage, const std::optional<int>(&original)) {
template<class T>
std::optional<int> usage_from_view(const std::string_view &usage, const std::optional<int>(&original)) {
#define _CONVERT_(x) \
if (usage == #x##sv) return (int) T::x
if (usage == #x##sv) \
return (int) T::x
_CONVERT_(lowlatency);
_CONVERT_(lowlatency_high_quality);
_CONVERT_(transcoding);
@@ -217,11 +225,16 @@ namespace config {
return original;
}
int
coder_from_view(const std::string_view &coder) {
if (coder == "auto"sv) return _auto;
if (coder == "cabac"sv || coder == "ac"sv) return cabac;
if (coder == "cavlc"sv || coder == "vlc"sv) return cavlc;
int coder_from_view(const std::string_view &coder) {
if (coder == "auto"sv) {
return _auto;
}
if (coder == "cabac"sv || coder == "ac"sv) {
return cabac;
}
if (coder == "cavlc"sv || coder == "vlc"sv) {
return cavlc;
}
return _auto;
}
@@ -244,10 +257,10 @@ namespace config {
disabled = false ///< Disabled
};
std::optional<int>
preset_from_view(const std::string_view &preset) {
std::optional<int> preset_from_view(const std::string_view &preset) {
#define _CONVERT_(x) \
if (preset == #x##sv) return x
if (preset == #x##sv) \
return x
_CONVERT_(veryslow);
_CONVERT_(slower);
_CONVERT_(slow);
@@ -259,11 +272,16 @@ namespace config {
return std::nullopt;
}
std::optional<int>
coder_from_view(const std::string_view &coder) {
if (coder == "auto"sv) return _auto;
if (coder == "cabac"sv || coder == "ac"sv) return disabled;
if (coder == "cavlc"sv || coder == "vlc"sv) return enabled;
std::optional<int> coder_from_view(const std::string_view &coder) {
if (coder == "auto"sv) {
return _auto;
}
if (coder == "cabac"sv || coder == "ac"sv) {
return disabled;
}
if (coder == "cavlc"sv || coder == "vlc"sv) {
return enabled;
}
return std::nullopt;
}
@@ -277,32 +295,40 @@ namespace config {
cavlc ///< CAVLC
};
int
coder_from_view(const std::string_view &coder) {
if (coder == "auto"sv) return _auto;
if (coder == "cabac"sv || coder == "ac"sv) return cabac;
if (coder == "cavlc"sv || coder == "vlc"sv) return cavlc;
int coder_from_view(const std::string_view &coder) {
if (coder == "auto"sv) {
return _auto;
}
if (coder == "cabac"sv || coder == "ac"sv) {
return cabac;
}
if (coder == "cavlc"sv || coder == "vlc"sv) {
return cavlc;
}
return -1;
}
int
allow_software_from_view(const std::string_view &software) {
if (software == "allowed"sv || software == "forced") return 1;
int allow_software_from_view(const std::string_view &software) {
if (software == "allowed"sv || software == "forced") {
return 1;
}
return 0;
}
int
force_software_from_view(const std::string_view &software) {
if (software == "forced") return 1;
int force_software_from_view(const std::string_view &software) {
if (software == "forced") {
return 1;
}
return 0;
}
int
rt_from_view(const std::string_view &rt) {
if (rt == "disabled" || rt == "off" || rt == "0") return 0;
int rt_from_view(const std::string_view &rt) {
if (rt == "disabled" || rt == "off" || rt == "0") {
return 0;
}
return 1;
}
@@ -310,10 +336,10 @@ namespace config {
} // namespace vt
namespace sw {
int
svtav1_preset_from_view(const std::string_view &preset) {
int svtav1_preset_from_view(const std::string_view &preset) {
#define _CONVERT_(x, y) \
if (preset == #x##sv) return y
if (preset == #x##sv) \
return y
_CONVERT_(veryslow, 1);
_CONVERT_(slower, 2);
_CONVERT_(slow, 4);
@@ -329,10 +355,10 @@ namespace config {
} // namespace sw
namespace dd {
video_t::dd_t::config_option_e
config_option_from_view(const std::string_view value) {
video_t::dd_t::config_option_e config_option_from_view(const std::string_view value) {
#define _CONVERT_(x) \
if (value == #x##sv) return video_t::dd_t::config_option_e::x
if (value == #x##sv) \
return video_t::dd_t::config_option_e::x
_CONVERT_(disabled);
_CONVERT_(verify_only);
_CONVERT_(ensure_active);
@@ -342,10 +368,10 @@ namespace config {
return video_t::dd_t::config_option_e::disabled; // Default to this if value is invalid
}
video_t::dd_t::resolution_option_e
resolution_option_from_view(const std::string_view value) {
video_t::dd_t::resolution_option_e resolution_option_from_view(const std::string_view value) {
#define _CONVERT_2_ARG_(str, val) \
if (value == #str##sv) return video_t::dd_t::resolution_option_e::val
if (value == #str##sv) \
return video_t::dd_t::resolution_option_e::val
#define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
_CONVERT_(disabled);
_CONVERT_2_ARG_(auto, automatic);
@@ -355,10 +381,10 @@ namespace config {
return video_t::dd_t::resolution_option_e::disabled; // Default to this if value is invalid
}
video_t::dd_t::refresh_rate_option_e
refresh_rate_option_from_view(const std::string_view value) {
video_t::dd_t::refresh_rate_option_e refresh_rate_option_from_view(const std::string_view value) {
#define _CONVERT_2_ARG_(str, val) \
if (value == #str##sv) return video_t::dd_t::refresh_rate_option_e::val
if (value == #str##sv) \
return video_t::dd_t::refresh_rate_option_e::val
#define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
_CONVERT_(disabled);
_CONVERT_2_ARG_(auto, automatic);
@@ -368,10 +394,10 @@ namespace config {
return video_t::dd_t::refresh_rate_option_e::disabled; // Default to this if value is invalid
}
video_t::dd_t::hdr_option_e
hdr_option_from_view(const std::string_view value) {
video_t::dd_t::hdr_option_e hdr_option_from_view(const std::string_view value) {
#define _CONVERT_2_ARG_(str, val) \
if (value == #str##sv) return video_t::dd_t::hdr_option_e::val
if (value == #str##sv) \
return video_t::dd_t::hdr_option_e::val
#define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
_CONVERT_(disabled);
_CONVERT_2_ARG_(auto, automatic);
@@ -380,9 +406,8 @@ namespace config {
return video_t::dd_t::hdr_option_e::disabled; // Default to this if value is invalid
}
video_t::dd_t::mode_remapping_t
mode_remapping_from_view(const std::string_view value) {
const auto parse_entry_list { [](const auto &entry_list, auto &output_field) {
video_t::dd_t::mode_remapping_t mode_remapping_from_view(const std::string_view value) {
const auto parse_entry_list {[](const auto &entry_list, auto &output_field) {
for (auto &[_, entry] : entry_list) {
auto requested_resolution = entry.template get_optional<std::string>("requested_resolution"s);
auto requested_fps = entry.template get_optional<std::string>("requested_fps"s);
@@ -393,9 +418,10 @@ namespace config {
requested_resolution.value_or(""),
requested_fps.value_or(""),
final_resolution.value_or(""),
final_refresh_rate.value_or("") });
final_refresh_rate.value_or("")
});
}
} };
}};
// We need to add a wrapping object to make it valid JSON, otherwise ptree cannot parse it.
std::stringstream json_stream;
@@ -478,6 +504,7 @@ namespace config {
{}, // manual_refresh_rate
video_t::dd_t::hdr_option_e::automatic, // hdr_option
3s, // config_revert_delay
{}, // config_revert_on_disconnect
{}, // mode_remapping
{} // wa
}, // display_device
@@ -515,13 +542,13 @@ namespace config {
input_t input {
{
{ 0x10, 0xA0 },
{ 0x11, 0xA2 },
{ 0x12, 0xA4 },
{0x10, 0xA0},
{0x11, 0xA2},
{0x12, 0xA4},
},
-1ms, // back_button_timeout
500ms, // key_repeat_delay
std::chrono::duration<double> { 1 / 24.9 }, // key_repeat_period
std::chrono::duration<double> {1 / 24.9}, // key_repeat_period
{
platf::supported_gamepads(nullptr).front().name.data(),
@@ -556,23 +583,19 @@ namespace config {
{}, // prep commands
};
bool
endline(char ch) {
bool endline(char ch) {
return ch == '\r' || ch == '\n';
}
bool
space_tab(char ch) {
bool space_tab(char ch) {
return ch == ' ' || ch == '\t';
}
bool
whitespace(char ch) {
bool whitespace(char ch) {
return space_tab(ch) || endline(ch);
}
std::string
to_string(const char *begin, const char *end) {
std::string to_string(const char *begin, const char *end) {
std::string result;
KITTY_WHILE_LOOP(auto pos = begin, pos != end, {
@@ -587,9 +610,8 @@ namespace config {
return result;
}
template <class It>
It
skip_list(It skipper, It end) {
template<class It>
It skip_list(It skipper, It end) {
int stack = 1;
while (skipper != end && stack) {
if (*skipper == '[') {
@@ -608,7 +630,7 @@ namespace config {
std::pair<
std::string_view::const_iterator,
std::optional<std::pair<std::string, std::string>>>
parse_option(std::string_view::const_iterator begin, std::string_view::const_iterator end) {
parse_option(std::string_view::const_iterator begin, std::string_view::const_iterator end) {
begin = std::find_if_not(begin, end, whitespace);
auto endl = std::find_if(begin, end, endline);
auto endc = std::find(begin, endl, '#');
@@ -638,11 +660,11 @@ namespace config {
return std::make_pair(
endl,
std::make_pair(to_string(begin, end_name), to_string(begin_val, endl)));
std::make_pair(to_string(begin, end_name), to_string(begin_val, endl))
);
}
std::unordered_map<std::string, std::string>
parse_config(const std::string_view &file_content) {
std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content) {
std::unordered_map<std::string, std::string> vars;
auto pos = std::begin(file_content);
@@ -667,8 +689,7 @@ namespace config {
return vars;
}
void
string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
void string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
auto it = vars.find(name);
if (it == std::end(vars)) {
return;
@@ -679,9 +700,8 @@ namespace config {
vars.erase(it);
}
template <typename T, typename F>
void
generic_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, T &input, F &&f) {
template<typename T, typename F>
void generic_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, T &input, F &&f) {
std::string tmp;
string_f(vars, name, tmp);
if (!tmp.empty()) {
@@ -689,8 +709,7 @@ namespace config {
}
}
void
string_restricted_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input, const std::vector<std::string_view> &allowed_vals) {
void string_restricted_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input, const std::vector<std::string_view> &allowed_vals) {
std::string temp;
string_f(vars, name, temp);
@@ -702,8 +721,7 @@ namespace config {
}
}
void
path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, fs::path &input) {
void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, fs::path &input) {
// appdata needs to be retrieved once only
static auto appdata = platf::appdata();
@@ -727,8 +745,7 @@ namespace config {
}
}
void
path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
fs::path temp = input;
path_f(vars, name, temp);
@@ -736,8 +753,7 @@ namespace config {
input = temp.string();
}
void
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
auto it = vars.find(name);
if (it == std::end(vars)) {
@@ -754,16 +770,14 @@ namespace config {
// If that integer is in hexadecimal
if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
input = util::from_hex<int>(val.substr(2));
}
else {
} else {
input = util::from_view(val);
}
vars.erase(it);
}
void
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input) {
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input) {
auto it = vars.find(name);
if (it == std::end(vars)) {
@@ -780,17 +794,15 @@ namespace config {
// If that integer is in hexadecimal
if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
input = util::from_hex<int>(val.substr(2));
}
else {
} else {
input = util::from_view(val);
}
vars.erase(it);
}
template <class F>
void
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, F &&f) {
template<class F>
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, F &&f) {
std::string tmp;
string_f(vars, name, tmp);
if (!tmp.empty()) {
@@ -798,9 +810,8 @@ namespace config {
}
}
template <class F>
void
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input, F &&f) {
template<class F>
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input, F &&f) {
std::string tmp;
string_f(vars, name, tmp);
if (!tmp.empty()) {
@@ -808,8 +819,7 @@ namespace config {
}
}
void
int_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, const std::pair<int, int> &range) {
void int_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, const std::pair<int, int> &range) {
int temp = input;
int_f(vars, name, temp);
@@ -820,9 +830,10 @@ namespace config {
}
}
bool
to_bool(std::string &boolean) {
std::for_each(std::begin(boolean), std::end(boolean), [](char ch) { return (char) std::tolower(ch); });
bool to_bool(std::string &boolean) {
std::for_each(std::begin(boolean), std::end(boolean), [](char ch) {
return (char) std::tolower(ch);
});
return boolean == "true"sv ||
boolean == "yes"sv ||
@@ -832,8 +843,7 @@ namespace config {
(std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean));
}
void
bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, bool &input) {
void bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, bool &input) {
std::string tmp;
string_f(vars, name, tmp);
@@ -844,8 +854,7 @@ namespace config {
input = to_bool(tmp);
}
void
double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
void double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
std::string tmp;
string_f(vars, name, tmp);
@@ -863,8 +872,7 @@ namespace config {
input = val;
}
void
double_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input, const std::pair<double, double> &range) {
void double_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input, const std::pair<double, double> &range) {
double temp = input;
double_f(vars, name, temp);
@@ -875,8 +883,7 @@ namespace config {
}
}
void
list_string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &input) {
void list_string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &input) {
std::string string;
string_f(vars, name, string);
@@ -900,15 +907,12 @@ namespace config {
while (pos < std::cend(string)) {
if (*pos == '[') {
pos = skip_list(pos + 1, std::cend(string)) + 1;
}
else if (*pos == ']') {
} else if (*pos == ']') {
break;
}
else if (*pos == ',') {
} else if (*pos == ',') {
input.emplace_back(begin, pos);
pos = begin = std::find_if_not(pos + 1, std::cend(string), whitespace);
}
else {
} else {
++pos;
}
}
@@ -918,8 +922,7 @@ namespace config {
}
}
void
list_prep_cmd_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<prep_cmd_t> &input) {
void list_prep_cmd_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<prep_cmd_t> &input) {
std::string string;
string_f(vars, name, string);
@@ -945,8 +948,7 @@ namespace config {
}
}
void
list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) {
void list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) {
std::vector<std::string> list;
list_string_f(vars, name, list);
@@ -972,16 +974,14 @@ namespace config {
// If the integer is a hexadecimal
if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
tmp = util::from_hex<int>(val.substr(2));
}
else {
} else {
tmp = util::from_view(val);
}
input.emplace_back(tmp);
}
}
void
map_int_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::unordered_map<int, int> &input) {
void map_int_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::unordered_map<int, int> &input) {
std::vector<int> list;
list_int_f(vars, name, list);
@@ -1000,8 +1000,7 @@ namespace config {
}
}
int
apply_flags(const char *line) {
int apply_flags(const char *line) {
int ret = 0;
while (*line != '\0') {
switch (*line) {
@@ -1028,8 +1027,7 @@ namespace config {
return ret;
}
std::vector<std::string_view> &
get_supported_gamepad_options() {
std::vector<std::string_view> &get_supported_gamepad_options() {
const auto options = platf::supported_gamepads(nullptr);
static std::vector<std::string_view> opts {};
opts.reserve(options.size());
@@ -1039,8 +1037,7 @@ namespace config {
return opts;
}
void
apply_config(std::unordered_map<std::string, std::string> &&vars) {
void apply_config(std::unordered_map<std::string, std::string> &&vars) {
if (!fs::exists(stream.file_apps.c_str())) {
fs::copy_file(SUNSHINE_ASSETS_DIR "/apps.json", stream.file_apps);
}
@@ -1050,8 +1047,8 @@ namespace config {
}
int_f(vars, "qp", video.qp);
int_between_f(vars, "hevc_mode", video.hevc_mode, { 0, 3 });
int_between_f(vars, "av1_mode", video.av1_mode, { 0, 3 });
int_between_f(vars, "hevc_mode", video.hevc_mode, {0, 3});
int_between_f(vars, "av1_mode", video.av1_mode, {0, 3});
int_f(vars, "min_threads", video.min_threads);
string_f(vars, "sw_preset", video.sw.sw_preset);
if (!video.sw.sw_preset.empty()) {
@@ -1059,8 +1056,8 @@ namespace config {
}
string_f(vars, "sw_tune", video.sw.sw_tune);
int_between_f(vars, "nvenc_preset", video.nv.quality_preset, { 1, 7 });
int_between_f(vars, "nvenc_vbv_increase", video.nv.vbv_percentage_increase, { 0, 400 });
int_between_f(vars, "nvenc_preset", video.nv.quality_preset, {1, 7});
int_between_f(vars, "nvenc_vbv_increase", video.nv.vbv_percentage_increase, {0, 400});
bool_f(vars, "nvenc_spatial_aq", video.nv.adaptive_quantization);
generic_f(vars, "nvenc_twopass", video.nv.two_pass, nv::twopass_from_view);
bool_f(vars, "nvenc_h264_cavlc", video.nv.h264_cavlc);
@@ -1131,15 +1128,16 @@ namespace config {
generic_f(vars, "dd_hdr_option", video.dd.hdr_option, dd::hdr_option_from_view);
{
int value = -1;
int_between_f(vars, "dd_config_revert_delay", value, { 0, std::numeric_limits<int>::max() });
int_between_f(vars, "dd_config_revert_delay", value, {0, std::numeric_limits<int>::max()});
if (value >= 0) {
video.dd.config_revert_delay = std::chrono::milliseconds { value };
video.dd.config_revert_delay = std::chrono::milliseconds {value};
}
}
bool_f(vars, "dd_config_revert_on_disconnect", video.dd.config_revert_on_disconnect);
generic_f(vars, "dd_mode_remapping", video.dd.mode_remapping, dd::mode_remapping_from_view);
bool_f(vars, "dd_wa_hdr_toggle", video.dd.wa.hdr_toggle);
int_between_f(vars, "min_fps_factor", video.min_fps_factor, { 1, 3 });
int_between_f(vars, "min_fps_factor", video.min_fps_factor, {1, 3});
path_f(vars, "pkey", nvhttp.pkey);
path_f(vars, "cert", nvhttp.cert);
@@ -1158,19 +1156,19 @@ namespace config {
string_f(vars, "virtual_sink", audio.virtual_sink);
bool_f(vars, "install_steam_audio_drivers", audio.install_steam_drivers);
string_restricted_f(vars, "origin_web_ui_allowed", nvhttp.origin_web_ui_allowed, { "pc"sv, "lan"sv, "wan"sv });
string_restricted_f(vars, "origin_web_ui_allowed", nvhttp.origin_web_ui_allowed, {"pc"sv, "lan"sv, "wan"sv});
int to = -1;
int_between_f(vars, "ping_timeout", to, { -1, std::numeric_limits<int>::max() });
int_between_f(vars, "ping_timeout", to, {-1, std::numeric_limits<int>::max()});
if (to != -1) {
stream.ping_timeout = std::chrono::milliseconds(to);
}
int_between_f(vars, "lan_encryption_mode", stream.lan_encryption_mode, { 0, 2 });
int_between_f(vars, "wan_encryption_mode", stream.wan_encryption_mode, { 0, 2 });
int_between_f(vars, "lan_encryption_mode", stream.lan_encryption_mode, {0, 2});
int_between_f(vars, "wan_encryption_mode", stream.wan_encryption_mode, {0, 2});
path_f(vars, "file_apps", stream.file_apps);
int_between_f(vars, "fec_percentage", stream.fec_percentage, { 1, 255 });
int_between_f(vars, "fec_percentage", stream.fec_percentage, {1, 255});
map_int_int_f(vars, "keybindings"s, input.keybindings);
@@ -1187,20 +1185,20 @@ namespace config {
int_f(vars, "back_button_timeout", to);
if (to > std::numeric_limits<int>::min()) {
input.back_button_timeout = std::chrono::milliseconds { to };
input.back_button_timeout = std::chrono::milliseconds {to};
}
double repeat_frequency { 0 };
double_between_f(vars, "key_repeat_frequency", repeat_frequency, { 0, std::numeric_limits<double>::max() });
double repeat_frequency {0};
double_between_f(vars, "key_repeat_frequency", repeat_frequency, {0, std::numeric_limits<double>::max()});
if (repeat_frequency > 0) {
config::input.key_repeat_period = std::chrono::duration<double> { 1 / repeat_frequency };
config::input.key_repeat_period = std::chrono::duration<double> {1 / repeat_frequency};
}
to = -1;
int_f(vars, "key_repeat_delay", to);
if (to >= 0) {
input.key_repeat_delay = std::chrono::milliseconds { to };
input.key_repeat_delay = std::chrono::milliseconds {to};
}
string_restricted_f(vars, "gamepad"s, input.gamepad, get_supported_gamepad_options());
@@ -1220,10 +1218,10 @@ namespace config {
bool_f(vars, "notify_pre_releases", sunshine.notify_pre_releases);
int port = sunshine.port;
int_between_f(vars, "port"s, port, { 1024 + nvhttp::PORT_HTTPS, 65535 - rtsp_stream::RTSP_SETUP_PORT });
int_between_f(vars, "port"s, port, {1024 + nvhttp::PORT_HTTPS, 65535 - rtsp_stream::RTSP_SETUP_PORT});
sunshine.port = (std::uint16_t) port;
string_restricted_f(vars, "address_family", sunshine.address_family, { "ipv4"sv, "both"sv });
string_restricted_f(vars, "address_family", sunshine.address_family, {"ipv4"sv, "both"sv});
bool upnp = false;
bool_f(vars, "upnp"s, upnp);
@@ -1259,26 +1257,19 @@ namespace config {
if (!log_level_string.empty()) {
if (log_level_string == "verbose"sv) {
sunshine.min_log_level = 0;
}
else if (log_level_string == "debug"sv) {
} else if (log_level_string == "debug"sv) {
sunshine.min_log_level = 1;
}
else if (log_level_string == "info"sv) {
} else if (log_level_string == "info"sv) {
sunshine.min_log_level = 2;
}
else if (log_level_string == "warning"sv) {
} else if (log_level_string == "warning"sv) {
sunshine.min_log_level = 3;
}
else if (log_level_string == "error"sv) {
} else if (log_level_string == "error"sv) {
sunshine.min_log_level = 4;
}
else if (log_level_string == "fatal"sv) {
} else if (log_level_string == "fatal"sv) {
sunshine.min_log_level = 5;
}
else if (log_level_string == "none"sv) {
} else if (log_level_string == "none"sv) {
sunshine.min_log_level = 6;
}
else {
} else {
// accept digit directly
auto val = log_level_string[0];
if (val >= '0' && val < '7') {
@@ -1301,8 +1292,7 @@ namespace config {
}
}
int
parse(int argc, char *argv[]) {
int parse(int argc, char *argv[]) {
std::unordered_map<std::string, std::string> cmd_vars;
#ifdef _WIN32
bool shortcut_launch = false;
@@ -1319,8 +1309,7 @@ namespace config {
#ifdef _WIN32
else if (line == "--shortcut"sv) {
shortcut_launch = true;
}
else if (line == "--shortcut-admin"sv) {
} else if (line == "--shortcut-admin"sv) {
service_admin_launch = true;
}
#endif
@@ -1336,15 +1325,13 @@ namespace config {
logging::print_help(*argv);
return -1;
}
}
else {
} else {
auto line_end = line + strlen(line);
auto pos = std::find(line, line_end, '=');
if (pos == line_end) {
sunshine.config_file = line;
}
else {
} else {
TUPLE_EL(var, 1, parse_option(line, line_end));
if (!var) {
logging::print_help(*argv);
@@ -1370,7 +1357,7 @@ namespace config {
// Create empty config file if it does not exist
if (!fs::exists(sunshine.config_file)) {
std::ofstream { sunshine.config_file };
std::ofstream {sunshine.config_file};
}
// Read config file
@@ -1385,11 +1372,9 @@ namespace config {
// the path is incorrect or inaccessible.
apply_config(std::move(vars));
config_loaded = true;
}
catch (const std::filesystem::filesystem_error &err) {
} catch (const std::filesystem::filesystem_error &err) {
BOOST_LOG(fatal) << "Failed to apply config: "sv << err.what();
}
catch (const boost::filesystem::filesystem_error &err) {
} catch (const boost::filesystem::filesystem_error &err) {
BOOST_LOG(fatal) << "Failed to apply config: "sv << err.what();
}
@@ -1419,7 +1404,7 @@ namespace config {
// Always return 1 to ensure Sunshine doesn't start normally
return 1;
}
else if (shortcut_launch) {
if (shortcut_launch) {
if (!service_ctrl::is_service_running()) {
// If the service isn't running, relaunch ourselves as admin to start it
WCHAR executable[MAX_PATH];

View File

@@ -4,6 +4,7 @@
*/
#pragma once
// standard includes
#include <bitset>
#include <chrono>
#include <optional>
@@ -11,6 +12,7 @@
#include <unordered_map>
#include <vector>
// local includes
#include "nvenc/nvenc_config.h"
namespace config {
@@ -22,6 +24,7 @@ namespace config {
int av1_mode;
int min_threads; // Minimum number of threads/slices for CPU encoding
struct {
std::string sw_preset;
std::string sw_tune;
@@ -129,6 +132,7 @@ namespace config {
std::string manual_refresh_rate; ///< Manual refresh rate in case `refresh_rate_option == refresh_rate_option_e::manual`.
hdr_option_e hdr_option;
std::chrono::milliseconds config_revert_delay; ///< Time to wait until settings are reverted (after stream ends/app exists).
bool config_revert_on_disconnect; ///< Specify whether to revert display configuration on client disconnect.
mode_remapping_t mode_remapping;
workarounds_t wa;
} dd;
@@ -204,17 +208,25 @@ namespace config {
CONST_PIN, ///< Use "universal" pin
FLAG_SIZE ///< Number of flags
};
}
} // namespace flag
struct prep_cmd_t {
prep_cmd_t(std::string &&do_cmd, std::string &&undo_cmd, bool &&elevated):
do_cmd(std::move(do_cmd)), undo_cmd(std::move(undo_cmd)), elevated(std::move(elevated)) {}
do_cmd(std::move(do_cmd)),
undo_cmd(std::move(undo_cmd)),
elevated(std::move(elevated)) {
}
explicit prep_cmd_t(std::string &&do_cmd, bool &&elevated):
do_cmd(std::move(do_cmd)), elevated(std::move(elevated)) {}
do_cmd(std::move(do_cmd)),
elevated(std::move(elevated)) {
}
std::string do_cmd;
std::string undo_cmd;
bool elevated;
};
struct sunshine_t {
std::string locale;
int min_log_level;
@@ -248,8 +260,6 @@ namespace config {
extern input_t input;
extern sunshine_t sunshine;
int
parse(int argc, char *argv[]);
std::unordered_map<std::string, std::string>
parse_config(const std::string_view &file_content);
int parse(int argc, char *argv[]);
std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content);
} // namespace config

File diff suppressed because it is too large Load Diff

View File

@@ -4,34 +4,34 @@
*/
#pragma once
#include <functional>
// standard includes
#include <string>
// local includes
#include "thread_safe.h"
#define WEB_DIR SUNSHINE_ASSETS_DIR "/web/"
namespace confighttp {
constexpr auto PORT_HTTPS = 1;
void
start();
void start();
} // namespace confighttp
// mime types map
const std::map<std::string, std::string> mime_types = {
{ "css", "text/css" },
{ "gif", "image/gif" },
{ "htm", "text/html" },
{ "html", "text/html" },
{ "ico", "image/x-icon" },
{ "jpeg", "image/jpeg" },
{ "jpg", "image/jpeg" },
{ "js", "application/javascript" },
{ "json", "application/json" },
{ "png", "image/png" },
{ "svg", "image/svg+xml" },
{ "ttf", "font/ttf" },
{ "txt", "text/plain" },
{ "woff2", "font/woff2" },
{ "xml", "text/xml" },
{"css", "text/css"},
{"gif", "image/gif"},
{"htm", "text/html"},
{"html", "text/html"},
{"ico", "image/x-icon"},
{"jpeg", "image/jpeg"},
{"jpg", "image/jpeg"},
{"js", "application/javascript"},
{"json", "application/json"},
{"png", "image/png"},
{"svg", "image/svg+xml"},
{"ttf", "font/ttf"},
{"txt", "text/plain"},
{"woff2", "font/woff2"},
{"xml", "text/xml"},
};

View File

@@ -2,29 +2,33 @@
* @file src/crypto.cpp
* @brief Definitions for cryptography functions.
*/
#include "crypto.h"
// lib includes
#include <openssl/pem.h>
#include <openssl/rsa.h>
// local includes
#include "crypto.h"
namespace crypto {
using asn1_string_t = util::safe_ptr<ASN1_STRING, ASN1_STRING_free>;
cert_chain_t::cert_chain_t():
_certs {}, _cert_ctx { X509_STORE_CTX_new() } {}
void
cert_chain_t::add(x509_t &&cert) {
x509_store_t x509_store { X509_STORE_new() };
_certs {},
_cert_ctx {X509_STORE_CTX_new()} {
}
void cert_chain_t::add(x509_t &&cert) {
x509_store_t x509_store {X509_STORE_new()};
X509_STORE_add_cert(x509_store.get(), cert.get());
_certs.emplace_back(std::make_pair(std::move(cert), std::move(x509_store)));
}
void
cert_chain_t::clear() {
void cert_chain_t::clear() {
_certs.clear();
}
static int
openssl_verify_cb(int ok, X509_STORE_CTX *ctx) {
static int openssl_verify_cb(int ok, X509_STORE_CTX *ctx) {
int err_code = X509_STORE_CTX_get_error(ctx);
switch (err_code) {
@@ -52,8 +56,7 @@ namespace crypto {
* @param cert The certificate to verify.
* @return nullptr if the certificate is valid, otherwise an error string.
*/
const char *
cert_chain_t::verify(x509_t::element_type *cert) {
const char *cert_chain_t::verify(x509_t::element_type *cert) {
int err_code = 0;
for (auto &[_, x509_store] : _certs) {
auto fg = util::fail_guard([this]() {
@@ -86,8 +89,7 @@ namespace crypto {
namespace cipher {
static int
init_decrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
static int init_decrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
ctx.reset(EVP_CIPHER_CTX_new());
if (!ctx) {
@@ -110,8 +112,7 @@ namespace crypto {
return 0;
}
static int
init_encrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
static int init_encrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
ctx.reset(EVP_CIPHER_CTX_new());
// Gen 7 servers use 128-bit AES ECB
@@ -131,8 +132,7 @@ namespace crypto {
return 0;
}
static int
init_encrypt_cbc(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
static int init_encrypt_cbc(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
ctx.reset(EVP_CIPHER_CTX_new());
// Gen 7 servers use 128-bit AES ECB
@@ -145,8 +145,7 @@ namespace crypto {
return 0;
}
int
gcm_t::decrypt(const std::string_view &tagged_cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv) {
int gcm_t::decrypt(const std::string_view &tagged_cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv) {
if (!decrypt_ctx && init_decrypt_gcm(decrypt_ctx, &key, iv, padding)) {
return -1;
}
@@ -185,8 +184,7 @@ namespace crypto {
* The function handles the creation and initialization of the encryption context, and manages the encryption process.
* The resulting ciphertext and the GCM tag are written into the tagged_cipher buffer.
*/
int
gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tag, std::uint8_t *ciphertext, aes_t *iv) {
int gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tag, std::uint8_t *ciphertext, aes_t *iv) {
if (!encrypt_ctx && init_encrypt_gcm(encrypt_ctx, &key, iv, padding)) {
return -1;
}
@@ -216,14 +214,12 @@ namespace crypto {
return update_outlen + final_outlen;
}
int
gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv) {
int gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv) {
// This overload handles the common case of [GCM tag][cipher text] buffer layout
return encrypt(plaintext, tagged_cipher, tagged_cipher + tag_size, iv);
}
int
ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
int ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
auto fg = util::fail_guard([this]() {
EVP_CIPHER_CTX_reset(decrypt_ctx.get());
});
@@ -250,8 +246,7 @@ namespace crypto {
return 0;
}
int
ecb_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
int ecb_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
auto fg = util::fail_guard([this]() {
EVP_CIPHER_CTX_reset(encrypt_ctx.get());
});
@@ -284,8 +279,7 @@ namespace crypto {
* The function handles the creation and initialization of the encryption context, and manages the encryption process.
* The resulting ciphertext is written into the cipher buffer.
*/
int
cbc_t::encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv) {
int cbc_t::encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv) {
if (!encrypt_ctx && init_encrypt_cbc(encrypt_ctx, &key, iv, padding)) {
return -1;
}
@@ -311,18 +305,20 @@ namespace crypto {
}
ecb_t::ecb_t(const aes_t &key, bool padding):
cipher_t { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_new(), key, padding } {}
cipher_t {EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_new(), key, padding} {
}
cbc_t::cbc_t(const aes_t &key, bool padding):
cipher_t { nullptr, nullptr, key, padding } {}
cipher_t {nullptr, nullptr, key, padding} {
}
gcm_t::gcm_t(const crypto::aes_t &key, bool padding):
cipher_t { nullptr, nullptr, key, padding } {}
cipher_t {nullptr, nullptr, key, padding} {
}
} // namespace cipher
aes_t
gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
aes_t key(16);
std::string salt_pin;
@@ -338,16 +334,14 @@ namespace crypto {
return key;
}
sha256_t
hash(const std::string_view &plaintext) {
sha256_t hash(const std::string_view &plaintext) {
sha256_t hsh;
EVP_Digest(plaintext.data(), plaintext.size(), hsh.data(), nullptr, EVP_sha256(), nullptr);
return hsh;
}
x509_t
x509(const std::string_view &x) {
bio_t io { BIO_new(BIO_s_mem()) };
x509_t x509(const std::string_view &x) {
bio_t io {BIO_new(BIO_s_mem())};
BIO_write(io.get(), x.data(), x.size());
@@ -357,9 +351,8 @@ namespace crypto {
return p;
}
pkey_t
pkey(const std::string_view &k) {
bio_t io { BIO_new(BIO_s_mem()) };
pkey_t pkey(const std::string_view &k) {
bio_t io {BIO_new(BIO_s_mem())};
BIO_write(io.get(), k.data(), k.size());
@@ -369,40 +362,36 @@ namespace crypto {
return p;
}
std::string
pem(x509_t &x509) {
bio_t bio { BIO_new(BIO_s_mem()) };
std::string pem(x509_t &x509) {
bio_t bio {BIO_new(BIO_s_mem())};
PEM_write_bio_X509(bio.get(), x509.get());
BUF_MEM *mem_ptr;
BIO_get_mem_ptr(bio.get(), &mem_ptr);
return { mem_ptr->data, mem_ptr->length };
return {mem_ptr->data, mem_ptr->length};
}
std::string
pem(pkey_t &pkey) {
bio_t bio { BIO_new(BIO_s_mem()) };
std::string pem(pkey_t &pkey) {
bio_t bio {BIO_new(BIO_s_mem())};
PEM_write_bio_PrivateKey(bio.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr);
BUF_MEM *mem_ptr;
BIO_get_mem_ptr(bio.get(), &mem_ptr);
return { mem_ptr->data, mem_ptr->length };
return {mem_ptr->data, mem_ptr->length};
}
std::string_view
signature(const x509_t &x) {
std::string_view signature(const x509_t &x) {
// X509_ALGOR *_ = nullptr;
const ASN1_BIT_STRING *asn1 = nullptr;
X509_get0_signature(&asn1, nullptr, x.get());
return { (const char *) asn1->data, (std::size_t) asn1->length };
return {(const char *) asn1->data, (std::size_t) asn1->length};
}
std::string
rand(std::size_t bytes) {
std::string rand(std::size_t bytes) {
std::string r;
r.resize(bytes);
@@ -411,9 +400,8 @@ namespace crypto {
return r;
}
std::vector<uint8_t>
sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
md_ctx_t ctx { EVP_MD_CTX_create() };
std::vector<uint8_t> sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
md_ctx_t ctx {EVP_MD_CTX_create()};
if (EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, (EVP_PKEY *) pkey.get()) != 1) {
return {};
@@ -436,10 +424,9 @@ namespace crypto {
return digest;
}
creds_t
gen_creds(const std::string_view &cn, std::uint32_t key_bits) {
x509_t x509 { X509_new() };
pkey_ctx_t ctx { EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr) };
creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits) {
x509_t x509 {X509_new()};
pkey_ctx_t ctx {EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr)};
pkey_t pkey;
EVP_PKEY_keygen_init(ctx.get());
@@ -449,7 +436,7 @@ namespace crypto {
X509_set_version(x509.get(), 2);
// Generate a real serial number to avoid SEC_ERROR_REUSED_ISSUER_AND_SERIAL with Firefox
bignum_t serial { BN_new() };
bignum_t serial {BN_new()};
BN_rand(serial.get(), 159, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); // 159 bits to fit in 20 bytes in DER format
BN_set_negative(serial.get(), 0); // Serial numbers must be positive
BN_to_ASN1_INTEGER(serial.get(), X509_get_serialNumber(x509.get()));
@@ -459,8 +446,8 @@ namespace crypto {
X509_gmtime_adj(X509_get_notBefore(x509.get()), 0);
X509_gmtime_adj(X509_get_notAfter(x509.get()), 20 * year);
#else
asn1_string_t not_before { ASN1_STRING_dup(X509_get0_notBefore(x509.get())) };
asn1_string_t not_after { ASN1_STRING_dup(X509_get0_notAfter(x509.get())) };
asn1_string_t not_before {ASN1_STRING_dup(X509_get0_notBefore(x509.get()))};
asn1_string_t not_after {ASN1_STRING_dup(X509_get0_notAfter(x509.get()))};
X509_gmtime_adj(not_before.get(), 0);
X509_gmtime_adj(not_after.get(), 20 * year);
@@ -472,26 +459,22 @@ namespace crypto {
X509_set_pubkey(x509.get(), pkey.get());
auto name = X509_get_subject_name(x509.get());
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
(const std::uint8_t *) cn.data(), cn.size(),
-1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const std::uint8_t *) cn.data(), cn.size(), -1, 0);
X509_set_issuer_name(x509.get(), name);
X509_sign(x509.get(), pkey.get(), EVP_sha256());
return { pem(x509), pem(pkey) };
return {pem(x509), pem(pkey)};
}
std::vector<uint8_t>
sign256(const pkey_t &pkey, const std::string_view &data) {
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data) {
return sign(pkey, data, EVP_sha256());
}
bool
verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
bool verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
auto pkey = X509_get0_pubkey(x509.get());
md_ctx_t ctx { EVP_MD_CTX_create() };
md_ctx_t ctx {EVP_MD_CTX_create()};
if (EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pkey) != 1) {
return false;
@@ -508,18 +491,15 @@ namespace crypto {
return true;
}
bool
verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
return verify(x509, data, signature, EVP_sha256());
}
void
md_ctx_destroy(EVP_MD_CTX *ctx) {
void md_ctx_destroy(EVP_MD_CTX *ctx) {
EVP_MD_CTX_destroy(ctx);
}
std::string
rand_alphabet(std::size_t bytes, const std::string_view &alphabet) {
std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet) {
auto value = rand(bytes);
for (std::size_t i = 0; i != value.size(); ++i) {

View File

@@ -4,12 +4,16 @@
*/
#pragma once
// standard includes
#include <array>
// lib includes
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <openssl/x509.h>
// local includes
#include "utility.h"
namespace crypto {
@@ -18,8 +22,7 @@ namespace crypto {
std::string pkey;
};
void
md_ctx_destroy(EVP_MD_CTX *);
void md_ctx_destroy(EVP_MD_CTX *);
using sha256_t = std::array<std::uint8_t, SHA256_DIGEST_LENGTH>;
@@ -39,50 +42,33 @@ namespace crypto {
* @param plaintext
* @return The SHA-256 hash of the plaintext.
*/
sha256_t
hash(const std::string_view &plaintext);
sha256_t hash(const std::string_view &plaintext);
aes_t
gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin);
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin);
x509_t x509(const std::string_view &x);
pkey_t pkey(const std::string_view &k);
std::string pem(x509_t &x509);
std::string pem(pkey_t &pkey);
x509_t
x509(const std::string_view &x);
pkey_t
pkey(const std::string_view &k);
std::string
pem(x509_t &x509);
std::string
pem(pkey_t &pkey);
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data);
bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature);
std::vector<uint8_t>
sign256(const pkey_t &pkey, const std::string_view &data);
bool
verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature);
creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits);
creds_t
gen_creds(const std::string_view &cn, std::uint32_t key_bits);
std::string_view signature(const x509_t &x);
std::string_view
signature(const x509_t &x);
std::string
rand(std::size_t bytes);
std::string
rand_alphabet(std::size_t bytes,
const std::string_view &alphabet = std::string_view { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-" });
std::string rand(std::size_t bytes);
std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet = std::string_view {"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-"});
class cert_chain_t {
public:
KITTY_DECL_CONSTR(cert_chain_t)
void
add(x509_t &&cert);
void add(x509_t &&cert);
void
clear();
void clear();
const char *
verify(x509_t::element_type *cert);
const char *verify(x509_t::element_type *cert);
private:
std::vector<std::pair<x509_t, x509_store_t>> _certs;
@@ -91,8 +77,8 @@ namespace crypto {
namespace cipher {
constexpr std::size_t tag_size = 16;
constexpr std::size_t
round_to_pkcs7_padded(std::size_t size) {
constexpr std::size_t round_to_pkcs7_padded(std::size_t size) {
return ((size + 15) / 16) * 16;
}
@@ -110,23 +96,19 @@ namespace crypto {
public:
ecb_t() = default;
ecb_t(ecb_t &&) noexcept = default;
ecb_t &
operator=(ecb_t &&) noexcept = default;
ecb_t &operator=(ecb_t &&) noexcept = default;
ecb_t(const aes_t &key, bool padding = true);
int
encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher);
int
decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
int encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher);
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
};
class gcm_t: public cipher_t {
public:
gcm_t() = default;
gcm_t(gcm_t &&) noexcept = default;
gcm_t &
operator=(gcm_t &&) noexcept = default;
gcm_t &operator=(gcm_t &&) noexcept = default;
gcm_t(const crypto::aes_t &key, bool padding = true);
@@ -138,8 +120,7 @@ namespace crypto {
* @param iv The initialization vector to be used for the encryption.
* @return The total length of the ciphertext and GCM tag. Returns -1 in case of an error.
*/
int
encrypt(const std::string_view &plaintext, std::uint8_t *tag, std::uint8_t *ciphertext, aes_t *iv);
int encrypt(const std::string_view &plaintext, std::uint8_t *tag, std::uint8_t *ciphertext, aes_t *iv);
/**
* @brief Encrypts the plaintext using AES GCM mode.
@@ -149,19 +130,16 @@ namespace crypto {
* @param iv The initialization vector to be used for the encryption.
* @return The total length of the ciphertext and GCM tag written into tagged_cipher. Returns -1 in case of an error.
*/
int
encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv);
int encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv);
int
decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv);
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv);
};
class cbc_t: public cipher_t {
public:
cbc_t() = default;
cbc_t(cbc_t &&) noexcept = default;
cbc_t &
operator=(cbc_t &&) noexcept = default;
cbc_t &operator=(cbc_t &&) noexcept = default;
cbc_t(const crypto::aes_t &key, bool padding = true);
@@ -173,8 +151,7 @@ namespace crypto {
* @param iv The initialization vector to be used for the encryption.
* @return The total length of the ciphertext written into cipher. Returns -1 in case of an error.
*/
int
encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv);
int encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv);
};
} // namespace cipher
} // namespace crypto

View File

@@ -29,15 +29,15 @@
namespace display_device {
namespace {
constexpr std::chrono::milliseconds DEFAULT_RETRY_INTERVAL { 5000 };
constexpr std::chrono::milliseconds DEFAULT_RETRY_INTERVAL {5000};
/**
* @brief A global for the settings manager interface and other settings whose lifetime is managed by `display_device::init(...)`.
*/
struct {
std::mutex mutex {};
std::chrono::milliseconds config_revert_delay { 0 };
std::unique_ptr<RetryScheduler<SettingsManagerInterface>> sm_instance { nullptr };
std::chrono::milliseconds config_revert_delay {0};
std::unique_ptr<RetryScheduler<SettingsManagerInterface>> sm_instance {nullptr};
} DD_DATA;
/**
@@ -49,8 +49,7 @@ namespace display_device {
*/
class sunshine_audio_context_t: public AudioContextInterface {
public:
[[nodiscard]] bool
capture() override {
[[nodiscard]] bool capture() override {
return context_scheduler.execute([](auto &audio_context) {
// Explicitly releasing the context first in case it was not release yet so that it can be potentially cleaned up.
audio_context = boost::none;
@@ -61,8 +60,7 @@ namespace display_device {
});
}
[[nodiscard]] bool
isCaptured() const override {
[[nodiscard]] bool isCaptured() const override {
return context_scheduler.execute([](const auto &audio_context) {
if (audio_context) {
// In case we still have context we need to check whether it was released or not.
@@ -74,8 +72,7 @@ namespace display_device {
});
}
void
release() override {
void release() override {
context_scheduler.schedule([](auto &audio_context, auto &stop_token) {
if (audio_context) {
audio_context->released = true;
@@ -93,7 +90,7 @@ namespace display_device {
audio_context = boost::none;
stop_token.requestStop();
},
SchedulerOptions { .m_sleep_durations = { 2s } });
SchedulerOptions {.m_sleep_durations = {2s}});
}
private:
@@ -102,20 +99,20 @@ namespace display_device {
* @brief A reference to the audio context that will automatically extend the audio session.
* @note It is auto-initialized here for convenience.
*/
decltype(audio::get_audio_ctx_ref()) audio_ctx_ref { audio::get_audio_ctx_ref() };
decltype(audio::get_audio_ctx_ref()) audio_ctx_ref {audio::get_audio_ctx_ref()};
/**
* @brief Will be set to true if the capture was released, but we still have to keep the context around, because the device is not available.
*/
bool released { false };
bool released {false};
/**
* @brief How many times to check if the audio sink is available before giving up.
*/
int retry_counter { 15 };
int retry_counter {15};
};
RetryScheduler<boost::optional<audio_context_t>> context_scheduler { std::make_unique<boost::optional<audio_context_t>>(boost::none) };
RetryScheduler<boost::optional<audio_context_t>> context_scheduler {std::make_unique<boost::optional<audio_context_t>>(boost::none)};
};
/**
@@ -124,9 +121,8 @@ namespace display_device {
* @param value String to be converted
* @return Parsed unsigned integer.
*/
unsigned int
stou(const std::string &value) {
unsigned long result { std::stoul(value) };
unsigned int stou(const std::string &value) {
unsigned long result {std::stoul(value)};
if (result > std::numeric_limits<unsigned int>::max()) {
throw std::out_of_range("stou");
}
@@ -151,10 +147,9 @@ namespace display_device {
* }
* @examples_end
*/
bool
parse_resolution_string(const std::string &input, std::optional<Resolution> &output) {
const std::string trimmed_input { boost::algorithm::trim_copy(input) };
const std::regex resolution_regex { R"(^(\d+)x(\d+)$)" };
bool parse_resolution_string(const std::string &input, std::optional<Resolution> &output) {
const std::string trimmed_input {boost::algorithm::trim_copy(input)};
const std::regex resolution_regex {R"(^(\d+)x(\d+)$)"};
if (std::smatch match; std::regex_match(trimmed_input, match, resolution_regex)) {
try {
@@ -163,16 +158,13 @@ namespace display_device {
stou(match[2].str())
};
return true;
}
catch (const std::out_of_range &) {
} catch (const std::out_of_range &) {
BOOST_LOG(error) << "Failed to parse resolution string " << trimmed_input << " (number out of range).";
}
catch (const std::exception &err) {
} catch (const std::exception &err) {
BOOST_LOG(error) << "Failed to parse resolution string " << trimmed_input << ":\n"
<< err.what();
}
}
else {
} else {
if (trimmed_input.empty()) {
output = std::nullopt;
return true;
@@ -203,16 +195,17 @@ namespace display_device {
* }
* @examples_end
*/
bool
parse_refresh_rate_string(const std::string &input, std::optional<FloatingPoint> &output, const bool allow_decimal_point = true) {
static const auto is_zero { [](const auto &character) { return character == '0'; } };
const std::string trimmed_input { boost::algorithm::trim_copy(input) };
const std::regex refresh_rate_regex { allow_decimal_point ? R"(^(\d+)(?:\.(\d+))?$)" : R"(^(\d+)$)" };
bool parse_refresh_rate_string(const std::string &input, std::optional<FloatingPoint> &output, const bool allow_decimal_point = true) {
static const auto is_zero {[](const auto &character) {
return character == '0';
}};
const std::string trimmed_input {boost::algorithm::trim_copy(input)};
const std::regex refresh_rate_regex {allow_decimal_point ? R"(^(\d+)(?:\.(\d+))?$)" : R"(^(\d+)$)"};
if (std::smatch match; std::regex_match(trimmed_input, match, refresh_rate_regex)) {
try {
// Here we are trimming zeros from the string to possibly reduce out of bounds case
std::string trimmed_match_1 { boost::algorithm::trim_left_copy_if(match[1].str(), is_zero) };
std::string trimmed_match_1 {boost::algorithm::trim_left_copy_if(match[1].str(), is_zero)};
if (trimmed_match_1.empty()) {
trimmed_match_1 = "0"s; // Just in case ALL the string is full of zeros, we want to leave one
}
@@ -230,33 +223,29 @@ namespace display_device {
// denominator = 1000
// We are essentially removing the decimal point here: 59.995 -> 59995
const std::string numerator_str { trimmed_match_1 + trimmed_match_2 };
const auto numerator { stou(numerator_str) };
const std::string numerator_str {trimmed_match_1 + trimmed_match_2};
const auto numerator {stou(numerator_str)};
// Here we are counting decimal places and calculating denominator: 10^decimal_places
const auto denominator { static_cast<unsigned int>(std::pow(10, trimmed_match_2.size())) };
const auto denominator {static_cast<unsigned int>(std::pow(10, trimmed_match_2.size()))};
output = Rational { numerator, denominator };
}
else {
output = Rational {numerator, denominator};
} else {
// We do not have a decimal point, just a valid number.
// For example:
// 60:
// numerator = 60
// denominator = 1
output = Rational { stou(trimmed_match_1), 1 };
output = Rational {stou(trimmed_match_1), 1};
}
return true;
}
catch (const std::out_of_range &) {
} catch (const std::out_of_range &) {
BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << " (number out of range).";
}
catch (const std::exception &err) {
} catch (const std::exception &err) {
BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << ":\n"
<< err.what();
}
}
else {
} else {
if (trimmed_input.empty()) {
output = std::nullopt;
return true;
@@ -279,8 +268,7 @@ namespace display_device {
* const auto device_prep_option = parse_device_prep_option(video_config);
* @examples_end
*/
std::optional<SingleDisplayConfiguration::DevicePreparation>
parse_device_prep_option(const config::video_t &video_config) {
std::optional<SingleDisplayConfiguration::DevicePreparation> parse_device_prep_option(const config::video_t &video_config) {
using enum config::video_t::dd_t::config_option_e;
using enum SingleDisplayConfiguration::DevicePreparation;
@@ -315,44 +303,42 @@ namespace display_device {
* const bool success = parse_resolution_option(video_config, *launch_session, config);
* @examples_end
*/
bool
parse_resolution_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
bool parse_resolution_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
using resolution_option_e = config::video_t::dd_t::resolution_option_e;
switch (video_config.dd.resolution_option) {
case resolution_option_e::automatic: {
if (!session.enable_sops) {
BOOST_LOG(warning) << R"(Sunshine is configured to change resolution automatically, but the "Optimize game settings" is not set in the client! Resolution will not be changed.)";
}
else if (session.width >= 0 && session.height >= 0) {
config.m_resolution = Resolution {
static_cast<unsigned int>(session.width),
static_cast<unsigned int>(session.height)
};
}
else {
BOOST_LOG(error) << "Resolution provided by client session config is invalid: " << session.width << "x" << session.height;
return false;
}
break;
}
case resolution_option_e::manual: {
if (!session.enable_sops) {
BOOST_LOG(warning) << R"(Sunshine is configured to change resolution manually, but the "Optimize game settings" is not set in the client! Resolution will not be changed.)";
}
else {
if (!parse_resolution_string(video_config.dd.manual_resolution, config.m_resolution)) {
BOOST_LOG(error) << "Failed to parse manual resolution string!";
case resolution_option_e::automatic:
{
if (!session.enable_sops) {
BOOST_LOG(warning) << R"(Sunshine is configured to change resolution automatically, but the "Optimize game settings" is not set in the client! Resolution will not be changed.)";
} else if (session.width >= 0 && session.height >= 0) {
config.m_resolution = Resolution {
static_cast<unsigned int>(session.width),
static_cast<unsigned int>(session.height)
};
} else {
BOOST_LOG(error) << "Resolution provided by client session config is invalid: " << session.width << "x" << session.height;
return false;
}
break;
}
case resolution_option_e::manual:
{
if (!session.enable_sops) {
BOOST_LOG(warning) << R"(Sunshine is configured to change resolution manually, but the "Optimize game settings" is not set in the client! Resolution will not be changed.)";
} else {
if (!parse_resolution_string(video_config.dd.manual_resolution, config.m_resolution)) {
BOOST_LOG(error) << "Failed to parse manual resolution string!";
return false;
}
if (!config.m_resolution) {
BOOST_LOG(error) << "Manual resolution must be specified!";
return false;
if (!config.m_resolution) {
BOOST_LOG(error) << "Manual resolution must be specified!";
return false;
}
}
break;
}
break;
}
case resolution_option_e::disabled:
break;
}
@@ -375,33 +361,33 @@ namespace display_device {
* const bool success = parse_refresh_rate_option(video_config, *launch_session, config);
* @examples_end
*/
bool
parse_refresh_rate_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
bool parse_refresh_rate_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
using refresh_rate_option_e = config::video_t::dd_t::refresh_rate_option_e;
switch (video_config.dd.refresh_rate_option) {
case refresh_rate_option_e::automatic: {
if (session.fps >= 0) {
config.m_refresh_rate = Rational { static_cast<unsigned int>(session.fps), 1 };
}
else {
BOOST_LOG(error) << "FPS value provided by client session config is invalid: " << session.fps;
return false;
}
break;
}
case refresh_rate_option_e::manual: {
if (!parse_refresh_rate_string(video_config.dd.manual_refresh_rate, config.m_refresh_rate)) {
BOOST_LOG(error) << "Failed to parse manual refresh rate string!";
return false;
case refresh_rate_option_e::automatic:
{
if (session.fps >= 0) {
config.m_refresh_rate = Rational {static_cast<unsigned int>(session.fps), 1};
} else {
BOOST_LOG(error) << "FPS value provided by client session config is invalid: " << session.fps;
return false;
}
break;
}
case refresh_rate_option_e::manual:
{
if (!parse_refresh_rate_string(video_config.dd.manual_refresh_rate, config.m_refresh_rate)) {
BOOST_LOG(error) << "Failed to parse manual refresh rate string!";
return false;
}
if (!config.m_refresh_rate) {
BOOST_LOG(error) << "Manual refresh rate must be specified!";
return false;
if (!config.m_refresh_rate) {
BOOST_LOG(error) << "Manual refresh rate must be specified!";
return false;
}
break;
}
break;
}
case refresh_rate_option_e::disabled:
break;
}
@@ -422,8 +408,7 @@ namespace display_device {
* const auto hdr_option = parse_hdr_option(video_config, *launch_session);
* @examples_end
*/
std::optional<HdrState>
parse_hdr_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
std::optional<HdrState> parse_hdr_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
using hdr_option_e = config::video_t::dd_t::hdr_option_e;
switch (video_config.dd.hdr_option) {
@@ -450,11 +435,10 @@ namespace display_device {
* @param video_config User's video related configuration.
* @returns Enum value if remapping can be performed, null optional if remapping shall be skipped.
*/
std::optional<remapping_type_e>
determine_remapping_type(const config::video_t &video_config) {
std::optional<remapping_type_e> determine_remapping_type(const config::video_t &video_config) {
using dd_t = config::video_t::dd_t;
const bool auto_resolution { video_config.dd.resolution_option == dd_t::resolution_option_e::automatic };
const bool auto_refresh_rate { video_config.dd.refresh_rate_option == dd_t::refresh_rate_option_e::automatic };
const bool auto_resolution {video_config.dd.resolution_option == dd_t::resolution_option_e::automatic};
const bool auto_refresh_rate {video_config.dd.refresh_rate_option == dd_t::refresh_rate_option_e::automatic};
if (auto_resolution && auto_refresh_rate) {
return remapping_type_e::mixed;
@@ -486,8 +470,7 @@ namespace display_device {
* @param type Remapping type to check.
* @returns True if resolution is to be mapped, false otherwise.
*/
bool
is_resolution_mapped(const remapping_type_e type) {
bool is_resolution_mapped(const remapping_type_e type) {
return type == remapping_type_e::resolution_only || type == remapping_type_e::mixed;
}
@@ -496,8 +479,7 @@ namespace display_device {
* @param type Remapping type to check.
* @returns True if FPS is to be mapped, false otherwise.
*/
bool
is_fps_mapped(const remapping_type_e type) {
bool is_fps_mapped(const remapping_type_e type) {
return type == remapping_type_e::refresh_rate_only || type == remapping_type_e::mixed;
}
@@ -507,17 +489,16 @@ namespace display_device {
* @param type Specify which entry fields should be parsed.
* @returns Parsed structure or null optional if a necessary field could not be parsed.
*/
std::optional<parsed_remapping_entry_t>
parse_remapping_entry(const config::video_t::dd_t::mode_remapping_entry_t &entry, const remapping_type_e type) {
std::optional<parsed_remapping_entry_t> parse_remapping_entry(const config::video_t::dd_t::mode_remapping_entry_t &entry, const remapping_type_e type) {
parsed_remapping_entry_t result {};
if (is_resolution_mapped(type) && (!parse_resolution_string(entry.requested_resolution, result.requested_resolution) ||
!parse_resolution_string(entry.final_resolution, result.final_resolution))) {
!parse_resolution_string(entry.final_resolution, result.final_resolution))) {
return std::nullopt;
}
if (is_fps_mapped(type) && (!parse_refresh_rate_string(entry.requested_fps, result.requested_fps, false) ||
!parse_refresh_rate_string(entry.final_refresh_rate, result.final_refresh_rate))) {
!parse_refresh_rate_string(entry.final_refresh_rate, result.final_refresh_rate))) {
return std::nullopt;
}
@@ -539,14 +520,13 @@ namespace display_device {
* const bool success = remap_display_mode_if_needed(video_config, *launch_session, config);
* @examples_end
*/
bool
remap_display_mode_if_needed(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
const auto remapping_type { determine_remapping_type(video_config) };
bool remap_display_mode_if_needed(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
const auto remapping_type {determine_remapping_type(video_config)};
if (!remapping_type) {
return true;
}
const auto &remapping_list { [&]() {
const auto &remapping_list {[&]() {
using enum remapping_type_e;
switch (*remapping_type) {
@@ -558,7 +538,7 @@ namespace display_device {
default:
return video_config.dd.mode_remapping.mixed;
}
}() };
}()};
if (remapping_list.empty()) {
BOOST_LOG(debug) << "No values are available for display mode remapping.";
@@ -566,9 +546,9 @@ namespace display_device {
}
BOOST_LOG(debug) << "Trying to remap display modes...";
const auto entry_to_string { [type = *remapping_type](const config::video_t::dd_t::mode_remapping_entry_t &entry) {
const bool mapping_resolution { is_resolution_mapped(type) };
const bool mapping_fps { is_fps_mapped(type) };
const auto entry_to_string {[type = *remapping_type](const config::video_t::dd_t::mode_remapping_entry_t &entry) {
const bool mapping_resolution {is_resolution_mapped(type)};
const bool mapping_fps {is_fps_mapped(type)};
// clang-format off
return (mapping_resolution ? " - requested resolution: "s + entry.requested_resolution + "\n" : "") +
@@ -576,10 +556,10 @@ namespace display_device {
(mapping_resolution ? " - final resolution: "s + entry.final_resolution + "\n" : "") +
(mapping_fps ? " - final refresh rate: "s + entry.final_refresh_rate : "");
// clang-format on
} };
}};
for (const auto &entry : remapping_list) {
const auto parsed_entry { parse_remapping_entry(entry, *remapping_type) };
const auto parsed_entry {parse_remapping_entry(entry, *remapping_type)};
if (!parsed_entry) {
BOOST_LOG(error) << "Failed to parse remapping entry from:\n"
<< entry_to_string(entry);
@@ -632,16 +612,18 @@ namespace display_device {
* @param video_config User's video related configuration.
* @return An interface or nullptr if the OS does not support the interface.
*/
std::unique_ptr<SettingsManagerInterface>
make_settings_manager([[maybe_unused]] const std::filesystem::path &persistence_filepath, [[maybe_unused]] const config::video_t &video_config) {
std::unique_ptr<SettingsManagerInterface> make_settings_manager([[maybe_unused]] const std::filesystem::path &persistence_filepath, [[maybe_unused]] const config::video_t &video_config) {
#ifdef _WIN32
return std::make_unique<SettingsManager>(
std::make_shared<WinDisplayDevice>(std::make_shared<WinApiLayer>()),
std::make_shared<sunshine_audio_context_t>(),
std::make_unique<PersistentState>(
std::make_shared<FileSettingsPersistence>(persistence_filepath)),
std::make_shared<FileSettingsPersistence>(persistence_filepath)
),
WinWorkarounds {
.m_hdr_blank_delay = video_config.dd.wa.hdr_toggle ? std::make_optional(500ms) : std::nullopt });
.m_hdr_blank_delay = video_config.dd.wa.hdr_toggle ? std::make_optional(500ms) : std::nullopt
}
);
#else
return nullptr;
#endif
@@ -660,17 +642,16 @@ namespace display_device {
* @brief Reverts the configuration based on the provided option.
* @note This is function does not lock mutex.
*/
void
revert_configuration_unlocked(const revert_option_e option) {
void revert_configuration_unlocked(const revert_option_e option) {
if (!DD_DATA.sm_instance) {
// Platform is not supported, nothing to do.
return;
}
// Note: by default the executor function is immediately executed in the calling thread. With delay, we want to avoid that.
SchedulerOptions scheduler_option { .m_sleep_durations = { DEFAULT_RETRY_INTERVAL } };
SchedulerOptions scheduler_option {.m_sleep_durations = {DEFAULT_RETRY_INTERVAL}};
if (option == revert_option_e::try_indefinitely_with_delay && DD_DATA.config_revert_delay > std::chrono::milliseconds::zero()) {
scheduler_option.m_sleep_durations = { DD_DATA.config_revert_delay, DEFAULT_RETRY_INTERVAL };
scheduler_option.m_sleep_durations = {DD_DATA.config_revert_delay, DEFAULT_RETRY_INTERVAL};
scheduler_option.m_execution = SchedulerOptions::Execution::ScheduledOnly;
}
@@ -681,17 +662,21 @@ namespace display_device {
return;
}
auto available_devices { [&settings_iface]() {
const auto devices { settings_iface.enumAvailableDevices() };
auto available_devices {[&settings_iface]() {
const auto devices {settings_iface.enumAvailableDevices()};
std::set<std::string> parsed_devices;
std::transform(
std::begin(devices), std::end(devices),
std::begin(devices),
std::end(devices),
std::inserter(parsed_devices, std::end(parsed_devices)),
[](const auto &device) { return device.m_device_id + " - " + device.m_friendly_name; });
[](const auto &device) {
return device.m_device_id + " - " + device.m_friendly_name;
}
);
return parsed_devices;
}() };
}()};
if (available_devices == tried_out_devices) {
BOOST_LOG(debug) << "Skipping reverting configuration, because no newly added/removed devices were detected since last check. Currently available devices:\n"
<< toJson(available_devices);
@@ -699,11 +684,10 @@ namespace display_device {
}
using enum SettingsManagerInterface::RevertResult;
if (const auto result { settings_iface.revertSettings() }; result == Ok) {
if (const auto result {settings_iface.revertSettings()}; result == Ok) {
stop_token.requestStop();
return;
}
else if (result == ApiTemporarilyUnavailable) {
} else if (result == ApiTemporarilyUnavailable) {
// Do nothing and retry next time
return;
}
@@ -713,13 +697,12 @@ namespace display_device {
<< toJson(available_devices);
tried_out_devices.swap(available_devices);
},
scheduler_option);
scheduler_option);
}
} // namespace
std::unique_ptr<platf::deinit_t>
init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config) {
std::lock_guard lock { DD_DATA.mutex };
std::unique_ptr<platf::deinit_t> init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config) {
std::lock_guard lock {DD_DATA.mutex};
// We can support re-init without any issues, however we should make sure to clean up first!
revert_configuration_unlocked(revert_option_e::try_once);
DD_DATA.config_revert_delay = video_config.dd.config_revert_delay;
@@ -727,10 +710,12 @@ namespace display_device {
// If we fail to create settings manager, this means platform is not supported, and
// we will need to provided error-free pass-trough in other methods
if (auto settings_manager { make_settings_manager(persistence_filepath, video_config) }) {
if (auto settings_manager {make_settings_manager(persistence_filepath, video_config)}) {
DD_DATA.sm_instance = std::make_unique<RetryScheduler<SettingsManagerInterface>>(std::move(settings_manager));
const auto available_devices { DD_DATA.sm_instance->execute([](auto &settings_iface) { return settings_iface.enumAvailableDevices(); }) };
const auto available_devices {DD_DATA.sm_instance->execute([](auto &settings_iface) {
return settings_iface.enumAvailableDevices();
})};
BOOST_LOG(info) << "Currently available display devices:\n"
<< toJson(available_devices);
@@ -742,44 +727,44 @@ namespace display_device {
class deinit_t: public platf::deinit_t {
public:
~deinit_t() override {
std::lock_guard lock { DD_DATA.mutex };
std::lock_guard lock {DD_DATA.mutex};
try {
// This may throw if used incorrectly. At the moment this will not happen, however
// in case some unforeseen changes are made that could raise an exception,
// we definitely don't want this to happen in destructor. Especially in the
// deinit_t where the outcome does not really matter.
revert_configuration_unlocked(revert_option_e::try_once);
}
catch (std::exception &err) {
} catch (std::exception &err) {
BOOST_LOG(fatal) << err.what();
}
DD_DATA.sm_instance = nullptr;
}
};
return std::make_unique<deinit_t>();
}
std::string
map_output_name(const std::string &output_name) {
std::lock_guard lock { DD_DATA.mutex };
std::string map_output_name(const std::string &output_name) {
std::lock_guard lock {DD_DATA.mutex};
if (!DD_DATA.sm_instance) {
// Fallback to giving back the output name if the platform is not supported.
return output_name;
}
return DD_DATA.sm_instance->execute([&output_name](auto &settings_iface) { return settings_iface.getDisplayName(output_name); });
return DD_DATA.sm_instance->execute([&output_name](auto &settings_iface) {
return settings_iface.getDisplayName(output_name);
});
}
void
configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
const auto result { parse_configuration(video_config, session) };
if (const auto *parsed_config { std::get_if<SingleDisplayConfiguration>(&result) }; parsed_config) {
void configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
const auto result {parse_configuration(video_config, session)};
if (const auto *parsed_config {std::get_if<SingleDisplayConfiguration>(&result)}; parsed_config) {
configure_display(*parsed_config);
return;
}
if (const auto *disabled { std::get_if<configuration_disabled_tag_t>(&result) }; disabled) {
if (const auto *disabled {std::get_if<configuration_disabled_tag_t>(&result)}; disabled) {
revert_configuration();
return;
}
@@ -788,9 +773,8 @@ namespace display_device {
// want to revert active configuration in case we have any
}
void
configure_display(const SingleDisplayConfiguration &config) {
std::lock_guard lock { DD_DATA.mutex };
void configure_display(const SingleDisplayConfiguration &config) {
std::lock_guard lock {DD_DATA.mutex};
if (!DD_DATA.sm_instance) {
// Platform is not supported, nothing to do.
return;
@@ -803,18 +787,16 @@ namespace display_device {
stop_token.requestStop();
}
},
{ .m_sleep_durations = { DEFAULT_RETRY_INTERVAL } });
{.m_sleep_durations = {DEFAULT_RETRY_INTERVAL}});
}
void
revert_configuration() {
std::lock_guard lock { DD_DATA.mutex };
void revert_configuration() {
std::lock_guard lock {DD_DATA.mutex};
revert_configuration_unlocked(revert_option_e::try_indefinitely_with_delay);
}
bool
reset_persistence() {
std::lock_guard lock { DD_DATA.mutex };
bool reset_persistence() {
std::lock_guard lock {DD_DATA.mutex};
if (!DD_DATA.sm_instance) {
// Platform is not supported, assume success.
return true;
@@ -828,9 +810,20 @@ namespace display_device {
});
}
std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration>
parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
const auto device_prep { parse_device_prep_option(video_config) };
EnumeratedDeviceList enumerate_devices() {
std::lock_guard lock {DD_DATA.mutex};
if (!DD_DATA.sm_instance) {
// Platform is not supported.
return {};
}
return DD_DATA.sm_instance->execute([](auto &settings_iface) {
return settings_iface.enumAvailableDevices();
});
}
std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration> parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
const auto device_prep {parse_device_prep_option(video_config)};
if (!device_prep) {
return configuration_disabled_tag_t {};
}

View File

@@ -4,18 +4,22 @@
*/
#pragma once
// lib includes
#include <display_device/types.h>
// standard includes
#include <filesystem>
#include <memory>
// lib includes
#include <display_device/types.h>
// forward declarations
namespace platf {
class deinit_t;
}
namespace config {
struct video_t;
}
namespace rtsp_stream {
struct launch_session_t;
}
@@ -32,8 +36,7 @@ namespace display_device {
* const auto init_guard { init("/my/persitence/file.state", video_config) };
* @examples_end
*/
[[nodiscard]] std::unique_ptr<platf::deinit_t>
init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config);
[[nodiscard]] std::unique_ptr<platf::deinit_t> init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config);
/**
* @brief Map the output name to a specific display.
@@ -45,8 +48,7 @@ namespace display_device {
* const auto mapped_name_custom { map_output_name("{some-device-id}") };
* @examples_end
*/
[[nodiscard]] std::string
map_output_name(const std::string &output_name);
[[nodiscard]] std::string map_output_name(const std::string &output_name);
/**
* @brief Configure the display device based on the user configuration and the session information.
@@ -62,8 +64,7 @@ namespace display_device {
* configure_display(video_config, *launch_session);
* @examples_end
*/
void
configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
void configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
/**
* @brief Configure the display device using the provided configuration.
@@ -83,8 +84,7 @@ namespace display_device {
* configure_display(valid_config);
* @examples_end
*/
void
configure_display(const SingleDisplayConfiguration &config);
void configure_display(const SingleDisplayConfiguration &config);
/**
* @brief Revert the display configuration and restore the previous state.
@@ -96,8 +96,7 @@ namespace display_device {
* revert_configuration();
* @examples_end
*/
void
revert_configuration();
void revert_configuration();
/**
* @brief Reset the persistence and currently held initial display state.
@@ -111,16 +110,25 @@ namespace display_device {
* The user then accepts that Sunshine is not able to restore the state and "agrees" to
* do it manually.
*
* @return
* @note Whether the function succeeds or fails, the any of the scheduled "retries" from
* @return True if persistence was reset, false otherwise.
* @note Whether the function succeeds or fails, any of the scheduled "retries" from
* other methods will be stopped to not interfere with the user actions.
*
* @examples
* const auto result = reset_persistence();
* @examples_end
*/
[[nodiscard]] bool
reset_persistence();
[[nodiscard]] bool reset_persistence();
/**
* @brief Enumerate the available devices.
* @return A list of devices.
*
* @examples
* const auto devices = enumerate_devices();
* @examples_end
*/
[[nodiscard]] EnumeratedDeviceList enumerate_devices();
/**
* @brief A tag structure indicating that configuration parsing has failed.
@@ -150,6 +158,5 @@ namespace display_device {
* }
* @examples_end
*/
[[nodiscard]] std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration>
parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
[[nodiscard]] std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration> parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
} // namespace display_device

View File

@@ -26,21 +26,18 @@ extern "C" {
using namespace std::literals;
void
launch_ui() {
void launch_ui() {
std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS));
platf::open_url(url);
}
void
launch_ui_with_path(std::string path) {
void launch_ui_with_path(std::string path) {
std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + path;
platf::open_url(url);
}
namespace args {
int
creds(const char *name, int argc, char *argv[]) {
int creds(const char *name, int argc, char *argv[]) {
if (argc < 2 || argv[0] == "help"sv || argv[1] == "help"sv) {
help(name);
}
@@ -50,21 +47,18 @@ namespace args {
return 0;
}
int
help(const char *name) {
int help(const char *name) {
logging::print_help(name);
return 0;
}
int
version() {
int version() {
// version was already logged at startup
return 0;
}
#ifdef _WIN32
int
restore_nvprefs_undo() {
int restore_nvprefs_undo() {
if (nvprefs_instance.load()) {
nvprefs_instance.restore_from_and_delete_undo_file_if_exists();
nvprefs_instance.unload();
@@ -78,8 +72,7 @@ namespace lifetime {
char **argv;
std::atomic_int desired_exit_code;
void
exit_sunshine(int exit_code, bool async) {
void exit_sunshine(int exit_code, bool async) {
// Store the exit code of the first exit_sunshine() call
int zero = 0;
desired_exit_code.compare_exchange_strong(zero, exit_code);
@@ -94,8 +87,7 @@ namespace lifetime {
}
}
void
debug_trap() {
void debug_trap() {
#ifdef _WIN32
DebugBreak();
#else
@@ -103,22 +95,19 @@ namespace lifetime {
#endif
}
char **
get_argv() {
char **get_argv() {
return argv;
}
} // namespace lifetime
void
log_publisher_data() {
void log_publisher_data() {
BOOST_LOG(info) << "Package Publisher: "sv << SUNSHINE_PUBLISHER_NAME;
BOOST_LOG(info) << "Publisher Website: "sv << SUNSHINE_PUBLISHER_WEBSITE;
BOOST_LOG(info) << "Get support: "sv << SUNSHINE_PUBLISHER_ISSUE_URL;
}
#ifdef _WIN32
bool
is_gamestream_enabled() {
bool is_gamestream_enabled() {
DWORD enabled;
DWORD size = sizeof(enabled);
return RegGetValueW(
@@ -128,7 +117,8 @@ is_gamestream_enabled() {
RRF_RT_REG_DWORD,
nullptr,
&enabled,
&size) == ERROR_SUCCESS &&
&size
) == ERROR_SUCCESS &&
enabled != 0;
}
@@ -168,8 +158,7 @@ namespace service_ctrl {
/**
* @brief Asynchronously starts the Sunshine service.
*/
bool
start_service() {
bool start_service() {
if (!service_handle) {
return false;
}
@@ -189,8 +178,7 @@ namespace service_ctrl {
* @brief Query the service status.
* @param status The SERVICE_STATUS struct to populate.
*/
bool
query_service_status(SERVICE_STATUS &status) {
bool query_service_status(SERVICE_STATUS &status) {
if (!service_handle) {
return false;
}
@@ -209,9 +197,8 @@ namespace service_ctrl {
SC_HANDLE service_handle = NULL;
};
bool
is_service_running() {
service_controller sc { SERVICE_QUERY_STATUS };
bool is_service_running() {
service_controller sc {SERVICE_QUERY_STATUS};
SERVICE_STATUS status;
if (!sc.query_service_status(status)) {
@@ -221,9 +208,8 @@ namespace service_ctrl {
return status.dwCurrentState == SERVICE_RUNNING;
}
bool
start_service() {
service_controller sc { SERVICE_QUERY_STATUS | SERVICE_START };
bool start_service() {
service_controller sc {SERVICE_QUERY_STATUS | SERVICE_START};
std::cout << "Starting Sunshine..."sv;
@@ -247,8 +233,7 @@ namespace service_ctrl {
return true;
}
bool
wait_for_ui_ready() {
bool wait_for_ui_ready() {
std::cout << "Waiting for Web UI to be ready...";
// Wait up to 30 seconds for the web UI to start

View File

@@ -18,8 +18,7 @@
* launch_ui();
* @examples_end
*/
void
launch_ui();
void launch_ui();
/**
* @brief Launch the Web UI at a specific endpoint.
@@ -27,8 +26,7 @@ launch_ui();
* launch_ui_with_path("/pin");
* @examples_end
*/
void
launch_ui_with_path(std::string path);
void launch_ui_with_path(std::string path);
/**
* @brief Functions for handling command line arguments.
@@ -43,8 +41,7 @@ namespace args {
* creds("sunshine", 2, {"new_username", "new_password"});
* @examples_end
*/
int
creds(const char *name, int argc, char *argv[]);
int creds(const char *name, int argc, char *argv[]);
/**
* @brief Print help to stdout, then exit.
@@ -53,8 +50,7 @@ namespace args {
* help("sunshine");
* @examples_end
*/
int
help(const char *name);
int help(const char *name);
/**
* @brief Print the version to stdout, then exit.
@@ -62,8 +58,7 @@ namespace args {
* version();
* @examples_end
*/
int
version();
int version();
#ifdef _WIN32
/**
@@ -75,8 +70,7 @@ namespace args {
* restore_nvprefs_undo();
* @examples_end
*/
int
restore_nvprefs_undo();
int restore_nvprefs_undo();
#endif
} // namespace args
@@ -92,35 +86,30 @@ namespace lifetime {
* @param exit_code The exit code to return from main().
* @param async Specifies whether our termination will be non-blocking.
*/
void
exit_sunshine(int exit_code, bool async);
void exit_sunshine(int exit_code, bool async);
/**
* @brief Breaks into the debugger or terminates Sunshine if no debugger is attached.
*/
void
debug_trap();
void debug_trap();
/**
* @brief Get the argv array passed to main().
*/
char **
get_argv();
char **get_argv();
} // namespace lifetime
/**
* @brief Log the publisher metadata provided from CMake.
*/
void
log_publisher_data();
void log_publisher_data();
#ifdef _WIN32
/**
* @brief Check if NVIDIA's GameStream software is running.
* @return `true` if GameStream is enabled, `false` otherwise.
*/
bool
is_gamestream_enabled();
bool is_gamestream_enabled();
/**
* @brief Namespace for controlling the Sunshine service model on Windows.
@@ -132,8 +121,7 @@ namespace service_ctrl {
* is_service_running();
* @examples_end
*/
bool
is_service_running();
bool is_service_running();
/**
* @brief Start the service and wait for startup to complete.
@@ -141,8 +129,7 @@ namespace service_ctrl {
* start_service();
* @examples_end
*/
bool
start_service();
bool start_service();
/**
* @brief Wait for the UI to be ready after Sunshine startup.
@@ -150,7 +137,6 @@ namespace service_ctrl {
* wait_for_ui_ready();
* @examples_end
*/
bool
wait_for_ui_ready();
bool wait_for_ui_ready();
} // namespace service_ctrl
#endif

View File

@@ -12,8 +12,7 @@
#include "logging.h"
namespace file_handler {
std::string
get_parent_directory(const std::string &path) {
std::string get_parent_directory(const std::string &path) {
// remove any trailing path separators
std::string trimmed_path = path;
while (!trimmed_path.empty() && trimmed_path.back() == '/') {
@@ -24,8 +23,7 @@ namespace file_handler {
return p.parent_path().string();
}
bool
make_directory(const std::string &path) {
bool make_directory(const std::string &path) {
// first, check if the directory already exists
if (std::filesystem::exists(path)) {
return true;
@@ -34,19 +32,17 @@ namespace file_handler {
return std::filesystem::create_directories(path);
}
std::string
read_file(const char *path) {
std::string read_file(const char *path) {
if (!std::filesystem::exists(path)) {
BOOST_LOG(debug) << "Missing file: " << path;
return {};
}
std::ifstream in(path);
return std::string { (std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>() };
return std::string {(std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>()};
}
int
write_file(const char *path, const std::string_view &contents) {
int write_file(const char *path, const std::string_view &contents) {
std::ofstream out(path);
if (!out.is_open()) {

View File

@@ -4,6 +4,7 @@
*/
#pragma once
// standard includes
#include <string>
/**
@@ -18,8 +19,7 @@ namespace file_handler {
* std::string parent_dir = get_parent_directory("path/to/file");
* @examples_end
*/
std::string
get_parent_directory(const std::string &path);
std::string get_parent_directory(const std::string &path);
/**
* @brief Make a directory.
@@ -29,8 +29,7 @@ namespace file_handler {
* bool dir_created = make_directory("path/to/directory");
* @examples_end
*/
bool
make_directory(const std::string &path);
bool make_directory(const std::string &path);
/**
* @brief Read a file to string.
@@ -40,8 +39,7 @@ namespace file_handler {
* std::string contents = read_file("path/to/file");
* @examples_end
*/
std::string
read_file(const char *path);
std::string read_file(const char *path);
/**
* @brief Writes a file.
@@ -52,6 +50,5 @@ namespace file_handler {
* int write_status = write_file("path/to/file", "file contents");
* @examples_end
*/
int
write_file(const char *path, const std::string_view &contents);
int write_file(const char *path, const std::string_view &contents);
} // namespace file_handler

View File

@@ -2,6 +2,7 @@
* @file globals.cpp
* @brief Definitions for globally accessible variables and functions.
*/
// local includes
#include "globals.h"
safe::mail_t mail::man;

View File

@@ -4,6 +4,7 @@
*/
#pragma once
// local includes
#include "entry_handler.h"
#include "thread_pool.h"
@@ -31,9 +32,9 @@ extern nvprefs::nvprefs_interface nvprefs_instance;
* @brief Handles process-wide communication.
*/
namespace mail {
#define MAIL(x) \
#define MAIL(x) \
constexpr auto x = std::string_view { \
#x \
#x \
}
/**

View File

@@ -4,22 +4,21 @@
*/
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include "process.h"
// standard includes
#include <filesystem>
#include <utility>
// lib includes
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/ssl/context_base.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/asio/ssl/context.hpp>
#include <curl/curl.h>
#include <Simple-Web-Server/server_http.hpp>
#include <Simple-Web-Server/server_https.hpp>
#include <boost/asio/ssl/context_base.hpp>
#include <curl/curl.h>
// local includes
#include "config.h"
#include "crypto.h"
#include "file_handler.h"
@@ -28,6 +27,7 @@
#include "network.h"
#include "nvhttp.h"
#include "platform/common.h"
#include "process.h"
#include "rtsp.h"
#include "utility.h"
#include "uuid.h"
@@ -37,16 +37,13 @@ namespace http {
namespace fs = std::filesystem;
namespace pt = boost::property_tree;
int
reload_user_creds(const std::string &file);
bool
user_creds_exist(const std::string &file);
int reload_user_creds(const std::string &file);
bool user_creds_exist(const std::string &file);
std::string unique_id;
net::net_e origin_web_ui_allowed;
int
init() {
int init() {
bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE];
origin_web_ui_allowed = net::from_enum_string(config::nvhttp.origin_web_ui_allowed);
@@ -57,29 +54,25 @@ namespace http {
config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string();
}
if (!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) {
if (create_creds(config::nvhttp.pkey, config::nvhttp.cert)) {
return -1;
}
if ((!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) &&
create_creds(config::nvhttp.pkey, config::nvhttp.cert)) {
return -1;
}
if (user_creds_exist(config::sunshine.credentials_file)) {
if (reload_user_creds(config::sunshine.credentials_file)) return -1;
}
else {
if (!user_creds_exist(config::sunshine.credentials_file)) {
BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started";
} else if (reload_user_creds(config::sunshine.credentials_file)) {
return -1;
}
return 0;
}
int
save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) {
int save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) {
pt::ptree outputTree;
if (fs::exists(file)) {
try {
pt::read_json(file, outputTree);
}
catch (std::exception &e) {
} catch (std::exception &e) {
BOOST_LOG(error) << "Couldn't read user credentials: "sv << e.what();
return -1;
}
@@ -91,8 +84,7 @@ namespace http {
outputTree.put("password", util::hex(crypto::hash(password + salt)).to_string());
try {
pt::write_json(file, outputTree);
}
catch (std::exception &e) {
} catch (std::exception &e) {
BOOST_LOG(error) << "error writing to the credentials file, perhaps try this again as an administrator? Details: "sv << e.what();
return -1;
}
@@ -101,8 +93,7 @@ namespace http {
return 0;
}
bool
user_creds_exist(const std::string &file) {
bool user_creds_exist(const std::string &file) {
if (!fs::exists(file)) {
return false;
}
@@ -113,32 +104,28 @@ namespace http {
return inputTree.find("username") != inputTree.not_found() &&
inputTree.find("password") != inputTree.not_found() &&
inputTree.find("salt") != inputTree.not_found();
}
catch (std::exception &e) {
} catch (std::exception &e) {
BOOST_LOG(error) << "validating user credentials: "sv << e.what();
}
return false;
}
int
reload_user_creds(const std::string &file) {
int reload_user_creds(const std::string &file) {
pt::ptree inputTree;
try {
pt::read_json(file, inputTree);
config::sunshine.username = inputTree.get<std::string>("username");
config::sunshine.password = inputTree.get<std::string>("password");
config::sunshine.salt = inputTree.get<std::string>("salt");
}
catch (std::exception &e) {
} catch (std::exception &e) {
BOOST_LOG(error) << "loading user credentials: "sv << e.what();
return -1;
}
return 0;
}
int
create_creds(const std::string &pkey, const std::string &cert) {
int create_creds(const std::string &pkey, const std::string &cert) {
fs::path pkey_path = pkey;
fs::path cert_path = cert;
@@ -172,18 +159,14 @@ namespace http {
return -1;
}
fs::permissions(pkey_path,
fs::perms::owner_read | fs::perms::owner_write,
fs::perm_options::replace, err_code);
fs::permissions(pkey_path, fs::perms::owner_read | fs::perms::owner_write, fs::perm_options::replace, err_code);
if (err_code) {
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message();
return -1;
}
fs::permissions(cert_path,
fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write,
fs::perm_options::replace, err_code);
fs::permissions(cert_path, fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write, fs::perm_options::replace, err_code);
if (err_code) {
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message();
@@ -193,21 +176,15 @@ namespace http {
return 0;
}
bool
download_file(const std::string &url, const std::string &file) {
CURL *curl = curl_easy_init();
if (curl) {
// sonar complains about weak ssl and tls versions
// ideally, the setopts should go after the early returns; however sonar cannot detect the fix
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
}
else {
bool download_file(const std::string &url, const std::string &file, long ssl_version) {
// sonar complains about weak ssl and tls versions; however sonar cannot detect the fix
CURL *curl = curl_easy_init(); // NOSONAR
if (!curl) {
BOOST_LOG(error) << "Couldn't create CURL instance";
return false;
}
std::string file_dir = file_handler::get_parent_directory(file);
if (!file_handler::make_directory(file_dir)) {
if (std::string file_dir = file_handler::get_parent_directory(file); !file_handler::make_directory(file_dir)) {
BOOST_LOG(error) << "Couldn't create directory ["sv << file_dir << ']';
curl_easy_cleanup(curl);
return false;
@@ -220,6 +197,7 @@ namespace http {
return false;
}
curl_easy_setopt(curl, CURLOPT_SSLVERSION, ssl_version); // NOSONAR
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
@@ -234,20 +212,16 @@ namespace http {
return result == CURLE_OK;
}
std::string
url_escape(const std::string &url) {
CURL *curl = curl_easy_init();
char *string = curl_easy_escape(curl, url.c_str(), url.length());
std::string url_escape(const std::string &url) {
char *string = curl_easy_escape(nullptr, url.c_str(), static_cast<int>(url.length()));
std::string result(string);
curl_free(string);
curl_easy_cleanup(curl);
return result;
}
std::string
url_get_host(const std::string &url) {
std::string url_get_host(const std::string &url) {
CURLU *curlu = curl_url();
curl_url_set(curlu, CURLUPART_URL, url.c_str(), url.length());
curl_url_set(curlu, CURLUPART_URL, url.c_str(), static_cast<unsigned int>(url.length()));
char *host;
if (curl_url_get(curlu, CURLUPART_HOST, &host, 0) != CURLUE_OK) {
curl_url_cleanup(curlu);
@@ -258,5 +232,4 @@ namespace http {
curl_url_cleanup(curlu);
return result;
}
} // namespace http

View File

@@ -4,30 +4,28 @@
*/
#pragma once
// lib includes
#include <curl/curl.h>
// local includes
#include "network.h"
#include "thread_safe.h"
namespace http {
int
init();
int
create_creds(const std::string &pkey, const std::string &cert);
int
save_user_creds(
int init();
int create_creds(const std::string &pkey, const std::string &cert);
int save_user_creds(
const std::string &file,
const std::string &username,
const std::string &password,
bool run_our_mouth = false);
bool run_our_mouth = false
);
int
reload_user_creds(const std::string &file);
bool
download_file(const std::string &url, const std::string &file);
std::string
url_escape(const std::string &url);
std::string
url_get_host(const std::string &url);
int reload_user_creds(const std::string &file);
bool download_file(const std::string &url, const std::string &file, long ssl_version = CURL_SSLVERSION_TLSv1_3);
std::string url_escape(const std::string &url);
std::string url_get_host(const std::string &url);
extern std::string unique_id;
extern net::net_e origin_web_ui_allowed;

View File

@@ -2,13 +2,13 @@
* @file src/input.cpp
* @brief Definitions for gamepad, keyboard, and mouse input handling.
*/
// define uint32_t for <moonlight-common-c/src/Input.h>
#include <cstdint>
extern "C" {
#include <moonlight-common-c/src/Input.h>
#include <moonlight-common-c/src/Limelight.h>
}
// standard includes
#include <bitset>
#include <chrono>
#include <cmath>
@@ -16,6 +16,10 @@ extern "C" {
#include <thread>
#include <unordered_map>
// lib includes
#include <boost/endian/buffers.hpp>
// local includes
#include "config.h"
#include "globals.h"
#include "input.h"
@@ -24,14 +28,13 @@ extern "C" {
#include "thread_pool.h"
#include "utility.h"
#include <boost/endian/buffers.hpp>
// Win32 WHEEL_DELTA constant
#ifndef WHEEL_DELTA
#define WHEEL_DELTA 120
#endif
using namespace std::literals;
namespace input {
constexpr auto MAX_GAMEPADS = std::min((std::size_t) platf::MAX_GAMEPADS, sizeof(std::int16_t) * 8);
@@ -54,9 +57,8 @@ namespace input {
UP ///< Button is up
};
template <std::size_t N>
int
alloc_id(std::bitset<N> &gamepad_mask) {
template<std::size_t N>
int alloc_id(std::bitset<N> &gamepad_mask) {
for (int x = 0; x < gamepad_mask.size(); ++x) {
if (!gamepad_mask[x]) {
gamepad_mask[x] = true;
@@ -67,23 +69,22 @@ namespace input {
return -1;
}
template <std::size_t N>
void
free_id(std::bitset<N> &gamepad_mask, int id) {
template<std::size_t N>
void free_id(std::bitset<N> &gamepad_mask, int id) {
gamepad_mask[id] = false;
}
typedef uint32_t key_press_id_t;
key_press_id_t
make_kpid(uint16_t vk, uint8_t flags) {
key_press_id_t make_kpid(uint16_t vk, uint8_t flags) {
return (key_press_id_t) vk << 8 | flags;
}
uint16_t
vk_from_kpid(key_press_id_t kpid) {
uint16_t vk_from_kpid(key_press_id_t kpid) {
return kpid >> 8;
}
uint8_t
flags_from_kpid(key_press_id_t kpid) {
uint8_t flags_from_kpid(key_press_id_t kpid) {
return kpid & 0xFF;
}
@@ -92,8 +93,7 @@ namespace input {
* @param f Netfloat value.
* @return The native endianness float value.
*/
float
from_netfloat(netfloat f) {
float from_netfloat(netfloat f) {
return boost::endian::endian_load<float, sizeof(float), boost::endian::order::little>(f);
}
@@ -104,8 +104,7 @@ namespace input {
* @param max The maximum value for clamping.
* @return Clamped native endianess float value.
*/
float
from_clamped_netfloat(netfloat f, float min, float max) {
float from_clamped_netfloat(netfloat f, float min, float max) {
return std::clamp(from_netfloat(f), min, max);
}
@@ -116,16 +115,21 @@ namespace input {
static platf::input_t platf_input;
static std::bitset<platf::MAX_GAMEPADS> gamepadMask {};
void
free_gamepad(platf::input_t &platf_input, int id) {
void free_gamepad(platf::input_t &platf_input, int id) {
platf::gamepad_update(platf_input, id, platf::gamepad_state_t {});
platf::free_gamepad(platf_input, id);
free_id(gamepadMask, id);
}
struct gamepad_t {
gamepad_t():
gamepad_state {}, back_timeout_id {}, id { -1 }, back_button_state { button_state_e::NONE } {}
gamepad_state {},
back_timeout_id {},
id {-1},
back_button_state {button_state_e::NONE} {
}
~gamepad_t() {
if (id >= 0) {
task_pool.push([id = this->id]() {
@@ -158,16 +162,18 @@ namespace input {
input_t(
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event,
platf::feedback_queue_t feedback_queue):
platf::feedback_queue_t feedback_queue
):
shortcutFlags {},
gamepads(MAX_GAMEPADS),
client_context { platf::allocate_client_input_context(platf_input) },
touch_port_event { std::move(touch_port_event) },
feedback_queue { std::move(feedback_queue) },
client_context {platf::allocate_client_input_context(platf_input)},
touch_port_event {std::move(touch_port_event)},
feedback_queue {std::move(feedback_queue)},
mouse_left_button_timeout {},
touch_port { { 0, 0, 0, 0 }, 0, 0, 1.0f },
touch_port {{0, 0, 0, 0}, 0, 0, 1.0f},
accumulated_vscroll_delta {},
accumulated_hscroll_delta {} {}
accumulated_hscroll_delta {} {
}
// Keep track of alt+ctrl+shift key combo
int shortcutFlags;
@@ -194,8 +200,7 @@ namespace input {
* @param keyCode The VKEY code
* @return 0 if no shortcut applied, > 0 if shortcut applied.
*/
inline int
apply_shortcut(short keyCode) {
inline int apply_shortcut(short keyCode) {
constexpr auto VK_F1 = 0x70;
constexpr auto VK_F13 = 0x7C;
@@ -215,8 +220,7 @@ namespace input {
return 0;
}
void
print(PNV_REL_MOUSE_MOVE_PACKET packet) {
void print(PNV_REL_MOUSE_MOVE_PACKET packet) {
BOOST_LOG(debug)
<< "--begin relative mouse move packet--"sv << std::endl
<< "deltaX ["sv << util::endian::big(packet->deltaX) << ']' << std::endl
@@ -224,8 +228,7 @@ namespace input {
<< "--end relative mouse move packet--"sv;
}
void
print(PNV_ABS_MOUSE_MOVE_PACKET packet) {
void print(PNV_ABS_MOUSE_MOVE_PACKET packet) {
BOOST_LOG(debug)
<< "--begin absolute mouse move packet--"sv << std::endl
<< "x ["sv << util::endian::big(packet->x) << ']' << std::endl
@@ -235,8 +238,7 @@ namespace input {
<< "--end absolute mouse move packet--"sv;
}
void
print(PNV_MOUSE_BUTTON_PACKET packet) {
void print(PNV_MOUSE_BUTTON_PACKET packet) {
BOOST_LOG(debug)
<< "--begin mouse button packet--"sv << std::endl
<< "action ["sv << util::hex(packet->header.magic).to_string_view() << ']' << std::endl
@@ -244,24 +246,21 @@ namespace input {
<< "--end mouse button packet--"sv;
}
void
print(PNV_SCROLL_PACKET packet) {
void print(PNV_SCROLL_PACKET packet) {
BOOST_LOG(debug)
<< "--begin mouse scroll packet--"sv << std::endl
<< "scrollAmt1 ["sv << util::endian::big(packet->scrollAmt1) << ']' << std::endl
<< "--end mouse scroll packet--"sv;
}
void
print(PSS_HSCROLL_PACKET packet) {
void print(PSS_HSCROLL_PACKET packet) {
BOOST_LOG(debug)
<< "--begin mouse hscroll packet--"sv << std::endl
<< "scrollAmount ["sv << util::endian::big(packet->scrollAmount) << ']' << std::endl
<< "--end mouse hscroll packet--"sv;
}
void
print(PNV_KEYBOARD_PACKET packet) {
void print(PNV_KEYBOARD_PACKET packet) {
BOOST_LOG(debug)
<< "--begin keyboard packet--"sv << std::endl
<< "keyAction ["sv << util::hex(packet->header.magic).to_string_view() << ']' << std::endl
@@ -271,8 +270,7 @@ namespace input {
<< "--end keyboard packet--"sv;
}
void
print(PNV_UNICODE_PACKET packet) {
void print(PNV_UNICODE_PACKET packet) {
std::string text(packet->text, util::endian::big(packet->header.size) - sizeof(packet->header.magic));
BOOST_LOG(debug)
<< "--begin unicode packet--"sv << std::endl
@@ -280,8 +278,7 @@ namespace input {
<< "--end unicode packet--"sv;
}
void
print(PNV_MULTI_CONTROLLER_PACKET packet) {
void print(PNV_MULTI_CONTROLLER_PACKET packet) {
// Moonlight spams controller packet even when not necessary
BOOST_LOG(verbose)
<< "--begin controller packet--"sv << std::endl
@@ -301,8 +298,7 @@ namespace input {
* @brief Prints a touch packet.
* @param packet The touch packet.
*/
void
print(PSS_TOUCH_PACKET packet) {
void print(PSS_TOUCH_PACKET packet) {
BOOST_LOG(debug)
<< "--begin touch packet--"sv << std::endl
<< "eventType ["sv << util::hex(packet->eventType).to_string_view() << ']' << std::endl
@@ -320,8 +316,7 @@ namespace input {
* @brief Prints a pen packet.
* @param packet The pen packet.
*/
void
print(PSS_PEN_PACKET packet) {
void print(PSS_PEN_PACKET packet) {
BOOST_LOG(debug)
<< "--begin pen packet--"sv << std::endl
<< "eventType ["sv << util::hex(packet->eventType).to_string_view() << ']' << std::endl
@@ -341,8 +336,7 @@ namespace input {
* @brief Prints a controller arrival packet.
* @param packet The controller arrival packet.
*/
void
print(PSS_CONTROLLER_ARRIVAL_PACKET packet) {
void print(PSS_CONTROLLER_ARRIVAL_PACKET packet) {
BOOST_LOG(debug)
<< "--begin controller arrival packet--"sv << std::endl
<< "controllerNumber ["sv << (uint32_t) packet->controllerNumber << ']' << std::endl
@@ -356,8 +350,7 @@ namespace input {
* @brief Prints a controller touch packet.
* @param packet The controller touch packet.
*/
void
print(PSS_CONTROLLER_TOUCH_PACKET packet) {
void print(PSS_CONTROLLER_TOUCH_PACKET packet) {
BOOST_LOG(debug)
<< "--begin controller touch packet--"sv << std::endl
<< "controllerNumber ["sv << (uint32_t) packet->controllerNumber << ']' << std::endl
@@ -373,8 +366,7 @@ namespace input {
* @brief Prints a controller motion packet.
* @param packet The controller motion packet.
*/
void
print(PSS_CONTROLLER_MOTION_PACKET packet) {
void print(PSS_CONTROLLER_MOTION_PACKET packet) {
BOOST_LOG(verbose)
<< "--begin controller motion packet--"sv << std::endl
<< "controllerNumber ["sv << util::hex(packet->controllerNumber).to_string_view() << ']' << std::endl
@@ -389,8 +381,7 @@ namespace input {
* @brief Prints a controller battery packet.
* @param packet The controller battery packet.
*/
void
print(PSS_CONTROLLER_BATTERY_PACKET packet) {
void print(PSS_CONTROLLER_BATTERY_PACKET packet) {
BOOST_LOG(verbose)
<< "--begin controller battery packet--"sv << std::endl
<< "controllerNumber ["sv << util::hex(packet->controllerNumber).to_string_view() << ']' << std::endl
@@ -399,8 +390,7 @@ namespace input {
<< "--end controller battery packet--"sv;
}
void
print(void *payload) {
void print(void *payload) {
auto header = (PNV_INPUT_HEADER) payload;
switch (util::endian::little(header->magic)) {
@@ -451,8 +441,7 @@ namespace input {
}
}
void
passthrough(std::shared_ptr<input_t> &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
if (!config::input.mouse) {
return;
}
@@ -468,8 +457,7 @@ namespace input {
* @param size The size of the client's surface containing the value.
* @return The host-relative coordinate pair if a touchport is available.
*/
std::optional<std::pair<float, float>>
client_to_touchport(std::shared_ptr<input_t> &input, const std::pair<float, float> &val, const std::pair<float, float> &size) {
std::optional<std::pair<float, float>> client_to_touchport(std::shared_ptr<input_t> &input, const std::pair<float, float> &val, const std::pair<float, float> &size) {
auto &touch_port_event = input->touch_port_event;
auto &touch_port = input->touch_port;
if (touch_port_event->peek()) {
@@ -492,7 +480,7 @@ namespace input {
x = std::clamp(x, offsetX, (size.first * scalarX) - offsetX);
y = std::clamp(y, offsetY, (size.second * scalarY) - offsetY);
return std::pair { (x - offsetX) * touch_port.scalar_inv, (y - offsetY) * touch_port.scalar_inv };
return std::pair {(x - offsetX) * touch_port.scalar_inv, (y - offsetY) * touch_port.scalar_inv};
}
/**
@@ -502,8 +490,7 @@ namespace input {
* @param scalar The scalar cartesian coordinate pair.
* @return The scaled radial coordinate.
*/
float
multiply_polar_by_cartesian_scalar(float r, float angle, const std::pair<float, float> &scalar) {
float multiply_polar_by_cartesian_scalar(float r, float angle, const std::pair<float, float> &scalar) {
// Convert polar to cartesian coordinates
float x = r * std::cos(angle);
float y = r * std::sin(angle);
@@ -516,8 +503,7 @@ namespace input {
return std::sqrt(std::pow(x, 2) + std::pow(y, 2));
}
std::pair<float, float>
scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar) {
std::pair<float, float> scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar) {
// If the rotation is unknown, we'll just scale both axes equally by using
// a 45-degree angle for our scaling calculations
float angle = rotation == LI_ROT_UNKNOWN ? (M_PI / 4) : (rotation * (M_PI / 180));
@@ -527,11 +513,10 @@ namespace input {
float minor = val.second != 0.0f ? val.second : val.first;
// The minor axis is perpendicular to major axis so the angle must be rotated by 90 degrees
return { multiply_polar_by_cartesian_scalar(major, angle, scalar), multiply_polar_by_cartesian_scalar(minor, angle + (M_PI / 2), scalar) };
return {multiply_polar_by_cartesian_scalar(major, angle, scalar), multiply_polar_by_cartesian_scalar(minor, angle + (M_PI / 2), scalar)};
}
void
passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
if (!config::input.mouse) {
return;
}
@@ -554,22 +539,23 @@ namespace input {
auto width = (float) util::endian::big(packet->width);
auto height = (float) util::endian::big(packet->height);
auto tpcoords = client_to_touchport(input, { x, y }, { width, height });
auto tpcoords = client_to_touchport(input, {x, y}, {width, height});
if (!tpcoords) {
return;
}
auto &touch_port = input->touch_port;
platf::touch_port_t abs_port {
touch_port.offset_x, touch_port.offset_y,
touch_port.env_width, touch_port.env_height
touch_port.offset_x,
touch_port.offset_y,
touch_port.env_width,
touch_port.env_height
};
platf::abs_mouse(platf_input, abs_port, tpcoords->first, tpcoords->second);
}
void
passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
if (!config::input.mouse) {
return;
}
@@ -617,7 +603,8 @@ namespace input {
}
if (
button == BUTTON_RIGHT && !release &&
input->mouse_left_button_timeout > DISABLE_LEFT_BUTTON_DELAY) {
input->mouse_left_button_timeout > DISABLE_LEFT_BUTTON_DELAY
) {
platf::button_mouse(platf_input, BUTTON_RIGHT, false);
platf::button_mouse(platf_input, BUTTON_RIGHT, true);
@@ -629,8 +616,7 @@ namespace input {
platf::button_mouse(platf_input, button, release);
}
short
map_keycode(short keycode) {
short map_keycode(short keycode) {
auto it = config::input.keybindings.find(keycode);
if (it != std::end(config::input.keybindings)) {
return it->second;
@@ -642,16 +628,14 @@ namespace input {
/**
* @brief Update flags for keyboard shortcut combo's
*/
inline void
update_shortcutFlags(int *flags, short keyCode, bool release) {
inline void update_shortcutFlags(int *flags, short keyCode, bool release) {
switch (keyCode) {
case VKEY_SHIFT:
case VKEY_LSHIFT:
case VKEY_RSHIFT:
if (release) {
*flags &= ~input_t::SHIFT;
}
else {
} else {
*flags |= input_t::SHIFT;
}
break;
@@ -660,8 +644,7 @@ namespace input {
case VKEY_RCONTROL:
if (release) {
*flags &= ~input_t::CTRL;
}
else {
} else {
*flags |= input_t::CTRL;
}
break;
@@ -670,16 +653,14 @@ namespace input {
case VKEY_RMENU:
if (release) {
*flags &= ~input_t::ALT;
}
else {
} else {
*flags |= input_t::ALT;
}
break;
}
}
bool
is_modifier(uint16_t keyCode) {
bool is_modifier(uint16_t keyCode) {
switch (keyCode) {
case VKEY_SHIFT:
case VKEY_LSHIFT:
@@ -696,8 +677,7 @@ namespace input {
}
}
void
send_key_and_modifiers(uint16_t key_code, bool release, uint8_t flags, uint8_t synthetic_modifiers) {
void send_key_and_modifiers(uint16_t key_code, bool release, uint8_t flags, uint8_t synthetic_modifiers) {
if (!release) {
// Press any synthetic modifiers required for this key
if (synthetic_modifiers & MODIFIER_SHIFT) {
@@ -727,8 +707,7 @@ namespace input {
}
}
void
repeat_key(uint16_t key_code, uint8_t flags, uint8_t synthetic_modifiers) {
void repeat_key(uint16_t key_code, uint8_t flags, uint8_t synthetic_modifiers) {
// If key no longer pressed, stop repeating
if (!key_press[make_kpid(key_code, flags)]) {
key_press_repeat_id = nullptr;
@@ -740,8 +719,7 @@ namespace input {
key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code, flags, synthetic_modifiers).task_id;
}
void
passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
if (!config::input.keyboard) {
return;
}
@@ -780,13 +758,11 @@ namespace input {
if (config::input.key_repeat_delay.count() > 0) {
key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_delay, keyCode, packet->flags, synthetic_modifiers).task_id;
}
}
else {
} else {
// Already released
return;
}
}
else if (!release) {
} else if (!release) {
// Already pressed down key
return;
}
@@ -803,16 +779,14 @@ namespace input {
* @param input The input context pointer.
* @param packet The scroll packet.
*/
void
passthrough(std::shared_ptr<input_t> &input, PNV_SCROLL_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PNV_SCROLL_PACKET packet) {
if (!config::input.mouse) {
return;
}
if (config::input.high_resolution_scrolling) {
platf::scroll(platf_input, util::endian::big(packet->scrollAmt1));
}
else {
} else {
input->accumulated_vscroll_delta += util::endian::big(packet->scrollAmt1);
auto full_ticks = input->accumulated_vscroll_delta / WHEEL_DELTA;
if (full_ticks) {
@@ -828,16 +802,14 @@ namespace input {
* @param input The input context pointer.
* @param packet The scroll packet.
*/
void
passthrough(std::shared_ptr<input_t> &input, PSS_HSCROLL_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PSS_HSCROLL_PACKET packet) {
if (!config::input.mouse) {
return;
}
if (config::input.high_resolution_scrolling) {
platf::hscroll(platf_input, util::endian::big(packet->scrollAmount));
}
else {
} else {
input->accumulated_hscroll_delta += util::endian::big(packet->scrollAmount);
auto full_ticks = input->accumulated_hscroll_delta / WHEEL_DELTA;
if (full_ticks) {
@@ -848,8 +820,7 @@ namespace input {
}
}
void
passthrough(PNV_UNICODE_PACKET packet) {
void passthrough(PNV_UNICODE_PACKET packet) {
if (!config::input.keyboard) {
return;
}
@@ -863,8 +834,7 @@ namespace input {
* @param input The input context pointer.
* @param packet The controller arrival packet.
*/
void
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_ARRIVAL_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_ARRIVAL_PACKET packet) {
if (!config::input.controller) {
return;
}
@@ -891,7 +861,7 @@ namespace input {
}
// Allocate a new gamepad
if (platf::alloc_gamepad(platf_input, { id, packet->controllerNumber }, arrival, input->feedback_queue)) {
if (platf::alloc_gamepad(platf_input, {id, packet->controllerNumber}, arrival, input->feedback_queue)) {
free_id(gamepadMask, id);
return;
}
@@ -904,25 +874,23 @@ namespace input {
* @param input The input context pointer.
* @param packet The touch packet.
*/
void
passthrough(std::shared_ptr<input_t> &input, PSS_TOUCH_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PSS_TOUCH_PACKET packet) {
if (!config::input.mouse) {
return;
}
// Convert the client normalized coordinates to touchport coordinates
auto coords = client_to_touchport(input,
{ from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f,
from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f },
{ 65535.f, 65535.f });
auto coords = client_to_touchport(input, {from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f, from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f}, {65535.f, 65535.f});
if (!coords) {
return;
}
auto &touch_port = input->touch_port;
platf::touch_port_t abs_port {
touch_port.offset_x, touch_port.offset_y,
touch_port.env_width, touch_port.env_height
touch_port.offset_x,
touch_port.offset_y,
touch_port.env_width,
touch_port.env_height
};
// Renormalize the coordinates
@@ -937,10 +905,11 @@ namespace input {
// Normalize the contact area based on the touchport
auto contact_area = scale_client_contact_area(
{ from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f,
from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f },
{from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f,
from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f},
rotation,
{ abs_port.width / 65535.f, abs_port.height / 65535.f });
{abs_port.width / 65535.f, abs_port.height / 65535.f}
);
platf::touch_input_t touch {
packet->eventType,
@@ -961,25 +930,23 @@ namespace input {
* @param input The input context pointer.
* @param packet The pen packet.
*/
void
passthrough(std::shared_ptr<input_t> &input, PSS_PEN_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PSS_PEN_PACKET packet) {
if (!config::input.mouse) {
return;
}
// Convert the client normalized coordinates to touchport coordinates
auto coords = client_to_touchport(input,
{ from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f,
from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f },
{ 65535.f, 65535.f });
auto coords = client_to_touchport(input, {from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f, from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f}, {65535.f, 65535.f});
if (!coords) {
return;
}
auto &touch_port = input->touch_port;
platf::touch_port_t abs_port {
touch_port.offset_x, touch_port.offset_y,
touch_port.env_width, touch_port.env_height
touch_port.offset_x,
touch_port.offset_y,
touch_port.env_width,
touch_port.env_height
};
// Renormalize the coordinates
@@ -994,10 +961,11 @@ namespace input {
// Normalize the contact area based on the touchport
auto contact_area = scale_client_contact_area(
{ from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f,
from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f },
{from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f,
from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f},
rotation,
{ abs_port.width / 65535.f, abs_port.height / 65535.f });
{abs_port.width / 65535.f, abs_port.height / 65535.f}
);
platf::pen_input_t pen {
packet->eventType,
@@ -1020,8 +988,7 @@ namespace input {
* @param input The input context pointer.
* @param packet The controller touch packet.
*/
void
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_TOUCH_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_TOUCH_PACKET packet) {
if (!config::input.controller) {
return;
}
@@ -1038,7 +1005,7 @@ namespace input {
}
platf::gamepad_touch_t touch {
{ gamepad.id, packet->controllerNumber },
{gamepad.id, packet->controllerNumber},
packet->eventType,
util::endian::little(packet->pointerId),
from_clamped_netfloat(packet->x, 0.0f, 1.0f),
@@ -1054,8 +1021,7 @@ namespace input {
* @param input The input context pointer.
* @param packet The controller motion packet.
*/
void
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_MOTION_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_MOTION_PACKET packet) {
if (!config::input.controller) {
return;
}
@@ -1072,7 +1038,7 @@ namespace input {
}
platf::gamepad_motion_t motion {
{ gamepad.id, packet->controllerNumber },
{gamepad.id, packet->controllerNumber},
packet->motionType,
from_netfloat(packet->x),
from_netfloat(packet->y),
@@ -1087,8 +1053,7 @@ namespace input {
* @param input The input context pointer.
* @param packet The controller battery packet.
*/
void
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_BATTERY_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_BATTERY_PACKET packet) {
if (!config::input.controller) {
return;
}
@@ -1105,7 +1070,7 @@ namespace input {
}
platf::gamepad_battery_t battery {
{ gamepad.id, packet->controllerNumber },
{gamepad.id, packet->controllerNumber},
packet->batteryState,
packet->batteryPercentage
};
@@ -1113,8 +1078,7 @@ namespace input {
platf::gamepad_battery(platf_input, battery);
}
void
passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET packet) {
if (!config::input.controller) {
return;
}
@@ -1135,14 +1099,13 @@ namespace input {
return;
}
if (platf::alloc_gamepad(platf_input, { id, (uint8_t) packet->controllerNumber }, {}, input->feedback_queue)) {
if (platf::alloc_gamepad(platf_input, {id, (uint8_t) packet->controllerNumber}, {}, input->feedback_queue)) {
free_id(gamepadMask, id);
return;
}
gamepad.id = id;
}
else if (!(packet->activeGamepadMask & (1 << packet->controllerNumber)) && gamepad.id >= 0) {
} else if (!(packet->activeGamepadMask & (1 << packet->controllerNumber)) && gamepad.id >= 0) {
// If this is the final event for a gamepad being removed, free the gamepad and return.
free_gamepad(platf_input, gamepad.id);
gamepad.id = -1;
@@ -1219,8 +1182,7 @@ namespace input {
gamepad.back_timeout_id = task_pool.pushDelayed(std::move(f), config::input.back_button_timeout).task_id;
}
}
else if (gamepad.back_timeout_id) {
} else if (gamepad.back_timeout_id) {
task_pool.cancel(gamepad.back_timeout_id);
gamepad.back_timeout_id = nullptr;
}
@@ -1243,8 +1205,7 @@ namespace input {
* @param src A later packet to attempt to batch.
* @return The status of the batching operation.
*/
batch_result_e
batch(PNV_REL_MOUSE_MOVE_PACKET dest, PNV_REL_MOUSE_MOVE_PACKET src) {
batch_result_e batch(PNV_REL_MOUSE_MOVE_PACKET dest, PNV_REL_MOUSE_MOVE_PACKET src) {
short deltaX, deltaY;
// Batching is safe as long as the result doesn't overflow a 16-bit integer
@@ -1267,8 +1228,7 @@ namespace input {
* @param src A later packet to attempt to batch.
* @return The status of the batching operation.
*/
batch_result_e
batch(PNV_ABS_MOUSE_MOVE_PACKET dest, PNV_ABS_MOUSE_MOVE_PACKET src) {
batch_result_e batch(PNV_ABS_MOUSE_MOVE_PACKET dest, PNV_ABS_MOUSE_MOVE_PACKET src) {
// Batching must only happen if the reference width and height don't change
if (dest->width != src->width || dest->height != src->height) {
return batch_result_e::terminate_batch;
@@ -1285,8 +1245,7 @@ namespace input {
* @param src A later packet to attempt to batch.
* @return The status of the batching operation.
*/
batch_result_e
batch(PNV_SCROLL_PACKET dest, PNV_SCROLL_PACKET src) {
batch_result_e batch(PNV_SCROLL_PACKET dest, PNV_SCROLL_PACKET src) {
short scrollAmt;
// Batching is safe as long as the result doesn't overflow a 16-bit integer
@@ -1306,8 +1265,7 @@ namespace input {
* @param src A later packet to attempt to batch.
* @return The status of the batching operation.
*/
batch_result_e
batch(PSS_HSCROLL_PACKET dest, PSS_HSCROLL_PACKET src) {
batch_result_e batch(PSS_HSCROLL_PACKET dest, PSS_HSCROLL_PACKET src) {
short scrollAmt;
// Batching is safe as long as the result doesn't overflow a 16-bit integer
@@ -1326,8 +1284,7 @@ namespace input {
* @param src A later packet to attempt to batch.
* @return The status of the batching operation.
*/
batch_result_e
batch(PNV_MULTI_CONTROLLER_PACKET dest, PNV_MULTI_CONTROLLER_PACKET src) {
batch_result_e batch(PNV_MULTI_CONTROLLER_PACKET dest, PNV_MULTI_CONTROLLER_PACKET src) {
// Do not allow batching if the active controllers change
if (dest->activeGamepadMask != src->activeGamepadMask) {
return batch_result_e::terminate_batch;
@@ -1355,8 +1312,7 @@ namespace input {
* @param src A later packet to attempt to batch.
* @return The status of the batching operation.
*/
batch_result_e
batch(PSS_TOUCH_PACKET dest, PSS_TOUCH_PACKET src) {
batch_result_e batch(PSS_TOUCH_PACKET dest, PSS_TOUCH_PACKET src) {
// Only batch hover or move events
if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
dest->eventType != LI_TOUCH_EVENT_HOVER) {
@@ -1390,8 +1346,7 @@ namespace input {
* @param src A later packet to attempt to batch.
* @return The status of the batching operation.
*/
batch_result_e
batch(PSS_PEN_PACKET dest, PSS_PEN_PACKET src) {
batch_result_e batch(PSS_PEN_PACKET dest, PSS_PEN_PACKET src) {
// Only batch hover or move events
if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
dest->eventType != LI_TOUCH_EVENT_HOVER) {
@@ -1424,8 +1379,7 @@ namespace input {
* @param src A later packet to attempt to batch.
* @return The status of the batching operation.
*/
batch_result_e
batch(PSS_CONTROLLER_TOUCH_PACKET dest, PSS_CONTROLLER_TOUCH_PACKET src) {
batch_result_e batch(PSS_CONTROLLER_TOUCH_PACKET dest, PSS_CONTROLLER_TOUCH_PACKET src) {
// Only batch hover or move events
if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
dest->eventType != LI_TOUCH_EVENT_HOVER) {
@@ -1465,8 +1419,7 @@ namespace input {
* @param src A later packet to attempt to batch.
* @return The status of the batching operation.
*/
batch_result_e
batch(PSS_CONTROLLER_MOTION_PACKET dest, PSS_CONTROLLER_MOTION_PACKET src) {
batch_result_e batch(PSS_CONTROLLER_MOTION_PACKET dest, PSS_CONTROLLER_MOTION_PACKET src) {
// We can only batch entries for the same controller, but allow batching attempts to continue
// in case we have more packets for this controller later in the queue.
if (dest->controllerNumber != src->controllerNumber) {
@@ -1489,8 +1442,7 @@ namespace input {
* @param src A later packet to attempt to batch.
* @return The status of the batching operation.
*/
batch_result_e
batch(PNV_INPUT_HEADER dest, PNV_INPUT_HEADER src) {
batch_result_e batch(PNV_INPUT_HEADER dest, PNV_INPUT_HEADER src) {
// We can only batch if the packet types are the same
if (dest->magic != src->magic) {
return batch_result_e::terminate_batch;
@@ -1526,8 +1478,7 @@ namespace input {
* @brief Called on a thread pool thread to process an input message.
* @param input The input context pointer.
*/
void
passthrough_next_message(std::shared_ptr<input_t> input) {
void passthrough_next_message(std::shared_ptr<input_t> input) {
// 'entry' backs the 'payload' pointer, so they must remain in scope together
std::vector<uint8_t> entry;
PNV_INPUT_HEADER payload;
@@ -1558,12 +1509,10 @@ namespace input {
if (batch_result == batch_result_e::terminate_batch) {
// Stop batching
break;
}
else if (batch_result == batch_result_e::batched) {
} else if (batch_result == batch_result_e::batched) {
// Erase this entry since it was batched
i = input->input_queue.erase(i);
}
else {
} else {
// We couldn't batch this entry, but try to batch later entries.
i++;
}
@@ -1627,8 +1576,7 @@ namespace input {
* @param input The input context pointer.
* @param input_data The input message.
*/
void
passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data) {
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data) {
{
std::lock_guard<std::mutex> lg(input->input_queue_lock);
input->input_queue.push_back(std::move(input_data));
@@ -1636,8 +1584,7 @@ namespace input {
task_pool.push(passthrough_next_message, input);
}
void
reset(std::shared_ptr<input_t> &input) {
void reset(std::shared_ptr<input_t> &input) {
task_pool.cancel(key_press_repeat_id);
task_pool.cancel(input->mouse_left_button_timeout);
@@ -1668,15 +1615,13 @@ namespace input {
}
};
[[nodiscard]] std::unique_ptr<platf::deinit_t>
init() {
[[nodiscard]] std::unique_ptr<platf::deinit_t> init() {
platf_input = platf::input();
return std::make_unique<deinit_t>();
}
bool
probe_gamepads() {
bool probe_gamepads() {
auto input = static_cast<platf::input_t *>(platf_input.get());
const auto gamepads = platf::supported_gamepads(input);
for (auto &gamepad : gamepads) {
@@ -1687,18 +1632,18 @@ namespace input {
return true;
}
std::shared_ptr<input_t>
alloc(safe::mail_t mail) {
std::shared_ptr<input_t> alloc(safe::mail_t mail) {
auto input = std::make_shared<input_t>(
mail->event<input::touch_port_t>(mail::touch_port),
mail->queue<platf::gamepad_feedback_msg_t>(mail::gamepad_feedback));
mail->queue<platf::gamepad_feedback_msg_t>(mail::gamepad_feedback)
);
// Workaround to ensure new frames will be captured when a client connects
task_pool.pushDelayed([]() {
platf::move_mouse(platf_input, 1, 1);
platf::move_mouse(platf_input, -1, -1);
},
100ms);
100ms);
return input;
}

View File

@@ -4,29 +4,25 @@
*/
#pragma once
// standard includes
#include <functional>
// local includes
#include "platform/common.h"
#include "thread_safe.h"
namespace input {
struct input_t;
void
print(void *input);
void
reset(std::shared_ptr<input_t> &input);
void
passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data);
void print(void *input);
void reset(std::shared_ptr<input_t> &input);
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data);
[[nodiscard]] std::unique_ptr<platf::deinit_t>
init();
[[nodiscard]] std::unique_ptr<platf::deinit_t> init();
bool
probe_gamepads();
bool probe_gamepads();
std::shared_ptr<input_t>
alloc(safe::mail_t mail);
std::shared_ptr<input_t> alloc(safe::mail_t mail);
struct touch_port_t: public platf::touch_port_t {
int env_width, env_height;
@@ -36,8 +32,7 @@ namespace input {
float scalar_inv;
explicit
operator bool() const {
explicit operator bool() const {
return width != 0 && height != 0 && env_width != 0 && env_height != 0;
}
};
@@ -49,6 +44,5 @@ namespace input {
* @param scalar The scalar cartesian coordinate pair.
* @return The major and minor axis pair.
*/
std::pair<float, float>
scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar);
std::pair<float, float> scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar);
} // namespace input

View File

@@ -47,15 +47,13 @@ namespace logging {
deinit();
}
void
deinit() {
void deinit() {
log_flush();
bl::core::get()->remove_sink(sink);
sink.reset();
}
void
formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os) {
void formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os) {
constexpr const char *message = "Message";
constexpr const char *severity = "Severity";
@@ -90,7 +88,8 @@ namespace logging {
auto now = std::chrono::system_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now - std::chrono::time_point_cast<std::chrono::seconds>(now));
now - std::chrono::time_point_cast<std::chrono::seconds>(now)
);
auto t = std::chrono::system_clock::to_time_t(now);
auto lt = *std::localtime(&t);
@@ -99,8 +98,7 @@ namespace logging {
<< log_type << view.attribute_values()[message].extract<std::string>();
}
[[nodiscard]] std::unique_ptr<deinit_t>
init(int min_log_level, const std::string &log_file) {
[[nodiscard]] std::unique_ptr<deinit_t> init(int min_log_level, const std::string &log_file) {
if (sink) {
// Deinitialize the logging system before reinitializing it. This can probably only ever be hit in tests.
deinit();
@@ -112,7 +110,7 @@ namespace logging {
sink = boost::make_shared<text_sink>();
#ifndef SUNSHINE_TESTS
boost::shared_ptr<std::ostream> stream { &std::cout, boost::null_deleter() };
boost::shared_ptr<std::ostream> stream {&std::cout, boost::null_deleter()};
sink->locked_backend()->add_stream(stream);
#endif
sink->locked_backend()->add_stream(boost::make_shared<std::ofstream>(log_file));
@@ -127,12 +125,10 @@ namespace logging {
return std::make_unique<deinit_t>();
}
void
setup_av_logging(int min_log_level) {
void setup_av_logging(int min_log_level) {
if (min_log_level >= 1) {
av_log_set_level(AV_LOG_QUIET);
}
else {
} else {
av_log_set_level(AV_LOG_DEBUG);
}
av_log_set_callback([](void *ptr, int level, const char *fmt, va_list vl) {
@@ -144,28 +140,23 @@ namespace logging {
// We print AV_LOG_FATAL at the error level. FFmpeg prints things as fatal that
// are expected in some cases, such as lack of codec support or similar things.
BOOST_LOG(error) << buffer;
}
else if (level <= AV_LOG_WARNING) {
} else if (level <= AV_LOG_WARNING) {
BOOST_LOG(warning) << buffer;
}
else if (level <= AV_LOG_INFO) {
} else if (level <= AV_LOG_INFO) {
BOOST_LOG(info) << buffer;
}
else if (level <= AV_LOG_VERBOSE) {
} else if (level <= AV_LOG_VERBOSE) {
// AV_LOG_VERBOSE is less verbose than AV_LOG_DEBUG
BOOST_LOG(debug) << buffer;
}
else {
} else {
BOOST_LOG(verbose) << buffer;
}
});
}
void
setup_libdisplaydevice_logging(int min_log_level) {
constexpr int min_level { static_cast<int>(display_device::Logger::LogLevel::verbose) };
constexpr int max_level { static_cast<int>(display_device::Logger::LogLevel::fatal) };
const auto log_level { static_cast<display_device::Logger::LogLevel>(std::min(std::max(min_level, min_log_level), max_level)) };
void setup_libdisplaydevice_logging(int min_log_level) {
constexpr int min_level {static_cast<int>(display_device::Logger::LogLevel::verbose)};
constexpr int max_level {static_cast<int>(display_device::Logger::LogLevel::fatal)};
const auto log_level {static_cast<display_device::Logger::LogLevel>(std::min(std::max(min_level, min_log_level), max_level))};
display_device::Logger::get().setLogLevel(log_level);
display_device::Logger::get().setCustomCallback([](const display_device::Logger::LogLevel level, const std::string &message) {
@@ -192,15 +183,13 @@ namespace logging {
});
}
void
log_flush() {
void log_flush() {
if (sink) {
sink->flush();
}
}
void
print_help(const char *name) {
void print_help(const char *name) {
std::cout
<< "Usage: "sv << name << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl
<< " Any configurable option can be overwritten with: \"name=value\""sv << std::endl
@@ -220,13 +209,11 @@ namespace logging {
<< std::endl;
}
std::string
bracket(const std::string &input) {
std::string bracket(const std::string &input) {
return "["s + input + "]"s;
}
std::wstring
bracket(const std::wstring &input) {
std::wstring bracket(const std::wstring &input) {
return L"["s + input + L"]"s;
}

View File

@@ -41,11 +41,9 @@ namespace logging {
* deinit();
* @examples_end
*/
void
deinit();
void deinit();
void
formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os);
void formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os);
/**
* @brief Initialize the logging system.
@@ -56,22 +54,19 @@ namespace logging {
* log_init(2, "sunshine.log");
* @examples_end
*/
[[nodiscard]] std::unique_ptr<deinit_t>
init(int min_log_level, const std::string &log_file);
[[nodiscard]] std::unique_ptr<deinit_t> init(int min_log_level, const std::string &log_file);
/**
* @brief Setup AV logging.
* @param min_log_level The log level.
*/
void
setup_av_logging(int min_log_level);
void setup_av_logging(int min_log_level);
/**
* @brief Setup logging for libdisplaydevice.
* @param min_log_level The log level.
*/
void
setup_libdisplaydevice_logging(int min_log_level);
void setup_libdisplaydevice_logging(int min_log_level);
/**
* @brief Flush the log.
@@ -79,8 +74,7 @@ namespace logging {
* log_flush();
* @examples_end
*/
void
log_flush();
void log_flush();
/**
* @brief Print help to stdout.
@@ -89,8 +83,7 @@ namespace logging {
* print_help("sunshine");
* @examples_end
*/
void
print_help(const char *name);
void print_help(const char *name);
/**
* @brief A helper class for tracking and logging numerical values across a period of time
@@ -105,28 +98,24 @@ namespace logging {
* // [2024:01:01:12:00:00]: Debug: Test time value (min/max/avg): 1ms/3ms/2.00ms
* @examples_end
*/
template <typename T>
template<typename T>
class min_max_avg_periodic_logger {
public:
min_max_avg_periodic_logger(boost::log::sources::severity_logger<int> &severity,
std::string_view message,
std::string_view units,
std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
min_max_avg_periodic_logger(boost::log::sources::severity_logger<int> &severity, std::string_view message, std::string_view units, std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
severity(severity),
message(message),
units(units),
interval(interval_in_seconds),
enabled(config::sunshine.min_log_level <= severity.default_severity()) {}
enabled(config::sunshine.min_log_level <= severity.default_severity()) {
}
void
collect_and_log(const T &value) {
void collect_and_log(const T &value) {
if (enabled) {
auto print_info = [&](const T &min_value, const T &max_value, double avg_value) {
auto f = stat_trackers::two_digits_after_decimal();
if constexpr (std::is_floating_point_v<T>) {
BOOST_LOG(severity.get()) << message << " (min/max/avg): " << f % min_value << units << "/" << f % max_value << units << "/" << f % avg_value << units;
}
else {
} else {
BOOST_LOG(severity.get()) << message << " (min/max/avg): " << min_value << units << "/" << max_value << units << "/" << f % avg_value << units;
}
};
@@ -134,18 +123,19 @@ namespace logging {
}
}
void
collect_and_log(std::function<T()> func) {
if (enabled) collect_and_log(func());
void collect_and_log(std::function<T()> func) {
if (enabled) {
collect_and_log(func());
}
}
void
reset() {
if (enabled) tracker.reset();
void reset() {
if (enabled) {
tracker.reset();
}
}
bool
is_enabled() const {
bool is_enabled() const {
return enabled;
}
@@ -175,40 +165,41 @@ namespace logging {
*/
class time_delta_periodic_logger {
public:
time_delta_periodic_logger(boost::log::sources::severity_logger<int> &severity,
std::string_view message,
std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
logger(severity, message, "ms", interval_in_seconds) {}
void
first_point(const std::chrono::steady_clock::time_point &point) {
if (logger.is_enabled()) point1 = point;
time_delta_periodic_logger(boost::log::sources::severity_logger<int> &severity, std::string_view message, std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
logger(severity, message, "ms", interval_in_seconds) {
}
void
first_point_now() {
if (logger.is_enabled()) first_point(std::chrono::steady_clock::now());
void first_point(const std::chrono::steady_clock::time_point &point) {
if (logger.is_enabled()) {
point1 = point;
}
}
void
second_point_and_log(const std::chrono::steady_clock::time_point &point) {
void first_point_now() {
if (logger.is_enabled()) {
first_point(std::chrono::steady_clock::now());
}
}
void second_point_and_log(const std::chrono::steady_clock::time_point &point) {
if (logger.is_enabled()) {
logger.collect_and_log(std::chrono::duration<double, std::milli>(point - point1).count());
}
}
void
second_point_now_and_log() {
if (logger.is_enabled()) second_point_and_log(std::chrono::steady_clock::now());
void second_point_now_and_log() {
if (logger.is_enabled()) {
second_point_and_log(std::chrono::steady_clock::now());
}
}
void
reset() {
if (logger.is_enabled()) logger.reset();
void reset() {
if (logger.is_enabled()) {
logger.reset();
}
}
bool
is_enabled() const {
bool is_enabled() const {
return logger.is_enabled();
}
@@ -222,15 +213,13 @@ namespace logging {
* @param input Input string.
* @return Enclosed string.
*/
std::string
bracket(const std::string &input);
std::string bracket(const std::string &input);
/**
* @brief Enclose string in square brackets.
* @param input Input string.
* @return Enclosed string.
*/
std::wstring
bracket(const std::wstring &input);
std::wstring bracket(const std::wstring &input);
} // namespace logging

View File

@@ -30,31 +30,37 @@ extern "C" {
using namespace std::literals;
std::map<int, std::function<void()>> signal_handlers;
void
on_signal_forwarder(int sig) {
void on_signal_forwarder(int sig) {
signal_handlers.at(sig)();
}
template <class FN>
void
on_signal(int sig, FN &&fn) {
template<class FN>
void on_signal(int sig, FN &&fn) {
signal_handlers.emplace(sig, std::forward<FN>(fn));
std::signal(sig, on_signal_forwarder);
}
std::map<std::string_view, std::function<int(const char *name, int argc, char **argv)>> cmd_to_func {
{ "creds"sv, [](const char *name, int argc, char **argv) { return args::creds(name, argc, argv); } },
{ "help"sv, [](const char *name, int argc, char **argv) { return args::help(name); } },
{ "version"sv, [](const char *name, int argc, char **argv) { return args::version(); } },
{"creds"sv, [](const char *name, int argc, char **argv) {
return args::creds(name, argc, argv);
}},
{"help"sv, [](const char *name, int argc, char **argv) {
return args::help(name);
}},
{"version"sv, [](const char *name, int argc, char **argv) {
return args::version();
}},
#ifdef _WIN32
{ "restore-nvprefs-undo"sv, [](const char *name, int argc, char **argv) { return args::restore_nvprefs_undo(); } },
{"restore-nvprefs-undo"sv, [](const char *name, int argc, char **argv) {
return args::restore_nvprefs_undo();
}},
#endif
};
#ifdef _WIN32
LRESULT CALLBACK
SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
LRESULT CALLBACK SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_CLOSE:
DestroyWindow(hwnd);
@@ -62,19 +68,19 @@ SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_ENDSESSION: {
// Terminate ourselves with a blocking exit call
std::cout << "Received WM_ENDSESSION"sv << std::endl;
lifetime::exit_sunshine(0, false);
return 0;
}
case WM_ENDSESSION:
{
// Terminate ourselves with a blocking exit call
std::cout << "Received WM_ENDSESSION"sv << std::endl;
lifetime::exit_sunshine(0, false);
return 0;
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
WINAPI BOOL
ConsoleCtrlHandler(DWORD type) {
WINAPI BOOL ConsoleCtrlHandler(DWORD type) {
if (type == CTRL_CLOSE_EVENT) {
BOOST_LOG(info) << "Console closed handler called";
lifetime::exit_sunshine(0, false);
@@ -83,8 +89,7 @@ ConsoleCtrlHandler(DWORD type) {
}
#endif
int
main(int argc, char *argv[]) {
int main(int argc, char *argv[]) {
lifetime::argv = argv;
task_pool_util::TaskPool::task_id_t force_shutdown = nullptr;
@@ -188,7 +193,8 @@ main(int argc, char *argv[]) {
nullptr,
nullptr,
nullptr,
nullptr);
nullptr
);
session_monitor_hwnd_promise.set_value(wnd);
@@ -216,12 +222,10 @@ main(int argc, char *argv[]) {
if (session_monitor_join_thread_future.wait_for(1s) == std::future_status::ready) {
session_monitor_thread.join();
return;
}
else {
} else {
BOOST_LOG(warning) << "session_monitor_join_thread_future reached timeout";
}
}
else {
} else {
BOOST_LOG(warning) << "session_monitor_hwnd_future reached timeout";
}
@@ -324,8 +328,8 @@ main(int argc, char *argv[]) {
return lifetime::desired_exit_code;
}
std::thread httpThread { nvhttp::start };
std::thread configThread { confighttp::start };
std::thread httpThread {nvhttp::start};
std::thread configThread {confighttp::start};
#ifdef _WIN32
// If we're using the default port and GameStream is enabled, warn the user

View File

@@ -12,5 +12,4 @@
* main(1, const char* args[] = {"sunshine", nullptr});
* @examples_end
*/
int
main(int argc, char *argv[]);
int main(int argc, char *argv[]);

View File

@@ -4,6 +4,7 @@
*/
#pragma once
// standard includes
#include <utility>
/**
@@ -14,7 +15,7 @@ namespace move_by_copy_util {
* When a copy is made, it moves the object
* This allows you to move an object when a move can't be done.
*/
template <class T>
template<class T>
class MoveByCopy {
public:
typedef T move_type;
@@ -24,7 +25,8 @@ namespace move_by_copy_util {
public:
explicit MoveByCopy(move_type &&to_move):
_to_move(std::move(to_move)) {}
_to_move(std::move(to_move)) {
}
MoveByCopy(MoveByCopy &&other) = default;
@@ -32,11 +34,9 @@ namespace move_by_copy_util {
*this = other;
}
MoveByCopy &
operator=(MoveByCopy &&other) = default;
MoveByCopy &operator=(MoveByCopy &&other) = default;
MoveByCopy &
operator=(const MoveByCopy &other) {
MoveByCopy &operator=(const MoveByCopy &other) {
this->_to_move = std::move(const_cast<MoveByCopy &>(other)._to_move);
return *this;
@@ -47,16 +47,14 @@ namespace move_by_copy_util {
}
};
template <class T>
MoveByCopy<T>
cmove(T &movable) {
template<class T>
MoveByCopy<T> cmove(T &movable) {
return MoveByCopy<T>(std::move(movable));
}
// Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller
template <class T>
MoveByCopy<T>
const_cmove(const T &movable) {
template<class T>
MoveByCopy<T> const_cmove(const T &movable) {
return MoveByCopy<T>(std::move(const_cast<T &>(movable)));
}
} // namespace move_by_copy_util

View File

@@ -2,13 +2,16 @@
* @file src/network.cpp
* @brief Definitions for networking related functions.
*/
#include "network.h"
#include "config.h"
#include "logging.h"
#include "utility.h"
// standard includes
#include <algorithm>
#include <sstream>
// local includes
#include "config.h"
#include "logging.h"
#include "network.h"
#include "utility.h"
using namespace std::literals;
namespace ip = boost::asio::ip;
@@ -33,8 +36,7 @@ namespace net {
ip::make_network_v6("fe80::/64"sv),
};
net_e
from_enum_string(const std::string_view &view) {
net_e from_enum_string(const std::string_view &view) {
if (view == "wan") {
return WAN;
}
@@ -45,8 +47,7 @@ namespace net {
return PC;
}
net_e
from_address(const std::string_view &view) {
net_e from_address(const std::string_view &view) {
auto addr = normalize_address(ip::make_address(view));
if (addr.is_v6()) {
@@ -61,8 +62,7 @@ namespace net {
return LAN;
}
}
}
else {
} else {
for (auto &range : pc_ips_v4) {
if (range.hosts().find(addr.to_v4()) != range.hosts().end()) {
return PC;
@@ -79,8 +79,7 @@ namespace net {
return WAN;
}
std::string_view
to_enum_string(net_e net) {
std::string_view to_enum_string(net_e net) {
switch (net) {
case PC:
return "pc"sv;
@@ -94,8 +93,7 @@ namespace net {
return "wan"sv;
}
af_e
af_from_enum_string(const std::string_view &view) {
af_e af_from_enum_string(const std::string_view &view) {
if (view == "ipv4") {
return IPV4;
}
@@ -107,8 +105,7 @@ namespace net {
return BOTH;
}
std::string_view
af_to_any_address_string(af_e af) {
std::string_view af_to_any_address_string(af_e af) {
switch (af) {
case IPV4:
return "0.0.0.0"sv;
@@ -120,8 +117,7 @@ namespace net {
return "::"sv;
}
boost::asio::ip::address
normalize_address(boost::asio::ip::address address) {
boost::asio::ip::address normalize_address(boost::asio::ip::address address) {
// Convert IPv6-mapped IPv4 addresses into regular IPv4 addresses
if (address.is_v6()) {
auto v6 = address.to_v6();
@@ -133,37 +129,31 @@ namespace net {
return address;
}
std::string
addr_to_normalized_string(boost::asio::ip::address address) {
std::string addr_to_normalized_string(boost::asio::ip::address address) {
return normalize_address(address).to_string();
}
std::string
addr_to_url_escaped_string(boost::asio::ip::address address) {
std::string addr_to_url_escaped_string(boost::asio::ip::address address) {
address = normalize_address(address);
if (address.is_v6()) {
std::stringstream ss;
ss << '[' << address.to_string() << ']';
return ss.str();
}
else {
} else {
return address.to_string();
}
}
int
encryption_mode_for_address(boost::asio::ip::address address) {
int encryption_mode_for_address(boost::asio::ip::address address) {
auto nettype = net::from_address(address.to_string());
if (nettype == net::net_e::PC || nettype == net::net_e::LAN) {
return config::stream.lan_encryption_mode;
}
else {
} else {
return config::stream.wan_encryption_mode;
}
}
host_t
host_create(af_e af, ENetAddress &addr, std::uint16_t port) {
host_t host_create(af_e af, ENetAddress &addr, std::uint16_t port) {
static std::once_flag enet_init_flag;
std::call_once(enet_init_flag, []() {
enet_initialize();
@@ -174,7 +164,7 @@ namespace net {
enet_address_set_port(&addr, port);
// Maximum of 128 clients, which should be enough for anyone
auto host = host_t { enet_host_create(af == IPV4 ? AF_INET : AF_INET6, &addr, 128, 0, 0, 0) };
auto host = host_t {enet_host_create(af == IPV4 ? AF_INET : AF_INET6, &addr, 128, 0, 0, 0)};
// Enable opportunistic QoS tagging (automatically disables if the network appears to drop tagged packets)
enet_socket_set_option(host->socket, ENET_SOCKOPT_QOS, 1);
@@ -182,8 +172,7 @@ namespace net {
return host;
}
void
free_host(ENetHost *host) {
void free_host(ENetHost *host) {
std::for_each(host->peers, host->peers + host->peerCount, [](ENetPeer &peer_ref) {
ENetPeer *peer = &peer_ref;
@@ -195,8 +184,7 @@ namespace net {
enet_host_destroy(host);
}
std::uint16_t
map_port(int port) {
std::uint16_t map_port(int port) {
// calculate the port from the config port
auto mapped_port = (std::uint16_t)((int) config::sunshine.port + port);
@@ -213,10 +201,9 @@ namespace net {
* @param hostname The hostname to use for instance name generation.
* @return Hostname-based instance name or "Sunshine" if hostname is invalid.
*/
std::string
mdns_instance_name(const std::string_view &hostname) {
std::string mdns_instance_name(const std::string_view &hostname) {
// Start with the unmodified hostname
std::string instancename { hostname.data(), hostname.size() };
std::string instancename {hostname.data(), hostname.size()};
// Truncate to 63 characters per RFC 6763 section 7.2.
if (instancename.size() > 63) {
@@ -227,8 +214,7 @@ namespace net {
// Replace any spaces with dashes
if (instancename[i] == ' ') {
instancename[i] = '-';
}
else if (!std::isalnum(instancename[i]) && instancename[i] != '-') {
} else if (!std::isalnum(instancename[i]) && instancename[i] != '-') {
// Stop at the first invalid character
instancename.resize(i);
break;

View File

@@ -4,18 +4,19 @@
*/
#pragma once
// standard includes
#include <tuple>
#include <utility>
// lib includes
#include <boost/asio.hpp>
#include <enet/enet.h>
// local includes
#include "utility.h"
namespace net {
void
free_host(ENetHost *host);
void free_host(ENetHost *host);
/**
* @brief Map a specified port based on the base port.
@@ -26,8 +27,7 @@ namespace net {
* @examples_end
* @todo Ensure port is not already in use by another application.
*/
std::uint16_t
map_port(int port);
std::uint16_t map_port(int port);
using host_t = util::safe_ptr<ENetHost, free_host>;
using peer_t = ENetPeer *;
@@ -44,32 +44,26 @@ namespace net {
BOTH ///< IPv4 and IPv6
};
net_e
from_enum_string(const std::string_view &view);
std::string_view
to_enum_string(net_e net);
net_e from_enum_string(const std::string_view &view);
std::string_view to_enum_string(net_e net);
net_e
from_address(const std::string_view &view);
net_e from_address(const std::string_view &view);
host_t
host_create(af_e af, ENetAddress &addr, std::uint16_t port);
host_t host_create(af_e af, ENetAddress &addr, std::uint16_t port);
/**
* @brief Get the address family enum value from a string.
* @param view The config option value.
* @return The address family enum value.
*/
af_e
af_from_enum_string(const std::string_view &view);
af_e af_from_enum_string(const std::string_view &view);
/**
* @brief Get the wildcard binding address for a given address family.
* @param af Address family.
* @return Normalized address.
*/
std::string_view
af_to_any_address_string(af_e af);
std::string_view af_to_any_address_string(af_e af);
/**
* @brief Convert an address to a normalized form.
@@ -77,8 +71,7 @@ namespace net {
* @param address The address to normalize.
* @return Normalized address.
*/
boost::asio::ip::address
normalize_address(boost::asio::ip::address address);
boost::asio::ip::address normalize_address(boost::asio::ip::address address);
/**
* @brief Get the given address in normalized string form.
@@ -86,8 +79,7 @@ namespace net {
* @param address The address to normalize.
* @return Normalized address in string form.
*/
std::string
addr_to_normalized_string(boost::asio::ip::address address);
std::string addr_to_normalized_string(boost::asio::ip::address address);
/**
* @brief Get the given address in a normalized form for the host portion of a URL.
@@ -95,22 +87,19 @@ namespace net {
* @param address The address to normalize and escape.
* @return Normalized address in URL-escaped string.
*/
std::string
addr_to_url_escaped_string(boost::asio::ip::address address);
std::string addr_to_url_escaped_string(boost::asio::ip::address address);
/**
* @brief Get the encryption mode for the given remote endpoint address.
* @param address The address used to look up the desired encryption mode.
* @return The WAN or LAN encryption mode, based on the provided address.
*/
int
encryption_mode_for_address(boost::asio::ip::address address);
int encryption_mode_for_address(boost::asio::ip::address address);
/**
* @brief Returns a string for use as the instance name for mDNS.
* @param hostname The hostname to use for instance name generation.
* @return Hostname-based instance name or "Sunshine" if hostname is invalid.
*/
std::string
mdns_instance_name(const std::string_view &hostname);
std::string mdns_instance_name(const std::string_view &hostname);
} // namespace net

File diff suppressed because it is too large Load Diff

View File

@@ -1,161 +1,155 @@
/**
* @file src/nvenc/nvenc_base.h
* @brief Declarations for abstract platform-agnostic base of standalone NVENC encoder.
*/
#pragma once
#include "nvenc_colorspace.h"
#include "nvenc_config.h"
#include "nvenc_encoded_frame.h"
#include "src/logging.h"
#include "src/video.h"
#include <ffnvcodec/nvEncodeAPI.h>
/**
* @brief Standalone NVENC encoder
*/
namespace nvenc {
/**
* @brief Abstract platform-agnostic base of standalone NVENC encoder.
* Derived classes perform platform-specific operations.
*/
class nvenc_base {
public:
/**
* @param device_type Underlying device type used by derived class.
*/
explicit nvenc_base(NV_ENC_DEVICE_TYPE device_type);
virtual ~nvenc_base();
nvenc_base(const nvenc_base &) = delete;
nvenc_base &
operator=(const nvenc_base &) = delete;
/**
* @brief Create the encoder.
* @param config NVENC encoder configuration.
* @param client_config Stream configuration requested by the client.
* @param colorspace YUV colorspace.
* @param buffer_format Platform-agnostic input surface format.
* @return `true` on success, `false` on error
*/
bool
create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format);
/**
* @brief Destroy the encoder.
* Derived classes classes call it in the destructor.
*/
void
destroy_encoder();
/**
* @brief Encode the next frame using platform-specific input surface.
* @param frame_index Frame index that uniquely identifies the frame.
* Afterwards serves as parameter for `invalidate_ref_frames()`.
* No restrictions on the first frame index, but later frame indexes must be subsequent.
* @param force_idr Whether to encode frame as forced IDR.
* @return Encoded frame.
*/
nvenc_encoded_frame
encode_frame(uint64_t frame_index, bool force_idr);
/**
* @brief Perform reference frame invalidation (RFI) procedure.
* @param first_frame First frame index of the invalidation range.
* @param last_frame Last frame index of the invalidation range.
* @return `true` on success, `false` on error.
* After error next frame must be encoded with `force_idr = true`.
*/
bool
invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame);
protected:
/**
* @brief Required. Used for loading NvEnc library and setting `nvenc` variable with `NvEncodeAPICreateInstance()`.
* Called during `create_encoder()` if `nvenc` variable is not initialized.
* @return `true` on success, `false` on error
*/
virtual bool
init_library() = 0;
/**
* @brief Required. Used for creating outside-facing input surface,
* registering this surface with `nvenc->nvEncRegisterResource()` and setting `registered_input_buffer` variable.
* Called during `create_encoder()`.
* @return `true` on success, `false` on error
*/
virtual bool
create_and_register_input_buffer() = 0;
/**
* @brief Optional. Override if you must perform additional operations on the registered input surface in the beginning of `encode_frame()`.
* Typically used for interop copy.
* @return `true` on success, `false` on error
*/
virtual bool
synchronize_input_buffer() { return true; }
/**
* @brief Optional. Override if you want to create encoder in async mode.
* In this case must also set `async_event_handle` variable.
* @param timeout_ms Wait timeout in milliseconds
* @return `true` on success, `false` on timeout or error
*/
virtual bool
wait_for_async_event(uint32_t timeout_ms) { return false; }
bool
nvenc_failed(NVENCSTATUS status);
/**
* @brief This function returns the corresponding struct version for the minimum API required by the codec.
* @details Reducing the struct versions maximizes driver compatibility by avoiding needless API breaks.
* @param version The raw structure version from `NVENCAPI_STRUCT_VERSION()`.
* @param v11_struct_version Optionally specifies the struct version to use with v11 SDK major versions.
* @param v12_struct_version Optionally specifies the struct version to use with v12 SDK major versions.
* @return A suitable struct version for the active codec.
*/
uint32_t
min_struct_version(uint32_t version, uint32_t v11_struct_version = 0, uint32_t v12_struct_version = 0);
const NV_ENC_DEVICE_TYPE device_type;
void *encoder = nullptr;
struct {
uint32_t width = 0;
uint32_t height = 0;
NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED;
uint32_t ref_frames_in_dpb = 0;
bool rfi = false;
} encoder_params;
std::string last_nvenc_error_string;
// Derived classes set these variables
void *device = nullptr; ///< Platform-specific handle of encoding device.
///< Should be set in constructor or `init_library()`.
std::shared_ptr<NV_ENCODE_API_FUNCTION_LIST> nvenc; ///< Function pointers list produced by `NvEncodeAPICreateInstance()`.
///< Should be set in `init_library()`.
NV_ENC_REGISTERED_PTR registered_input_buffer = nullptr; ///< Platform-specific input surface registered with `NvEncRegisterResource()`.
///< Should be set in `create_and_register_input_buffer()`.
void *async_event_handle = nullptr; ///< (optional) Platform-specific handle of event object event.
///< Can be set in constructor or `init_library()`, must override `wait_for_async_event()`.
private:
NV_ENC_OUTPUT_PTR output_bitstream = nullptr;
uint32_t minimum_api_version = 0;
struct {
uint64_t last_encoded_frame_index = 0;
bool rfi_needs_confirmation = false;
std::pair<uint64_t, uint64_t> last_rfi_range;
logging::min_max_avg_periodic_logger<double> frame_size_logger = { debug, "NvEnc: encoded frame sizes in kB", "" };
} encoder_state;
};
} // namespace nvenc
/**
* @file src/nvenc/nvenc_base.h
* @brief Declarations for abstract platform-agnostic base of standalone NVENC encoder.
*/
#pragma once
// lib includes
#include <ffnvcodec/nvEncodeAPI.h>
// local includes
#include "nvenc_colorspace.h"
#include "nvenc_config.h"
#include "nvenc_encoded_frame.h"
#include "src/logging.h"
#include "src/video.h"
/**
* @brief Standalone NVENC encoder
*/
namespace nvenc {
/**
* @brief Abstract platform-agnostic base of standalone NVENC encoder.
* Derived classes perform platform-specific operations.
*/
class nvenc_base {
public:
/**
* @param device_type Underlying device type used by derived class.
*/
explicit nvenc_base(NV_ENC_DEVICE_TYPE device_type);
virtual ~nvenc_base();
nvenc_base(const nvenc_base &) = delete;
nvenc_base &operator=(const nvenc_base &) = delete;
/**
* @brief Create the encoder.
* @param config NVENC encoder configuration.
* @param client_config Stream configuration requested by the client.
* @param colorspace YUV colorspace.
* @param buffer_format Platform-agnostic input surface format.
* @return `true` on success, `false` on error
*/
bool create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format);
/**
* @brief Destroy the encoder.
* Derived classes classes call it in the destructor.
*/
void destroy_encoder();
/**
* @brief Encode the next frame using platform-specific input surface.
* @param frame_index Frame index that uniquely identifies the frame.
* Afterwards serves as parameter for `invalidate_ref_frames()`.
* No restrictions on the first frame index, but later frame indexes must be subsequent.
* @param force_idr Whether to encode frame as forced IDR.
* @return Encoded frame.
*/
nvenc_encoded_frame encode_frame(uint64_t frame_index, bool force_idr);
/**
* @brief Perform reference frame invalidation (RFI) procedure.
* @param first_frame First frame index of the invalidation range.
* @param last_frame Last frame index of the invalidation range.
* @return `true` on success, `false` on error.
* After error next frame must be encoded with `force_idr = true`.
*/
bool invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame);
protected:
/**
* @brief Required. Used for loading NvEnc library and setting `nvenc` variable with `NvEncodeAPICreateInstance()`.
* Called during `create_encoder()` if `nvenc` variable is not initialized.
* @return `true` on success, `false` on error
*/
virtual bool init_library() = 0;
/**
* @brief Required. Used for creating outside-facing input surface,
* registering this surface with `nvenc->nvEncRegisterResource()` and setting `registered_input_buffer` variable.
* Called during `create_encoder()`.
* @return `true` on success, `false` on error
*/
virtual bool create_and_register_input_buffer() = 0;
/**
* @brief Optional. Override if you must perform additional operations on the registered input surface in the beginning of `encode_frame()`.
* Typically used for interop copy.
* @return `true` on success, `false` on error
*/
virtual bool synchronize_input_buffer() {
return true;
}
/**
* @brief Optional. Override if you want to create encoder in async mode.
* In this case must also set `async_event_handle` variable.
* @param timeout_ms Wait timeout in milliseconds
* @return `true` on success, `false` on timeout or error
*/
virtual bool wait_for_async_event(uint32_t timeout_ms) {
return false;
}
bool nvenc_failed(NVENCSTATUS status);
/**
* @brief This function returns the corresponding struct version for the minimum API required by the codec.
* @details Reducing the struct versions maximizes driver compatibility by avoiding needless API breaks.
* @param version The raw structure version from `NVENCAPI_STRUCT_VERSION()`.
* @param v11_struct_version Optionally specifies the struct version to use with v11 SDK major versions.
* @param v12_struct_version Optionally specifies the struct version to use with v12 SDK major versions.
* @return A suitable struct version for the active codec.
*/
uint32_t min_struct_version(uint32_t version, uint32_t v11_struct_version = 0, uint32_t v12_struct_version = 0);
const NV_ENC_DEVICE_TYPE device_type;
void *encoder = nullptr;
struct {
uint32_t width = 0;
uint32_t height = 0;
NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED;
uint32_t ref_frames_in_dpb = 0;
bool rfi = false;
} encoder_params;
std::string last_nvenc_error_string;
// Derived classes set these variables
void *device = nullptr; ///< Platform-specific handle of encoding device.
///< Should be set in constructor or `init_library()`.
std::shared_ptr<NV_ENCODE_API_FUNCTION_LIST> nvenc; ///< Function pointers list produced by `NvEncodeAPICreateInstance()`.
///< Should be set in `init_library()`.
NV_ENC_REGISTERED_PTR registered_input_buffer = nullptr; ///< Platform-specific input surface registered with `NvEncRegisterResource()`.
///< Should be set in `create_and_register_input_buffer()`.
void *async_event_handle = nullptr; ///< (optional) Platform-specific handle of event object event.
///< Can be set in constructor or `init_library()`, must override `wait_for_async_event()`.
private:
NV_ENC_OUTPUT_PTR output_bitstream = nullptr;
uint32_t minimum_api_version = 0;
struct {
uint64_t last_encoded_frame_index = 0;
bool rfi_needs_confirmation = false;
std::pair<uint64_t, uint64_t> last_rfi_range;
logging::min_max_avg_periodic_logger<double> frame_size_logger = {debug, "NvEnc: encoded frame sizes in kB", ""};
} encoder_state;
};
} // namespace nvenc

View File

@@ -1,21 +1,22 @@
/**
* @file src/nvenc/nvenc_colorspace.h
* @brief Declarations for NVENC YUV colorspace.
*/
#pragma once
#include <ffnvcodec/nvEncodeAPI.h>
namespace nvenc {
/**
* @brief YUV colorspace and color range.
*/
struct nvenc_colorspace_t {
NV_ENC_VUI_COLOR_PRIMARIES primaries;
NV_ENC_VUI_TRANSFER_CHARACTERISTIC tranfer_function;
NV_ENC_VUI_MATRIX_COEFFS matrix;
bool full_range;
};
} // namespace nvenc
/**
* @file src/nvenc/nvenc_colorspace.h
* @brief Declarations for NVENC YUV colorspace.
*/
#pragma once
// lib includes
#include <ffnvcodec/nvEncodeAPI.h>
namespace nvenc {
/**
* @brief YUV colorspace and color range.
*/
struct nvenc_colorspace_t {
NV_ENC_VUI_COLOR_PRIMARIES primaries;
NV_ENC_VUI_TRANSFER_CHARACTERISTIC tranfer_function;
NV_ENC_VUI_MATRIX_COEFFS matrix;
bool full_range;
};
} // namespace nvenc

View File

@@ -1,53 +1,53 @@
/**
* @file src/nvenc/nvenc_config.h
* @brief Declarations for NVENC encoder configuration.
*/
#pragma once
namespace nvenc {
enum class nvenc_two_pass {
disabled, ///< Single pass, the fastest and no extra vram
quarter_resolution, ///< Larger motion vectors being caught, faster and uses less extra vram
full_resolution, ///< Better overall statistics, slower and uses more extra vram
};
/**
* @brief NVENC encoder configuration.
*/
struct nvenc_config {
// Quality preset from 1 to 7, higher is slower
int quality_preset = 1;
// Use optional preliminary pass for better motion vectors, bitrate distribution and stricter VBV(HRD), uses CUDA cores
nvenc_two_pass two_pass = nvenc_two_pass::quarter_resolution;
// Percentage increase of VBV/HRD from the default single frame, allows low-latency variable bitrate
int vbv_percentage_increase = 0;
// Improves fades compression, uses CUDA cores
bool weighted_prediction = false;
// Allocate more bitrate to flat regions since they're visually more perceptible, uses CUDA cores
bool adaptive_quantization = false;
// Don't use QP below certain value, limits peak image quality to save bitrate
bool enable_min_qp = false;
// Min QP value for H.264 when enable_min_qp is selected
unsigned min_qp_h264 = 19;
// Min QP value for HEVC when enable_min_qp is selected
unsigned min_qp_hevc = 23;
// Min QP value for AV1 when enable_min_qp is selected
unsigned min_qp_av1 = 23;
// Use CAVLC entropy coding in H.264 instead of CABAC, not relevant and here for historical reasons
bool h264_cavlc = false;
// Add filler data to encoded frames to stay at target bitrate, mainly for testing
bool insert_filler_data = false;
};
} // namespace nvenc
/**
* @file src/nvenc/nvenc_config.h
* @brief Declarations for NVENC encoder configuration.
*/
#pragma once
namespace nvenc {
enum class nvenc_two_pass {
disabled, ///< Single pass, the fastest and no extra vram
quarter_resolution, ///< Larger motion vectors being caught, faster and uses less extra vram
full_resolution, ///< Better overall statistics, slower and uses more extra vram
};
/**
* @brief NVENC encoder configuration.
*/
struct nvenc_config {
// Quality preset from 1 to 7, higher is slower
int quality_preset = 1;
// Use optional preliminary pass for better motion vectors, bitrate distribution and stricter VBV(HRD), uses CUDA cores
nvenc_two_pass two_pass = nvenc_two_pass::quarter_resolution;
// Percentage increase of VBV/HRD from the default single frame, allows low-latency variable bitrate
int vbv_percentage_increase = 0;
// Improves fades compression, uses CUDA cores
bool weighted_prediction = false;
// Allocate more bitrate to flat regions since they're visually more perceptible, uses CUDA cores
bool adaptive_quantization = false;
// Don't use QP below certain value, limits peak image quality to save bitrate
bool enable_min_qp = false;
// Min QP value for H.264 when enable_min_qp is selected
unsigned min_qp_h264 = 19;
// Min QP value for HEVC when enable_min_qp is selected
unsigned min_qp_hevc = 23;
// Min QP value for AV1 when enable_min_qp is selected
unsigned min_qp_av1 = 23;
// Use CAVLC entropy coding in H.264 instead of CABAC, not relevant and here for historical reasons
bool h264_cavlc = false;
// Add filler data to encoded frames to stay at target bitrate, mainly for testing
bool insert_filler_data = false;
};
} // namespace nvenc

View File

@@ -1,58 +1,57 @@
/**
* @file src/nvenc/nvenc_d3d11.cpp
* @brief Definitions for abstract Direct3D11 NVENC encoder.
*/
#include "src/logging.h"
#ifdef _WIN32
#include "nvenc_d3d11.h"
namespace nvenc {
nvenc_d3d11::~nvenc_d3d11() {
if (dll) {
FreeLibrary(dll);
dll = NULL;
}
}
bool
nvenc_d3d11::init_library() {
if (dll) return true;
#ifdef _WIN64
constexpr auto dll_name = "nvEncodeAPI64.dll";
#else
constexpr auto dll_name = "nvEncodeAPI.dll";
#endif
if ((dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
if (auto create_instance = (decltype(NvEncodeAPICreateInstance) *) GetProcAddress(dll, "NvEncodeAPICreateInstance")) {
auto new_nvenc = std::make_unique<NV_ENCODE_API_FUNCTION_LIST>();
new_nvenc->version = min_struct_version(NV_ENCODE_API_FUNCTION_LIST_VER);
if (nvenc_failed(create_instance(new_nvenc.get()))) {
BOOST_LOG(error) << "NvEnc: NvEncodeAPICreateInstance() failed: " << last_nvenc_error_string;
}
else {
nvenc = std::move(new_nvenc);
return true;
}
}
else {
BOOST_LOG(error) << "NvEnc: No NvEncodeAPICreateInstance() in " << dll_name;
}
}
else {
BOOST_LOG(debug) << "NvEnc: Couldn't load NvEnc library " << dll_name;
}
if (dll) {
FreeLibrary(dll);
dll = NULL;
}
return false;
}
} // namespace nvenc
#endif
/**
* @file src/nvenc/nvenc_d3d11.cpp
* @brief Definitions for abstract Direct3D11 NVENC encoder.
*/
// local includes
#include "src/logging.h"
#ifdef _WIN32
#include "nvenc_d3d11.h"
namespace nvenc {
nvenc_d3d11::~nvenc_d3d11() {
if (dll) {
FreeLibrary(dll);
dll = NULL;
}
}
bool nvenc_d3d11::init_library() {
if (dll) {
return true;
}
#ifdef _WIN64
constexpr auto dll_name = "nvEncodeAPI64.dll";
#else
constexpr auto dll_name = "nvEncodeAPI.dll";
#endif
if ((dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
if (auto create_instance = (decltype(NvEncodeAPICreateInstance) *) GetProcAddress(dll, "NvEncodeAPICreateInstance")) {
auto new_nvenc = std::make_unique<NV_ENCODE_API_FUNCTION_LIST>();
new_nvenc->version = min_struct_version(NV_ENCODE_API_FUNCTION_LIST_VER);
if (nvenc_failed(create_instance(new_nvenc.get()))) {
BOOST_LOG(error) << "NvEnc: NvEncodeAPICreateInstance() failed: " << last_nvenc_error_string;
} else {
nvenc = std::move(new_nvenc);
return true;
}
} else {
BOOST_LOG(error) << "NvEnc: No NvEncodeAPICreateInstance() in " << dll_name;
}
} else {
BOOST_LOG(debug) << "NvEnc: Couldn't load NvEnc library " << dll_name;
}
if (dll) {
FreeLibrary(dll);
dll = NULL;
}
return false;
}
} // namespace nvenc
#endif

View File

@@ -1,47 +1,48 @@
/**
* @file src/nvenc/nvenc_d3d11.h
* @brief Declarations for abstract Direct3D11 NVENC encoder.
*/
#pragma once
#ifdef _WIN32
#include <comdef.h>
#include <d3d11.h>
#include "nvenc_base.h"
namespace nvenc {
_COM_SMARTPTR_TYPEDEF(ID3D11Device, IID_ID3D11Device);
_COM_SMARTPTR_TYPEDEF(ID3D11Texture2D, IID_ID3D11Texture2D);
_COM_SMARTPTR_TYPEDEF(IDXGIDevice, IID_IDXGIDevice);
_COM_SMARTPTR_TYPEDEF(IDXGIAdapter, IID_IDXGIAdapter);
/**
* @brief Abstract Direct3D11 NVENC encoder.
* Encapsulates common code used by native and interop implementations.
*/
class nvenc_d3d11: public nvenc_base {
public:
explicit nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type):
nvenc_base(device_type) {}
~nvenc_d3d11();
/**
* @brief Get input surface texture.
* @return Input surface texture.
*/
virtual ID3D11Texture2D *
get_input_texture() = 0;
protected:
bool
init_library() override;
private:
HMODULE dll = NULL;
};
} // namespace nvenc
#endif
/**
* @file src/nvenc/nvenc_d3d11.h
* @brief Declarations for abstract Direct3D11 NVENC encoder.
*/
#pragma once
#ifdef _WIN32
// standard includes
#include <comdef.h>
#include <d3d11.h>
// local includes
#include "nvenc_base.h"
namespace nvenc {
_COM_SMARTPTR_TYPEDEF(ID3D11Device, IID_ID3D11Device);
_COM_SMARTPTR_TYPEDEF(ID3D11Texture2D, IID_ID3D11Texture2D);
_COM_SMARTPTR_TYPEDEF(IDXGIDevice, IID_IDXGIDevice);
_COM_SMARTPTR_TYPEDEF(IDXGIAdapter, IID_IDXGIAdapter);
/**
* @brief Abstract Direct3D11 NVENC encoder.
* Encapsulates common code used by native and interop implementations.
*/
class nvenc_d3d11: public nvenc_base {
public:
explicit nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type):
nvenc_base(device_type) {
}
~nvenc_d3d11();
/**
* @brief Get input surface texture.
* @return Input surface texture.
*/
virtual ID3D11Texture2D *get_input_texture() = 0;
protected:
bool init_library() override;
private:
HMODULE dll = NULL;
};
} // namespace nvenc
#endif

View File

@@ -1,71 +1,74 @@
/**
* @file src/nvenc/nvenc_d3d11_native.cpp
* @brief Definitions for native Direct3D11 NVENC encoder.
*/
#ifdef _WIN32
#include "nvenc_d3d11_native.h"
#include "nvenc_utils.h"
namespace nvenc {
nvenc_d3d11_native::nvenc_d3d11_native(ID3D11Device *d3d_device):
nvenc_d3d11(NV_ENC_DEVICE_TYPE_DIRECTX),
d3d_device(d3d_device) {
device = d3d_device;
}
nvenc_d3d11_native::~nvenc_d3d11_native() {
if (encoder) destroy_encoder();
}
ID3D11Texture2D *
nvenc_d3d11_native::get_input_texture() {
return d3d_input_texture.GetInterfacePtr();
}
bool
nvenc_d3d11_native::create_and_register_input_buffer() {
if (encoder_params.buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) {
BOOST_LOG(error) << "NvEnc: 10-bit 4:4:4 encoding is incompatible with D3D11 surface formats, use CUDA interop";
return false;
}
if (!d3d_input_texture) {
D3D11_TEXTURE2D_DESC desc = {};
desc.Width = encoder_params.width;
desc.Height = encoder_params.height;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = dxgi_format_from_nvenc_format(encoder_params.buffer_format);
desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_RENDER_TARGET;
if (d3d_device->CreateTexture2D(&desc, nullptr, &d3d_input_texture) != S_OK) {
BOOST_LOG(error) << "NvEnc: couldn't create input texture";
return false;
}
}
if (!registered_input_buffer) {
NV_ENC_REGISTER_RESOURCE register_resource = { min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4) };
register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX;
register_resource.width = encoder_params.width;
register_resource.height = encoder_params.height;
register_resource.resourceToRegister = d3d_input_texture.GetInterfacePtr();
register_resource.bufferFormat = encoder_params.buffer_format;
register_resource.bufferUsage = NV_ENC_INPUT_IMAGE;
if (nvenc_failed(nvenc->nvEncRegisterResource(encoder, &register_resource))) {
BOOST_LOG(error) << "NvEnc: NvEncRegisterResource() failed: " << last_nvenc_error_string;
return false;
}
registered_input_buffer = register_resource.registeredResource;
}
return true;
}
} // namespace nvenc
#endif
/**
* @file src/nvenc/nvenc_d3d11_native.cpp
* @brief Definitions for native Direct3D11 NVENC encoder.
*/
#ifdef _WIN32
// this include
#include "nvenc_d3d11_native.h"
// local includes
#include "nvenc_utils.h"
namespace nvenc {
nvenc_d3d11_native::nvenc_d3d11_native(ID3D11Device *d3d_device):
nvenc_d3d11(NV_ENC_DEVICE_TYPE_DIRECTX),
d3d_device(d3d_device) {
device = d3d_device;
}
nvenc_d3d11_native::~nvenc_d3d11_native() {
if (encoder) {
destroy_encoder();
}
}
ID3D11Texture2D *
nvenc_d3d11_native::get_input_texture() {
return d3d_input_texture.GetInterfacePtr();
}
bool nvenc_d3d11_native::create_and_register_input_buffer() {
if (encoder_params.buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) {
BOOST_LOG(error) << "NvEnc: 10-bit 4:4:4 encoding is incompatible with D3D11 surface formats, use CUDA interop";
return false;
}
if (!d3d_input_texture) {
D3D11_TEXTURE2D_DESC desc = {};
desc.Width = encoder_params.width;
desc.Height = encoder_params.height;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = dxgi_format_from_nvenc_format(encoder_params.buffer_format);
desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_RENDER_TARGET;
if (d3d_device->CreateTexture2D(&desc, nullptr, &d3d_input_texture) != S_OK) {
BOOST_LOG(error) << "NvEnc: couldn't create input texture";
return false;
}
}
if (!registered_input_buffer) {
NV_ENC_REGISTER_RESOURCE register_resource = {min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4)};
register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX;
register_resource.width = encoder_params.width;
register_resource.height = encoder_params.height;
register_resource.resourceToRegister = d3d_input_texture.GetInterfacePtr();
register_resource.bufferFormat = encoder_params.buffer_format;
register_resource.bufferUsage = NV_ENC_INPUT_IMAGE;
if (nvenc_failed(nvenc->nvEncRegisterResource(encoder, &register_resource))) {
BOOST_LOG(error) << "NvEnc: NvEncRegisterResource() failed: " << last_nvenc_error_string;
return false;
}
registered_input_buffer = register_resource.registeredResource;
}
return true;
}
} // namespace nvenc
#endif

View File

@@ -1,38 +1,37 @@
/**
* @file src/nvenc/nvenc_d3d11_native.h
* @brief Declarations for native Direct3D11 NVENC encoder.
*/
#pragma once
#ifdef _WIN32
#include <comdef.h>
#include <d3d11.h>
#include "nvenc_d3d11.h"
namespace nvenc {
/**
* @brief Native Direct3D11 NVENC encoder.
*/
class nvenc_d3d11_native final: public nvenc_d3d11 {
public:
/**
* @param d3d_device Direct3D11 device used for encoding.
*/
explicit nvenc_d3d11_native(ID3D11Device *d3d_device);
~nvenc_d3d11_native();
ID3D11Texture2D *
get_input_texture() override;
private:
bool
create_and_register_input_buffer() override;
const ID3D11DevicePtr d3d_device;
ID3D11Texture2DPtr d3d_input_texture;
};
} // namespace nvenc
#endif
/**
* @file src/nvenc/nvenc_d3d11_native.h
* @brief Declarations for native Direct3D11 NVENC encoder.
*/
#pragma once
#ifdef _WIN32
// standard includes
#include <comdef.h>
#include <d3d11.h>
// local includes
#include "nvenc_d3d11.h"
namespace nvenc {
/**
* @brief Native Direct3D11 NVENC encoder.
*/
class nvenc_d3d11_native final: public nvenc_d3d11 {
public:
/**
* @param d3d_device Direct3D11 device used for encoding.
*/
explicit nvenc_d3d11_native(ID3D11Device *d3d_device);
~nvenc_d3d11_native();
ID3D11Texture2D *get_input_texture() override;
private:
bool create_and_register_input_buffer() override;
const ID3D11DevicePtr d3d_device;
ID3D11Texture2DPtr d3d_input_texture;
};
} // namespace nvenc
#endif

View File

@@ -1,267 +1,269 @@
/**
* @file src/nvenc/nvenc_d3d11_on_cuda.cpp
* @brief Definitions for CUDA NVENC encoder with Direct3D11 input surfaces.
*/
#ifdef _WIN32
#include "nvenc_d3d11_on_cuda.h"
#include "nvenc_utils.h"
namespace nvenc {
nvenc_d3d11_on_cuda::nvenc_d3d11_on_cuda(ID3D11Device *d3d_device):
nvenc_d3d11(NV_ENC_DEVICE_TYPE_CUDA),
d3d_device(d3d_device) {
}
nvenc_d3d11_on_cuda::~nvenc_d3d11_on_cuda() {
if (encoder) destroy_encoder();
if (cuda_context) {
{
auto autopop_context = push_context();
if (cuda_d3d_input_texture) {
if (cuda_failed(cuda_functions.cuGraphicsUnregisterResource(cuda_d3d_input_texture))) {
BOOST_LOG(error) << "NvEnc: cuGraphicsUnregisterResource() failed: error " << last_cuda_error;
}
cuda_d3d_input_texture = nullptr;
}
if (cuda_surface) {
if (cuda_failed(cuda_functions.cuMemFree(cuda_surface))) {
BOOST_LOG(error) << "NvEnc: cuMemFree() failed: error " << last_cuda_error;
}
cuda_surface = 0;
}
}
if (cuda_failed(cuda_functions.cuCtxDestroy(cuda_context))) {
BOOST_LOG(error) << "NvEnc: cuCtxDestroy() failed: error " << last_cuda_error;
}
cuda_context = nullptr;
}
if (cuda_functions.dll) {
FreeLibrary(cuda_functions.dll);
cuda_functions = {};
}
}
ID3D11Texture2D *
nvenc_d3d11_on_cuda::get_input_texture() {
return d3d_input_texture.GetInterfacePtr();
}
bool
nvenc_d3d11_on_cuda::init_library() {
if (!nvenc_d3d11::init_library()) return false;
constexpr auto dll_name = "nvcuda.dll";
if ((cuda_functions.dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
auto load_function = [&]<typename T>(T &location, auto symbol) -> bool {
location = (T) GetProcAddress(cuda_functions.dll, symbol);
return location != nullptr;
};
if (!load_function(cuda_functions.cuInit, "cuInit") ||
!load_function(cuda_functions.cuD3D11GetDevice, "cuD3D11GetDevice") ||
!load_function(cuda_functions.cuCtxCreate, "cuCtxCreate_v2") ||
!load_function(cuda_functions.cuCtxDestroy, "cuCtxDestroy_v2") ||
!load_function(cuda_functions.cuCtxPushCurrent, "cuCtxPushCurrent_v2") ||
!load_function(cuda_functions.cuCtxPopCurrent, "cuCtxPopCurrent_v2") ||
!load_function(cuda_functions.cuMemAllocPitch, "cuMemAllocPitch_v2") ||
!load_function(cuda_functions.cuMemFree, "cuMemFree_v2") ||
!load_function(cuda_functions.cuGraphicsD3D11RegisterResource, "cuGraphicsD3D11RegisterResource") ||
!load_function(cuda_functions.cuGraphicsUnregisterResource, "cuGraphicsUnregisterResource") ||
!load_function(cuda_functions.cuGraphicsMapResources, "cuGraphicsMapResources") ||
!load_function(cuda_functions.cuGraphicsUnmapResources, "cuGraphicsUnmapResources") ||
!load_function(cuda_functions.cuGraphicsSubResourceGetMappedArray, "cuGraphicsSubResourceGetMappedArray") ||
!load_function(cuda_functions.cuMemcpy2D, "cuMemcpy2D_v2")) {
BOOST_LOG(error) << "NvEnc: missing CUDA functions in " << dll_name;
FreeLibrary(cuda_functions.dll);
cuda_functions = {};
}
}
else {
BOOST_LOG(debug) << "NvEnc: couldn't load CUDA dynamic library " << dll_name;
}
if (cuda_functions.dll) {
IDXGIDevicePtr dxgi_device;
IDXGIAdapterPtr dxgi_adapter;
if (d3d_device &&
SUCCEEDED(d3d_device->QueryInterface(IID_PPV_ARGS(&dxgi_device))) &&
SUCCEEDED(dxgi_device->GetAdapter(&dxgi_adapter))) {
CUdevice cuda_device;
if (cuda_succeeded(cuda_functions.cuInit(0)) &&
cuda_succeeded(cuda_functions.cuD3D11GetDevice(&cuda_device, dxgi_adapter)) &&
cuda_succeeded(cuda_functions.cuCtxCreate(&cuda_context, CU_CTX_SCHED_BLOCKING_SYNC, cuda_device)) &&
cuda_succeeded(cuda_functions.cuCtxPopCurrent(&cuda_context))) {
device = cuda_context;
}
else {
BOOST_LOG(error) << "NvEnc: couldn't create CUDA interop context: error " << last_cuda_error;
}
}
else {
BOOST_LOG(error) << "NvEnc: couldn't get DXGI adapter for CUDA interop";
}
}
return device != nullptr;
}
bool
nvenc_d3d11_on_cuda::create_and_register_input_buffer() {
if (encoder_params.buffer_format != NV_ENC_BUFFER_FORMAT_YUV444_10BIT) {
BOOST_LOG(error) << "NvEnc: CUDA interop is expected to be used only for 10-bit 4:4:4 encoding";
return false;
}
if (!d3d_input_texture) {
D3D11_TEXTURE2D_DESC desc = {};
desc.Width = encoder_params.width;
desc.Height = encoder_params.height * 3; // Planar YUV
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = dxgi_format_from_nvenc_format(encoder_params.buffer_format);
desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_RENDER_TARGET;
if (d3d_device->CreateTexture2D(&desc, nullptr, &d3d_input_texture) != S_OK) {
BOOST_LOG(error) << "NvEnc: couldn't create input texture";
return false;
}
}
{
auto autopop_context = push_context();
if (!autopop_context) return false;
if (!cuda_d3d_input_texture) {
if (cuda_failed(cuda_functions.cuGraphicsD3D11RegisterResource(
&cuda_d3d_input_texture,
d3d_input_texture,
CU_GRAPHICS_REGISTER_FLAGS_NONE))) {
BOOST_LOG(error) << "NvEnc: cuGraphicsD3D11RegisterResource() failed: error " << last_cuda_error;
return false;
}
}
if (!cuda_surface) {
if (cuda_failed(cuda_functions.cuMemAllocPitch(
&cuda_surface,
&cuda_surface_pitch,
// Planar 16-bit YUV
encoder_params.width * 2,
encoder_params.height * 3, 16))) {
BOOST_LOG(error) << "NvEnc: cuMemAllocPitch() failed: error " << last_cuda_error;
return false;
}
}
}
if (!registered_input_buffer) {
NV_ENC_REGISTER_RESOURCE register_resource = { min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4) };
register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR;
register_resource.width = encoder_params.width;
register_resource.height = encoder_params.height;
register_resource.pitch = cuda_surface_pitch;
register_resource.resourceToRegister = (void *) cuda_surface;
register_resource.bufferFormat = encoder_params.buffer_format;
register_resource.bufferUsage = NV_ENC_INPUT_IMAGE;
if (nvenc_failed(nvenc->nvEncRegisterResource(encoder, &register_resource))) {
BOOST_LOG(error) << "NvEnc: NvEncRegisterResource() failed: " << last_nvenc_error_string;
return false;
}
registered_input_buffer = register_resource.registeredResource;
}
return true;
}
bool
nvenc_d3d11_on_cuda::synchronize_input_buffer() {
auto autopop_context = push_context();
if (!autopop_context) return false;
if (cuda_failed(cuda_functions.cuGraphicsMapResources(1, &cuda_d3d_input_texture, 0))) {
BOOST_LOG(error) << "NvEnc: cuGraphicsMapResources() failed: error " << last_cuda_error;
return false;
}
auto unmap = [&]() -> bool {
if (cuda_failed(cuda_functions.cuGraphicsUnmapResources(1, &cuda_d3d_input_texture, 0))) {
BOOST_LOG(error) << "NvEnc: cuGraphicsUnmapResources() failed: error " << last_cuda_error;
return false;
}
return true;
};
auto unmap_guard = util::fail_guard(unmap);
CUarray input_texture_array;
if (cuda_failed(cuda_functions.cuGraphicsSubResourceGetMappedArray(&input_texture_array, cuda_d3d_input_texture, 0, 0))) {
BOOST_LOG(error) << "NvEnc: cuGraphicsSubResourceGetMappedArray() failed: error " << last_cuda_error;
return false;
}
{
CUDA_MEMCPY2D copy_params = {};
copy_params.srcMemoryType = CU_MEMORYTYPE_ARRAY;
copy_params.srcArray = input_texture_array;
copy_params.dstMemoryType = CU_MEMORYTYPE_DEVICE;
copy_params.dstDevice = cuda_surface;
copy_params.dstPitch = cuda_surface_pitch;
// Planar 16-bit YUV
copy_params.WidthInBytes = encoder_params.width * 2;
copy_params.Height = encoder_params.height * 3;
if (cuda_failed(cuda_functions.cuMemcpy2D(&copy_params))) {
BOOST_LOG(error) << "NvEnc: cuMemcpy2D() failed: error " << last_cuda_error;
return false;
}
}
unmap_guard.disable();
return unmap();
}
bool
nvenc_d3d11_on_cuda::cuda_succeeded(CUresult result) {
last_cuda_error = result;
return result == CUDA_SUCCESS;
}
bool
nvenc_d3d11_on_cuda::cuda_failed(CUresult result) {
last_cuda_error = result;
return result != CUDA_SUCCESS;
}
nvenc_d3d11_on_cuda::autopop_context::~autopop_context() {
if (pushed_context) {
CUcontext popped_context;
if (parent.cuda_failed(parent.cuda_functions.cuCtxPopCurrent(&popped_context))) {
BOOST_LOG(error) << "NvEnc: cuCtxPopCurrent() failed: error " << parent.last_cuda_error;
}
}
}
nvenc_d3d11_on_cuda::autopop_context
nvenc_d3d11_on_cuda::push_context() {
if (cuda_context &&
cuda_succeeded(cuda_functions.cuCtxPushCurrent(cuda_context))) {
return { *this, cuda_context };
}
else {
BOOST_LOG(error) << "NvEnc: cuCtxPushCurrent() failed: error " << last_cuda_error;
return { *this, nullptr };
}
}
} // namespace nvenc
#endif
/**
* @file src/nvenc/nvenc_d3d11_on_cuda.cpp
* @brief Definitions for CUDA NVENC encoder with Direct3D11 input surfaces.
*/
#ifdef _WIN32
// this include
#include "nvenc_d3d11_on_cuda.h"
// local includes
#include "nvenc_utils.h"
namespace nvenc {
nvenc_d3d11_on_cuda::nvenc_d3d11_on_cuda(ID3D11Device *d3d_device):
nvenc_d3d11(NV_ENC_DEVICE_TYPE_CUDA),
d3d_device(d3d_device) {
}
nvenc_d3d11_on_cuda::~nvenc_d3d11_on_cuda() {
if (encoder) {
destroy_encoder();
}
if (cuda_context) {
{
auto autopop_context = push_context();
if (cuda_d3d_input_texture) {
if (cuda_failed(cuda_functions.cuGraphicsUnregisterResource(cuda_d3d_input_texture))) {
BOOST_LOG(error) << "NvEnc: cuGraphicsUnregisterResource() failed: error " << last_cuda_error;
}
cuda_d3d_input_texture = nullptr;
}
if (cuda_surface) {
if (cuda_failed(cuda_functions.cuMemFree(cuda_surface))) {
BOOST_LOG(error) << "NvEnc: cuMemFree() failed: error " << last_cuda_error;
}
cuda_surface = 0;
}
}
if (cuda_failed(cuda_functions.cuCtxDestroy(cuda_context))) {
BOOST_LOG(error) << "NvEnc: cuCtxDestroy() failed: error " << last_cuda_error;
}
cuda_context = nullptr;
}
if (cuda_functions.dll) {
FreeLibrary(cuda_functions.dll);
cuda_functions = {};
}
}
ID3D11Texture2D *nvenc_d3d11_on_cuda::get_input_texture() {
return d3d_input_texture.GetInterfacePtr();
}
bool nvenc_d3d11_on_cuda::init_library() {
if (!nvenc_d3d11::init_library()) {
return false;
}
constexpr auto dll_name = "nvcuda.dll";
if ((cuda_functions.dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
auto load_function = [&]<typename T>(T &location, auto symbol) -> bool {
location = (T) GetProcAddress(cuda_functions.dll, symbol);
return location != nullptr;
};
if (!load_function(cuda_functions.cuInit, "cuInit") ||
!load_function(cuda_functions.cuD3D11GetDevice, "cuD3D11GetDevice") ||
!load_function(cuda_functions.cuCtxCreate, "cuCtxCreate_v2") ||
!load_function(cuda_functions.cuCtxDestroy, "cuCtxDestroy_v2") ||
!load_function(cuda_functions.cuCtxPushCurrent, "cuCtxPushCurrent_v2") ||
!load_function(cuda_functions.cuCtxPopCurrent, "cuCtxPopCurrent_v2") ||
!load_function(cuda_functions.cuMemAllocPitch, "cuMemAllocPitch_v2") ||
!load_function(cuda_functions.cuMemFree, "cuMemFree_v2") ||
!load_function(cuda_functions.cuGraphicsD3D11RegisterResource, "cuGraphicsD3D11RegisterResource") ||
!load_function(cuda_functions.cuGraphicsUnregisterResource, "cuGraphicsUnregisterResource") ||
!load_function(cuda_functions.cuGraphicsMapResources, "cuGraphicsMapResources") ||
!load_function(cuda_functions.cuGraphicsUnmapResources, "cuGraphicsUnmapResources") ||
!load_function(cuda_functions.cuGraphicsSubResourceGetMappedArray, "cuGraphicsSubResourceGetMappedArray") ||
!load_function(cuda_functions.cuMemcpy2D, "cuMemcpy2D_v2")) {
BOOST_LOG(error) << "NvEnc: missing CUDA functions in " << dll_name;
FreeLibrary(cuda_functions.dll);
cuda_functions = {};
}
} else {
BOOST_LOG(debug) << "NvEnc: couldn't load CUDA dynamic library " << dll_name;
}
if (cuda_functions.dll) {
IDXGIDevicePtr dxgi_device;
IDXGIAdapterPtr dxgi_adapter;
if (d3d_device &&
SUCCEEDED(d3d_device->QueryInterface(IID_PPV_ARGS(&dxgi_device))) &&
SUCCEEDED(dxgi_device->GetAdapter(&dxgi_adapter))) {
CUdevice cuda_device;
if (cuda_succeeded(cuda_functions.cuInit(0)) &&
cuda_succeeded(cuda_functions.cuD3D11GetDevice(&cuda_device, dxgi_adapter)) &&
cuda_succeeded(cuda_functions.cuCtxCreate(&cuda_context, CU_CTX_SCHED_BLOCKING_SYNC, cuda_device)) &&
cuda_succeeded(cuda_functions.cuCtxPopCurrent(&cuda_context))) {
device = cuda_context;
} else {
BOOST_LOG(error) << "NvEnc: couldn't create CUDA interop context: error " << last_cuda_error;
}
} else {
BOOST_LOG(error) << "NvEnc: couldn't get DXGI adapter for CUDA interop";
}
}
return device != nullptr;
}
bool nvenc_d3d11_on_cuda::create_and_register_input_buffer() {
if (encoder_params.buffer_format != NV_ENC_BUFFER_FORMAT_YUV444_10BIT) {
BOOST_LOG(error) << "NvEnc: CUDA interop is expected to be used only for 10-bit 4:4:4 encoding";
return false;
}
if (!d3d_input_texture) {
D3D11_TEXTURE2D_DESC desc = {};
desc.Width = encoder_params.width;
desc.Height = encoder_params.height * 3; // Planar YUV
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = dxgi_format_from_nvenc_format(encoder_params.buffer_format);
desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_RENDER_TARGET;
if (d3d_device->CreateTexture2D(&desc, nullptr, &d3d_input_texture) != S_OK) {
BOOST_LOG(error) << "NvEnc: couldn't create input texture";
return false;
}
}
{
auto autopop_context = push_context();
if (!autopop_context) {
return false;
}
if (!cuda_d3d_input_texture) {
if (cuda_failed(cuda_functions.cuGraphicsD3D11RegisterResource(
&cuda_d3d_input_texture,
d3d_input_texture,
CU_GRAPHICS_REGISTER_FLAGS_NONE
))) {
BOOST_LOG(error) << "NvEnc: cuGraphicsD3D11RegisterResource() failed: error " << last_cuda_error;
return false;
}
}
if (!cuda_surface) {
if (cuda_failed(cuda_functions.cuMemAllocPitch(
&cuda_surface,
&cuda_surface_pitch,
// Planar 16-bit YUV
encoder_params.width * 2,
encoder_params.height * 3,
16
))) {
BOOST_LOG(error) << "NvEnc: cuMemAllocPitch() failed: error " << last_cuda_error;
return false;
}
}
}
if (!registered_input_buffer) {
NV_ENC_REGISTER_RESOURCE register_resource = {min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4)};
register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR;
register_resource.width = encoder_params.width;
register_resource.height = encoder_params.height;
register_resource.pitch = cuda_surface_pitch;
register_resource.resourceToRegister = (void *) cuda_surface;
register_resource.bufferFormat = encoder_params.buffer_format;
register_resource.bufferUsage = NV_ENC_INPUT_IMAGE;
if (nvenc_failed(nvenc->nvEncRegisterResource(encoder, &register_resource))) {
BOOST_LOG(error) << "NvEnc: NvEncRegisterResource() failed: " << last_nvenc_error_string;
return false;
}
registered_input_buffer = register_resource.registeredResource;
}
return true;
}
bool nvenc_d3d11_on_cuda::synchronize_input_buffer() {
auto autopop_context = push_context();
if (!autopop_context) {
return false;
}
if (cuda_failed(cuda_functions.cuGraphicsMapResources(1, &cuda_d3d_input_texture, 0))) {
BOOST_LOG(error) << "NvEnc: cuGraphicsMapResources() failed: error " << last_cuda_error;
return false;
}
auto unmap = [&]() -> bool {
if (cuda_failed(cuda_functions.cuGraphicsUnmapResources(1, &cuda_d3d_input_texture, 0))) {
BOOST_LOG(error) << "NvEnc: cuGraphicsUnmapResources() failed: error " << last_cuda_error;
return false;
}
return true;
};
auto unmap_guard = util::fail_guard(unmap);
CUarray input_texture_array;
if (cuda_failed(cuda_functions.cuGraphicsSubResourceGetMappedArray(&input_texture_array, cuda_d3d_input_texture, 0, 0))) {
BOOST_LOG(error) << "NvEnc: cuGraphicsSubResourceGetMappedArray() failed: error " << last_cuda_error;
return false;
}
{
CUDA_MEMCPY2D copy_params = {};
copy_params.srcMemoryType = CU_MEMORYTYPE_ARRAY;
copy_params.srcArray = input_texture_array;
copy_params.dstMemoryType = CU_MEMORYTYPE_DEVICE;
copy_params.dstDevice = cuda_surface;
copy_params.dstPitch = cuda_surface_pitch;
// Planar 16-bit YUV
copy_params.WidthInBytes = encoder_params.width * 2;
copy_params.Height = encoder_params.height * 3;
if (cuda_failed(cuda_functions.cuMemcpy2D(&copy_params))) {
BOOST_LOG(error) << "NvEnc: cuMemcpy2D() failed: error " << last_cuda_error;
return false;
}
}
unmap_guard.disable();
return unmap();
}
bool nvenc_d3d11_on_cuda::cuda_succeeded(CUresult result) {
last_cuda_error = result;
return result == CUDA_SUCCESS;
}
bool nvenc_d3d11_on_cuda::cuda_failed(CUresult result) {
last_cuda_error = result;
return result != CUDA_SUCCESS;
}
nvenc_d3d11_on_cuda::autopop_context::~autopop_context() {
if (pushed_context) {
CUcontext popped_context;
if (parent.cuda_failed(parent.cuda_functions.cuCtxPopCurrent(&popped_context))) {
BOOST_LOG(error) << "NvEnc: cuCtxPopCurrent() failed: error " << parent.last_cuda_error;
}
}
}
nvenc_d3d11_on_cuda::autopop_context nvenc_d3d11_on_cuda::push_context() {
if (cuda_context &&
cuda_succeeded(cuda_functions.cuCtxPushCurrent(cuda_context))) {
return {*this, cuda_context};
} else {
BOOST_LOG(error) << "NvEnc: cuCtxPushCurrent() failed: error " << last_cuda_error;
return {*this, nullptr};
}
}
} // namespace nvenc
#endif

View File

@@ -1,96 +1,89 @@
/**
* @file src/nvenc/nvenc_d3d11_on_cuda.h
* @brief Declarations for CUDA NVENC encoder with Direct3D11 input surfaces.
*/
#pragma once
#ifdef _WIN32
#include "nvenc_d3d11.h"
#include <ffnvcodec/dynlink_cuda.h>
namespace nvenc {
/**
* @brief Interop Direct3D11 on CUDA NVENC encoder.
* Input surface is Direct3D11, encoding is performed by CUDA.
*/
class nvenc_d3d11_on_cuda final: public nvenc_d3d11 {
public:
/**
* @param d3d_device Direct3D11 device that will create input surface texture.
* CUDA encoding device will be derived from it.
*/
explicit nvenc_d3d11_on_cuda(ID3D11Device *d3d_device);
~nvenc_d3d11_on_cuda();
ID3D11Texture2D *
get_input_texture() override;
private:
bool
init_library() override;
bool
create_and_register_input_buffer() override;
bool
synchronize_input_buffer() override;
bool
cuda_succeeded(CUresult result);
bool
cuda_failed(CUresult result);
struct autopop_context {
autopop_context(nvenc_d3d11_on_cuda &parent, CUcontext pushed_context):
parent(parent),
pushed_context(pushed_context) {
}
~autopop_context();
explicit
operator bool() const {
return pushed_context != nullptr;
}
nvenc_d3d11_on_cuda &parent;
CUcontext pushed_context = nullptr;
};
autopop_context
push_context();
HMODULE dll = NULL;
const ID3D11DevicePtr d3d_device;
ID3D11Texture2DPtr d3d_input_texture;
struct {
tcuInit *cuInit;
tcuD3D11GetDevice *cuD3D11GetDevice;
tcuCtxCreate_v2 *cuCtxCreate;
tcuCtxDestroy_v2 *cuCtxDestroy;
tcuCtxPushCurrent_v2 *cuCtxPushCurrent;
tcuCtxPopCurrent_v2 *cuCtxPopCurrent;
tcuMemAllocPitch_v2 *cuMemAllocPitch;
tcuMemFree_v2 *cuMemFree;
tcuGraphicsD3D11RegisterResource *cuGraphicsD3D11RegisterResource;
tcuGraphicsUnregisterResource *cuGraphicsUnregisterResource;
tcuGraphicsMapResources *cuGraphicsMapResources;
tcuGraphicsUnmapResources *cuGraphicsUnmapResources;
tcuGraphicsSubResourceGetMappedArray *cuGraphicsSubResourceGetMappedArray;
tcuMemcpy2D_v2 *cuMemcpy2D;
HMODULE dll;
} cuda_functions = {};
CUresult last_cuda_error = CUDA_SUCCESS;
CUcontext cuda_context = nullptr;
CUgraphicsResource cuda_d3d_input_texture = nullptr;
CUdeviceptr cuda_surface = 0;
size_t cuda_surface_pitch = 0;
};
} // namespace nvenc
#endif
/**
* @file src/nvenc/nvenc_d3d11_on_cuda.h
* @brief Declarations for CUDA NVENC encoder with Direct3D11 input surfaces.
*/
#pragma once
#ifdef _WIN32
// lib includes
#include <ffnvcodec/dynlink_cuda.h>
// local includes
#include "nvenc_d3d11.h"
namespace nvenc {
/**
* @brief Interop Direct3D11 on CUDA NVENC encoder.
* Input surface is Direct3D11, encoding is performed by CUDA.
*/
class nvenc_d3d11_on_cuda final: public nvenc_d3d11 {
public:
/**
* @param d3d_device Direct3D11 device that will create input surface texture.
* CUDA encoding device will be derived from it.
*/
explicit nvenc_d3d11_on_cuda(ID3D11Device *d3d_device);
~nvenc_d3d11_on_cuda();
ID3D11Texture2D *get_input_texture() override;
private:
bool init_library() override;
bool create_and_register_input_buffer() override;
bool synchronize_input_buffer() override;
bool cuda_succeeded(CUresult result);
bool cuda_failed(CUresult result);
struct autopop_context {
autopop_context(nvenc_d3d11_on_cuda &parent, CUcontext pushed_context):
parent(parent),
pushed_context(pushed_context) {
}
~autopop_context();
explicit operator bool() const {
return pushed_context != nullptr;
}
nvenc_d3d11_on_cuda &parent;
CUcontext pushed_context = nullptr;
};
autopop_context push_context();
HMODULE dll = NULL;
const ID3D11DevicePtr d3d_device;
ID3D11Texture2DPtr d3d_input_texture;
struct {
tcuInit *cuInit;
tcuD3D11GetDevice *cuD3D11GetDevice;
tcuCtxCreate_v2 *cuCtxCreate;
tcuCtxDestroy_v2 *cuCtxDestroy;
tcuCtxPushCurrent_v2 *cuCtxPushCurrent;
tcuCtxPopCurrent_v2 *cuCtxPopCurrent;
tcuMemAllocPitch_v2 *cuMemAllocPitch;
tcuMemFree_v2 *cuMemFree;
tcuGraphicsD3D11RegisterResource *cuGraphicsD3D11RegisterResource;
tcuGraphicsUnregisterResource *cuGraphicsUnregisterResource;
tcuGraphicsMapResources *cuGraphicsMapResources;
tcuGraphicsUnmapResources *cuGraphicsUnmapResources;
tcuGraphicsSubResourceGetMappedArray *cuGraphicsSubResourceGetMappedArray;
tcuMemcpy2D_v2 *cuMemcpy2D;
HMODULE dll;
} cuda_functions = {};
CUresult last_cuda_error = CUDA_SUCCESS;
CUcontext cuda_context = nullptr;
CUgraphicsResource cuda_d3d_input_texture = nullptr;
CUdeviceptr cuda_surface = 0;
size_t cuda_surface_pitch = 0;
};
} // namespace nvenc
#endif

View File

@@ -1,22 +1,23 @@
/**
* @file src/nvenc/nvenc_encoded_frame.h
* @brief Declarations for NVENC encoded frame.
*/
#pragma once
#include <cstdint>
#include <vector>
namespace nvenc {
/**
* @brief Encoded frame.
*/
struct nvenc_encoded_frame {
std::vector<uint8_t> data;
uint64_t frame_index = 0;
bool idr = false;
bool after_ref_frame_invalidation = false;
};
} // namespace nvenc
/**
* @file src/nvenc/nvenc_encoded_frame.h
* @brief Declarations for NVENC encoded frame.
*/
#pragma once
// standard includes
#include <cstdint>
#include <vector>
namespace nvenc {
/**
* @brief Encoded frame.
*/
struct nvenc_encoded_frame {
std::vector<uint8_t> data;
uint64_t frame_index = 0;
bool idr = false;
bool after_ref_frame_invalidation = false;
};
} // namespace nvenc

View File

@@ -1,94 +1,93 @@
/**
* @file src/nvenc/nvenc_utils.cpp
* @brief Definitions for NVENC utilities.
*/
#include <cassert>
#include "nvenc_utils.h"
namespace nvenc {
#ifdef _WIN32
DXGI_FORMAT
dxgi_format_from_nvenc_format(NV_ENC_BUFFER_FORMAT format) {
switch (format) {
case NV_ENC_BUFFER_FORMAT_YUV420_10BIT:
return DXGI_FORMAT_P010;
case NV_ENC_BUFFER_FORMAT_NV12:
return DXGI_FORMAT_NV12;
case NV_ENC_BUFFER_FORMAT_AYUV:
return DXGI_FORMAT_AYUV;
case NV_ENC_BUFFER_FORMAT_YUV444_10BIT:
return DXGI_FORMAT_R16_UINT;
default:
return DXGI_FORMAT_UNKNOWN;
}
}
#endif
NV_ENC_BUFFER_FORMAT
nvenc_format_from_sunshine_format(platf::pix_fmt_e format) {
switch (format) {
case platf::pix_fmt_e::nv12:
return NV_ENC_BUFFER_FORMAT_NV12;
case platf::pix_fmt_e::p010:
return NV_ENC_BUFFER_FORMAT_YUV420_10BIT;
case platf::pix_fmt_e::ayuv:
return NV_ENC_BUFFER_FORMAT_AYUV;
case platf::pix_fmt_e::yuv444p16:
return NV_ENC_BUFFER_FORMAT_YUV444_10BIT;
default:
return NV_ENC_BUFFER_FORMAT_UNDEFINED;
}
}
nvenc_colorspace_t
nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace) {
nvenc_colorspace_t colorspace;
switch (sunshine_colorspace.colorspace) {
case video::colorspace_e::rec601:
// Rec. 601
colorspace.primaries = NV_ENC_VUI_COLOR_PRIMARIES_SMPTE170M;
colorspace.tranfer_function = NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE170M;
colorspace.matrix = NV_ENC_VUI_MATRIX_COEFFS_SMPTE170M;
break;
case video::colorspace_e::rec709:
// Rec. 709
colorspace.primaries = NV_ENC_VUI_COLOR_PRIMARIES_BT709;
colorspace.tranfer_function = NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT709;
colorspace.matrix = NV_ENC_VUI_MATRIX_COEFFS_BT709;
break;
case video::colorspace_e::bt2020sdr:
// Rec. 2020
colorspace.primaries = NV_ENC_VUI_COLOR_PRIMARIES_BT2020;
assert(sunshine_colorspace.bit_depth == 10);
colorspace.tranfer_function = NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT2020_10;
colorspace.matrix = NV_ENC_VUI_MATRIX_COEFFS_BT2020_NCL;
break;
case video::colorspace_e::bt2020:
// Rec. 2020 with ST 2084 perceptual quantizer
colorspace.primaries = NV_ENC_VUI_COLOR_PRIMARIES_BT2020;
assert(sunshine_colorspace.bit_depth == 10);
colorspace.tranfer_function = NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE2084;
colorspace.matrix = NV_ENC_VUI_MATRIX_COEFFS_BT2020_NCL;
break;
}
colorspace.full_range = sunshine_colorspace.full_range;
return colorspace;
}
} // namespace nvenc
/**
* @file src/nvenc/nvenc_utils.cpp
* @brief Definitions for NVENC utilities.
*/
// standard includes
#include <cassert>
// local includes
#include "nvenc_utils.h"
namespace nvenc {
#ifdef _WIN32
DXGI_FORMAT dxgi_format_from_nvenc_format(NV_ENC_BUFFER_FORMAT format) {
switch (format) {
case NV_ENC_BUFFER_FORMAT_YUV420_10BIT:
return DXGI_FORMAT_P010;
case NV_ENC_BUFFER_FORMAT_NV12:
return DXGI_FORMAT_NV12;
case NV_ENC_BUFFER_FORMAT_AYUV:
return DXGI_FORMAT_AYUV;
case NV_ENC_BUFFER_FORMAT_YUV444_10BIT:
return DXGI_FORMAT_R16_UINT;
default:
return DXGI_FORMAT_UNKNOWN;
}
}
#endif
NV_ENC_BUFFER_FORMAT nvenc_format_from_sunshine_format(platf::pix_fmt_e format) {
switch (format) {
case platf::pix_fmt_e::nv12:
return NV_ENC_BUFFER_FORMAT_NV12;
case platf::pix_fmt_e::p010:
return NV_ENC_BUFFER_FORMAT_YUV420_10BIT;
case platf::pix_fmt_e::ayuv:
return NV_ENC_BUFFER_FORMAT_AYUV;
case platf::pix_fmt_e::yuv444p16:
return NV_ENC_BUFFER_FORMAT_YUV444_10BIT;
default:
return NV_ENC_BUFFER_FORMAT_UNDEFINED;
}
}
nvenc_colorspace_t nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace) {
nvenc_colorspace_t colorspace;
switch (sunshine_colorspace.colorspace) {
case video::colorspace_e::rec601:
// Rec. 601
colorspace.primaries = NV_ENC_VUI_COLOR_PRIMARIES_SMPTE170M;
colorspace.tranfer_function = NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE170M;
colorspace.matrix = NV_ENC_VUI_MATRIX_COEFFS_SMPTE170M;
break;
case video::colorspace_e::rec709:
// Rec. 709
colorspace.primaries = NV_ENC_VUI_COLOR_PRIMARIES_BT709;
colorspace.tranfer_function = NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT709;
colorspace.matrix = NV_ENC_VUI_MATRIX_COEFFS_BT709;
break;
case video::colorspace_e::bt2020sdr:
// Rec. 2020
colorspace.primaries = NV_ENC_VUI_COLOR_PRIMARIES_BT2020;
assert(sunshine_colorspace.bit_depth == 10);
colorspace.tranfer_function = NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT2020_10;
colorspace.matrix = NV_ENC_VUI_MATRIX_COEFFS_BT2020_NCL;
break;
case video::colorspace_e::bt2020:
// Rec. 2020 with ST 2084 perceptual quantizer
colorspace.primaries = NV_ENC_VUI_COLOR_PRIMARIES_BT2020;
assert(sunshine_colorspace.bit_depth == 10);
colorspace.tranfer_function = NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE2084;
colorspace.matrix = NV_ENC_VUI_MATRIX_COEFFS_BT2020_NCL;
break;
}
colorspace.full_range = sunshine_colorspace.full_range;
return colorspace;
}
} // namespace nvenc

View File

@@ -1,31 +1,30 @@
/**
* @file src/nvenc/nvenc_utils.h
* @brief Declarations for NVENC utilities.
*/
#pragma once
#ifdef _WIN32
#include <dxgiformat.h>
#endif
#include "nvenc_colorspace.h"
#include "src/platform/common.h"
#include "src/video_colorspace.h"
#include <ffnvcodec/nvEncodeAPI.h>
namespace nvenc {
#ifdef _WIN32
DXGI_FORMAT
dxgi_format_from_nvenc_format(NV_ENC_BUFFER_FORMAT format);
#endif
NV_ENC_BUFFER_FORMAT
nvenc_format_from_sunshine_format(platf::pix_fmt_e format);
nvenc_colorspace_t
nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace);
} // namespace nvenc
/**
* @file src/nvenc/nvenc_utils.h
* @brief Declarations for NVENC utilities.
*/
#pragma once
// plafform includes
#ifdef _WIN32
#include <dxgiformat.h>
#endif
// lib includes
#include <ffnvcodec/nvEncodeAPI.h>
// local includes
#include "nvenc_colorspace.h"
#include "src/platform/common.h"
#include "src/video_colorspace.h"
namespace nvenc {
#ifdef _WIN32
DXGI_FORMAT dxgi_format_from_nvenc_format(NV_ENC_BUFFER_FORMAT format);
#endif
NV_ENC_BUFFER_FORMAT nvenc_format_from_sunshine_format(platf::pix_fmt_e format);
nvenc_colorspace_t nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace);
} // namespace nvenc

View File

@@ -7,16 +7,16 @@
// standard includes
#include <filesystem>
#include <string>
#include <utility>
// lib includes
#include <Simple-Web-Server/server_http.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/ssl/context_base.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <string>
#include <Simple-Web-Server/server_http.hpp>
// local includes
#include "config.h"
@@ -36,6 +36,7 @@
#include "video.h"
using namespace std::literals;
namespace nvhttp {
namespace fs = std::filesystem;
@@ -61,8 +62,7 @@ namespace nvhttp {
protected:
boost::asio::ssl::context context;
void
after_bind() override {
void after_bind() override {
if (verify) {
context.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once);
context.set_verify_callback([](int verified, boost::asio::ssl::verify_context &ctx) {
@@ -73,17 +73,18 @@ namespace nvhttp {
}
// This is Server<HTTPS>::accept() with SSL validation support added
void
accept() override {
void accept() override {
auto connection = create_connection(*io_service, context);
acceptor->async_accept(connection->socket->lowest_layer(), [this, connection](const SimpleWeb::error_code &ec) {
auto lock = connection->handler_runner->continue_lock();
if (!lock)
if (!lock) {
return;
}
if (ec != SimpleWeb::error::operation_aborted)
if (ec != SimpleWeb::error::operation_aborted) {
this->accept();
}
auto session = std::make_shared<Session>(config.max_request_streambuf_size, connection);
@@ -96,20 +97,22 @@ namespace nvhttp {
session->connection->socket->async_handshake(boost::asio::ssl::stream_base::server, [this, session](const SimpleWeb::error_code &ec) {
session->connection->cancel_timeout();
auto lock = session->connection->handler_runner->continue_lock();
if (!lock)
if (!lock) {
return;
if (!ec) {
if (verify && !verify(session->connection->socket->native_handle()))
this->write(session, on_verify_failed);
else
this->read(session);
}
else if (this->on_error)
if (!ec) {
if (verify && !verify(session->connection->socket->native_handle())) {
this->write(session, on_verify_failed);
} else {
this->read(session);
}
} else if (this->on_error) {
this->on_error(session->request, ec);
}
});
}
else if (this->on_error)
} else if (this->on_error) {
this->on_error(session->request, ec);
}
});
}
};
@@ -148,8 +151,7 @@ namespace nvhttp {
REMOVE ///< Remove certificate
};
std::string
get_arg(const args_t &args, const char *name, const char *default_value = nullptr) {
std::string get_arg(const args_t &args, const char *name, const char *default_value = nullptr) {
auto it = args.find(name);
if (it == std::end(args)) {
if (default_value != NULL) {
@@ -161,15 +163,13 @@ namespace nvhttp {
return it->second;
}
void
save_state() {
void save_state() {
pt::ptree root;
if (fs::exists(config::nvhttp.file_state)) {
try {
pt::read_json(config::nvhttp.file_state, root);
}
catch (std::exception &e) {
} catch (std::exception &e) {
BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what();
return;
}
@@ -193,15 +193,13 @@ namespace nvhttp {
try {
pt::write_json(config::nvhttp.file_state, root);
}
catch (std::exception &e) {
} catch (std::exception &e) {
BOOST_LOG(error) << "Couldn't write "sv << config::nvhttp.file_state << ": "sv << e.what();
return;
}
}
void
load_state() {
void load_state() {
if (!fs::exists(config::nvhttp.file_state)) {
BOOST_LOG(info) << "File "sv << config::nvhttp.file_state << " doesn't exist"sv;
http::unique_id = uuid_util::uuid_t::generate().string();
@@ -211,8 +209,7 @@ namespace nvhttp {
pt::ptree tree;
try {
pt::read_json(config::nvhttp.file_state, tree);
}
catch (std::exception &e) {
} catch (std::exception &e) {
BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what();
return;
@@ -266,8 +263,7 @@ namespace nvhttp {
client_root = client;
}
void
add_authorized_client(const std::string &name, std::string &&cert) {
void add_authorized_client(const std::string &name, std::string &&cert) {
client_t &client = client_root;
named_cert_t named_cert;
named_cert.name = name;
@@ -280,8 +276,7 @@ namespace nvhttp {
}
}
std::shared_ptr<rtsp_stream::launch_session_t>
make_launch_session(bool host_audio, const args_t &args) {
std::shared_ptr<rtsp_stream::launch_session_t> make_launch_session(bool host_audio, const args_t &args) {
auto launch_session = std::make_shared<rtsp_stream::launch_session_t>();
launch_session->id = ++session_id_counter;
@@ -295,9 +290,15 @@ namespace nvhttp {
int x = 0;
std::string segment;
while (std::getline(mode, segment, 'x')) {
if (x == 0) launch_session->width = atoi(segment.c_str());
if (x == 1) launch_session->height = atoi(segment.c_str());
if (x == 2) launch_session->fps = atoi(segment.c_str());
if (x == 0) {
launch_session->width = atoi(segment.c_str());
}
if (x == 1) {
launch_session->height = atoi(segment.c_str());
}
if (x == 2) {
launch_session->fps = atoi(segment.c_str());
}
x++;
}
launch_session->unique_id = (get_arg(args, "uniqueid", "unknown"));
@@ -312,7 +313,8 @@ namespace nvhttp {
auto corever = util::from_view(get_arg(args, "corever", "0"));
if (corever >= 1) {
launch_session->rtsp_cipher = crypto::cipher::gcm_t {
launch_session->gcm_key, false
launch_session->gcm_key,
false
};
launch_session->rtsp_iv_counter = 0;
}
@@ -331,21 +333,18 @@ namespace nvhttp {
return launch_session;
}
void
remove_session(const pair_session_t &sess) {
void remove_session(const pair_session_t &sess) {
map_id_sess.erase(sess.client.uniqueID);
}
void
fail_pair(pair_session_t &sess, pt::ptree &tree, const std::string status_msg) {
void fail_pair(pair_session_t &sess, pt::ptree &tree, const std::string status_msg) {
tree.put("root.paired", 0);
tree.put("root.<xmlattr>.status_code", 400);
tree.put("root.<xmlattr>.status_message", status_msg);
remove_session(sess); // Security measure, delete the session when something went wrong and force a re-pair
}
void
getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) {
void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) {
if (sess.last_phase != PAIR_PHASE::NONE) {
fail_pair(sess, tree, "Out of order call to getservercert");
return;
@@ -357,7 +356,7 @@ namespace nvhttp {
return;
}
std::string_view salt_view { sess.async_insert_pin.salt.data(), 32 };
std::string_view salt_view {sess.async_insert_pin.salt.data(), 32};
auto salt = util::from_hex<std::array<uint8_t, 16>>(salt_view, true);
@@ -369,8 +368,7 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200);
}
void
clientchallenge(pair_session_t &sess, pt::ptree &tree, const std::string &challenge) {
void clientchallenge(pair_session_t &sess, pt::ptree &tree, const std::string &challenge) {
if (sess.last_phase != PAIR_PHASE::GETSERVERCERT) {
fail_pair(sess, tree, "Out of order call to clientchallenge");
return;
@@ -393,7 +391,7 @@ namespace nvhttp {
decrypted.insert(std::end(decrypted), std::begin(sign), std::end(sign));
decrypted.insert(std::end(decrypted), std::begin(serversecret), std::end(serversecret));
auto hash = crypto::hash({ (char *) decrypted.data(), decrypted.size() });
auto hash = crypto::hash({(char *) decrypted.data(), decrypted.size()});
auto serverchallenge = crypto::rand(16);
std::string plaintext;
@@ -413,8 +411,7 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200);
}
void
serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const std::string &encrypted_response) {
void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const std::string &encrypted_response) {
if (sess.last_phase != PAIR_PHASE::CLIENTCHALLENGE) {
fail_pair(sess, tree, "Out of order call to serverchallengeresp");
return;
@@ -443,8 +440,7 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200);
}
void
clientpairingsecret(pair_session_t &sess, std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, pt::ptree &tree, const std::string &client_pairing_secret) {
void clientpairingsecret(pair_session_t &sess, std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, pt::ptree &tree, const std::string &client_pairing_secret) {
if (sess.last_phase != PAIR_PHASE::SERVERCHALLENGERESP) {
fail_pair(sess, tree, "Out of order call to clientpairingsecret");
return;
@@ -458,8 +454,8 @@ namespace nvhttp {
return;
}
std::string_view secret { client_pairing_secret.data(), 16 };
std::string_view sign { client_pairing_secret.data() + secret.size(), client_pairing_secret.size() - secret.size() };
std::string_view secret {client_pairing_secret.data(), 16};
std::string_view sign {client_pairing_secret.data() + secret.size(), client_pairing_secret.size() - secret.size()};
auto x509 = crypto::x509(client.cert);
if (!x509) {
@@ -486,8 +482,7 @@ namespace nvhttp {
// The client is now successfully paired and will be authorized to connect
add_authorized_client(client.name, std::move(client.cert));
}
else {
} else {
tree.put("root.paired", 0);
}
@@ -495,22 +490,21 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200);
}
template <class T>
template<class T>
struct tunnel;
template <>
template<>
struct tunnel<SunshineHTTPS> {
static auto constexpr to_string = "HTTPS"sv;
};
template <>
template<>
struct tunnel<SimpleWeb::HTTP> {
static auto constexpr to_string = "NONE"sv;
};
template <class T>
void
print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
template<class T>
void print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
BOOST_LOG(debug) << "TUNNEL :: "sv << tunnel<T>::to_string;
BOOST_LOG(debug) << "METHOD :: "sv << request->method;
@@ -529,9 +523,8 @@ namespace nvhttp {
BOOST_LOG(debug) << " [--] "sv;
}
template <class T>
void
not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
template<class T>
void not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request);
pt::ptree tree;
@@ -549,9 +542,8 @@ namespace nvhttp {
response->close_connection_after_response = true;
}
template <class T>
void
pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
template<class T>
void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request);
pt::ptree tree;
@@ -572,7 +564,7 @@ namespace nvhttp {
return;
}
auto uniqID { get_arg(args, "uniqueid") };
auto uniqID {get_arg(args, "uniqueid")};
args_t::const_iterator it;
if (it = args.find("phrase"); it != std::end(args)) {
@@ -593,8 +585,7 @@ namespace nvhttp {
std::getline(std::cin, pin);
getservercert(ptr->second, tree, pin);
}
else {
} else {
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
system_tray::update_tray_require_pin();
#endif
@@ -603,8 +594,7 @@ namespace nvhttp {
fg.disable();
return;
}
}
else if (it->second == "pairchallenge"sv) {
} else if (it->second == "pairchallenge"sv) {
tree.put("root.paired", 1);
tree.put("root.<xmlattr>.status_code", 200);
return;
@@ -622,23 +612,19 @@ namespace nvhttp {
if (it = args.find("clientchallenge"); it != std::end(args)) {
auto challenge = util::from_hex_vec(it->second, true);
clientchallenge(sess_it->second, tree, challenge);
}
else if (it = args.find("serverchallengeresp"); it != std::end(args)) {
} else if (it = args.find("serverchallengeresp"); it != std::end(args)) {
auto encrypted_response = util::from_hex_vec(it->second, true);
serverchallengeresp(sess_it->second, tree, encrypted_response);
}
else if (it = args.find("clientpairingsecret"); it != std::end(args)) {
} else if (it = args.find("clientpairingsecret"); it != std::end(args)) {
auto pairingsecret = util::from_hex_vec(it->second, true);
clientpairingsecret(sess_it->second, add_cert, tree, pairingsecret);
}
else {
} else {
tree.put("root.<xmlattr>.status_code", 404);
tree.put("root.<xmlattr>.status_message", "Invalid pairing request");
}
}
bool
pin(std::string pin, std::string name) {
bool pin(std::string pin, std::string name) {
pt::ptree tree;
if (map_id_sess.empty()) {
return false;
@@ -649,7 +635,9 @@ namespace nvhttp {
tree.put("root.paired", 0);
tree.put("root.<xmlattr>.status_code", 400);
tree.put(
"root.<xmlattr>.status_message", "Pin must be 4 digits, " + std::to_string(pin.size()) + " provided");
"root.<xmlattr>.status_message",
"Pin must be 4 digits, " + std::to_string(pin.size()) + " provided"
);
return false;
}
@@ -672,11 +660,9 @@ namespace nvhttp {
auto &async_response = sess.async_insert_pin.response;
if (async_response.has_left() && async_response.left()) {
async_response.left()->write(data.str());
}
else if (async_response.has_right() && async_response.right()) {
} else if (async_response.has_right() && async_response.right()) {
async_response.right()->write(data.str());
}
else {
} else {
return false;
}
@@ -686,9 +672,8 @@ namespace nvhttp {
return true;
}
template <class T>
void
serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
template<class T>
void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request);
int pair_status = 0;
@@ -719,8 +704,7 @@ namespace nvhttp {
// For HTTP requests, use a placeholder MAC address that Moonlight knows to ignore.
if constexpr (std::is_same_v<SunshineHTTPS, T>) {
tree.put("root.mac", platf::get_mac_address(net::addr_to_normalized_string(local_endpoint.address())));
}
else {
} else {
tree.put("root.mac", "00:00:00:00:00:00");
}
@@ -735,8 +719,7 @@ namespace nvhttp {
// support know to ignore this bogus address.
if (local_endpoint.address().is_v6() && !local_endpoint.address().to_v6().is_v4_mapped()) {
tree.put("root.LocalIP", "127.0.0.1");
}
else {
} else {
tree.put("root.LocalIP", net::addr_to_normalized_string(local_endpoint.address()));
}
@@ -782,22 +765,20 @@ namespace nvhttp {
response->close_connection_after_response = true;
}
pt::ptree
get_all_clients() {
pt::ptree named_cert_nodes;
nlohmann::json get_all_clients() {
nlohmann::json named_cert_nodes = nlohmann::json::array();
client_t &client = client_root;
for (auto &named_cert : client.named_devices) {
pt::ptree named_cert_node;
named_cert_node.put("name"s, named_cert.name);
named_cert_node.put("uuid"s, named_cert.uuid);
named_cert_nodes.push_back(std::make_pair(""s, named_cert_node));
nlohmann::json named_cert_node;
named_cert_node["name"] = named_cert.name;
named_cert_node["uuid"] = named_cert.uuid;
named_cert_nodes.push_back(named_cert_node);
}
return named_cert_nodes;
}
void
applist(resp_https_t response, req_https_t request) {
void applist(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);
pt::ptree tree;
@@ -825,12 +806,11 @@ namespace nvhttp {
}
}
void
launch(bool &host_audio, resp_https_t response, req_https_t request) {
void launch(bool &host_audio, resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);
pt::ptree tree;
bool revert_display_configuration { false };
bool revert_display_configuration {false};
auto g = util::fail_guard([&]() {
std::ostringstream data;
@@ -848,7 +828,8 @@ namespace nvhttp {
args.find("rikey"s) == std::end(args) ||
args.find("rikeyid"s) == std::end(args) ||
args.find("localAudioPlayMode"s) == std::end(args) ||
args.find("appid"s) == std::end(args)) {
args.find("appid"s) == std::end(args)
) {
tree.put("root.resume", 0);
tree.put("root.<xmlattr>.status_code", 400);
tree.put("root.<xmlattr>.status_message", "Missing a required launch parameter");
@@ -871,14 +852,14 @@ namespace nvhttp {
auto launch_session = make_launch_session(host_audio, args);
if (rtsp_stream::session_count() == 0) {
// The display should be restored in case something fails as there are no other sessions.
revert_display_configuration = true;
// We want to prepare display only if there are no active sessions at
// the moment. This should be done before probing encoders as it could
// change the active displays.
display_device::configure_display(config::video, *launch_session);
// The display should be restored in case something fails as there are no other sessions.
revert_display_configuration = true;
// Probe encoders again before streaming to ensure our chosen
// encoder matches the active GPU (which could have changed
// due to hotplugging, driver crash, primary monitor change,
@@ -915,9 +896,7 @@ namespace nvhttp {
}
tree.put("root.<xmlattr>.status_code", 200);
tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme +
net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' +
std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
tree.put("root.gamesession", 1);
rtsp_stream::launch_session_raise(launch_session);
@@ -926,8 +905,7 @@ namespace nvhttp {
revert_display_configuration = false;
}
void
resume(bool &host_audio, resp_https_t response, req_https_t request) {
void resume(bool &host_audio, resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);
pt::ptree tree;
@@ -951,7 +929,8 @@ namespace nvhttp {
auto args = request->parse_query_string();
if (
args.find("rikey"s) == std::end(args) ||
args.find("rikeyid"s) == std::end(args)) {
args.find("rikeyid"s) == std::end(args)
) {
tree.put("root.resume", 0);
tree.put("root.<xmlattr>.status_code", 400);
tree.put("root.<xmlattr>.status_message", "Missing a required resume parameter");
@@ -962,7 +941,7 @@ namespace nvhttp {
// Newer Moonlight clients send localAudioPlayMode on /resume too,
// so we should use it if it's present in the args and there are
// no active sessions we could be interfering with.
const bool no_active_sessions { rtsp_stream::session_count() == 0 };
const bool no_active_sessions {rtsp_stream::session_count() == 0};
if (no_active_sessions && args.find("localAudioPlayMode"s) != std::end(args)) {
host_audio = util::from_view(get_arg(args, "localAudioPlayMode"));
}
@@ -999,16 +978,13 @@ namespace nvhttp {
}
tree.put("root.<xmlattr>.status_code", 200);
tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme +
net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' +
std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
tree.put("root.resume", 1);
rtsp_stream::launch_session_raise(launch_session);
}
void
cancel(resp_https_t response, req_https_t request) {
void cancel(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);
pt::ptree tree;
@@ -1033,8 +1009,7 @@ namespace nvhttp {
display_device::revert_configuration();
}
void
appasset(resp_https_t response, req_https_t request) {
void appasset(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);
auto args = request->parse_query_string();
@@ -1047,14 +1022,12 @@ namespace nvhttp {
response->close_connection_after_response = true;
}
void
setup(const std::string &pkey, const std::string &cert) {
void setup(const std::string &pkey, const std::string &cert) {
conf_intern.pkey = pkey;
conf_intern.servercert = cert;
}
void
start() {
void start() {
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
auto port_http = net::map_port(PORT_HTTP);
@@ -1077,7 +1050,7 @@ namespace nvhttp {
// launch will store it in host_audio
bool host_audio {};
https_server_t https_server { config::nvhttp.cert, config::nvhttp.pkey };
https_server_t https_server {config::nvhttp.cert, config::nvhttp.pkey};
http_server_t http_server;
// Verify certificates after establishing connection
@@ -1143,11 +1116,17 @@ namespace nvhttp {
https_server.default_resource["GET"] = not_found<SunshineHTTPS>;
https_server.resource["^/serverinfo$"]["GET"] = serverinfo<SunshineHTTPS>;
https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair<SunshineHTTPS>(add_cert, resp, req); };
https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) {
pair<SunshineHTTPS>(add_cert, resp, req);
};
https_server.resource["^/applist$"]["GET"] = applist;
https_server.resource["^/appasset$"]["GET"] = appasset;
https_server.resource["^/launch$"]["GET"] = [&host_audio](auto resp, auto req) { launch(host_audio, resp, req); };
https_server.resource["^/resume$"]["GET"] = [&host_audio](auto resp, auto req) { resume(host_audio, resp, req); };
https_server.resource["^/launch$"]["GET"] = [&host_audio](auto resp, auto req) {
launch(host_audio, resp, req);
};
https_server.resource["^/resume$"]["GET"] = [&host_audio](auto resp, auto req) {
resume(host_audio, resp, req);
};
https_server.resource["^/cancel$"]["GET"] = cancel;
https_server.config.reuse_address = true;
@@ -1156,7 +1135,9 @@ namespace nvhttp {
http_server.default_resource["GET"] = not_found<SimpleWeb::HTTP>;
http_server.resource["^/serverinfo$"]["GET"] = serverinfo<SimpleWeb::HTTP>;
http_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair<SimpleWeb::HTTP>(add_cert, resp, req); };
http_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) {
pair<SimpleWeb::HTTP>(add_cert, resp, req);
};
http_server.config.reuse_address = true;
http_server.config.address = net::af_to_any_address_string(address_family);
@@ -1165,8 +1146,7 @@ namespace nvhttp {
auto accept_and_run = [&](auto *http_server) {
try {
http_server->start();
}
catch (boost::system::system_error &err) {
} catch (boost::system::system_error &err) {
// It's possible the exception gets thrown after calling http_server->stop() from a different thread
if (shutdown_event->peek()) {
return;
@@ -1177,8 +1157,8 @@ namespace nvhttp {
return;
}
};
std::thread ssl { accept_and_run, &https_server };
std::thread tcp { accept_and_run, &http_server };
std::thread ssl {accept_and_run, &https_server};
std::thread tcp {accept_and_run, &http_server};
// Wait for any event
shutdown_event->view();
@@ -1190,24 +1170,21 @@ namespace nvhttp {
tcp.join();
}
void
erase_all_clients() {
void erase_all_clients() {
client_t client;
client_root = client;
cert_chain.clear();
save_state();
}
int
unpair_client(std::string uuid) {
int removed = 0;
bool unpair_client(const std::string_view uuid) {
bool removed = false;
client_t &client = client_root;
for (auto it = client.named_devices.begin(); it != client.named_devices.end();) {
if ((*it).uuid == uuid) {
it = client.named_devices.erase(it);
removed++;
}
else {
removed = true;
} else {
++it;
}
}

View File

@@ -9,8 +9,9 @@
#include <string>
// lib includes
#include <Simple-Web-Server/server_https.hpp>
#include <boost/property_tree/ptree.hpp>
#include <nlohmann/json.hpp>
#include <Simple-Web-Server/server_https.hpp>
// local includes
#include "crypto.h"
@@ -49,21 +50,20 @@ namespace nvhttp {
* nvhttp::start();
* @examples_end
*/
void
start();
void start();
/**
* @brief Setup the nvhttp server.
* @param pkey
* @param cert
*/
void
setup(const std::string &pkey, const std::string &cert);
void setup(const std::string &pkey, const std::string &cert);
class SunshineHTTPS: public SimpleWeb::HTTPS {
public:
SunshineHTTPS(boost::asio::io_context &io_context, boost::asio::ssl::context &ctx):
SimpleWeb::HTTPS(io_context, ctx) {}
SimpleWeb::HTTPS(io_context, ctx) {
}
virtual ~SunshineHTTPS() {
// Gracefully shutdown the TLS connection
@@ -111,8 +111,7 @@ namespace nvhttp {
* @brief removes the temporary pairing session
* @param sess
*/
void
remove_session(const pair_session_t &sess);
void remove_session(const pair_session_t &sess);
/**
* @brief Pair, phase 1
@@ -124,8 +123,7 @@ namespace nvhttp {
*
* At this stage we only have to send back our public certificate.
*/
void
getservercert(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &pin);
void getservercert(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &pin);
/**
* @brief Pair, phase 2
@@ -139,8 +137,7 @@ namespace nvhttp {
*
* The hash + server_challenge will then be AES encrypted and sent as the `challengeresponse` in the returned XML
*/
void
clientchallenge(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &challenge);
void clientchallenge(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &challenge);
/**
* @brief Pair, phase 3
@@ -149,8 +146,7 @@ namespace nvhttp {
* we have to send back the `pairingsecret`:
* using our private key we have to sign the certificate_signature + server_secret (generated in phase 2)
*/
void
serverchallengeresp(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &encrypted_response);
void serverchallengeresp(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &encrypted_response);
/**
* @brief Pair, phase 4 (final)
@@ -166,8 +162,7 @@ namespace nvhttp {
* Then using the client certificate public key we should be able to verify that
* the client secret has been signed by Moonlight
*/
void
clientpairingsecret(pair_session_t &sess, std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, boost::property_tree::ptree &tree, const std::string &client_pairing_secret);
void clientpairingsecret(pair_session_t &sess, std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, boost::property_tree::ptree &tree, const std::string &client_pairing_secret);
/**
* @brief Compare the user supplied pin to the Moonlight pin.
@@ -178,27 +173,25 @@ namespace nvhttp {
* bool pin_status = nvhttp::pin("1234", "laptop");
* @examples_end
*/
bool
pin(std::string pin, std::string name);
bool pin(std::string pin, std::string name);
/**
* @brief Remove single client.
* @param uuid The UUID of the client to remove.
* @examples
* nvhttp::unpair_client("4D7BB2DD-5704-A405-B41C-891A022932E1");
* @examples_end
*/
int
unpair_client(std::string uniqueid);
bool unpair_client(std::string_view uuid);
/**
* @brief Get all paired clients.
* @return The list of all paired clients.
* @examples
* boost::property_tree::ptree clients = nvhttp::get_all_clients();
* nlohmann::json clients = nvhttp::get_all_clients();
* @examples_end
*/
boost::property_tree::ptree
get_all_clients();
nlohmann::json get_all_clients();
/**
* @brief Remove all paired clients.
@@ -206,6 +199,5 @@ namespace nvhttp {
* nvhttp::erase_all_clients();
* @examples_end
*/
void
erase_all_clients();
void erase_all_clients();
} // namespace nvhttp

View File

@@ -4,18 +4,21 @@
*/
#pragma once
// standard includes
#include <bitset>
#include <filesystem>
#include <functional>
#include <mutex>
#include <string>
// lib includes
#include <boost/core/noncopyable.hpp>
#ifndef _WIN32
#include <boost/asio.hpp>
#include <boost/process.hpp>
#endif
// local includes
#include "src/config.h"
#include "src/logging.h"
#include "src/thread_safe.h"
@@ -44,13 +47,15 @@ namespace boost {
class address;
} // namespace ip
} // namespace asio
namespace filesystem {
class path;
}
namespace process::inline v1 {
class child;
class group;
template <typename Char>
template<typename Char>
class basic_environment;
typedef basic_environment<char> environment;
} // namespace process::inline v1
@@ -59,6 +64,7 @@ namespace boost {
namespace video {
struct config_t;
} // namespace video
namespace nvenc {
class nvenc_base;
}
@@ -103,26 +109,23 @@ namespace platf {
};
struct gamepad_feedback_msg_t {
static gamepad_feedback_msg_t
make_rumble(std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq) {
static gamepad_feedback_msg_t make_rumble(std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq) {
gamepad_feedback_msg_t msg;
msg.type = gamepad_feedback_e::rumble;
msg.id = id;
msg.data.rumble = { lowfreq, highfreq };
msg.data.rumble = {lowfreq, highfreq};
return msg;
}
static gamepad_feedback_msg_t
make_rumble_triggers(std::uint16_t id, std::uint16_t left, std::uint16_t right) {
static gamepad_feedback_msg_t make_rumble_triggers(std::uint16_t id, std::uint16_t left, std::uint16_t right) {
gamepad_feedback_msg_t msg;
msg.type = gamepad_feedback_e::rumble_triggers;
msg.id = id;
msg.data.rumble_triggers = { left, right };
msg.data.rumble_triggers = {left, right};
return msg;
}
static gamepad_feedback_msg_t
make_motion_event_state(std::uint16_t id, std::uint8_t motion_type, std::uint16_t report_rate) {
static gamepad_feedback_msg_t make_motion_event_state(std::uint16_t id, std::uint8_t motion_type, std::uint16_t report_rate) {
gamepad_feedback_msg_t msg;
msg.type = gamepad_feedback_e::set_motion_event_state;
msg.id = id;
@@ -131,30 +134,33 @@ namespace platf {
return msg;
}
static gamepad_feedback_msg_t
make_rgb_led(std::uint16_t id, std::uint8_t r, std::uint8_t g, std::uint8_t b) {
static gamepad_feedback_msg_t make_rgb_led(std::uint16_t id, std::uint8_t r, std::uint8_t g, std::uint8_t b) {
gamepad_feedback_msg_t msg;
msg.type = gamepad_feedback_e::set_rgb_led;
msg.id = id;
msg.data.rgb_led = { r, g, b };
msg.data.rgb_led = {r, g, b};
return msg;
}
gamepad_feedback_e type;
std::uint16_t id;
union {
struct {
std::uint16_t lowfreq;
std::uint16_t highfreq;
} rumble;
struct {
std::uint16_t left_trigger;
std::uint16_t right_trigger;
} rumble_triggers;
struct {
std::uint16_t report_rate;
std::uint8_t motion_type;
} motion_event_state;
struct {
std::uint8_t r;
std::uint8_t g;
@@ -179,7 +185,8 @@ namespace platf {
};
constexpr std::uint8_t map_stereo[] {
FRONT_LEFT, FRONT_RIGHT
FRONT_LEFT,
FRONT_RIGHT
};
constexpr std::uint8_t map_surround51[] {
FRONT_LEFT,
@@ -221,10 +228,9 @@ namespace platf {
unknown ///< Unknown
};
inline std::string_view
from_pix_fmt(pix_fmt_e pix_fmt) {
inline std::string_view from_pix_fmt(pix_fmt_e pix_fmt) {
using namespace std::literals;
#define _CONVERT(x) \
#define _CONVERT(x) \
case pix_fmt_e::x: \
return #x##sv
switch (pix_fmt) {
@@ -344,10 +350,8 @@ namespace platf {
img_t(img_t &&) = delete;
img_t(const img_t &) = delete;
img_t &
operator=(img_t &&) = delete;
img_t &
operator=(const img_t &) = delete;
img_t &operator=(img_t &&) = delete;
img_t &operator=(const img_t &) = delete;
std::uint8_t *data {};
std::int32_t width {};
@@ -371,14 +375,14 @@ namespace platf {
std::string surround51;
std::string surround71;
};
std::optional<null_t> null;
};
struct encode_device_t {
virtual ~encode_device_t() = default;
virtual int
convert(platf::img_t &img) = 0;
virtual int convert(platf::img_t &img) = 0;
video::sunshine_colorspace_t colorspace;
};
@@ -387,21 +391,18 @@ namespace platf {
void *data {};
AVFrame *frame {};
int
convert(platf::img_t &img) override {
int convert(platf::img_t &img) override {
return -1;
}
virtual void
apply_colorspace() {
virtual void apply_colorspace() {
}
/**
* @brief Set the frame to be encoded.
* @note Implementations must take ownership of 'frame'.
*/
virtual int
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
virtual int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
BOOST_LOG(error) << "Illegal call to hwdevice_t::set_frame(). Did you forget to override it?";
return -1;
};
@@ -410,29 +411,25 @@ namespace platf {
* @brief Initialize the hwframes context.
* @note Implementations may set parameters during initialization of the hwframes context.
*/
virtual void
init_hwframes(AVHWFramesContext *frames) {};
virtual void init_hwframes(AVHWFramesContext *frames) {};
/**
* @brief Provides a hook for allow platform-specific code to adjust codec options.
* @note Implementations may set or modify codec options prior to codec initialization.
*/
virtual void
init_codec_options(AVCodecContext *ctx, AVDictionary **options) {};
virtual void init_codec_options(AVCodecContext *ctx, AVDictionary **options) {};
/**
* @brief Prepare to derive a context.
* @note Implementations may make modifications required before context derivation
*/
virtual int
prepare_to_derive_context(int hw_device_type) {
virtual int prepare_to_derive_context(int hw_device_type) {
return 0;
};
};
struct nvenc_encode_device_t: encode_device_t {
virtual bool
init_encoder(const video::config_t &client_config, const video::sunshine_colorspace_t &colorspace) = 0;
virtual bool init_encoder(const video::config_t &client_config, const video::sunshine_colorspace_t &colorspace) = 0;
nvenc::nvenc_base *nvenc = nullptr;
};
@@ -466,7 +463,9 @@ namespace platf {
using pull_free_image_cb_t = std::function<bool(std::shared_ptr<img_t> &img_out)>;
display_t() noexcept:
offset_x { 0 }, offset_y { 0 } {}
offset_x {0},
offset_y {0} {
}
/**
* @brief Capture a frame.
@@ -480,32 +479,25 @@ namespace platf {
* @retval capture_e::error On error
* @retval capture_e::reinit When need of reinitialization
*/
virtual capture_e
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) = 0;
virtual capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) = 0;
virtual std::shared_ptr<img_t>
alloc_img() = 0;
virtual std::shared_ptr<img_t> alloc_img() = 0;
virtual int
dummy_img(img_t *img) = 0;
virtual int dummy_img(img_t *img) = 0;
virtual std::unique_ptr<avcodec_encode_device_t>
make_avcodec_encode_device(pix_fmt_e pix_fmt) {
virtual std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) {
return nullptr;
}
virtual std::unique_ptr<nvenc_encode_device_t>
make_nvenc_encode_device(pix_fmt_e pix_fmt) {
virtual std::unique_ptr<nvenc_encode_device_t> make_nvenc_encode_device(pix_fmt_e pix_fmt) {
return nullptr;
}
virtual bool
is_hdr() {
virtual bool is_hdr() {
return false;
}
virtual bool
get_hdr_metadata(SS_HDR_METADATA &metadata) {
virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) {
std::memset(&metadata, 0, sizeof(metadata));
return false;
}
@@ -516,8 +508,7 @@ namespace platf {
* @param config The codec configuration.
* @return `true` if supported, `false` otherwise.
*/
virtual bool
is_codec_supported(std::string_view name, const ::video::config_t &config) {
virtual bool is_codec_supported(std::string_view name, const ::video::config_t &config) {
return true;
}
@@ -531,57 +522,46 @@ namespace platf {
protected:
// collect capture timing data (at loglevel debug)
logging::time_delta_periodic_logger sleep_overshoot_logger = { debug, "Frame capture sleep overshoot" };
logging::time_delta_periodic_logger sleep_overshoot_logger = {debug, "Frame capture sleep overshoot"};
};
class mic_t {
public:
virtual capture_e
sample(std::vector<float> &frame_buffer) = 0;
virtual capture_e sample(std::vector<float> &frame_buffer) = 0;
virtual ~mic_t() = default;
};
class audio_control_t {
public:
virtual int
set_sink(const std::string &sink) = 0;
virtual int set_sink(const std::string &sink) = 0;
virtual std::unique_ptr<mic_t>
microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) = 0;
virtual std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) = 0;
/**
* @brief Check if the audio sink is available in the system.
* @param sink Sink to be checked.
* @returns True if available, false otherwise.
*/
virtual bool
is_sink_available(const std::string &sink) = 0;
virtual bool is_sink_available(const std::string &sink) = 0;
virtual std::optional<sink_t>
sink_info() = 0;
virtual std::optional<sink_t> sink_info() = 0;
virtual ~audio_control_t() = default;
};
void
freeInput(void *);
void freeInput(void *);
using input_t = util::safe_ptr<void, freeInput>;
std::filesystem::path
appdata();
std::filesystem::path appdata();
std::string
get_mac_address(const std::string_view &address);
std::string get_mac_address(const std::string_view &address);
std::string
from_sockaddr(const sockaddr *const);
std::pair<std::uint16_t, std::string>
from_sockaddr_ex(const sockaddr *const);
std::string from_sockaddr(const sockaddr *const);
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const);
std::unique_ptr<audio_control_t>
audio_control();
std::unique_ptr<audio_control_t> audio_control();
/**
* @brief Get the display_t instance for the given hwdevice_type.
@@ -591,22 +571,18 @@ namespace platf {
* @param config Stream configuration
* @return The display_t instance based on hwdevice_type.
*/
std::shared_ptr<display_t>
display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
// A list of names of displays accepted as display_name with the mem_type_e
std::vector<std::string>
display_names(mem_type_e hwdevice_type);
std::vector<std::string> display_names(mem_type_e hwdevice_type);
/**
* @brief Check if GPUs/drivers have changed since the last call to this function.
* @return `true` if a change has occurred or if it is unknown whether a change occurred.
*/
bool
needs_encoder_reenumeration();
bool needs_encoder_reenumeration();
boost::process::v1::child
run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const boost::process::v1::environment &env, FILE *file, std::error_code &ec, boost::process::v1::group *group);
boost::process::v1::child run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const boost::process::v1::environment &env, FILE *file, std::error_code &ec, boost::process::v1::group *group);
enum class thread_priority_e : int {
low, ///< Low priority
@@ -614,17 +590,13 @@ namespace platf {
high, ///< High priority
critical ///< Critical priority
};
void
adjust_thread_priority(thread_priority_e priority);
void adjust_thread_priority(thread_priority_e priority);
// Allow OS-specific actions to be taken to prepare for streaming
void
streaming_will_start();
void
streaming_will_stop();
void streaming_will_start();
void streaming_will_stop();
void
restart();
void restart();
/**
* @brief Set an environment variable.
@@ -632,16 +604,14 @@ namespace platf {
* @param value The value to set the environment variable to.
* @return 0 on success, non-zero on failure.
*/
int
set_env(const std::string &name, const std::string &value);
int set_env(const std::string &name, const std::string &value);
/**
* @brief Unset an environment variable.
* @param name The name of the environment variable.
* @return 0 on success, non-zero on failure.
*/
int
unset_env(const std::string &name);
int unset_env(const std::string &name);
struct buffer_descriptor_t {
const char *buffer;
@@ -649,9 +619,14 @@ namespace platf {
// Constructors required for emplace_back() prior to C++20
buffer_descriptor_t(const char *buffer, size_t size):
buffer(buffer), size(size) {}
buffer(buffer),
size(size) {
}
buffer_descriptor_t():
buffer(nullptr), size(0) {}
buffer(nullptr),
size(0) {
}
};
struct batched_send_info_t {
@@ -682,24 +657,22 @@ namespace platf {
* @param offset The offset in the total payload data (bytes).
* @return Buffer descriptor describing the region at the given offset.
*/
buffer_descriptor_t
buffer_for_payload_offset(ptrdiff_t offset) {
buffer_descriptor_t buffer_for_payload_offset(ptrdiff_t offset) {
for (const auto &desc : payload_buffers) {
if (offset < desc.size) {
return {
desc.buffer + offset,
desc.size - offset,
};
}
else {
} else {
offset -= desc.size;
}
}
return {};
}
};
bool
send_batch(batched_send_info_t &send_info);
bool send_batch(batched_send_info_t &send_info);
struct send_info_t {
const char *header;
@@ -712,8 +685,8 @@ namespace platf {
uint16_t target_port;
boost::asio::ip::address &source_address;
};
bool
send(send_info_t &send_info);
bool send(send_info_t &send_info);
enum class qos_data_type_e : int {
audio, ///< Audio
@@ -728,34 +701,29 @@ namespace platf {
* @param data_type The type of traffic sent on this socket.
* @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic.
*/
std::unique_ptr<deinit_t>
enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging);
std::unique_ptr<deinit_t> enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging);
/**
* @brief Open a url in the default web browser.
* @param url The url to open.
*/
void
open_url(const std::string &url);
void open_url(const std::string &url);
/**
* @brief Attempt to gracefully terminate a process group.
* @param native_handle The native handle of the process group.
* @return `true` if termination was successfully requested.
*/
bool
request_process_group_exit(std::uintptr_t native_handle);
bool request_process_group_exit(std::uintptr_t native_handle);
/**
* @brief Check if a process group still has running children.
* @param native_handle The native handle of the process group.
* @return `true` if processes are still running.
*/
bool
process_group_running(std::uintptr_t native_handle);
bool process_group_running(std::uintptr_t native_handle);
input_t
input();
input_t input();
/**
* @brief Get the current mouse position on screen
* @param input The input_t instance to use.
@@ -764,24 +732,15 @@ namespace platf {
* auto [x, y] = get_mouse_loc(input);
* @examples_end
*/
util::point_t
get_mouse_loc(input_t &input);
void
move_mouse(input_t &input, int deltaX, int deltaY);
void
abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y);
void
button_mouse(input_t &input, int button, bool release);
void
scroll(input_t &input, int distance);
void
hscroll(input_t &input, int distance);
void
keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags);
void
gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state);
void
unicode(input_t &input, char *utf8, int size);
util::point_t get_mouse_loc(input_t &input);
void move_mouse(input_t &input, int deltaX, int deltaY);
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y);
void button_mouse(input_t &input, int button, bool release);
void scroll(input_t &input, int distance);
void hscroll(input_t &input, int distance);
void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags);
void gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state);
void unicode(input_t &input, char *utf8, int size);
typedef deinit_t client_input_t;
@@ -790,8 +749,7 @@ namespace platf {
* @param input The global input context.
* @return A unique pointer to a per-client input data context.
*/
std::unique_ptr<client_input_t>
allocate_client_input_context(input_t &input);
std::unique_ptr<client_input_t> allocate_client_input_context(input_t &input);
/**
* @brief Send a touch event to the OS.
@@ -799,8 +757,7 @@ namespace platf {
* @param touch_port The current viewport for translating to screen coordinates.
* @param touch The touch event.
*/
void
touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch);
void touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch);
/**
* @brief Send a pen event to the OS.
@@ -808,32 +765,28 @@ namespace platf {
* @param touch_port The current viewport for translating to screen coordinates.
* @param pen The pen event.
*/
void
pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen);
void pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen);
/**
* @brief Send a gamepad touch event to the OS.
* @param input The global input context.
* @param touch The touch event.
*/
void
gamepad_touch(input_t &input, const gamepad_touch_t &touch);
void gamepad_touch(input_t &input, const gamepad_touch_t &touch);
/**
* @brief Send a gamepad motion event to the OS.
* @param input The global input context.
* @param motion The motion event.
*/
void
gamepad_motion(input_t &input, const gamepad_motion_t &motion);
void gamepad_motion(input_t &input, const gamepad_motion_t &motion);
/**
* @brief Send a gamepad battery event to the OS.
* @param input The global input context.
* @param battery The battery event.
*/
void
gamepad_battery(input_t &input, const gamepad_battery_t &battery);
void gamepad_battery(input_t &input, const gamepad_battery_t &battery);
/**
* @brief Create a new virtual gamepad.
@@ -843,35 +796,29 @@ namespace platf {
* @param feedback_queue The queue for posting messages back to the client.
* @return 0 on success.
*/
int
alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue);
void
free_gamepad(input_t &input, int nr);
int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue);
void free_gamepad(input_t &input, int nr);
/**
* @brief Get the supported platform capabilities to advertise to the client.
* @return Capability flags.
*/
platform_caps::caps_t
get_capabilities();
platform_caps::caps_t get_capabilities();
#define SERVICE_NAME "Sunshine"
#define SERVICE_TYPE "_nvstream._tcp"
namespace publish {
[[nodiscard]] std::unique_ptr<deinit_t>
start();
[[nodiscard]] std::unique_ptr<deinit_t> start();
}
[[nodiscard]] std::unique_ptr<deinit_t>
init();
[[nodiscard]] std::unique_ptr<deinit_t> init();
/**
* @brief Returns the current computer name in UTF-8.
* @return Computer name or a placeholder upon failure.
*/
std::string
get_host_name();
std::string get_host_name();
/**
* @brief Gets the supported gamepads for this platform backend.
@@ -879,8 +826,7 @@ namespace platf {
* @param input Pointer to the platform's `input_t` or `nullptr`.
* @return Vector of gamepad options and status.
*/
std::vector<supported_gamepad_t> &
supported_gamepads(input_t *input);
std::vector<supported_gamepad_t> &supported_gamepads(input_t *input);
struct high_precision_timer: private boost::noncopyable {
virtual ~high_precision_timer() = default;
@@ -889,22 +835,19 @@ namespace platf {
* @brief Sleep for the duration
* @param duration Sleep duration
*/
virtual void
sleep_for(const std::chrono::nanoseconds &duration) = 0;
virtual void sleep_for(const std::chrono::nanoseconds &duration) = 0;
/**
* @brief Check if platform-specific timer backend has been initialized successfully
* @return `true` on success, `false` on error
*/
virtual
operator bool() = 0;
virtual operator bool() = 0;
};
/**
* @brief Create platform-specific timer capable of high-precision sleep
* @return A unique pointer to timer
*/
std::unique_ptr<high_precision_timer>
create_high_precision_timer();
std::unique_ptr<high_precision_timer> create_high_precision_timer();
} // namespace platf

View File

@@ -2,20 +2,21 @@
* @file src/platform/linux/audio.cpp
* @brief Definitions for audio control on Linux.
*/
// standard includes
#include <bitset>
#include <sstream>
#include <thread>
// lib includes
#include <boost/regex.hpp>
#include <pulse/error.h>
#include <pulse/pulseaudio.h>
#include <pulse/simple.h>
#include "src/platform/common.h"
// local includes
#include "src/config.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/thread_safe.h"
namespace platf {
@@ -32,8 +33,7 @@ namespace platf {
PA_CHANNEL_POSITION_SIDE_RIGHT,
};
std::string
to_string(const char *name, const std::uint8_t *mapping, int channels) {
std::string to_string(const char *name, const std::uint8_t *mapping, int channels) {
std::stringstream ss;
ss << "rate=48000 sink_name="sv << name << " format=float channels="sv << channels << " channel_map="sv;
@@ -53,8 +53,7 @@ namespace platf {
struct mic_attr_t: public mic_t {
util::safe_ptr<pa_simple, pa_simple_free> mic;
capture_e
sample(std::vector<float> &sample_buf) override {
capture_e sample(std::vector<float> &sample_buf) override {
auto sample_size = sample_buf.size();
auto buf = sample_buf.data();
@@ -69,11 +68,10 @@ namespace platf {
}
};
std::unique_ptr<mic_t>
microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, std::string source_name) {
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, std::string source_name) {
auto mic = std::make_unique<mic_attr_t>();
pa_sample_spec ss { PA_SAMPLE_FLOAT32, sample_rate, (std::uint8_t) channels };
pa_sample_spec ss {PA_SAMPLE_FLOAT32, sample_rate, (std::uint8_t) channels};
pa_channel_map pa_map;
pa_map.channels = channels;
@@ -92,9 +90,8 @@ namespace platf {
int status;
mic->mic.reset(
pa_simple_new(nullptr, "sunshine",
pa_stream_direction_t::PA_STREAM_RECORD, source_name.c_str(),
"sunshine-record", &ss, &pa_map, &pa_attr, &status));
pa_simple_new(nullptr, "sunshine", pa_stream_direction_t::PA_STREAM_RECORD, source_name.c_str(), "sunshine-record", &ss, &pa_map, &pa_attr, &status)
);
if (!mic->mic) {
auto err_str = pa_strerror(status);
@@ -106,38 +103,37 @@ namespace platf {
}
namespace pa {
template <bool B, class T>
template<bool B, class T>
struct add_const_helper;
template <class T>
template<class T>
struct add_const_helper<true, T> {
using type = const std::remove_pointer_t<T> *;
};
template <class T>
template<class T>
struct add_const_helper<false, T> {
using type = const T *;
};
template <class T>
template<class T>
using add_const_t = typename add_const_helper<std::is_pointer_v<T>, T>::type;
template <class T>
void
pa_free(T *p) {
template<class T>
void pa_free(T *p) {
pa_xfree(p);
}
using ctx_t = util::safe_ptr<pa_context, pa_context_unref>;
using loop_t = util::safe_ptr<pa_mainloop, pa_mainloop_free>;
using op_t = util::safe_ptr<pa_operation, pa_operation_unref>;
using string_t = util::safe_ptr<char, pa_free<char>>;
template <class T>
template<class T>
using cb_simple_t = std::function<void(ctx_t::pointer, add_const_t<T> i)>;
template <class T>
void
cb(ctx_t::pointer ctx, add_const_t<T> i, void *userdata) {
template<class T>
void cb(ctx_t::pointer ctx, add_const_t<T> i, void *userdata) {
auto &f = *(cb_simple_t<T> *) userdata;
// Cannot similarly filter on eol here. Unless reported otherwise assume
@@ -145,12 +141,11 @@ namespace platf {
f(ctx, i);
}
template <class T>
template<class T>
using cb_t = std::function<void(ctx_t::pointer, add_const_t<T> i, int eol)>;
template <class T>
void
cb(ctx_t::pointer ctx, add_const_t<T> i, int eol, void *userdata) {
template<class T>
void cb(ctx_t::pointer ctx, add_const_t<T> i, int eol, void *userdata) {
auto &f = *(cb_t<T> *) userdata;
// For some reason, pulseaudio calls this callback after disconnecting
@@ -161,22 +156,19 @@ namespace platf {
f(ctx, i, eol);
}
void
cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) {
void cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) {
auto alarm = (safe::alarm_raw_t<int> *) userdata;
alarm->ring(i);
}
void
ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
void ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
auto &f = *(std::function<void(ctx_t::pointer)> *) userdata;
f(ctx);
}
void
success_cb(ctx_t::pointer ctx, int status, void *userdata) {
void success_cb(ctx_t::pointer ctx, int status, void *userdata) {
assert(userdata != nullptr);
auto alarm = (safe::alarm_raw_t<int> *) userdata;
@@ -205,8 +197,8 @@ namespace platf {
std::unique_ptr<std::function<void(ctx_t::pointer)>> events_cb;
std::thread worker;
int
init() {
int init() {
events = std::make_unique<safe::event_t<ctx_event_e>>();
loop.reset(pa_mainloop_new());
ctx.reset(pa_context_new(pa_mainloop_get_api(loop.get()), "sunshine"));
@@ -262,8 +254,7 @@ namespace platf {
return 0;
}
int
load_null(const char *name, const std::uint8_t *channel_mapping, int channels) {
int load_null(const char *name, const std::uint8_t *channel_mapping, int channels) {
auto alarm = safe::make_alarm<int>();
op_t op {
@@ -272,15 +263,15 @@ namespace platf {
"module-null-sink",
to_string(name, channel_mapping, channels).c_str(),
cb_i,
alarm.get()),
alarm.get()
),
};
alarm->wait();
return *alarm->status();
}
int
unload_null(std::uint32_t i) {
int unload_null(std::uint32_t i) {
if (i == PA_INVALID_INDEX) {
return 0;
}
@@ -301,8 +292,7 @@ namespace platf {
return 0;
}
std::optional<sink_t>
sink_info() override {
std::optional<sink_t> sink_info() override {
constexpr auto stereo = "sink-sunshine-stereo";
constexpr auto surround51 = "sink-sunshine-surround51";
constexpr auto surround71 = "sink-sunshine-surround71";
@@ -331,20 +321,18 @@ namespace platf {
index.stereo = sink_info->owner_module;
++nullcount;
}
else if (!std::strcmp(sink_info->name, surround51)) {
} else if (!std::strcmp(sink_info->name, surround51)) {
index.surround51 = sink_info->owner_module;
++nullcount;
}
else if (!std::strcmp(sink_info->name, surround71)) {
} else if (!std::strcmp(sink_info->name, surround71)) {
index.surround71 = sink_info->owner_module;
++nullcount;
}
};
op_t op { pa_context_get_sink_info_list(ctx.get(), cb<pa_sink_info *>, &f) };
op_t op {pa_context_get_sink_info_list(ctx.get(), cb<pa_sink_info *>, &f)};
if (!op) {
BOOST_LOG(error) << "Couldn't create card info operation: "sv << pa_strerror(pa_context_errno(ctx.get()));
@@ -365,8 +353,7 @@ namespace platf {
index.stereo = load_null(stereo, speaker::map_stereo, sizeof(speaker::map_stereo));
if (index.stereo == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for stereo: "sv << pa_strerror(pa_context_errno(ctx.get()));
}
else {
} else {
++nullcount;
}
}
@@ -375,8 +362,7 @@ namespace platf {
index.surround51 = load_null(surround51, speaker::map_surround51, sizeof(speaker::map_surround51));
if (index.surround51 == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for surround-51: "sv << pa_strerror(pa_context_errno(ctx.get()));
}
else {
} else {
++nullcount;
}
}
@@ -385,8 +371,7 @@ namespace platf {
index.surround71 = load_null(surround71, speaker::map_surround71, sizeof(speaker::map_surround71));
if (index.surround71 == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for surround-71: "sv << pa_strerror(pa_context_errno(ctx.get()));
}
else {
} else {
++nullcount;
}
}
@@ -396,14 +381,13 @@ namespace platf {
}
if (nullcount == 3) {
sink.null = std::make_optional(sink_t::null_t { stereo, surround51, surround71 });
sink.null = std::make_optional(sink_t::null_t {stereo, surround51, surround71});
}
return std::make_optional(std::move(sink));
}
std::string
get_default_sink_name() {
std::string get_default_sink_name() {
std::string sink_name;
auto alarm = safe::make_alarm<int>();
@@ -419,14 +403,13 @@ namespace platf {
alarm->ring(0);
};
op_t server_op { pa_context_get_server_info(ctx.get(), cb<pa_server_info *>, &server_f) };
op_t server_op {pa_context_get_server_info(ctx.get(), cb<pa_server_info *>, &server_f)};
alarm->wait();
// No need to check status. If it failed just return default name.
return sink_name;
}
std::string
get_monitor_name(const std::string &sink_name) {
std::string get_monitor_name(const std::string &sink_name) {
std::string monitor_name;
auto alarm = safe::make_alarm<int>();
@@ -449,7 +432,7 @@ namespace platf {
monitor_name = sink_info->monitor_source_name;
};
op_t sink_op { pa_context_get_sink_info_by_name(ctx.get(), sink_name.c_str(), cb<pa_sink_info *>, &sink_f) };
op_t sink_op {pa_context_get_sink_info_by_name(ctx.get(), sink_name.c_str(), cb<pa_sink_info *>, &sink_f)};
alarm->wait();
// No need to check status. If it failed just return default name.
@@ -457,8 +440,7 @@ namespace platf {
return monitor_name;
}
std::unique_ptr<mic_t>
microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
// Sink choice priority:
// 1. Config sink
// 2. Last sink swapped to (Usually virtual in this case)
@@ -467,26 +449,32 @@ namespace platf {
// but this happens right after the swap so the default returned by PA was not
// the new one just set!
auto sink_name = config::audio.sink;
if (sink_name.empty()) sink_name = requested_sink;
if (sink_name.empty()) sink_name = get_default_sink_name();
if (sink_name.empty()) {
sink_name = requested_sink;
}
if (sink_name.empty()) {
sink_name = get_default_sink_name();
}
return ::platf::microphone(mapping, channels, sample_rate, frame_size, get_monitor_name(sink_name));
}
bool
is_sink_available(const std::string &sink) override {
bool is_sink_available(const std::string &sink) override {
BOOST_LOG(warning) << "audio_control_t::is_sink_available() unimplemented: "sv << sink;
return true;
}
int
set_sink(const std::string &sink) override {
int set_sink(const std::string &sink) override {
auto alarm = safe::make_alarm<int>();
BOOST_LOG(info) << "Setting default sink to: ["sv << sink << "]"sv;
op_t op {
pa_context_set_default_sink(
ctx.get(), sink.c_str(), success_cb, alarm.get()),
ctx.get(),
sink.c_str(),
success_cb,
alarm.get()
),
};
if (!op) {
@@ -525,8 +513,7 @@ namespace platf {
};
} // namespace pa
std::unique_ptr<audio_control_t>
audio_control() {
std::unique_ptr<audio_control_t> audio_control() {
auto audio = std::make_unique<pa::server_t>();
if (audio->init()) {
@@ -535,4 +522,4 @@ namespace platf {
return audio;
}
} // namespace platf
} // namespace platf

View File

@@ -2,13 +2,15 @@
* @file src/platform/linux/cuda.cpp
* @brief Definitions for CUDA encoding.
*/
// standard includes
#include <bitset>
#include <fcntl.h>
#include <filesystem>
#include <thread>
#include <NvFBC.h>
// lib includes
#include <ffnvcodec/dynlink_loader.h>
#include <NvFBC.h>
extern "C" {
#include <libavcodec/avcodec.h>
@@ -16,6 +18,7 @@ extern "C" {
#include <libavutil/imgutils.h>
}
// local includes
#include "cuda.h"
#include "graphics.h"
#include "src/logging.h"
@@ -27,7 +30,8 @@ extern "C" {
#define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x)
#define CU_CHECK(x, y) \
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) return -1
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return -1
#define CU_CHECK_IGNORE(x, y) \
check((x), SUNSHINE_STRINGVIEW(y ": "))
@@ -35,17 +39,16 @@ extern "C" {
namespace fs = std::filesystem;
using namespace std::literals;
namespace cuda {
constexpr auto cudaDevAttrMaxThreadsPerBlock = (CUdevice_attribute) 1;
constexpr auto cudaDevAttrMaxThreadsPerMultiProcessor = (CUdevice_attribute) 39;
void
pass_error(const std::string_view &sv, const char *name, const char *description) {
void pass_error(const std::string_view &sv, const char *name, const char *description) {
BOOST_LOG(error) << sv << name << ':' << description;
}
void
cff(CudaFunctions *cf) {
void cff(CudaFunctions *cf) {
cuda_free_functions(&cf);
}
@@ -53,8 +56,7 @@ namespace cuda {
static cdf_t cdf;
inline static int
check(CUresult result, const std::string_view &sv) {
inline static int check(CUresult result, const std::string_view &sv) {
if (result != CUDA_SUCCESS) {
const char *name;
const char *description;
@@ -69,13 +71,11 @@ namespace cuda {
return 0;
}
void
freeStream(CUstream stream) {
void freeStream(CUstream stream) {
CU_CHECK_IGNORE(cdf->cuStreamDestroy(stream), "Couldn't destroy cuda stream");
}
void
unregisterResource(CUgraphicsResource resource) {
void unregisterResource(CUgraphicsResource resource) {
CU_CHECK_IGNORE(cdf->cuGraphicsUnregisterResource(resource), "Couldn't unregister resource");
}
@@ -86,8 +86,7 @@ namespace cuda {
tex_t tex;
};
int
init() {
int init() {
auto status = cuda_load_functions(&cdf, nullptr);
if (status) {
BOOST_LOG(error) << "Couldn't load cuda: "sv << status;
@@ -102,8 +101,7 @@ namespace cuda {
class cuda_t: public platf::avcodec_encode_device_t {
public:
int
init(int in_width, int in_height) {
int init(int in_width, int in_height) {
if (!cdf) {
BOOST_LOG(warning) << "cuda not initialized"sv;
return -1;
@@ -117,8 +115,7 @@ namespace cuda {
return 0;
}
int
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
this->hwframe.reset(frame);
this->frame = frame;
@@ -156,8 +153,7 @@ namespace cuda {
return 0;
}
void
apply_colorspace() override {
void apply_colorspace() override {
sws.apply_colorspace(colorspace);
auto tex = tex_t::make(height, width * 4);
@@ -182,11 +178,10 @@ namespace cuda {
return;
}
sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex->texture.linear, stream.get(), { frame->width, frame->height, 0, 0 });
sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex->texture.linear, stream.get(), {frame->width, frame->height, 0, 0});
}
cudaTextureObject_t
tex_obj(const tex_t &tex) const {
cudaTextureObject_t tex_obj(const tex_t &tex) const {
return linear_interpolation ? tex.texture.linear : tex.texture.point;
}
@@ -203,13 +198,11 @@ namespace cuda {
class cuda_ram_t: public cuda_t {
public:
int
convert(platf::img_t &img) override {
int convert(platf::img_t &img) override {
return sws.load_ram(img, tex.array) || sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(tex), stream.get());
}
int
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
if (cuda_t::set_frame(frame, hw_frames_ctx)) {
return -1;
}
@@ -229,8 +222,7 @@ namespace cuda {
class cuda_vram_t: public cuda_t {
public:
int
convert(platf::img_t &img) override {
int convert(platf::img_t &img) override {
return sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(((img_t *) &img)->tex), stream.get());
}
};
@@ -240,8 +232,7 @@ namespace cuda {
* @param index CUDA device index to open.
* @return File descriptor or -1 on failure.
*/
file_t
open_drm_fd_for_cuda_device(int index) {
file_t open_drm_fd_for_cuda_device(int index) {
CUdevice device;
CU_CHECK(cdf->cuDeviceGet(&device, index), "Couldn't get CUDA device");
@@ -252,29 +243,29 @@ namespace cuda {
BOOST_LOG(debug) << "Found CUDA device with PCI bus ID: "sv << pci_bus_id.data();
// Linux uses lowercase hexadecimal while CUDA uses uppercase
std::transform(pci_bus_id.begin(), pci_bus_id.end(), pci_bus_id.begin(),
[](char c) { return std::tolower(c); });
std::transform(pci_bus_id.begin(), pci_bus_id.end(), pci_bus_id.begin(), [](char c) {
return std::tolower(c);
});
// Look for the name of the primary node in sysfs
try {
char sysfs_path[PATH_MAX];
std::snprintf(sysfs_path, sizeof(sysfs_path), "/sys/bus/pci/devices/%s/drm", pci_bus_id.data());
fs::path sysfs_dir { sysfs_path };
for (auto &entry : fs::directory_iterator { sysfs_dir }) {
fs::path sysfs_dir {sysfs_path};
for (auto &entry : fs::directory_iterator {sysfs_dir}) {
auto file = entry.path().filename();
auto filestring = file.generic_string();
if (std::string_view { filestring }.substr(0, 4) != "card"sv) {
if (std::string_view {filestring}.substr(0, 4) != "card"sv) {
continue;
}
BOOST_LOG(debug) << "Found DRM primary node: "sv << filestring;
fs::path dri_path { "/dev/dri"sv };
fs::path dri_path {"/dev/dri"sv};
auto device_path = dri_path / file;
return open(device_path.c_str(), O_RDWR);
}
}
catch (const std::filesystem::filesystem_error &err) {
} catch (const std::filesystem::filesystem_error &err) {
BOOST_LOG(error) << "Failed to read sysfs: "sv << err.what();
}
@@ -292,8 +283,7 @@ namespace cuda {
* @param offset_y Offset of content in captured frame.
* @return 0 on success or -1 on failure.
*/
int
init(int in_width, int in_height, int offset_x, int offset_y) {
int init(int in_width, int in_height, int offset_x, int offset_y) {
// This must be non-zero to tell the video core that it's a hardware encoding device.
data = (void *) 0x1;
@@ -340,8 +330,7 @@ namespace cuda {
* @param hw_frames_ctx_buf FFmpeg hardware frame context.
* @return 0 on success or -1 on failure.
*/
int
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override {
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override {
this->hwframe.reset(frame);
this->frame = frame;
@@ -377,10 +366,8 @@ namespace cuda {
cuda_ctx->stream = stream.get();
CU_CHECK(cdf->cuGraphicsGLRegisterImage(&y_res, nv12->tex[0], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY),
"Couldn't register Y plane texture");
CU_CHECK(cdf->cuGraphicsGLRegisterImage(&uv_res, nv12->tex[1], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY),
"Couldn't register UV plane texture");
CU_CHECK(cdf->cuGraphicsGLRegisterImage(&y_res, nv12->tex[0], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY), "Couldn't register Y plane texture");
CU_CHECK(cdf->cuGraphicsGLRegisterImage(&uv_res, nv12->tex[1], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY), "Couldn't register UV plane texture");
return 0;
}
@@ -390,15 +377,13 @@ namespace cuda {
* @param img Captured screen image.
* @return 0 on success or -1 on failure.
*/
int
convert(platf::img_t &img) override {
int convert(platf::img_t &img) override {
auto &descriptor = (egl::img_descriptor_t &) img;
if (descriptor.sequence == 0) {
// For dummy images, use a blank RGB texture instead of importing a DMA-BUF
rgb = egl::create_blank(img);
}
else if (descriptor.sequence > sequence) {
} else if (descriptor.sequence > sequence) {
sequence = descriptor.sequence;
rgb = egl::rgb_t {};
@@ -419,7 +404,7 @@ namespace cuda {
auto fmt_desc = av_pix_fmt_desc_get(sw_format);
// Map the GL textures to read for CUDA
CUgraphicsResource resources[2] = { y_res.get(), uv_res.get() };
CUgraphicsResource resources[2] = {y_res.get(), uv_res.get()};
CU_CHECK(cdf->cuGraphicsMapResources(2, resources, stream.get()), "Couldn't map GL textures in CUDA");
// Copy from the GL textures to the target CUDA frame
@@ -445,8 +430,7 @@ namespace cuda {
/**
* @brief Configures shader parameters for the specified colorspace.
*/
void
apply_colorspace() override {
void apply_colorspace() override {
sws.apply_colorspace(colorspace);
}
@@ -474,8 +458,7 @@ namespace cuda {
int offset_x, offset_y;
};
std::unique_ptr<platf::avcodec_encode_device_t>
make_avcodec_encode_device(int width, int height, bool vram) {
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram) {
if (init()) {
return nullptr;
}
@@ -484,8 +467,7 @@ namespace cuda {
if (vram) {
cuda = std::make_unique<cuda_vram_t>();
}
else {
} else {
cuda = std::make_unique<cuda_ram_t>();
}
@@ -504,8 +486,7 @@ namespace cuda {
* @param offset_y Offset of content in captured frame.
* @return FFmpeg encoding device context.
*/
std::unique_ptr<platf::avcodec_encode_device_t>
make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y) {
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y) {
if (init()) {
return nullptr;
}
@@ -521,29 +502,30 @@ namespace cuda {
namespace nvfbc {
static PNVFBCCREATEINSTANCE createInstance {};
static NVFBC_API_FUNCTION_LIST func { NVFBC_VERSION };
static NVFBC_API_FUNCTION_LIST func {NVFBC_VERSION};
static constexpr inline NVFBC_BOOL
nv_bool(bool b) {
static constexpr inline NVFBC_BOOL nv_bool(bool b) {
return b ? NVFBC_TRUE : NVFBC_FALSE;
}
static void *handle { nullptr };
int
init() {
static void *handle {nullptr};
int init() {
static bool funcs_loaded = false;
if (funcs_loaded) return 0;
if (funcs_loaded) {
return 0;
}
if (!handle) {
handle = dyn::handle({ "libnvidia-fbc.so.1", "libnvidia-fbc.so" });
handle = dyn::handle({"libnvidia-fbc.so.1", "libnvidia-fbc.so"});
if (!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &createInstance, "NvFBCCreateInstance" },
{(dyn::apiproc *) &createInstance, "NvFBCCreateInstance"},
};
if (dyn::load(handle, funcs)) {
@@ -569,7 +551,7 @@ namespace cuda {
class ctx_t {
public:
ctx_t(NVFBC_SESSION_HANDLE handle) {
NVFBC_BIND_CONTEXT_PARAMS params { NVFBC_BIND_CONTEXT_PARAMS_VER };
NVFBC_BIND_CONTEXT_PARAMS params {NVFBC_BIND_CONTEXT_PARAMS_VER};
if (func.nvFBCBindContext(handle, &params)) {
BOOST_LOG(error) << "Couldn't bind NvFBC context to current thread: " << func.nvFBCGetLastErrorStr(handle);
@@ -579,7 +561,7 @@ namespace cuda {
}
~ctx_t() {
NVFBC_RELEASE_CONTEXT_PARAMS params { NVFBC_RELEASE_CONTEXT_PARAMS_VER };
NVFBC_RELEASE_CONTEXT_PARAMS params {NVFBC_RELEASE_CONTEXT_PARAMS_VER};
if (func.nvFBCReleaseContext(handle, &params)) {
BOOST_LOG(error) << "Couldn't release NvFBC context from current thread: " << func.nvFBCGetLastErrorStr(handle);
}
@@ -597,26 +579,26 @@ namespace cuda {
public:
handle_t() = default;
handle_t(handle_t &&other):
handle_flags { other.handle_flags }, handle { other.handle } {
handle_flags {other.handle_flags},
handle {other.handle} {
other.handle_flags.reset();
}
handle_t &
operator=(handle_t &&other) {
handle_t &operator=(handle_t &&other) {
std::swap(handle_flags, other.handle_flags);
std::swap(handle, other.handle);
return *this;
}
static std::optional<handle_t>
make() {
NVFBC_CREATE_HANDLE_PARAMS params { NVFBC_CREATE_HANDLE_PARAMS_VER };
static std::optional<handle_t> make() {
NVFBC_CREATE_HANDLE_PARAMS params {NVFBC_CREATE_HANDLE_PARAMS_VER};
// Set privateData to allow NvFBC on consumer NVIDIA GPUs.
// Based on https://github.com/keylase/nvidia-patch/blob/3193b4b1cea91527bf09ea9b8db5aade6a3f3c0a/win/nvfbcwrp/nvfbcwrp_main.cpp#L23-L25 .
const unsigned int MAGIC_PRIVATE_DATA[4] = { 0xAEF57AC5, 0x401D1A39, 0x1B856BBE, 0x9ED0CEBA };
const unsigned int MAGIC_PRIVATE_DATA[4] = {0xAEF57AC5, 0x401D1A39, 0x1B856BBE, 0x9ED0CEBA};
params.privateData = MAGIC_PRIVATE_DATA;
params.privateDataSize = sizeof(MAGIC_PRIVATE_DATA);
@@ -633,14 +615,12 @@ namespace cuda {
return handle;
}
const char *
last_error() {
const char *last_error() {
return func.nvFBCGetLastErrorStr(handle);
}
std::optional<NVFBC_GET_STATUS_PARAMS>
status() {
NVFBC_GET_STATUS_PARAMS params { NVFBC_GET_STATUS_PARAMS_VER };
std::optional<NVFBC_GET_STATUS_PARAMS> status() {
NVFBC_GET_STATUS_PARAMS params {NVFBC_GET_STATUS_PARAMS_VER};
auto status = func.nvFBCGetStatus(handle, &params);
if (status) {
@@ -652,8 +632,7 @@ namespace cuda {
return params;
}
int
capture(NVFBC_CREATE_CAPTURE_SESSION_PARAMS &capture_params) {
int capture(NVFBC_CREATE_CAPTURE_SESSION_PARAMS &capture_params) {
if (func.nvFBCCreateCaptureSession(handle, &capture_params)) {
BOOST_LOG(error) << "Failed to start capture session: "sv << last_error();
return -1;
@@ -673,13 +652,12 @@ namespace cuda {
return 0;
}
int
stop() {
int stop() {
if (!handle_flags[SESSION_CAPTURE]) {
return 0;
}
NVFBC_DESTROY_CAPTURE_SESSION_PARAMS params { NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER };
NVFBC_DESTROY_CAPTURE_SESSION_PARAMS params {NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER};
if (func.nvFBCDestroyCaptureSession(handle, &params)) {
BOOST_LOG(error) << "Couldn't destroy capture session: "sv << last_error();
@@ -692,17 +670,16 @@ namespace cuda {
return 0;
}
int
reset() {
int reset() {
if (!handle_flags[SESSION_HANDLE]) {
return 0;
}
stop();
NVFBC_DESTROY_HANDLE_PARAMS params { NVFBC_DESTROY_HANDLE_PARAMS_VER };
NVFBC_DESTROY_HANDLE_PARAMS params {NVFBC_DESTROY_HANDLE_PARAMS_VER};
ctx_t ctx { handle };
ctx_t ctx {handle};
if (func.nvFBCDestroyHandle(handle, &params)) {
BOOST_LOG(error) << "Couldn't destroy session handle: "sv << func.nvFBCGetLastErrorStr(handle);
}
@@ -723,14 +700,13 @@ namespace cuda {
class display_t: public platf::display_t {
public:
int
init(const std::string_view &display_name, const ::video::config_t &config) {
int init(const std::string_view &display_name, const ::video::config_t &config) {
auto handle = handle_t::make();
if (!handle) {
return -1;
}
ctx_t ctx { handle->handle };
ctx_t ctx {handle->handle};
auto status_params = handle->status();
if (!status_params) {
@@ -744,19 +720,17 @@ namespace cuda {
if (monitor_nr < 0 || monitor_nr >= status_params->dwOutputNum) {
BOOST_LOG(warning) << "Can't stream monitor ["sv << monitor_nr << "], it needs to be between [0] and ["sv << status_params->dwOutputNum - 1 << "], defaulting to virtual desktop"sv;
}
else {
} else {
streamedMonitor = monitor_nr;
}
}
else {
} else {
BOOST_LOG(warning) << "XrandR not available, streaming entire virtual desktop"sv;
}
}
delay = std::chrono::nanoseconds { 1s } / config.framerate;
delay = std::chrono::nanoseconds {1s} / config.framerate;
capture_params = NVFBC_CREATE_CAPTURE_SESSION_PARAMS { NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER };
capture_params = NVFBC_CREATE_CAPTURE_SESSION_PARAMS {NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER};
capture_params.eCaptureType = NVFBC_CAPTURE_SHARED_CUDA;
capture_params.bDisableAutoModesetRecovery = nv_bool(true);
@@ -773,8 +747,7 @@ namespace cuda {
capture_params.eTrackingType = NVFBC_TRACKING_OUTPUT;
capture_params.dwOutputId = output.dwId;
}
else {
} else {
capture_params.eTrackingType = NVFBC_TRACKING_SCREEN;
width = status_params->screenSize.w;
@@ -788,8 +761,7 @@ namespace cuda {
return 0;
}
platf::capture_e
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
auto next_frame = std::chrono::steady_clock::now();
{
@@ -802,7 +774,7 @@ namespace cuda {
// Force display_t::capture to initialize handle_t::capture
cursor_visible = !*cursor;
ctx_t ctx { handle.handle };
ctx_t ctx {handle.handle};
auto fg = util::fail_guard([&]() {
handle.reset();
});
@@ -849,8 +821,7 @@ namespace cuda {
}
// Reinitialize the capture session.
platf::capture_e
reinit(bool cursor) {
platf::capture_e reinit(bool cursor) {
if (handle.stop()) {
return platf::capture_e::error;
}
@@ -860,8 +831,7 @@ namespace cuda {
capture_params.bPushModel = nv_bool(false);
capture_params.bWithCursor = nv_bool(true);
capture_params.bAllowDirectCapture = nv_bool(false);
}
else {
} else {
capture_params.bPushModel = nv_bool(true);
capture_params.bWithCursor = nv_bool(false);
capture_params.bAllowDirectCapture = nv_bool(true);
@@ -919,8 +889,7 @@ namespace cuda {
return platf::capture_e::ok;
}
platf::capture_e
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
if (cursor != cursor_visible) {
auto status = reinit(cursor);
if (status != platf::capture_e::ok) {
@@ -960,13 +929,11 @@ namespace cuda {
return platf::capture_e::ok;
}
std::unique_ptr<platf::avcodec_encode_device_t>
make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) {
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) {
return ::cuda::make_avcodec_encode_device(width, height, true);
}
std::shared_ptr<platf::img_t>
alloc_img() override {
std::shared_ptr<platf::img_t> alloc_img() override {
auto img = std::make_shared<cuda::img_t>();
img->data = nullptr;
@@ -985,8 +952,7 @@ namespace cuda {
return img;
};
int
dummy_img(platf::img_t *) override {
int dummy_img(platf::img_t *) override {
return 0;
}
@@ -1001,8 +967,7 @@ namespace cuda {
} // namespace cuda
namespace platf {
std::shared_ptr<display_t>
nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
std::shared_ptr<display_t> nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
if (hwdevice_type != mem_type_e::cuda) {
BOOST_LOG(error) << "Could not initialize nvfbc display with the given hw device type"sv;
return nullptr;
@@ -1017,8 +982,7 @@ namespace platf {
return display;
}
std::vector<std::string>
nvfbc_display_names() {
std::vector<std::string> nvfbc_display_names() {
if (cuda::init() || cuda::nvfbc::init()) {
return {};
}

View File

@@ -2,14 +2,17 @@
* @file src/platform/linux/cuda.cu
* @brief CUDA implementation for Linux.
*/
// #include <algorithm>
#include <helper_math.h>
// standard includes
#include <chrono>
#include <limits>
#include <memory>
#include <optional>
#include <string_view>
// platform includes
#include <helper_math.h>
// local includes
#include "cuda.h"
using namespace std::literals;
@@ -18,16 +21,20 @@ using namespace std::literals;
#define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x)
#define CU_CHECK(x, y) \
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return -1
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return -1
#define CU_CHECK_VOID(x, y) \
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return;
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return;
#define CU_CHECK_PTR(x, y) \
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return nullptr;
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return nullptr;
#define CU_CHECK_OPT(x, y) \
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return std::nullopt;
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return std::nullopt;
#define CU_CHECK_IGNORE(x, y) \
check((x), SUNSHINE_STRINGVIEW(y ": "))
@@ -42,277 +49,293 @@ using namespace std::literals;
* Not pretty and extremely error-prone, fix at earliest convenience.
*/
namespace platf {
struct img_t: std::enable_shared_from_this<img_t> {
public:
std::uint8_t *data {};
std::int32_t width {};
std::int32_t height {};
std::int32_t pixel_pitch {};
std::int32_t row_pitch {};
struct img_t: std::enable_shared_from_this<img_t> {
public:
std::uint8_t *data {};
std::int32_t width {};
std::int32_t height {};
std::int32_t pixel_pitch {};
std::int32_t row_pitch {};
std::optional<std::chrono::steady_clock::time_point> frame_timestamp;
std::optional<std::chrono::steady_clock::time_point> frame_timestamp;
virtual ~img_t() = default;
};
} // namespace platf
virtual ~img_t() = default;
};
} // namespace platf
// End special declarations
namespace cuda {
struct alignas(16) cuda_color_t {
float4 color_vec_y;
float4 color_vec_u;
float4 color_vec_v;
float2 range_y;
float2 range_uv;
};
struct alignas(16) cuda_color_t {
float4 color_vec_y;
float4 color_vec_u;
float4 color_vec_v;
float2 range_y;
float2 range_uv;
};
static_assert(sizeof(video::color_t) == sizeof(cuda::cuda_color_t), "color matrix struct mismatch");
static_assert(sizeof(video::color_t) == sizeof(cuda::cuda_color_t), "color matrix struct mismatch");
auto constexpr INVALID_TEXTURE = std::numeric_limits<cudaTextureObject_t>::max();
auto constexpr INVALID_TEXTURE = std::numeric_limits<cudaTextureObject_t>::max();
template<class T>
inline T div_align(T l, T r) {
return (l + r - 1) / r;
}
void pass_error(const std::string_view &sv, const char *name, const char *description);
inline static int check(cudaError_t result, const std::string_view &sv) {
if(result) {
auto name = cudaGetErrorName(result);
auto description = cudaGetErrorString(result);
pass_error(sv, name, description);
return -1;
template<class T>
inline T div_align(T l, T r) {
return (l + r - 1) / r;
}
return 0;
}
void pass_error(const std::string_view &sv, const char *name, const char *description);
template<class T>
ptr_t make_ptr() {
void *p;
CU_CHECK_PTR(cudaMalloc(&p, sizeof(T)), "Couldn't allocate color matrix");
inline static int check(cudaError_t result, const std::string_view &sv) {
if (result) {
auto name = cudaGetErrorName(result);
auto description = cudaGetErrorString(result);
ptr_t ptr { p };
pass_error(sv, name, description);
return -1;
}
return ptr;
}
void freeCudaPtr_t::operator()(void *ptr) {
CU_CHECK_IGNORE(cudaFree(ptr), "Couldn't free cuda device pointer");
}
void freeCudaStream_t::operator()(cudaStream_t ptr) {
CU_CHECK_IGNORE(cudaStreamDestroy(ptr), "Couldn't free cuda stream");
}
stream_t make_stream(int flags) {
cudaStream_t stream;
if(!flags) {
CU_CHECK_PTR(cudaStreamCreate(&stream), "Couldn't create cuda stream");
}
else {
CU_CHECK_PTR(cudaStreamCreateWithFlags(&stream, flags), "Couldn't create cuda stream with flags");
return 0;
}
return stream_t { stream };
}
template<class T>
ptr_t make_ptr() {
void *p;
CU_CHECK_PTR(cudaMalloc(&p, sizeof(T)), "Couldn't allocate color matrix");
inline __device__ float3 bgra_to_rgb(uchar4 vec) {
return make_float3((float)vec.z, (float)vec.y, (float)vec.x);
}
ptr_t ptr {p};
inline __device__ float3 bgra_to_rgb(float4 vec) {
return make_float3(vec.z, vec.y, vec.x);
}
inline __device__ float2 calcUV(float3 pixel, const cuda_color_t *const color_matrix) {
float4 vec_u = color_matrix->color_vec_u;
float4 vec_v = color_matrix->color_vec_v;
float u = dot(pixel, make_float3(vec_u)) + vec_u.w;
float v = dot(pixel, make_float3(vec_v)) + vec_v.w;
u = u * color_matrix->range_uv.x + color_matrix->range_uv.y;
v = v * color_matrix->range_uv.x + color_matrix->range_uv.y;
return make_float2(u, v);
}
inline __device__ float calcY(float3 pixel, const cuda_color_t *const color_matrix) {
float4 vec_y = color_matrix->color_vec_y;
return (dot(pixel, make_float3(vec_y)) + vec_y.w) * color_matrix->range_y.x + color_matrix->range_y.y;
}
__global__ void RGBA_to_NV12(
cudaTextureObject_t srcImage, std::uint8_t *dstY, std::uint8_t *dstUV,
std::uint32_t dstPitchY, std::uint32_t dstPitchUV,
float scale, const viewport_t viewport, const cuda_color_t *const color_matrix) {
int idX = (threadIdx.x + blockDim.x * blockIdx.x) * 2;
int idY = (threadIdx.y + blockDim.y * blockIdx.y) * 2;
if(idX >= viewport.width) return;
if(idY >= viewport.height) return;
float x = idX * scale;
float y = idY * scale;
idX += viewport.offsetX;
idY += viewport.offsetY;
uint8_t *dstY0 = dstY + idX + idY * dstPitchY;
uint8_t *dstY1 = dstY + idX + (idY + 1) * dstPitchY;
dstUV = dstUV + idX + (idY / 2 * dstPitchUV);
float3 rgb_lt = bgra_to_rgb(tex2D<float4>(srcImage, x, y));
float3 rgb_rt = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y));
float3 rgb_lb = bgra_to_rgb(tex2D<float4>(srcImage, x, y + scale));
float3 rgb_rb = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y + scale));
float2 uv_lt = calcUV(rgb_lt, color_matrix) * 256.0f;
float2 uv_rt = calcUV(rgb_rt, color_matrix) * 256.0f;
float2 uv_lb = calcUV(rgb_lb, color_matrix) * 256.0f;
float2 uv_rb = calcUV(rgb_rb, color_matrix) * 256.0f;
float2 uv = (uv_lt + uv_lb + uv_rt + uv_rb) * 0.25f;
dstUV[0] = uv.x;
dstUV[1] = uv.y;
dstY0[0] = calcY(rgb_lt, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
dstY0[1] = calcY(rgb_rt, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
dstY1[0] = calcY(rgb_lb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
dstY1[1] = calcY(rgb_rb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
}
int tex_t::copy(std::uint8_t *src, int height, int pitch) {
CU_CHECK(cudaMemcpy2DToArray(array, 0, 0, src, pitch, pitch, height, cudaMemcpyDeviceToDevice), "Couldn't copy to cuda array from deviceptr");
return 0;
}
std::optional<tex_t> tex_t::make(int height, int pitch) {
tex_t tex;
auto format = cudaCreateChannelDesc<uchar4>();
CU_CHECK_OPT(cudaMallocArray(&tex.array, &format, pitch, height, cudaArrayDefault), "Couldn't allocate cuda array");
cudaResourceDesc res {};
res.resType = cudaResourceTypeArray;
res.res.array.array = tex.array;
cudaTextureDesc desc {};
desc.readMode = cudaReadModeNormalizedFloat;
desc.filterMode = cudaFilterModePoint;
desc.normalizedCoords = false;
std::fill_n(std::begin(desc.addressMode), 2, cudaAddressModeClamp);
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.point, &res, &desc, nullptr), "Couldn't create cuda texture that uses point interpolation");
desc.filterMode = cudaFilterModeLinear;
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.linear, &res, &desc, nullptr), "Couldn't create cuda texture that uses linear interpolation");
return tex;
}
tex_t::tex_t() : array {}, texture { INVALID_TEXTURE, INVALID_TEXTURE } {}
tex_t::tex_t(tex_t &&other) : array { other.array }, texture { other.texture } {
other.array = 0;
other.texture.point = INVALID_TEXTURE;
other.texture.linear = INVALID_TEXTURE;
}
tex_t &tex_t::operator=(tex_t &&other) {
std::swap(array, other.array);
std::swap(texture, other.texture);
return *this;
}
tex_t::~tex_t() {
if(texture.point != INVALID_TEXTURE) {
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.point), "Couldn't deallocate cuda texture that uses point interpolation");
texture.point = INVALID_TEXTURE;
return ptr;
}
if(texture.linear != INVALID_TEXTURE) {
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.linear), "Couldn't deallocate cuda texture that uses linear interpolation");
texture.linear = INVALID_TEXTURE;
void freeCudaPtr_t::operator()(void *ptr) {
CU_CHECK_IGNORE(cudaFree(ptr), "Couldn't free cuda device pointer");
}
if(array) {
CU_CHECK_IGNORE(cudaFreeArray(array), "Couldn't deallocate cuda array");
array = cudaArray_t {};
}
}
sws_t::sws_t(int in_width, int in_height, int out_width, int out_height, int pitch, int threadsPerBlock, ptr_t &&color_matrix)
: threadsPerBlock { threadsPerBlock }, color_matrix { std::move(color_matrix) } {
// Ensure aspect ratio is maintained
auto scalar = std::fminf(out_width / (float)in_width, out_height / (float)in_height);
auto out_width_f = in_width * scalar;
auto out_height_f = in_height * scalar;
// result is always positive
auto offsetX_f = (out_width - out_width_f) / 2;
auto offsetY_f = (out_height - out_height_f) / 2;
viewport.width = out_width_f;
viewport.height = out_height_f;
viewport.offsetX = offsetX_f;
viewport.offsetY = offsetY_f;
scale = 1.0f / scalar;
}
std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int out_height, int pitch) {
cudaDeviceProp props;
int device;
CU_CHECK_OPT(cudaGetDevice(&device), "Couldn't get cuda device");
CU_CHECK_OPT(cudaGetDeviceProperties(&props, device), "Couldn't get cuda device properties");
auto ptr = make_ptr<cuda_color_t>();
if(!ptr) {
return std::nullopt;
void freeCudaStream_t::operator()(cudaStream_t ptr) {
CU_CHECK_IGNORE(cudaStreamDestroy(ptr), "Couldn't free cuda stream");
}
return std::make_optional<sws_t>(in_width, in_height, out_width, out_height, pitch, props.maxThreadsPerMultiProcessor / props.maxBlocksPerMultiProcessor, std::move(ptr));
}
stream_t make_stream(int flags) {
cudaStream_t stream;
int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream) {
return convert(Y, UV, pitchY, pitchUV, texture, stream, viewport);
}
if (!flags) {
CU_CHECK_PTR(cudaStreamCreate(&stream), "Couldn't create cuda stream");
} else {
CU_CHECK_PTR(cudaStreamCreateWithFlags(&stream, flags), "Couldn't create cuda stream with flags");
}
int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport) {
int threadsX = viewport.width / 2;
int threadsY = viewport.height / 2;
return stream_t {stream};
}
dim3 block(threadsPerBlock);
dim3 grid(div_align(threadsX, threadsPerBlock), threadsY);
inline __device__ float3 bgra_to_rgb(uchar4 vec) {
return make_float3((float) vec.z, (float) vec.y, (float) vec.x);
}
RGBA_to_NV12<<<grid, block, 0, stream>>>(texture, Y, UV, pitchY, pitchUV, scale, viewport, (cuda_color_t *)color_matrix.get());
inline __device__ float3 bgra_to_rgb(float4 vec) {
return make_float3(vec.z, vec.y, vec.x);
}
return CU_CHECK_IGNORE(cudaGetLastError(), "RGBA_to_NV12 failed");
}
inline __device__ float2 calcUV(float3 pixel, const cuda_color_t *const color_matrix) {
float4 vec_u = color_matrix->color_vec_u;
float4 vec_v = color_matrix->color_vec_v;
void sws_t::apply_colorspace(const video::sunshine_colorspace_t& colorspace) {
auto color_p = video::color_vectors_from_colorspace(colorspace);
CU_CHECK_IGNORE(cudaMemcpy(color_matrix.get(), color_p, sizeof(video::color_t), cudaMemcpyHostToDevice), "Couldn't copy color matrix to cuda");
}
float u = dot(pixel, make_float3(vec_u)) + vec_u.w;
float v = dot(pixel, make_float3(vec_v)) + vec_v.w;
int sws_t::load_ram(platf::img_t &img, cudaArray_t array) {
return CU_CHECK_IGNORE(cudaMemcpy2DToArray(array, 0, 0, img.data, img.row_pitch, img.width * img.pixel_pitch, img.height, cudaMemcpyHostToDevice), "Couldn't copy to cuda array");
}
u = u * color_matrix->range_uv.x + color_matrix->range_uv.y;
v = v * color_matrix->range_uv.x + color_matrix->range_uv.y;
} // namespace cuda
return make_float2(u, v);
}
inline __device__ float calcY(float3 pixel, const cuda_color_t *const color_matrix) {
float4 vec_y = color_matrix->color_vec_y;
return (dot(pixel, make_float3(vec_y)) + vec_y.w) * color_matrix->range_y.x + color_matrix->range_y.y;
}
__global__ void RGBA_to_NV12(
cudaTextureObject_t srcImage,
std::uint8_t *dstY,
std::uint8_t *dstUV,
std::uint32_t dstPitchY,
std::uint32_t dstPitchUV,
float scale,
const viewport_t viewport,
const cuda_color_t *const color_matrix
) {
int idX = (threadIdx.x + blockDim.x * blockIdx.x) * 2;
int idY = (threadIdx.y + blockDim.y * blockIdx.y) * 2;
if (idX >= viewport.width) {
return;
}
if (idY >= viewport.height) {
return;
}
float x = idX * scale;
float y = idY * scale;
idX += viewport.offsetX;
idY += viewport.offsetY;
uint8_t *dstY0 = dstY + idX + idY * dstPitchY;
uint8_t *dstY1 = dstY + idX + (idY + 1) * dstPitchY;
dstUV = dstUV + idX + (idY / 2 * dstPitchUV);
float3 rgb_lt = bgra_to_rgb(tex2D<float4>(srcImage, x, y));
float3 rgb_rt = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y));
float3 rgb_lb = bgra_to_rgb(tex2D<float4>(srcImage, x, y + scale));
float3 rgb_rb = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y + scale));
float2 uv_lt = calcUV(rgb_lt, color_matrix) * 256.0f;
float2 uv_rt = calcUV(rgb_rt, color_matrix) * 256.0f;
float2 uv_lb = calcUV(rgb_lb, color_matrix) * 256.0f;
float2 uv_rb = calcUV(rgb_rb, color_matrix) * 256.0f;
float2 uv = (uv_lt + uv_lb + uv_rt + uv_rb) * 0.25f;
dstUV[0] = uv.x;
dstUV[1] = uv.y;
dstY0[0] = calcY(rgb_lt, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
dstY0[1] = calcY(rgb_rt, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
dstY1[0] = calcY(rgb_lb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
dstY1[1] = calcY(rgb_rb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
}
int tex_t::copy(std::uint8_t *src, int height, int pitch) {
CU_CHECK(cudaMemcpy2DToArray(array, 0, 0, src, pitch, pitch, height, cudaMemcpyDeviceToDevice), "Couldn't copy to cuda array from deviceptr");
return 0;
}
std::optional<tex_t> tex_t::make(int height, int pitch) {
tex_t tex;
auto format = cudaCreateChannelDesc<uchar4>();
CU_CHECK_OPT(cudaMallocArray(&tex.array, &format, pitch, height, cudaArrayDefault), "Couldn't allocate cuda array");
cudaResourceDesc res {};
res.resType = cudaResourceTypeArray;
res.res.array.array = tex.array;
cudaTextureDesc desc {};
desc.readMode = cudaReadModeNormalizedFloat;
desc.filterMode = cudaFilterModePoint;
desc.normalizedCoords = false;
std::fill_n(std::begin(desc.addressMode), 2, cudaAddressModeClamp);
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.point, &res, &desc, nullptr), "Couldn't create cuda texture that uses point interpolation");
desc.filterMode = cudaFilterModeLinear;
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.linear, &res, &desc, nullptr), "Couldn't create cuda texture that uses linear interpolation");
return tex;
}
tex_t::tex_t():
array {},
texture {INVALID_TEXTURE, INVALID_TEXTURE} {
}
tex_t::tex_t(tex_t &&other):
array {other.array},
texture {other.texture} {
other.array = 0;
other.texture.point = INVALID_TEXTURE;
other.texture.linear = INVALID_TEXTURE;
}
tex_t &tex_t::operator=(tex_t &&other) {
std::swap(array, other.array);
std::swap(texture, other.texture);
return *this;
}
tex_t::~tex_t() {
if (texture.point != INVALID_TEXTURE) {
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.point), "Couldn't deallocate cuda texture that uses point interpolation");
texture.point = INVALID_TEXTURE;
}
if (texture.linear != INVALID_TEXTURE) {
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.linear), "Couldn't deallocate cuda texture that uses linear interpolation");
texture.linear = INVALID_TEXTURE;
}
if (array) {
CU_CHECK_IGNORE(cudaFreeArray(array), "Couldn't deallocate cuda array");
array = cudaArray_t {};
}
}
sws_t::sws_t(int in_width, int in_height, int out_width, int out_height, int pitch, int threadsPerBlock, ptr_t &&color_matrix):
threadsPerBlock {threadsPerBlock},
color_matrix {std::move(color_matrix)} {
// Ensure aspect ratio is maintained
auto scalar = std::fminf(out_width / (float) in_width, out_height / (float) in_height);
auto out_width_f = in_width * scalar;
auto out_height_f = in_height * scalar;
// result is always positive
auto offsetX_f = (out_width - out_width_f) / 2;
auto offsetY_f = (out_height - out_height_f) / 2;
viewport.width = out_width_f;
viewport.height = out_height_f;
viewport.offsetX = offsetX_f;
viewport.offsetY = offsetY_f;
scale = 1.0f / scalar;
}
std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int out_height, int pitch) {
cudaDeviceProp props;
int device;
CU_CHECK_OPT(cudaGetDevice(&device), "Couldn't get cuda device");
CU_CHECK_OPT(cudaGetDeviceProperties(&props, device), "Couldn't get cuda device properties");
auto ptr = make_ptr<cuda_color_t>();
if (!ptr) {
return std::nullopt;
}
return std::make_optional<sws_t>(in_width, in_height, out_width, out_height, pitch, props.maxThreadsPerMultiProcessor / props.maxBlocksPerMultiProcessor, std::move(ptr));
}
int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream) {
return convert(Y, UV, pitchY, pitchUV, texture, stream, viewport);
}
int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport) {
int threadsX = viewport.width / 2;
int threadsY = viewport.height / 2;
dim3 block(threadsPerBlock);
dim3 grid(div_align(threadsX, threadsPerBlock), threadsY);
RGBA_to_NV12<<<grid, block, 0, stream>>>(texture, Y, UV, pitchY, pitchUV, scale, viewport, (cuda_color_t *) color_matrix.get());
return CU_CHECK_IGNORE(cudaGetLastError(), "RGBA_to_NV12 failed");
}
void sws_t::apply_colorspace(const video::sunshine_colorspace_t &colorspace) {
auto color_p = video::color_vectors_from_colorspace(colorspace);
CU_CHECK_IGNORE(cudaMemcpy(color_matrix.get(), color_p, sizeof(video::color_t), cudaMemcpyHostToDevice), "Couldn't copy color matrix to cuda");
}
int sws_t::load_ram(platf::img_t &img, cudaArray_t array) {
return CU_CHECK_IGNORE(cudaMemcpy2DToArray(array, 0, 0, img.data, img.row_pitch, img.width * img.pixel_pitch, img.height, cudaMemcpyHostToDevice), "Couldn't copy to cuda array");
}
} // namespace cuda

View File

@@ -5,15 +5,16 @@
#pragma once
#if defined(SUNSHINE_BUILD_CUDA)
#include "src/video_colorspace.h"
// standard includes
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <vector>
// local includes
#include "src/video_colorspace.h"
namespace platf {
class avcodec_encode_device_t;
class img_t;
@@ -22,11 +23,10 @@ namespace platf {
namespace cuda {
namespace nvfbc {
std::vector<std::string>
display_names();
std::vector<std::string> display_names();
}
std::unique_ptr<platf::avcodec_encode_device_t>
make_avcodec_encode_device(int width, int height, bool vram);
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram);
/**
* @brief Create a GL->CUDA encoding device for consuming captured dmabufs.
@@ -36,11 +36,9 @@ namespace cuda {
* @param offset_y Offset of content in captured frame.
* @return FFmpeg encoding device context.
*/
std::unique_ptr<platf::avcodec_encode_device_t>
make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y);
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y);
int
init();
int init();
} // namespace cuda
typedef struct cudaArray *cudaArray_t;
@@ -57,21 +55,18 @@ namespace cuda {
class freeCudaPtr_t {
public:
void
operator()(void *ptr);
void operator()(void *ptr);
};
class freeCudaStream_t {
public:
void
operator()(cudaStream_t ptr);
void operator()(cudaStream_t ptr);
};
using ptr_t = std::unique_ptr<void, freeCudaPtr_t>;
using stream_t = std::unique_ptr<CUstream_st, freeCudaStream_t>;
stream_t
make_stream(int flags = 0);
stream_t make_stream(int flags = 0);
struct viewport_t {
int width, height;
@@ -80,19 +75,16 @@ namespace cuda {
class tex_t {
public:
static std::optional<tex_t>
make(int height, int pitch);
static std::optional<tex_t> make(int height, int pitch);
tex_t();
tex_t(tex_t &&);
tex_t &
operator=(tex_t &&other);
tex_t &operator=(tex_t &&other);
~tex_t();
int
copy(std::uint8_t *src, int height, int pitch);
int copy(std::uint8_t *src, int height, int pitch);
cudaArray_t array;
@@ -113,20 +105,15 @@ namespace cuda {
*
* pitch -- The size of a single row of pixels in bytes
*/
static std::optional<sws_t>
make(int in_width, int in_height, int out_width, int out_height, int pitch);
static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_height, int pitch);
// Converts loaded image into a CUDevicePtr
int
convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream);
int
convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport);
int convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream);
int convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport);
void
apply_colorspace(const video::sunshine_colorspace_t &colorspace);
void apply_colorspace(const video::sunshine_colorspace_t &colorspace);
int
load_ram(platf::img_t &img, cudaArray_t array);
int load_ram(platf::img_t &img, cudaArray_t array);
ptr_t color_matrix;
@@ -138,4 +125,4 @@ namespace cuda {
};
} // namespace cuda
#endif
#endif

View File

@@ -2,13 +2,15 @@
* @file src/platform/linux/graphics.cpp
* @brief Definitions for graphics related functions.
*/
// standard includes
#include <fcntl.h>
// local includes
#include "graphics.h"
#include "src/file_handler.h"
#include "src/logging.h"
#include "src/video.h"
#include <fcntl.h>
extern "C" {
#include <libavutil/pixdesc.h>
}
@@ -17,8 +19,7 @@ extern "C" {
// There aren't that many DRM_FORMAT I need to use, so define them here
//
// They aren't likely to change any time soon.
#define fourcc_code(a, b, c, d) ((std::uint32_t)(a) | ((std::uint32_t)(b) << 8) | \
((std::uint32_t)(c) << 16) | ((std::uint32_t)(d) << 24))
#define fourcc_code(a, b, c, d) ((std::uint32_t)(a) | ((std::uint32_t)(b) << 8) | ((std::uint32_t)(c) << 16) | ((std::uint32_t)(d) << 24))
#define fourcc_mod_code(vendor, val) ((((uint64_t) vendor) << 56) | ((val) & 0x00ffffffffffffffULL))
#define DRM_FORMAT_MOD_INVALID fourcc_mod_code(0, ((1ULL << 56) - 1))
@@ -27,11 +28,11 @@ extern "C" {
#endif
using namespace std::literals;
namespace gl {
GladGLContext ctx;
void
drain_errors(const std::string_view &prefix) {
void drain_errors(const std::string_view &prefix) {
GLenum err;
while ((err = ctx.GetError()) != GL_NO_ERROR) {
BOOST_LOG(error) << "GL: "sv << prefix << ": ["sv << util::hex(err).to_string_view() << ']';
@@ -44,13 +45,12 @@ namespace gl {
}
}
tex_t
tex_t::make(std::size_t count) {
tex_t textures { count };
tex_t tex_t::make(std::size_t count) {
tex_t textures {count};
ctx.GenTextures(textures.size(), textures.begin());
float color[] = { 0.0f, 0.0f, 0.0f, 1.0f };
float color[] = {0.0f, 0.0f, 0.0f, 1.0f};
for (auto tex : textures) {
gl::ctx.BindTexture(GL_TEXTURE_2D, tex);
@@ -70,25 +70,22 @@ namespace gl {
}
}
frame_buf_t
frame_buf_t::make(std::size_t count) {
frame_buf_t frame_buf { count };
frame_buf_t frame_buf_t::make(std::size_t count) {
frame_buf_t frame_buf {count};
ctx.GenFramebuffers(frame_buf.size(), frame_buf.begin());
return frame_buf;
}
void
frame_buf_t::copy(int id, int texture, int offset_x, int offset_y, int width, int height) {
void frame_buf_t::copy(int id, int texture, int offset_x, int offset_y, int width, int height) {
gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, (*this)[id]);
gl::ctx.ReadBuffer(GL_COLOR_ATTACHMENT0 + id);
gl::ctx.BindTexture(GL_TEXTURE_2D, texture);
gl::ctx.CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, offset_x, offset_y, width, height);
}
std::string
shader_t::err_str() {
std::string shader_t::err_str() {
int length;
ctx.GetShaderiv(handle(), GL_INFO_LOG_LENGTH, &length);
@@ -102,8 +99,7 @@ namespace gl {
return string;
}
util::Either<shader_t, std::string>
shader_t::compile(const std::string_view &source, GLenum type) {
util::Either<shader_t, std::string> shader_t::compile(const std::string_view &source, GLenum type) {
shader_t shader;
auto data = source.data();
@@ -123,13 +119,11 @@ namespace gl {
return shader;
}
GLuint
shader_t::handle() const {
GLuint shader_t::handle() const {
return _shader.el;
}
buffer_t
buffer_t::make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data) {
buffer_t buffer_t::make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data) {
buffer_t buffer;
buffer._block = block;
buffer._size = data.size();
@@ -142,25 +136,21 @@ namespace gl {
return buffer;
}
GLuint
buffer_t::handle() const {
GLuint buffer_t::handle() const {
return _buffer.el;
}
const char *
buffer_t::block() const {
const char *buffer_t::block() const {
return _block;
}
void
buffer_t::update(const std::string_view &view, std::size_t offset) {
void buffer_t::update(const std::string_view &view, std::size_t offset) {
ctx.BindBuffer(GL_UNIFORM_BUFFER, handle());
ctx.BufferSubData(GL_UNIFORM_BUFFER, offset, view.size(), (const void *) view.data());
}
void
buffer_t::update(std::string_view *members, std::size_t count, std::size_t offset) {
util::buffer_t<std::uint8_t> buffer { _size };
void buffer_t::update(std::string_view *members, std::size_t count, std::size_t offset) {
util::buffer_t<std::uint8_t> buffer {_size};
for (int x = 0; x < count; ++x) {
auto val = members[x];
@@ -171,8 +161,7 @@ namespace gl {
update(util::view(buffer.begin(), buffer.end()), offset);
}
std::string
program_t::err_str() {
std::string program_t::err_str() {
int length;
ctx.GetProgramiv(handle(), GL_INFO_LOG_LENGTH, &length);
@@ -186,8 +175,7 @@ namespace gl {
return string;
}
util::Either<program_t, std::string>
program_t::link(const shader_t &vert, const shader_t &frag) {
util::Either<program_t, std::string> program_t::link(const shader_t &vert, const shader_t &frag) {
program_t program;
program._program.el = ctx.CreateProgram();
@@ -214,16 +202,14 @@ namespace gl {
return program;
}
void
program_t::bind(const buffer_t &buffer) {
void program_t::bind(const buffer_t &buffer) {
ctx.UseProgram(handle());
auto i = ctx.GetUniformBlockIndex(handle(), buffer.block());
ctx.BindBufferBase(GL_UNIFORM_BUFFER, i, buffer.handle());
}
std::optional<buffer_t>
program_t::uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count) {
std::optional<buffer_t> program_t::uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count) {
auto i = ctx.GetUniformBlockIndex(handle(), block);
if (i == GL_INVALID_INDEX) {
BOOST_LOG(error) << "Couldn't find index of ["sv << block << ']';
@@ -235,7 +221,7 @@ namespace gl {
bool error_flag = false;
util::buffer_t<GLint> offsets { count };
util::buffer_t<GLint> offsets {count};
auto indices = (std::uint32_t *) alloca(count * sizeof(std::uint32_t));
auto names = (const char **) alloca(count * sizeof(const char *));
auto names_p = names;
@@ -260,7 +246,7 @@ namespace gl {
}
ctx.GetActiveUniformsiv(handle(), count, indices, GL_UNIFORM_OFFSET, offsets.begin());
util::buffer_t<std::uint8_t> buffer { (std::size_t) size };
util::buffer_t<std::uint8_t> buffer {(std::size_t) size};
for (int x = 0; x < count; ++x) {
auto val = std::get<1>(members[x]);
@@ -268,11 +254,10 @@ namespace gl {
std::copy_n((const std::uint8_t *) val.data(), val.size(), &buffer[offsets[x]]);
}
return buffer_t::make(std::move(offsets), block, std::string_view { (char *) buffer.begin(), buffer.size() });
return buffer_t::make(std::move(offsets), block, std::string_view {(char *) buffer.begin(), buffer.size()});
}
GLuint
program_t::handle() const {
GLuint program_t::handle() const {
return _program.el;
}
@@ -282,23 +267,24 @@ namespace gbm {
device_destroy_fn device_destroy;
create_device_fn create_device;
int
init() {
static void *handle { nullptr };
int init() {
static void *handle {nullptr};
static bool funcs_loaded = false;
if (funcs_loaded) return 0;
if (funcs_loaded) {
return 0;
}
if (!handle) {
handle = dyn::handle({ "libgbm.so.1", "libgbm.so" });
handle = dyn::handle({"libgbm.so.1", "libgbm.so"});
if (!handle) {
return -1;
}
}
std::vector<std::tuple<GLADapiproc *, const char *>> funcs {
{ (GLADapiproc *) &device_destroy, "gbm_device_destroy" },
{ (GLADapiproc *) &create_device, "gbm_create_device" },
{(GLADapiproc *) &device_destroy, "gbm_device_destroy"},
{(GLADapiproc *) &create_device, "gbm_create_device"},
};
if (dyn::load(handle, funcs)) {
@@ -334,16 +320,14 @@ namespace egl {
constexpr auto EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT = 0x3449;
constexpr auto EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT = 0x344A;
bool
fail() {
bool fail() {
return eglGetError() != EGL_SUCCESS;
}
/**
* @memberof egl::display_t
*/
display_t
make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display) {
display_t make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display) {
constexpr auto EGL_PLATFORM_GBM_MESA = 0x31D7;
constexpr auto EGL_PLATFORM_WAYLAND_KHR = 0x31D8;
constexpr auto EGL_PLATFORM_X11_KHR = 0x31D5;
@@ -408,10 +392,11 @@ namespace egl {
return display;
}
std::optional<ctx_t>
make_ctx(display_t::pointer display) {
std::optional<ctx_t> make_ctx(display_t::pointer display) {
constexpr int conf_attr[] {
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE
EGL_RENDERABLE_TYPE,
EGL_OPENGL_BIT,
EGL_NONE
};
int count;
@@ -427,10 +412,12 @@ namespace egl {
}
constexpr int attr[] {
EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE
EGL_CONTEXT_CLIENT_VERSION,
3,
EGL_NONE
};
ctx_t ctx { display, eglCreateContext(display, conf, EGL_NO_CONTEXT, attr) };
ctx_t ctx {display, eglCreateContext(display, conf, EGL_NO_CONTEXT, attr)};
if (fail()) {
BOOST_LOG(error) << "Couldn't create EGL context: ["sv << util::hex(eglGetError()).to_string_view() << ']';
return std::nullopt;
@@ -465,8 +452,7 @@ namespace egl {
EGLAttrib hi;
};
inline plane_attr_t
get_plane(std::uint32_t plane_indice) {
inline plane_attr_t get_plane(std::uint32_t plane_indice) {
switch (plane_indice) {
case 0:
return {
@@ -511,8 +497,7 @@ namespace egl {
* @param surface The surface descriptor.
* @return Vector of EGL attributes.
*/
std::vector<EGLAttrib>
surface_descriptor_to_egl_attribs(const surface_descriptor_t &surface) {
std::vector<EGLAttrib> surface_descriptor_to_egl_attribs(const surface_descriptor_t &surface) {
std::vector<EGLAttrib> attribs;
attribs.emplace_back(EGL_WIDTH);
@@ -549,8 +534,7 @@ namespace egl {
return attribs;
}
std::optional<rgb_t>
import_source(display_t::pointer egl_display, const surface_descriptor_t &xrgb) {
std::optional<rgb_t> import_source(display_t::pointer egl_display, const surface_descriptor_t &xrgb) {
auto attribs = surface_descriptor_to_egl_attribs(xrgb);
rgb_t rgb {
@@ -580,8 +564,7 @@ namespace egl {
* @param img The image to use for texture sizing.
* @return The new RGB texture.
*/
rgb_t
create_blank(platf::img_t &img) {
rgb_t create_blank(platf::img_t &img) {
rgb_t rgb {
EGL_NO_DISPLAY,
EGL_NO_IMAGE,
@@ -597,7 +580,7 @@ namespace egl {
GLenum attachment = GL_COLOR_ATTACHMENT0;
gl::ctx.DrawBuffers(1, &attachment);
const GLuint rgb_black[] = { 0, 0, 0, 0 };
const GLuint rgb_black[] = {0, 0, 0, 0};
gl::ctx.ClearBufferuiv(GL_COLOR, 0, rgb_black);
gl_drain_errors;
@@ -605,8 +588,7 @@ namespace egl {
return rgb;
}
std::optional<nv12_t>
import_target(display_t::pointer egl_display, std::array<file_t, nv12_img_t::num_fds> &&fds, const surface_descriptor_t &y, const surface_descriptor_t &uv) {
std::optional<nv12_t> import_target(display_t::pointer egl_display, std::array<file_t, nv12_img_t::num_fds> &&fds, const surface_descriptor_t &y, const surface_descriptor_t &uv) {
auto y_attribs = surface_descriptor_to_egl_attribs(y);
auto uv_attribs = surface_descriptor_to_egl_attribs(uv);
@@ -642,8 +624,8 @@ namespace egl {
gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, nv12->buf[x]);
gl::ctx.DrawBuffers(1, &attachments[x]);
const float y_black[] = { 0.0f, 0.0f, 0.0f, 0.0f };
const float uv_black[] = { 0.5f, 0.5f, 0.5f, 0.5f };
const float y_black[] = {0.0f, 0.0f, 0.0f, 0.0f};
const float uv_black[] = {0.5f, 0.5f, 0.5f, 0.5f};
gl::ctx.ClearBufferfv(GL_COLOR, 0, x == 0 ? y_black : uv_black);
}
@@ -661,8 +643,7 @@ namespace egl {
* @param format Format of the target frame.
* @return The new RGB texture.
*/
std::optional<nv12_t>
create_target(int width, int height, AVPixelFormat format) {
std::optional<nv12_t> create_target(int width, int height, AVPixelFormat format) {
nv12_t nv12 {
EGL_NO_DISPLAY,
EGL_NO_IMAGE,
@@ -679,12 +660,10 @@ namespace egl {
if (fmt_desc->comp[0].depth <= 8) {
y_format = GL_R8;
uv_format = GL_RG8;
}
else if (fmt_desc->comp[0].depth <= 16) {
} else if (fmt_desc->comp[0].depth <= 16) {
y_format = GL_R16;
uv_format = GL_RG16;
}
else {
} else {
BOOST_LOG(error) << "Unsupported target pixel format: "sv << format;
return std::nullopt;
}
@@ -693,8 +672,7 @@ namespace egl {
gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, y_format, width, height);
gl::ctx.BindTexture(GL_TEXTURE_2D, nv12->tex[1]);
gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, uv_format,
width >> fmt_desc->log2_chroma_w, height >> fmt_desc->log2_chroma_h);
gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, uv_format, width >> fmt_desc->log2_chroma_w, height >> fmt_desc->log2_chroma_h);
nv12->buf.bind(std::begin(nv12->tex), std::end(nv12->tex));
@@ -707,8 +685,8 @@ namespace egl {
gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, nv12->buf[x]);
gl::ctx.DrawBuffers(1, &attachments[x]);
const float y_black[] = { 0.0f, 0.0f, 0.0f, 0.0f };
const float uv_black[] = { 0.5f, 0.5f, 0.5f, 0.5f };
const float y_black[] = {0.0f, 0.0f, 0.0f, 0.0f};
const float uv_black[] = {0.5f, 0.5f, 0.5f, 0.5f};
gl::ctx.ClearBufferfv(GL_COLOR, 0, x == 0 ? y_black : uv_black);
}
@@ -719,8 +697,7 @@ namespace egl {
return nv12;
}
void
sws_t::apply_colorspace(const video::sunshine_colorspace_t &colorspace) {
void sws_t::apply_colorspace(const video::sunshine_colorspace_t &colorspace) {
auto color_p = video::color_vectors_from_colorspace(colorspace);
std::string_view members[] {
@@ -737,8 +714,7 @@ namespace egl {
program[1].bind(color_matrix);
}
std::optional<sws_t>
sws_t::make(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex) {
std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex) {
sws_t sws;
sws.serial = std::numeric_limits<std::uint64_t>::max();
@@ -866,8 +842,7 @@ namespace egl {
return sws;
}
int
sws_t::blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height) {
int sws_t::blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height) {
auto f = [&]() {
std::swap(offsetX, this->offsetX);
std::swap(offsetY, this->offsetY);
@@ -881,8 +856,7 @@ namespace egl {
return convert(fb);
}
std::optional<sws_t>
sws_t::make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format) {
std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format) {
GLint gl_format;
// Decide the bit depth format of the backing texture based the target frame format
@@ -916,16 +890,14 @@ namespace egl {
return make(in_width, in_height, out_width, out_height, std::move(tex));
}
void
sws_t::load_ram(platf::img_t &img) {
void sws_t::load_ram(platf::img_t &img) {
loaded_texture = tex[0];
gl::ctx.BindTexture(GL_TEXTURE_2D, loaded_texture);
gl::ctx.TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, img.width, img.height, GL_BGRA, GL_UNSIGNED_BYTE, img.data);
}
void
sws_t::load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture) {
void sws_t::load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture) {
// When only a sub-part of the image must be encoded...
const bool copy = offset_x || offset_y || img.sd.width != in_width || img.sd.height != in_height;
if (copy) {
@@ -934,8 +906,7 @@ namespace egl {
loaded_texture = tex[0];
framebuf.copy(0, loaded_texture, offset_x, offset_y, in_width, in_height);
}
else {
} else {
loaded_texture = texture;
}
@@ -985,8 +956,7 @@ namespace egl {
}
}
int
sws_t::convert(gl::frame_buf_t &fb) {
int sws_t::convert(gl::frame_buf_t &fb) {
gl::ctx.BindTexture(GL_TEXTURE_2D, loaded_texture);
GLenum attachments[] {
@@ -1019,7 +989,6 @@ namespace egl {
}
} // namespace egl
void
free_frame(AVFrame *frame) {
void free_frame(AVFrame *frame) {
av_frame_free(&frame);
}

View File

@@ -4,12 +4,15 @@
*/
#pragma once
// standard includes
#include <optional>
#include <string_view>
// lib includes
#include <glad/egl.h>
#include <glad/gl.h>
// local includes
#include "misc.h"
#include "src/logging.h"
#include "src/platform/common.h"
@@ -21,35 +24,30 @@
#define gl_drain_errors_helper(x) gl::drain_errors(x)
#define gl_drain_errors gl_drain_errors_helper(__FILE__ ":" SUNSHINE_STRINGIFY(__LINE__))
extern "C" int
close(int __fd);
extern "C" int close(int __fd);
// X11 Display
extern "C" struct _XDisplay;
struct AVFrame;
void
free_frame(AVFrame *frame);
void free_frame(AVFrame *frame);
using frame_t = util::safe_ptr<AVFrame, free_frame>;
namespace gl {
extern GladGLContext ctx;
void
drain_errors(const std::string_view &prefix);
void drain_errors(const std::string_view &prefix);
class tex_t: public util::buffer_t<GLuint> {
using util::buffer_t<GLuint>::buffer_t;
public:
tex_t(tex_t &&) = default;
tex_t &
operator=(tex_t &&) = default;
tex_t &operator=(tex_t &&) = default;
~tex_t();
static tex_t
make(std::size_t count);
static tex_t make(std::size_t count);
};
class frame_buf_t: public util::buffer_t<GLuint> {
@@ -57,16 +55,13 @@ namespace gl {
public:
frame_buf_t(frame_buf_t &&) = default;
frame_buf_t &
operator=(frame_buf_t &&) = default;
frame_buf_t &operator=(frame_buf_t &&) = default;
~frame_buf_t();
static frame_buf_t
make(std::size_t count);
static frame_buf_t make(std::size_t count);
inline void
bind(std::nullptr_t, std::nullptr_t) {
inline void bind(std::nullptr_t, std::nullptr_t) {
int x = 0;
for (auto fb : (*this)) {
ctx.BindFramebuffer(GL_FRAMEBUFFER, fb);
@@ -77,9 +72,8 @@ namespace gl {
return;
}
template <class It>
void
bind(It it_begin, It it_end) {
template<class It>
void bind(It it_begin, It it_end) {
using namespace std::literals;
if (std::distance(it_begin, it_end) > size()) {
BOOST_LOG(warning) << "To many elements to bind"sv;
@@ -100,8 +94,7 @@ namespace gl {
/**
* Copies a part of the framebuffer to texture
*/
void
copy(int id, int texture, int offset_x, int offset_y, int width, int height);
void copy(int id, int texture, int offset_x, int offset_y, int width, int height);
};
class shader_t {
@@ -112,14 +105,11 @@ namespace gl {
});
public:
std::string
err_str();
std::string err_str();
static util::Either<shader_t, std::string>
compile(const std::string_view &source, GLenum type);
static util::Either<shader_t, std::string> compile(const std::string_view &source, GLenum type);
GLuint
handle() const;
GLuint handle() const;
private:
shader_internal_t _shader;
@@ -133,19 +123,14 @@ namespace gl {
});
public:
static buffer_t
make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data);
static buffer_t make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data);
GLuint
handle() const;
GLuint handle() const;
const char *
block() const;
const char *block() const;
void
update(const std::string_view &view, std::size_t offset = 0);
void
update(std::string_view *members, std::size_t count, std::size_t offset = 0);
void update(const std::string_view &view, std::size_t offset = 0);
void update(std::string_view *members, std::size_t count, std::size_t offset = 0);
private:
const char *_block;
@@ -165,20 +150,15 @@ namespace gl {
});
public:
std::string
err_str();
std::string err_str();
static util::Either<program_t, std::string>
link(const shader_t &vert, const shader_t &frag);
static util::Either<program_t, std::string> link(const shader_t &vert, const shader_t &frag);
void
bind(const buffer_t &buffer);
void bind(const buffer_t &buffer);
std::optional<buffer_t>
uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count);
std::optional<buffer_t> uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count);
GLuint
handle() const;
GLuint handle() const;
private:
program_internal_t _program;
@@ -195,8 +175,7 @@ namespace gbm {
using gbm_t = util::dyn_safe_ptr<device, &device_destroy>;
int
init();
int init();
} // namespace gbm
@@ -258,24 +237,23 @@ namespace egl {
std::uint32_t offsets[4];
};
display_t
make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display);
std::optional<ctx_t>
make_ctx(display_t::pointer display);
display_t make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display);
std::optional<ctx_t> make_ctx(display_t::pointer display);
std::optional<rgb_t>
import_source(
display_t::pointer egl_display,
const surface_descriptor_t &xrgb);
import_source(
display_t::pointer egl_display,
const surface_descriptor_t &xrgb
);
rgb_t
create_blank(platf::img_t &img);
rgb_t create_blank(platf::img_t &img);
std::optional<nv12_t>
import_target(
std::optional<nv12_t> import_target(
display_t::pointer egl_display,
std::array<file_t, nv12_img_t::num_fds> &&fds,
const surface_descriptor_t &y, const surface_descriptor_t &uv);
const surface_descriptor_t &y,
const surface_descriptor_t &uv
);
/**
* @brief Creates biplanar YUV textures to render into.
@@ -284,8 +262,7 @@ namespace egl {
* @param format Format of the target frame.
* @return The new RGB texture.
*/
std::optional<nv12_t>
create_target(int width, int height, AVPixelFormat format);
std::optional<nv12_t> create_target(int width, int height, AVPixelFormat format);
class cursor_t: public platf::img_t {
public:
@@ -304,8 +281,7 @@ namespace egl {
reset();
}
void
reset() {
void reset() {
for (auto x = 0; x < 4; ++x) {
if (sd.fds[x] >= 0) {
close(sd.fds[x]);
@@ -323,26 +299,19 @@ namespace egl {
class sws_t {
public:
static std::optional<sws_t>
make(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex);
static std::optional<sws_t>
make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format);
static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex);
static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format);
// Convert the loaded image into the first two framebuffers
int
convert(gl::frame_buf_t &fb);
int convert(gl::frame_buf_t &fb);
// Make an area of the image black
int
blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height);
int blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height);
void
load_ram(platf::img_t &img);
void
load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture);
void load_ram(platf::img_t &img);
void load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture);
void
apply_colorspace(const video::sunshine_colorspace_t &colorspace);
void apply_colorspace(const video::sunshine_colorspace_t &colorspace);
// The first texture is the monitor image.
// The second texture is the cursor image
@@ -367,6 +336,5 @@ namespace egl {
std::uint64_t serial;
};
bool
fail();
bool fail();
} // namespace egl

View File

@@ -2,132 +2,114 @@
* @file src/platform/linux/input/inputtino.cpp
* @brief Definitions for the inputtino Linux input handling.
*/
// lib includes
#include <inputtino/input.hpp>
#include <libevdev/libevdev.h>
#include "src/config.h"
#include "src/platform/common.h"
#include "src/utility.h"
// local includes
#include "inputtino_common.h"
#include "inputtino_gamepad.h"
#include "inputtino_keyboard.h"
#include "inputtino_mouse.h"
#include "inputtino_pen.h"
#include "inputtino_touch.h"
#include "src/config.h"
#include "src/platform/common.h"
#include "src/utility.h"
using namespace std::literals;
namespace platf {
input_t
input() {
return { new input_raw_t() };
input_t input() {
return {new input_raw_t()};
}
std::unique_ptr<client_input_t>
allocate_client_input_context(input_t &input) {
std::unique_ptr<client_input_t> allocate_client_input_context(input_t &input) {
return std::make_unique<client_input_raw_t>(input);
}
void
freeInput(void *p) {
void freeInput(void *p) {
auto *input = (input_raw_t *) p;
delete input;
}
void
move_mouse(input_t &input, int deltaX, int deltaY) {
void move_mouse(input_t &input, int deltaX, int deltaY) {
auto raw = (input_raw_t *) input.get();
platf::mouse::move(raw, deltaX, deltaY);
}
void
abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
auto raw = (input_raw_t *) input.get();
platf::mouse::move_abs(raw, touch_port, x, y);
}
void
button_mouse(input_t &input, int button, bool release) {
void button_mouse(input_t &input, int button, bool release) {
auto raw = (input_raw_t *) input.get();
platf::mouse::button(raw, button, release);
}
void
scroll(input_t &input, int high_res_distance) {
void scroll(input_t &input, int high_res_distance) {
auto raw = (input_raw_t *) input.get();
platf::mouse::scroll(raw, high_res_distance);
}
void
hscroll(input_t &input, int high_res_distance) {
void hscroll(input_t &input, int high_res_distance) {
auto raw = (input_raw_t *) input.get();
platf::mouse::hscroll(raw, high_res_distance);
}
void
keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
auto raw = (input_raw_t *) input.get();
platf::keyboard::update(raw, modcode, release, flags);
}
void
unicode(input_t &input, char *utf8, int size) {
void unicode(input_t &input, char *utf8, int size) {
auto raw = (input_raw_t *) input.get();
platf::keyboard::unicode(raw, utf8, size);
}
void
touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch) {
void touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch) {
auto raw = (client_input_raw_t *) input;
platf::touch::update(raw, touch_port, touch);
}
void
pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen) {
void pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen) {
auto raw = (client_input_raw_t *) input;
platf::pen::update(raw, touch_port, pen);
}
int
alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
auto raw = (input_raw_t *) input.get();
return platf::gamepad::alloc(raw, id, metadata, feedback_queue);
}
void
free_gamepad(input_t &input, int nr) {
void free_gamepad(input_t &input, int nr) {
auto raw = (input_raw_t *) input.get();
platf::gamepad::free(raw, nr);
}
void
gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
void gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
auto raw = (input_raw_t *) input.get();
platf::gamepad::update(raw, nr, gamepad_state);
}
void
gamepad_touch(input_t &input, const gamepad_touch_t &touch) {
void gamepad_touch(input_t &input, const gamepad_touch_t &touch) {
auto raw = (input_raw_t *) input.get();
platf::gamepad::touch(raw, touch);
}
void
gamepad_motion(input_t &input, const gamepad_motion_t &motion) {
void gamepad_motion(input_t &input, const gamepad_motion_t &motion) {
auto raw = (input_raw_t *) input.get();
platf::gamepad::motion(raw, motion);
}
void
gamepad_battery(input_t &input, const gamepad_battery_t &battery) {
void gamepad_battery(input_t &input, const gamepad_battery_t &battery) {
auto raw = (input_raw_t *) input.get();
platf::gamepad::battery(raw, battery);
}
platform_caps::caps_t
get_capabilities() {
platform_caps::caps_t get_capabilities() {
platform_caps::caps_t caps = 0;
// TODO: if has_uinput
caps |= platform_caps::pen_touch;
@@ -140,14 +122,12 @@ namespace platf {
return caps;
}
util::point_t
get_mouse_loc(input_t &input) {
util::point_t get_mouse_loc(input_t &input) {
auto raw = (input_raw_t *) input.get();
return platf::mouse::get_location(raw);
}
std::vector<supported_gamepad_t> &
supported_gamepads(input_t *input) {
std::vector<supported_gamepad_t> &supported_gamepads(input_t *input) {
return platf::gamepad::supported_gamepads(input);
}
} // namespace platf

View File

@@ -4,10 +4,12 @@
*/
#pragma once
// lib includes
#include <boost/locale.hpp>
#include <inputtino/input.hpp>
#include <libevdev/libevdev.h>
// local includes
#include "src/config.h"
#include "src/logging.h"
#include "src/platform/common.h"
@@ -94,8 +96,7 @@ namespace platf {
inputtino::Result<inputtino::PenTablet> pen;
};
inline float
deg2rad(float degree) {
inline float deg2rad(float degree) {
return degree * (M_PI / 180.f);
}
} // namespace platf

View File

@@ -2,18 +2,19 @@
* @file src/platform/linux/input/inputtino_gamepad.cpp
* @brief Definitions for inputtino gamepad input handling.
*/
// lib includes
#include <boost/locale.hpp>
#include <inputtino/input.hpp>
#include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_gamepad.h"
#include "src/config.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/utility.h"
#include "inputtino_common.h"
#include "inputtino_gamepad.h"
using namespace std::literals;
namespace platf::gamepad {
@@ -25,69 +26,54 @@ namespace platf::gamepad {
GAMEPAD_STATUS ///< Helper to indicate the number of status
};
auto
create_xbox_one() {
return inputtino::XboxOneJoypad::create({ .name = "Sunshine X-Box One (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147
.vendor_id = 0x045E,
.product_id = 0x02EA,
.version = 0x0408 });
auto create_xbox_one() {
return inputtino::XboxOneJoypad::create({.name = "Sunshine X-Box One (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147
.vendor_id = 0x045E,
.product_id = 0x02EA,
.version = 0x0408});
}
auto
create_switch() {
return inputtino::SwitchJoypad::create({ .name = "Sunshine Nintendo (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981
.vendor_id = 0x057e,
.product_id = 0x2009,
.version = 0x8111 });
auto create_switch() {
return inputtino::SwitchJoypad::create({.name = "Sunshine Nintendo (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981
.vendor_id = 0x057e,
.product_id = 0x2009,
.version = 0x8111});
}
auto
create_ds5() {
return inputtino::PS5Joypad::create({ .name = "Sunshine DualSense (virtual) pad",
.vendor_id = 0x054C,
.product_id = 0x0CE6,
.version = 0x8111 });
auto create_ds5() {
return inputtino::PS5Joypad::create({.name = "Sunshine DualSense (virtual) pad", .vendor_id = 0x054C, .product_id = 0x0CE6, .version = 0x8111});
}
int
alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
ControllerType selectedGamepadType;
if (config::input.gamepad == "xone"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (manual selection)"sv;
selectedGamepadType = XboxOneWired;
}
else if (config::input.gamepad == "ds5"sv) {
} else if (config::input.gamepad == "ds5"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualSense 5 controller (manual selection)"sv;
selectedGamepadType = DualSenseWired;
}
else if (config::input.gamepad == "switch"sv) {
} else if (config::input.gamepad == "switch"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Nintendo Pro controller (manual selection)"sv;
selectedGamepadType = SwitchProWired;
}
else if (metadata.type == LI_CTYPE_XBOX) {
} else if (metadata.type == LI_CTYPE_XBOX) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (auto-selected by client-reported type)"sv;
selectedGamepadType = XboxOneWired;
}
else if (metadata.type == LI_CTYPE_PS) {
} else if (metadata.type == LI_CTYPE_PS) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by client-reported type)"sv;
selectedGamepadType = DualSenseWired;
}
else if (metadata.type == LI_CTYPE_NINTENDO) {
} else if (metadata.type == LI_CTYPE_NINTENDO) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Nintendo Pro controller (auto-selected by client-reported type)"sv;
selectedGamepadType = SwitchProWired;
}
else if (config::input.motion_as_ds4 && (metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) {
} else if (config::input.motion_as_ds4 && (metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by motion sensor presence)"sv;
selectedGamepadType = DualSenseWired;
}
else if (config::input.touchpad_as_ds4 && (metadata.capabilities & LI_CCAP_TOUCHPAD)) {
} else if (config::input.touchpad_as_ds4 && (metadata.capabilities & LI_CCAP_TOUCHPAD)) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by touchpad presence)"sv;
selectedGamepadType = DualSenseWired;
}
else {
} else {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (default)"sv;
selectedGamepadType = XboxOneWired;
}
@@ -102,8 +88,7 @@ namespace platf::gamepad {
if (metadata.capabilities & LI_CCAP_RGB_LED) {
BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " has an RGB LED, but it is not usable when emulating a joypad different from DS5"sv;
}
}
else if (selectedGamepadType == DualSenseWired) {
} else if (selectedGamepadType == DualSenseWired) {
if (!(metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) {
BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " is emulating a DualShock 5 controller, but the client gamepad doesn't have motion sensors active"sv;
}
@@ -125,73 +110,71 @@ namespace platf::gamepad {
};
switch (selectedGamepadType) {
case XboxOneWired: {
auto xOne = create_xbox_one();
if (xOne) {
(*xOne).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*xOne));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
case XboxOneWired:
{
auto xOne = create_xbox_one();
if (xOne) {
(*xOne).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*xOne));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
} else {
BOOST_LOG(warning) << "Unable to create virtual Xbox One controller: " << xOne.getErrorMessage();
return -1;
}
}
else {
BOOST_LOG(warning) << "Unable to create virtual Xbox One controller: " << xOne.getErrorMessage();
return -1;
case SwitchProWired:
{
auto switchPro = create_switch();
if (switchPro) {
(*switchPro).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*switchPro));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
} else {
BOOST_LOG(warning) << "Unable to create virtual Switch Pro controller: " << switchPro.getErrorMessage();
return -1;
}
}
}
case SwitchProWired: {
auto switchPro = create_switch();
if (switchPro) {
(*switchPro).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*switchPro));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
}
else {
BOOST_LOG(warning) << "Unable to create virtual Switch Pro controller: " << switchPro.getErrorMessage();
return -1;
}
}
case DualSenseWired: {
auto ds5 = create_ds5();
if (ds5) {
(*ds5).set_on_rumble(on_rumble_fn);
(*ds5).set_on_led([feedback_queue, idx = id.clientRelativeIndex, gamepad](int r, int g, int b) {
// Don't resend duplicate LED data
if (gamepad->last_rgb_led.type == platf::gamepad_feedback_e::set_rgb_led && gamepad->last_rgb_led.data.rgb_led.r == r && gamepad->last_rgb_led.data.rgb_led.g == g && gamepad->last_rgb_led.data.rgb_led.b == b) {
return;
}
case DualSenseWired:
{
auto ds5 = create_ds5();
if (ds5) {
(*ds5).set_on_rumble(on_rumble_fn);
(*ds5).set_on_led([feedback_queue, idx = id.clientRelativeIndex, gamepad](int r, int g, int b) {
// Don't resend duplicate LED data
if (gamepad->last_rgb_led.type == platf::gamepad_feedback_e::set_rgb_led && gamepad->last_rgb_led.data.rgb_led.r == r && gamepad->last_rgb_led.data.rgb_led.g == g && gamepad->last_rgb_led.data.rgb_led.b == b) {
return;
}
auto msg = gamepad_feedback_msg_t::make_rgb_led(idx, r, g, b);
feedback_queue->raise(msg);
gamepad->last_rgb_led = msg;
});
auto msg = gamepad_feedback_msg_t::make_rgb_led(idx, r, g, b);
feedback_queue->raise(msg);
gamepad->last_rgb_led = msg;
});
// Activate the motion sensors
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_ACCEL, 100));
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_GYRO, 100));
// Activate the motion sensors
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_ACCEL, 100));
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_GYRO, 100));
gamepad->joypad = std::make_unique<joypads_t>(std::move(*ds5));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
gamepad->joypad = std::make_unique<joypads_t>(std::move(*ds5));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
} else {
BOOST_LOG(warning) << "Unable to create virtual DualShock 5 controller: " << ds5.getErrorMessage();
return -1;
}
}
else {
BOOST_LOG(warning) << "Unable to create virtual DualShock 5 controller: " << ds5.getErrorMessage();
return -1;
}
}
}
return -1;
}
void
free(input_raw_t *raw, int nr) {
void free(input_raw_t *raw, int nr) {
// This will call the destructor which in turn will stop the background threads for rumble and LED (and ultimately remove the joypad device)
raw->gamepads[nr]->joypad.reset();
raw->gamepads[nr].reset();
}
void
update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state) {
void update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state) {
auto gamepad = raw->gamepads[nr];
if (!gamepad) {
return;
@@ -203,11 +186,10 @@ namespace platf::gamepad {
gc.set_stick(inputtino::Joypad::RS, gamepad_state.rsX, gamepad_state.rsY);
gc.set_triggers(gamepad_state.lt, gamepad_state.rt);
},
*gamepad->joypad);
*gamepad->joypad);
}
void
touch(input_raw_t *raw, const gamepad_touch_t &touch) {
void touch(input_raw_t *raw, const gamepad_touch_t &touch) {
auto gamepad = raw->gamepads[touch.id.globalIndex];
if (!gamepad) {
return;
@@ -216,15 +198,13 @@ namespace platf::gamepad {
if (std::holds_alternative<inputtino::PS5Joypad>(*gamepad->joypad)) {
if (touch.pressure > 0.5) {
std::get<inputtino::PS5Joypad>(*gamepad->joypad).place_finger(touch.pointerId, touch.x * inputtino::PS5Joypad::touchpad_width, touch.y * inputtino::PS5Joypad::touchpad_height);
}
else {
} else {
std::get<inputtino::PS5Joypad>(*gamepad->joypad).release_finger(touch.pointerId);
}
}
}
void
motion(input_raw_t *raw, const gamepad_motion_t &motion) {
void motion(input_raw_t *raw, const gamepad_motion_t &motion) {
auto gamepad = raw->gamepads[motion.id.globalIndex];
if (!gamepad) {
return;
@@ -242,8 +222,7 @@ namespace platf::gamepad {
}
}
void
battery(input_raw_t *raw, const gamepad_battery_t &battery) {
void battery(input_raw_t *raw, const gamepad_battery_t &battery) {
auto gamepad = raw->gamepads[battery.id.globalIndex];
if (!gamepad) {
return;
@@ -272,14 +251,13 @@ namespace platf::gamepad {
}
}
std::vector<supported_gamepad_t> &
supported_gamepads(input_t *input) {
std::vector<supported_gamepad_t> &supported_gamepads(input_t *input) {
if (!input) {
static std::vector gps {
supported_gamepad_t { "auto", true, "" },
supported_gamepad_t { "xone", false, "" },
supported_gamepad_t { "ds5", false, "" },
supported_gamepad_t { "switch", false, "" },
supported_gamepad_t {"auto", true, ""},
supported_gamepad_t {"xone", false, ""},
supported_gamepad_t {"ds5", false, ""},
supported_gamepad_t {"switch", false, ""},
};
return gps;
@@ -290,10 +268,10 @@ namespace platf::gamepad {
auto xOne = create_xbox_one();
static std::vector gps {
supported_gamepad_t { "auto", true, "" },
supported_gamepad_t { "xone", static_cast<bool>(xOne), !xOne ? xOne.getErrorMessage() : "" },
supported_gamepad_t { "ds5", static_cast<bool>(ds5), !ds5 ? ds5.getErrorMessage() : "" },
supported_gamepad_t { "switch", static_cast<bool>(switchPro), !switchPro ? switchPro.getErrorMessage() : "" },
supported_gamepad_t {"auto", true, ""},
supported_gamepad_t {"xone", static_cast<bool>(xOne), !xOne ? xOne.getErrorMessage() : ""},
supported_gamepad_t {"ds5", static_cast<bool>(ds5), !ds5 ? ds5.getErrorMessage() : ""},
supported_gamepad_t {"switch", static_cast<bool>(switchPro), !switchPro ? switchPro.getErrorMessage() : ""},
};
for (auto &[name, is_enabled, reason_disabled] : gps) {

View File

@@ -4,13 +4,14 @@
*/
#pragma once
// lib includes
#include <boost/locale.hpp>
#include <inputtino/input.hpp>
#include <libevdev/libevdev.h>
#include "src/platform/common.h"
// local includes
#include "inputtino_common.h"
#include "src/platform/common.h"
using namespace std::literals;
@@ -22,24 +23,17 @@ namespace platf::gamepad {
SwitchProWired ///< Switch Pro Wired Controller
};
int
alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue);
int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue);
void
free(input_raw_t *raw, int nr);
void free(input_raw_t *raw, int nr);
void
update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state);
void update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state);
void
touch(input_raw_t *raw, const gamepad_touch_t &touch);
void touch(input_raw_t *raw, const gamepad_touch_t &touch);
void
motion(input_raw_t *raw, const gamepad_motion_t &motion);
void motion(input_raw_t *raw, const gamepad_motion_t &motion);
void
battery(input_raw_t *raw, const gamepad_battery_t &battery);
void battery(input_raw_t *raw, const gamepad_battery_t &battery);
std::vector<supported_gamepad_t> &
supported_gamepads(input_t *input);
std::vector<supported_gamepad_t> &supported_gamepads(input_t *input);
} // namespace platf::gamepad

View File

@@ -2,18 +2,19 @@
* @file src/platform/linux/input/inputtino_keyboard.cpp
* @brief Definitions for inputtino keyboard input handling.
*/
// lib includes
#include <boost/locale.hpp>
#include <inputtino/input.hpp>
#include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_keyboard.h"
#include "src/config.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/utility.h"
#include "inputtino_common.h"
#include "inputtino_keyboard.h"
using namespace std::literals;
namespace platf::keyboard {
@@ -25,8 +26,7 @@ namespace platf::keyboard {
*
* adapted from: https://stackoverflow.com/a/7639754
*/
std::string
to_hex(const std::basic_string<char32_t> &str) {
std::string to_hex(const std::basic_string<char32_t> &str) {
std::stringstream ss;
ss << std::hex << std::setfill('0');
for (const auto &ch : str) {
@@ -42,48 +42,123 @@ namespace platf::keyboard {
* A map of linux scan code -> Moonlight keyboard code
*/
static const std::map<short, short> key_mappings = {
{ KEY_BACKSPACE, 0x08 }, { KEY_TAB, 0x09 }, { KEY_ENTER, 0x0D }, { KEY_LEFTSHIFT, 0x10 },
{ KEY_LEFTCTRL, 0x11 }, { KEY_CAPSLOCK, 0x14 }, { KEY_ESC, 0x1B }, { KEY_SPACE, 0x20 },
{ KEY_PAGEUP, 0x21 }, { KEY_PAGEDOWN, 0x22 }, { KEY_END, 0x23 }, { KEY_HOME, 0x24 },
{ KEY_LEFT, 0x25 }, { KEY_UP, 0x26 }, { KEY_RIGHT, 0x27 }, { KEY_DOWN, 0x28 },
{ KEY_SYSRQ, 0x2C }, { KEY_INSERT, 0x2D }, { KEY_DELETE, 0x2E }, { KEY_0, 0x30 },
{ KEY_1, 0x31 }, { KEY_2, 0x32 }, { KEY_3, 0x33 }, { KEY_4, 0x34 },
{ KEY_5, 0x35 }, { KEY_6, 0x36 }, { KEY_7, 0x37 }, { KEY_8, 0x38 },
{ KEY_9, 0x39 }, { KEY_A, 0x41 }, { KEY_B, 0x42 }, { KEY_C, 0x43 },
{ KEY_D, 0x44 }, { KEY_E, 0x45 }, { KEY_F, 0x46 }, { KEY_G, 0x47 },
{ KEY_H, 0x48 }, { KEY_I, 0x49 }, { KEY_J, 0x4A }, { KEY_K, 0x4B },
{ KEY_L, 0x4C }, { KEY_M, 0x4D }, { KEY_N, 0x4E }, { KEY_O, 0x4F },
{ KEY_P, 0x50 }, { KEY_Q, 0x51 }, { KEY_R, 0x52 }, { KEY_S, 0x53 },
{ KEY_T, 0x54 }, { KEY_U, 0x55 }, { KEY_V, 0x56 }, { KEY_W, 0x57 },
{ KEY_X, 0x58 }, { KEY_Y, 0x59 }, { KEY_Z, 0x5A }, { KEY_LEFTMETA, 0x5B },
{ KEY_RIGHTMETA, 0x5C }, { KEY_KP0, 0x60 }, { KEY_KP1, 0x61 }, { KEY_KP2, 0x62 },
{ KEY_KP3, 0x63 }, { KEY_KP4, 0x64 }, { KEY_KP5, 0x65 }, { KEY_KP6, 0x66 },
{ KEY_KP7, 0x67 }, { KEY_KP8, 0x68 }, { KEY_KP9, 0x69 }, { KEY_KPASTERISK, 0x6A },
{ KEY_KPPLUS, 0x6B }, { KEY_KPMINUS, 0x6D }, { KEY_KPDOT, 0x6E }, { KEY_KPSLASH, 0x6F },
{ KEY_F1, 0x70 }, { KEY_F2, 0x71 }, { KEY_F3, 0x72 }, { KEY_F4, 0x73 },
{ KEY_F5, 0x74 }, { KEY_F6, 0x75 }, { KEY_F7, 0x76 }, { KEY_F8, 0x77 },
{ KEY_F9, 0x78 }, { KEY_F10, 0x79 }, { KEY_F11, 0x7A }, { KEY_F12, 0x7B },
{ KEY_NUMLOCK, 0x90 }, { KEY_SCROLLLOCK, 0x91 }, { KEY_LEFTSHIFT, 0xA0 }, { KEY_RIGHTSHIFT, 0xA1 },
{ KEY_LEFTCTRL, 0xA2 }, { KEY_RIGHTCTRL, 0xA3 }, { KEY_LEFTALT, 0xA4 }, { KEY_RIGHTALT, 0xA5 },
{ KEY_SEMICOLON, 0xBA }, { KEY_EQUAL, 0xBB }, { KEY_COMMA, 0xBC }, { KEY_MINUS, 0xBD },
{ KEY_DOT, 0xBE }, { KEY_SLASH, 0xBF }, { KEY_GRAVE, 0xC0 }, { KEY_LEFTBRACE, 0xDB },
{ KEY_BACKSLASH, 0xDC }, { KEY_RIGHTBRACE, 0xDD }, { KEY_APOSTROPHE, 0xDE }, { KEY_102ND, 0xE2 }
{KEY_BACKSPACE, 0x08},
{KEY_TAB, 0x09},
{KEY_ENTER, 0x0D},
{KEY_LEFTSHIFT, 0x10},
{KEY_LEFTCTRL, 0x11},
{KEY_CAPSLOCK, 0x14},
{KEY_ESC, 0x1B},
{KEY_SPACE, 0x20},
{KEY_PAGEUP, 0x21},
{KEY_PAGEDOWN, 0x22},
{KEY_END, 0x23},
{KEY_HOME, 0x24},
{KEY_LEFT, 0x25},
{KEY_UP, 0x26},
{KEY_RIGHT, 0x27},
{KEY_DOWN, 0x28},
{KEY_SYSRQ, 0x2C},
{KEY_INSERT, 0x2D},
{KEY_DELETE, 0x2E},
{KEY_0, 0x30},
{KEY_1, 0x31},
{KEY_2, 0x32},
{KEY_3, 0x33},
{KEY_4, 0x34},
{KEY_5, 0x35},
{KEY_6, 0x36},
{KEY_7, 0x37},
{KEY_8, 0x38},
{KEY_9, 0x39},
{KEY_A, 0x41},
{KEY_B, 0x42},
{KEY_C, 0x43},
{KEY_D, 0x44},
{KEY_E, 0x45},
{KEY_F, 0x46},
{KEY_G, 0x47},
{KEY_H, 0x48},
{KEY_I, 0x49},
{KEY_J, 0x4A},
{KEY_K, 0x4B},
{KEY_L, 0x4C},
{KEY_M, 0x4D},
{KEY_N, 0x4E},
{KEY_O, 0x4F},
{KEY_P, 0x50},
{KEY_Q, 0x51},
{KEY_R, 0x52},
{KEY_S, 0x53},
{KEY_T, 0x54},
{KEY_U, 0x55},
{KEY_V, 0x56},
{KEY_W, 0x57},
{KEY_X, 0x58},
{KEY_Y, 0x59},
{KEY_Z, 0x5A},
{KEY_LEFTMETA, 0x5B},
{KEY_RIGHTMETA, 0x5C},
{KEY_KP0, 0x60},
{KEY_KP1, 0x61},
{KEY_KP2, 0x62},
{KEY_KP3, 0x63},
{KEY_KP4, 0x64},
{KEY_KP5, 0x65},
{KEY_KP6, 0x66},
{KEY_KP7, 0x67},
{KEY_KP8, 0x68},
{KEY_KP9, 0x69},
{KEY_KPASTERISK, 0x6A},
{KEY_KPPLUS, 0x6B},
{KEY_KPMINUS, 0x6D},
{KEY_KPDOT, 0x6E},
{KEY_KPSLASH, 0x6F},
{KEY_F1, 0x70},
{KEY_F2, 0x71},
{KEY_F3, 0x72},
{KEY_F4, 0x73},
{KEY_F5, 0x74},
{KEY_F6, 0x75},
{KEY_F7, 0x76},
{KEY_F8, 0x77},
{KEY_F9, 0x78},
{KEY_F10, 0x79},
{KEY_F11, 0x7A},
{KEY_F12, 0x7B},
{KEY_NUMLOCK, 0x90},
{KEY_SCROLLLOCK, 0x91},
{KEY_LEFTSHIFT, 0xA0},
{KEY_RIGHTSHIFT, 0xA1},
{KEY_LEFTCTRL, 0xA2},
{KEY_RIGHTCTRL, 0xA3},
{KEY_LEFTALT, 0xA4},
{KEY_RIGHTALT, 0xA5},
{KEY_SEMICOLON, 0xBA},
{KEY_EQUAL, 0xBB},
{KEY_COMMA, 0xBC},
{KEY_MINUS, 0xBD},
{KEY_DOT, 0xBE},
{KEY_SLASH, 0xBF},
{KEY_GRAVE, 0xC0},
{KEY_LEFTBRACE, 0xDB},
{KEY_BACKSLASH, 0xDC},
{KEY_RIGHTBRACE, 0xDD},
{KEY_APOSTROPHE, 0xDE},
{KEY_102ND, 0xE2}
};
void
update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags) {
void update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags) {
if (raw->keyboard) {
if (release) {
(*raw->keyboard).release(modcode);
}
else {
} else {
(*raw->keyboard).press(modcode);
}
}
}
void
unicode(input_raw_t *raw, char *utf8, int size) {
void unicode(input_raw_t *raw, char *utf8, int size) {
if (raw->keyboard) {
/* Reading input text as UTF-8 */
auto utf8_str = boost::locale::conv::to_utf<wchar_t>(utf8, utf8 + size, "UTF-8");
@@ -106,8 +181,7 @@ namespace platf::keyboard {
auto wincode = key_mappings.find(keycode);
if (keycode == -1 || wincode == key_mappings.end()) {
BOOST_LOG(warning) << "Unicode, unable to find keycode for: "sv << ch;
}
else {
} else {
(*raw->keyboard).press(wincode->second);
(*raw->keyboard).release(wincode->second);
}

View File

@@ -4,18 +4,18 @@
*/
#pragma once
// lib includes
#include <boost/locale.hpp>
#include <inputtino/input.hpp>
#include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
using namespace std::literals;
namespace platf::keyboard {
void
update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags);
void update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags);
void
unicode(input_raw_t *raw, char *utf8, int size);
void unicode(input_raw_t *raw, char *utf8, int size);
} // namespace platf::keyboard

View File

@@ -2,38 +2,36 @@
* @file src/platform/linux/input/inputtino_mouse.cpp
* @brief Definitions for inputtino mouse input handling.
*/
// lib includes
#include <boost/locale.hpp>
#include <inputtino/input.hpp>
#include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_mouse.h"
#include "src/config.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/utility.h"
#include "inputtino_common.h"
#include "inputtino_mouse.h"
using namespace std::literals;
namespace platf::mouse {
void
move(input_raw_t *raw, int deltaX, int deltaY) {
void move(input_raw_t *raw, int deltaX, int deltaY) {
if (raw->mouse) {
(*raw->mouse).move(deltaX, deltaY);
}
}
void
move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y) {
void move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y) {
if (raw->mouse) {
(*raw->mouse).move_abs(x, y, touch_port.width, touch_port.height);
}
}
void
button(input_raw_t *raw, int button, bool release) {
void button(input_raw_t *raw, int button, bool release) {
if (raw->mouse) {
inputtino::Mouse::MOUSE_BUTTON btn_type;
switch (button) {
@@ -58,35 +56,31 @@ namespace platf::mouse {
}
if (release) {
(*raw->mouse).release(btn_type);
}
else {
} else {
(*raw->mouse).press(btn_type);
}
}
}
void
scroll(input_raw_t *raw, int high_res_distance) {
void scroll(input_raw_t *raw, int high_res_distance) {
if (raw->mouse) {
(*raw->mouse).vertical_scroll(high_res_distance);
}
}
void
hscroll(input_raw_t *raw, int high_res_distance) {
void hscroll(input_raw_t *raw, int high_res_distance) {
if (raw->mouse) {
(*raw->mouse).horizontal_scroll(high_res_distance);
}
}
util::point_t
get_location(input_raw_t *raw) {
util::point_t get_location(input_raw_t *raw) {
if (raw->mouse) {
// TODO: decide what to do after https://github.com/games-on-whales/inputtino/issues/6 is resolved.
// TODO: auto x = (*raw->mouse).get_absolute_x();
// TODO: auto y = (*raw->mouse).get_absolute_y();
return { 0, 0 };
return {0, 0};
}
return { 0, 0 };
return {0, 0};
}
} // namespace platf::mouse

View File

@@ -3,33 +3,27 @@
* @brief Declarations for inputtino mouse input handling.
*/
#pragma once
// lib includes
#include <boost/locale.hpp>
#include <inputtino/input.hpp>
#include <libevdev/libevdev.h>
#include "src/platform/common.h"
// local includes
#include "inputtino_common.h"
#include "src/platform/common.h"
using namespace std::literals;
namespace platf::mouse {
void
move(input_raw_t *raw, int deltaX, int deltaY);
void move(input_raw_t *raw, int deltaX, int deltaY);
void
move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y);
void move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y);
void
button(input_raw_t *raw, int button, bool release);
void button(input_raw_t *raw, int button, bool release);
void
scroll(input_raw_t *raw, int high_res_distance);
void scroll(input_raw_t *raw, int high_res_distance);
void
hscroll(input_raw_t *raw, int high_res_distance);
void hscroll(input_raw_t *raw, int high_res_distance);
util::point_t
get_location(input_raw_t *raw);
util::point_t get_location(input_raw_t *raw);
} // namespace platf::mouse

View File

@@ -2,23 +2,23 @@
* @file src/platform/linux/input/inputtino_pen.cpp
* @brief Definitions for inputtino pen input handling.
*/
// lib includes
#include <boost/locale.hpp>
#include <inputtino/input.hpp>
#include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_pen.h"
#include "src/config.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/utility.h"
#include "inputtino_common.h"
#include "inputtino_pen.h"
using namespace std::literals;
namespace platf::pen {
void
update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen) {
void update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen) {
if (raw->pen) {
// First set the buttons
(*raw->pen).set_btn(inputtino::PenTablet::PRIMARY, pen.penButtons & LI_PEN_BUTTON_PRIMARY);
@@ -63,13 +63,7 @@ namespace platf::pen {
bool is_touching = pen.eventType == LI_TOUCH_EVENT_DOWN || pen.eventType == LI_TOUCH_EVENT_MOVE;
(*raw->pen).place_tool(tool,
pen.x,
pen.y,
is_touching ? pen.pressureOrDistance : -1,
is_touching ? -1 : pen.pressureOrDistance,
tilt_x,
tilt_y);
(*raw->pen).place_tool(tool, pen.x, pen.y, is_touching ? pen.pressureOrDistance : -1, is_touching ? -1 : pen.pressureOrDistance, tilt_x, tilt_y);
}
}
} // namespace platf::pen

View File

@@ -4,17 +4,17 @@
*/
#pragma once
// lib includes
#include <boost/locale.hpp>
#include <inputtino/input.hpp>
#include <libevdev/libevdev.h>
#include "src/platform/common.h"
// local includes
#include "inputtino_common.h"
#include "src/platform/common.h"
using namespace std::literals;
namespace platf::pen {
void
update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen);
void update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen);
}

View File

@@ -2,52 +2,53 @@
* @file src/platform/linux/input/inputtino_touch.cpp
* @brief Definitions for inputtino touch input handling.
*/
// lib includes
#include <boost/locale.hpp>
#include <inputtino/input.hpp>
#include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_touch.h"
#include "src/config.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/utility.h"
#include "inputtino_common.h"
#include "inputtino_touch.h"
using namespace std::literals;
namespace platf::touch {
void
update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch) {
void update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch) {
if (raw->touch) {
switch (touch.eventType) {
case LI_TOUCH_EVENT_HOVER:
case LI_TOUCH_EVENT_DOWN:
case LI_TOUCH_EVENT_MOVE: {
// Convert our 0..360 range to -90..90 relative to Y axis
int adjusted_angle = touch.rotation;
case LI_TOUCH_EVENT_MOVE:
{
// Convert our 0..360 range to -90..90 relative to Y axis
int adjusted_angle = touch.rotation;
if (adjusted_angle > 90 && adjusted_angle < 270) {
// Lower hemisphere
adjusted_angle = 180 - adjusted_angle;
}
if (adjusted_angle > 90 && adjusted_angle < 270) {
// Lower hemisphere
adjusted_angle = 180 - adjusted_angle;
}
// Wrap the value if it's out of range
if (adjusted_angle > 90) {
adjusted_angle -= 360;
// Wrap the value if it's out of range
if (adjusted_angle > 90) {
adjusted_angle -= 360;
} else if (adjusted_angle < -90) {
adjusted_angle += 360;
}
(*raw->touch).place_finger(touch.pointerId, touch.x, touch.y, touch.pressureOrDistance, adjusted_angle);
break;
}
else if (adjusted_angle < -90) {
adjusted_angle += 360;
}
(*raw->touch).place_finger(touch.pointerId, touch.x, touch.y, touch.pressureOrDistance, adjusted_angle);
break;
}
case LI_TOUCH_EVENT_CANCEL:
case LI_TOUCH_EVENT_UP:
case LI_TOUCH_EVENT_HOVER_LEAVE: {
(*raw->touch).release_finger(touch.pointerId);
break;
}
case LI_TOUCH_EVENT_HOVER_LEAVE:
{
(*raw->touch).release_finger(touch.pointerId);
break;
}
// TODO: LI_TOUCH_EVENT_CANCEL_ALL
}
}

View File

@@ -4,17 +4,17 @@
*/
#pragma once
// lib includes
#include <boost/locale.hpp>
#include <inputtino/input.hpp>
#include <libevdev/libevdev.h>
#include "src/platform/common.h"
// local includes
#include "inputtino_common.h"
#include "src/platform/common.h"
using namespace std::literals;
namespace platf::touch {
void
update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch);
void update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch);
}

Some files were not shown because too many files have changed in this diff Show More