From 83ec28f99772b53bd81011f034ebe0f82015604b Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Sun, 31 May 2026 21:33:30 +0300 Subject: [PATCH] Prevent OS sleep and display blanking while SDR is running Add PowerInhibit.pas with InhibitSleep/UninhibitSleep: - Windows: SetThreadExecutionState (kernel32, no child process) - macOS: caffeinate -d -i -s -w (auto-exits on crash via -w flag) - Linux: systemd-inhibit with kill-0 poll loop (releases within 5s on crash) Co-Authored-By: Claude Sonnet 4.6 --- MainForm.pas | 8 +++- PowerInhibit.pas | 109 +++++++++++++++++++++++++++++++++++++++++++++++ ewsdr.lpi | 4 ++ 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 PowerInhibit.pas diff --git a/MainForm.pas b/MainForm.pas index 299897f..bbfb1f1 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -39,7 +39,8 @@ uses WinFirewall, BoardUtils, WisdomBuilder, UISync, FlatEdit, FMRepeater, - ChannelStore, ChannelsForm; + ChannelStore, ChannelsForm, + PowerInhibit; const CLR_BG = TColor($00101010); @@ -1464,6 +1465,7 @@ begin FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), 0, False, True, True); FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0); FRunning := False; + UninhibitSleep; FTransmitting := False; if FWDSPReady then FDSPEngine.SetTXRun(False); end; @@ -3046,6 +3048,7 @@ begin BtnStartStop.Caption := 'START'; StyleButton(BtnStartStop, False); FRunning := False; + UninhibitSleep; FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0); SetRadioOfflineStatus('Hardware timeout'); BtnMOX.Enabled := False; @@ -3057,6 +3060,7 @@ begin BtnStartStop.Caption := 'START'; StyleButton(BtnStartStop, False); FRunning := False; + UninhibitSleep; FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0); SetRadioOfflineStatus('Connection lost'); BtnMOX.Enabled := False; @@ -3897,6 +3901,7 @@ begin FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), 0, False, True, True); FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0); FRunning := False; + UninhibitSleep; FTransmitting := False; if FWDSPReady then FDSPEngine.SetTXRun(False); StyleButton(BtnMOX, False); @@ -4220,6 +4225,7 @@ begin FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, False, True, True); FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel); FRunning := True; + InhibitSleep; FRXStartTime := GetTickCount64; FRXLastPktTime := 0; FRXPacketCount := 0; diff --git a/PowerInhibit.pas b/PowerInhibit.pas new file mode 100644 index 0000000..b4361d7 --- /dev/null +++ b/PowerInhibit.pas @@ -0,0 +1,109 @@ +unit PowerInhibit; + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{ + Prevents OS idle sleep and display blanking while the SDR is running. + + Windows : SetThreadExecutionState (kernel32) — one call, no child process. + macOS : spawns /usr/bin/caffeinate -d -i -s -w + caffeinate exits automatically when our process exits (crash-safe). + Linux : spawns systemd-inhibit in --mode=block with a shell loop that + polls kill -0 so the inhibitor is released within + ~5 s even on a crash (no orphan leak). +} + +interface + +procedure InhibitSleep; +procedure UninhibitSleep; + +implementation + +uses + SysUtils, Process + {$IFDEF WINDOWS}, Windows{$ENDIF}; + +{$IFDEF WINDOWS} +type + EXECUTION_STATE = DWORD; +const + ES_CONTINUOUS = DWORD($80000000); + ES_SYSTEM_REQUIRED = DWORD($00000001); + ES_DISPLAY_REQUIRED = DWORD($00000002); +function SetThreadExecutionState(esFlags: EXECUTION_STATE): EXECUTION_STATE; stdcall; + external 'kernel32.dll' name 'SetThreadExecutionState'; +{$ENDIF} + +var + FInhibitProc: TProcess = nil; + +procedure InhibitSleep; +{$IF DEFINED(DARWIN) OR DEFINED(LINUX)} +var + PidStr: string; +{$ENDIF} +begin +{$IFDEF WINDOWS} + SetThreadExecutionState(ES_CONTINUOUS or ES_SYSTEM_REQUIRED or ES_DISPLAY_REQUIRED); +{$ENDIF} + +{$IF DEFINED(DARWIN) OR DEFINED(LINUX)} + if FInhibitProc <> nil then Exit; + PidStr := IntToStr(GetProcessID); + FInhibitProc := TProcess.Create(nil); + try +{$IFDEF DARWIN} + FInhibitProc.Executable := '/usr/bin/caffeinate'; + FInhibitProc.Parameters.Add('-d'); // prevent display sleep + FInhibitProc.Parameters.Add('-i'); // prevent idle sleep + FInhibitProc.Parameters.Add('-s'); // prevent system sleep (AC power) + FInhibitProc.Parameters.Add('-w'); // watch our PID — caffeinate auto-exits with us + FInhibitProc.Parameters.Add(PidStr); +{$ENDIF} +{$IFDEF LINUX} + // systemd-inhibit holds the lock until the sh loop exits. + // The loop polls kill -0 every 5 s, so if we crash the + // inhibitor is released within 5 s (no orphan process leak). + FInhibitProc.Executable := 'systemd-inhibit'; + FInhibitProc.Parameters.Add('--what=idle:sleep:handle-lid-switch'); + FInhibitProc.Parameters.Add('--why=SDR running'); + FInhibitProc.Parameters.Add('--who=ewsdr'); + FInhibitProc.Parameters.Add('--mode=block'); + FInhibitProc.Parameters.Add('sh'); + FInhibitProc.Parameters.Add('-c'); + FInhibitProc.Parameters.Add('while kill -0 ' + PidStr + ' 2>/dev/null; do sleep 5; done'); +{$ENDIF} + FInhibitProc.Options := [poNoConsole]; + FInhibitProc.Execute; + except + FreeAndNil(FInhibitProc); + end; +{$ENDIF} +end; + +procedure UninhibitSleep; +begin +{$IFDEF WINDOWS} + SetThreadExecutionState(ES_CONTINUOUS); +{$ENDIF} + +{$IF DEFINED(DARWIN) OR DEFINED(LINUX)} + if FInhibitProc <> nil then + begin + if FInhibitProc.Running then + FInhibitProc.Terminate(0); + FreeAndNil(FInhibitProc); + end; +{$ENDIF} +end; + +initialization + FInhibitProc := nil; + +finalization + UninhibitSleep; + +end. diff --git a/ewsdr.lpi b/ewsdr.lpi index 7379264..0dcfe24 100644 --- a/ewsdr.lpi +++ b/ewsdr.lpi @@ -229,6 +229,10 @@ + + + +