Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.0.8] - 2026-05-04

### Fixed
- Single-instance protection: launching PassKey when it is already running in the system tray
now brings the existing window to the foreground instead of silently starting (and immediately
hiding) a second invisible process. This was the primary cause of the "app installs but
doesn't start" symptom reported on v1.0.7.
- Startup crash log: if the application fails to initialise before the main window is created,
a diagnostic file is written to `%LOCALAPPDATA%\PassKey\startup-crash.log`. This makes
silent startup failures diagnosable without attaching a debugger.

## [1.0.7] - 2026-05-02

### Fixed
Expand Down Expand Up @@ -117,7 +128,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Giuseppe Imperato** — concept, design, product decisions
- **[Claude](https://www.anthropic.com/claude) by Anthropic** — architecture, implementation, documentation

[Unreleased]: https://github.com/pexatar/PassKey/compare/v1.0.7...HEAD
[Unreleased]: https://github.com/pexatar/PassKey/compare/v1.0.8...HEAD
[1.0.8]: https://github.com/pexatar/PassKey/compare/v1.0.7...v1.0.8
[1.0.7]: https://github.com/pexatar/PassKey/compare/v1.0.6...v1.0.7
[1.0.6]: https://github.com/pexatar/PassKey/compare/v1.0.5...v1.0.6
[1.0.5]: https://github.com/pexatar/PassKey/compare/v1.0.0...v1.0.5
Expand Down
4 changes: 2 additions & 2 deletions Installer/PassKey.iss
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
[Setup]
AppId={{A7F3C2D1-8E4B-4F9A-B6D5-3C1E7A2F0D84}
AppName=PassKey
AppVersion=1.0.7
AppVerName=PassKey 1.0.7
AppVersion=1.0.8
AppVerName=PassKey 1.0.8
AppPublisher=Giuseppe Imperato
AppPublisherURL=https://github.com/pexatar/PassKey
AppSupportURL=https://github.com/pexatar/PassKey/issues
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ PassKey stores your passwords, credit cards, identities, and secure notes in a l

| Version | Platform | Type |
|---------|----------|------|
| [v1.0.7](https://github.com/pexatar/PassKey/releases/tag/v1.0.7) | Windows x64 | Installer EXE |
| [v1.0.7](https://github.com/pexatar/PassKey/releases/tag/v1.0.7) | Windows x64 | Portable ZIP |
| [v1.0.8](https://github.com/pexatar/PassKey/releases/tag/v1.0.8) | Windows x64 | Installer EXE |
| [v1.0.8](https://github.com/pexatar/PassKey/releases/tag/v1.0.8) | Windows x64 | Portable ZIP |

> **Requirements:** Windows 10 version 1809 (build 17763) or later, x64 processor.
> No .NET runtime required — PassKey is fully self-contained.
Expand Down
6 changes: 3 additions & 3 deletions src/PassKey.Desktop/PassKey.Desktop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<PublishAot>false</PublishAot>
<RootNamespace>PassKey.Desktop</RootNamespace>
<Version>1.0.7</Version>
<AssemblyVersion>1.0.7.0</AssemblyVersion>
<FileVersion>1.0.7.0</FileVersion>
<Version>1.0.8</Version>
<AssemblyVersion>1.0.8.0</AssemblyVersion>
<FileVersion>1.0.8.0</FileVersion>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>Assets\PassKey.ico</ApplicationIcon>
<DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
Expand Down
111 changes: 105 additions & 6 deletions src/PassKey.Desktop/Program.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,121 @@
using System.Reflection;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;

namespace PassKey.Desktop;

public static class Program
{
// Named event used for single-instance detection and window-restore signalling.
// AutoReset: each Set() wakes exactly one WaitOne() call.
private const string ShowEventName = "PassKey.Desktop.ShowWindow";

// Path to the startup crash log — written only when Application.Start() throws.
private static readonly string CrashLogPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"PassKey", "startup-crash.log");

[STAThread]
public static void Main(string[] args)
{
WinRT.ComWrappersSupport.InitializeComWrappers();
// ── Single-instance ──────────────────────────────────────────────────
// If another instance is already running, signal it to restore its window
// and exit immediately. This prevents the "app doesn't appear to start"
// symptom when PassKey is already running minimised in the system tray.

EventWaitHandle? showEvent = null;
bool isFirstInstance = true;

try
{
showEvent = new EventWaitHandle(
initialState: false,
mode: EventResetMode.AutoReset,
name: ShowEventName,
createdNew: out isFirstInstance);
}
catch
{
// Named-event creation failed (unusual system configuration) —
// treat as first instance so the app always starts.
}

if (!isFirstInstance)
{
try { showEvent?.Set(); } catch { }
showEvent?.Dispose();
return;
}

// Start a daemon thread that listens for subsequent launch attempts and
// restores the main window via the UI dispatcher whenever signalled.
if (showEvent != null)
{
new Thread(() => MonitorShowEvent(showEvent))
{
IsBackground = true,
Name = "PassKey.SingleInstance.Monitor"
}.Start();
}

// ── App startup ──────────────────────────────────────────────────────
// Note: ApplySavedLanguage() is called inside App() constructor before
// InitializeComponent(), following the official Microsoft docs pattern.
Application.Start(p =>
try
{
WinRT.ComWrappersSupport.InitializeComWrappers();

Application.Start(p =>
{
var context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
_ = new App();
});
}
catch (Exception ex)
{
// Write a crash log so users can share it for support.
// The log is only created on an actual startup failure.
WriteCrashLog(ex);
throw;
}
}

/// <summary>
/// Background loop: waits for the named event to be signalled (a subsequent
/// launch attempt), then marshals a <see cref="MainWindow.RestoreWindow"/> call
/// onto the UI thread so the window becomes visible again.
/// </summary>
private static void MonitorShowEvent(EventWaitHandle showEvent)
{
while (true)
{
try
{
showEvent.WaitOne();
App.MainWindow?.DispatcherQueue.TryEnqueue(() => App.MainWindow?.RestoreWindow());
}
catch
{
// Silently ignore — this is a non-critical background thread.
}
}
}

private static void WriteCrashLog(Exception ex)
{
try
{
var logDir = Path.GetDirectoryName(CrashLogPath)!;
Directory.CreateDirectory(logDir);
var version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "unknown";
File.AppendAllText(
CrashLogPath,
$"[{DateTime.Now:s}] PassKey v{version} startup crash:{Environment.NewLine}{ex}{Environment.NewLine}{Environment.NewLine}");
}
catch
{
var context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
_ = new App();
});
// If we can't write the log, there is nothing more we can do.
}
}
}
Loading