{
  Copyright (C)
  2026 - Uladzimir Karpenka, EW8BAK

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
}

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 <OurPID>
              caffeinate exits automatically when our process exits (crash-safe).
  Linux   : spawns systemd-inhibit in --mode=block with a shell loop that
              polls kill -0 <OurPID> 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 <PID> 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.
