mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-18 15:00:46 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40698efb7d | ||
|
|
3d2a2b3d98 | ||
|
|
78a314b761 | ||
|
|
2d1dd3788b | ||
|
|
56d3160641 | ||
|
|
5db99041bc | ||
|
|
9ec56338c3 | ||
|
|
894ebf8eb7 | ||
|
|
6bec8a6c5c | ||
|
|
b9e7bc761b | ||
|
|
b54c896498 | ||
|
|
1cd7b7db8f | ||
|
|
f87aaca2fb | ||
|
|
901ba615ab | ||
|
|
cdbf9ac1eb | ||
|
|
f098bc1b5d | ||
|
|
e1f7c8c06c | ||
|
|
f383fbbff4 |
No files matched your search
@@ -64,3 +64,4 @@ jobs:
|
||||
bash tests/managed_payload_build_contract_test.sh .
|
||||
bash tests/flatpak_packaging_test.sh .
|
||||
bash tests/native_packaging_test.sh .
|
||||
bash tests/anylinux_dependencies_test.sh .
|
||||
@@ -160,6 +160,9 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Download Flatpak
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
@@ -174,13 +177,16 @@ jobs:
|
||||
set -Eeuo pipefail
|
||||
tag="${GITHUB_REF_NAME}"
|
||||
(cd release && sha256sum Nightcap-x86_64.flatpak > Nightcap-x86_64.flatpak.sha256)
|
||||
./scripts/release_notes.sh "$tag" > release/notes.md
|
||||
# The Release workflow creates the same release; whichever job
|
||||
# arrives second sees "already exists" and continues.
|
||||
if ! gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
gh release create "$tag" --repo "$GITHUB_REPOSITORY" \
|
||||
--title "Nightcap $tag" --generate-notes ||
|
||||
--title "Nightcap $tag" --notes-file release/notes.md ||
|
||||
gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null
|
||||
fi
|
||||
gh release edit "$tag" --repo "$GITHUB_REPOSITORY" \
|
||||
--title "Nightcap $tag" --notes-file release/notes.md
|
||||
gh release upload "$tag" --repo "$GITHUB_REPOSITORY" --clobber \
|
||||
release/Nightcap-x86_64.flatpak \
|
||||
release/Nightcap-x86_64.flatpak.sha256
|
||||
+58
-220
@@ -1,39 +1,47 @@
|
||||
name: Native packages
|
||||
name: Nightly AppImage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- '.github/workflows/ci.yml'
|
||||
- '.gitignore'
|
||||
- 'README.md'
|
||||
- 'assets/screenshots/**'
|
||||
- 'docs/**'
|
||||
- 'packaging/aur/**'
|
||||
- 'scripts/build_flatpak.sh'
|
||||
- 'tests/**'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '.github/workflows/ci.yml'
|
||||
- '.gitignore'
|
||||
- 'README.md'
|
||||
- 'assets/screenshots/**'
|
||||
- 'docs/**'
|
||||
- 'packaging/aur/**'
|
||||
- 'scripts/build_flatpak.sh'
|
||||
- 'tests/**'
|
||||
schedule:
|
||||
# 05:00 UTC daily. A run whose commit already carries the continuous tag
|
||||
# exits without rebuilding, so a quiet day costs one probe job.
|
||||
- cron: '0 5 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: packages-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
group: nightly-appimage
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
probe:
|
||||
name: Check for new commits
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
build: ${{ steps.decide.outputs.build }}
|
||||
steps:
|
||||
- name: Compare HEAD against the published continuous tag
|
||||
id: decide
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -Eeuo pipefail
|
||||
published="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/continuous" \
|
||||
--jq .object.sha 2>/dev/null || true)"
|
||||
if [[ "$GITHUB_EVENT_NAME" == "schedule" && "$published" == "$GITHUB_SHA" ]]; then
|
||||
echo 'build=false' >>"$GITHUB_OUTPUT"
|
||||
echo "continuous already points at $GITHUB_SHA; nothing to build"
|
||||
else
|
||||
echo 'build=true' >>"$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
appimage:
|
||||
name: AppImage (AnyLinux)
|
||||
needs: probe
|
||||
if: needs.probe.outputs.build == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
@@ -45,10 +53,13 @@ jobs:
|
||||
|
||||
- name: Build AppImage
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
docker run --rm \
|
||||
--volume "$GITHUB_WORKSPACE:/workspace" \
|
||||
--workdir /workspace \
|
||||
--env GH_TOKEN \
|
||||
ghcr.io/archlinux/archlinux:base-devel@sha256:c3953446016873a756fb7814f10087cac1634c127f7302e80c81c512da796d05 bash -Eeuo pipefail -c '
|
||||
pacman -Syu --noconfirm --needed \
|
||||
cmake git ninja pkgconf lld sdl3 sdl3_ttf curl openssl \
|
||||
@@ -135,155 +146,17 @@ jobs:
|
||||
- name: Upload AppImage
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Mocktail-AppImage
|
||||
name: Nightcap-AppImage
|
||||
path: dist/Mocktail-x86_64.AppImage
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
deb:
|
||||
name: DEB (Ubuntu 26.04)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Build DEB
|
||||
shell: bash
|
||||
run: |
|
||||
nightly_suffix=".nightly.$(git show -s --format=%cd --date=format:%Y%m%d%H%M%S "$GITHUB_SHA").g${GITHUB_SHA:0:8}"
|
||||
docker run --rm \
|
||||
--volume "$GITHUB_WORKSPACE:/workspace" \
|
||||
--workdir /workspace \
|
||||
--env MOCKTAIL_PACKAGE_VERSION_SUFFIX="$nightly_suffix" \
|
||||
ubuntu:26.04 bash -Eeuo pipefail -c '
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install --yes --no-install-recommends \
|
||||
build-essential cmake git ninja-build pkg-config lld fakeroot \
|
||||
dpkg-dev file libsdl3-dev libsdl3-ttf-dev \
|
||||
libcurl4-openssl-dev libssl-dev nlohmann-json3-dev \
|
||||
libyaml-dev libelf-dev libminizip-dev libcapstone-dev \
|
||||
libgtk-4-dev libadwaita-1-dev libwebkitgtk-6.0-dev \
|
||||
libutf8proc-dev libfontconfig1-dev libegl-dev \
|
||||
libvulkan-dev libplacebo-dev zlib1g-dev
|
||||
cmake -S . -B build-deb -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DMOCKTAIL_PACKAGE_NAME=mocktail-nightly \
|
||||
-DMOCKTAIL_PACKAGE_VERSION_SUFFIX="$MOCKTAIL_PACKAGE_VERSION_SUFFIX" \
|
||||
-DBUILD_TESTING=OFF
|
||||
cmake --build build-deb --parallel 4
|
||||
cpack --config build-deb/CPackConfig.cmake \
|
||||
-G DEB -B dist/deb
|
||||
dpkg-deb --info dist/deb/*.deb >/dev/null
|
||||
'
|
||||
|
||||
- name: Upload DEB
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Mocktail-DEB
|
||||
path: dist/deb/*.deb
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
rpm:
|
||||
name: RPM (Fedora 44)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Build RPM
|
||||
shell: bash
|
||||
run: |
|
||||
nightly_suffix=".nightly.$(git show -s --format=%cd --date=format:%Y%m%d%H%M%S "$GITHUB_SHA").g${GITHUB_SHA:0:8}"
|
||||
docker run --rm \
|
||||
--volume "$GITHUB_WORKSPACE:/workspace" \
|
||||
--workdir /workspace \
|
||||
--env MOCKTAIL_PACKAGE_VERSION_SUFFIX="$nightly_suffix" \
|
||||
fedora:44 bash -Eeuo pipefail -c '
|
||||
dnf install --assumeyes \
|
||||
gcc-c++ cmake git ninja-build pkgconf-pkg-config lld rpm-build \
|
||||
SDL3-devel SDL3_ttf-devel libcurl-devel openssl-devel \
|
||||
nlohmann-json-devel libyaml-devel elfutils-libelf-devel \
|
||||
minizip-ng-compat-devel capstone-devel gtk4-devel libadwaita-devel \
|
||||
webkitgtk6.0-devel utf8proc-devel fontconfig-devel \
|
||||
libglvnd-devel vulkan-headers vulkan-loader-devel \
|
||||
libplacebo-devel zlib-ng-compat-devel
|
||||
cmake -S . -B build-rpm -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DCMAKE_INSTALL_LIBDIR=lib \
|
||||
-DMOCKTAIL_PACKAGE_NAME=mocktail-nightly \
|
||||
-DMOCKTAIL_PACKAGE_VERSION_SUFFIX="$MOCKTAIL_PACKAGE_VERSION_SUFFIX" \
|
||||
-DBUILD_TESTING=OFF
|
||||
cmake --build build-rpm --parallel 4
|
||||
cpack --config build-rpm/CPackConfig.cmake \
|
||||
-G RPM -B dist/rpm
|
||||
rpm --query --package --info dist/rpm/*.rpm >/dev/null
|
||||
'
|
||||
|
||||
- name: Upload RPM
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Mocktail-RPM
|
||||
path: dist/rpm/*.rpm
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
arch:
|
||||
name: Pacman package (Arch Linux)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Build pacman package
|
||||
shell: bash
|
||||
run: |
|
||||
docker run --rm \
|
||||
--volume "$GITHUB_WORKSPACE:/workspace" \
|
||||
--workdir /workspace \
|
||||
archlinux:base-devel bash -Eeuo pipefail -c '
|
||||
pacman -Syu --needed --noconfirm \
|
||||
cmake git ninja pkgconf lld sdl3 sdl3_ttf curl openssl \
|
||||
nlohmann-json libyaml libelf minizip capstone gtk4 \
|
||||
libadwaita webkitgtk-6.0 libutf8proc fontconfig \
|
||||
libplacebo vulkan-headers vulkan-icd-loader libglvnd zlib
|
||||
useradd --create-home builder
|
||||
chown -R builder:builder /workspace
|
||||
runuser -u builder -- env MOCKTAIL_SOURCE_DIR=/workspace \
|
||||
makepkg --dir /workspace/packaging/arch \
|
||||
--noconfirm --cleanbuild --force
|
||||
install -d dist/arch
|
||||
cp packaging/arch/*.pkg.tar.zst dist/arch/
|
||||
pacman -Qp dist/arch/*.pkg.tar.zst >/dev/null
|
||||
'
|
||||
|
||||
- name: Upload pacman package
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Mocktail-Arch
|
||||
path: dist/arch/*.pkg.tar.zst
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
publish:
|
||||
name: Publish continuous packages
|
||||
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||
needs: [appimage, deb, rpm, arch]
|
||||
name: Publish continuous release
|
||||
needs: [probe, appimage]
|
||||
# A manual run from another branch still builds and smoke-tests, but the
|
||||
# continuous tag and release only ever follow main.
|
||||
if: needs.probe.outputs.build == 'true' && github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
@@ -294,84 +167,49 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download packages
|
||||
- name: Download AppImage
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: Mocktail-*
|
||||
name: Nightcap-AppImage
|
||||
path: release
|
||||
merge-multiple: true
|
||||
|
||||
- name: Prepare continuous packages
|
||||
- name: Name and checksum the nightly build
|
||||
shell: bash
|
||||
run: |
|
||||
set -Eeuo pipefail
|
||||
mv -- release/*.AppImage release/mocktail-nightly.AppImage
|
||||
mv -- release/*.deb release/mocktail-nightly.deb
|
||||
mv -- release/*.rpm release/mocktail-nightly.rpm
|
||||
mv -- release/*.pkg.tar.zst release/mocktail-nightly.pkg.tar.zst
|
||||
# The Makefile builds under upstream's artifact name; the published
|
||||
# asset carries this fork's.
|
||||
mv -- release/Mocktail-x86_64.AppImage release/Nightcap-nightly-x86_64.AppImage
|
||||
(cd release && sha256sum Nightcap-nightly-x86_64.AppImage \
|
||||
> Nightcap-nightly-x86_64.AppImage.sha256)
|
||||
find release -maxdepth 1 -type f -print
|
||||
|
||||
- name: Build package repositories
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
FLATPAK_GPG_PRIVATE_KEY: ${{ secrets.FLATPAK_GPG_PRIVATE_KEY }}
|
||||
FLATPAK_GPG_KEY_ID: ${{ secrets.FLATPAK_GPG_KEY_ID }}
|
||||
run: |
|
||||
set -Eeuo pipefail
|
||||
[[ -n "$FLATPAK_GPG_PRIVATE_KEY" && -n "$FLATPAK_GPG_KEY_ID" ]]
|
||||
sudo apt-get update
|
||||
sudo apt-get install --yes --no-install-recommends \
|
||||
apt-utils createrepo-c gnupg rpm
|
||||
install -d stable/deb stable/rpm
|
||||
stable_tag="$(gh api "repos/$GITHUB_REPOSITORY/releases/latest" --jq .tag_name)"
|
||||
gh release download "$stable_tag" --pattern '*.deb' --dir stable/deb
|
||||
gh release download "$stable_tag" --pattern '*.rpm' --dir stable/rpm
|
||||
mapfile -t stable_debs < <(find stable/deb -maxdepth 1 -type f -name '*.deb' -print)
|
||||
mapfile -t stable_rpms < <(find stable/rpm -maxdepth 1 -type f -name '*.rpm' -print)
|
||||
[[ "${#stable_debs[@]}" -eq 1 && "${#stable_rpms[@]}" -eq 1 ]]
|
||||
install -d -m 0700 "$RUNNER_TEMP/mocktail-native-gnupg"
|
||||
printf '%s' "$FLATPAK_GPG_PRIVATE_KEY" | \
|
||||
gpg --batch --homedir "$RUNNER_TEMP/mocktail-native-gnupg" --import
|
||||
gpg --batch --homedir "$RUNNER_TEMP/mocktail-native-gnupg" \
|
||||
--export "$FLATPAK_GPG_KEY_ID" >mocktail-packages.gpg
|
||||
./scripts/assemble_native_repositories.sh \
|
||||
"${stable_debs[0]}" release/mocktail-nightly.deb \
|
||||
"${stable_rpms[0]}" release/mocktail-nightly.rpm \
|
||||
mocktail-packages.gpg "$FLATPAK_GPG_KEY_ID" \
|
||||
"$RUNNER_TEMP/mocktail-native-gnupg" native-repositories
|
||||
|
||||
- name: Update continuous release
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -Eeuo pipefail
|
||||
git config user.name komaruworld
|
||||
git config user.email rtysus228@gmail.com
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
|
||||
git tag --force continuous "$GITHUB_SHA"
|
||||
git push origin refs/tags/continuous --force
|
||||
notes="Automated build of main at ${GITHUB_SHA:0:8}. Untested and unreleased; use a tagged release for normal play."
|
||||
if ! gh release view continuous >/dev/null 2>&1; then
|
||||
gh release create continuous \
|
||||
--prerelease \
|
||||
--title "Mocktail continuous" \
|
||||
--notes "Automated development packages from main."
|
||||
--title "Nightcap continuous" \
|
||||
--notes "$notes"
|
||||
fi
|
||||
gh release edit continuous --title "Nightcap continuous" --notes "$notes"
|
||||
gh release upload continuous release/* --clobber
|
||||
# Drop assets left by earlier runs under other names.
|
||||
while IFS= read -r asset; do
|
||||
case "$asset" in
|
||||
mocktail-nightly.AppImage|mocktail-nightly.deb|mocktail-nightly.rpm|mocktail-nightly.pkg.tar.zst)
|
||||
Nightcap-nightly-x86_64.AppImage|Nightcap-nightly-x86_64.AppImage.sha256)
|
||||
;;
|
||||
Mocktail-*.AppImage|mocktail_*.deb|mocktail-*.deb|mocktail-*.rpm|mocktail-*.pkg.tar.zst)
|
||||
*)
|
||||
gh release delete-asset continuous "$asset" --yes
|
||||
;;
|
||||
esac
|
||||
done < <(gh release view continuous --json assets --jq '.assets[].name')
|
||||
|
||||
- name: Upload package repositories
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: Mocktail-native-repositories
|
||||
path: native-repositories
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
@@ -24,12 +24,20 @@ jobs:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Check release notes
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
shell: bash
|
||||
run: ./scripts/release_notes.sh "$GITHUB_REF_NAME" >/dev/null
|
||||
|
||||
- name: Build AppImage
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
docker run --rm \
|
||||
--volume "$GITHUB_WORKSPACE:/workspace" \
|
||||
--workdir /workspace \
|
||||
--env GH_TOKEN \
|
||||
ghcr.io/archlinux/archlinux:base-devel@sha256:c3953446016873a756fb7814f10087cac1634c127f7302e80c81c512da796d05 bash -Eeuo pipefail -c '
|
||||
pacman -Syu --noconfirm --needed \
|
||||
cmake git ninja pkgconf lld sdl3 sdl3_ttf curl openssl \
|
||||
@@ -127,6 +135,9 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Download AppImage
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
@@ -142,13 +153,16 @@ jobs:
|
||||
tag="${GITHUB_REF_NAME}"
|
||||
mv release/Mocktail-x86_64.AppImage release/Nightcap-x86_64.AppImage
|
||||
(cd release && sha256sum Nightcap-x86_64.AppImage > Nightcap-x86_64.AppImage.sha256)
|
||||
./scripts/release_notes.sh "$tag" > release/notes.md
|
||||
# The Flatpak workflow creates the same release; whichever job
|
||||
# arrives second sees "already exists" and continues.
|
||||
if ! gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
gh release create "$tag" --repo "$GITHUB_REPOSITORY" \
|
||||
--title "Nightcap $tag" --generate-notes ||
|
||||
--title "Nightcap $tag" --notes-file release/notes.md ||
|
||||
gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null
|
||||
fi
|
||||
gh release edit "$tag" --repo "$GITHUB_REPOSITORY" \
|
||||
--title "Nightcap $tag" --notes-file release/notes.md
|
||||
gh release upload "$tag" --repo "$GITHUB_REPOSITORY" --clobber \
|
||||
release/Nightcap-x86_64.AppImage \
|
||||
release/Nightcap-x86_64.AppImage.sha256
|
||||
@@ -0,0 +1,80 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to Nightcap. The format follows
|
||||
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Release notes on
|
||||
GitHub are taken from the matching section here.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.2.0] - 2026-09-16
|
||||
|
||||
### Added
|
||||
|
||||
- FAQ covering Discord RPC, log locations, and VR, adapted from upstream.
|
||||
- `input.raw_mouse` in `config.yaml` sends mouse look deltas in the host's
|
||||
own units. Turn it on when first-person aiming feels too slow or too fast
|
||||
under fractional display scaling. Off by default.
|
||||
- Nightly AppImage builds, published as the `continuous` prerelease. They are
|
||||
untested; use a tagged release for normal play.
|
||||
|
||||
### Changed
|
||||
|
||||
- Values in `fflags.json` now win over the performance preset instead of
|
||||
stopping the launch. Startup prints each key it kept.
|
||||
- Discord Rich Presence uses Nightcap's own Discord application, so it shows
|
||||
as "Playing Nightcap". Terms and privacy pages for it live on the
|
||||
project site.
|
||||
- Discord Rich Presence is on by default. Set
|
||||
`integrations.discord_rpc.enabled: false` to turn it off.
|
||||
- The AppImage build verifies its reduced dependency packages against digests
|
||||
taken at build time, rather than a digest list kept in the repository.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Mouse look keeps responding after a long turn. The tracked pointer position
|
||||
could drift past the edge of the view, which swallowed every reversal until
|
||||
the mouse travelled all the way back.
|
||||
- Nightcap no longer refuses to start with `cannot lock appStorage`. It
|
||||
created its own credential directory using the process umask, then rejected
|
||||
that directory for being group writable. Systems with a permissive umask,
|
||||
such as `002`, failed on first run.
|
||||
|
||||
## [0.1.0] - 2026-09-15
|
||||
|
||||
First Nightcap release, forked from komaruworld/mocktail.
|
||||
|
||||
### Added
|
||||
|
||||
- ETC2 and EAC texture emulation on Vulkan, so games that use compressed
|
||||
mobile textures render correctly on desktop GPUs. Decoding runs on worker
|
||||
threads to avoid stutter.
|
||||
- PNG texture overrides in `~/.config/mocktail/textures`, keyed by upload
|
||||
hash, with a dump mode to find the texture you want to replace.
|
||||
- Small textures are upscaled 4x by default. Set
|
||||
`MOCKTAIL_SMALL_TEXTURE_UPSCALE=1` to turn it off.
|
||||
- AppImage and signed Flatpak builds on every release tag. The Flatpak repo
|
||||
is published at https://coderdayton.github.io/nightcap/.
|
||||
- Roadmap in `docs/ROADMAP.md`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Frames are presented as soon as they are ready instead of waiting in a
|
||||
queue, which lowers input latency.
|
||||
- Roblox now sees the real host RAM and display size instead of a low-end
|
||||
phone profile.
|
||||
- App ID is `io.github.CoderDayton.nightcap`, so Nightcap installs next to
|
||||
upstream Mocktail. The binary and config paths keep the `mocktail` name.
|
||||
- New name, icon, and colors.
|
||||
- The desktop entry launches on Wayland and points at the installed binary.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Servers no longer kick with error 319 shortly after joining.
|
||||
- The FMOD output-device bridge is optional, so the updater no longer fails
|
||||
without it.
|
||||
- The AppImage build no longer fails on stale checksums for the slimmed
|
||||
Arch packages.
|
||||
|
||||
[Unreleased]: https://github.com/CoderDayton/nightcap/compare/v0.2.0...HEAD
|
||||
[0.2.0]: https://github.com/CoderDayton/nightcap/releases/tag/v0.2.0
|
||||
[0.1.0]: https://github.com/CoderDayton/nightcap/releases/tag/v0.1.0
|
||||
+1
-1
@@ -61,7 +61,7 @@ set(MOCKTAIL_DEFAULT_SIGNING_TRUST_MANIFEST
|
||||
CACHE FILEPATH
|
||||
"Fallback signing trust manifest used outside an installed runtime"
|
||||
)
|
||||
set(MOCKTAIL_DISCORD_APPLICATION_ID "1537088975720812655" CACHE STRING
|
||||
set(MOCKTAIL_DISCORD_APPLICATION_ID "1549428231927500843" CACHE STRING
|
||||
"Discord application ID baked into Mocktail Rich Presence releases"
|
||||
)
|
||||
string(LENGTH "${MOCKTAIL_DISCORD_APPLICATION_ID}"
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<!-- Modified by vii from komaruworld/mocktail. See README "About this fork". -->
|
||||
# FAQ
|
||||
|
||||
## How do I turn Discord RPC off?
|
||||
|
||||
RPC is on by default. It never signs in to Discord and never reads an
|
||||
account token. To turn it off, launch Nightcap once to create the config,
|
||||
then close it. Open `config.yaml`:
|
||||
|
||||
- Native / AppImage: `~/.config/mocktail/config.yaml`
|
||||
- Flatpak: `~/.var/app/io.github.CoderDayton.nightcap/config/mocktail/config.yaml`
|
||||
|
||||
For native installs, a custom `$XDG_CONFIG_HOME` replaces `~/.config`.
|
||||
|
||||
Set `enabled` to `false` under `integrations.discord_rpc`. If the block is
|
||||
missing, add it under the existing `integrations` section:
|
||||
|
||||
```yaml
|
||||
integrations:
|
||||
discord_rpc:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
Save the file and restart Nightcap. Set `enabled` back to `true` to turn
|
||||
RPC on again.
|
||||
|
||||
## Can Discord count Nightcap as Roblox play time or quests?
|
||||
|
||||
No. Discord only credits play time and quests to games its own scanner
|
||||
detects, and Rich Presence never counts. On Linux the scanner only matches
|
||||
games with a Linux entry in Discord's list. Roblox has none, so no process
|
||||
name Nightcap could use would be detected. Only Roblox or Discord can change
|
||||
that.
|
||||
|
||||
## Where can I find logs for a bug report?
|
||||
|
||||
Nightcap saves logs automatically. Reproduce the issue, close Nightcap, and
|
||||
attach `latest.log`:
|
||||
|
||||
- Native / AppImage: `~/.local/state/mocktail/logs/latest.log`
|
||||
- Flatpak: `~/.var/app/io.github.CoderDayton.nightcap/.local/state/mocktail/logs/latest.log`
|
||||
|
||||
For native installs, a custom `$XDG_STATE_HOME` replaces `~/.local/state`.
|
||||
|
||||
## Can I play in VR?
|
||||
|
||||
VR is experimental and lives on upstream's `vr` branch, which does not
|
||||
include the Nightcap changes. Install the
|
||||
[build dependencies](README.md#building), then switch to that branch and
|
||||
build:
|
||||
|
||||
```bash
|
||||
git remote add upstream https://github.com/komaruworld/mocktail.git
|
||||
git fetch upstream
|
||||
git switch -c vr upstream/vr
|
||||
git submodule update --init --recursive
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DMOCKTAIL_ENABLE_VR=ON
|
||||
cmake --build build -j4
|
||||
```
|
||||
|
||||
Start WiVRn or SteamVR/ALVR, connect your headset, then run:
|
||||
|
||||
```bash
|
||||
./build/mocktail -vr
|
||||
```
|
||||
@@ -35,7 +35,9 @@ side by side.
|
||||
binary.
|
||||
|
||||
Everything else works the same as upstream. See the
|
||||
[roadmap](docs/ROADMAP.md) for what is coming next.
|
||||
[roadmap](docs/ROADMAP.md) for what is coming next, the
|
||||
[changelog](CHANGELOG.md) for what each release changed, and the
|
||||
[FAQ](FAQ.md) for Discord RPC, logs, and VR.
|
||||
|
||||
## Install with Flatpak
|
||||
|
||||
@@ -195,7 +197,6 @@ Support this fork by giving it a star or with cryptocurrency:
|
||||
|
||||
- LTC: `LdABR2ELRYrUESWZWrEk1uNx38gHHMuHUU`
|
||||
- SOL: `tjeDnPoWeyW8zfTf8c5CVAm5QTV8vFPaPouaScAdwfT`
|
||||
- BCH: `qpktw5jwut9ae8e6898qt3v7rj3km44utgk2r9mj8n`
|
||||
|
||||
Support the upstream project, which does most of the heavy lifting:
|
||||
|
||||
|
||||
@@ -77,6 +77,14 @@ audio:
|
||||
# between boots; prefer the exact device name when it is unique.
|
||||
input_device: default
|
||||
|
||||
input:
|
||||
# Boolean (default: false): send mouse look deltas in the host's own units.
|
||||
# They are otherwise scaled by the window's pixel density divided by the
|
||||
# desktop display scale, which cancels to 1.0 unless fractional scaling makes
|
||||
# the two disagree. Turn this on when first-person aiming feels too slow or
|
||||
# too fast under fractional scaling. The pointer position is unaffected.
|
||||
raw_mouse: false
|
||||
|
||||
integrations:
|
||||
fleasion:
|
||||
# Boolean (default: false): trust Fleasion's CA without editing Roblox files.
|
||||
@@ -89,9 +97,9 @@ integrations:
|
||||
# or ~/.config/Fleasion/proxy_ca/ca.crt. Never select ca.key.
|
||||
# ca_certificate: /home/user/.config/Fleasion/proxy_ca/ca.crt
|
||||
discord_rpc:
|
||||
# Boolean (default: false): publish Mocktail activity to Discord Desktop.
|
||||
# Boolean (default: true): publish Nightcap activity to Discord Desktop.
|
||||
# This never signs in to Discord and never reads an account token.
|
||||
enabled: false
|
||||
enabled: true
|
||||
# Boolean (default: true): show the current Roblox experience name.
|
||||
show_place_name: true
|
||||
# Boolean (default: true): show how long the current session has run.
|
||||
|
||||
+27
-17
@@ -13,7 +13,7 @@ Goal: a small GTK4 settings window, reachable from the app menu and from a
|
||||
`--settings` flag, that reads and writes the same `config.yaml`.
|
||||
|
||||
- Graphics tab: backend, frame rate limit, vsync, small texture upscale.
|
||||
- Input tab: raw mouse and controller options (see item 2).
|
||||
- Input tab: the `input.raw_mouse` toggle and controller options.
|
||||
- Advanced tab: FFlag overrides with a plain text editor for `fflags.json`.
|
||||
- Discord tab: the `discord_rpc` block (see item 3).
|
||||
- Changes apply on next launch. A restart button restarts Nightcap.
|
||||
@@ -21,28 +21,29 @@ Goal: a small GTK4 settings window, reachable from the app menu and from a
|
||||
Done when every option in `config/mocktail.example.yaml` that a player would
|
||||
touch can be set without a text editor.
|
||||
|
||||
## 2. Controller and raw mouse input
|
||||
## 2. Controller button glyphs
|
||||
|
||||
Upstream added Xbox and PlayStation gamepad support through SDL. Desktop play
|
||||
still has rough edges.
|
||||
Upstream added Xbox and PlayStation gamepad support through SDL. Controllers
|
||||
already hot-plug and release their held inputs when unplugged. One rough edge
|
||||
is left.
|
||||
|
||||
- Raw mouse input in first-person games, with no acceleration or clamping.
|
||||
- Better controller support: hot-plug without a restart, correct button
|
||||
glyphs for the connected pad, and no stuck inputs after a disconnect.
|
||||
- Correct button glyphs for the connected pad. `SDL_GetGamepadType` is only
|
||||
read for Xbox and PlayStation pads, so a Switch Pro, a GameCube pad, or any
|
||||
generic pad SDL reports as `SDL_GAMEPAD_TYPE_STANDARD` reaches Roblox as
|
||||
type 0 and draws generic glyphs. `SDL_GetRealGamepadType`, and the vendor
|
||||
and product IDs, are available and unused.
|
||||
|
||||
Done when a mouse feels like a mouse and a plugged-in controller just works.
|
||||
Done when a plugged-in pad shows its own buttons.
|
||||
|
||||
## 3. Discord Rich Presence
|
||||
|
||||
Upstream ships Discord RPC, off by default, behind `discord_rpc.enabled`. It
|
||||
shows the experience name, elapsed time, and a join button when the server is
|
||||
public.
|
||||
Upstream ships Discord RPC. Nightcap turns it on by default; see the
|
||||
[FAQ](../FAQ.md) to switch it off. It shows the experience name, elapsed
|
||||
time, the experience icon, and a join button when the server is public.
|
||||
|
||||
- Turn it on by default. It never reads a Discord token, so the privacy cost
|
||||
is only the experience name, which the player already shares in Roblox.
|
||||
- Show the experience icon as the large image instead of a static logo.
|
||||
- Show the current server region and player count when Roblox exposes them.
|
||||
- Make the join button work for private servers the player has a link for.
|
||||
- Custom presence: set the title, details line, state line, and large and
|
||||
small icons from `config.yaml`, with placeholders like the experience name
|
||||
and elapsed time.
|
||||
- A Discord tab in the settings window to toggle each field.
|
||||
|
||||
Done when a friend can see what you are playing and join in one click, and
|
||||
@@ -57,7 +58,8 @@ feeling.
|
||||
GPU time, and present latency.
|
||||
- Shader compile and pipeline cache tracing, so first-load stutter can be
|
||||
measured and cut.
|
||||
- A `--profile` flag that writes a trace file readable by Perfetto.
|
||||
- A `--profile` flag that writes a Chrome-format trace file, readable in
|
||||
`ui.perfetto.dev` and `chrome://tracing`.
|
||||
- A short benchmark guide in `docs/` with a fixed set of experiences, so
|
||||
before and after numbers can be compared across machines.
|
||||
|
||||
@@ -67,12 +69,20 @@ proves it.
|
||||
## Shipped
|
||||
|
||||
- Lower input latency: frames are presented as soon as they are ready.
|
||||
- Controllers hot-plug without a restart, and a pad that is unplugged mid-game
|
||||
releases its held buttons and sticks instead of leaving them stuck.
|
||||
- Mouse look keeps responding after a long turn, instead of going dead until
|
||||
the pointer travels back from past the screen edge.
|
||||
- Optional raw mouse input: `input.raw_mouse` in `config.yaml` sends look
|
||||
deltas in host units, so fractional display scaling no longer changes
|
||||
first-person sensitivity.
|
||||
- Small textures upscaled 4x by default, with PNG overrides in
|
||||
`~/.config/mocktail/textures`.
|
||||
- ETC2 textures decoded on desktop GPUs without stutter.
|
||||
- No more error 319 kicks after joining a server.
|
||||
- Roblox sees the real host RAM and screen size.
|
||||
- Desktop launcher that works on Wayland and finds the installed binary.
|
||||
- Discord Rich Presence on by default.
|
||||
- Own app ID `io.github.CoderDayton.nightcap`, so Nightcap installs next to
|
||||
upstream.
|
||||
- AppImage and signed Flatpak releases on every `v*` tag, with the Flatpak
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#ifndef MOCKTAIL_RUNTIME_PERFORMANCE_POLICY_H_
|
||||
#define MOCKTAIL_RUNTIME_PERFORMANCE_POLICY_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "runtime/frame_rate_policy.h"
|
||||
#include "runtime/game_mode.h"
|
||||
@@ -49,18 +51,18 @@ PerformancePolicy ParsePerformancePolicy(
|
||||
// Respects CPU affinity and falls back to the available logical count.
|
||||
int DetectAvailablePhysicalCoreCount();
|
||||
|
||||
// Disabled policy preserves overrides; conflicting values fail.
|
||||
bool MergePerformanceClientSettingsOverrides(const PerformancePolicy& policy,
|
||||
std::string_view base_json,
|
||||
std::string* merged_json,
|
||||
std::string* error);
|
||||
// Disabled policy preserves overrides. A base_json value that differs from
|
||||
// the preset is kept as-is and its key is appended to kept_overrides.
|
||||
bool MergePerformanceClientSettingsOverrides(
|
||||
const PerformancePolicy& policy, std::string_view base_json,
|
||||
std::string* merged_json, std::string* error,
|
||||
std::vector<std::string>* kept_overrides = nullptr);
|
||||
|
||||
// Applies mandatory HTTP and crash policies after caller settings.
|
||||
bool MergeRuntimeClientSettingsOverrides(const FrameRatePolicy& frame_rate,
|
||||
const PerformancePolicy& performance,
|
||||
std::string_view base_json,
|
||||
std::string* merged_json,
|
||||
std::string* error);
|
||||
bool MergeRuntimeClientSettingsOverrides(
|
||||
const FrameRatePolicy& frame_rate, const PerformancePolicy& performance,
|
||||
std::string_view base_json, std::string* merged_json, std::string* error,
|
||||
std::vector<std::string>* kept_overrides = nullptr);
|
||||
|
||||
// Keeps the legacy test bypass disabled. The host PermissionsProtocol now
|
||||
// answers microphone authorization from capture configuration. No permission
|
||||
|
||||
@@ -123,6 +123,7 @@ class RobloxInputRuntime final {
|
||||
RobloxInputSnapshot Snapshot() const;
|
||||
Status GetMainWindowIsMouseLockedCenter(bool* locked_center);
|
||||
bool SupportsGamepads() const;
|
||||
void SetRawMouseEnabled(bool enabled);
|
||||
|
||||
static void PlatformEventCallback(void* context,
|
||||
const platform::PlatformEvent& event);
|
||||
|
||||
@@ -171,6 +171,9 @@ class RobloxInputRouter final {
|
||||
const RobloxTextFocusProperties& properties);
|
||||
RobloxInputDispatchResult HandleEvent(const platform::PlatformEvent& event);
|
||||
RobloxInputSnapshot Snapshot() const;
|
||||
// Raw mouse sends look deltas in host units instead of guest surface units.
|
||||
// The reported pointer position stays in guest units either way.
|
||||
void SetRawMouseEnabled(bool enabled);
|
||||
|
||||
private:
|
||||
struct ActiveTouch {
|
||||
@@ -237,6 +240,7 @@ class RobloxInputRouter final {
|
||||
std::vector<ActiveGamepad> gamepads_;
|
||||
float mouse_x_ = 0.0f;
|
||||
float mouse_y_ = 0.0f;
|
||||
bool raw_mouse_ = false;
|
||||
};
|
||||
|
||||
const char* RobloxInputDispatchStateName(RobloxInputDispatchState state);
|
||||
|
||||
@@ -36,6 +36,7 @@ class RobloxWindowInputRuntime final {
|
||||
Status UpdateTextFocusProperties(uint64_t generation,
|
||||
const RobloxTextFocusProperties& properties);
|
||||
RobloxInputSnapshot Snapshot() const;
|
||||
void SetRawMouseEnabled(bool enabled);
|
||||
|
||||
private:
|
||||
static void PlatformEventCallback(void* context,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#ifndef MOCKTAIL_RUNTIME_RUNTIME_CONFIG_H_
|
||||
#define MOCKTAIL_RUNTIME_RUNTIME_CONFIG_H_
|
||||
|
||||
@@ -38,6 +39,10 @@ struct InputCapabilityConfig {
|
||||
bool touch_enabled = false;
|
||||
bool mouse_enabled = true;
|
||||
bool keyboard_enabled = true;
|
||||
// Mouse look deltas keep their host units instead of being scaled into guest
|
||||
// surface units. The two differ only when the desktop display scale and the
|
||||
// window's pixel density disagree, as under fractional scaling.
|
||||
bool raw_mouse = false;
|
||||
};
|
||||
|
||||
struct NetworkProxyConfig {
|
||||
@@ -55,7 +60,7 @@ struct DiscordRpcTextConfig {
|
||||
};
|
||||
|
||||
struct DiscordRpcConfig {
|
||||
bool enabled = false;
|
||||
bool enabled = true;
|
||||
bool show_place_name = true;
|
||||
bool show_elapsed_time = true;
|
||||
bool join_enabled = true;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# pkgforge-dev/archlinux-pkgs-debloated, continuous release, 2026-09-15.
|
||||
# These assets are rolling. Refresh names/hashes together; do not bypass a
|
||||
# checksum mismatch. Only the disposable AppImage build container installs them.
|
||||
eb87771dabc819b318e0ddf94e7ea175a662c1b6fbe748661126c520a0fd4627 ffmpeg-mini-x86_64.pkg.tar.zst
|
||||
e62d30173cc87d3a717a218610c9291b9f647b29b99ee86a0a591226c39a0728 gdk-pixbuf2-mini-x86_64.pkg.tar.zst
|
||||
ca18bc00a343650cde3d674135fa482b54faa639017f82792906db8c68ca9fb6 gtk4-mini-x86_64.pkg.tar.zst
|
||||
8500f31bb0f793d159d58f2e428594baba63f0c1a0276394c5a3f1ac1da422ef icu-mini-x86_64.pkg.tar.zst
|
||||
3d51ee49d0e49a065971703b3f61b610bd1c307cd5165ec16846d75f3efe4a08 librsvg-mini-x86_64.pkg.tar.zst
|
||||
fd52222372202ac7d37ff3f6ef0a47b0decee224aab37ac2b24086fb623b8b86 libxml2-mini-x86_64.pkg.tar.zst
|
||||
da5598b3f8e3a33ba0811c225752466a1abd3821053a19b480410937454feb04 mesa-mini-x86_64.pkg.tar.zst
|
||||
ca23eed050345d8c3f45a21610f91aecc273569b09d8d6eed574014e2e560d89 opus-mini-x86_64.pkg.tar.zst
|
||||
@@ -0,0 +1,12 @@
|
||||
# Allowed reduced packages from pkgforge-dev/archlinux-pkgs-debloated.
|
||||
# continuous is rolling: the installer snapshots GitHub asset IDs and SHA256
|
||||
# digests together at build time, and verifies every archive before installation.
|
||||
# Only the disposable AppImage build container installs these packages.
|
||||
ffmpeg-mini-x86_64.pkg.tar.zst
|
||||
gdk-pixbuf2-mini-x86_64.pkg.tar.zst
|
||||
gtk4-mini-x86_64.pkg.tar.zst
|
||||
icu-mini-x86_64.pkg.tar.zst
|
||||
librsvg-mini-x86_64.pkg.tar.zst
|
||||
libxml2-mini-x86_64.pkg.tar.zst
|
||||
mesa-mini-x86_64.pkg.tar.zst
|
||||
opus-mini-x86_64.pkg.tar.zst
|
||||
@@ -70,3 +70,5 @@ EOF
|
||||
|
||||
install -m 0644 -- "${PROJECT_DIR}/site/index.html" "${OUTPUT}/index.html"
|
||||
install -m 0644 -- "${PROJECT_DIR}/site/styles.css" "${OUTPUT}/styles.css"
|
||||
install -m 0644 -- "${PROJECT_DIR}/site/terms.html" "${OUTPUT}/terms.html"
|
||||
install -m 0644 -- "${PROJECT_DIR}/site/privacy.html" "${OUTPUT}/privacy.html"
|
||||
@@ -4,36 +4,122 @@
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
# The reduced packages are intentionally limited to the disposable builder.
|
||||
# Never replace a developer's desktop / graphics packages on the host.
|
||||
[[ -f /.dockerenv || -f /run/.containerenv ]] || {
|
||||
printf 'AnyLinux dependency installation requires a disposable container\n' >&2
|
||||
exit 1
|
||||
}
|
||||
[[ "$(id -u)" == 0 ]] || {
|
||||
printf 'AnyLinux dependency installation requires container root\n' >&2
|
||||
AnyLinuxDependencyDie() {
|
||||
printf '[anylinux-dependencies] error: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
readonly ROOT="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
readonly MANIFEST="${ROOT}/packaging/anylinux-dependencies.sha256"
|
||||
readonly DOWNLOADS="$(mktemp -d "${TMPDIR:-/tmp}/mocktail-debloated.XXXXXX")"
|
||||
trap 'rm -rf -- "${DOWNLOADS}"' EXIT
|
||||
readonly BASE_URL=https://github.com/pkgforge-dev/archlinux-pkgs-debloated/releases/download/continuous
|
||||
declare -a packages=()
|
||||
while read -r digest filename remainder; do
|
||||
[[ -n "${digest}" && "${digest}" != \#* ]] || continue
|
||||
[[ "${digest}" =~ ^[0-9a-f]{64}$ &&
|
||||
"${filename}" =~ ^[a-z0-9-]+-x86_64\.pkg\.tar\.zst$ &&
|
||||
-z "${remainder}" ]] || {
|
||||
printf 'Invalid AnyLinux dependency manifest entry\n' >&2
|
||||
exit 1
|
||||
}
|
||||
curl --fail --location --retry 3 --output "${DOWNLOADS}/${filename}" \
|
||||
"${BASE_URL}/${filename}"
|
||||
printf '%s %s\n' "${digest}" "${DOWNLOADS}/${filename}" |
|
||||
sha256sum --check --strict
|
||||
packages+=("${DOWNLOADS}/${filename}")
|
||||
done <"${MANIFEST}"
|
||||
(( ${#packages[@]} > 0 ))
|
||||
pacman -U --noconfirm --needed "${packages[@]}"
|
||||
AnyLinuxDependencyRequireContainer() {
|
||||
# Never replace a developer's desktop / graphics packages on the host.
|
||||
[[ -f /.dockerenv || -f /run/.containerenv ]] ||
|
||||
AnyLinuxDependencyDie 'installation requires a disposable container'
|
||||
[[ "$(id -u)" == 0 ]] ||
|
||||
AnyLinuxDependencyDie 'installation requires container root'
|
||||
}
|
||||
|
||||
AnyLinuxDependencySnapshot() {
|
||||
local manifest="$1" release="$2" filename remainder names_json
|
||||
local -a names=()
|
||||
local -A seen=()
|
||||
while read -r filename remainder || [[ -n "${filename}" ]]; do
|
||||
[[ -n "${filename}" && "${filename}" != \#* ]] || continue
|
||||
[[ "${filename}" =~ ^[a-z0-9-]+-mini-x86_64\.pkg\.tar\.zst$ &&
|
||||
-z "${remainder}" && -z "${seen[${filename}]:-}" ]] ||
|
||||
AnyLinuxDependencyDie 'invalid or duplicate dependency manifest entry'
|
||||
seen["${filename}"]=1
|
||||
names+=("${filename}")
|
||||
done <"${manifest}"
|
||||
(( ${#names[@]} > 0 )) || AnyLinuxDependencyDie 'empty dependency manifest'
|
||||
names_json="$(printf '%s\n' "${names[@]}" | jq --raw-input --slurp \
|
||||
'split("\n")[:-1]')"
|
||||
|
||||
# continuous assets are replaced on upstream rebuilds. Resolve all required
|
||||
# names and GitHub-computed digests in one response, then download by asset ID
|
||||
# rather than by rolling filename. These are per-build integrity checks, not
|
||||
# immutable version pins. Never accept absent digests or compute our own trust.
|
||||
jq --exit-status --raw-output --argjson names "${names_json}" \
|
||||
--arg base 'https://api.github.com/repos/pkgforge-dev/archlinux-pkgs-debloated/releases/assets/' '
|
||||
(if .tag_name != "continuous" or (.assets | type) != "array" then
|
||||
error("invalid continuous release metadata")
|
||||
else .assets end) as $assets |
|
||||
$names[] as $name |
|
||||
[$assets[] | select(.name == $name)] as $matches |
|
||||
if ($matches | length) != 1 then
|
||||
error("missing or duplicate dependency asset: " + $name)
|
||||
else $matches[0] end |
|
||||
if .state != "uploaded" or (.id | type) != "number" or
|
||||
.id <= 0 or .id != (.id | floor) or
|
||||
(.digest | type) != "string" then
|
||||
error("invalid dependency asset metadata: " + $name)
|
||||
elif (.digest | test("^sha256:[0-9a-f]{64}$") | not) or
|
||||
.url != ($base + (.id | tostring)) then
|
||||
error("invalid dependency digest or URL: " + $name)
|
||||
else
|
||||
[(.digest | ltrimstr("sha256:")), $name, .url] | @tsv
|
||||
end
|
||||
' "${release}" || AnyLinuxDependencyDie 'cannot resolve verified dependency assets'
|
||||
}
|
||||
|
||||
AnyLinuxDownloadDependencies() {
|
||||
local manifest="$1" downloads="$2" attempt digest filename url failed
|
||||
local -a curl_options=(
|
||||
--fail --silent --show-error --location --retry 3
|
||||
--proto '=https' --proto-redir '=https'
|
||||
--connect-timeout 15 --max-time 180
|
||||
--header 'X-GitHub-Api-Version: 2022-11-28'
|
||||
)
|
||||
if [[ -n "${GH_TOKEN:-}" ]]; then
|
||||
curl_options+=(--header "Authorization: Bearer ${GH_TOKEN}")
|
||||
fi
|
||||
for attempt in 1 2 3; do
|
||||
curl "${curl_options[@]}" --header 'Accept: application/vnd.github+json' \
|
||||
--output "${downloads}/release.json" \
|
||||
https://api.github.com/repos/pkgforge-dev/archlinux-pkgs-debloated/releases/tags/continuous ||
|
||||
AnyLinuxDependencyDie 'cannot fetch upstream release metadata'
|
||||
AnyLinuxDependencySnapshot "${manifest}" "${downloads}/release.json" \
|
||||
>"${downloads}/dependencies.tsv"
|
||||
: >"${downloads}/SHA256SUMS"
|
||||
failed=false
|
||||
while IFS=$'\t' read -r digest filename url; do
|
||||
printf '[anylinux-dependencies] downloading %s (SHA256 %s)\n' \
|
||||
"${filename}" "${digest}" >&2
|
||||
if ! curl "${curl_options[@]}" --header 'Accept: application/octet-stream' \
|
||||
--output "${downloads}/${filename}" "${url}"; then
|
||||
failed=true
|
||||
break
|
||||
fi
|
||||
printf '%s %s\n' "${digest}" "${filename}" >>"${downloads}/SHA256SUMS"
|
||||
done <"${downloads}/dependencies.tsv"
|
||||
if [[ "${failed}" == true ]]; then
|
||||
# An upstream upload can delete an ID between resolution and download.
|
||||
# Retry a complete snapshot, never mix old checksums with new filenames.
|
||||
printf '[anylinux-dependencies] download failed; refreshing snapshot (%s/3)\n' \
|
||||
"${attempt}" >&2
|
||||
continue
|
||||
fi
|
||||
(cd -- "${downloads}" && sha256sum --check --strict SHA256SUMS) ||
|
||||
AnyLinuxDependencyDie 'dependency checksum verification failed'
|
||||
return 0
|
||||
done
|
||||
AnyLinuxDependencyDie 'dependency downloads failed after 3 snapshots'
|
||||
}
|
||||
|
||||
AnyLinuxDependenciesMain() (
|
||||
AnyLinuxDependencyRequireContainer
|
||||
(( $# == 0 )) || AnyLinuxDependencyDie 'no arguments are supported'
|
||||
readonly ROOT="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
readonly MANIFEST="${ROOT}/packaging/anylinux-dependencies.txt"
|
||||
readonly DOWNLOADS="$(mktemp -d "${TMPDIR:-/tmp}/mocktail-debloated.XXXXXX")"
|
||||
trap 'rm -rf -- "${DOWNLOADS}"' EXIT
|
||||
AnyLinuxDownloadDependencies "${MANIFEST}" "${DOWNLOADS}"
|
||||
local digest filename url
|
||||
local -a packages=()
|
||||
while IFS=$'\t' read -r digest filename url; do
|
||||
packages+=("${DOWNLOADS}/${filename}")
|
||||
done <"${DOWNLOADS}/dependencies.tsv"
|
||||
pacman -U --noconfirm --needed "${packages[@]}"
|
||||
)
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
AnyLinuxDependenciesMain "$@"
|
||||
fi
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
# Print the CHANGELOG.md section for one version, for use as GitHub release
|
||||
# notes. Usage: scripts/release_notes.sh v0.1.0 [CHANGELOG.md]
|
||||
# Exits 1 when the version has no section, so a release cannot ship without
|
||||
# notes.
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
readonly tag="${1:?tag is required, e.g. v0.1.0}"
|
||||
readonly changelog="${2:-$(dirname -- "${BASH_SOURCE[0]}")/../CHANGELOG.md}"
|
||||
readonly version="${tag#v}"
|
||||
|
||||
notes="$(awk -v version="${version}" '
|
||||
/^## \[/ {
|
||||
if (found) exit
|
||||
found = ($0 ~ "^## \\[" version "\\]")
|
||||
next
|
||||
}
|
||||
/^\[[^]]+\]: / { if (found) exit }
|
||||
found { print }
|
||||
' "${changelog}")"
|
||||
|
||||
# Trim leading and trailing blank lines.
|
||||
notes="$(printf '%s\n' "${notes}" | sed -e '/./,$!d' | sed -e ':a' -e '/^\n*$/{$d;N;ba' -e '}')"
|
||||
|
||||
[[ -n "${notes}" ]] || {
|
||||
printf 'CHANGELOG.md has no section for version %s\n' "${version}" >&2
|
||||
exit 1
|
||||
}
|
||||
printf '%s\n' "${notes}"
|
||||
@@ -41,6 +41,8 @@ cd nightcap && make build
|
||||
<a href="https://github.com/CoderDayton/nightcap/releases/latest/download/Nightcap-x86_64.AppImage">AppImage</a>
|
||||
<a href="Nightcap-x86_64.flatpak">Flatpak bundle</a>
|
||||
<a href="nightcap-flatpak.gpg">Signing key</a>
|
||||
<a href="terms.html">Terms</a>
|
||||
<a href="privacy.html">Privacy</a>
|
||||
</p>
|
||||
<p><a class="source" href="https://github.com/CoderDayton/nightcap">Source code</a> ·
|
||||
<a class="source" href="https://github.com/komaruworld/mocktail">Upstream Mocktail</a></p>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="dark">
|
||||
<title>Nightcap Privacy Policy</title>
|
||||
<link rel="icon" href="nightcap.svg" type="image/svg+xml">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body><main>
|
||||
<h1>Privacy Policy</h1>
|
||||
<p>Nightcap is a Linux runtime for the Roblox Android client. This policy covers Nightcap itself and its Discord application. Roblox and Discord have their own policies for the data they handle.</p>
|
||||
|
||||
<h2>What Nightcap collects</h2>
|
||||
<p>Nothing. Nightcap has no server. It does not collect, store, or send telemetry, analytics, crash reports, or account data to the Nightcap project.</p>
|
||||
|
||||
<h2>What stays on your machine</h2>
|
||||
<ul>
|
||||
<li>Configuration in <code>~/.config/mocktail/</code></li>
|
||||
<li>Session logs and support bundles in <code>~/.local/state/mocktail/</code></li>
|
||||
<li>Roblox login session under <code>~/.local/share/mocktail/</code></li>
|
||||
<li>Roblox cache, downloaded payloads, and shader cache under <code>~/.cache/mocktail/</code></li>
|
||||
</ul>
|
||||
<p>Flatpak installs keep the same layout under <code>~/.var/app/io.github.CoderDayton.nightcap/</code>.</p>
|
||||
|
||||
<h2>Discord Rich Presence</h2>
|
||||
<p>When Rich Presence is on, Nightcap sends the current experience name, its public icon URL, elapsed time, and an optional public server join code to the Discord desktop client on the same machine over a local socket. Discord displays this on your profile under its own privacy settings. Nightcap never signs in to Discord, never reads a Discord token, and never contacts Discord's servers.</p>
|
||||
<p>Set <code>integrations.discord_rpc.enabled: false</code> in <code>config.yaml</code> to turn it off.</p>
|
||||
|
||||
<h2>Roblox</h2>
|
||||
<p>Nightcap talks to Roblox's services exactly as the Roblox client would. That traffic is covered by the Roblox Privacy Policy.</p>
|
||||
|
||||
<h2>Contact</h2>
|
||||
<p>Open an issue at <a class="source" href="https://github.com/CoderDayton/nightcap/issues">github.com/CoderDayton/nightcap/issues</a>.</p>
|
||||
|
||||
<p class="links">
|
||||
<a href="index.html">Home</a>
|
||||
<a href="terms.html">Terms of Service</a>
|
||||
<a href="https://github.com/CoderDayton/nightcap">Source code</a>
|
||||
</p>
|
||||
</main></body>
|
||||
</html>
|
||||
@@ -0,0 +1,36 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="dark">
|
||||
<title>Nightcap Terms of Service</title>
|
||||
<link rel="icon" href="nightcap.svg" type="image/svg+xml">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body><main>
|
||||
<h1>Terms of Service</h1>
|
||||
<p>Nightcap is free and open-source software licensed under the Apache License, Version 2.0. The license text shipped with the source governs your use of the software.</p>
|
||||
|
||||
<h2>No warranty</h2>
|
||||
<p>Nightcap is provided "as is", without warranty of any kind. The authors are not liable for any loss, damage, or account action that results from using it. See sections 7 and 8 of the Apache License.</p>
|
||||
|
||||
<h2>Not affiliated</h2>
|
||||
<p>Nightcap is an independent community project. It is not affiliated with, endorsed by, or supported by Roblox Corporation or Discord Inc. Roblox and Discord are trademarks of their respective owners.</p>
|
||||
|
||||
<h2>Your responsibility</h2>
|
||||
<p>You are responsible for following the Roblox Terms of Use and the Discord Terms of Service while using Nightcap with those services. Nightcap does not modify Roblox game logic, does not bypass Roblox security, and is not a cheat.</p>
|
||||
|
||||
<h2>Discord application</h2>
|
||||
<p>The Nightcap Discord application only provides Rich Presence for the Nightcap desktop runtime. It requests no permissions, has no bot, and does not access your Discord account.</p>
|
||||
|
||||
<h2>Changes</h2>
|
||||
<p>These terms may change with new releases. The current version always lives at this address.</p>
|
||||
|
||||
<p class="links">
|
||||
<a href="index.html">Home</a>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
<a href="https://github.com/CoderDayton/nightcap">Source code</a>
|
||||
</p>
|
||||
</main></body>
|
||||
</html>
|
||||
@@ -6801,6 +6801,7 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options,
|
||||
window_input_runtime =
|
||||
std::make_shared<mocktail::runtime::RobloxWindowInputRuntime>(
|
||||
input_environment, roblox_capabilities.input);
|
||||
window_input_runtime->SetRawMouseEnabled(input_capabilities.raw_mouse);
|
||||
const mocktail::Status input_status = window_input_runtime->Initialize();
|
||||
if (!input_status.ok()) {
|
||||
std::cerr << "[FATAL] Typed production input did not initialize: "
|
||||
|
||||
+8
-1
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
@@ -779,14 +780,20 @@ int main(int argc, char* argv[]) {
|
||||
<< " FFlag overrides from " << fflags_path << '\n';
|
||||
}
|
||||
std::string client_settings_overrides;
|
||||
std::vector<std::string> kept_fflag_overrides;
|
||||
if (!mocktail::runtime::MergeRuntimeClientSettingsOverrides(
|
||||
runtime_config.config.frame_rate(),
|
||||
runtime_config.config.performance(), fflags.json,
|
||||
&client_settings_overrides, &command_line_error)) {
|
||||
&client_settings_overrides, &command_line_error,
|
||||
&kept_fflag_overrides)) {
|
||||
std::cerr << "[FATAL] Cannot apply runtime client-settings policy: "
|
||||
<< command_line_error << '\n';
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
for (const std::string& name : kept_fflag_overrides) {
|
||||
std::cout << " [runtime] keeping " << name << " from " << fflags_path
|
||||
<< " over the performance preset\n";
|
||||
}
|
||||
std::string audio_capture_overrides;
|
||||
if (!mocktail::runtime::MergeAudioCaptureClientSettingsOverrides(
|
||||
runtime_config.config.microphone_enabled(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#include "runtime/performance_policy.h"
|
||||
|
||||
#include <sched.h>
|
||||
@@ -27,22 +28,21 @@ struct ClientSetting {
|
||||
std::string_view value;
|
||||
};
|
||||
|
||||
bool SetCompatibleValue(nlohmann::json* object, const ClientSetting& setting,
|
||||
std::string* error) {
|
||||
// A caller-supplied value always wins over the preset; the key is reported
|
||||
// through kept_overrides so startup can say which preset values were skipped.
|
||||
void SetCompatibleValue(nlohmann::json* object, const ClientSetting& setting,
|
||||
std::vector<std::string>* kept_overrides) {
|
||||
const std::string name(setting.name);
|
||||
const std::string value(setting.value);
|
||||
const auto existing = object->find(name);
|
||||
if (existing != object->end()) {
|
||||
if (!existing->is_string() || existing->get<std::string>() != value) {
|
||||
if (error != nullptr) {
|
||||
*error = std::string("performance policy conflicts with ") + name;
|
||||
}
|
||||
return false;
|
||||
if ((!existing->is_string() || existing->get<std::string>() != value) &&
|
||||
kept_overrides != nullptr) {
|
||||
kept_overrides->push_back(name);
|
||||
}
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
(*object)[name] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadTopologyValue(int logical_cpu, std::string_view name, int* value) {
|
||||
@@ -154,10 +154,10 @@ int DetectAvailablePhysicalCoreCount() {
|
||||
: static_cast<int>(physical_cores.size());
|
||||
}
|
||||
|
||||
bool MergePerformanceClientSettingsOverrides(const PerformancePolicy& policy,
|
||||
std::string_view base_json,
|
||||
std::string* merged_json,
|
||||
std::string* error) {
|
||||
bool MergePerformanceClientSettingsOverrides(
|
||||
const PerformancePolicy& policy, std::string_view base_json,
|
||||
std::string* merged_json, std::string* error,
|
||||
std::vector<std::string>* kept_overrides) {
|
||||
if (merged_json == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = "performance client-settings output is required";
|
||||
@@ -184,13 +184,10 @@ bool MergePerformanceClientSettingsOverrides(const PerformancePolicy& policy,
|
||||
return true;
|
||||
}
|
||||
const auto apply_settings = [&overrides,
|
||||
error](const auto& settings) -> bool {
|
||||
kept_overrides](const auto& settings) {
|
||||
for (const ClientSetting& setting : settings) {
|
||||
if (!SetCompatibleValue(&overrides, setting, error)) {
|
||||
return false;
|
||||
}
|
||||
SetCompatibleValue(&overrides, setting, kept_overrides);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const bool throughput_mode =
|
||||
policy.physics_worker_mode == PhysicsWorkerMode::kThroughput;
|
||||
@@ -198,9 +195,7 @@ bool MergePerformanceClientSettingsOverrides(const PerformancePolicy& policy,
|
||||
const std::array<ClientSetting, 1> physics_settings = {{
|
||||
{"DFIntSimMidPhaseContactPipelineBatchSize", "128"},
|
||||
}};
|
||||
if (!apply_settings(physics_settings)) {
|
||||
return false;
|
||||
}
|
||||
apply_settings(physics_settings);
|
||||
}
|
||||
int render_worker_count = 0;
|
||||
if (throughput_mode ||
|
||||
@@ -223,9 +218,7 @@ bool MergePerformanceClientSettingsOverrides(const PerformancePolicy& policy,
|
||||
{"FIntTaskSchedulerAutoThreadLimit", workers},
|
||||
{"DFIntSimMidPhaseContactPipelineBatchSize", "128"},
|
||||
}};
|
||||
if (!apply_settings(physics_settings)) {
|
||||
return false;
|
||||
}
|
||||
apply_settings(physics_settings);
|
||||
} else if (policy.multithreaded_rendering &&
|
||||
policy.physics_worker_mode != PhysicsWorkerMode::kLatency) {
|
||||
const std::string workers = std::to_string(render_worker_count);
|
||||
@@ -233,9 +226,7 @@ bool MergePerformanceClientSettingsOverrides(const PerformancePolicy& policy,
|
||||
{"FIntTaskSchedulerThreadMin", workers},
|
||||
{"FIntTaskSchedulerAsyncTasksMinimumThreadCount", workers},
|
||||
}};
|
||||
if (!apply_settings(scheduler_settings)) {
|
||||
return false;
|
||||
}
|
||||
apply_settings(scheduler_settings);
|
||||
}
|
||||
if ((policy.multithreaded_rendering || throughput_mode) &&
|
||||
policy.physics_worker_mode != PhysicsWorkerMode::kLatency) {
|
||||
@@ -322,26 +313,21 @@ bool MergePerformanceClientSettingsOverrides(const PerformancePolicy& policy,
|
||||
{"FIntDefaultAudioDecodeBufferSizeMs", "50"},
|
||||
{"FIntMaxAudibleSoundChannels", "32"},
|
||||
}};
|
||||
if (!apply_settings(rendering_settings)) {
|
||||
return false;
|
||||
}
|
||||
apply_settings(rendering_settings);
|
||||
if (!manual_quality) {
|
||||
if (!SetCompatibleValue(
|
||||
&overrides, {"FIntDebugFRMQualityLevelOverride", quality_str},
|
||||
error)) {
|
||||
return false;
|
||||
}
|
||||
SetCompatibleValue(&overrides,
|
||||
{"FIntDebugFRMQualityLevelOverride", quality_str},
|
||||
kept_overrides);
|
||||
}
|
||||
}
|
||||
*merged_json = overrides.dump();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MergeRuntimeClientSettingsOverrides(const FrameRatePolicy& frame_rate,
|
||||
const PerformancePolicy& performance,
|
||||
std::string_view base_json,
|
||||
std::string* merged_json,
|
||||
std::string* error) {
|
||||
bool MergeRuntimeClientSettingsOverrides(
|
||||
const FrameRatePolicy& frame_rate, const PerformancePolicy& performance,
|
||||
std::string_view base_json, std::string* merged_json, std::string* error,
|
||||
std::vector<std::string>* kept_overrides) {
|
||||
std::string frame_rate_overrides;
|
||||
if (!MergeFrameRateClientSettingsOverrides(frame_rate, base_json,
|
||||
&frame_rate_overrides, error)) {
|
||||
@@ -349,7 +335,8 @@ bool MergeRuntimeClientSettingsOverrides(const FrameRatePolicy& frame_rate,
|
||||
}
|
||||
std::string performance_overrides;
|
||||
if (!MergePerformanceClientSettingsOverrides(
|
||||
performance, frame_rate_overrides, &performance_overrides, error)) {
|
||||
performance, frame_rate_overrides, &performance_overrides, error,
|
||||
kept_overrides)) {
|
||||
return false;
|
||||
}
|
||||
std::string http_client_overrides;
|
||||
|
||||
@@ -627,6 +627,10 @@ RobloxInputDispatchResult RobloxInputRuntime::HandleEvent(
|
||||
return result;
|
||||
}
|
||||
|
||||
void RobloxInputRuntime::SetRawMouseEnabled(bool enabled) {
|
||||
router_.SetRawMouseEnabled(enabled);
|
||||
}
|
||||
|
||||
RobloxInputSnapshot RobloxInputRuntime::Snapshot() const {
|
||||
return router_.Snapshot();
|
||||
}
|
||||
|
||||
@@ -532,6 +532,11 @@ RobloxInputSnapshot RobloxInputRouter::Snapshot() const {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
void RobloxInputRouter::SetRawMouseEnabled(bool enabled) {
|
||||
const std::lock_guard<std::mutex> lock(mutex_);
|
||||
raw_mouse_ = enabled;
|
||||
}
|
||||
|
||||
RobloxInputDispatchResult RobloxInputRouter::HandleMouseMotionLocked(
|
||||
const platform::MouseMotionEvent& event) {
|
||||
if (sink_.mouse_move == nullptr) {
|
||||
@@ -544,21 +549,31 @@ RobloxInputDispatchResult RobloxInputRouter::HandleMouseMotionLocked(
|
||||
const float delta_x = transform.HostLogicalToGuestX(event.delta_x);
|
||||
const float delta_y = transform.HostLogicalToGuestY(event.delta_y);
|
||||
|
||||
if (event.x == 0.0f && event.y == 0.0f &&
|
||||
(delta_x != 0.0f || delta_y != 0.0f)) {
|
||||
mouse_x_ += delta_x;
|
||||
mouse_y_ += delta_y;
|
||||
} else {
|
||||
mouse_x_ = transform.HostLogicalToGuestX(event.x);
|
||||
mouse_y_ = transform.HostLogicalToGuestY(event.y);
|
||||
}
|
||||
const float max_x = std::max(0.0F, transform.guest_width() - 1.0F);
|
||||
const float max_y = std::max(0.0F, transform.guest_height() - 1.0F);
|
||||
const float clamped_x = std::clamp(mouse_x_, 0.0f, max_x);
|
||||
const float clamped_y = std::clamp(mouse_y_, 0.0f, max_y);
|
||||
|
||||
// mouse_x_/mouse_y_ stay inside the viewport. Relative-mode motion reports
|
||||
// zero absolute coordinates, so the accumulated position is the only
|
||||
// position there; letting it drift past the edge would swallow every
|
||||
// reversal until the pointer walked all the way back.
|
||||
if (event.x == 0.0f && event.y == 0.0f &&
|
||||
(delta_x != 0.0f || delta_y != 0.0f)) {
|
||||
mouse_x_ = std::clamp(mouse_x_ + delta_x, 0.0f, max_x);
|
||||
mouse_y_ = std::clamp(mouse_y_ + delta_y, 0.0f, max_y);
|
||||
} else {
|
||||
mouse_x_ = std::clamp(transform.HostLogicalToGuestX(event.x), 0.0f, max_x);
|
||||
mouse_y_ = std::clamp(transform.HostLogicalToGuestY(event.y), 0.0f, max_y);
|
||||
}
|
||||
|
||||
// Raw mouse reports the host's own delta, skipping the guest-surface scale
|
||||
// that otherwise changes look sensitivity when the desktop display scale and
|
||||
// the window's pixel density disagree. The position stays in guest units.
|
||||
const float reported_delta_x = raw_mouse_ ? event.delta_x : delta_x;
|
||||
const float reported_delta_y = raw_mouse_ ? event.delta_y : delta_y;
|
||||
|
||||
return NativeResultLocked(
|
||||
sink_.mouse_move(sink_.context, clamped_x, clamped_y, delta_x, delta_y),
|
||||
sink_.mouse_move(sink_.context, mouse_x_, mouse_y_, reported_delta_x,
|
||||
reported_delta_y),
|
||||
RobloxInputEventKind::kMouseMotion);
|
||||
}
|
||||
|
||||
|
||||
@@ -195,6 +195,10 @@ Status RobloxWindowInputRuntime::UpdateTextFocusProperties(
|
||||
return runtime_.UpdateTextFocusProperties(generation, properties);
|
||||
}
|
||||
|
||||
void RobloxWindowInputRuntime::SetRawMouseEnabled(bool enabled) {
|
||||
runtime_.SetRawMouseEnabled(enabled);
|
||||
}
|
||||
|
||||
RobloxInputSnapshot RobloxWindowInputRuntime::Snapshot() const {
|
||||
return runtime_.Snapshot();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#include "runtime/runtime_config.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -229,6 +230,11 @@ RuntimeConfig RuntimeConfig::FromEnvironment(const Environment& environment) {
|
||||
keyboard.has_value()) {
|
||||
config.input_capabilities_.keyboard_enabled = *keyboard;
|
||||
}
|
||||
if (const std::optional<bool> raw_mouse =
|
||||
InputEnabled(environment, "MOCKTAIL_RAW_MOUSE");
|
||||
raw_mouse.has_value()) {
|
||||
config.input_capabilities_.raw_mouse = *raw_mouse;
|
||||
}
|
||||
bool customized_device = false;
|
||||
config.device_profile_valid_ =
|
||||
config.device_profile_valid_ &&
|
||||
@@ -325,7 +331,7 @@ RuntimeConfig RuntimeConfig::FromEnvironment(const Environment& environment) {
|
||||
}
|
||||
bool discord_booleans_valid = true;
|
||||
config.discord_rpc_.enabled = ReadBoolean(
|
||||
environment, "MOCKTAIL_DISCORD_RPC_ENABLED", false,
|
||||
environment, "MOCKTAIL_DISCORD_RPC_ENABLED", true,
|
||||
&discord_booleans_valid);
|
||||
config.discord_rpc_.show_place_name = ReadBoolean(
|
||||
environment, "MOCKTAIL_DISCORD_RPC_SHOW_PLACE_NAME", true,
|
||||
|
||||
@@ -17,7 +17,8 @@ namespace runtime {
|
||||
namespace {
|
||||
|
||||
constexpr std::string_view kDefaultRuntimeConfig =
|
||||
R"yaml(# Mocktail configuration.
|
||||
R"yaml(# Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
# Mocktail configuration.
|
||||
# The file is created once with mode 0600. Mocktail never replaces your edits.
|
||||
|
||||
# Integer: configuration schema version. Only version 1 is supported.
|
||||
@@ -95,6 +96,14 @@ audio:
|
||||
# between boots; prefer the exact device name when it is unique.
|
||||
input_device: default
|
||||
|
||||
input:
|
||||
# Boolean (default: false): send mouse look deltas in the host's own units.
|
||||
# They are otherwise scaled by the window's pixel density divided by the
|
||||
# desktop display scale, which cancels to 1.0 unless fractional scaling makes
|
||||
# the two disagree. Turn this on when first-person aiming feels too slow or
|
||||
# too fast under fractional scaling. The pointer position is unaffected.
|
||||
raw_mouse: false
|
||||
|
||||
integrations:
|
||||
fleasion:
|
||||
# Boolean (default: false): trust Fleasion's CA without editing Roblox files.
|
||||
@@ -107,9 +116,9 @@ integrations:
|
||||
# or ~/.config/Fleasion/proxy_ca/ca.crt. Never select ca.key.
|
||||
# ca_certificate: /home/user/.config/Fleasion/proxy_ca/ca.crt
|
||||
discord_rpc:
|
||||
# Boolean (default: false): publish Mocktail activity to Discord Desktop.
|
||||
# Boolean (default: true): publish Nightcap activity to Discord Desktop.
|
||||
# This never signs in to Discord and never reads an account token.
|
||||
enabled: false
|
||||
enabled: true
|
||||
# Boolean (default: true): show the current Roblox experience name.
|
||||
show_place_name: true
|
||||
# Boolean (default: true): show how long the current session has run.
|
||||
|
||||
@@ -183,6 +183,7 @@ bool ValidateAndMap(const ValueMap& yaml, ValueMap* environment,
|
||||
"window.title",
|
||||
"window.high_dpi",
|
||||
"input.touch_enabled",
|
||||
"input.raw_mouse",
|
||||
"compatibility.desktop_playability",
|
||||
"network.use_system_proxy",
|
||||
"network.proxy_host",
|
||||
@@ -433,6 +434,14 @@ bool ValidateAndMap(const ValueMap& yaml, ValueMap* environment,
|
||||
}
|
||||
(*environment)["MOCKTAIL_TOUCH_MODE"] = parsed ? "on" : "off";
|
||||
}
|
||||
if (const auto raw_mouse = value("input.raw_mouse"); raw_mouse.has_value()) {
|
||||
bool parsed = false;
|
||||
if (!ParseBoolean(*raw_mouse, &parsed)) {
|
||||
*error = "input.raw_mouse must be true or false";
|
||||
return false;
|
||||
}
|
||||
(*environment)["MOCKTAIL_RAW_MOUSE"] = parsed ? "on" : "off";
|
||||
}
|
||||
if (const auto desktop_playability =
|
||||
value("compatibility.desktop_playability");
|
||||
desktop_playability.has_value()) {
|
||||
@@ -877,6 +886,9 @@ bool ExportRuntimeConfigEnvironment(const RuntimeConfig& config,
|
||||
SetEnvironmentValue(
|
||||
"MOCKTAIL_KEYBOARD_MODE",
|
||||
config.input_capabilities().keyboard_enabled ? "on" : "off", error) &&
|
||||
SetEnvironmentValue("MOCKTAIL_RAW_MOUSE",
|
||||
config.input_capabilities().raw_mouse ? "on" : "off",
|
||||
error) &&
|
||||
SetEnvironmentValue("MOCKTAIL_DESKTOP_PLAYABILITY",
|
||||
config.desktop_playability() ? "1" : "0", error) &&
|
||||
SetEnvironmentValue("MOCKTAIL_FRAME_RATE_LIMIT",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <random>
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
namespace mocktail {
|
||||
namespace services {
|
||||
@@ -211,6 +212,48 @@ bool FindExistingDirectoryAncestor(const std::filesystem::path& directory,
|
||||
}
|
||||
}
|
||||
|
||||
// Creates every component of `directory` below `existing_ancestor` with mode
|
||||
// 0700, whatever the process umask. mkdir masks its mode argument, so a umask
|
||||
// of 002 would leave the directory group writable and rejected by the
|
||||
// IsPrivateDirectory checks that guard this file's credentials, while a umask
|
||||
// carrying owner bits would leave it unwritable by its own owner. Only
|
||||
// components this call creates are tightened; one that already exists keeps
|
||||
// its mode and is judged by IsPrivateDirectory like any other.
|
||||
bool CreatePrivateDirectories(const std::filesystem::path& directory,
|
||||
const std::filesystem::path& existing_ancestor) {
|
||||
std::vector<std::filesystem::path> missing;
|
||||
for (std::filesystem::path candidate = directory;
|
||||
candidate != existing_ancestor;) {
|
||||
if (candidate.empty()) return false;
|
||||
missing.push_back(candidate);
|
||||
// Mirror FindExistingDirectoryAncestor's walk, including the implicit "."
|
||||
// parent of a bare relative name, so the two agree on which components are
|
||||
// missing.
|
||||
std::filesystem::path parent = candidate.parent_path();
|
||||
if (parent.empty() && candidate != ".") parent = ".";
|
||||
if (parent.empty() || parent == candidate) return false;
|
||||
candidate = parent;
|
||||
}
|
||||
for (auto component = missing.rbegin(); component != missing.rend();
|
||||
++component) {
|
||||
if (mkdir(component->c_str(), 0700) != 0) {
|
||||
if (errno != EEXIST) return false;
|
||||
continue;
|
||||
}
|
||||
const int created = open(component->c_str(),
|
||||
O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
|
||||
if (created < 0) return false;
|
||||
struct stat metadata = {};
|
||||
const bool tightened = fstat(created, &metadata) == 0 &&
|
||||
S_ISDIR(metadata.st_mode) &&
|
||||
metadata.st_uid == geteuid() &&
|
||||
fchmod(created, 0700) == 0;
|
||||
close(created);
|
||||
if (!tightened) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FsyncDirectoryChain(const std::filesystem::path& first,
|
||||
const std::filesystem::path& last) {
|
||||
std::filesystem::path current = first;
|
||||
@@ -322,8 +365,7 @@ bool AtomicWriteContents(const std::filesystem::path& path,
|
||||
return false;
|
||||
}
|
||||
const bool parent_existed = existing_ancestor == parent;
|
||||
std::filesystem::create_directories(parent, filesystem_error);
|
||||
if (filesystem_error) {
|
||||
if (!CreatePrivateDirectories(parent, existing_ancestor)) {
|
||||
error->assign(directory_error);
|
||||
return false;
|
||||
}
|
||||
@@ -449,10 +491,11 @@ BrowserTrackerResult BrowserTrackerService::EnsureInitialized(
|
||||
return result;
|
||||
}
|
||||
const bool lock_parent_existed = existing_lock_ancestor == lock_parent;
|
||||
std::filesystem::create_directories(lock_parent, filesystem_error);
|
||||
const bool lock_parent_created =
|
||||
CreatePrivateDirectories(lock_parent, existing_lock_ancestor);
|
||||
struct stat lock_parent_status = {};
|
||||
const bool safe_lock_parent =
|
||||
!filesystem_error &&
|
||||
lock_parent_created &&
|
||||
lstat(lock_parent.c_str(), &lock_parent_status) == 0 &&
|
||||
IsPrivateDirectory(lock_parent_status);
|
||||
const int lock = safe_lock_parent
|
||||
|
||||
@@ -1049,6 +1049,12 @@ add_test(
|
||||
${CMAKE_SOURCE_DIR}/packaging/AppRun
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME AnyLinuxDependenciesTest.RollingAssetsAreVerified
|
||||
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/anylinux_dependencies_test.sh
|
||||
${CMAKE_SOURCE_DIR}
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME AnyLinuxPackagingTest.RelocatedRuntimePaths
|
||||
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/anylinux_packaging_test.sh
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright 2026 Mocktail Project Authors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
readonly ROOT="${1:?source root is required}"
|
||||
readonly TEMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/mocktail-dependency-test.XXXXXX")"
|
||||
trap 'rm -rf -- "${TEMP_DIR}"' EXIT
|
||||
source "${ROOT}/scripts/install_anylinux_dependencies.sh"
|
||||
|
||||
Fail() {
|
||||
printf 'AnyLinux dependency test failed: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
readonly API=https://api.github.com/repos/pkgforge-dev/archlinux-pkgs-debloated/releases
|
||||
readonly FFMPEG=ffmpeg-mini-x86_64.pkg.tar.zst
|
||||
readonly OPUS=opus-mini-x86_64.pkg.tar.zst
|
||||
printf 'ffmpeg fixture\n' >"${TEMP_DIR}/ffmpeg"
|
||||
printf 'updated ffmpeg fixture\n' >"${TEMP_DIR}/updated-ffmpeg"
|
||||
printf 'opus fixture\n' >"${TEMP_DIR}/opus"
|
||||
readonly FFMPEG_HASH="$(sha256sum "${TEMP_DIR}/ffmpeg" | cut -d ' ' -f 1)"
|
||||
readonly UPDATED_FFMPEG_HASH="$(sha256sum "${TEMP_DIR}/updated-ffmpeg" | cut -d ' ' -f 1)"
|
||||
readonly OPUS_HASH="$(sha256sum "${TEMP_DIR}/opus" | cut -d ' ' -f 1)"
|
||||
printf '# Required fixture packages\n\n%s\n%s' "${FFMPEG}" "${OPUS}" \
|
||||
>"${TEMP_DIR}/manifest.txt"
|
||||
jq --null-input --arg api "${API}" --arg ffmpeg "${FFMPEG}" --arg opus "${OPUS}" \
|
||||
--arg ffmpeg_hash "${FFMPEG_HASH}" --arg opus_hash "${OPUS_HASH}" '
|
||||
{tag_name: "continuous", assets: [
|
||||
{id: 10, name: $ffmpeg, state: "uploaded", digest: ("sha256:" + $ffmpeg_hash),
|
||||
url: ($api + "/assets/10")},
|
||||
{id: 20, name: $opus, state: "uploaded", digest: ("sha256:" + $opus_hash),
|
||||
url: ($api + "/assets/20")},
|
||||
{id: 30, name: "unrequested-mini-x86_64.pkg.tar.zst", state: "uploaded",
|
||||
digest: null, url: ($api + "/assets/30")}
|
||||
]}
|
||||
' >"${TEMP_DIR}/release.json"
|
||||
jq --arg hash "${UPDATED_FFMPEG_HASH}" '
|
||||
.assets[0].id = 11 | .assets[0].url |= sub("/10$"; "/11") |
|
||||
.assets[0].digest = ("sha256:" + $hash) |
|
||||
.assets[1].id = 21 | .assets[1].url |= sub("/20$"; "/21")
|
||||
' \
|
||||
"${TEMP_DIR}/release.json" >"${TEMP_DIR}/refreshed-release.json"
|
||||
mkdir -p "${TEMP_DIR}/project/scripts" "${TEMP_DIR}/project/packaging"
|
||||
cp "${ROOT}/scripts/install_anylinux_dependencies.sh" "${TEMP_DIR}/project/scripts/"
|
||||
cp "${TEMP_DIR}/manifest.txt" "${TEMP_DIR}/project/packaging/anylinux-dependencies.txt"
|
||||
|
||||
# Record requests without any network access or changes to host packages.
|
||||
curl() {
|
||||
local output="" accept="" authorization="" url=""
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--output) output="$2"; shift 2 ;;
|
||||
--header)
|
||||
case "$2" in
|
||||
Accept:*) accept="$2" ;;
|
||||
Authorization:*) authorization="$2" ;;
|
||||
esac
|
||||
shift 2 ;;
|
||||
--retry|--proto|--proto-redir|--connect-timeout|--max-time) shift 2 ;;
|
||||
--fail|--silent|--show-error|--location) shift ;;
|
||||
https://*) url="$1"; shift ;;
|
||||
*) Fail "unexpected curl option: $1" ;;
|
||||
esac
|
||||
done
|
||||
if [[ -n "${GH_TOKEN:-}" ]]; then
|
||||
[[ "${authorization}" == "Authorization: Bearer ${GH_TOKEN}" ]] ||
|
||||
Fail 'missing authenticated GitHub request'
|
||||
fi
|
||||
printf '%s\n' "${url}" >>"${TEMP_DIR}/calls"
|
||||
case "${url}" in
|
||||
"${API}/tags/continuous")
|
||||
[[ "${accept}" == 'Accept: application/vnd.github+json' ]]
|
||||
if [[ "${SCENARIO}" == metadata-failure ]]; then return 22; fi
|
||||
if [[ "${SCENARIO}" == replaced && -f "${TEMP_DIR}/asset-replaced" ]]; then
|
||||
cp "${TEMP_DIR}/refreshed-release.json" "${output}"
|
||||
else
|
||||
cp "${TEST_RELEASE}" "${output}"
|
||||
fi ;;
|
||||
"${API}/assets/10"|"${API}/assets/11"|"${API}/assets/20"|"${API}/assets/21")
|
||||
[[ "${accept}" == 'Accept: application/octet-stream' ]]
|
||||
if [[ "${SCENARIO}" == download-failure ]]; then return 22; fi
|
||||
if [[ "${SCENARIO}" == replaced && "${url}" == "${API}/assets/20" ]]; then
|
||||
touch "${TEMP_DIR}/asset-replaced"
|
||||
return 22
|
||||
fi
|
||||
if [[ "${url}" == "${API}/assets/20" || "${url}" == "${API}/assets/21" ]]; then
|
||||
cp "${TEMP_DIR}/opus" "${output}"
|
||||
elif [[ "${url}" == "${API}/assets/11" ]]; then
|
||||
cp "${TEMP_DIR}/updated-ffmpeg" "${output}"
|
||||
else
|
||||
cp "${TEMP_DIR}/ffmpeg" "${output}"
|
||||
fi
|
||||
if [[ "${SCENARIO}" == corrupted && "${url}" == "${API}/assets/20" ]]; then
|
||||
printf 'corrupted\n' >>"${output}"
|
||||
fi ;;
|
||||
*) Fail "unrequested or rolling asset URL: ${url}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
pacman() {
|
||||
[[ "$#" == 5 && "$1" == -U && "$2" == --noconfirm && "$3" == --needed ]]
|
||||
if [[ "${SCENARIO}" == replaced ]]; then
|
||||
cmp "${TEMP_DIR}/updated-ffmpeg" "$4"
|
||||
else
|
||||
cmp "${TEMP_DIR}/ffmpeg" "$4"
|
||||
fi
|
||||
cmp "${TEMP_DIR}/opus" "$5"
|
||||
local downloads="${4%/*}"
|
||||
(cd "${downloads}" && sha256sum --check --strict SHA256SUMS)
|
||||
cp "$4" "$5" "${downloads}/SHA256SUMS" "${downloads}/dependencies.tsv" \
|
||||
"${TEMP_DIR}/${SCENARIO}/"
|
||||
touch "${TEMP_DIR}/installed"
|
||||
}
|
||||
|
||||
export -f curl pacman Fail
|
||||
export TEMP_DIR API FFMPEG OPUS SCENARIO TEST_RELEASE
|
||||
|
||||
ExpectRejectedSnapshot() {
|
||||
local description="$1" filter="$2"
|
||||
jq "${filter}" "${TEMP_DIR}/release.json" >"${TEMP_DIR}/invalid.json"
|
||||
if (AnyLinuxDependencySnapshot "${TEMP_DIR}/manifest.txt" \
|
||||
"${TEMP_DIR}/invalid.json") >"${TEMP_DIR}/invalid.log" 2>&1; then
|
||||
Fail "accepted ${description}"
|
||||
fi
|
||||
}
|
||||
|
||||
(AnyLinuxDependencySnapshot "${TEMP_DIR}/manifest.txt" \
|
||||
"${TEMP_DIR}/release.json") >"${TEMP_DIR}/snapshot.tsv" ||
|
||||
Fail 'rejected valid dependency snapshot'
|
||||
printf '%s\t%s\t%s\n' \
|
||||
"${FFMPEG_HASH}" "${FFMPEG}" "${API}/assets/10" \
|
||||
"${OPUS_HASH}" "${OPUS}" "${API}/assets/20" \
|
||||
>"${TEMP_DIR}/expected-snapshot.tsv"
|
||||
cmp "${TEMP_DIR}/expected-snapshot.tsv" "${TEMP_DIR}/snapshot.tsv" ||
|
||||
Fail 'incorrect dependency snapshot'
|
||||
|
||||
ExpectRejectedSnapshot 'missing asset' '.assets |= .[1:]'
|
||||
ExpectRejectedSnapshot 'duplicate asset' '.assets += [.assets[0]]'
|
||||
ExpectRejectedSnapshot 'absent SHA256' '.assets[0].digest = null'
|
||||
ExpectRejectedSnapshot 'malformed SHA256' '.assets[0].digest = "sha256:bad"'
|
||||
ExpectRejectedSnapshot 'foreign asset URL' '.assets[0].url = "https://example.org/archive"'
|
||||
ExpectRejectedSnapshot 'fractional asset ID' '.assets[0].id = 10.5'
|
||||
ExpectRejectedSnapshot 'unuploaded asset' '.assets[0].state = "starter"'
|
||||
ExpectRejectedSnapshot 'different release' '.tag_name = "other"'
|
||||
|
||||
for invalid_manifest in '../ffmpeg-mini-x86_64.pkg.tar.zst' \
|
||||
"${FFMPEG} extra" "${FFMPEG}"$'\n'"${FFMPEG}" ''; do
|
||||
printf '%s\n' "${invalid_manifest}" >"${TEMP_DIR}/invalid-manifest.txt"
|
||||
if (AnyLinuxDependencySnapshot "${TEMP_DIR}/invalid-manifest.txt" \
|
||||
"${TEMP_DIR}/release.json") >"${TEMP_DIR}/invalid.log" 2>&1; then
|
||||
Fail 'accepted unsafe, duplicate, or empty package allowlist'
|
||||
fi
|
||||
done
|
||||
|
||||
for SCENARIO in success replaced corrupted download-failure metadata-failure invalid-metadata; do
|
||||
TEST_RELEASE="${TEMP_DIR}/release.json"
|
||||
if [[ "${SCENARIO}" == invalid-metadata ]]; then
|
||||
# A valid first row must not allow downloads when a later row is invalid.
|
||||
jq '.assets[1].digest = null' "${TEMP_DIR}/release.json" >"${TEMP_DIR}/invalid.json"
|
||||
TEST_RELEASE="${TEMP_DIR}/invalid.json"
|
||||
fi
|
||||
mkdir "${TEMP_DIR}/${SCENARIO}"
|
||||
: >"${TEMP_DIR}/calls"
|
||||
if GH_TOKEN=fixture-token bash -Eeuo pipefail -c '
|
||||
source "$1/project/scripts/install_anylinux_dependencies.sh"
|
||||
AnyLinuxDependencyRequireContainer() { :; }
|
||||
AnyLinuxDependenciesMain
|
||||
' _ "${TEMP_DIR}" \
|
||||
>"${TEMP_DIR}/${SCENARIO}.log" 2>&1; then
|
||||
[[ "${SCENARIO}" == success || "${SCENARIO}" == replaced ]] ||
|
||||
Fail "installed packages after ${SCENARIO}"
|
||||
[[ -f "${TEMP_DIR}/installed" ]]
|
||||
(cd "${TEMP_DIR}/${SCENARIO}" && sha256sum --check --strict SHA256SUMS)
|
||||
rm -- "${TEMP_DIR}/installed"
|
||||
else
|
||||
if [[ "${SCENARIO}" == success || "${SCENARIO}" == replaced ]]; then
|
||||
cat -- "${TEMP_DIR}/${SCENARIO}.log" >&2
|
||||
Fail "valid download failed: ${SCENARIO}"
|
||||
fi
|
||||
[[ ! -e "${TEMP_DIR}/installed" ]] || Fail 'installed unverified archives'
|
||||
fi
|
||||
case "${SCENARIO}" in
|
||||
success)
|
||||
[[ "$(wc -l <"${TEMP_DIR}/calls")" == 3 ]]
|
||||
grep -Fq "${API}/assets/10" "${TEMP_DIR}/calls" ;;
|
||||
replaced)
|
||||
[[ "$(wc -l <"${TEMP_DIR}/calls")" == 6 ]]
|
||||
grep -Fq "${API}/assets/11" "${TEMP_DIR}/calls"
|
||||
grep -Fq "${API}/assets/11" "${TEMP_DIR}/replaced/dependencies.tsv"
|
||||
grep -Fq "${API}/assets/21" "${TEMP_DIR}/replaced/dependencies.tsv" ;;
|
||||
corrupted)
|
||||
grep -Fq 'dependency checksum verification failed' "${TEMP_DIR}/${SCENARIO}.log" ;;
|
||||
download-failure)
|
||||
[[ "$(wc -l <"${TEMP_DIR}/calls")" == 6 ]]
|
||||
grep -Fq 'failed after 3 snapshots' "${TEMP_DIR}/${SCENARIO}.log" ;;
|
||||
metadata-failure|invalid-metadata)
|
||||
[[ "$(wc -l <"${TEMP_DIR}/calls")" == 1 ]] ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if (id() { printf '1000\n'; }; AnyLinuxDependencyRequireContainer) \
|
||||
>"${TEMP_DIR}/guard.log" 2>&1; then
|
||||
Fail 'container/root installation guard was bypassed'
|
||||
fi
|
||||
|
||||
printf 'AnyLinux dependency tests passed\n'
|
||||
@@ -628,6 +628,13 @@ TEST(OpenSlQueueTest, GetStatePollsWhilePlaybackReleasesBuffers) {
|
||||
probe.cv.wait_for(lock, 2s, [&probe] { return probe.calls == 1; }));
|
||||
}
|
||||
|
||||
// A freshly spawned thread is not guaranteed to have been scheduled yet, so
|
||||
// wait for the first concurrent poll before asking the poller to stop.
|
||||
const auto poll_deadline = std::chrono::steady_clock::now() + 2s;
|
||||
while (polls.load(std::memory_order_relaxed) == 0 &&
|
||||
std::chrono::steady_clock::now() < poll_deadline) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
stop_polling.store(true, std::memory_order_relaxed);
|
||||
poller.join();
|
||||
EXPECT_GT(polls.load(std::memory_order_relaxed), 0);
|
||||
|
||||
@@ -41,18 +41,26 @@ class FakeHttpClient final : public HttpClient {
|
||||
class BrowserTrackerServiceTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// The service refuses to hold credentials in a group- or world-writable
|
||||
// directory, so the directories these tests create must be private
|
||||
// whatever umask the developer or CI runner happens to use.
|
||||
previous_umask_ = umask(077);
|
||||
root_ = std::filesystem::temp_directory_path() /
|
||||
("mocktail-browser-tracker-" + std::to_string(getpid()) + "-" +
|
||||
::testing::UnitTest::GetInstance()->current_test_info()->name());
|
||||
std::filesystem::remove_all(root_);
|
||||
}
|
||||
void TearDown() override { std::filesystem::remove_all(root_); }
|
||||
void TearDown() override {
|
||||
std::filesystem::remove_all(root_);
|
||||
umask(previous_umask_);
|
||||
}
|
||||
|
||||
std::filesystem::path Storage() const {
|
||||
return root_ / "appData/LocalStorage/appStorage.json";
|
||||
}
|
||||
std::filesystem::path Cookies() const { return root_ / "roblox.cookie"; }
|
||||
std::filesystem::path root_;
|
||||
mode_t previous_umask_ = 0;
|
||||
};
|
||||
|
||||
TEST_F(BrowserTrackerServiceTest, ReusesValidStoredIdentityWithoutNetwork) {
|
||||
@@ -171,6 +179,58 @@ TEST_F(BrowserTrackerServiceTest,
|
||||
EXPECT_FALSE(std::filesystem::exists(Storage()));
|
||||
}
|
||||
|
||||
TEST_F(BrowserTrackerServiceTest, CreatesPrivateStorageDirectoryUnderAnyUmask) {
|
||||
std::filesystem::create_directories(root_);
|
||||
ASSERT_EQ(chmod(root_.c_str(), 0700), 0);
|
||||
std::ofstream(Cookies()) << ".ROBLOSECURITY=secret";
|
||||
FakeHttpClient http;
|
||||
http.response = {true,
|
||||
200,
|
||||
R"({"browserTrackerId":987654321})",
|
||||
"",
|
||||
{"Set-Cookie: RBXEventTrackerV2="
|
||||
"CreateDate=1&rbxid=2&browserid=987654321; Path=/"}};
|
||||
|
||||
// A permissive umask must not widen the directories the service creates for
|
||||
// its own credentials; it refuses to use any that are group or world
|
||||
// writable.
|
||||
const mode_t previous_umask = umask(0);
|
||||
const BrowserTrackerResult result =
|
||||
BrowserTrackerService(http).EnsureInitialized(Storage(), Cookies(), true);
|
||||
umask(previous_umask);
|
||||
|
||||
ASSERT_TRUE(result) << result.error;
|
||||
struct stat storage_parent = {};
|
||||
ASSERT_EQ(stat(Storage().parent_path().c_str(), &storage_parent), 0);
|
||||
EXPECT_EQ(storage_parent.st_mode & 0777, 0700U);
|
||||
}
|
||||
|
||||
TEST_F(BrowserTrackerServiceTest,
|
||||
CreatesUsableStorageDirectoryUnderRestrictiveUmask) {
|
||||
std::filesystem::create_directories(root_);
|
||||
ASSERT_EQ(chmod(root_.c_str(), 0700), 0);
|
||||
std::ofstream(Cookies()) << ".ROBLOSECURITY=secret";
|
||||
FakeHttpClient http;
|
||||
http.response = {true,
|
||||
200,
|
||||
R"({"browserTrackerId":987654321})",
|
||||
"",
|
||||
{"Set-Cookie: RBXEventTrackerV2="
|
||||
"CreateDate=1&rbxid=2&browserid=987654321; Path=/"}};
|
||||
|
||||
// mkdir applies the umask to its mode argument, so a umask that strips owner
|
||||
// bits would otherwise leave the service's own directory unwritable.
|
||||
const mode_t previous_umask = umask(0200);
|
||||
const BrowserTrackerResult result =
|
||||
BrowserTrackerService(http).EnsureInitialized(Storage(), Cookies(), true);
|
||||
umask(previous_umask);
|
||||
|
||||
ASSERT_TRUE(result) << result.error;
|
||||
struct stat storage_parent = {};
|
||||
ASSERT_EQ(stat(Storage().parent_path().c_str(), &storage_parent), 0);
|
||||
EXPECT_EQ(storage_parent.st_mode & 0777, 0700U);
|
||||
}
|
||||
|
||||
TEST_F(BrowserTrackerServiceTest, RejectsCorruptStoredIdentityFailClosed) {
|
||||
std::filesystem::create_directories(Storage().parent_path());
|
||||
std::ofstream(Storage()) << R"({"BrowserTrackerId":"0"})";
|
||||
|
||||
@@ -86,34 +86,35 @@ grep -Fq 'mz_stream_tell(unzGetStream(archive))' "${ANDROID_STUB}" ||
|
||||
Fail 'Android assets do not support the minizip-ng offset API'
|
||||
|
||||
for expected in \
|
||||
'ubuntu:26.04' \
|
||||
'fedora:44' \
|
||||
'archlinux:base-devel' \
|
||||
'minizip-ng-compat-devel' \
|
||||
'-G DEB' \
|
||||
'-G RPM' \
|
||||
'makepkg --dir' \
|
||||
'cron:' \
|
||||
'APPIMAGE_FORMAT=anylinux' \
|
||||
'quick-sharun' \
|
||||
'./scripts/install_anylinux_dependencies.sh' \
|
||||
'--env GH_TOKEN' \
|
||||
'MOCKTAIL_ANYLINUX_SYSTEM_INSTALL=1' \
|
||||
'--appimage-extract-and-run mocktail_updater status' \
|
||||
'Mocktail-x86_64.AppImage' \
|
||||
'Nightcap-nightly-x86_64.AppImage' \
|
||||
'github-actions[bot]' \
|
||||
'gh release upload continuous'; do
|
||||
grep -Fq -- "${expected}" "${WORKFLOW}" ||
|
||||
Fail "native package workflow is missing: ${expected}"
|
||||
Fail "nightly AppImage workflow is missing: ${expected}"
|
||||
done
|
||||
|
||||
grep -Fq 'paths-ignore:' "${WORKFLOW}" ||
|
||||
Fail 'native package workflow has no path filters'
|
||||
grep -Fq "'assets/screenshots/**'" "${WORKFLOW}" ||
|
||||
Fail 'native packages rebuild for screenshot-only changes'
|
||||
if grep -Fq "'packaging/flatpak/**'" "${WORKFLOW}"; then
|
||||
Fail 'native packages ignore Flatpak changes required by Pages publication'
|
||||
fi
|
||||
if grep -Fq "'site/**'" "${WORKFLOW}"; then
|
||||
Fail 'native packages ignore website changes required by Pages publication'
|
||||
fi
|
||||
# The nightly runs on a schedule and on demand. Per-commit triggers would
|
||||
# rebuild a full AppImage for every push and pull request. The DEB, RPM and
|
||||
# pacman packages are built by the Makefile but are not published from here;
|
||||
# upstream ships those for unmodified Mocktail.
|
||||
for unexpected in \
|
||||
'push:' \
|
||||
'pull_request:' \
|
||||
'-G DEB' \
|
||||
'-G RPM' \
|
||||
'makepkg --dir'; do
|
||||
if grep -Fq -- "${unexpected}" "${WORKFLOW}"; then
|
||||
Fail "nightly AppImage workflow should not contain: ${unexpected}"
|
||||
fi
|
||||
done
|
||||
|
||||
grep -Fq 'Ubuntu 26.04+' "${README}" ||
|
||||
Fail 'README has no Ubuntu source dependency guide'
|
||||
|
||||
@@ -51,6 +51,9 @@ class TemporaryDirectory final {
|
||||
void Write(const std::filesystem::path& path, std::string_view contents) {
|
||||
std::filesystem::create_directories(path.parent_path());
|
||||
std::ofstream(path, std::ios::binary) << contents;
|
||||
// Approval artifacts are rejected when group or world writable, which a
|
||||
// permissive umask would otherwise leave them.
|
||||
chmod(path.c_str(), 0600);
|
||||
}
|
||||
|
||||
std::string ReadFile(const std::filesystem::path& path) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#include "runtime/performance_policy.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
@@ -5,6 +6,7 @@
|
||||
#define JSON_NOEXCEPTION 1
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace mocktail {
|
||||
namespace runtime {
|
||||
@@ -228,22 +230,50 @@ TEST(PerformancePolicyTest, ComposesFrameRateBeforePerformanceMode) {
|
||||
EXPECT_EQ(parsed.at("FFlagLuaAppDefaultHttpRetry"), "False");
|
||||
}
|
||||
|
||||
TEST(PerformancePolicyTest, RejectsConflictingOrMalformedOverrides) {
|
||||
TEST(PerformancePolicyTest, KeepsUserOverridesThatConflictWithPreset) {
|
||||
std::string merged;
|
||||
std::string error;
|
||||
std::vector<std::string> kept;
|
||||
ASSERT_TRUE(MergePerformanceClientSettingsOverrides(
|
||||
{true, 14},
|
||||
R"({"FIntOcclusionWorkerThreadCount":"2","FFlagMovePrerenderV2":true,)"
|
||||
R"("FIntDebugForceMSAASamples":4})",
|
||||
&merged, &error, &kept))
|
||||
<< error;
|
||||
const nlohmann::json parsed = nlohmann::json::parse(merged);
|
||||
EXPECT_EQ(parsed.at("FIntOcclusionWorkerThreadCount"), "2");
|
||||
EXPECT_EQ(parsed.at("FFlagMovePrerenderV2"), true);
|
||||
EXPECT_EQ(parsed.at("FIntDebugForceMSAASamples"), 4);
|
||||
EXPECT_EQ(parsed.at("FFlagFastGPULightGrid"), "True");
|
||||
EXPECT_EQ(kept, (std::vector<std::string>{"FIntOcclusionWorkerThreadCount",
|
||||
"FFlagMovePrerenderV2",
|
||||
"FIntDebugForceMSAASamples"}));
|
||||
|
||||
PerformancePolicy latency =
|
||||
ParsePerformancePolicy("true", "0", "auto", "latency");
|
||||
latency.physical_core_count = 14;
|
||||
kept.clear();
|
||||
ASSERT_TRUE(MergePerformanceClientSettingsOverrides(
|
||||
latency, R"({"DFIntSimMidPhaseContactPipelineBatchSize":"64"})", &merged,
|
||||
&error, &kept))
|
||||
<< error;
|
||||
EXPECT_EQ(nlohmann::json::parse(merged)
|
||||
.at("DFIntSimMidPhaseContactPipelineBatchSize"),
|
||||
"64");
|
||||
EXPECT_EQ(kept, (std::vector<std::string>{
|
||||
"DFIntSimMidPhaseContactPipelineBatchSize"}));
|
||||
|
||||
kept.clear();
|
||||
ASSERT_TRUE(MergeRuntimeClientSettingsOverrides(
|
||||
ParseFrameRatePolicy(""), {true, 14},
|
||||
R"({"FIntOcclusionWorkerThreadCount":"2"})", &merged, &error, &kept))
|
||||
<< error;
|
||||
EXPECT_EQ(kept, (std::vector<std::string>{"FIntOcclusionWorkerThreadCount"}));
|
||||
}
|
||||
|
||||
TEST(PerformancePolicyTest, RejectsMalformedOverrides) {
|
||||
std::string merged = "unchanged";
|
||||
std::string error;
|
||||
EXPECT_FALSE(MergePerformanceClientSettingsOverrides(
|
||||
{true, 14}, R"({"FIntOcclusionWorkerThreadCount":"2"})", &merged,
|
||||
&error));
|
||||
EXPECT_EQ(merged, "unchanged");
|
||||
EXPECT_NE(error.find("FIntOcclusionWorkerThreadCount"), std::string::npos);
|
||||
|
||||
error.clear();
|
||||
EXPECT_FALSE(MergePerformanceClientSettingsOverrides(
|
||||
{true, 14}, R"({"FFlagMovePrerenderV2":true})", &merged, &error));
|
||||
EXPECT_EQ(merged, "unchanged");
|
||||
EXPECT_NE(error.find("FFlagMovePrerenderV2"), std::string::npos);
|
||||
|
||||
error.clear();
|
||||
EXPECT_FALSE(MergePerformanceClientSettingsOverrides({true, 14}, "not-json",
|
||||
&merged, &error));
|
||||
EXPECT_EQ(merged, "unchanged");
|
||||
@@ -254,16 +284,6 @@ TEST(PerformancePolicyTest, RejectsConflictingOrMalformedOverrides) {
|
||||
&merged, &error));
|
||||
EXPECT_EQ(merged, "unchanged");
|
||||
EXPECT_NE(error.find("JSON object"), std::string::npos);
|
||||
|
||||
PerformancePolicy latency =
|
||||
ParsePerformancePolicy("true", "0", "auto", "latency");
|
||||
latency.physical_core_count = 14;
|
||||
error.clear();
|
||||
EXPECT_FALSE(MergePerformanceClientSettingsOverrides(
|
||||
latency, R"({"DFIntSimMidPhaseContactPipelineBatchSize":"64"})", &merged,
|
||||
&error));
|
||||
EXPECT_NE(error.find("DFIntSimMidPhaseContactPipelineBatchSize"),
|
||||
std::string::npos);
|
||||
}
|
||||
|
||||
TEST(PerformancePolicyTest, HostAudioMenuUsesBridgedDeviceQueries) {
|
||||
|
||||
@@ -238,6 +238,71 @@ TEST_F(RobloxInputRouterTest, RoutesMouseMotionButtonAndVerticalWheel) {
|
||||
EXPECT_FLOAT_EQ(probe_.mouse_wheels[0].delta_y, -2.0f);
|
||||
}
|
||||
|
||||
TEST_F(RobloxInputRouterTest,
|
||||
KeepsRelativeMotionResponsiveAfterOvershootingViewportEdge) {
|
||||
// Relative-mode motion reports zero absolute coordinates, so the router
|
||||
// accumulates deltas. The accumulator stays inside the viewport: a reversal
|
||||
// moves the pointer on the next event, however far the overshoot ran.
|
||||
ASSERT_TRUE(
|
||||
router_
|
||||
.HandleEvent(Event(platform::MouseMotionEvent{640.0f, 360.0f, 0.0f,
|
||||
0.0f, 0}))
|
||||
.dispatched());
|
||||
ASSERT_TRUE(
|
||||
router_
|
||||
.HandleEvent(Event(platform::MouseMotionEvent{0.0f, 0.0f, 2000.0f,
|
||||
0.0f, 0}))
|
||||
.dispatched());
|
||||
ASSERT_TRUE(
|
||||
router_
|
||||
.HandleEvent(Event(platform::MouseMotionEvent{0.0f, 0.0f, -100.0f,
|
||||
0.0f, 0}))
|
||||
.dispatched());
|
||||
|
||||
ASSERT_EQ(probe_.mouse_moves.size(), 3U);
|
||||
EXPECT_FLOAT_EQ(probe_.mouse_moves[1].x, 1279.0f);
|
||||
EXPECT_FLOAT_EQ(probe_.mouse_moves[2].x, 1179.0f);
|
||||
EXPECT_FLOAT_EQ(probe_.mouse_moves[2].delta_x, -100.0f);
|
||||
}
|
||||
|
||||
// A fractionally scaled X11 desktop: the buffer is 1:1 with logical units
|
||||
// while the display scale is not, which is the case where the guest-surface
|
||||
// scale actually changes a look delta.
|
||||
constexpr platform::WindowResizedEvent kFractionalScalingViewport{
|
||||
1624, 811, 1624, 811, 1.145833F};
|
||||
|
||||
TEST_F(RobloxInputRouterTest, ScalesLookDeltasUnlessRawMouseIsEnabled) {
|
||||
ASSERT_EQ(router_.HandleEvent(Event(kFractionalScalingViewport)).state,
|
||||
RobloxInputDispatchState::kStateUpdated);
|
||||
ASSERT_TRUE(
|
||||
router_
|
||||
.HandleEvent(Event(platform::MouseMotionEvent{0.0f, 0.0f, 10.0f,
|
||||
-6.0f, 0}))
|
||||
.dispatched());
|
||||
|
||||
ASSERT_EQ(probe_.mouse_moves.size(), 1U);
|
||||
EXPECT_NEAR(probe_.mouse_moves[0].delta_x, 8.727f, 0.01f);
|
||||
EXPECT_NEAR(probe_.mouse_moves[0].delta_y, -5.236f, 0.01f);
|
||||
}
|
||||
|
||||
TEST_F(RobloxInputRouterTest, RawMouseSendsLookDeltasUnscaled) {
|
||||
ASSERT_EQ(router_.HandleEvent(Event(kFractionalScalingViewport)).state,
|
||||
RobloxInputDispatchState::kStateUpdated);
|
||||
router_.SetRawMouseEnabled(true);
|
||||
ASSERT_TRUE(
|
||||
router_
|
||||
.HandleEvent(Event(platform::MouseMotionEvent{0.0f, 0.0f, 10.0f,
|
||||
-6.0f, 0}))
|
||||
.dispatched());
|
||||
|
||||
ASSERT_EQ(probe_.mouse_moves.size(), 1U);
|
||||
EXPECT_FLOAT_EQ(probe_.mouse_moves[0].delta_x, 10.0f);
|
||||
EXPECT_FLOAT_EQ(probe_.mouse_moves[0].delta_y, -6.0f);
|
||||
// The reported position stays in guest units whatever the delta carries.
|
||||
EXPECT_NEAR(probe_.mouse_moves[0].x, 8.727f, 0.01f);
|
||||
EXPECT_FLOAT_EQ(probe_.mouse_moves[0].y, 0.0f);
|
||||
}
|
||||
|
||||
TEST_F(RobloxInputRouterTest,
|
||||
ScalesMouseCoordinatesAfterDisplayScaleChange) {
|
||||
const RobloxInputDispatchResult resize = router_.HandleEvent(
|
||||
|
||||
@@ -156,9 +156,9 @@ TEST(RuntimeConfigBootstrapTest,
|
||||
"startup.\n output_device: default",
|
||||
"# between boots; prefer the exact device name when it is unique.\n"
|
||||
" input_device: default",
|
||||
"# Boolean (default: false): publish Mocktail activity to Discord "
|
||||
"# Boolean (default: true): publish Nightcap activity to Discord "
|
||||
"Desktop.\n # This never signs in to Discord and never reads an "
|
||||
"account token.\n enabled: false",
|
||||
"account token.\n enabled: true",
|
||||
"# Boolean (default: true): show the current Roblox experience "
|
||||
"name.\n show_place_name: true",
|
||||
"# Boolean (default: true): show how long the current session has "
|
||||
@@ -288,6 +288,28 @@ TEST(RuntimeConfigFileTest, RejectsSymlinkAndOversizedConfiguration) {
|
||||
EXPECT_NE(loaded.error.find("no larger than 1 MiB"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST(RuntimeConfigFileTest, RawMouseDefaultsOffAndLoadsFromYaml) {
|
||||
TemporaryDirectory defaulted_directory;
|
||||
const RuntimeConfigLoadResult defaulted = LoadRuntimeConfig(
|
||||
MapEnvironment(), defaulted_directory.Write(R"yaml(
|
||||
version: 1
|
||||
input:
|
||||
touch_enabled: false
|
||||
)yaml"));
|
||||
ASSERT_TRUE(defaulted) << defaulted.error;
|
||||
EXPECT_FALSE(defaulted.config.input_capabilities().raw_mouse);
|
||||
|
||||
TemporaryDirectory enabled_directory;
|
||||
const RuntimeConfigLoadResult enabled = LoadRuntimeConfig(
|
||||
MapEnvironment(), enabled_directory.Write(R"yaml(
|
||||
version: 1
|
||||
input:
|
||||
raw_mouse: true
|
||||
)yaml"));
|
||||
ASSERT_TRUE(enabled) << enabled.error;
|
||||
EXPECT_TRUE(enabled.config.input_capabilities().raw_mouse);
|
||||
}
|
||||
|
||||
TEST(RuntimeConfigFileTest, LoadsTypedDesktopAndGraphicsSettings) {
|
||||
TemporaryDirectory temporary;
|
||||
const std::filesystem::path file = temporary.Write(R"yaml(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
||||
#include "runtime/runtime_config.h"
|
||||
#include "runtime/system_proxy.h"
|
||||
|
||||
@@ -74,13 +75,13 @@ TEST(RuntimeConfigTest, UsesSupportedDefaults) {
|
||||
EXPECT_FALSE(config.fleasion_enabled());
|
||||
EXPECT_TRUE(config.fleasion_valid());
|
||||
EXPECT_FALSE(config.network_proxy().has_value());
|
||||
EXPECT_FALSE(config.discord_rpc().enabled);
|
||||
EXPECT_TRUE(config.discord_rpc().enabled);
|
||||
EXPECT_TRUE(config.discord_rpc().show_place_name);
|
||||
EXPECT_TRUE(config.discord_rpc().show_elapsed_time);
|
||||
EXPECT_TRUE(config.discord_rpc().join_enabled);
|
||||
EXPECT_TRUE(config.discord_rpc().public_servers_only);
|
||||
EXPECT_EQ(config.discord_rpc().join_button_label, "Join Server");
|
||||
EXPECT_EQ(config.discord_rpc().application_id, "1537088975720812655");
|
||||
EXPECT_EQ(config.discord_rpc().application_id, "1549428231927500843");
|
||||
EXPECT_EQ(config.discord_rpc().text.browsing, "Browsing experiences");
|
||||
EXPECT_EQ(config.discord_rpc().text.joining, "Joining an experience");
|
||||
EXPECT_EQ(config.discord_rpc().text.playing, "{place_name}");
|
||||
|
||||
@@ -349,14 +349,18 @@ TEST(WebViewHelperLauncherTest, OwnsAndClosesTheExactSpawnedProcess) {
|
||||
const std::filesystem::path helper = temporary.path() / "fake-helper";
|
||||
const std::filesystem::path close_packet = temporary.path() / "close-packet";
|
||||
const std::filesystem::path ready = temporary.path() / "ready";
|
||||
ASSERT_TRUE(WriteExecutable(helper,
|
||||
"#!/bin/sh\n"
|
||||
"cat >/dev/null\n"
|
||||
"touch '" +
|
||||
ready.string() +
|
||||
"'\n"
|
||||
"dd bs=65548 count=1 status=none <&198 >'" +
|
||||
close_packet.string() + "'\n"));
|
||||
// dash rejects multi-digit file descriptors, so the control socket is read
|
||||
// in python3 rather than through a `<&198` redirection.
|
||||
ASSERT_TRUE(WriteExecutable(
|
||||
helper, "#!/bin/sh\n"
|
||||
"cat >/dev/null\n"
|
||||
"touch '" +
|
||||
ready.string() +
|
||||
"'\n"
|
||||
"python3 -c 'import socket,sys; s=socket.socket(fileno=198); "
|
||||
"s.settimeout(2); "
|
||||
"open(sys.argv[1],\"wb\").write(s.recv(65548))' '" +
|
||||
close_packet.string() + "'\n"));
|
||||
|
||||
const WebViewHelperLaunchResult result =
|
||||
LaunchWebViewHelper(helper, "https://www.roblox.com/login");
|
||||
|
||||
Reference in new issue
Block a user