33e4b4b614
- PM.AutoStart wraps HKCU\Software\Microsoft\Windows\CurrentVersion\Run.
Value "PMServer" = "<exe>" -tray. Per-user, no admin required, shows
up in Task Manager → Startup so the user can override from there.
- UMainForm honours the -tray CLI flag (set by the registry entry):
after the server starts, MinimizeToTray via TThread.ForceQueue so the
app comes up directly in the tray with no visible window flash.
- Bridge cmd://autostart/{get,set} + Bridge.getAutoStart() /
setAutoStart() / onAutoStartStatus(). Settings exposes a toggle in
the Security section, visible only when Bridge.active (the PHP
frontend can't touch the registry).
- Toggle reads "on" only when the registered command matches the
current exe path, so a stale entry from a moved exe lets the user
re-enable to refresh.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
99 lines
2.6 KiB
ObjectPascal
99 lines
2.6 KiB
ObjectPascal
unit PM.AutoStart;
|
|
|
|
{
|
|
Start-with-Windows toggle.
|
|
|
|
Mechanism: HKCU\Software\Microsoft\Windows\CurrentVersion\Run
|
|
- Per-user (no admin needed)
|
|
- Visible in Task Manager → Startup tab (user can disable from there)
|
|
- Standard pattern used by 1Password, Bitwarden, Slack, Discord, etc.
|
|
|
|
Command line: launched with --tray so PMServer starts minimized to the
|
|
tray instead of popping a window during Windows login.
|
|
|
|
Value name "PMServer" — fixed string, not exe path-derived, so moving
|
|
the exe + flipping the toggle off/on cleans up the old entry.
|
|
}
|
|
|
|
interface
|
|
|
|
function IsAutoStartEnabled: Boolean;
|
|
|
|
// Returns True on success. Failures (registry locked, etc.) are silent —
|
|
// caller surfaces the resulting status via IsAutoStartEnabled.
|
|
function SetAutoStart(AEnabled: Boolean): Boolean;
|
|
|
|
implementation
|
|
|
|
uses
|
|
System.SysUtils, System.Win.Registry, Winapi.Windows;
|
|
|
|
const
|
|
RUN_KEY = 'Software\Microsoft\Windows\CurrentVersion\Run';
|
|
VALUE_NAME = 'PMServer';
|
|
TRAY_ARG = '-tray'; // single dash so Delphi's FindCmdLineSwitch picks it up
|
|
|
|
function BuildAutoStartCommand: string;
|
|
begin
|
|
// Quote the exe path (may contain spaces) and append the silent-start
|
|
// arg. Single string written to a REG_SZ value — Windows splits args
|
|
// the same way CommandLineToArgvW does.
|
|
Result := '"' + ParamStr(0) + '" ' + TRAY_ARG;
|
|
end;
|
|
|
|
function IsAutoStartEnabled: Boolean;
|
|
var
|
|
R: TRegistry;
|
|
LCurrent: string;
|
|
begin
|
|
Result := False;
|
|
R := TRegistry.Create(KEY_READ);
|
|
try
|
|
R.RootKey := HKEY_CURRENT_USER;
|
|
if not R.OpenKeyReadOnly(RUN_KEY) then Exit;
|
|
try
|
|
if not R.ValueExists(VALUE_NAME) then Exit;
|
|
LCurrent := R.ReadString(VALUE_NAME);
|
|
// Consider the toggle "on" only if the registered command points
|
|
// to OUR exe — a stale entry from a moved exe should read as off
|
|
// so the user can re-enable to refresh the path.
|
|
Result := SameText(LCurrent, BuildAutoStartCommand);
|
|
finally
|
|
R.CloseKey;
|
|
end;
|
|
finally
|
|
R.Free;
|
|
end;
|
|
end;
|
|
|
|
function SetAutoStart(AEnabled: Boolean): Boolean;
|
|
var
|
|
R: TRegistry;
|
|
begin
|
|
Result := False;
|
|
R := TRegistry.Create(KEY_READ or KEY_WRITE);
|
|
try
|
|
R.RootKey := HKEY_CURRENT_USER;
|
|
if not R.OpenKey(RUN_KEY, True) then Exit;
|
|
try
|
|
if AEnabled then
|
|
begin
|
|
R.WriteString(VALUE_NAME, BuildAutoStartCommand);
|
|
Result := True;
|
|
end
|
|
else
|
|
begin
|
|
if R.ValueExists(VALUE_NAME) then
|
|
R.DeleteValue(VALUE_NAME);
|
|
Result := True;
|
|
end;
|
|
finally
|
|
R.CloseKey;
|
|
end;
|
|
finally
|
|
R.Free;
|
|
end;
|
|
end;
|
|
|
|
end.
|