206 lines
7.0 KiB
ObjectPascal
206 lines
7.0 KiB
ObjectPascal
unit Setup.SpawnClient;
|
|
|
|
{
|
|
Inno Setup
|
|
Copyright (C) 1997-2024 Jordan Russell
|
|
Portions by Martijn Laan
|
|
For conditions of distribution and use, see LICENSE.TXT.
|
|
|
|
Spawn client
|
|
|
|
NOTE: These functions are NOT thread-safe. Do not call them from multiple
|
|
threads simultaneously.
|
|
}
|
|
|
|
interface
|
|
|
|
uses
|
|
Windows, SysUtils, Messages, Setup.InstFunc, Shared.CommonFunc;
|
|
|
|
procedure InitializeSpawnClient(const AServerWnd: HWND);
|
|
function InstExecEx(const RunAsOriginalUser: Boolean;
|
|
const DisableFsRedir: Boolean; const Filename, Params, WorkingDir: String;
|
|
const Wait: TExecWait; const ShowCmd: Integer;
|
|
const ProcessMessagesProc: TProcedure; const OutputReader: TCreateProcessOutputReader;
|
|
var ResultCode: Integer): Boolean;
|
|
function InstShellExecEx(const RunAsOriginalUser: Boolean;
|
|
const Verb, Filename, Params, WorkingDir: String;
|
|
const Wait: TExecWait; const ShowCmd: Integer;
|
|
const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
|
|
|
|
implementation
|
|
|
|
uses
|
|
Classes, Setup.SpawnCommon;
|
|
|
|
var
|
|
SpawnServerPresent: Boolean;
|
|
SpawnServerWnd: HWND;
|
|
|
|
procedure WriteLongintToStream(const M: TMemoryStream; const Value: Longint);
|
|
begin
|
|
M.WriteBuffer(Value, SizeOf(Value));
|
|
end;
|
|
|
|
procedure WriteStringToStream(const M: TMemoryStream; const Value: String);
|
|
var
|
|
Len: Integer;
|
|
begin
|
|
Len := Length(Value);
|
|
if Len > $FFFF then
|
|
InternalError('WriteStringToStream: Length limit exceeded');
|
|
WriteLongintToStream(M, Len);
|
|
M.WriteBuffer(Value[1], Len * SizeOf(Value[1]));
|
|
end;
|
|
|
|
procedure AllowSpawnServerToSetForegroundWindow;
|
|
{ This is called to allow processes started by the spawn server process to
|
|
come to the foreground, above the current process's windows. The effect
|
|
normally lasts until new input is generated (a keystroke or click, not
|
|
simply mouse movement).
|
|
Note: If the spawn server process has no visible windows, it seems this
|
|
isn't needed; the process can set the foreground window as it pleases.
|
|
If it does have a visible window, though, it definitely is needed (e.g. in
|
|
the /DebugSpawnServer case). Let's not rely on any undocumented behavior and
|
|
call AllowSetForegroundWindow unconditionally. }
|
|
var
|
|
PID: DWORD;
|
|
AllowSetForegroundWindowFunc: function(dwProcessId: DWORD): BOOL; stdcall;
|
|
begin
|
|
if GetWindowThreadProcessId(SpawnServerWnd, @PID) <> 0 then begin
|
|
AllowSetForegroundWindowFunc := GetProcAddress(GetModuleHandle(user32),
|
|
'AllowSetForegroundWindow');
|
|
if Assigned(AllowSetForegroundWindowFunc) then
|
|
AllowSetForegroundWindowFunc(PID);
|
|
end;
|
|
end;
|
|
|
|
function QuerySpawnServer(const SequenceNumber: Word;
|
|
const Operation: Integer): Word;
|
|
var
|
|
MsgResult: LRESULT;
|
|
begin
|
|
MsgResult := SendMessage(SpawnServerWnd, WM_SpawnServer_Query, Operation,
|
|
SequenceNumber);
|
|
if MsgResult and not $FFFF <> SPAWN_MSGRESULT_SUCCESS_BITS then
|
|
InternalErrorFmt('QuerySpawnServer: Unexpected response: $%x', [MsgResult]);
|
|
Result := Word(MsgResult);
|
|
end;
|
|
|
|
function CallSpawnServer(const CopyDataMsg: DWORD; var M: TMemoryStream;
|
|
const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
|
|
var
|
|
CopyDataStruct: TCopyDataStruct;
|
|
MsgResult: LRESULT;
|
|
SequenceNumber: Word;
|
|
Status: Word;
|
|
LastQueryTime, NowTime: DWORD;
|
|
begin
|
|
CopyDataStruct.dwData := CopyDataMsg;
|
|
CopyDataStruct.cbData := M.Size;
|
|
CopyDataStruct.lpData := M.Memory;
|
|
AllowSpawnServerToSetForegroundWindow;
|
|
MsgResult := SendMessage(SpawnServerWnd, WM_COPYDATA, 0, LPARAM(@CopyDataStruct));
|
|
FreeAndNil(M); { it isn't needed anymore, might as well free now }
|
|
if MsgResult = SPAWN_MSGRESULT_OUT_OF_MEMORY then
|
|
OutOfMemoryError;
|
|
if MsgResult and not $FFFF <> SPAWN_MSGRESULT_SUCCESS_BITS then
|
|
InternalErrorFmt('CallSpawnServer: Unexpected response: $%x', [MsgResult]);
|
|
SequenceNumber := Word(MsgResult);
|
|
|
|
LastQueryTime := GetTickCount;
|
|
repeat
|
|
ProcessMessagesProc;
|
|
{ Now that the queue is empty (we mustn't break without first processing
|
|
messages found by a previous MsgWaitForMultipleObjects call), see if
|
|
the status changed, but only if at least 10 ms has elapsed since the
|
|
last query }
|
|
NowTime := GetTickCount;
|
|
if Cardinal(NowTime - LastQueryTime) >= Cardinal(10) then begin
|
|
LastQueryTime := NowTime;
|
|
Status := QuerySpawnServer(SequenceNumber, SPAWN_QUERY_STATUS);
|
|
case Status of
|
|
SPAWN_STATUS_RUNNING: ;
|
|
SPAWN_STATUS_RETURNED_TRUE, SPAWN_STATUS_RETURNED_FALSE: Break;
|
|
else
|
|
InternalErrorFmt('CallSpawnServer: Unexpected status: %d', [Status]);
|
|
end;
|
|
end;
|
|
{ Delay for 10 ms, or until a message arrives }
|
|
MsgWaitForMultipleObjects(0, THandle(nil^), False, 10, QS_ALLINPUT);
|
|
until False;
|
|
|
|
ResultCode := QuerySpawnServer(SequenceNumber, SPAWN_QUERY_RESULTCODE_LO) or
|
|
(QuerySpawnServer(SequenceNumber, SPAWN_QUERY_RESULTCODE_HI) shl 16);
|
|
Result := (Status = SPAWN_STATUS_RETURNED_TRUE);
|
|
end;
|
|
|
|
function InstExecEx(const RunAsOriginalUser: Boolean;
|
|
const DisableFsRedir: Boolean; const Filename, Params, WorkingDir: String;
|
|
const Wait: TExecWait; const ShowCmd: Integer;
|
|
const ProcessMessagesProc: TProcedure; const OutputReader: TCreateProcessOutputReader;
|
|
var ResultCode: Integer): Boolean;
|
|
var
|
|
M: TMemoryStream;
|
|
begin
|
|
if not RunAsOriginalUser or not SpawnServerPresent then begin
|
|
Result := InstExec(DisableFsRedir, Filename, Params, WorkingDir,
|
|
Wait, ShowCmd, ProcessMessagesProc, OutputReader, ResultCode);
|
|
Exit;
|
|
end;
|
|
|
|
M := TMemoryStream.Create;
|
|
try
|
|
WriteLongintToStream(M, Ord(DisableFsRedir));
|
|
WriteStringToStream(M, Filename);
|
|
WriteStringToStream(M, Params);
|
|
WriteStringToStream(M, WorkingDir);
|
|
WriteLongintToStream(M, Ord(Wait));
|
|
WriteLongintToStream(M, ShowCmd);
|
|
WriteStringToStream(M, GetCurrentDir);
|
|
|
|
Result := CallSpawnServer(CD_SpawnServer_Exec, M, ProcessMessagesProc,
|
|
ResultCode);
|
|
finally
|
|
M.Free;
|
|
end;
|
|
end;
|
|
|
|
function InstShellExecEx(const RunAsOriginalUser: Boolean;
|
|
const Verb, Filename, Params, WorkingDir: String;
|
|
const Wait: TExecWait; const ShowCmd: Integer;
|
|
const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
|
|
var
|
|
M: TMemoryStream;
|
|
begin
|
|
if not RunAsOriginalUser or not SpawnServerPresent then begin
|
|
Result := InstShellExec(Verb, Filename, Params, WorkingDir,
|
|
Wait, ShowCmd, ProcessMessagesProc, ResultCode);
|
|
Exit;
|
|
end;
|
|
|
|
M := TMemoryStream.Create;
|
|
try
|
|
WriteStringToStream(M, Verb);
|
|
WriteStringToStream(M, Filename);
|
|
WriteStringToStream(M, Params);
|
|
WriteStringToStream(M, WorkingDir);
|
|
WriteLongintToStream(M, Ord(Wait));
|
|
WriteLongintToStream(M, ShowCmd);
|
|
WriteStringToStream(M, GetCurrentDir);
|
|
|
|
Result := CallSpawnServer(CD_SpawnServer_ShellExec, M, ProcessMessagesProc,
|
|
ResultCode);
|
|
finally
|
|
M.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure InitializeSpawnClient(const AServerWnd: HWND);
|
|
begin
|
|
SpawnServerWnd := AServerWnd;
|
|
SpawnServerPresent := True;
|
|
end;
|
|
|
|
end.
|