diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000..6db4b8b --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,117 @@ +# Captail Privacy Policy + +Effective date: August 3, 2026 + +This policy applies to the Captail desktop application and its official +Microsoft Store and GitHub distributions. + +## Summary + +Captail records instant replays locally on your Windows PC. Captail does not +sell personal information, include advertising or analytics, automatically +upload recordings, or provide the developer with access to your captures. + +## Information processed locally + +Depending on the features you enable, Captail may process: + +- desktop, application, or game video frames; +- desktop or game audio; +- microphone audio; +- monitor, audio-device, GPU, codec, and game-process information needed to + configure capture; +- replay files and their technical metadata; +- settings such as hotkeys, selected devices, and output folders. + +This information is processed on your device to maintain the replay buffer, +save clips, organize recordings, show previews, and trim saved videos. Captail +does not transmit captured video or audio to the developer or to a Captail +cloud service. + +Captail does not attempt to bypass DRM or protected-media restrictions. +Protected content may appear black or unavailable in recordings. + +## Local storage + +Saved replays are written to the folder you select. The rolling replay buffer +is held locally and only becomes a normal video file when you save a replay. + +Captail stores configuration and diagnostic logs under its local application +data. The logical log location is: + +```text +%LOCALAPPDATA%\Captail\log.txt +``` + +Windows may map Store-package application data to a package-specific location. +Logs can contain technical errors, device and GPU names, codec settings, game +or process names, and local file paths. Logs do not intentionally contain raw +video frames or recorded audio. Logs are not uploaded automatically. Review +them and remove sensitive paths or names before attaching them to a public bug +report. + +## Network access + +Captail has no telemetry, analytics, advertising, account, or cloud-storage +service. + +The Microsoft Store build relies on Microsoft Store for application updates +and does not check GitHub Releases for updates. Microsoft Store may process +installation, licensing, update, and diagnostic information under Microsoft's +own privacy terms. + +Installer and Portable builds distributed through GitHub may contact the +GitHub Releases API to check for and download updates. Those requests identify +the Captail version and necessarily expose network information such as the IP +address to GitHub. GitHub processes that information under the +[GitHub Privacy Statement](https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement). + +Opening a GitHub, support, or repository link from Captail launches your +default web browser. The destination website then applies its own privacy +policy. + +## Permissions + +Captail uses only permissions needed for its recording workflow, including: + +- screen and game capture; +- system-audio and optional microphone capture; +- global replay hotkeys; +- access to folders selected for replay storage; +- hardware encoder detection; +- optional startup with Windows. + +Microphone recording and startup with Windows can be disabled in Captail's +settings. Capture can be stopped at any time by turning Instant Replay off or +exiting Captail. + +## Sharing and disclosure + +Captail does not sell or share local recordings, settings, or diagnostic logs. +Information leaves your device only when you choose to share a replay or log, +or when a non-Store build performs the GitHub update request described above. + +## Retention and deletion + +You control saved replay retention. Replays can be deleted from Captail or +Windows File Explorer. Configuration and logs remain locally until you delete +them or Windows removes the application's data. Uninstalling Captail does not +guarantee removal of replay files stored in your chosen output folder. + +## Children's privacy + +Captail is a general-purpose recording utility and is not directed to children. +The developer does not knowingly collect personal information from children. + +## Changes to this policy + +Material changes will be published in this file and recorded in the +repository's Git history. The effective date above will be updated when the +policy changes. + +## Contact + +For privacy questions, open an issue in the +[Captail repository](https://github.com/FaulMit/captail/issues). Do not include +recordings, logs, local paths, or other sensitive information in a public +issue. diff --git a/README.md b/README.md index 00064e4..6454e56 100644 --- a/README.md +++ b/README.md @@ -266,6 +266,8 @@ Read [CONTRIBUTING.md](CONTRIBUTING.md) before submitting changes. Release maint Captail is licensed under [GNU GPL-2.0-or-later](LICENSE). +Privacy details are documented in [PRIVACY.md](PRIVACY.md). + Captail uses libobs and selected OBS Studio runtime components. See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). Captail is not affiliated with or endorsed by NVIDIA or OBS Project. NVIDIA and ShadowPlay are trademarks of NVIDIA Corporation. diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 3d7b561..1361a4e 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -76,3 +76,18 @@ Output is written to `artifacts\release\0.1.1`. - Major: breaking change (`0.x` → `1.0.0`) Never replace published binaries under an existing tag. Publish a new version. + +## Microsoft Store release + +Microsoft Store is a separate package channel. It must not be attached to a +GitHub Release because Store installs, signs, and updates the package. + +Build an upload-ready package locally: + +```powershell +.\tools\BuildStorePackage.ps1 -Version 0.1.5 +``` + +Upload `artifacts\store\0.1.5\Captail-0.1.5.0-x64.msixupload` in Partner +Center. Full identity, validation, update-channel, and submission instructions +are in [`packaging/msix/README.md`](../packaging/msix/README.md). diff --git a/packaging/msix/AppxManifest.xml.template b/packaging/msix/AppxManifest.xml.template new file mode 100644 index 0000000..386dfec --- /dev/null +++ b/packaging/msix/AppxManifest.xml.template @@ -0,0 +1,61 @@ + + + + + Captail + faulmit + Lightweight instant replay recorder for Windows. + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/msix/README.md b/packaging/msix/README.md new file mode 100644 index 0000000..d7b80b1 --- /dev/null +++ b/packaging/msix/README.md @@ -0,0 +1,64 @@ +# Microsoft Store package + +This package channel is separate from Captail's GitHub Installer and Portable +releases. Microsoft Store installs and updates this build. Captail's GitHub +self-updater is disabled at compile time and guarded again at runtime whenever +Windows reports package identity. + +## Partner Center identity + +- Package name: `faulmit.Captail` +- Publisher: `CN=1BD9448E-C83F-401D-B530-ED5258C6319A` +- Publisher display name: `faulmit` +- Package family name: `faulmit.Captail_ryepxmzt2jqew` +- Store product ID: `9PKVNVLKPTPS` + +Do not change package name or publisher. Partner Center rejects packages whose +manifest identity does not match the reserved product. + +## Build + +From repository root: + +```powershell +.\tools\BuildStorePackage.ps1 -Version 0.1.5 +``` + +Output: + +```text +artifacts\store\0.1.5\Captail-0.1.5.0-x64.msix +artifacts\store\0.1.5\Captail-0.1.5.0-x64.msixupload +artifacts\store\0.1.5\SHA256SUMS.txt +``` + +Upload `.msixupload` on Partner Center's **Packages** page. Do not publish this +artifact as a GitHub Release. Microsoft Store signs the accepted package for +distribution. + +Generated package is intentionally unsigned. It is ready for Partner Center, +but Windows will not allow direct sideloading until a trusted test certificate +is applied. Store customers receive Microsoft's signed package after +certification. + +## Versioning + +MSIX requires four numeric components. Captail `0.1.5` becomes package version +`0.1.5.0`. Every Store submission must use a strictly higher package version. +Never reuse a version already submitted to Partner Center. + +## Store-specific behavior + +- Updates are delivered only by Microsoft Store. +- Footer shows `Microsoft Store` instead of an update action. +- Startup uses the package `windows.startupTask` extension. +- Installer and Portable builds continue using GitHub Releases and HKCU Run. + +## Before submission + +1. Build package from clean `main` commit. +2. Check `SHA256SUMS.txt` and inspect generated manifest identity. +3. Test install, first launch, tray, capture, replay save, microphone permission, + startup toggle, uninstall, and Store update from previous package version. +4. Complete Partner Center privacy, age rating, properties, screenshots, and + certification notes. diff --git a/src/Captail/App.xaml.cs b/src/Captail/App.xaml.cs index 3667124..e2d18ca 100644 --- a/src/Captail/App.xaml.cs +++ b/src/Captail/App.xaml.cs @@ -107,8 +107,9 @@ protected override async void OnStartup(StartupEventArgs e) const bool previewGeometryTest = false; #endif bool backgroundLaunch = e.Args.Contains( - "--background", - StringComparer.OrdinalIgnoreCase); + "--background", + StringComparer.OrdinalIgnoreCase) || + AppDistribution.IsStartupTaskActivation(); bool shutdownExisting = e.Args.Contains( "--shutdown-existing", StringComparer.OrdinalIgnoreCase); @@ -136,7 +137,7 @@ protected override async void OnStartup(StartupEventArgs e) { try { - Autostart.SetEnabled(true); + await Autostart.SetEnabledAsync(true); } catch (Exception exception) { @@ -1307,6 +1308,9 @@ private void OpenSettings() bool force, CancellationToken cancellationToken) { + if (AppDistribution.IsMicrosoftStore) + return Task.FromResult(null); + #if DEBUG if (_qaUpdateAvailable) { @@ -1568,7 +1572,7 @@ private async Task ApplySettingsAsync( await _pipelineGate.WaitAsync(); Config previous = _config!.Clone(); - bool previousAutostart = Autostart.IsEnabled(); + bool previousAutostart = await Autostart.IsEnabledAsync(); bool wasRunning = IsReplayRunning; bool pipelineChanged = !previous.PipelineEquals(candidate); bool pipelineTouched = false; @@ -1595,7 +1599,7 @@ private async Task ApplySettingsAsync( Localization.Text("L.Engine.BufferStartFailed")); } - Autostart.SetEnabled(autostartEnabled); + await Autostart.SetEnabledAsync(autostartEnabled); _config.Save(); UpdateUiState(); @@ -1628,7 +1632,7 @@ private async Task ApplySettingsAsync( } try { - Autostart.SetEnabled(previousAutostart); + await Autostart.SetEnabledAsync(previousAutostart); } catch (Exception rollbackException) { diff --git a/src/Captail/AppDistribution.cs b/src/Captail/AppDistribution.cs new file mode 100644 index 0000000..ead6b54 --- /dev/null +++ b/src/Captail/AppDistribution.cs @@ -0,0 +1,47 @@ +using System.Runtime.InteropServices; +using System.Text; +using Windows.ApplicationModel.Activation; + +namespace Captail; + +internal static class AppDistribution +{ + internal const string StoreProductId = "9PKVNVLKPTPS"; + +#if MICROSOFT_STORE + internal static bool IsMicrosoftStore { get; } = true; +#else + internal static bool IsMicrosoftStore { get; } = HasPackageIdentity(); +#endif + + internal static bool IsStartupTaskActivation() + { + if (!IsMicrosoftStore) + return false; + + try + { + return Windows.ApplicationModel.AppInstance + .GetActivatedEventArgs()?.Kind == ActivationKind.StartupTask; + } + catch (Exception exception) + { + Log.Write($"Could not read package activation: {exception.Message}"); + return false; + } + } + + private static bool HasPackageIdentity() + { + int length = 0; + return GetCurrentPackageFullName(ref length, null) == + ErrorInsufficientBuffer; + } + + private const int ErrorInsufficientBuffer = 122; + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern int GetCurrentPackageFullName( + ref int packageFullNameLength, + StringBuilder? packageFullName); +} diff --git a/src/Captail/Autostart.cs b/src/Captail/Autostart.cs index 4123c53..ea7ee26 100644 --- a/src/Captail/Autostart.cs +++ b/src/Captail/Autostart.cs @@ -1,15 +1,25 @@ using Microsoft.Win32; +using Windows.ApplicationModel; namespace Captail; -/// Per-user startup through HKCU\...\Run; administrator rights are not required. +/// Per-user startup through MSIX StartupTask or HKCU Run. public static class Autostart { private const string RunKey = @"Software\Microsoft\Windows\CurrentVersion\Run"; private const string ValueName = "Captail"; + private const string StoreTaskId = "CaptailStartup"; - public static bool IsEnabled() + public static async Task IsEnabledAsync() { + if (AppDistribution.IsMicrosoftStore) + { + StartupTask task = await StartupTask.GetAsync(StoreTaskId); + return task.State is + StartupTaskState.Enabled or + StartupTaskState.EnabledByPolicy; + } + using var key = Registry.CurrentUser.OpenSubKey(RunKey); return string.Equals( ReadCommand(key), @@ -19,12 +29,44 @@ public static bool IsEnabled() internal static bool HasEntry() { + if (AppDistribution.IsMicrosoftStore) + return false; + using var key = Registry.CurrentUser.OpenSubKey(RunKey); return !string.IsNullOrWhiteSpace(ReadCommand(key)); } - public static void SetEnabled(bool enabled) + public static async Task SetEnabledAsync(bool enabled) { + if (AppDistribution.IsMicrosoftStore) + { + StartupTask task = await StartupTask.GetAsync(StoreTaskId); + if (!enabled) + { + if (task.State is + StartupTaskState.Enabled or + StartupTaskState.EnabledByPolicy) + { + task.Disable(); + } + return; + } + + StartupTaskState state = task.State is + StartupTaskState.Enabled or + StartupTaskState.EnabledByPolicy + ? task.State + : await task.RequestEnableAsync(); + if (state is not + (StartupTaskState.Enabled or + StartupTaskState.EnabledByPolicy)) + { + throw new InvalidOperationException( + Localization.Text("L.Error.AutostartDenied")); + } + return; + } + using var key = Registry.CurrentUser.CreateSubKey(RunKey); if (enabled) { diff --git a/src/Captail/Captail.csproj b/src/Captail/Captail.csproj index 111a60e..45f84fd 100644 --- a/src/Captail/Captail.csproj +++ b/src/Captail/Captail.csproj @@ -16,6 +16,7 @@ Assets\Captail.ico 0.1.5 true + $(DefineConstants);MICROSOFT_STORE diff --git a/src/Captail/Languages/Strings.en.xaml b/src/Captail/Languages/Strings.en.xaml index b45913d..7fca013 100644 --- a/src/Captail/Languages/Strings.en.xaml +++ b/src/Captail/Languages/Strings.en.xaml @@ -24,6 +24,8 @@ Could not check for updates · click to retry Update check failed Update failed + v{0} · Microsoft Store + Updates are delivered by Microsoft Store Instant Replay is on Instant Replay is off @@ -195,6 +197,7 @@ Codec unavailable Choose a codec supported by current GPU and driver. Could not update startup + Windows did not allow Captail to start automatically. Check Startup apps in Windows Settings. Audio source unavailable Change cancelled. Previous audio configuration restored. Could not change source diff --git a/src/Captail/Languages/Strings.ru.xaml b/src/Captail/Languages/Strings.ru.xaml index 975dc29..d736894 100644 --- a/src/Captail/Languages/Strings.ru.xaml +++ b/src/Captail/Languages/Strings.ru.xaml @@ -24,6 +24,8 @@ Не удалось проверить обновления · нажмите, чтобы повторить Ошибка проверки обновлений Ошибка обновления + v{0} · Microsoft Store + Обновления устанавливает Microsoft Store Мгновенный повтор включён Мгновенный повтор выключен @@ -195,6 +197,7 @@ Кодек недоступен Выберите кодек, который поддерживается текущим GPU и драйвером. Не удалось изменить автозапуск + Windows не разрешила автозапуск Captail. Проверьте раздел «Автозагрузка» в параметрах Windows. Источник звука недоступен Настройка отменена. Предыдущая конфигурация восстановлена. Не удалось изменить источник diff --git a/src/Captail/SettingsWindow.xaml b/src/Captail/SettingsWindow.xaml index 6fab3b9..f83b170 100644 --- a/src/Captail/SettingsWindow.xaml +++ b/src/Captail/SettingsWindow.xaml @@ -855,6 +855,7 @@