first version

This commit is contained in:
miyu0-bit committed 2026-09-30 04:28:38 -03:00
commit 3d43bfe5ca
47 files changed
+25167

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
# Build environment for FrameEye Helper (Windows + Linux) and the Linux panel preview.
# Everything is built inside this container so nothing has to be installed on the host.
# podman build -t frameeye-build -f build/Containerfile build/
FROM registry.fedoraproject.org/fedora:44
RUN dnf -y install --setopt=install_weak_deps=False \
rust cargo rust-std-static-x86_64-pc-windows-gnu \
mingw64-gcc mingw64-binutils mingw64-winpthreads-static \
gcc gcc-c++ cmake ninja-build pkgconf-pkg-config make \
cairo-devel freetype-devel sdl2-compat-devel vulkan-loader-devel vulkan-headers \
file python3 \
&& dnf clean all
ENV CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER=x86_64-w64-mingw32-gcc \
CC_x86_64_pc_windows_gnu=x86_64-w64-mingw32-gcc \
AR_x86_64_pc_windows_gnu=x86_64-w64-mingw32-ar \
CARGO_HOME=/cargo-home
WORKDIR /src
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Builds FrameEye Helper for Windows (x86_64, .exe) and Linux inside the frameeye-build container,
# runs the unit tests, and copies the results to dist/.
# build/build-helper.sh (from the project root, or anywhere)
set -euo pipefail
root="$(cd "$(dirname "$0")/.." && pwd)"
image=frameeye-build
if ! podman image exists "$image"; then
podman build -t "$image" -f "$root/build/Containerfile" "$root/build"
fi
mkdir -p "$root/build/cargo-home" "$root/dist"
podman run --rm --userns=keep-id \
-v "$root/helper:/src:Z" \
-v "$root/build/cargo-home:/cargo-home:Z" \
-e CARGO_TARGET_DIR=/src/target-container \
"$image" bash -c '
set -euo pipefail
cargo test --release --locked
cargo build --release --locked --target x86_64-unknown-linux-gnu
cargo build --release --locked --target x86_64-pc-windows-gnu
'
cp "$root/helper/target-container/x86_64-pc-windows-gnu/release/FrameEyeHelper.exe" "$root/dist/FrameEyeHelper.exe"
cp "$root/helper/target-container/x86_64-unknown-linux-gnu/release/FrameEyeHelper" "$root/dist/FrameEyeHelper-linux"
ls -la "$root/dist"
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Builds the Linux preview of the headset's Eye panel inside the frameeye-build container:
# frameeyeosc (the headset program, for this PC), the pretend eye tracker, and the panel as a window.
# Results go to preview/bin/.
set -euo pipefail
root="$(cd "$(dirname "$0")/.." && pwd)"
image=frameeye-build
if ! podman image exists "$image"; then
podman build -t "$image" -f "$root/build/Containerfile" "$root/build"
fi
mkdir -p "$root/build/cargo-home" "$root/preview/bin"
podman run --rm --userns=keep-id \
-v "$root:/w:Z" \
-v "$root/build/cargo-home:/cargo-home:Z" \
"$image" bash -c '
set -euo pipefail
cd /w/upstream/frameeyeosc && cargo build --release --locked --target-dir /w/build/target-upstream
cd /w/preview/fake-eye-server && cargo build --release --target-dir /w/build/target-fake
cmake -G Ninja -S /w/preview/panel-desktop -B /w/build/panel-preview -DCMAKE_BUILD_TYPE=Release >/dev/null
ninja -C /w/build/panel-preview
'
cp "$root/build/target-upstream/release/frameeyeosc" "$root/preview/bin/"
cp "$root/build/target-fake/release/fake-eye-server" "$root/preview/bin/"
cp "$root/build/panel-preview/frameeyeosc-panel-preview" "$root/preview/bin/"
chmod +x "$root/preview/run-panel-preview.sh"
ls -la "$root/preview/bin"
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env bash
# Puts the finished downloads together in dist/ (run build-helper.sh and build-preview.sh first):
# dist/FrameEyeHelper.exe the Windows app
# dist/FrameEyeHelper-Windows.zip the same, with a short "read me first", for sending
# dist/linux/ the same app for Linux, plus the Eye panel preview
# dist/FrameEyeHelper-Linux.tar.gz dist/linux/ packed up for the release page
set -euo pipefail
root="$(cd "$(dirname "$0")/.." && pwd)"
dist="$root/dist"
if [[ -f "$dist/linux/FrameEyeHelper" && ! -f "$dist/FrameEyeHelper-linux" ]]; then
mv "$dist/linux/FrameEyeHelper" "$dist/FrameEyeHelper-linux" # packaging again
fi
test -f "$dist/FrameEyeHelper.exe" && test -f "$dist/FrameEyeHelper-linux" || { echo "run build/build-helper.sh first" >&2; exit 1; }
test -x "$root/preview/bin/frameeyeosc-panel-preview" || { echo "run build/build-preview.sh first" >&2; exit 1; }
rm -rf "$dist/linux" "$dist/FrameEyeHelper-Windows.zip"
mkdir -p "$dist/linux/FrameEye-Panel-Preview/bin"
mv "$dist/FrameEyeHelper-linux" "$dist/linux/FrameEyeHelper"
cp "$root/preview/run-panel-preview.sh" "$dist/linux/FrameEye-Panel-Preview/"
cp "$root/preview/bin/"* "$dist/linux/FrameEye-Panel-Preview/bin/"
cp "$root/helper/assets/FRAMEEYEOSC_LICENSE.txt" "$dist/linux/FrameEye-Panel-Preview/FRAMEEYEOSC_LICENSE.txt"
cp "$root/upstream/frameeyeosc/THIRD_PARTY_LICENSES.md" "$dist/linux/FrameEye-Panel-Preview/FRAMEEYEOSC_THIRD_PARTY_LICENSES.md"
cp "$root/LICENSE" "$root/THIRD_PARTY_LICENSES.md" "$dist/linux/"
cat > "$dist/linux/README.txt" <<'EOF'
FrameEye Helper for Linux: the same app as FrameEyeHelper.exe, plus a preview of the headset's Eye panel.
FrameEyeHelper
Double-click it (or run ./FrameEyeHelper). "Try the demo" on the first page walks through every
step with a pretend headset. The "Open the Eye panel preview" button (Linux only) opens the
in-headset Eye panel in a window.
FrameEye-Panel-Preview/run-panel-preview.sh
The same preview from a terminal. It runs the real frameeyeosc and its Eye panel, fed by a pretend
Steam Frame. Space (or the button at the top) closes and opens the pretend dashboard; with it closed
you see what the headset shows, like the eye fit's dot. --reset starts over with default settings.
Nothing here sends anything to VRChat: the preview's frameeyeosc runs in its own empty network, and its
settings live in a scratch folder ($XDG_RUNTIME_DIR/frameeye-preview) that is gone after logging out.
Needs SDL2, cairo, freetype and the Noto Sans CJK fonts (all standard on Fedora).
EOF
readme="$(mktemp)"
cat > "$readme" <<'EOF'
FrameEye Helper - Steam Frame eye tracking for VRChat
======================================================
1. Double-click FrameEyeHelper.exe.
If Windows says "Windows protected your PC", click "More info", then "Run anyway".
(The app isn't signed by a company, so Windows doesn't know it yet. Nothing gets installed on this PC.)
2. Follow the 4 steps in the window:
Prepare headset -> Connect -> Install -> Check it works
You only need it once. After that, eye tracking starts by itself whenever SteamVR runs on the headset.
EOF
python3 - "$dist/FrameEyeHelper-Windows.zip" "$dist/FrameEyeHelper.exe" "$readme" "$root/LICENSE" "$root/THIRD_PARTY_LICENSES.md" <<'EOF'
import sys, zipfile
out, exe, readme, license_file, third_party = sys.argv[1:6]
crlf = lambda path: open(path, encoding="utf-8").read().replace("\n", "\r\n")
with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED, compresslevel=9) as z:
z.write(exe, "FrameEyeHelper/FrameEyeHelper.exe")
z.writestr("FrameEyeHelper/READ ME FIRST.txt", crlf(readme))
z.writestr("FrameEyeHelper/LICENSE.txt", crlf(license_file))
z.writestr("FrameEyeHelper/THIRD_PARTY_LICENSES.md", crlf(third_party))
EOF
rm -f "$readme"
rm -f "$dist/FrameEyeHelper-Linux.tar.gz"
tar -C "$dist" --transform 's,^linux,FrameEyeHelper-Linux,' -czf "$dist/FrameEyeHelper-Linux.tar.gz" linux
(cd "$dist" && sha256sum FrameEyeHelper-Windows.zip FrameEyeHelper-Linux.tar.gz FrameEyeHelper.exe > SHA256SUMS)
find "$dist" -type f -printf '%10s %P\n' | sort -k2
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""Writes THIRD_PARTY_LICENSES.md: the licenses of the Rust crates built into FrameEyeHelper (Windows and
Linux) and the preview's fake-eye-server. Crates with the same license text are listed together.
CARGO_HOME=build/cargo-home python3 build/third-party-licenses.py > THIRD_PARTY_LICENSES.md
"""
import hashlib
import json
import os
import subprocess
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECTS = [
("helper", ["x86_64-pc-windows-gnu", "x86_64-unknown-linux-gnu"]),
("preview/fake-eye-server", ["x86_64-unknown-linux-gnu"]),
]
LICENSE_NAMES = ("license", "licence", "copying", "notice", "unlicense")
def linked_packages(project, target):
"""Packages reachable from the project through normal dependencies (build scripts' own deps are left out)."""
out = subprocess.run(
["cargo", "metadata", "--format-version", "1", "--filter-platform", target],
cwd=os.path.join(ROOT, project), check=True, capture_output=True, text=True,
).stdout
meta = json.loads(out)
packages = {p["id"]: p for p in meta["packages"]}
nodes = {n["id"]: n for n in meta["resolve"]["nodes"]}
root = meta["resolve"]["root"]
seen, stack = set(), [root]
while stack:
node = nodes[stack.pop()]
for dep in node["deps"]:
if any(kind["kind"] is None for kind in dep["dep_kinds"]) and dep["pkg"] not in seen:
seen.add(dep["pkg"])
stack.append(dep["pkg"])
return [packages[i] for i in seen]
def license_files(package):
folder = os.path.dirname(package["manifest_path"])
candidates = [(name, os.path.join(folder, name)) for name in sorted(os.listdir(folder))]
# egui's font crate keeps the licenses of the fonts it embeds next to them
fonts = os.path.join(folder, "fonts")
if os.path.isdir(fonts):
candidates += [(f"fonts/{name}", os.path.join(fonts, name)) for name in sorted(os.listdir(fonts)) if name.endswith(".txt")]
found = []
for name, path in candidates:
base = os.path.basename(name).lower()
if os.path.isfile(path) and (base.startswith(LICENSE_NAMES) or name.startswith("fonts/")):
with open(path, encoding="utf-8", errors="replace") as f:
found.append((name, f.read().strip()))
return found
def main():
crates = {}
for project, targets in PROJECTS:
for target in targets:
for package in linked_packages(project, target):
if package["source"] is None: # our own crates
continue
crates[(package["name"], package["version"])] = package
groups = {} # text hash -> (text, [crate labels])
no_file = []
for (name, version), package in sorted(crates.items()):
label = f"{name} {version}"
files = license_files(package)
if not files:
authors = ", ".join(package.get("authors") or []) or "its authors"
source = package.get("repository") or package.get("homepage") or ""
no_file.append(f"{label}: {package.get('license') or 'no license field'}, (c) {authors} {source}".rstrip())
continue
for filename, text in files:
key = hashlib.sha256(text.encode()).hexdigest()
groups.setdefault(key, (filename, text, []))[2].append(label)
print("# Third-party licenses\n")
print("FrameEye Helper is MIT licensed (see LICENSE). Its binaries contain the Rust crates below; each")
print("crate's license text follows, crates with the same text grouped together.\n")
print("frameeyeosc itself (bundled in the helper, and built into the Linux preview) is MIT, (c) konsti219 and")
print("sasaken1102r; its own third-party licenses ship inside its release and in its repository.\n")
print(f"{len(crates)} crates.\n")
for filename, text, labels in sorted(groups.values(), key=lambda g: g[2][0]):
print("---\n")
print(f"**{', '.join(labels)}** ({filename})\n")
print("```")
print(text.replace("```", "'''"))
print("```\n")
if no_file:
print("---\n")
print("These crates publish no license file; their license comes from their manifest. The standard")
print("texts (MIT, Apache-2.0, ...) appear above with other crates, and at https://spdx.org/licenses/.\n")
for label in no_file:
print(f"- {label}")
if __name__ == "__main__":
sys.exit(main())