control/Installer.iss
Rama main · réplica de lectura del repositorio original.
; =====================================================
; ArtLightControl - GitHub Release Installer
; WinUI 3 (Windows App SDK 2.4) unpackaged deployment
; =====================================================
#define MyAppName "ArtLight Control"
#define MyAppVersion "1.6.1"
#define MyAppPublisher "onaiaku"
#define MyAppExeName "ArtLightControl.exe"
#define MyAppURL "https://github.com/onaiaku/ArtLight"
#define ServiceName "ArtLightControlService"
#define ServiceExe "ArtLightControlService.exe"
#include "CodeDependencies.iss"
[Setup]
AppId={{D37D0ED6-5E8D-4131-B2C1-30A5840AC97B}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
UninstallDisplayName={#MyAppName}
AppPublisher={#MyAppPublisher}
DefaultDirName={autopf}\ArtLight\{#MyAppName}
DefaultGroupName={#MyAppName}
InfoBeforeFile=changelog.txt
SetupIconFile=ArtLightControl\Resources\artlightcontrol.ico
; Wizard artwork lives under installer\ (not ArtLightControl\Resources\): the WinUI
; targets glob images in the app's Resources folder into the build output, which the
; [Files] sweep would then ship into {app} — installer-only assets have no business
; in the install directory. Same layout as ArtMoon.
WizardSmallImageFile=installer\resources\artlightcontrol.png
WizardImageFile=installer\resources\artlightcontrol-installer.png
UninstallDisplayIcon={app}\Resources\artlightcontrol.ico
AllowNoIcons=yes
DirExistsWarning=no
CloseApplications=yes
Compression=lzma2
SolidCompression=yes
OutputDir=Output
OutputBaseFilename=ArtLightControl_{#MyAppVersion}_Installer
; WinUI 3 + Windows App SDK 2.4 require Windows 10 1903+ (build 18362) — the 2.x line
; raised this from the 1809 (17763) floor of the 1.x line.
; ArtLightControl targets 19041 (20H1), which is stricter than both, so nothing changes here.
MinVersion=10.0.19041
PrivilegesRequired=admin
; 64-bit Setup binary (Inno Setup 7+). The app is x64-only, so a 32-bit installer
; bought nothing; this also gets high-entropy ASLR by default.
SetupArchitecture=x64
; x64os (not the deprecated "x64", and not "x64compatible"): ArtLightControl is the HOST
; tool — it drives the NIC via CIM, reads GPU sensors via D3DKMT/NVML and hosts the
; streaming server. Running that emulated on ARM64 is not a scenario worth supporting.
; ArtMoon, the client, deliberately uses x64compatible instead.
ArchitecturesAllowed=x64os
ArchitecturesInstallIn64BitMode=x64os
WizardStyle=modern
DisableWelcomePage=no
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
; Start-with-Windows is ON by default. UsePreviousTasks (Inno default) remembers
; the user's choice on upgrades, so a user who unticked it stays unticked.
; The value written here is exactly what the in-app toggle writes
; (SettingsViewModel.StartWithWindows): "<exe>" --minimized — the app reads the
; Run key directly, so installer and app stay in sync. --minimized launches the
; app into the tray (App.xaml.cs skips window activation on that flag).
Name: "autostart"; Description: "Start ArtLight Control when Windows starts (runs minimized in the tray)"; GroupDescription: "Startup:"
[Messages]
WelcomeLabel1=Welcome to the ArtLight Control Setup Wizard
WelcomeLabel2=
[Files]
; ── Main WinUI 3 application ─────────────────────────────────────────────────
; dotnet build output. ArtLightControl.Core.dll is included automatically (ProjectReference).
; Excludes:
; *.pdb — debug symbols, not needed at runtime
; ref\* — compiler-only reference assemblies
; ArtLightControl.exe.WebView2\* — WebView2 user-data folder. The app stopped creating it
; in 7.2.0 (Store tab removed), but a stale one left over
; from the 6.2.x era can still sit in bin and would be
; swept in by recursesubdirs — it holds browsing cache
; and cookies and must never ship. Safety net only.
Source: "ArtLightControl\bin\x64\Release\net8.0-windows10.0.19041.0\win-x64\*"; DestDir: "{app}"; Excludes: "*.pdb,ref\*,ArtLightControl.exe.WebView2\*"; Flags: ignoreversion recursesubdirs createallsubdirs
; ── Background service (LocalSystem account, manages NIC speed via CIM) ─────
Source: "ArtLightControlService\bin\x64\Release\net8.0-windows\win-x64\*"; DestDir: "{app}"; Excludes: "*.pdb,ref\*"; Flags: ignoreversion recursesubdirs createallsubdirs
; ── Release notes ────────────────────────────────────────────────────────────
Source: "changelog.txt"; DestDir: "{app}"; Flags: ignoreversion
; ── Installer wizard logo (extracted to temp for the welcome page) ────────────
Source: "installer\resources\artlightcontrol.png"; Flags: dontcopy
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
; Autostart on by default (task "autostart", checked). Same value the in-app
; toggle writes, so the Settings page reflects ON after install with no drift.
[Registry]
Root: HKCU; Subkey: "SOFTWARE\Microsoft\Windows\CurrentVersion\Run"; ValueType: string; ValueName: "ArtLightControl"; \
ValueData: """{app}\{#MyAppExeName}"" --minimized"; Flags: uninsdeletevalue; Tasks: autostart
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#MyAppName}}"; Flags: postinstall skipifsilent nowait
[Code]
// ── Stop Windows from maximising the wizard ──────────────────────────────────
// Mirror of the same block in ArtMoon's installer — keep the two identical.
//
// On a handheld the wizard opens filling the whole screen with the layout still
// drawn for a small window: artwork at natural size in the top-left, a large empty
// area around it. That is a window MAXIMISED after its layout was computed, not a
// wizard computed too large (which would stretch the artwork to full height). It is
// not the Inno Setup version — it did the same under Inno Setup 6.
//
// Windows only auto-maximises windows that can be maximised, so the fix is to say
// this one cannot: take the sizing frame and the maximise box off it. Setup's wizard
// is not meant to be resized anyway — Inno Setup 7 dropped WizardResizable for
// exactly that reason, which makes this a no-op there and a fix everywhere else.
//
// ⚠️ SetWindowLongW, not SetWindowLongPtrW. Both are exported by user32 on 64-bit,
// but the Ptr variant takes a LONG_PTR (8 bytes) and we build a 64-bit installer
// (SetupArchitecture=x64os), so handing it a 32-bit value is the argument-size
// mismatch Inno Setup 7's release notes warn about. Window styles are 32-bit, so the
// plain variant is the correct one for GWL_STYLE on any architecture.
const
GWL_STYLE = -16;
WS_MAXIMIZEBOX = $00010000;
WS_THICKFRAME = $00040000;
SWP_NOSIZE = $0001;
SWP_NOMOVE = $0002;
SWP_NOZORDER = $0004;
SWP_NOACTIVATE = $0010;
SWP_FRAMECHANGED = $0020;
function GetWindowLong(hWnd: HWND; nIndex: Integer): LongInt;
external 'GetWindowLongW@user32.dll stdcall';
function SetWindowLong(hWnd: HWND; nIndex: Integer; dwNewLong: LongInt): LongInt;
external 'SetWindowLongW@user32.dll stdcall';
function SetWindowPos(hWnd: HWND; hWndInsertAfter: HWND; X, Y, cx, cy: Integer; uFlags: Cardinal): LongInt;
external 'SetWindowPos@user32.dll stdcall';
procedure MakeWizardFixedSize;
begin
SetWindowLong(WizardForm.Handle, GWL_STYLE,
GetWindowLong(WizardForm.Handle, GWL_STYLE) and not (WS_MAXIMIZEBOX or WS_THICKFRAME));
// Required after any style change: SetWindowLong alters the style bits but leaves the
// cached non-client frame alone, so the window keeps the client area computed for the old
// styles until Windows is asked to recompute it. SetWindowLong's own documentation
// prescribes this call.
//
// ⚠️ It is NOT the fix for the check boxes being clipped on their left edge — that was my
// first theory and hardware disproved it. That symptom appears only on the Ally and is
// unexplained; see §28. This call stays because it is correct on its own terms.
SetWindowPos(WizardForm.Handle, 0, 0, 0, 0, 0,
SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER or SWP_NOACTIVATE or SWP_FRAMECHANGED);
end;
var
LogoImage: TBitmapImage;
DevelopedByLabel: TNewStaticText;
GitHubLinkLabel: TNewStaticText;
ArtMoonPage: TWizardPage;
ArtMoonIntroLabel: TNewStaticText;
ArtMoonBulletsLabel: TNewStaticText;
ArtMoonOutroLabel: TNewStaticText;
ArtMoonLearnMoreLabel: TNewStaticText;
ArtMoonLinkLabel: TNewStaticText;
procedure GitHubLinkClick(Sender: TObject);
var
ErrorCode: Integer;
begin
ShellExec('open', '{#MyAppURL}', '', '', SW_SHOWNORMAL, ewNoWait, ErrorCode);
end;
procedure ArtMoonLinkClick(Sender: TObject);
var
ErrorCode: Integer;
begin
ShellExec('open', 'https://github.com/onaiaku/ArtMoon', '', '', SW_SHOWNORMAL, ewNoWait, ErrorCode);
end;
procedure InitializeWizard;
var
TmpFileName: String;
begin
// Before anything is laid out: the window exists by now, but has not been shown.
MakeWizardFixedSize;
ExtractTemporaryFile('artlightcontrol.png');
TmpFileName := ExpandConstant('{tmp}\artlightcontrol.png');
LogoImage := TBitmapImage.Create(WizardForm);
LogoImage.Parent := WizardForm.WelcomePage;
// PngImage (not Bitmap) is the loader for .png — see Inno Setup's CodeClasses example.
LogoImage.PngImage.LoadFromFile(TmpFileName);
LogoImage.Left := WizardForm.WelcomeLabel1.Left;
LogoImage.Top := WizardForm.WelcomeLabel1.Top + WizardForm.WelcomeLabel1.Height + ScaleY(25);
// Sized explicitly, NOT with AutoSize.
//
// AutoSize draws the PNG at its native pixel size, so the artwork's own resolution silently
// becomes the layout. That held while the file happened to be 96x96; the moment it was
// replaced with a 672x672 master the logo rendered seven times too big and bled across the
// welcome page. Stretch scales whatever it is given into the box below, so the asset can be
// any resolution — and a higher one is now the better choice, since it downscales cleanly.
//
// ScaleX/ScaleY where AutoSize gave raw pixels: the rest of this layout is already
// DPI-scaled, so the logo was the one element that shrank on a high-DPI display.
LogoImage.AutoSize := False;
LogoImage.Stretch := True;
LogoImage.Width := ScaleX(96);
LogoImage.Height := ScaleY(96);
DevelopedByLabel := TNewStaticText.Create(WizardForm);
DevelopedByLabel.Parent := WizardForm.WelcomePage;
DevelopedByLabel.Left := LogoImage.Left;
DevelopedByLabel.Top := LogoImage.Top + LogoImage.Height + ScaleY(30);
DevelopedByLabel.Caption := 'Developed by onaiaku © 2026';
DevelopedByLabel.Font.Size := 10;
DevelopedByLabel.AutoSize := True;
GitHubLinkLabel := TNewStaticText.Create(WizardForm);
GitHubLinkLabel.Parent := WizardForm.WelcomePage;
GitHubLinkLabel.Left := DevelopedByLabel.Left;
GitHubLinkLabel.Top := DevelopedByLabel.Top + DevelopedByLabel.Height + ScaleY(15);
GitHubLinkLabel.Caption := '{#MyAppURL}';
GitHubLinkLabel.Cursor := crHand;
GitHubLinkLabel.Font.Color := clHighlight;
GitHubLinkLabel.Font.Style := [fsUnderline];
GitHubLinkLabel.OnClick := @GitHubLinkClick;
// Companion page: points at ArtMoon, our client, since neither app is much use alone.
// the other, since neither is much use to a streaming setup on its own. Same layout,
// same full inner-page width — the Welcome page's right panel is too narrow for a
// bullet list.
ArtMoonPage := CreateCustomPage(wpWelcome,
'ArtMoon — recommended companion app', #13#10 +
'Install ArtMoon on the device you play from to unlock ArtLight Control''s features.');
ArtMoonIntroLabel := TNewStaticText.Create(ArtMoonPage);
ArtMoonIntroLabel.Parent := ArtMoonPage.Surface;
ArtMoonIntroLabel.Left := 0;
ArtMoonIntroLabel.Top := 0;
ArtMoonIntroLabel.Width := ArtMoonPage.SurfaceWidth;
ArtMoonIntroLabel.WordWrap := True;
ArtMoonIntroLabel.AutoSize := True;
ArtMoonIntroLabel.Caption :=
'ArtLight Control tunes this host for any Moonlight-compatible client. Paired with ' +
'ArtMoon — a free open-source Moonlight fork for the client device, from the ' +
'same team — the two work as one:';
ArtMoonBulletsLabel := TNewStaticText.Create(ArtMoonPage);
ArtMoonBulletsLabel.Parent := ArtMoonPage.Surface;
ArtMoonBulletsLabel.Left := ScaleX(16);
ArtMoonBulletsLabel.Top := ArtMoonIntroLabel.Top + ArtMoonIntroLabel.Height + ScaleY(14);
ArtMoonBulletsLabel.AutoSize := True;
ArtMoonBulletsLabel.Caption :=
// NB: this label has no WordWrap, so every bullet must stay on one line —
// keep them at or under ~76 characters or they get clipped on the right.
'• Link-speed matching — this host follows the client, before connecting' + #13#10 +
'• Seamless launch — the client can wait for your game to appear' + #13#10 +
'• Wake this host and type its PIN from the client, with the controller' + #13#10 +
'• This host''s GPU, encoder, VRAM, temperature and CPU in the game overlay' + #13#10 +
'• Store badges on your synced game covers (Steam, Epic, GOG, Xbox, …)' + #13#10 +
'• Per-session quality grading, charts and delivered-vs-target bitrate' + #13#10 +
'• This host''s last session shown on the client, cover art included' + #13#10 +
'• Sleep, restart or power this host off, or update it, from the client' + #13#10 +
'• Gamepad-first interface: every action reachable from the pad' + #13#10 +
'• Per-game and per-host profiles, custom resolutions, live stream settings' + #13#10 +
'• Tailscale presence for streaming from outside your network';
ArtMoonOutroLabel := TNewStaticText.Create(ArtMoonPage);
ArtMoonOutroLabel.Parent := ArtMoonPage.Surface;
ArtMoonOutroLabel.Left := 0;
ArtMoonOutroLabel.Top := ArtMoonBulletsLabel.Top + ArtMoonBulletsLabel.Height + ScaleY(18);
ArtMoonOutroLabel.Width := ArtMoonPage.SurfaceWidth;
ArtMoonOutroLabel.WordWrap := True;
ArtMoonOutroLabel.AutoSize := True;
ArtMoonOutroLabel.Caption :=
'ArtMoon is optional — ArtLight Control works with any Moonlight-compatible client, ' +
'and you can install it on the client device at any time. Click Next to continue ' +
'installing ArtLight Control.';
ArtMoonLearnMoreLabel := TNewStaticText.Create(ArtMoonPage);
ArtMoonLearnMoreLabel.Parent := ArtMoonPage.Surface;
ArtMoonLearnMoreLabel.Left := 0;
ArtMoonLearnMoreLabel.Top := ArtMoonOutroLabel.Top + ArtMoonOutroLabel.Height + ScaleY(16);
ArtMoonLearnMoreLabel.Caption := 'Learn more:';
ArtMoonLearnMoreLabel.AutoSize := True;
ArtMoonLinkLabel := TNewStaticText.Create(ArtMoonPage);
ArtMoonLinkLabel.Parent := ArtMoonPage.Surface;
ArtMoonLinkLabel.Left := ArtMoonLearnMoreLabel.Left + ArtMoonLearnMoreLabel.Width + ScaleX(4);
ArtMoonLinkLabel.Top := ArtMoonLearnMoreLabel.Top;
ArtMoonLinkLabel.Caption := 'https://github.com/onaiaku/ArtMoon';
ArtMoonLinkLabel.Cursor := crHand;
ArtMoonLinkLabel.Font.Color := clHighlight;
ArtMoonLinkLabel.Font.Style := [fsUnderline];
ArtMoonLinkLabel.OnClick := @ArtMoonLinkClick;
ArtMoonLinkLabel.AutoSize := True;
end;
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
ResultCode: Integer;
begin
// Stop the service BEFORE [Files] copies anything. On an upgrade over a running
// install, ArtLightControlService.exe holds a lock on its own binary, so overwriting it
// would otherwise depend on Restart Manager (CloseApplications=yes) noticing the
// service and shutting it down — which is implicit, not guaranteed, and can race
// with the sc delete/create that runs later in ssPostInstall.
// A failure here is ignored on purpose: on a first install the service doesn't exist.
Exec('sc.exe', 'stop ' + '{#ServiceName}', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
// sc.exe returns as soon as the STOP is accepted, not once the service has actually
// stopped, so give it a moment to release the file handle before the copy starts.
Sleep(1500);
Result := '';
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
ResultCode: Integer;
AppDir: String;
begin
if CurStep = ssPostInstall then
begin
AppDir := ExpandConstant('{app}');
// Stop and remove any existing service instance before (re)creating
Exec('sc.exe', 'stop ' + '{#ServiceName}', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Exec('sc.exe', 'delete ' + '{#ServiceName}', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
// Create service with automatic start, running as LocalSystem
Exec('sc.exe',
'create ' + '{#ServiceName}' +
' binPath= "' + AppDir + '\{#ServiceExe}"' +
' DisplayName= "ArtLight Control Speed Service"' +
' start= auto',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
// Set service description
Exec('sc.exe',
'description ' + '{#ServiceName}' +
' "Applies network adapter speed changes for ArtLight Control without UAC prompts."',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
// Start the service immediately
Exec('sc.exe', 'start ' + '{#ServiceName}', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
ResultCode: Integer;
begin
if CurUninstallStep = usUninstall then
begin
Exec('sc.exe', 'stop ' + '{#ServiceName}', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Exec('sc.exe', 'delete ' + '{#ServiceName}', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
// Remove autostart registry entry if the user had it enabled in-app
RegDeleteValue(HKCU, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Run', 'ArtLightControl');
// ...and the logon task, if the user chose Priority instead. Without this the task
// survives the uninstall and fails at every logon, forever, launching an exe that is gone.
Exec('schtasks.exe', '/Delete /TN "ArtLightControl Startup" /F', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
end;
function InitializeSetup: Boolean;
begin
// .NET 8 base runtime (Microsoft.NETCore.App) — WinUI 3 does not need Desktop runtime
Dependency_AddDotNet80;
// Windows App SDK 2.4 runtime — provides the WinUI 3 XAML framework (DDLM package)
Dependency_AddWindowsAppRuntime24;
Result := True;
end;