From a787655a6a482f1686fc70b8e1cb0797e107b2fd Mon Sep 17 00:00:00 2001 From: kalopsia-space Date: Sat, 17 Oct 2026 14:44:10 +0100 Subject: [PATCH 01/12] build: keep keyboard Windows-only and pin the uploader's Linux keyring backend 2.0 replaced the Linux-only jeepney pin with an unconditional keyboard pin. FTHR_UI never imports keyboard; only FTHR.spec names it as a hidden import, so it gets a win32 marker and jeepney (GlobalShortcuts portal) comes back with its non-Windows marker. Restore keyboard's licence text, which the merge lost because the linux branch had dropped the package. keyring resolves its Secret Service backend through SecretStorage and jeepney on Linux. Pin both with linux markers in requirements-uploader.txt so the frozen uploader is reproducible, and add their licence texts to the optional-uploader inventory. Every pin has a manylinux_2_34-or-older wheel for CPython 3.12 and 3.14, inside the glibc 2.35 baseline. --- licenses/keyboard-LICENSE.txt | 21 ++++++++++++++++ .../optional-uploader/SecretStorage/LICENSE | 25 +++++++++++++++++++ licenses/optional-uploader/jeepney/LICENSE | 21 ++++++++++++++++ requirements-alpha.txt | 1 + requirements-uploader.txt | 3 +++ requirements.in | 10 ++++++-- 6 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 licenses/keyboard-LICENSE.txt create mode 100644 licenses/optional-uploader/SecretStorage/LICENSE create mode 100644 licenses/optional-uploader/jeepney/LICENSE diff --git a/licenses/keyboard-LICENSE.txt b/licenses/keyboard-LICENSE.txt new file mode 100644 index 00000000..4f3cb7b8 --- /dev/null +++ b/licenses/keyboard-LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Lucas Boppre Niehues + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licenses/optional-uploader/SecretStorage/LICENSE b/licenses/optional-uploader/SecretStorage/LICENSE new file mode 100644 index 00000000..076e6425 --- /dev/null +++ b/licenses/optional-uploader/SecretStorage/LICENSE @@ -0,0 +1,25 @@ +Copyright 2012-2025 Dmitry Shachnev +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of the University nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/optional-uploader/jeepney/LICENSE b/licenses/optional-uploader/jeepney/LICENSE new file mode 100644 index 00000000..b0ae9dbc --- /dev/null +++ b/licenses/optional-uploader/jeepney/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Thomas Kluyver + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/requirements-alpha.txt b/requirements-alpha.txt index 95cf49da..c6231241 100644 --- a/requirements-alpha.txt +++ b/requirements-alpha.txt @@ -7,6 +7,7 @@ PySide6==6.11.1 opencv-python-headless==4.13.0.92 numpy==2.4.4 sounddevice==0.5.5 +keyboard==0.13.5; sys_platform == "win32" jeepney==0.9.0; sys_platform != "win32" # Transitive dependencies — pinned so a silent upstream release cannot change diff --git a/requirements-uploader.txt b/requirements-uploader.txt index 160317ed..6405585b 100644 --- a/requirements-uploader.txt +++ b/requirements-uploader.txt @@ -12,3 +12,6 @@ jaraco.context==6.1.2 jaraco.functools==4.6.0 more-itertools==11.1.0 pywin32-ctypes==0.2.3 ; sys_platform == 'win32' +# keyring's Secret Service backend on Linux (D-Bus, pure Python). +SecretStorage==3.5.0 ; sys_platform == 'linux' +jeepney==0.9.0 ; sys_platform == 'linux' diff --git a/requirements.in b/requirements.in index b66c9178..d85cde52 100644 --- a/requirements.in +++ b/requirements.in @@ -16,5 +16,11 @@ numpy>=2.4,<3 sounddevice>=0.5,<0.6 # Input -# Windows keyboard integration. -keyboard>=0.13.5,<0.14 +# Windows keyboard integration (named as a PyInstaller hidden import by +# FTHR.spec). Never installed on Linux. +keyboard>=0.13.5,<0.14; sys_platform == "win32" +# Linux global hotkeys go through the XDG GlobalShortcuts portal over D-Bus +# (core/portal_shortcuts.py). jeepney is pure Python and can marshal the +# a(sa{sv}) struct arrays that PySide6's QtDBus cannot. Windows uses +# RegisterHotKey natively; the app never reads input devices itself. +jeepney>=0.8,<1.0; sys_platform != "win32" From 5fcee9f8f94aac9242cedffa708e2c17c781dab5 Mon Sep 17 00:00:00 2001 From: kalopsia-space Date: Sat, 17 Oct 2026 14:44:26 +0100 Subject: [PATCH 02/12] build(uploader): share plugin identity and Core bindings between both builders 2.0's _write_core_manifest regenerates uploader_bundle_manifest.py wholesale, which erased EXPECTED_UPLOADER_LINUX_BUNDLE_SHA256 on every Windows build, while build_linux_uploader.py still sealed the 1.1.0 plugin against the 2026-08-24 terms. build_optional_uploaders.py is now the one source of the plugin version, legal versions, licence inventory and binding format: render_core_manifest() regenerates the file from those constants and carries the other platform's hash over, and the Linux builder imports it. Both builders now refuse to freeze when requirements-uploader.txt is not satisfied. PyInstaller otherwise builds an executable without paramiko and the sealed package only fails when it runs, which is why "Test built optional upload packages" fails on Windows. The Linux builder also embeds the optional-uploader licences like the Windows package, names sftp_provider and keyring's Secret Service backend as hidden imports, drops the FTHR_Hardware_ID path 2.0 removed, and unpacks the ELF members of its one-file payload so the glibc gate can inspect them. --- BUILDING.md | 9 ++ tests/test_uploader_builders.py | 81 ++++++++++++++ tools/build_linux_uploader.py | 179 +++++++++++++++++++----------- tools/build_optional_uploaders.py | 118 +++++++++++++++++--- 4 files changed, 308 insertions(+), 79 deletions(-) create mode 100644 tests/test_uploader_builders.py diff --git a/BUILDING.md b/BUILDING.md index 4a3d5416..bfbfeb83 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -23,6 +23,15 @@ python -m pip install -r requirements-alpha.txt -r requirements-dev.txt `requirements-alpha.txt` is the lock and the only file a release build installs from. +Building the optional uploader package (`tools/build_optional_uploaders.py` on +Windows, `tools/build_linux_uploader.py`, which `build_linux.sh` runs, on +Linux) also needs its own lock in the same environment. Both builders refuse +to run without it: + +```bash +python -m pip install -r requirements-uploader.txt +``` + > **Do not install `imageio-ffmpeg`.** It bundles a GPLv3 FFmpeg build. Having it > in the environment risks PyInstaller pulling it into the bundle and placing the > whole artifact under the GPL (AUDIT-005). It is excluded in `FTHR.spec` and diff --git a/tests/test_uploader_builders.py b/tests/test_uploader_builders.py new file mode 100644 index 00000000..1377ccfd --- /dev/null +++ b/tests/test_uploader_builders.py @@ -0,0 +1,81 @@ +"""The Windows and Linux uploader builders share one plugin identity and +cooperate on FTHR_UI/core/uploader_bundle_manifest.py.""" + +from __future__ import annotations + +import runpy +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +WINDOWS_BUILDER = runpy.run_path(str(ROOT / 'tools' / 'build_optional_uploaders.py')) +render = WINDOWS_BUILDER['render_core_manifest'] + + +def _manifest(windows: str, linux: str | None = None) -> str: + return render('', windows_hash=windows, linux_hash=linux) + + +def test_windows_build_keeps_the_linux_bundle_hash(): + existing = _manifest('old-windows', 'linux-hash') + result = render(existing, windows_hash='new-windows') + assert "EXPECTED_UPLOADER_BUNDLE_SHA256 = 'new-windows'" in result + assert "EXPECTED_UPLOADER_LINUX_BUNDLE_SHA256 = 'linux-hash'" in result + + +def test_linux_build_keeps_the_windows_bundle_hash(): + existing = _manifest('windows-hash') + result = render(existing, linux_hash='new-linux') + assert "EXPECTED_UPLOADER_BUNDLE_SHA256 = 'windows-hash'" in result + assert "EXPECTED_UPLOADER_LINUX_BUNDLE_SHA256 = 'new-linux'" in result + + +def test_rendering_is_stable_for_the_committed_manifest(): + committed = (ROOT / 'FTHR_UI' / 'core' / 'uploader_bundle_manifest.py').read_text( + encoding='utf-8') + assert render(committed) == committed + + +def test_regenerated_manifest_takes_versions_from_the_shared_builder(): + stale = '''UPLOADER_PLUGIN_VERSION = '1.1.0' +UPLOADER_TERMS_VERSION = '2026-08-24-v1' +EXPECTED_UPLOADER_BUNDLE_SHA256 = 'windows' +EXPECTED_UPLOADER_LINUX_BUNDLE_SHA256 = 'linux' +''' + namespace: dict[str, object] = {} + exec(render(stale, linux_hash='new-linux'), namespace) # noqa: S102 + assert namespace['UPLOADER_PLUGIN_VERSION'] == WINDOWS_BUILDER['UPLOADER_PLUGIN_VERSION'] + assert namespace['UPLOADER_TERMS_VERSION'] == WINDOWS_BUILDER['UPLOADER_TERMS_VERSION'] + assert namespace['UPLOADER_PRIVACY_VERSION'] == WINDOWS_BUILDER['UPLOADER_PRIVACY_VERSION'] + assert namespace['EXPECTED_UPLOADER_BUNDLE_SHA256'] == 'windows' + assert namespace['EXPECTED_UPLOADER_LINUX_BUNDLE_SHA256'] == 'new-linux' + + +def test_linux_builder_has_no_private_copy_of_the_plugin_constants(): + source = (ROOT / 'tools' / 'build_linux_uploader.py').read_text(encoding='utf-8') + for literal in ('1.1.0', '1.4.0', '2026-08-24-v1', '2026-10-15-v3'): + assert literal not in source + assert 'FTHR_Hardware_ID' not in source + assert "'sftp_provider'" in source + + +def test_requirement_check_reports_missing_and_mismatched_pins(tmp_path): + requirements = tmp_path / 'requirements.txt' + requirements.write_text( + '# comment\n' + 'pytest==0.0.1\n' + 'fthr-package-that-does-not-exist==1.0\n' + "never-on-this-platform==1.0 ; sys_platform == 'nonexistent-os'\n", + encoding='utf-8') + problems = WINDOWS_BUILDER['missing_uploader_requirements'](requirements) + assert any(problem.startswith('pytest ') for problem in problems) + assert 'fthr-package-that-does-not-exist is not installed' in problems + assert not any('never-on-this-platform' in problem for problem in problems) + assert len(problems) == 2 + + +def test_every_bundle_carries_the_whole_uploader_licence_inventory(): + files = WINDOWS_BUILDER['uploader_license_files']() + packages = {path.relative_to(ROOT / 'licenses' / 'optional-uploader').parts[0] + for path in files} + assert {'paramiko', 'keyring', 'SecretStorage', 'jeepney', 'pywin32-ctypes'} <= packages diff --git a/tools/build_linux_uploader.py b/tools/build_linux_uploader.py index cbd4efae..d73c9cef 100644 --- a/tools/build_linux_uploader.py +++ b/tools/build_linux_uploader.py @@ -1,29 +1,51 @@ #!/usr/bin/env python3 -"""Build and seal the optional Linux upload extension bundle.""" +"""Build and seal the optional Linux upload extension bundle. + +The plugin identity, legal document versions, licence inventory and the Core +binding format come from tools/build_optional_uploaders.py, so the Linux and +Windows bundles always describe the same plugin release and neither build +erases the other platform's bundle hash in +FTHR_UI/core/uploader_bundle_manifest.py. +""" from __future__ import annotations import argparse import hashlib import json import os -import re +import shutil import subprocess import sys import zipfile from pathlib import Path -ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(Path(__file__).resolve().parent)) +import build_optional_uploaders as shared # noqa: E402 + +ROOT = shared.ROOT BUILD_ROOT = ROOT / 'build' / 'optional-uploader-linux' DIST = BUILD_ROOT / 'dist' WORK = BUILD_ROOT / 'work' SPEC = BUILD_ROOT / 'spec' +# Every ELF frozen inside the one-file payload, unpacked so that +# tools/verify_linux_glibc.py can hold the plugin to the same glibc ceiling as +# the AppImage. The compressed payload inside the .fthrplugin is invisible to +# a scan of the AppDir. +PAYLOAD_ELF_DIR = BUILD_ROOT / 'payload-elf' BUNDLE = ROOT / 'plugin-packages' / 'FTHR-Uploader-linux.fthrplugin' -MANIFEST_MODULE = ROOT / 'FTHR_UI' / 'core' / 'uploader_bundle_manifest.py' +UPLOADER_DIR = ROOT / 'FTHR_Uploader' ENTRYPOINT = 'FTHR-Uploader' -PLUGIN_ID = 'com.fthrclips.uploader' -PLUGIN_VERSION = '1.1.0' -TERMS_VERSION = '2026-08-24-v1' -PRIVACY_VERSION = '2026-08-24-v1' +_ELF_MAGIC = b'\x7fELF' + +# sftp_provider is imported lazily by the service; keyring resolves backends +# through entry points that a frozen executable does not carry, so name the +# Secret Service backend (SecretStorage over jeepney) explicitly. +HIDDEN_IMPORTS = ( + 'sftp_provider', + 'keyring.backends.SecretService', + 'secretstorage', + 'jeepney.io.blocking', +) def sha256(path: Path) -> str: @@ -38,79 +60,110 @@ def run(command: list[str]) -> None: subprocess.run(command, cwd=ROOT, check=True) -def bind_linux_bundle_hash(existing: str, bundle_hash: str) -> str: - """Update only the generated Linux hash while preserving other bindings.""" - linux_hash_line = ( - f'EXPECTED_UPLOADER_LINUX_BUNDLE_SHA256 = {bundle_hash!r}') - if 'EXPECTED_UPLOADER_LINUX_BUNDLE_SHA256' in existing: - return re.sub( - r"^EXPECTED_UPLOADER_LINUX_BUNDLE_SHA256 = .*?$", - linux_hash_line, - existing, - count=1, - flags=re.MULTILINE) - marker_match = re.search( - r"^EXPECTED_UPLOADER_BUNDLE_SHA256 = .*?$", - existing, - flags=re.MULTILINE) - if not marker_match: - raise ValueError('uploader manifest is missing the Windows bundle hash') - marker = marker_match.group(0) - return existing.replace(marker, marker + '\n' + linux_hash_line, 1) +def _remove_build_dir(path: Path) -> None: + resolved = path.resolve(strict=False) + if BUILD_ROOT.resolve(strict=False) not in (resolved, *resolved.parents): + raise ValueError(f'Refusing to remove a path outside {BUILD_ROOT}: {resolved}') + if resolved.exists(): + shutil.rmtree(resolved) -def main() -> int: - parser = argparse.ArgumentParser() - parser.add_argument('--python', default=sys.executable) - args = parser.parse_args() - if sys.platform == 'win32': - parser.error('This builder is for the Linux AppImage only.') +def extract_payload_elves(executable: Path, destination: Path) -> list[Path]: + """Unpack every ELF member of a PyInstaller one-file executable.""" + from PyInstaller.archive.readers import CArchiveReader # noqa: PLC0415 + + _remove_build_dir(destination) + destination.mkdir(parents=True) + root = destination.resolve() + extracted = [] + archive = CArchiveReader(str(executable)) + for name in sorted(archive.toc): + data = archive.extract(name) + if not data.startswith(_ELF_MAGIC): + continue + target = (root / name).resolve() + if root not in target.parents: + raise ValueError(f'unsafe member name in {executable.name}: {name!r}') + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(data) + extracted.append(target) + return extracted - for path in (DIST, WORK, SPEC): - path.mkdir(parents=True, exist_ok=True) - run([ - args.python, '-m', 'PyInstaller', '--noconfirm', '--clean', - '--onefile', '--console', '--name', ENTRYPOINT, - '--paths', str(ROOT / 'FTHR_Uploader'), - '--paths', str(ROOT / 'FTHR_Hardware_ID'), - '--distpath', str(DIST), '--workpath', str(WORK), - '--specpath', str(SPEC), - str(ROOT / 'FTHR_Uploader' / 'uploader_service.py'), - ]) - executable = DIST / ENTRYPOINT - if not executable.is_file(): - raise FileNotFoundError(executable) - executable.chmod(executable.stat().st_mode | 0o111) - - payload_hash = sha256(executable) + +def seal(executable: Path) -> Path: + licenses = shared.uploader_license_files() manifest = { 'schema_version': 1, - 'plugin_id': PLUGIN_ID, - 'plugin_version': PLUGIN_VERSION, - 'terms_version': TERMS_VERSION, - 'privacy_version': PRIVACY_VERSION, + 'plugin_id': shared.UPLOADER_PLUGIN_ID, + 'plugin_version': shared.UPLOADER_PLUGIN_VERSION, + 'terms_version': shared.UPLOADER_TERMS_VERSION, + 'privacy_version': shared.UPLOADER_PRIVACY_VERSION, 'entrypoint': ENTRYPOINT, 'activation': 'in_app_explicit_consent_only', 'files': [{ 'path': f'payload/{ENTRYPOINT}', - 'sha256': payload_hash, + 'sha256': sha256(executable), 'size': executable.stat().st_size, - }], + }, *[{ + 'path': 'payload/' + path.relative_to(ROOT).as_posix(), + 'sha256': sha256(path), + 'size': path.stat().st_size, + } for path in licenses]], } BUNDLE.parent.mkdir(parents=True, exist_ok=True) temporary = BUNDLE.with_suffix('.tmp') + temporary.unlink(missing_ok=True) with zipfile.ZipFile(temporary, 'w', compression=zipfile.ZIP_DEFLATED) as archive: archive.writestr('manifest.json', json.dumps(manifest, indent=2, sort_keys=True)) - archive.write(ROOT / 'FTHR_Uploader' / 'TERMS_OF_SERVICE.txt', 'TERMS_OF_SERVICE.txt') - archive.write(ROOT / 'FTHR_Uploader' / 'PRIVACY_POLICY.txt', 'PRIVACY_POLICY.txt') + archive.write(UPLOADER_DIR / 'TERMS_OF_SERVICE.txt', 'TERMS_OF_SERVICE.txt') + archive.write(UPLOADER_DIR / 'PRIVACY_POLICY.txt', 'PRIVACY_POLICY.txt') + for license_file in licenses: + archive.write(license_file, 'payload/' + license_file.relative_to(ROOT).as_posix()) archive.write(executable, f'payload/{ENTRYPOINT}') os.replace(temporary, BUNDLE) - bundle_hash = sha256(BUNDLE) + return BUNDLE + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--python', default=sys.executable) + args = parser.parse_args() + if sys.platform == 'win32': + parser.error('This builder is for the Linux AppImage only.') + if Path(args.python).resolve() != Path(sys.executable).resolve(): + # The requirement check below inspects this interpreter's packages. + parser.error('run this script with the interpreter passed as --python') + + try: + shared.require_uploader_requirements() + for path in (DIST, WORK, SPEC): + _remove_build_dir(path) + path.mkdir(parents=True) + command = [ + args.python, '-m', 'PyInstaller', '--noconfirm', '--clean', + '--onefile', '--console', '--name', ENTRYPOINT, + '--paths', str(UPLOADER_DIR), + '--distpath', str(DIST), '--workpath', str(WORK), + '--specpath', str(SPEC), + ] + for module in HIDDEN_IMPORTS: + command.extend(['--hidden-import', module]) + command.append(str(UPLOADER_DIR / 'uploader_service.py')) + run(command) + executable = DIST / ENTRYPOINT + if not executable.is_file(): + raise FileNotFoundError(executable) + executable.chmod(executable.stat().st_mode | 0o111) - existing = MANIFEST_MODULE.read_text(encoding='utf-8') - MANIFEST_MODULE.write_text( - bind_linux_bundle_hash(existing, bundle_hash), encoding='utf-8') - print(f'Built {BUNDLE} ({bundle_hash})') + elves = extract_payload_elves(executable, PAYLOAD_ELF_DIR) + bundle = seal(executable) + bundle_hash = sha256(bundle) + shared.write_core_manifest(linux_hash=bundle_hash) + except (OSError, ValueError, subprocess.CalledProcessError) as exc: + print(f'ERROR: {exc}', file=sys.stderr) + return 1 + print(f'Unpacked {len(elves)} payload ELF file(s) to {PAYLOAD_ELF_DIR} for the glibc gate') + print(f'Built {bundle} ({bundle_hash})') return 0 diff --git a/tools/build_optional_uploaders.py b/tools/build_optional_uploaders.py index 93368029..fa9bcd0a 100644 --- a/tools/build_optional_uploaders.py +++ b/tools/build_optional_uploaders.py @@ -4,20 +4,28 @@ The uploader is frozen as a one-shot executable, optionally signed, wrapped in integrity-declared ``.fthrplugin`` archives, and then bound into Core through generated SHA-256 constants. + +This module is also the single source of the uploader plugin version, legal +document versions, licence inventory and Core binding format; +tools/build_linux_uploader.py imports them so the two platform builds cannot +drift apart or erase each other's bundle hash. """ from __future__ import annotations import argparse +import ast import hashlib import json import os +import re import shlex import shutil import subprocess import sys import zipfile from dataclasses import dataclass +from importlib import metadata from pathlib import Path @@ -26,12 +34,17 @@ PACKAGES = ROOT / 'plugin-packages' CORE_MANIFEST = ROOT / 'FTHR_UI' / 'core' / 'uploader_bundle_manifest.py' ICON = ROOT / 'FTHR_UI' / 'assets' / 'favicon.ico' +REQUIREMENTS = ROOT / 'requirements-uploader.txt' +LICENSE_ROOT = ROOT / 'licenses' / 'optional-uploader' UPLOADER_PLUGIN_ID = 'com.fthrclips.uploader' UPLOADER_PLUGIN_VERSION = '1.4.0' UPLOADER_TERMS_VERSION = '2026-10-15-v3' UPLOADER_PRIVACY_VERSION = '2026-10-15-v3' +WINDOWS_BUNDLE_BINDING = 'EXPECTED_UPLOADER_BUNDLE_SHA256' +LINUX_BUNDLE_BINDING = 'EXPECTED_UPLOADER_LINUX_BUNDLE_SHA256' + @dataclass(frozen=True) class Package: @@ -71,6 +84,92 @@ def _sha256(path: Path) -> str: return digest.hexdigest() +def uploader_license_files() -> list[Path]: + """Every reviewed uploader licence file; each bundle carries all of them.""" + if not LICENSE_ROOT.is_dir(): + return [] + return sorted(path for path in LICENSE_ROOT.rglob('*') if path.is_file()) + + +def missing_uploader_requirements(requirements: Path = REQUIREMENTS) -> list[str]: + """Pins from requirements-uploader.txt that this interpreter does not satisfy. + + PyInstaller silently freezes an executable without a module it cannot + find, so a missing paramiko only shows up when the sealed package runs. + """ + from packaging.requirements import Requirement # noqa: PLC0415 (PyInstaller dep) + + problems = [] + for raw in requirements.read_text(encoding='utf-8').splitlines(): + line = raw.split('#', 1)[0].strip() + if not line: + continue + requirement = Requirement(line) + if requirement.marker is not None and not requirement.marker.evaluate(): + continue + try: + installed = metadata.version(requirement.name) + except metadata.PackageNotFoundError: + problems.append(f'{requirement.name} is not installed') + continue + if not requirement.specifier.contains(installed, prereleases=True): + problems.append( + f'{requirement.name} {installed} does not match {requirement.specifier}') + return problems + + +def require_uploader_requirements() -> None: + problems = missing_uploader_requirements() + if problems: + raise ValueError( + 'the uploader build environment does not match ' + f'{REQUIREMENTS.name}: ' + '; '.join(problems) + + f'. Run: {Path(sys.executable).name} -m pip install -r {REQUIREMENTS.name}') + + +def _existing_binding(text: str, name: str) -> str | None: + match = re.search(rf'^{re.escape(name)} = (.+)$', text, flags=re.MULTILINE) + if match is None: + return None + try: + value = ast.literal_eval(match.group(1).strip()) + except (ValueError, SyntaxError): + return None + return value if isinstance(value, str) and value else None + + +def render_core_manifest(existing: str, *, windows_hash: str | None = None, + linux_hash: str | None = None) -> str: + """Regenerate Core's uploader bindings from this module's constants. + + Each platform builder passes only its own bundle hash. The other + platform's hash is carried over from ``existing`` so a Windows build does + not erase the Linux binding and vice versa. + """ + windows_hash = windows_hash or _existing_binding(existing, WINDOWS_BUNDLE_BINDING) + linux_hash = linux_hash or _existing_binding(existing, LINUX_BUNDLE_BINDING) + if not windows_hash: + raise ValueError(f'uploader manifest is missing {WINDOWS_BUNDLE_BINDING}') + linux_line = (f'{LINUX_BUNDLE_BINDING} = {linux_hash!r}\n' if linux_hash else '') + return f'''"""Generated release bindings for dormant optional uploader packages.""" + +UPLOADER_PLUGIN_ID = {UPLOADER_PLUGIN_ID!r} +UPLOADER_PLUGIN_VERSION = {UPLOADER_PLUGIN_VERSION!r} +UPLOADER_TERMS_VERSION = {UPLOADER_TERMS_VERSION!r} +UPLOADER_PRIVACY_VERSION = {UPLOADER_PRIVACY_VERSION!r} +{WINDOWS_BUNDLE_BINDING} = {windows_hash!r} +{linux_line} +''' + + +def write_core_manifest(*, windows_hash: str | None = None, + linux_hash: str | None = None) -> None: + existing = CORE_MANIFEST.read_text(encoding='utf-8') if CORE_MANIFEST.is_file() else '' + CORE_MANIFEST.write_text( + render_core_manifest(existing, windows_hash=windows_hash, linux_hash=linux_hash), + encoding='utf-8') + + def _run(command: list[str], label: str) -> None: print(f'\n== {label} ==') subprocess.run(command, cwd=ROOT, check=True) @@ -135,8 +234,7 @@ def _build_executable(package: Package) -> Path: def _seal(package: Package, executable: Path) -> Path: destination = PACKAGES / package.bundle - licenses = (sorted(path for path in (ROOT / 'licenses' / 'optional-uploader').rglob('*') - if path.is_file()) if package.name == 'uploader' else []) + licenses = uploader_license_files() if package.name == 'uploader' else [] manifest = { 'schema_version': 1, 'plugin_id': package.plugin_id, @@ -172,19 +270,6 @@ def _seal(package: Package, executable: Path) -> Path: return destination -def _write_core_manifest(hashes: dict[str, str]) -> None: - text = f'''"""Generated release bindings for dormant optional uploader packages.""" - -UPLOADER_PLUGIN_ID = {UPLOADER_PLUGIN_ID!r} -UPLOADER_PLUGIN_VERSION = {UPLOADER_PLUGIN_VERSION!r} -UPLOADER_TERMS_VERSION = {UPLOADER_TERMS_VERSION!r} -UPLOADER_PRIVACY_VERSION = {UPLOADER_PRIVACY_VERSION!r} -EXPECTED_UPLOADER_BUNDLE_SHA256 = {hashes['uploader']!r} - -''' - CORE_MANIFEST.write_text(text, encoding='utf-8') - - def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( @@ -194,6 +279,7 @@ def main() -> int: if sys.platform != 'win32': parser.error('The current optional package entrypoints are Windows executables.') try: + require_uploader_requirements() _safe_remove_tree(BUILD_ROOT) hashes: dict[str, str] = {} for package in PACKAGE_DEFINITIONS: @@ -202,7 +288,7 @@ def main() -> int: _sign(args.sign_command, executable) bundle = _seal(package, executable) hashes[package.name] = _sha256(bundle) - _write_core_manifest(hashes) + write_core_manifest(windows_hash=hashes['uploader']) except (OSError, ValueError, subprocess.CalledProcessError) as exc: print(f'ERROR: {exc}', file=sys.stderr) return 1 From 83a3c7c26930b16797d275d351fd9b8f492ac2ed Mon Sep 17 00:00:00 2001 From: kalopsia-space Date: Sat, 17 Oct 2026 14:44:26 +0100 Subject: [PATCH 03/12] build(linux): bundle the Qt Quick runtime the 2.0 editor needs The 2.0 editor renders the clip preview and snapshots in QQuickWidget scenes (ui/qml/*.qml importing QtQuick and QtMultimedia, with a precompiled color.frag.qsb), so FTHR_linux.spec stops excluding QtQuick/QtQml, collects ui/qml, gatze.png and ui/capture_card_process.py, and names the Quick modules as hidden imports. Every other new 2.0 module is imported statically; the build's PYZ table confirms it is collected. Like FTHR.spec, the Qt hooks' output is filtered: only the QtQml, QtQml/Models, QtQml/WorkerScript, QtQuick and QtMultimedia import directories, only Qt libraries listed in qt_runtime_manifest.json, and no qmltooling plugins (QML debugger; one links the GPL-only Quick 3D). The filter is anchored to the wheel's qml/ destination so it cannot drop the application's own ui/qml files. The manifest gains 2.0's Windows Quick modules and licence sources, and QmlCore, QuickWidgets and MultimediaQuick for Linux. A local build ships 25 Qt modules, every libQt6 NEEDED entry resolves inside the bundle, and both scenes load with only the bundled QML import tree on the import path. --- FTHR_linux.spec | 46 ++++++++++++++++++++++++++++++---- tests/test_qt_license_gate.py | 35 +++++++++++++++++++++++++- tools/qt_runtime_manifest.json | 22 +++++++++++++--- 3 files changed, 94 insertions(+), 9 deletions(-) diff --git a/FTHR_linux.spec b/FTHR_linux.spec index 263f0dd2..7c19b8c3 100644 --- a/FTHR_linux.spec +++ b/FTHR_linux.spec @@ -155,6 +155,7 @@ a = Analysis( (str(ASSETS_DIR / 'fthr_logo.png'), 'assets'), (str(ASSETS_DIR / 'preview_desktop.png'), 'assets'), (str(ASSETS_DIR / 'gary.png'), 'assets'), + (str(ASSETS_DIR / 'gatze.png'), 'assets'), (str(PLUGIN_BUNDLE), 'plugin-packages'), # Licence paperwork must travel INSIDE the bundle (AUDIT-005), so a # portable copy is as complete as an installed one. @@ -170,11 +171,21 @@ a = Analysis( *_asset_tree(ASSETS_DIR / 'fonts', 'assets/fonts'), *_asset_tree(ASSETS_DIR / 'icons', 'assets/icons'), *_asset_tree(ASSETS_DIR / 'sounds', 'assets/sounds'), + # The capture card runs this file in a child interpreter, and the + # editor loads its QML scenes and precompiled shader (color.frag.qsb) + # from beside ui/quick_*.py at runtime. + (str(UI_DIR / 'ui' / 'capture_card_process.py'), 'ui'), + *_asset_tree(UI_DIR / 'ui' / 'qml', 'ui/qml'), ], hiddenimports=[ 'PySide6.QtDBus', 'PySide6.QtMultimedia', 'PySide6.QtMultimediaWidgets', + # The 2.0 editor renders previews and snapshots in QQuickWidget scenes. + 'PySide6.QtQuick', + 'PySide6.QtQuickWidgets', + 'PySide6.QtQml', + 'PySide6.QtNetwork', 'sounddevice', 'numpy', 'cv2', @@ -202,6 +213,7 @@ a = Analysis( 'core.ffmpeg_playback', 'core.game_detector', 'core.hotkey_manager', + 'core.instance_activation', 'core.mic_recorder', 'core.presets_manager', 'core.settings_manager', @@ -214,8 +226,9 @@ a = Analysis( # Exclude imageio_ffmpeg; use the project-pinned FFmpeg runtime beside # the engine. See core/ffmpeg_tools.py. 'imageio_ffmpeg', - # Qt modules we don't use (Widgets-only app) - 'PySide6.QtQuick', 'PySide6.QtQml', 'PySide6.QtWebEngine', + # Qt modules we don't use. Quick/QML power the editor's video preview + # and snapshots, so they are filtered below rather than excluded. + 'PySide6.QtWebEngine', 'PySide6.QtWebEngineCore', 'PySide6.QtBluetooth', 'PySide6.QtPositioning', 'PySide6.QtSensors', 'PySide6.QtLocation', 'PySide6.Qt3D', 'PySide6.QtPdf', 'PySide6.QtPdfWidgets', 'PySide6.QtNfc', @@ -236,10 +249,22 @@ a = Analysis( noarchive=False, ) -# QtGui's generic hook also sees the optional Virtual Keyboard plugin. It is a -# GPL-only Qt module and drags in unused QML/Quick libraries. Excludes stop +# Qt hooks collect every optional QML module and its libraries. Excludes stop # Python imports but not hook-added binaries, so filter the actual artifact -# TOCs as a second, deterministic boundary. +# TOCs as a second, deterministic boundary: keep the QML runtime the +# application's QtQuick and QtMultimedia scenes import, and only the Qt +# libraries reviewed in tools/qt_runtime_manifest.json. The Virtual Keyboard +# is a GPL-only Qt module and never ships. +import json as _json # noqa: E402 +_REVIEWED_QT = _json.loads( + (ROOT / 'tools' / 'qt_runtime_manifest.json').read_text(encoding='utf-8')) +_APPROVED_QT_SONAMES = { + f'libqt6{module.lower()}.so' + for module in _REVIEWED_QT['approved_runtime_qt_modules']['linux']} +# Destination of the wheel's QML import tree, e.g. pyside6/qt/qml/. +_QT_QML_DEST = (_QT6_PLUGIN_DEST.parent / 'qml').as_posix().casefold() + '/' +_KEPT_QML_DIRS = ('qtqml', 'qtqml/models', 'qtqml/workerscript', + 'qtquick', 'qtmultimedia') # The compiler runtime stays on the host. PyInstaller puts _internal first on # LD_LIBRARY_PATH, so a bundled libstdc++/libgcc_s built on the Ubuntu 22.04 # baseline shadows the host's newer copy for every library the process or its @@ -255,6 +280,17 @@ def _keep_reviewed_qt_runtime(entry): dest = str(entry[0]).replace('\\', '/').casefold() name = dest.rsplit('/', 1)[-1] forbidden_prefixes = ('libqt6virtualkeyboard',) + # QML debugger/profiler plugins are development tooling, and + # libqmldbg_quick3dprofiler links the GPL-only Quick 3D. + if '/plugins/qmltooling/' in dest: + return False + if dest.startswith(_QT_QML_DEST): + qml_parent = dest[len(_QT_QML_DEST):].rpartition('/')[0] + if qml_parent not in _KEPT_QML_DIRS: + return False + if name.startswith('libqt6') and '.so' in name: + if name.split('.so', 1)[0] + '.so' not in _APPROVED_QT_SONAMES: + return False return (name not in _SKIPPED_QT_PLUGINS and name not in _HOST_TOOLCHAIN_LIBS and 'virtualkeyboard' not in dest diff --git a/tests/test_qt_license_gate.py b/tests/test_qt_license_gate.py index 8e461f0f..b8d69699 100644 --- a/tests/test_qt_license_gate.py +++ b/tests/test_qt_license_gate.py @@ -68,7 +68,8 @@ def _source_tree(tmp_path: Path) -> Path: 'shiboken6==6.11.1', )), ) - for name in ('requirements.in', 'FTHR.spec'): + for name in ('requirements.in', 'FTHR.spec', 'FTHR_linux.spec', + 'build_linux.sh'): _write(root / name, 'PySide6\n') return root @@ -144,5 +145,37 @@ def test_artifact_rejects_gpl_only_qt_module(tmp_path, module): assert any('GPL-only' in failure for failure in rep.failures) +def test_linux_artifact_requires_xcb_and_wayland_plugins(tmp_path): + manifest_root = _manifest_root(tmp_path / 'source') + artifact = tmp_path / 'artifact' + _license_files(artifact) + _write(artifact / 'PySide6' / 'QtCore.so') + for module in ('Core', 'Gui', 'Widgets', 'Multimedia', + 'MultimediaWidgets'): + _write(artifact / 'PySide6' / 'Qt' / 'lib' / + f'libQt6{module}.so.6') + rep = vrl.Report() + vrl.check_qt_artifact(artifact, rep, 'linux', manifest_root=manifest_root) + assert any('libqxcb.so missing' in failure for failure in rep.failures) + assert any('Wayland Qt platform plugin missing' in failure + for failure in rep.failures) + + +def test_linux_artifact_accepts_every_reviewed_runtime_module(tmp_path): + manifest_root = _manifest_root(tmp_path / 'source') + artifact = tmp_path / 'artifact' + _license_files(artifact) + _write(artifact / 'PySide6' / 'QtCore.so') + data = json.loads(REAL_MANIFEST.read_text(encoding='utf-8')) + for module in data['approved_runtime_qt_modules']['linux']: + _write(artifact / 'PySide6' / 'Qt' / 'lib' / + f'libQt6{module}.so.6') + _write(artifact / 'PySide6' / 'Qt' / 'plugins' / 'platforms' / + 'libqxcb.so') + _write(artifact / 'PySide6' / 'Qt' / 'plugins' / 'platforms' / + 'libqwayland.so') + rep = vrl.Report() + vrl.check_qt_artifact(artifact, rep, 'linux', manifest_root=manifest_root) + assert not rep.failures, rep.failures diff --git a/tools/qt_runtime_manifest.json b/tools/qt_runtime_manifest.json index ba06afb2..c9fe5d00 100644 --- a/tools/qt_runtime_manifest.json +++ b/tools/qt_runtime_manifest.json @@ -18,7 +18,10 @@ "Gui", "Widgets", "Multimedia", - "MultimediaWidgets" + "MultimediaWidgets", + "Qml", + "Quick", + "QuickWidgets" ], "approved_runtime_qt_modules": { "windows": [ @@ -31,7 +34,15 @@ "OpenGLWidgets", "Pdf", "Svg", - "Widgets" + "Widgets", + "Qml", + "QmlCore", + "QmlMeta", + "QmlModels", + "QmlWorkerScript", + "Quick", + "QuickWidgets", + "MultimediaQuick" ], "linux": [ "Concurrent", @@ -41,16 +52,19 @@ "EglFsKmsSupport", "Gui", "Multimedia", + "MultimediaQuick", "MultimediaWidgets", "Network", "OpenGL", "OpenGLWidgets", "Pdf", "Qml", + "QmlCore", "QmlMeta", "QmlModels", "QmlWorkerScript", "Quick", + "QuickWidgets", "Svg", "WaylandClient", "WaylandEglClientHwIntegration", @@ -95,6 +109,8 @@ ], "official_sources": { "qt": "https://download.qt.io/official_releases/qt/6.11/6.11.1/single/qt-everywhere-src-6.11.1.tar.xz", - "qt_for_python": "https://download.qt.io/official_releases/QtForPython/pyside6/PySide6-6.11.1-src/pyside-setup-everywhere-src-6.11.1.tar.xz" + "qt_for_python": "https://download.qt.io/official_releases/QtForPython/pyside6/PySide6-6.11.1-src/pyside-setup-everywhere-src-6.11.1.tar.xz", + "qml_license": "https://doc.qt.io/qt-6/qtqml-index.html#licenses-and-attributions", + "quick_license": "https://doc.qt.io/qt-6/qtquick-index.html#licenses-and-attributions" } } From 7c65696232502e030760d3cf63a041a6157a4ecd Mon Sep 17 00:00:00 2001 From: kalopsia-space Date: Sat, 17 Oct 2026 14:44:26 +0100 Subject: [PATCH 04/12] release: document the 2.0 assets for both platforms The licence gate failed on linux-2.0 because the kept linux asset manifest did not know 2.0's new assets (Oswald Regular/SemiBold, gatze.png, 24 SVG icons, the Ko-fi symbol, the polished installer artwork) and still listed .github/social_preview.png, which upstream deleted. Take 2.0's manifest, keep the linux entries for shared files and the AppDir icon, and give every Windows-distributed new asset the same _internal/assets path in the AppImage, where FTHR_linux.spec puts it. generate_release_assets.py stops expecting the deleted social preview so its --check passes again. --- tools/generate_release_assets.py | 4 +- tools/release_asset_manifest.json | 2610 ++++++++++++++++++++++++++++- 2 files changed, 2582 insertions(+), 32 deletions(-) diff --git a/tools/generate_release_assets.py b/tools/generate_release_assets.py index b303c1af..001110bc 100644 --- a/tools/generate_release_assets.py +++ b/tools/generate_release_assets.py @@ -381,8 +381,10 @@ def sound(notes: list[tuple[float, float]], gap: float = 0.025) -> bytes: def generated_files() -> dict[Path, bytes]: logo = draw_mark(512).png() + # The repository social preview (social_preview()) is no longer committed: + # upstream removed .github/social_preview.png in 2.0.0-alpha. Regenerate it + # by hand if the GitHub repository card needs it again. files = { - ROOT / '.github' / 'social_preview.png': social_preview().png(), ROOT / 'AppDir' / 'fthr-clips.png': logo, ROOT / 'installer_assets' / 'wizard_banner.bmp': installer_banner(164, 314).bmp(), diff --git a/tools/release_asset_manifest.json b/tools/release_asset_manifest.json index f641b498..111f1188 100644 --- a/tools/release_asset_manifest.json +++ b/tools/release_asset_manifest.json @@ -27,6 +27,11 @@ ".woff2" ], "origins": { + "user-provided-gary-cat": { + "type": "user-provided", + "description": "Workspace owner supplied gatze.png on 2026-10-15 for the one-in-ten alternate Gary Mode appearance; included unchanged.", + "notice": "licenses/FTHR-USER-PROVIDED-ASSETS.txt" + }, "fthr-original-gary": { "type": "project-original", "description": "Original Gary artwork created by FTHR, confirmed by the project owner on 2026-09-17; retained as a source image, not an output of the release asset generator.", @@ -58,39 +63,2582 @@ "type": "user-provided", "description": "Workspace owner supplied the FTHR brand ICO used by the application window, executable, and Windows installer.", "notice": "licenses/FTHR-USER-PROVIDED-ASSETS.txt" + }, + "oswald-4.103-regular": { + "type": "upstream-unmodified", + "publisher": "The Oswald Project Authors / Google Fonts", + "version": "4.103", + "source_revision": "89795261ac9eeb9aa8cd99f43982c4e4b0e53261", + "source_file": "https://raw.githubusercontent.com/googlefonts/OswaldFont/89795261ac9eeb9aa8cd99f43982c4e4b0e53261/fonts/ttf/Oswald-Regular.ttf", + "source_license": "https://raw.githubusercontent.com/googlefonts/OswaldFont/89795261ac9eeb9aa8cd99f43982c4e4b0e53261/OFL.txt", + "notice": "licenses/Oswald-OFL-1.1.txt" + }, + "oswald-4.103-semibold": { + "type": "upstream-unmodified", + "publisher": "The Oswald Project Authors / Google Fonts", + "version": "4.103", + "source_revision": "89795261ac9eeb9aa8cd99f43982c4e4b0e53261", + "source_file": "https://raw.githubusercontent.com/googlefonts/OswaldFont/89795261ac9eeb9aa8cd99f43982c4e4b0e53261/fonts/ttf/Oswald-SemiBold.ttf", + "source_license": "https://raw.githubusercontent.com/googlefonts/OswaldFont/89795261ac9eeb9aa8cd99f43982c4e4b0e53261/OFL.txt", + "notice": "licenses/Oswald-OFL-1.1.txt" + }, + "fthr-ui-reference-svg": { + "type": "project-source", + "description": "Interface SVG artwork ported unchanged from the user-supplied FTHR-Clips-experimental/FTHR_UI/assets/icons source tree. Governed by the project source license; distinct from the generated MIT asset set.", + "notice": "LICENSE" + }, + "fthr-installer-polish-v1": { + "type": "project-generated", + "generator": "tools/generate_installer_assets.py", + "generator_version": 1, + "external_creative_inputs": [ + "FTHR_UI/assets/icons/close.svg", + "FTHR_UI/assets/fonts/Oswald-Bold.ttf" + ], + "description": "Antialiased step indicators using the existing Oswald font, and a proportion-preserving raster of the current application close SVG.", + "notice": "LICENSE" + }, + "kofi-promotional-symbol": { + "type": "third-party-brand-asset", + "publisher": "Ko-fi / Ko-fi Labs Limited", + "description": "Existing Ko-fi cup PNG visually verified against the official creator kit on 2026-10-13. Original acquisition URL not recorded. Used solely to promote the project Ko-fi page under the official promotional permission.", + "source_url": "https://more.ko-fi.com/brand-assets", + "permission_url": "https://help.ko-fi.com/hc/en-us/articles/360007169493-Can-I-use-the-Ko-fi-logo-to-promote-my-page", + "notice": "licenses/KO-FI-BRAND-ASSETS.txt" } }, "assets": [ - {"path": "FTHR_UI/assets/gary.png", "sha256": "4ccfb741b009977f0af444ba114c08340a070769d250dd163e279a28ccdc8571", "media_type": "image/png", "usage": ["Gary mode default image"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/gary.png"], "linux": ["_internal/assets/gary.png"]}, "packaging_references": ["FTHR.spec", "FTHR_linux.spec"], "license": "MIT", "copyright": "Copyright (c) 2026 Haakon Schaller, trading as FTHR-Software", "origin": "fthr-original-gary", "redistribution_status": "approved"}, - {"path": ".github/social_preview.png", "sha256": "ea90be692d4c58f23cc70f139021938ca196b7039255d98e098820962cbefe14", "media_type": "image/png", "usage": ["repository social preview"], "distributions": ["repository-only"], "artifact_paths": {}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "AppDir/fthr-clips.png", "sha256": "0266ea11270d48a927c09805d59a2fd957e2cceb035f6793bff29fd7d8b50e08", "media_type": "image/png", "usage": ["AppImage desktop icon"], "distributions": ["linux"], "artifact_paths": {"linux": ["fthr-clips.png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/fonts/Oswald-Bold.ttf", "sha256": "eb7d46f856dd57f18a8c03d033c57802692bf127f01dbd95ba8984338e6b5135", "media_type": "font/ttf", "usage": ["application display font"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/fonts/Oswald-Bold.ttf"], "linux": ["_internal/assets/fonts/Oswald-Bold.ttf"]}, "license": "OFL-1.1", "copyright": "Copyright 2016 The Oswald Project Authors (https://github.com/googlefonts/OswaldFont)", "origin": "oswald-4.103", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/fthr_logo.ico", "sha256": "026af246059efffd9bf899b62e898f74efb6bb6b3ae80f6a168376c42f4cc936", "media_type": "image/vnd.microsoft.icon", "usage": ["Windows executable icon"], "distributions": ["windows-executable-embedded"], "artifact_paths": {}, "packaging_references": ["FTHR.spec"], "license": "User-provided", "copyright": "Workspace owner supplied brand icon", "origin": "user-provided-brand-icon", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/favicon.ico", "sha256": "026af246059efffd9bf899b62e898f74efb6bb6b3ae80f6a168376c42f4cc936", "media_type": "image/vnd.microsoft.icon", "usage": ["window icon", "system tray icon", "watermark default", "theme default", "Windows installer icon and title-bar mark"], "distributions": ["windows", "linux", "windows-installer-embedded"], "artifact_paths": {"windows": ["_internal/assets/favicon.ico"], "linux": ["_internal/assets/favicon.ico"]}, "packaging_references": ["installer_windows.iss"], "license": "User-provided", "copyright": "Workspace owner supplied brand icon", "origin": "user-provided-brand-icon", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/fthr_logo.png", "sha256": "1164d541d01f2b71ba2003892389152f0f0535954f44c020bda3f0412919e90e", "media_type": "image/png", "usage": ["main window logo", "theme default"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/fthr_logo.png"], "linux": ["_internal/assets/fthr_logo.png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/preview_desktop.png", "sha256": "4ba07a6b9b478f63d4897952a2bc02e9bc23ace1de18470da58cbdabe24cda55", "media_type": "image/png", "usage": ["standard visual-overlays preview background"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/preview_desktop.png"], "linux": ["_internal/assets/preview_desktop.png"]}, "license": "User-provided", "copyright": "Workspace owner supplied image", "origin": "user-provided-preview", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/icons/clip.png", "sha256": "7dc91171c1d16bc9edd58501058ca597fe7fb9932abb869f35466b3d267a83c2", "media_type": "image/png", "usage": ["clip tab icon"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/icons/clip.png"], "linux": ["_internal/assets/icons/clip.png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/icons/close.png", "sha256": "751d4c4b00852fa0fbd153e4f89360c51a22192a2fd3df51f864658b4403923f", "media_type": "image/png", "usage": ["close button icon"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/icons/close.png"], "linux": ["_internal/assets/icons/close.png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/icons/dropdown.png", "sha256": "8918f835bb6c8404932020d8d1f09ea420801a800ebd840f16d4ed0170b3b09f", "media_type": "image/png", "usage": ["combo-box arrow"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/icons/dropdown.png"], "linux": ["_internal/assets/icons/dropdown.png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/icons/home.png", "sha256": "e2d589bfab6e06e63cc676dc8dcb924808b9e7776230738678dde6364faae313", "media_type": "image/png", "usage": ["home button icon"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/icons/home.png"], "linux": ["_internal/assets/icons/home.png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/icons/maximize.png", "sha256": "ee2c602cbc8ca68d8b6e8e0a7e744544d4613695eb0e8ea5095a355078578809", "media_type": "image/png", "usage": ["maximize button icon"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/icons/maximize.png"], "linux": ["_internal/assets/icons/maximize.png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/icons/minimize.png", "sha256": "e7861bdc5ba185c22f295408bda8cbf588b325b3ffd69bac43852d31dc8c38c4", "media_type": "image/png", "usage": ["minimize button icon"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/icons/minimize.png"], "linux": ["_internal/assets/icons/minimize.png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/icons/pause.png", "sha256": "1c25e339c453542fb3159ab102e22d7302bcc8ae6a253cdd44f2c26a1dc1fef7", "media_type": "image/png", "usage": ["media pause icon"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/icons/pause.png"], "linux": ["_internal/assets/icons/pause.png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/icons/personalize.png", "sha256": "c3fea9fedd4f37f372f3dbe38928b4d7674709ae4cf9ef1451921b8457f1f983", "media_type": "image/png", "usage": ["customization tab icon"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/icons/personalize.png"], "linux": ["_internal/assets/icons/personalize.png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/icons/play.png", "sha256": "3c9c4ba3d8a9794005c9d31ddac7cb8a237c17f4939fdaadac935c8ff0b2f9f8", "media_type": "image/png", "usage": ["media play icon"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/icons/play.png"], "linux": ["_internal/assets/icons/play.png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/icons/refresh.png", "sha256": "101d18adaba7c478bcf65e6a39e27da545d6e4eb88a0aff43eda47d5f409c7c1", "media_type": "image/png", "usage": ["refresh button icon"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/icons/refresh.png"], "linux": ["_internal/assets/icons/refresh.png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/icons/settings(general).png", "sha256": "da070779137d109cbc175a0c8c64eecf4dac0277b01ae9fde90babdad20e04bf", "media_type": "image/png", "usage": ["settings tab icon"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/icons/settings(general).png"], "linux": ["_internal/assets/icons/settings(general).png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/icons/sound.png", "sha256": "d1b08687a69d0743fa1cfe39608e6931740fb2dd7d7469d4e24269a833b52b39", "media_type": "image/png", "usage": ["audio tab icon"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/icons/sound.png"], "linux": ["_internal/assets/icons/sound.png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/icons/shutdown.png", "sha256": "964597788bd296f4843c7114b1e6af3899499c9eb3afa8da5a726180dca14bff", "media_type": "image/png", "usage": ["shutdown button icon"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/icons/shutdown.png"], "linux": ["_internal/assets/icons/shutdown.png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/icons/updates.png", "sha256": "da8f7dadb9aa1ece5bfee82e1d5568e8506eef656153f591c4f33cb46b990032", "media_type": "image/png", "usage": ["updates and performance icon"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/icons/updates.png"], "linux": ["_internal/assets/icons/updates.png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/icons/visuals.png", "sha256": "e3176bb051eae62705e309248ca5320c037c0c218b9612dc2452c63fd3cbfd48", "media_type": "image/png", "usage": ["visuals tab icon"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/icons/visuals.png"], "linux": ["_internal/assets/icons/visuals.png"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/sounds/clip_captured.wav", "sha256": "e0da4dd3382d3e91347c59796e904a775afbfb81ef3752defffbb9769bf2863b", "media_type": "audio/wav", "usage": ["clip-captured notification"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/sounds/clip_captured.wav"], "linux": ["_internal/assets/sounds/clip_captured.wav"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/sounds/error.wav", "sha256": "4af4ae3d084043b0c3cb7bda6943ad3ff10444c67722ca48c616b3a0827297a0", "media_type": "audio/wav", "usage": ["error notification"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/sounds/error.wav"], "linux": ["_internal/assets/sounds/error.wav"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/sounds/screenshot_saved.wav", "sha256": "80a3e9e313d6b825177eb5a82513c5907a81550d0d4d16e744f49ffb42356efb", "media_type": "audio/wav", "usage": ["screenshot-saved notification"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/sounds/screenshot_saved.wav"], "linux": ["_internal/assets/sounds/screenshot_saved.wav"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/sounds/startup.wav", "sha256": "56a04ecc28578f8cd83adfb2f37f2fa7d68ad3a7d29cb0068b6ec4cc556d4152", "media_type": "audio/wav", "usage": ["startup notification"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/sounds/startup.wav"], "linux": ["_internal/assets/sounds/startup.wav"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/sounds/upload_failed.wav", "sha256": "8e99c82a31e66f85a86d97df68c5fc05b0601dafd566b3b15e29b84770b35f1c", "media_type": "audio/wav", "usage": ["upload-failed notification"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/sounds/upload_failed.wav"], "linux": ["_internal/assets/sounds/upload_failed.wav"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "FTHR_UI/assets/sounds/upload_successful.wav", "sha256": "9eb8a4605642b28663697bb8045434208aeb2198359d5c5ee2c236ed9bd62137", "media_type": "audio/wav", "usage": ["upload-success notification"], "distributions": ["windows", "linux"], "artifact_paths": {"windows": ["_internal/assets/sounds/upload_successful.wav"], "linux": ["_internal/assets/sounds/upload_successful.wav"]}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "installer_assets/wizard_banner.bmp", "sha256": "22c0a8a0d2687cb4fd903856988aca208487948c4f92b007d8b068947e95d45a", "media_type": "image/bmp", "usage": ["legacy installer artwork retained in source but not embedded"], "distributions": [], "artifact_paths": {}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"}, - {"path": "installer_assets/wizard_small.bmp", "sha256": "55448c510e3562627239f18cd7ad4e384654dc8abcda7a319d44ef99794c0852", "media_type": "image/bmp", "usage": ["legacy installer artwork retained in source but not embedded"], "distributions": [], "artifact_paths": {}, "license": "MIT", "copyright": "Copyright (c) 2026 FTHR-Community", "origin": "fthr-generator-v1", "redistribution_status": "approved"} + { + "path": "FTHR_UI/assets/gatze.png", + "sha256": "cbbbdf11da3d21f1fe1710798396c3f21f9f35cb47ecda07d0b6540b8250799f", + "media_type": "image/png", + "usage": [ + "Gary mode one-in-ten alternate image" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/gatze.png" + ], + "linux": [ + "_internal/assets/gatze.png" + ] + }, + "packaging_references": [ + "FTHR.spec", + "FTHR_linux.spec" + ], + "license": "User-provided", + "copyright": "Workspace owner supplied image", + "origin": "user-provided-gary-cat", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/gary.png", + "sha256": "4ccfb741b009977f0af444ba114c08340a070769d250dd163e279a28ccdc8571", + "media_type": "image/png", + "usage": [ + "Gary mode default image" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/gary.png" + ], + "linux": [ + "_internal/assets/gary.png" + ] + }, + "packaging_references": [ + "FTHR.spec", + "FTHR_linux.spec" + ], + "license": "MIT", + "copyright": "Copyright (c) 2026 Haakon Schaller, trading as FTHR-Software", + "origin": "fthr-original-gary", + "redistribution_status": "approved" + }, + { + "path": "AppDir/fthr-clips.png", + "sha256": "0266ea11270d48a927c09805d59a2fd957e2cceb035f6793bff29fd7d8b50e08", + "media_type": "image/png", + "usage": [ + "AppImage desktop icon" + ], + "distributions": [ + "linux" + ], + "artifact_paths": { + "linux": [ + "fthr-clips.png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/fonts/Oswald-Bold.ttf", + "sha256": "eb7d46f856dd57f18a8c03d033c57802692bf127f01dbd95ba8984338e6b5135", + "media_type": "font/ttf", + "usage": [ + "application display font" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/fonts/Oswald-Bold.ttf" + ], + "linux": [ + "_internal/assets/fonts/Oswald-Bold.ttf" + ] + }, + "license": "OFL-1.1", + "copyright": "Copyright 2016 The Oswald Project Authors (https://github.com/googlefonts/OswaldFont)", + "origin": "oswald-4.103", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/fthr_logo.ico", + "sha256": "026af246059efffd9bf899b62e898f74efb6bb6b3ae80f6a168376c42f4cc936", + "media_type": "image/vnd.microsoft.icon", + "usage": [ + "Windows executable icon" + ], + "distributions": [ + "windows-executable-embedded" + ], + "artifact_paths": {}, + "packaging_references": [ + "FTHR.spec" + ], + "license": "User-provided", + "copyright": "Workspace owner supplied brand icon", + "origin": "user-provided-brand-icon", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/favicon.ico", + "sha256": "026af246059efffd9bf899b62e898f74efb6bb6b3ae80f6a168376c42f4cc936", + "media_type": "image/vnd.microsoft.icon", + "usage": [ + "window icon", + "system tray icon", + "watermark default", + "theme default", + "Windows installer icon and title-bar mark" + ], + "distributions": [ + "windows", + "linux", + "windows-installer-embedded" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/favicon.ico" + ], + "linux": [ + "_internal/assets/favicon.ico" + ] + }, + "packaging_references": [ + "installer_windows.iss" + ], + "license": "User-provided", + "copyright": "Workspace owner supplied brand icon", + "origin": "user-provided-brand-icon", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/fthr_logo.png", + "sha256": "1164d541d01f2b71ba2003892389152f0f0535954f44c020bda3f0412919e90e", + "media_type": "image/png", + "usage": [ + "main window logo", + "theme default" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/fthr_logo.png" + ], + "linux": [ + "_internal/assets/fthr_logo.png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/preview_desktop.png", + "sha256": "4ba07a6b9b478f63d4897952a2bc02e9bc23ace1de18470da58cbdabe24cda55", + "media_type": "image/png", + "usage": [ + "standard visual-overlays preview background" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/preview_desktop.png" + ], + "linux": [ + "_internal/assets/preview_desktop.png" + ] + }, + "license": "User-provided", + "copyright": "Workspace owner supplied image", + "origin": "user-provided-preview", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/clip.png", + "sha256": "7dc91171c1d16bc9edd58501058ca597fe7fb9932abb869f35466b3d267a83c2", + "media_type": "image/png", + "usage": [ + "clip tab icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/clip.png" + ], + "linux": [ + "_internal/assets/icons/clip.png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/close.png", + "sha256": "751d4c4b00852fa0fbd153e4f89360c51a22192a2fd3df51f864658b4403923f", + "media_type": "image/png", + "usage": [ + "close button icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/close.png" + ], + "linux": [ + "_internal/assets/icons/close.png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/dropdown.png", + "sha256": "8918f835bb6c8404932020d8d1f09ea420801a800ebd840f16d4ed0170b3b09f", + "media_type": "image/png", + "usage": [ + "combo-box arrow" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/dropdown.png" + ], + "linux": [ + "_internal/assets/icons/dropdown.png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/home.png", + "sha256": "e2d589bfab6e06e63cc676dc8dcb924808b9e7776230738678dde6364faae313", + "media_type": "image/png", + "usage": [ + "home button icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/home.png" + ], + "linux": [ + "_internal/assets/icons/home.png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/maximize.png", + "sha256": "ee2c602cbc8ca68d8b6e8e0a7e744544d4613695eb0e8ea5095a355078578809", + "media_type": "image/png", + "usage": [ + "maximize button icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/maximize.png" + ], + "linux": [ + "_internal/assets/icons/maximize.png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/minimize.png", + "sha256": "e7861bdc5ba185c22f295408bda8cbf588b325b3ffd69bac43852d31dc8c38c4", + "media_type": "image/png", + "usage": [ + "minimize button icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/minimize.png" + ], + "linux": [ + "_internal/assets/icons/minimize.png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/pause.png", + "sha256": "1c25e339c453542fb3159ab102e22d7302bcc8ae6a253cdd44f2c26a1dc1fef7", + "media_type": "image/png", + "usage": [ + "media pause icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/pause.png" + ], + "linux": [ + "_internal/assets/icons/pause.png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/personalize.png", + "sha256": "c3fea9fedd4f37f372f3dbe38928b4d7674709ae4cf9ef1451921b8457f1f983", + "media_type": "image/png", + "usage": [ + "customization tab icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/personalize.png" + ], + "linux": [ + "_internal/assets/icons/personalize.png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/play.png", + "sha256": "3c9c4ba3d8a9794005c9d31ddac7cb8a237c17f4939fdaadac935c8ff0b2f9f8", + "media_type": "image/png", + "usage": [ + "media play icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/play.png" + ], + "linux": [ + "_internal/assets/icons/play.png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/refresh.png", + "sha256": "101d18adaba7c478bcf65e6a39e27da545d6e4eb88a0aff43eda47d5f409c7c1", + "media_type": "image/png", + "usage": [ + "refresh button icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/refresh.png" + ], + "linux": [ + "_internal/assets/icons/refresh.png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/settings(general).png", + "sha256": "da070779137d109cbc175a0c8c64eecf4dac0277b01ae9fde90babdad20e04bf", + "media_type": "image/png", + "usage": [ + "settings tab icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/settings(general).png" + ], + "linux": [ + "_internal/assets/icons/settings(general).png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/sound.png", + "sha256": "d1b08687a69d0743fa1cfe39608e6931740fb2dd7d7469d4e24269a833b52b39", + "media_type": "image/png", + "usage": [ + "audio tab icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/sound.png" + ], + "linux": [ + "_internal/assets/icons/sound.png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/shutdown.png", + "sha256": "964597788bd296f4843c7114b1e6af3899499c9eb3afa8da5a726180dca14bff", + "media_type": "image/png", + "usage": [ + "shutdown button icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/shutdown.png" + ], + "linux": [ + "_internal/assets/icons/shutdown.png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/updates.png", + "sha256": "da8f7dadb9aa1ece5bfee82e1d5568e8506eef656153f591c4f33cb46b990032", + "media_type": "image/png", + "usage": [ + "updates and performance icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/updates.png" + ], + "linux": [ + "_internal/assets/icons/updates.png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/visuals.png", + "sha256": "e3176bb051eae62705e309248ca5320c037c0c218b9612dc2452c63fd3cbfd48", + "media_type": "image/png", + "usage": [ + "visuals tab icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/visuals.png" + ], + "linux": [ + "_internal/assets/icons/visuals.png" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/sounds/clip_captured.wav", + "sha256": "e0da4dd3382d3e91347c59796e904a775afbfb81ef3752defffbb9769bf2863b", + "media_type": "audio/wav", + "usage": [ + "clip-captured notification" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/sounds/clip_captured.wav" + ], + "linux": [ + "_internal/assets/sounds/clip_captured.wav" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/sounds/error.wav", + "sha256": "4af4ae3d084043b0c3cb7bda6943ad3ff10444c67722ca48c616b3a0827297a0", + "media_type": "audio/wav", + "usage": [ + "error notification" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/sounds/error.wav" + ], + "linux": [ + "_internal/assets/sounds/error.wav" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/sounds/screenshot_saved.wav", + "sha256": "80a3e9e313d6b825177eb5a82513c5907a81550d0d4d16e744f49ffb42356efb", + "media_type": "audio/wav", + "usage": [ + "screenshot-saved notification" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/sounds/screenshot_saved.wav" + ], + "linux": [ + "_internal/assets/sounds/screenshot_saved.wav" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/sounds/startup.wav", + "sha256": "56a04ecc28578f8cd83adfb2f37f2fa7d68ad3a7d29cb0068b6ec4cc556d4152", + "media_type": "audio/wav", + "usage": [ + "startup notification" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/sounds/startup.wav" + ], + "linux": [ + "_internal/assets/sounds/startup.wav" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/sounds/upload_failed.wav", + "sha256": "8e99c82a31e66f85a86d97df68c5fc05b0601dafd566b3b15e29b84770b35f1c", + "media_type": "audio/wav", + "usage": [ + "upload-failed notification" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/sounds/upload_failed.wav" + ], + "linux": [ + "_internal/assets/sounds/upload_failed.wav" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/sounds/upload_successful.wav", + "sha256": "9eb8a4605642b28663697bb8045434208aeb2198359d5c5ee2c236ed9bd62137", + "media_type": "audio/wav", + "usage": [ + "upload-success notification" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/sounds/upload_successful.wav" + ], + "linux": [ + "_internal/assets/sounds/upload_successful.wav" + ] + }, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/wizard_banner.bmp", + "sha256": "22c0a8a0d2687cb4fd903856988aca208487948c4f92b007d8b068947e95d45a", + "media_type": "image/bmp", + "usage": [ + "legacy installer artwork retained in source but not embedded" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/wizard_small.bmp", + "sha256": "55448c510e3562627239f18cd7ad4e384654dc8abcda7a319d44ef99794c0852", + "media_type": "image/bmp", + "usage": [ + "legacy installer artwork retained in source but not embedded" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT", + "copyright": "Copyright (c) 2026 FTHR-Community", + "origin": "fthr-generator-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/fonts/Oswald-Regular.ttf", + "sha256": "0d9980da817ee71ad9c34bb46950fcf15dd53e928031f39d2e11ae19178ab826", + "media_type": "font/ttf", + "usage": [ + "application regular interface font" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/fonts/Oswald-Regular.ttf" + ], + "linux": [ + "_internal/assets/fonts/Oswald-Regular.ttf" + ] + }, + "license": "OFL-1.1", + "copyright": "Copyright 2016 The Oswald Project Authors (https://github.com/googlefonts/OswaldFont)", + "origin": "oswald-4.103-regular", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/fonts/Oswald-SemiBold.ttf", + "sha256": "8c5e61737c100d0a1ca87a9a1608afc53246b8a77f8d73c56b03406adc2d9282", + "media_type": "font/ttf", + "usage": [ + "application semibold interface font" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/fonts/Oswald-SemiBold.ttf" + ], + "linux": [ + "_internal/assets/fonts/Oswald-SemiBold.ttf" + ] + }, + "license": "OFL-1.1", + "copyright": "Copyright 2016 The Oswald Project Authors (https://github.com/googlefonts/OswaldFont)", + "origin": "oswald-4.103-semibold", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/camera.svg", + "sha256": "852987633643a36ae2ac2ba75ae306b7a4af1bf12fc602b4053ce63de70a919e", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/camera.svg" + ], + "linux": [ + "_internal/assets/icons/camera.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/chevronleft.svg", + "sha256": "eb36867087cf0074c67029c848c749ae7bf40d2a058efdc48a346a3780858dac", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/chevronleft.svg" + ], + "linux": [ + "_internal/assets/icons/chevronleft.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/chevronright.svg", + "sha256": "3cb67583cf220d219a490db86ee209645866d0619db36f47be04ea18f12f137b", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/chevronright.svg" + ], + "linux": [ + "_internal/assets/icons/chevronright.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/clips.svg", + "sha256": "1c4d1fe273ccaf209f31983a0f448f4800e95e88e6d5ffb1c3a695a1c1f607b5", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/clips.svg" + ], + "linux": [ + "_internal/assets/icons/clips.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/close.svg", + "sha256": "026a61b97ae9c76662d1ae2ecd42029e0fa56cb61ce948bf4c2edc22c8b19103", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/close.svg" + ], + "linux": [ + "_internal/assets/icons/close.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/copylink.svg", + "sha256": "04fa1ee1894b458e4f6575293c0fcbbf8dcffc908c74aec0594260af68192a07", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/copylink.svg" + ], + "linux": [ + "_internal/assets/icons/copylink.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/customise.svg", + "sha256": "95610614af94201e590e22571763538440afa11a8c3fb5f067c531ef0fa9e530", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/customise.svg" + ], + "linux": [ + "_internal/assets/icons/customise.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/delete.svg", + "sha256": "429b3f711367f4072e89cd670346471fc73b26e061283d114f17323cbc8f11ac", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/delete.svg" + ], + "linux": [ + "_internal/assets/icons/delete.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/dropdown.svg", + "sha256": "e2e2b9bdea6cc43937d3894b98df0c7207344fc9530c02aa896e75dcadc0daec", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/dropdown.svg" + ], + "linux": [ + "_internal/assets/icons/dropdown.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/home.svg", + "sha256": "1c4d1fe273ccaf209f31983a0f448f4800e95e88e6d5ffb1c3a695a1c1f607b5", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/home.svg" + ], + "linux": [ + "_internal/assets/icons/home.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/maximise.svg", + "sha256": "f608bf3e31c5fca36ff472f95932928ed20e1f6a6d89dc3683c6b3398090f0d3", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/maximise.svg" + ], + "linux": [ + "_internal/assets/icons/maximise.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/minimise.svg", + "sha256": "450dbeda48d38f703b4c1ecbd3b17a70c0dd2b5ec1738d0f076bc60c77bd4b90", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/minimise.svg" + ], + "linux": [ + "_internal/assets/icons/minimise.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/pause.svg", + "sha256": "2a3c86dc08ba30efbd4b9f021c1dd9ee7318be41541137e7069e17fc425a6446", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/pause.svg" + ], + "linux": [ + "_internal/assets/icons/pause.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/performance.svg", + "sha256": "d5e13ffdcf86e32d19e2c62db1b743aadaf50526378ee83034c1de6c24f70b97", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/performance.svg" + ], + "linux": [ + "_internal/assets/icons/performance.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/play.svg", + "sha256": "99287ca034fc58d3eb984111c50fe2277505ba8e9f8e6b1aba8243b053dded1b", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/play.svg" + ], + "linux": [ + "_internal/assets/icons/play.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/plus.svg", + "sha256": "3fb969804f21bf7e3746d9a845a7eaed49d3c0c45c19ef70cd0f5d51de26128d", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/plus.svg" + ], + "linux": [ + "_internal/assets/icons/plus.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/refresh.svg", + "sha256": "aa3b373577d10c4e4a2e695bca8a21875e0c4dfed52660c0ed5d581d5544554a", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/refresh.svg" + ], + "linux": [ + "_internal/assets/icons/refresh.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/settings.svg", + "sha256": "52ec0340493277455b1f2e6a7e05dfdf0b05f0211b2728b1a8ad4633dd3acc9b", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/settings.svg" + ], + "linux": [ + "_internal/assets/icons/settings.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/shortcuts.svg", + "sha256": "f43ecb9b857fffc8cf2108f69e510f05e70910d63a448ffa849d453f981ffc8c", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/shortcuts.svg" + ], + "linux": [ + "_internal/assets/icons/shortcuts.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/shutdown.svg", + "sha256": "2aac6a5fadad6a25e866d6b7d214541f50da45fdbcd3804bf8a8291e45d82dad", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/shutdown.svg" + ], + "linux": [ + "_internal/assets/icons/shutdown.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/sound.svg", + "sha256": "21107ee03ba994ac0f9f26808eef1e3f28268e4f7e4d983dae5c68c04c53d1c8", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/sound.svg" + ], + "linux": [ + "_internal/assets/icons/sound.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/update.svg", + "sha256": "1b3f9dd2f5741465d0790817f75f6ecec76815fc4f7e95df32f5590ad7f1a555", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/update.svg" + ], + "linux": [ + "_internal/assets/icons/update.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/upload.svg", + "sha256": "2c6b59d1f061056228a1176766a8b71fabc7a52ccd2713dc6dbe0ca0a328619d", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/upload.svg" + ], + "linux": [ + "_internal/assets/icons/upload.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/visuals.svg", + "sha256": "f0166a1ec71eb48db378114e2cbd57700f4f690dc1bea73909c3a94df55ee698", + "media_type": "image/svg+xml", + "usage": [ + "application interface icon" + ], + "distributions": [ + "windows", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/visuals.svg" + ], + "linux": [ + "_internal/assets/icons/visuals.svg" + ] + }, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Software (project-supplied source artwork)", + "origin": "fthr-ui-reference-svg", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/close-current.png", + "sha256": "940a88bf9bffcdccdefe519046c588233d505ed1906c21eda57b94211798bf23", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "GPL-3.0-only", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-active-108.png", + "sha256": "6278873a7ce8a218772da2b6456454d1ff1fd48439e8676e64ca503e022e7633", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-active-122.png", + "sha256": "4b0d4b07184be1adbb9e9aac208339bf89eb8782f363f220215b89341f2e3e35", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-active-135.png", + "sha256": "5d8685c5299fb373762d9e57ef9a28304eaab6f0c9dfbd703472ee89a2500d2a", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-active-162.png", + "sha256": "e5abb277168e4a749e95143b0787754ed2b9f8f0f102729b553d7703d9f1caaa", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-active-216.png", + "sha256": "1c2dca7306dcf3b95b18fc9c17946634717dbf6b19b09d1ac4de5bfbf1af90c9", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-active-54.png", + "sha256": "246d54ea75cd401e38f02dbd5563b90a343e2b82cc96d3db6e83b54765095a6a", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-active-68.png", + "sha256": "f01d327fb7f57c540960a47f3d6d113da8e0f88d287ef67b4b811a5bf84a61a3", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-active-81.png", + "sha256": "d715e657a069a5f97cccfcd1cd6d6cdc901c046003ef5d5dcee37e2f7d340554", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-active-95.png", + "sha256": "d42df6c61b3e8e1e5db65409c84f9098f063277992bc53852014d9980f7c725a", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-done-108.png", + "sha256": "ea3cdb0de27ef861768910b5cdcb8cab173e94f3a60f120e091c0ac60145f90f", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-done-122.png", + "sha256": "0c3905ed3137c9096c37a3740787ed89490c5b5c087230cbc90c58936fdd8a6b", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-done-135.png", + "sha256": "370dabaf4ff1e906d63ec4cd11ae9094feee5fef579cdb40c862c06d16c7f331", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-done-162.png", + "sha256": "cbbff0dfd6712e8d32c102bde2f8b0fa6e455bce8fdca1de2c121e91ece29328", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-done-216.png", + "sha256": "153fb4a2f9bc7da3a08aeb19b4b1f5e2125d5243a3ce67f448deb8f14f1ed97f", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-done-54.png", + "sha256": "57d6966f8ab4ad244eec113d7171c0e7e09703d4244df3c5e9e42bff55942c04", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-done-68.png", + "sha256": "1ae1ec134ca5e6bb3c6c2753007325fdfe383549b6d810a6ac23a0d27f10df01", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-done-81.png", + "sha256": "1cf25cad987430648ab7f98d56f7c94105c3ed8190790e36216f4b840fd67d42", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-done-95.png", + "sha256": "36affb0a5f129e256f47d581f9e757fed1b7cae1b54613056daa2c260d117936", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-pending-108.png", + "sha256": "a30429707c2875dcc210ff3a99d128ee245950556bcaec619b039d0bc6496405", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-pending-122.png", + "sha256": "4bb3f9bec9cea5311748bbf0f33d4ef90221d50f35f269308b78d750ebe3f0cc", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-pending-135.png", + "sha256": "927508ff69a4d790391907dbb08259bf7bd426c7b600178186246bec8a4074f2", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-pending-162.png", + "sha256": "4dc3cd5b09cc69665c43a094bf0080de9798ce2b5926b210a549c7ef833a192c", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-pending-216.png", + "sha256": "11906f84a16c17a6c342be334505e463cb2932764f06647f5e668cc1a2264564", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-pending-54.png", + "sha256": "b908f6f6da476d5d29b1c7551fff154a6449881bde2f0b80dd66a12af7623228", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-pending-68.png", + "sha256": "1156b100803eb9cc5919d4099964350472a98433fa4cb0e331f9be0e96e9c00b", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-pending-81.png", + "sha256": "8844b483784a2b3945b2a2aee92d4c65fcb9ee1dec3ca363c2e55b4daca80e9e", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-1-pending-95.png", + "sha256": "f643a52c9ee7a1b3e82314e586074bda0b2b54ce650237097be13e97ae710726", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-active-108.png", + "sha256": "b2cc27b12da0875632bde63ac210380becc0936ed4b461f6466cd92fb8a8e367", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-active-122.png", + "sha256": "2213d648fe012fb6b9a9f4ab5a2f19f69d136b095ea9cb47eee6570f547712b2", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-active-135.png", + "sha256": "be98c53a70d37ac7dc264dbba0768b6fcd3507d62900fa91756b360bd9b7c621", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-active-162.png", + "sha256": "bc876b2f180a678fddb94d400c3b1d54e6d555a5e644bb4f71d2c31532c9a054", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-active-216.png", + "sha256": "c189fe81df6ae3efcf605c2f396e555b89e519f247576f497a3f409342f1d9d4", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-active-54.png", + "sha256": "53bd8e25e5d50701cb694b612fd403f28ff2561cfb41c0c6d0dd52d389ffcb37", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-active-68.png", + "sha256": "d5d091fb56814348397f40b37a442f3319a219cb9164f3d4f67e679307aa447c", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-active-81.png", + "sha256": "0d12657c460d2c88e434142ab50bb780f94be92756847d3baa6fd11b0ded3fd5", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-active-95.png", + "sha256": "3eee2c6e2f1f9082db7e804ec8e1f9769fcc9a50da260a1c3d7cbc05e2975b53", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-done-108.png", + "sha256": "d0e59e4aac5e74a485ea6a154717daf61d2a1f4fac3906be07d934abccddd143", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-done-122.png", + "sha256": "84b5992c566088309a48161f58221bd4b050ff2aa4b1394820767c13439f5d53", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-done-135.png", + "sha256": "2972e698ac572bc915bb991eaa7d7ac682d36f4a9f8cfefca883f57ebe9039ea", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-done-162.png", + "sha256": "40f1b37fcf16ec5fdd040047f06beff29569f46452a1038435e1ec49a0a75131", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-done-216.png", + "sha256": "ef7a4b5bb49c5f507142090d0a87688bdd6d064d5b42714d47b4d4a9fa765c1b", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-done-54.png", + "sha256": "4abc50f7a1acf19e69c62e8eaee97581cc10c62c843db5bd50148284556198d9", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-done-68.png", + "sha256": "dd3e3e263364a9595ddb7dfcb11c0315feea6230019812855d843ef872fd148c", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-done-81.png", + "sha256": "ab5aab216e0e98943e3af4343781acbbe8d96dd2dd666d9e761f5d47975c9ea6", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-done-95.png", + "sha256": "485c99cb2d0712cf54d0d8f6acda6617c219e6e118b24a25aba4bec6d495240a", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-pending-108.png", + "sha256": "c31a0b6168c7a87d6b2da2537dda7b1a6a33065eb208bcfa7262c015c10ab7fb", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-pending-122.png", + "sha256": "d6aa3608bdc56c90c6f11a7c72ef7d1de4cd2037ad48f22f25affd1fd8668666", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-pending-135.png", + "sha256": "e560d391f0c0d2a5560bc8fb90fcb39534b55996f525837ec7facada35369110", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-pending-162.png", + "sha256": "65c7854ca52cc13673cec4efd6be7be669b71eeb4e952f04d44f37adc0d485c8", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-pending-216.png", + "sha256": "cea6f0b3f4302bd2bf778d678d22ecc605238c41db948deb9d199305d5d0fb59", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-pending-54.png", + "sha256": "301c0135d2253af0e5a59b1245b4550794a61092163b9372a314598f7a837ab1", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-pending-68.png", + "sha256": "40ca8a77d8568bba9e5604c31c8405ca699dfef1205277033771cfdfc08ba87d", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-pending-81.png", + "sha256": "939955ecf08af064ad6df8e35e01f0891e48ef915275083b16864cd4aa6d2bf5", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-2-pending-95.png", + "sha256": "00e73facf55b9596b5ed5d14217d2405507e8fbd4244d0b1147ffe89e4bab2e1", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-active-108.png", + "sha256": "660c4a970d432e411ffa2af8d94be3072e5a53af86eb9b296671628a4e12140d", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-active-122.png", + "sha256": "d0b26a5aa3e7c14a94f490a345fe75825dcd01090f822b2dcb7bf3dd71cb6e0c", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-active-135.png", + "sha256": "a366cc8cadd171276de95a116b6581a23ada4db816381e05a812331f3c588088", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-active-162.png", + "sha256": "ae8910fd51c3ffb0465ba3e60a3b63e8fab6e278bb015c293d8ced9d901c8ced", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-active-216.png", + "sha256": "ef45d48d4d6fb239c2a332d396a626a95a4d76033b04735f0a02e23b6ecf33ca", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-active-54.png", + "sha256": "49ed06bd3eb4d4d141be2b934a4b67a6a669bc45b83409e0a8222ba2517abb48", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-active-68.png", + "sha256": "0aa316d200c7491f76ff65965c081e079521ea86351ec8ae61e6231fae7323bb", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-active-81.png", + "sha256": "3a4468cd8448de923b5b5e2654f193cdd8aa3f344c21163841d204d544cc015f", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-active-95.png", + "sha256": "ef9ea3259de3dcfa0afdee6f7251607e1e556b71e65054f50bd9a010d9b0ca9f", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-done-108.png", + "sha256": "dcc19050fd4a815402703cbe765cb6662ce3514930b9c4311a47564a0319a1cf", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-done-122.png", + "sha256": "7347818e59e452093ae7da2af6f39da4e1255cff526cbf6e597c1c2390d9cf65", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-done-135.png", + "sha256": "7299e550e1f595b7b2e8351ba22d566c49cc05eae667def84ec8a7cb6d47bb26", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-done-162.png", + "sha256": "75cb2a39c2bbe420b9edb835707782e4a11f3d6c47f63451696d11b526a3ba81", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-done-216.png", + "sha256": "ddf8dbcc372fdb04b4841ccdb98d51ec2364a3199d1fdc5580e2e9210ca25d3a", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-done-54.png", + "sha256": "9717a71cc79dc4894fc5c6722477477b60d3f78708da064c11e97d7164468171", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-done-68.png", + "sha256": "6de20add10a7f5edd4b1865be0be01a9cb27a8149dc7294229af67915b49cd12", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-done-81.png", + "sha256": "1f83db156b5571447c48e265524faffb44b4ba60b035f22cb89b13d32c477ff5", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-done-95.png", + "sha256": "ac9ca5ba8d19650eceac5162867bca04bad6b8ebce51fe1f22270810a32db4a6", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-pending-108.png", + "sha256": "c88caacd9a2ed1e6ca0e20dd06b3b1e33ba3cbe0eaaea180362d6a4ef916e698", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-pending-122.png", + "sha256": "6a09419396ffb33671eee62ba6a4a98062c7fbc9515790a2cf89e6b7ee748a30", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-pending-135.png", + "sha256": "b7dd38ec3f14ad788ec8be2eef47d0c8c614b0760200a664491672d40d95415d", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-pending-162.png", + "sha256": "dff2e52c337b423c2c56902ab7ce075eb97ba47dfd183841efa672b903722327", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-pending-216.png", + "sha256": "1e3244141a480cd1d2e4ab3fd04092088a7d5d56f692281f45990d8913a71e04", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-pending-54.png", + "sha256": "a00785e95ca455beaf4ebb62dcf39309e3c48acadacaa12bc02198eda225d0f2", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-pending-68.png", + "sha256": "5a0b848b7119283487dc943ae4301e6b61095ef2c1440a334c16bfec543a8873", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-pending-81.png", + "sha256": "47590197aff3c0ec686ea5c4e289e1bc3a9c6e1d2292c76ca96495d776b6a02c", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "installer_assets/polished/step-3-pending-95.png", + "sha256": "8e784531a552f6bd4f08a0e49d11b152a28be4060a762ff23ccb57ed7984aae9", + "media_type": "image/png", + "usage": [ + "Windows installer artwork" + ], + "distributions": [], + "artifact_paths": {}, + "license": "MIT; font rendering from OFL-1.1 Oswald", + "copyright": "Copyright (c) 2026 FTHR-Community; close artwork: FTHR-Software; font: The Oswald Project Authors", + "origin": "fthr-installer-polish-v1", + "redistribution_status": "approved" + }, + { + "path": "FTHR_UI/assets/icons/kofi_symbol.png", + "sha256": "6b0f958d50f68c84e235473a151f9e365a02e5a9abae4de63048d5f1210c6637", + "media_type": "image/png", + "usage": [ + "Donation button linking to the project Ko-fi page" + ], + "distributions": [ + "windows", + "linux", + "linux" + ], + "artifact_paths": { + "windows": [ + "_internal/assets/icons/kofi_symbol.png" + ], + "linux": [ + "_internal/assets/icons/kofi_symbol.png" + ] + }, + "packaging_references": [ + "FTHR_UI/main.py" + ], + "license": "Ko-fi promotional brand-asset permission; subject to content guidelines", + "copyright": "Ko-fi / Ko-fi Labs Limited", + "origin": "kofi-promotional-symbol", + "redistribution_status": "approved" + } ] } From 83c53bea81d25bc1772e0b8655823373239dade5 Mon Sep 17 00:00:00 2001 From: kalopsia-space Date: Sat, 17 Oct 2026 14:44:41 +0100 Subject: [PATCH 05/12] docs: merge the Linux and 2.0 third-party notices The merge took 2.0's THIRD_PARTY_NOTICES.md, which removed every Linux entry (Linux FFmpeg, jeepney, Wayland protocols, PipeWire/D-Bus headers, the AppImage runtime) although the AppImage still ships or uses them. Start from the linux notices and add what 2.0 brought: the Windows-only keyboard package, the Qt Quick/QML modules the editor bundles, the optional uploader's dependencies (paramiko LGPL-2.1+, the Linux SecretStorage and jeepney backend), and the new Oswald, SVG, user-provided and Ko-fi assets. --- THIRD_PARTY_NOTICES.md | 153 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 145 insertions(+), 8 deletions(-) diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index a02fcd3b..4323d22b 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -10,9 +10,9 @@ identified below retain their respective licences. Full licence texts live in [`licenses/`](licenses/), and are installed alongside the application (Windows: `licenses\` in the install directory; -the `licenses/` directory). +Linux: `licenses/` inside the AppImage). -Last verified: **2026-08-24**. +Last verified: **2026-10-17**. --- @@ -93,6 +93,26 @@ remain fully available. > outside the scope of the LGPL fix — but it is worth a deliberate decision > before a large public release. +### FFmpeg (Linux engine and AppImage) + +| | | +|---|---| +| **Version** | `n8.1.2-34-g9b6c8969e0` (release branch 8.1) | +| **Source** | [BtbN/FFmpeg-Builds](https://github.com/BtbN/FFmpeg-Builds), release `autobuild-2026-07-31-14-10`, asset `ffmpeg-n8.1.2-34-g9b6c8969e0-linux64-lgpl-shared-8.1.tar.xz` | +| **SHA-256 (archive)** | `c882a80f06617149...` — full value, and the hash of every shipped library, recorded in [`tools/ffmpeg_manifest_linux.json`](tools/ffmpeg_manifest_linux.json) | +| **Licence** | **LGPL v3 or later** (`--enable-version3`, no `--enable-gpl`, no `--enable-nonfree`) — self-reported by the binaries as `LGPL version 3 or later` | +| **Linkage** | Dynamic. The native capture engine (`FTHRclips`) and `libFTHRPlaybackMixer.so` link the shared libraries through an `$ORIGIN` RPATH; the Python UI shells out to the bundled `ffmpeg` and `ffprobe`, which use the same libraries. | +| **Used for** | The same roles as the Windows build: encoding and muxing in the capture engine; audio mix, watermark, webcam overlay, clip export and share in the UI. | +| **Shipped files** | `libavcodec.so.62`, `libavdevice.so.62`, `libavfilter.so.11`, `libavformat.so.62`, `libavutil.so.60`, `libswresample.so.6`, `libswscale.so.9` (seven libraries, hash-pinned), and the `ffmpeg` and `ffprobe` command-line tools (RPATH `$ORIGIN/../lib:$ORIGIN`, verified by banner, configure flags, NEEDED and RPATH rather than by hash) | +| **Source route** | The binaries are the unmodified upstream build apart from the RPATH of the two command-line tools, which `tools/fetch_third_party.py --ffmpeg-linux` rewrites with `patchelf`. The corresponding source is FFmpeg tag `n8.1.2` plus the build scripts in the BtbN release named above; the exact asset is reproduced with `python tools/fetch_third_party.py --ffmpeg-linux`. | +| **Licence text** | [`licenses/FFmpeg-LICENSE.txt`](licenses/FFmpeg-LICENSE.txt) | + +The configuration is the Windows build's LGPL configuration (see above): no +GPL encoders, software H.264 through `libopenh264`, H.265 through `libkvazaar`, +AV1 through `libsvtav1`. The distribution's own FFmpeg (a GPL build on Ubuntu) +is never bundled; the licence gate refuses an AppDir that contains it, or an +`ffmpeg`/`ffprobe` that links it or carries a non-`$ORIGIN` RPATH. + ### Qt 6 | | | @@ -101,7 +121,8 @@ remain fully available. | **Source** | [Qt 6.11.1 source](https://download.qt.io/official_releases/qt/6.11/6.11.1/single/qt-everywhere-src-6.11.1.tar.xz) | | **Licence** | LGPL-3.0-only option selected | | **Linkage** | Dynamic (shared libraries bundled by PyInstaller) | -| **Used for** | Entire GUI, multimedia playback | +| **Used for** | Entire GUI, multimedia playback. Qt Quick and Qt QML render the editor's GPU video preview and snapshots (`FTHR_UI/ui/qml/`). | +| **Modules** | The bundled set is fixed per platform in [`tools/qt_runtime_manifest.json`](tools/qt_runtime_manifest.json). Since 2.0.0-alpha it includes the Qt Quick/QML runtime: `Qml`, `QmlCore`, `QmlMeta`, `QmlModels`, `QmlWorkerScript`, `Quick`, `QuickWidgets` and `MultimediaQuick`, plus only the `QtQml`, `QtQml.Models`, `QtQml.WorkerScript`, `QtQuick` and `QtMultimedia` QML import directories. All are part of Qt's LGPL-3.0 offering; the GPL-only Qt Quick add-ons (Quick 3D, Quick Timeline, Virtual Keyboard) are refused by the licence gate. | | **Licence/source notice** | [`licenses/Qt6-LICENSE.txt`](licenses/Qt6-LICENSE.txt), [`licenses/Qt6-SOURCE.txt`](licenses/Qt6-SOURCE.txt), [`licenses/Qt6-THIRD-PARTY-NOTICES.txt`](licenses/Qt6-THIRD-PARTY-NOTICES.txt) | ### FFmpeg bundled inside Qt Multimedia @@ -156,14 +177,22 @@ conflict. | **Version** | 0.5.5 · **Licence** MIT | | **Used for** | Microphone capture | | **Licence text** | [`licenses/sounddevice-LICENSE.txt`](licenses/sounddevice-LICENSE.txt) | -| **Note** | Wraps **PortAudio** (MIT). | +| **Note** | Wraps **PortAudio** (MIT). On Linux `libportaudio.so.2` is bundled explicitly by `FTHR_linux.spec`. | + +### jeepney + +| | | +|---|---| +| **Version** | 0.9.0 · **Licence** MIT | +| **Used for** | Linux only: D-Bus client for the XDG GlobalShortcuts portal (`core/portal_shortcuts.py`). Pure Python, no native code. | +| **Licence text** | [`licenses/jeepney-LICENSE.txt`](licenses/jeepney-LICENSE.txt) | ### keyboard | | | |---|---| | **Version** | 0.13.5 · **Licence** MIT | -| **Used for** | Global hotkeys (the only hotkey path on Windows) | +| **Used for** | Windows only: named as a hidden import by `FTHR.spec`. It is installed only on Windows (environment marker) and is not part of the Linux AppImage. | | **Licence text** | [`licenses/keyboard-LICENSE.txt`](licenses/keyboard-LICENSE.txt) | ### cffi and pycparser @@ -175,6 +204,36 @@ conflict. | **Used for** | Transitive runtime dependencies of python-sounddevice | | **Licence texts** | [`licenses/cffi-LICENSE.txt`](licenses/cffi-LICENSE.txt), [`licenses/pycparser-LICENSE.txt`](licenses/pycparser-LICENSE.txt) | +### Optional uploader package dependencies + +The optional SFTP uploader is not part of Core. It ships as a sealed, dormant +`.fthrplugin` package (`FTHR-Uploader.fthrplugin` on Windows, +`FTHR-Uploader-linux.fthrplugin` inside the AppImage) and is installed only +after the user's explicit in-app consent. Its frozen executable contains the +packages below, pinned in +[`requirements-uploader.txt`](requirements-uploader.txt). Each package carries +its full licence text inside the plugin under +`payload/licenses/optional-uploader/`; the same files are in +[`licenses/optional-uploader/`](licenses/optional-uploader/). + +| Package | Version | Licence | Platforms | +|---|---|---|---| +| paramiko | 4.0.0 | LGPL-2.1-or-later | both | +| bcrypt | 5.0.0 | Apache-2.0 | both | +| cryptography | 50.0.1 | Apache-2.0 OR BSD-3-Clause | both | +| PyNaCl (bundles libsodium, ISC) | 1.6.2 | Apache-2.0 | both | +| invoke | 3.0.3 | BSD-2-Clause | both | +| keyring | 25.7.0 | MIT | both | +| jaraco.classes, jaraco.context, jaraco.functools | 3.4.0, 6.1.2, 4.6.0 | MIT | both | +| more-itertools | 11.1.0 | MIT | both | +| cffi, pycparser | 2.0.0, 3.0 | MIT-0, BSD-3-Clause | both | +| pywin32-ctypes | 0.2.3 | BSD-3-Clause | Windows | +| SecretStorage | 3.5.0 | BSD-3-Clause | Linux (keyring's Secret Service backend) | +| jeepney | 0.9.0 | MIT | Linux (D-Bus client used by SecretStorage) | + +paramiko is used unmodified as a separate library inside the plugin +executable; its LGPL source is the published paramiko 4.0.0 release. + ### NVIDIA Video Codec SDK header | | | @@ -185,6 +244,26 @@ conflict. | **Linkage** | **None at build time.** NVENC is resolved at runtime via `LoadLibraryA("nvEncodeAPI64.dll")` against the user's installed driver. The header is source-only and the NVIDIA runtime is **not** redistributed. | | **Licence text** | [`licenses/NVIDIA-NVENC-SDK-LICENSE.txt`](licenses/NVIDIA-NVENC-SDK-LICENSE.txt) | +### Wayland protocol definitions and generated bindings + +| | | +|---|---| +| **Files** | `FTHRcapture_linux/protocols/*.xml` and generated client bindings compiled into the Linux engine | +| **Copyright** | The wlroots, Chromium OS, and Wayland contributors named in the source notices | +| **Licence** | MIT-style permissive grants stated in the protocol sources | +| **Used for** | Linux compositor capture and related Wayland protocol integration | +| **Licence notices** | [`licenses/Wayland-Protocols-NOTICES.txt`](licenses/Wayland-Protocols-NOTICES.txt) | + +### PipeWire and D-Bus headers (Linux engine) + +| | | +|---|---| +| **Files** | System headers `pipewire-0.3/`, `spa-0.2/` and `dbus-1.0/` read at build time only; nothing from either project is copied into this repository or the AppImage | +| **Copyright** | PipeWire: Wim Taymans and contributors. D-Bus: Red Hat, Inc. and contributors | +| **Licence** | PipeWire headers: MIT. libdbus: dual AFL-2.1 / GPL-2.0-or-later, used under the AFL-2.1 option | +| **Linkage** | **None at build time.** The engine resolves `libpipewire-0.3.so.0` and `libdbus-1.so.3` with `dlopen()`/`dlsym()` on the user's system when the ScreenCast portal capture backend is needed, and reports that backend as unavailable otherwise. The libraries are **not** redistributed. | +| **Used for** | `org.freedesktop.portal.ScreenCast` session setup over the session bus and PipeWire video stream capture on compositors without a capture protocol | + ### Microsoft Visual C++ Redistributable | | | @@ -193,12 +272,37 @@ conflict. | **Licence** | Microsoft Visual Studio redistributable terms | | **Note** | Redistributed unmodified as permitted for VC++ runtime redistribution. | +### AppImage type-2 runtime + +| | | +|---|---| +| **Version** | `20251108`, upstream commit `dd6cebedcbddde9c82f89b011e8e1d40b6e43868` | +| **Source** | [AppImage/type2-runtime](https://github.com/AppImage/type2-runtime/releases/tag/20251108), asset `runtime-x86_64` | +| **SHA-256** | `2fca8b443c92510f1483a883f60061ad09b46b978b2631c807cd873a47ec260d` | +| **Usage** | Embedded by the pinned Linux AppImage build as its type-2 runtime; it is not loaded by the FTHR application process. | +| **Licence** | MIT for the runtime project, with statically linked musl, libfuse, squashfuse, libzstd, and zlib components listed in the upstream notice. | +| **Licence text** | [`licenses/AppImage-type2-runtime-LICENSE.txt`](licenses/AppImage-type2-runtime-LICENSE.txt) | + +The runtime is fetched by the manifest-pinned build step and supplied to +`appimagetool` explicitly with `--runtime-file`. Its URL, release tag, commit, +size, and hash are recorded in +[`tools/appimage_tool_manifest.json`](tools/appimage_tool_manifest.json). + +### AppImage packaging tool (build-only) + +`appimagetool` itself is a build-time dependency and is not part of the +distributed AppImage. The build uses AppImage/appimagetool `1.9.1`, commit +`8c8c91f762b412a19f4e8d2c4b35afb98f2d7c81`, and verifies its size and SHA-256 +from the same manifest before executing it. + +--- + ## Project assets ### FTHR-generated media -The shipped logos, icons, installer artwork, repository social preview, and -four PCM WAV notification sounds are reproducibly generated from code in +The shipped logos, PNG icons, legacy installer bitmaps, the AppImage icon, and +the PCM WAV notification sounds are reproducibly generated from code in `tools/generate_release_assets.py`. The generator uses no external creative input, font, image, sound sample, or icon library. These outputs are FTHR project material under MIT; the notice and exact file list are in @@ -228,6 +332,32 @@ by the release asset script. It uses the project's MIT asset terms; see | **Verification** | Repository and pinned upstream files have SHA-256 `eb7d46f856dd57f18a8c03d033c57802692bf127f01dbd95ba8984338e6b5135` | | **Licence text** | `licenses/Oswald-OFL-1.1.txt` | +### Oswald Regular and SemiBold + +`Oswald-Regular.ttf` and `Oswald-SemiBold.ttf` come unmodified from the same +pinned `googlefonts/OswaldFont` revision as Oswald Bold, under the same SIL +Open Font License 1.1 (`licenses/Oswald-OFL-1.1.txt`). + +### Interface SVG icons and installer artwork + +The SVG interface icons in `FTHR_UI/assets/icons/` are project-supplied source +artwork under the project's GPL-3.0-only licence. The polished Windows +installer artwork in `installer_assets/polished/` is generated by +`tools/generate_installer_assets.py` from those icons and Oswald, also under +GPL-3.0-only; it is used only by the Windows installer. + +### User-provided images + +`preview_desktop.png`, the brand ICO files and the Gary Mode cat image +`gatze.png` were supplied by the workspace owner; see +`licenses/FTHR-USER-PROVIDED-ASSETS.txt`. + +### Ko-fi cup symbol + +`FTHR_UI/assets/icons/kofi_symbol.png` is Ko-fi's brand asset, used only on +the donation button under Ko-fi's promotional permission. It is not covered by +FTHR's licence; see `licenses/KO-FI-BRAND-ASSETS.txt`. + The authoritative per-file paths, hashes, origins, usage, and platform mappings are in `tools/release_asset_manifest.json`. @@ -239,9 +369,15 @@ Listed so future audits do not have to re-derive it: - **`imageio-ffmpeg`** — *removed* on 2026-08-05. Its bundled binary is a gyan.dev build with `--enable-gpl --enable-libx264 --enable-libx265` - (GPLv3). It is now excluded in the Windows PyInstaller spec and removed from + (GPLv3). It is now excluded in both PyInstaller specs and removed from `requirements.txt`. See `FTHR_UI/core/ffmpeg_tools.py`. +- **System FFmpeg on Linux** — if the bundled binary is absent, FTHR falls back + to `ffmpeg` on `PATH`. That copy belongs to the user's distribution and is not + redistributed by this project. - **Development tooling** — pytest, ruff, PyInstaller, Inno Setup, MSVC. +- **PipeWire and libdbus runtime libraries** — loaded from the user's system + with `dlopen()` by the Linux engine when the ScreenCast portal backend runs; + never bundled. See the headers entry above. --- @@ -255,6 +391,7 @@ files described here. Run it before every release: ```bash python tools/verify_release_licenses.py --tree . python tools/verify_release_licenses.py --windows-dist dist/FTHRClips +python tools/verify_release_licenses.py --appdir build_output/AppDir ``` **This is a technical verification, not legal advice.** From 115a46f343a4f304c2f175835c9bf844a6f2bbb3 Mon Sep 17 00:00:00 2001 From: kalopsia-space Date: Sat, 17 Oct 2026 14:44:41 +0100 Subject: [PATCH 06/12] test: restore the Linux pytest path, marker and ruff excludes The merge took 2.0's pyproject.toml, which drops pythonpath, the linux_only marker (--strict-markers turns its uses into errors) and the FTHRcapture_linux/AppDir ruff excludes. Keep those and 2.0's new backup directory excludes. --- pyproject.toml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b795e8c1..cffb57a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ # Tooling configuration only. # # FTHR Clips is NOT distributed as a Python package — it ships as a PyInstaller -# bundle (Windows installer). There is deliberately no +# bundle (Windows installer / Linux AppImage). There is deliberately no # [project] table and no build-system: adding one would imply `pip install .` # is a supported path, which it is not. @@ -9,6 +9,7 @@ minversion = "9.0" testpaths = ["tests"] addopts = "-q --strict-markers" +pythonpath = [".", "FTHR_UI"] # QT_QPA_PLATFORM=offscreen is set in tests/conftest.py rather than here, so it # works without the pytest-env plugin. markers = [ @@ -22,8 +23,8 @@ target-version = "py312" # lowest interpreter CI exercises exclude = [ "build", "dist", "dist_old_gpl_*", ".ui-port", ".ux-backup", ".shortcut-backup", - "FTHRcapture", - "Output", "redist", + "FTHRcapture", "FTHRcapture_linux", + "AppDir", "Output", "redist", ] [tool.ruff.lint] From e9bcacac9a0c984ea436716fd7dcc8e9927182b0 Mon Sep 17 00:00:00 2001 From: kalopsia-space Date: Sat, 17 Oct 2026 14:44:41 +0100 Subject: [PATCH 07/12] ci: install the uploader lock and gate the Linux uploader payload "Test built optional upload packages" failed on Windows with "No module named 'paramiko'": no job installed requirements-uploader.txt, so the sealed executable was frozen without its SFTP stack. The Windows engine, AppImage and Python jobs now install it (tests/test_sftp_provider.py imports paramiko too). The AppImage job and build_linux.sh pass the unpacked uploader payload to the glibc gate; the one-file executable inside the .fthrplugin was invisible to it. From 2.0: bash as the default shell (the MSBuild step moves to pwsh explicitly) and the no-secrets comment. Kept from linux: every Linux job, the PyInstaller exit-code check, ubuntu release verification and the narrow "test_built_" run on Windows, since the python matrix already runs the rest of the plugin boundary tests. The CI contract tests get back the Linux engine job check and a check for the uploader lock installs. --- .github/workflows/ci.yml | 26 +++++++++++++++++++++----- build_linux.sh | 13 ++++++++++++- tests/test_ci_contract.py | 25 ++++++++++++++++++++++++- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 371c6150..7b50cff4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,9 @@ on: pull_request: branches: [main, master, linux, linux-2.0] -# Keep CI read-only and free of secrets; release verification scans untrusted changes. +# No secrets are used anywhere in this workflow, and none should ever be added. +# The release-verification job exists partly to catch a credential that made it +# into the tree; giving that job access to credentials would be self-defeating. permissions: contents: read @@ -15,6 +17,10 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +defaults: + run: + shell: bash + jobs: # Python — the UI and its tests, on both platforms that ship a build. python: @@ -38,6 +44,7 @@ jobs: cache-dependency-path: | requirements-alpha.txt requirements-dev.txt + requirements-uploader.txt - name: Install system dependencies (Linux) if: runner.os == 'Linux' @@ -52,9 +59,11 @@ jobs: - name: Install pinned dependencies # The alpha lock is the ONLY install path. If it does not resolve on a # supported interpreter, that is a release bug, not a CI inconvenience. + # The uploader set is installed too: tests/test_sftp_provider.py + # imports paramiko from the optional uploader sources. run: | python -m pip install --upgrade pip - python -m pip install -r requirements-alpha.txt -r requirements-dev.txt + python -m pip install -r requirements-alpha.txt -r requirements-dev.txt -r requirements-uploader.txt - name: Assert the environment matches the lock # Catches a transitive dependency that resolved to something other than @@ -214,6 +223,7 @@ jobs: cache-dependency-path: | requirements-alpha.txt requirements-dev.txt + requirements-uploader.txt - uses: microsoft/setup-msbuild@v2 @@ -239,6 +249,7 @@ jobs: } - name: Build (Release | x64) + shell: pwsh run: > msbuild FTHRcapture\FTHRcapture.sln -p:Configuration=Release @@ -263,9 +274,12 @@ jobs: "engine OK, x64" - name: Install pinned bundle dependencies + # requirements-uploader.txt is what the sealed uploader executable is + # frozen from. Without it PyInstaller silently builds a package that + # fails at runtime with "No module named 'paramiko'". run: | python -m pip install --upgrade pip - python -m pip install -r requirements-alpha.txt -r requirements-dev.txt + python -m pip install -r requirements-alpha.txt -r requirements-dev.txt -r requirements-uploader.txt python -m pip check - name: Build sealed optional upload packages @@ -391,6 +405,7 @@ jobs: cache-dependency-path: | requirements-alpha.txt requirements-dev.txt + requirements-uploader.txt - name: Install packaging dependencies run: | @@ -405,7 +420,7 @@ jobs: libxext-dev libxfixes-dev libwayland-dev wayland-protocols libwayland-bin \ libpipewire-0.3-dev libdbus-1-dev python -m pip install --upgrade pip - python -m pip install -r requirements-alpha.txt -r requirements-dev.txt + python -m pip install -r requirements-alpha.txt -r requirements-dev.txt -r requirements-uploader.txt python -m pip check - name: Build AppImage @@ -421,7 +436,8 @@ jobs: python tools/verify_linux_glibc.py \ --manifest tools/linux_release_baseline.json \ --target build_output/AppDir \ - --target "${image[0]}" + --target "${image[0]}" \ + --target build/optional-uploader-linux/payload-elf - name: Upload verified Linux AppImage uses: actions/upload-artifact@v4 with: diff --git a/build_linux.sh b/build_linux.sh index fc19bbf9..13a8e607 100644 --- a/build_linux.sh +++ b/build_linux.sh @@ -164,7 +164,14 @@ cd "$SCRIPT_DIR" # 3. Build the optional Linux uploader bundle before the main AppImage. echo "" echo ">>> Building optional Linux upload extension..." +# Needs requirements-uploader.txt installed in the same interpreter; the +# builder refuses to freeze an executable without paramiko and friends. "$PYTHON_BIN" "$SCRIPT_DIR/tools/build_linux_uploader.py" --python "$PYTHON_BIN" +UPLOADER_PAYLOAD_ELF="$SCRIPT_DIR/build/optional-uploader-linux/payload-elf" +[ -d "$UPLOADER_PAYLOAD_ELF" ] || { + echo "ERROR: the uploader build left no unpacked payload for the glibc gate." >&2 + exit 1 +} # 4. Bundle with PyInstaller echo "" @@ -422,10 +429,14 @@ cp "$NATIVE_OUTPUT" "$OUTPUT" # and the success banner so a failed portability check cannot look releasable. echo "" echo ">>> Verifying Linux glibc baseline..." +# The optional uploader's one-file payload is compressed inside its +# .fthrplugin, so tools/build_linux_uploader.py unpacks its ELF members for +# this gate; the plugin runs on the same hosts as the AppImage. if ! "$PYTHON_BIN" "$SCRIPT_DIR/tools/verify_linux_glibc.py" \ --manifest "$GLIBC_MANIFEST" \ --target "$APPDIR" \ - --target "$OUTPUT"; then + --target "$OUTPUT" \ + --target "$UPLOADER_PAYLOAD_ELF"; then rm -f "$NATIVE_OUTPUT" "$OUTPUT" "$OUTPUT.sha256" echo "ERROR: Linux glibc baseline verification failed; refusing to publish the AppImage." >&2 exit 1 diff --git a/tests/test_ci_contract.py b/tests/test_ci_contract.py index eabe174f..24257f0d 100644 --- a/tests/test_ci_contract.py +++ b/tests/test_ci_contract.py @@ -13,12 +13,35 @@ def _workflow() -> str: return WORKFLOW.read_text(encoding="utf-8") +def test_linux_release_ci_fetches_and_uses_pinned_ffmpeg() -> None: + workflow = _workflow() + linux_job = workflow[workflow.index(" linux-engine:") : workflow.index( + " windows-engine:" + )] + + assert "fetch_third_party.py --ffmpeg-linux" in linux_job + assert "-DFTHR_FFMPEG_ROOT=${{ github.workspace }}" in linux_job + assert "ctest --test-dir" in linux_job + assert "verify_release_licenses.py --tree ." in linux_job + assert "git diff --exit-code" in linux_job + + +def test_optional_uploader_builds_install_the_uploader_lock() -> None: + workflow = _workflow() + windows_job = workflow[workflow.index(" windows-engine:") : workflow.index( + " release-verification:" + )] + appimage_job = workflow[workflow.index(" linux-appimage-package:") :] + + for job in (windows_job, appimage_job): + assert "-r requirements-uploader.txt" in job + assert "build/optional-uploader-linux/payload-elf" in appimage_job def test_windows_bundle_is_built_only_after_the_engine() -> None: workflow = _workflow() python_job = workflow[workflow.index(" python:") : workflow.index( - " windows-engine:" + " linux-engine:" )] windows_job = workflow[workflow.index(" windows-engine:") : workflow.index( " release-verification:" From 8249e44dc97436fbcb4e137ef0bc457403293717 Mon Sep 17 00:00:00 2001 From: kalopsia-space Date: Sat, 17 Oct 2026 15:01:21 +0100 Subject: [PATCH 08/12] fix(windows): keep the editor's QML scenes in the Windows bundle FTHR.spec's QML filter matched any destination containing '/qml/'. The application's own scenes are bundled at ui/qml/, so VideoPreview.qml, Snapshot.qml and color.frag.qsb failed the "allowed QML module" test and were dropped: the packaged editor loses the GPU preview (it silently falls back to the CPU path) and snapshot scenes fail to load. Anchor the filter to the PySide6 wheel's QML tree as PyInstaller's Qt hooks lay it out (pyside6_library_info.qt_rel_dir: 'PySide6' on Windows, so 'PySide6/qml/'), as FTHR_linux.spec already does. The licence gate now also requires the three files in --windows-dist, --appdir and --all artifacts, byte-identical to FTHR_UI/ui/qml, so a filter regression fails the release instead of shipping a degraded editor. --- FTHR.spec | 15 +++++-- tests/test_qml_scene_gate.py | 76 ++++++++++++++++++++++++++++++++ tools/verify_release_licenses.py | 28 ++++++++++++ 3 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 tests/test_qml_scene_gate.py diff --git a/FTHR.spec b/FTHR.spec index 486dbcf0..9a6158e6 100644 --- a/FTHR.spec +++ b/FTHR.spec @@ -204,15 +204,22 @@ import json as _json _REVIEWED_QT = _json.loads((ROOT / 'tools' / 'qt_runtime_manifest.json').read_text()) _APPROVED_QT_DLLS = {f'qt6{module.lower()}.dll' for module in _REVIEWED_QT['approved_runtime_qt_modules']['windows']} +# Destination of the PySide6 wheel's QML import tree as PyInstaller's Qt hooks +# lay it out ('PySide6/qml/' for the Windows wheel). The filter must be +# anchored here: the application's own scenes live under ui/qml/ and a bare +# '/qml/' match drops VideoPreview.qml, Snapshot.qml and color.frag.qsb. +from PyInstaller.utils.hooks.qt import pyside6_library_info as _pyside6_info # noqa: E402 +_QT_QML_DEST = (Path(_pyside6_info.qt_rel_dir) / 'qml').as_posix().casefold() + '/' +_KEPT_QML_DIRS = ('qtqml', 'qtqml/models', 'qtqml/workerscript', + 'qtquick', 'qtmultimedia') def _keep_reviewed_qt_runtime(entry): dest = str(entry[0]).replace('\\', '/').casefold() name = dest.rsplit('/', 1)[-1] - if '/qml/' in dest: - qml_parent = dest.split('/qml/', 1)[1].rsplit('/', 1)[0] - if qml_parent not in ('qtqml', 'qtqml/models', 'qtqml/workerscript', - 'qtquick', 'qtmultimedia'): + if dest.startswith(_QT_QML_DEST): + qml_parent = dest[len(_QT_QML_DEST):].rpartition('/')[0] + if qml_parent not in _KEPT_QML_DIRS: return False # Qt on supported Windows versions resolves the operating system ICU. # Never collect unrelated ICU copies which happen to be on PATH: a newer diff --git a/tests/test_qml_scene_gate.py b/tests/test_qml_scene_gate.py new file mode 100644 index 00000000..55673fb0 --- /dev/null +++ b/tests/test_qml_scene_gate.py @@ -0,0 +1,76 @@ +"""The release gate refuses a bundle without the editor's QML scenes.""" + +from __future__ import annotations + +import importlib.util +import re +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +_SPEC = importlib.util.spec_from_file_location( + 'verify_release_licenses_qml', ROOT / 'tools' / 'verify_release_licenses.py') +vrl = importlib.util.module_from_spec(_SPEC) +_SPEC.loader.exec_module(vrl) + + +def _source_tree(root: Path) -> None: + for name in vrl.QML_SCENE_FILES: + path = root / vrl.QML_SCENE_SOURCE_DIR / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(f'source {name}\n'.encode()) + + +def _artifact(root: Path, source: Path, skip: str | None = None) -> None: + for name in vrl.QML_SCENE_FILES: + if name == skip: + continue + path = root / vrl.QML_SCENE_ARTIFACT_DIR / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes((source / vrl.QML_SCENE_SOURCE_DIR / name).read_bytes()) + + +def test_complete_bundle_passes(tmp_path): + source, artifact = tmp_path / 'src', tmp_path / 'dist' + _source_tree(source) + _artifact(artifact, source) + for platform in ('windows', 'linux'): + rep = vrl.Report() + vrl.check_qml_scenes(artifact, rep, platform, manifest_root=source) + assert not rep.failures, rep.failures + + +def test_each_missing_scene_fails(tmp_path): + source = tmp_path / 'src' + _source_tree(source) + for name in vrl.QML_SCENE_FILES: + artifact = tmp_path / f'dist-{name}' + _artifact(artifact, source, skip=name) + rep = vrl.Report() + vrl.check_qml_scenes(artifact, rep, 'windows', manifest_root=source) + assert rep.failures == [ + f'windows artifact: application QML file missing: ' + f'{vrl.QML_SCENE_ARTIFACT_DIR}/{name}'] + + +def test_stale_scene_fails(tmp_path): + source, artifact = tmp_path / 'src', tmp_path / 'dist' + _source_tree(source) + _artifact(artifact, source) + (artifact / vrl.QML_SCENE_ARTIFACT_DIR / 'color.frag.qsb').write_bytes(b'old') + rep = vrl.Report() + vrl.check_qml_scenes(artifact, rep, 'linux', manifest_root=source) + assert len(rep.failures) == 1 and 'differs' in rep.failures[0] + + +def test_gate_files_match_the_real_sources(): + for name in vrl.QML_SCENE_FILES: + assert (ROOT / vrl.QML_SCENE_SOURCE_DIR / name).is_file() + + +def test_specs_anchor_the_qml_filter_to_the_qt_tree(): + # A bare '/qml/' match also hits the application's ui/qml/ scenes. + for spec in ('FTHR.spec', 'FTHR_linux.spec'): + text = (ROOT / spec).read_text(encoding='utf-8') + assert "'/qml/' in dest" not in text, spec + assert re.search(r'dest\.startswith\(_QT_QML_DEST\)', text), spec diff --git a/tools/verify_release_licenses.py b/tools/verify_release_licenses.py index 9ce9148f..1d18da4a 100644 --- a/tools/verify_release_licenses.py +++ b/tools/verify_release_licenses.py @@ -638,6 +638,31 @@ def check_asset_artifact(root: Path, rep: Report, platform: str, else: rep.ok(f'{platform} artifact contains exactly {len(expected)} ' 'approved asset files') +# Scenes and the precompiled shader the editor loads from beside +# ui/quick_*.py at runtime. Without them the GPU preview silently falls back to +# the CPU path and snapshots fail, so a bundle that lacks them is broken. +QML_SCENE_FILES = ('VideoPreview.qml', 'Snapshot.qml', 'color.frag.qsb') +QML_SCENE_ARTIFACT_DIR = '_internal/ui/qml' +QML_SCENE_SOURCE_DIR = 'FTHR_UI/ui/qml' + + +def check_qml_scenes(root: Path, rep: Report, platform: str, + manifest_root: Path | None = None) -> None: + """Require the application's QML scenes, byte-identical to the source.""" + print(f'\n-- Application QML scenes in {platform} artifact --') + source_root = manifest_root or _repo_root() + for name in QML_SCENE_FILES: + rel = f'{QML_SCENE_ARTIFACT_DIR}/{name}' + shipped = root / rel + source = source_root / QML_SCENE_SOURCE_DIR / name + if not shipped.is_file(): + rep.fail(f'{platform} artifact: application QML file missing: {rel}') + elif source.is_file() and _file_sha256(source) != _file_sha256(shipped): + rep.fail(f'{platform} artifact: {rel} differs from {QML_SCENE_SOURCE_DIR}/{name}') + else: + rep.ok(f'{platform} artifact: {rel}') + + def check_linux_manifest(root, rep) -> None: """The Linux FFmpeg provenance manifest (AUDIT-014).""" print('\n-- Linux FFmpeg provenance manifest --') @@ -926,6 +951,7 @@ def main() -> int: check_qt_artifact(cand, rep, platform, manifest_root=root) check_asset_artifact( cand, rep, platform, manifest_root=root) + check_qml_scenes(cand, rep, platform, manifest_root=root) if args.windows_dist: d = args.windows_dist.resolve() @@ -935,6 +961,7 @@ def main() -> int: check_qt_artifact(d, rep, 'windows', manifest_root=_repo_root()) check_asset_artifact( d, rep, 'windows', manifest_root=_repo_root()) + check_qml_scenes(d, rep, 'windows', manifest_root=_repo_root()) if args.appdir: d = args.appdir.resolve() @@ -943,6 +970,7 @@ def main() -> int: scan_dir(d, rep, 'appdir') check_qt_artifact(d, rep, 'linux', manifest_root=_repo_root()) check_asset_artifact(d, rep, 'linux', manifest_root=_repo_root()) + check_qml_scenes(d, rep, 'linux', manifest_root=_repo_root()) # AUDIT-014: the artifact is where it actually matters. A GPL library # the engine never loads is still a GPL library being distributed, and # PyInstaller collects the system FFmpeg through cv2 and Qt. From 151999286b291b051ca698ec3df2fda9876ef0a1 Mon Sep 17 00:00:00 2001 From: kalopsia-space Date: Sat, 17 Oct 2026 15:02:06 +0100 Subject: [PATCH 09/12] ci: run the Linux runtime, tools, AppImage report and backend tests The Linux pytest row ignored test_linux_appimage_report.py, test_linux_runtime.py, test_linux_tools.py and test_capture_backend_detection.py, and the Windows row ignores them too, so they ran nowhere. The commit that added the ignores gives no reason, and all four pass headless: the report test only runs the shell script against a fake image, and the backend test uses the engine "Prepare Linux test artifacts" already builds with the pinned FFmpeg on LD_LIBRARY_PATH (the one case that needs a real Wayland session skips itself). A CI contract test keeps them in the Linux row. --- .github/workflows/ci.yml | 7 +++---- tests/test_ci_contract.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b50cff4..6edef55d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,11 +112,10 @@ jobs: QT_QPA_PLATFORM: offscreen run: | if [ "${{ runner.os }}" = "Linux" ]; then + # Every Linux test runs here; test_capture_backend_detection uses + # the engine built in "Prepare Linux test artifacts" (FFmpeg on + # LD_LIBRARY_PATH). Only Windows-only suites are left out. python -m pytest tests/ -v --tb=short \ - --ignore=tests/test_linux_appimage_report.py \ - --ignore=tests/test_linux_runtime.py \ - --ignore=tests/test_linux_tools.py \ - --ignore=tests/test_capture_backend_detection.py \ --ignore=tests/test_windows_monitor.py \ --ignore=tests/test_windows_native_hotkeys.py \ --ignore=tests/test_windows_autostart.py \ diff --git a/tests/test_ci_contract.py b/tests/test_ci_contract.py index 24257f0d..d851d8f9 100644 --- a/tests/test_ci_contract.py +++ b/tests/test_ci_contract.py @@ -113,3 +113,13 @@ def test_release_ci_runs_response_and_exception_contracts() -> None: assert "verify_engine_response_contract.py" in release_job assert "verify_exception_handling.py" in release_job + + +def test_linux_python_job_runs_every_linux_test() -> None: + workflow = _workflow() + tests_step = workflow[workflow.index(" - name: Tests") :] + linux_branch = tests_step[: tests_step.index(" else")] + + for name in ("test_linux_appimage_report", "test_linux_runtime", + "test_linux_tools", "test_capture_backend_detection"): + assert f"--ignore=tests/{name}.py" not in linux_branch From 8535671cefb87bc2e6f35ec0054f067ef78199d0 Mon Sep 17 00:00:00 2001 From: kalopsia-space Date: Sat, 17 Oct 2026 15:02:29 +0100 Subject: [PATCH 10/12] build(linux): hold the uploader executable itself to the glibc baseline The glibc gate saw the ELF members unpacked from the uploader's one-file payload but not the executable that carries them: its PyInstaller bootloader is what the host loader runs first. Pass it as a target in build_linux.sh and in the AppImage CI job (it needs GLIBC_2.14 locally). --- .github/workflows/ci.yml | 1 + build_linux.sh | 11 +++++++---- tests/test_ci_contract.py | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6edef55d..87510081 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -436,6 +436,7 @@ jobs: --manifest tools/linux_release_baseline.json \ --target build_output/AppDir \ --target "${image[0]}" \ + --target build/optional-uploader-linux/dist/FTHR-Uploader \ --target build/optional-uploader-linux/payload-elf - name: Upload verified Linux AppImage uses: actions/upload-artifact@v4 diff --git a/build_linux.sh b/build_linux.sh index 13a8e607..f8a0fdb0 100644 --- a/build_linux.sh +++ b/build_linux.sh @@ -168,8 +168,9 @@ echo ">>> Building optional Linux upload extension..." # builder refuses to freeze an executable without paramiko and friends. "$PYTHON_BIN" "$SCRIPT_DIR/tools/build_linux_uploader.py" --python "$PYTHON_BIN" UPLOADER_PAYLOAD_ELF="$SCRIPT_DIR/build/optional-uploader-linux/payload-elf" -[ -d "$UPLOADER_PAYLOAD_ELF" ] || { - echo "ERROR: the uploader build left no unpacked payload for the glibc gate." >&2 +UPLOADER_EXECUTABLE="$SCRIPT_DIR/build/optional-uploader-linux/dist/FTHR-Uploader" +[ -d "$UPLOADER_PAYLOAD_ELF" ] && [ -x "$UPLOADER_EXECUTABLE" ] || { + echo "ERROR: the uploader build left no executable or unpacked payload for the glibc gate." >&2 exit 1 } @@ -430,12 +431,14 @@ cp "$NATIVE_OUTPUT" "$OUTPUT" echo "" echo ">>> Verifying Linux glibc baseline..." # The optional uploader's one-file payload is compressed inside its -# .fthrplugin, so tools/build_linux_uploader.py unpacks its ELF members for -# this gate; the plugin runs on the same hosts as the AppImage. +# .fthrplugin, so check the executable (its bootloader) and the ELF members +# tools/build_linux_uploader.py unpacks from it; the plugin runs on the same +# hosts as the AppImage. if ! "$PYTHON_BIN" "$SCRIPT_DIR/tools/verify_linux_glibc.py" \ --manifest "$GLIBC_MANIFEST" \ --target "$APPDIR" \ --target "$OUTPUT" \ + --target "$UPLOADER_EXECUTABLE" \ --target "$UPLOADER_PAYLOAD_ELF"; then rm -f "$NATIVE_OUTPUT" "$OUTPUT" "$OUTPUT.sha256" echo "ERROR: Linux glibc baseline verification failed; refusing to publish the AppImage." >&2 diff --git a/tests/test_ci_contract.py b/tests/test_ci_contract.py index d851d8f9..39277bfc 100644 --- a/tests/test_ci_contract.py +++ b/tests/test_ci_contract.py @@ -36,6 +36,7 @@ def test_optional_uploader_builds_install_the_uploader_lock() -> None: for job in (windows_job, appimage_job): assert "-r requirements-uploader.txt" in job assert "build/optional-uploader-linux/payload-elf" in appimage_job + assert "build/optional-uploader-linux/dist/FTHR-Uploader" in appimage_job def test_windows_bundle_is_built_only_after_the_engine() -> None: From 03fdc3c5d448c5a766caf1f64eb8bd5d4b64a925 Mon Sep 17 00:00:00 2001 From: kalopsia-space Date: Sat, 17 Oct 2026 15:02:54 +0100 Subject: [PATCH 11/12] docs: do not claim a working Secret Service password store on Linux SecretStorage and jeepney are bundled into the Linux uploader so keyring's Secret Service backend is available, but sftp_provider still saves passwords only through Windows Credential Manager; the Linux wiring is the next UI change. Say so in the notices, docs/linux.md and the uploader lock. --- THIRD_PARTY_NOTICES.md | 6 +++++- docs/linux.md | 4 ++++ requirements-uploader.txt | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 4323d22b..a37c154f 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -228,9 +228,13 @@ its full licence text inside the plugin under | more-itertools | 11.1.0 | MIT | both | | cffi, pycparser | 2.0.0, 3.0 | MIT-0, BSD-3-Clause | both | | pywin32-ctypes | 0.2.3 | BSD-3-Clause | Windows | -| SecretStorage | 3.5.0 | BSD-3-Clause | Linux (keyring's Secret Service backend) | +| SecretStorage | 3.5.0 | BSD-3-Clause | Linux (bundled for keyring's Secret Service backend) | | jeepney | 0.9.0 | MIT | Linux (D-Bus client used by SecretStorage) | +SecretStorage and jeepney are bundled for the Linux uploader, but saving an +SFTP password through the Secret Service is not wired up on Linux yet; the +uploader currently stores saved passwords only in Windows Credential Manager. + paramiko is used unmodified as a separate library inside the plugin executable; its LGPL source is the published paramiko 4.0.0 release. diff --git a/docs/linux.md b/docs/linux.md index 9d403299..fe72c2ad 100644 --- a/docs/linux.md +++ b/docs/linux.md @@ -273,6 +273,10 @@ not automatic: enable it in Settings and complete the required consent and provider terms first. The uploader remains a separate subprocess from the main application and only receives approved clip paths through the existing boundary. +Saving an SFTP password is not available on Linux yet. The bundle already +contains keyring's Secret Service backend (SecretStorage), but the uploader +only stores passwords in Windows Credential Manager so far. + Catbox and custom-server providers do not require the separate hardware-identity component. Lustful hardware-bound account and upload flows remain unavailable on Linux until a Linux hardware-identity component is implemented and qualified. diff --git a/requirements-uploader.txt b/requirements-uploader.txt index 6405585b..50fcc05e 100644 --- a/requirements-uploader.txt +++ b/requirements-uploader.txt @@ -12,6 +12,7 @@ jaraco.context==6.1.2 jaraco.functools==4.6.0 more-itertools==11.1.0 pywin32-ctypes==0.2.3 ; sys_platform == 'win32' -# keyring's Secret Service backend on Linux (D-Bus, pure Python). +# keyring's Secret Service backend on Linux (D-Bus, pure Python). Bundled for +# the Linux uploader; saved SFTP passwords are not wired up on Linux yet. SecretStorage==3.5.0 ; sys_platform == 'linux' jeepney==0.9.0 ; sys_platform == 'linux' From 5397b6fc9a2f9f105cef55bd216a811646762295 Mon Sep 17 00:00:00 2001 From: kalopsia-space Date: Sat, 17 Oct 2026 15:10:54 +0100 Subject: [PATCH 12/12] ci: run the frozen SFTP round trip where the uploader package is built test_frozen_extension_connects_to_real_sftp activates the sealed Windows uploader package, which only the Windows engine job builds. In the python matrix it failed with "bundle is missing" as soon as paramiko was installed and the module could be collected. Skip it when the package is absent and run it in "Test built optional upload packages" after the build. --- .github/workflows/ci.yml | 6 ++++-- tests/test_sftp_provider.py | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87510081..955ea287 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -285,9 +285,11 @@ jobs: run: python tools/build_optional_uploaders.py - name: Test built optional upload packages + # The python matrix has no sealed package, so the frozen-executable + # tests (including a real SFTP round trip) run here, after the build. run: > - python -m pytest tests/test_uploader_plugin_boundary.py - -k "test_built_" --tb=short + python -m pytest tests/test_uploader_plugin_boundary.py tests/test_sftp_provider.py + -k "test_built_ or test_frozen_" --tb=short - name: Build and verify the Windows bundle shell: pwsh diff --git a/tests/test_sftp_provider.py b/tests/test_sftp_provider.py index fa40ff38..76f04746 100644 --- a/tests/test_sftp_provider.py +++ b/tests/test_sftp_provider.py @@ -392,7 +392,14 @@ def test_core_never_persists_password(tmp_path, monkeypatch): assert 'secret' not in (tmp_path / 'settings.json').read_text() +_BUILT_WINDOWS_UPLOADER = ( + Path(__file__).resolve().parents[1] / 'plugin-packages' / 'FTHR-Uploader.fthrplugin') + + @pytest.mark.skipif(sys.platform != 'win32', reason='Windows frozen uploader') +@pytest.mark.skipif( + not _BUILT_WINDOWS_UPLOADER.is_file(), + reason='build the package with tools/build_optional_uploaders.py first') def test_frozen_extension_connects_to_real_sftp(server, tmp_path, monkeypatch): from core import upload_manager from test_uploader_plugin_boundary import _CoreSettings