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
33 changes: 13 additions & 20 deletions engine/services/updates.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,34 +454,27 @@ def check_now(self):
def install_now(self) -> bool:
"""Launches the installer and returns True on success.

Uses ShellExecuteW to ensure the installer survives if the
parent application is running inside a restrictive job object
(like PyInstaller's bootloader job).
Uses CREATE_BREAKAWAY_FROM_JOB to ensure the installer survives if the
parent application is running inside a restrictive job object.
Passes the current PID so the installer can deterministically wait
for this application instance to fully close before overwriting files.
"""
if self.state != UpdateState.READY_TO_INSTALL or not self.installer_path:
return False

installer_path = str(self.installer_path)
current_pid = os.getpid()
logger.info(
f"Launching decoupled installer via ShellExecute: {installer_path} (PID: {current_pid})"
)
logger.info(f"Launching decoupled installer: {installer_path} (PID: {current_pid})")
try:
import ctypes

# SW_SHOWNORMAL = 1
# ShellExecute delegates process creation to Explorer.exe, completely
# bypassing any Job Object restrictions placed on our current process.
# This prevents the installer from being "killed halfway" when the app exits.
result = ctypes.windll.shell32.ShellExecuteW(
None, "open", installer_path, f"/SILENT /pid={current_pid}", None, 1
# Senior Architecture: Use Breakaway flag to decouple from parent job objects.
# This is the industry-standard way to ensure a child setup process
# survives parent termination in all Windows environments.
create_breakaway_from_job = 0x01000000
subprocess.Popen(
[installer_path, "/SILENT", f"/pid={current_pid}"],
creationflags=create_breakaway_from_job | subprocess.DETACHED_PROCESS,
shell=False,
)

# ShellExecuteW returns a value > 32 on success
if result <= 32:
logger.error(f"ShellExecuteW failed with error code: {result}")
return False

return True
except Exception as e:
logger.error(f"Failed to launch decoupled installer: {e}")
Expand Down
175 changes: 175 additions & 0 deletions fail.txt

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions open_prs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
13 Fix/installer dll crash fix/installer-dll-crash OPEN 2026-03-30T11:27:20Z
11 chore(deps): bump skia-python from 138.0 to 144.0.post1 dependabot/uv/skia-python-144.0.post1 OPEN 2026-03-29T18:41:04Z
32 changes: 15 additions & 17 deletions packaging/inno/parrotink.iss
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
; Inno Setup Script for ParrotInk

#define MyAppName "ParrotInk"
#define MyAppVersion "0.2.32"
#define MyAppVersion "0.2.33"
#define MyAppPublisher "Aalwattar"
#define MyAppURL "https://github.com/Aalwattar/ParrotInk"
#define MyAppExeName "ParrotInk.exe"
Expand Down Expand Up @@ -47,8 +47,7 @@ Name: "{userdesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: de
; Normal install: User sees the checkbox to launch the app
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
; Silent install (updates): Force launch automatically because the wizard pages are hidden
; The Check function introduces a small delay to prevent Windows Defender extraction crashes
Filename: "{app}\{#MyAppExeName}"; Flags: nowait; Check: ShouldDelayLaunchAndSilent
Filename: "{app}\{#MyAppExeName}"; Flags: nowait; Check: WizardSilent

[UninstallDelete]
Type: filesandordirs; Name: "{app}\assets"
Expand All @@ -59,19 +58,6 @@ const
SYNCHRONIZE = $00100000;
INFINITE = $FFFFFFFF;

function ShouldDelayLaunchAndSilent: Boolean;
begin
if WizardSilent() then
begin
Log('Delaying post-install launch for 3000ms to allow AV scanning...');
Sleep(3000);
Result := True;
end else
begin
Result := False;
end;
end;

function OpenProcess(dwAccess: DWORD; bInherit: Boolean; dwPID: DWORD): THandle;
external 'OpenProcess@kernel32.dll stdcall';
function WaitForSingleObject(hHandle: THandle; dwMilliseconds: DWORD): DWORD;
Expand Down Expand Up @@ -125,7 +111,19 @@ begin
// No PID passed (manual install) — just try to kill by name as a safety measure
Log('No PID passed. Performing name-based cleanup.');
Exec(ExpandConstant('{sys}\taskkill.exe'), '/f /im {#MyAppExeName}', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Sleep(1500);
Sleep(4000); // Increased buffer to 4000ms to avoid DLL crash during legacy (no-PID) upgrades
end;
end;

procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
if WizardSilent() then
begin
Log('Delaying post-install launch for 3000ms to allow AV scanning before [Run] phase...');
Sleep(3000);
end;
end;
end;

Expand Down
8 changes: 4 additions & 4 deletions packaging/pyinstaller/version_info.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ VSVersionInfo(
ffi=FixedFileInfo(
# filevers and prodvers should be (major, minor, micro, build)
# THe app version can be updated by the build script
filevers=(0, 2, 32, 0),
prodvers=(0, 2, 32, 0),
filevers=(0, 2, 33, 0),
prodvers=(0, 2, 33, 0),
# Contains a bitmask that specifies the valid bits 'flags' r
mask=0x3f,
# Contains a bitmask that specifies the attributes of the file.
Expand All @@ -32,12 +32,12 @@ VSVersionInfo(
u'040904B0',
[StringStruct(u'CompanyName', u'ParrotInk'),
StringStruct(u'FileDescription', u'ParrotInk Voice-To-Text'),
StringStruct(u'FileVersion', u'0.2.32'),
StringStruct(u'FileVersion', u'0.2.33'),
StringStruct(u'InternalName', u'ParrotInk'),
StringStruct(u'LegalCopyright', u'Copyright (c) 2026 ParrotInk'),
StringStruct(u'OriginalFilename', u'ParrotInk.exe'),
StringStruct(u'ProductName', u'ParrotInk'),
StringStruct(u'ProductVersion', u'0.2.32')])
StringStruct(u'ProductVersion', u'0.2.33')])
]),
VarFileInfo([VarStruct(u'Translation', [1033, 1200])])
]
Expand Down
5 changes: 5 additions & 0 deletions prs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
14 fix(installer): address Intermittent DLL crash during in-app update fix/installer-dll-race-condition MERGED 2026-03-30T11:43:31Z
12 refactor(hud): event-driven PostMessage architecture for stability (v0.2.31) fix/hud-disappearance MERGED 2026-03-29T19:18:21Z
10 chore(deps): bump cryptography from 46.0.5 to 46.0.6 dependabot/uv/cryptography-46.0.6 MERGED 2026-03-29T18:40:59Z
9 fix(updates): resolve PyInstaller DLL race condition and silent installer termination fix/installer-dll-race-condition MERGED 2026-03-17T23:35:35Z
8 feat(updates): robust windows-native update manager with BITS and graceful handoff feat/windows-native-updater MERGED 2026-03-15T22:59:24Z
30 changes: 15 additions & 15 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "parrotink"
version = "0.2.32"
version = "0.2.33"
description = "Real-time voice-to-text application for Windows"
readme = "README.md"
requires-python = ">=3.11"
Expand All @@ -21,24 +21,24 @@ keywords = [
]
dependencies = [
"sounddevice==0.5.5",
"numpy>=1.26.0",
"numpy==2.4.4",
"websockets==16.0",
"httpx==0.28.1",
"openai==2.17.0",
"assemblyai==0.50.0",
"openai==2.30.0",
"assemblyai==0.59.0",
"pystray==0.19.5",
"pynput==1.8.1",
"keyring>=25.0.0",
"pydantic-settings>=2.0.0",
"keyring==25.7.0",
"pydantic-settings==2.13.1",
"pywin32==311; sys_platform == 'win32'",
"platformdirs>=4.5.1",
"olefile>=0.46",
"platformdirs==4.9.4",
"olefile==0.47",
"soxr>=1.0.0",
"skia-python>=138.0",
"skia-python==144.0.post2",
"tomli-w>=1.2.0",
"keyboard>=0.13.5",
"win11toast",
"ttkbootstrap>=1.10.1",
"ttkbootstrap==1.20.2",
"darkdetect>=0.8.0",
"packaging>=26.0",
"tomlkit>=0.14.0",
Expand All @@ -53,15 +53,15 @@ dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.23.0",
"pytest-mock>=3.12.0",
"pytest-cov>=4.1.0",
"ruff>=0.3.0",
"pytest-cov==7.1.0",
"ruff==0.15.8",
"mypy>=1.9.0",
"types-requests>=2.31.0",
"types-requests==2.33.0.20260327",
"types-setuptools>=69.1.0",
"types-pynput>=1.8.1.20250809",
"types-pywin32>=311.0.0.20251008",
"types-pywin32==311.0.0.20260323",
"types-keyboard>=0.13.2.20250801",
"pyinstaller>=6.18.0",
"pyinstaller==6.19.0",
"pre-commit>=4.5.1",
"pytest-timeout>=2.4.0",
]
Expand Down
5 changes: 5 additions & 0 deletions runs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
completed failure chore(deps): bump cryptography from 46.0.5 to 46.0.6 (#10) Continuous Integration master push 23743021745 2m44s 2026-03-30T11:44:26Z
completed cancelled fix(installer): address Intermittent DLL crash during in-app update (… Continuous Integration master push 23742997007 52s 2026-03-30T11:43:45Z
completed success fix(installer): address Intermittent DLL crash during in-app update Continuous Integration fix/installer-dll-race-condition pull_request 23742991573 1m15s 2026-03-30T11:43:36Z
completed success Release (Windows exe) Release (Windows exe) master workflow_dispatch 23717118781 3m36s 2026-03-29T19:25:09Z
completed success refactor(hud): event-driven PostMessage architecture for stability (… Continuous Integration master push 23717008610 3m24s 2026-03-29T19:19:38Z
142 changes: 142 additions & 0 deletions updates.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
parrotink v0.2.32
├── assemblyai v0.50.0 (latest: v0.59.0)
│ ├── httpx v0.28.1
│ │ ├── anyio v4.12.1 (latest: v4.13.0)
│ │ │ └── idna v3.11
│ │ ├── certifi v2026.2.25
│ │ ├── httpcore v1.0.9
│ │ │ ├── certifi v2026.2.25
│ │ │ └── h11 v0.16.0
│ │ └── idna v3.11
│ ├── pydantic v2.12.5
│ │ ├── annotated-types v0.7.0
│ │ ├── pydantic-core v2.41.5 (latest: v2.44.0)
│ │ │ └── typing-extensions v4.15.0
│ │ ├── typing-extensions v4.15.0
│ │ └── typing-inspection v0.4.2
│ │ └── typing-extensions v4.15.0
│ ├── typing-extensions v4.15.0
│ └── websockets v16.0
├── darkdetect v0.8.0
├── httpx v0.28.1 (*)
├── keyboard v0.13.5
├── keyring v25.7.0
│ ├── jaraco-classes v3.4.0
│ │ └── more-itertools v10.8.0
│ ├── jaraco-context v6.1.0 (latest: v6.1.2)
│ ├── jaraco-functools v4.4.0
│ │ └── more-itertools v10.8.0
│ └── pywin32-ctypes v0.2.3
├── numpy v2.4.2 (latest: v2.4.4)
├── olefile v0.47
├── openai v2.17.0 (latest: v2.30.0)
│ ├── anyio v4.12.1 (*)
│ ├── distro v1.9.0
│ ├── httpx v0.28.1 (*)
│ ├── jiter v0.13.0
│ ├── pydantic v2.12.5 (*)
│ ├── sniffio v1.3.1
│ ├── tqdm v4.67.3
│ │ └── colorama v0.4.6
│ └── typing-extensions v4.15.0
├── packaging v26.0
├── platformdirs v4.9.2 (latest: v4.9.4)
├── pydantic-settings v2.13.1
│ ├── pydantic v2.12.5 (*)
│ ├── python-dotenv v1.2.2
│ └── typing-inspection v0.4.2 (*)
├── pynput v1.8.1
│ └── six v1.17.0
├── pystray v0.19.5
│ ├── pillow v12.1.1
│ └── six v1.17.0
├── pywin32 v311
├── skia-python v138.0 (latest: v144.0.post2)
│ ├── numpy v2.4.2
│ └── pybind11 v3.0.2
├── sounddevice v0.5.5
│ └── cffi v2.0.0
│ └── pycparser v3.0
├── soxr v1.0.0
│ └── numpy v2.4.2
├── tomli-w v1.2.0
├── tomlkit v0.14.0
├── ttkbootstrap v1.20.1 (latest: v1.20.2)
│ └── pillow v12.1.1
├── websockets v16.0
├── win11toast v0.36.3
│ ├── winrt-windows-data-xml-dom v3.2.1
│ │ └── winrt-runtime v3.2.1
│ │ └── typing-extensions v4.15.0
│ ├── winrt-windows-foundation v3.2.1
│ │ └── winrt-runtime v3.2.1 (*)
│ ├── winrt-windows-foundation-collections v3.2.1
│ │ └── winrt-runtime v3.2.1 (*)
│ ├── winrt-windows-globalization v3.2.1
│ │ └── winrt-runtime v3.2.1 (*)
│ ├── winrt-windows-graphics-imaging v3.2.1
│ │ └── winrt-runtime v3.2.1 (*)
│ ├── winrt-windows-media-core v3.2.1
│ │ └── winrt-runtime v3.2.1 (*)
│ ├── winrt-windows-media-ocr v3.2.1
│ │ └── winrt-runtime v3.2.1 (*)
│ ├── winrt-windows-media-playback v3.2.1
│ │ └── winrt-runtime v3.2.1 (*)
│ ├── winrt-windows-media-speechsynthesis v3.2.1
│ │ └── winrt-runtime v3.2.1 (*)
│ ├── winrt-windows-storage v3.2.1
│ │ └── winrt-runtime v3.2.1 (*)
│ ├── winrt-windows-storage-streams v3.2.1
│ │ └── winrt-runtime v3.2.1 (*)
│ └── winrt-windows-ui-notifications v3.2.1
│ └── winrt-runtime v3.2.1 (*)
├── mypy v1.19.1 (group: dev)
│ ├── librt v0.8.1
│ ├── mypy-extensions v1.1.0
│ ├── pathspec v1.0.4
│ └── typing-extensions v4.15.0
├── pre-commit v4.5.1 (group: dev)
│ ├── cfgv v3.5.0
│ ├── identify v2.6.17 (latest: v2.6.18)
│ ├── nodeenv v1.10.0
│ ├── pyyaml v6.0.3
│ └── virtualenv v21.1.0 (latest: v21.2.0)
│ ├── distlib v0.4.0
│ ├── filelock v3.25.0 (latest: v3.25.2)
│ ├── platformdirs v4.9.2
│ └── python-discovery v1.1.0 (latest: v1.2.1)
│ ├── filelock v3.25.0
│ └── platformdirs v4.9.2
├── pyinstaller v6.19.0 (group: dev)
│ ├── altgraph v0.17.5
│ ├── packaging v26.0
│ ├── pefile v2024.8.26
│ ├── pyinstaller-hooks-contrib v2026.1 (latest: v2026.3)
│ │ ├── packaging v26.0
│ │ └── setuptools v82.0.0 (latest: v82.0.1)
│ ├── pywin32-ctypes v0.2.3
│ └── setuptools v82.0.0
├── pytest v9.0.2 (group: dev)
│ ├── colorama v0.4.6
│ ├── iniconfig v2.3.0
│ ├── packaging v26.0
│ ├── pluggy v1.6.0
│ └── pygments v2.19.2 (latest: v2.20.0)
├── pytest-asyncio v1.3.0 (group: dev)
│ └── pytest v9.0.2 (*)
├── pytest-cov v7.0.0 (group: dev) (latest: v7.1.0)
│ ├── coverage[toml] v7.13.4 (latest: v7.13.5)
│ ├── pluggy v1.6.0
│ └── pytest v9.0.2 (*)
├── pytest-mock v3.15.1 (group: dev)
│ └── pytest v9.0.2 (*)
├── pytest-timeout v2.4.0 (group: dev)
│ └── pytest v9.0.2 (*)
├── ruff v0.15.1 (group: dev) (latest: v0.15.8)
├── types-keyboard v0.13.2.20250801 (group: dev)
├── types-pynput v1.8.1.20250809 (group: dev)
├── types-pywin32 v311.0.0.20251008 (group: dev) (latest: v311.0.0.20260323)
├── types-requests v2.32.4.20260107 (group: dev) (latest: v2.33.0.20260327)
│ └── urllib3 v2.6.3
└── types-setuptools v82.0.0.20260210 (group: dev)
(*) Package tree already displayed
Loading
Loading