f267c430d9
Three features plus the design-system swap. Installer. Inno-compile.iss builds a per-user installer into %APPDATA%\ipswap with PrivilegesRequired=lowest, so it never shows a UAC prompt. That matches the app's asInvoker manifest, and it is also what lets the in-app updater replace the .exe later without elevation — a Program Files install could not. It offers a desktop shortcut and an optional start-with-Windows entry, and reuses the app's own single-instance mutex as AppMutex so setup notices a running copy, since a running .exe cannot be overwritten. Uninstall leaves presets.json, config.json and the log in place. Launching. Opening ipswap opens the editor in a browser, starting the tray first if it is not already up; if it is, the running copy opens the browser and the second process exits. internal/instance does this with a loopback control listener whose port and token live in a mode-600 session.json, plus a session-local named mutex on Windows to settle a launch race. A stale session file from a crash is detected by a failed call and treated as "no primary", so it can never wedge startup. The Run-key entry now passes --background, because a browser tab at every login is not wanted. Switching from the browser. New endpoints for active state, apply preview and apply. The browser confirms against the same before/after text the tray's MessageBox shows, built from a live read at prompt time. Apply requires the session token in a header like every other mutation — it changes the machine's network, so it is not a weaker case than editing a preset. A netsh refusal for lack of elevation comes back as 409 with a flag, so the page can point at "Relaunch as administrator" instead of showing a generic failure. To avoid two copies of that sequence, internal/switcher now owns everything between "the user said yes" and "the adapter changed", and both front ends call it. It serialises applies: two interleaved netsh sequences on one adapter would leave it matching neither preset, and there are now two ways to start one. CSS. apointless.css is vendored from bsncubed/css and left untouched; the previous file was the boarding-pass stylesheet and its class names did not match this markup. ipswap.css adds only what the design system does not ship — page chrome, tables, modals, stat tiles, a success alert — on its tokens, so re-pulling apointless.css restyles the app. Not verified: the installer is uncompiled (no Inno Setup or wine on this box) and the UI is not visually rendered (headless Firefox hangs here). Both are checked as far as the tooling allows — assets and endpoints serve, and every DOM id the JS touches exists in the markup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
112 lines
4.2 KiB
Plaintext
112 lines
4.2 KiB
Plaintext
; ipswap installer.
|
|
;
|
|
; Compile with Inno Setup 6:
|
|
; ISCC.exe /DAppVersion=0.1.0 Inno-compile.iss
|
|
; or from the repo root on Linux:
|
|
; make installer VERSION=0.1.0
|
|
;
|
|
; This is a per-user install. Nothing here needs administrator rights, which is
|
|
; deliberate and matches the application: ipswap ships an asInvoker manifest and
|
|
; degrades to offering "Relaunch as administrator" rather than demanding
|
|
; elevation up front. An installer that popped UAC would undo that.
|
|
|
|
#define AppName "ipswap"
|
|
#define AppExeName "ipswap.exe"
|
|
#define AppPublisher "apointless.space"
|
|
#define AppURL "https://gitea.apointless.space/bsncubed/ipswap"
|
|
|
|
; Overridden by the Makefile with /DAppVersion=...
|
|
#ifndef AppVersion
|
|
#define AppVersion "0.0.0-dev"
|
|
#endif
|
|
|
|
[Setup]
|
|
; Never change AppId: it is what lets a new version upgrade an old one in place
|
|
; rather than installing alongside it.
|
|
AppId={{17223C0B-73DA-457E-9AF7-22220467538A}
|
|
AppName={#AppName}
|
|
AppVersion={#AppVersion}
|
|
AppVerName={#AppName} {#AppVersion}
|
|
AppPublisher={#AppPublisher}
|
|
AppPublisherURL={#AppURL}
|
|
AppSupportURL={#AppURL}
|
|
AppUpdatesURL={#AppURL}/releases
|
|
|
|
; The app installs into its own data directory under %APPDATA%, so the binary
|
|
; sits beside presets.json, config.json and the log. That keeps the whole
|
|
; installation inside the user profile — no Program Files, no admin, and the
|
|
; in-app updater can replace the .exe without a privilege prompt.
|
|
DefaultDirName={userappdata}\{#AppName}
|
|
DisableDirPage=auto
|
|
DefaultGroupName={#AppName}
|
|
DisableProgramGroupPage=yes
|
|
|
|
; lowest, not admin: see the header. PrivilegesRequiredOverridesAllowed is
|
|
; deliberately not set, so there is no path to a machine-wide install that
|
|
; would break the updater's ability to swap the binary.
|
|
PrivilegesRequired=lowest
|
|
|
|
; ipswap holds this mutex for its lifetime. Inno uses it to notice a running
|
|
; copy and ask the user to close it, which matters because a running .exe on
|
|
; Windows cannot be overwritten.
|
|
AppMutex=Local\ipswap-single-instance
|
|
|
|
ArchitecturesAllowed=x64compatible
|
|
ArchitecturesInstallIn64BitMode=x64compatible
|
|
|
|
OutputDir=Output
|
|
OutputBaseFilename={#AppName}-{#AppVersion}-setup
|
|
SetupIconFile=internal\tray\icon.ico
|
|
UninstallDisplayIcon={app}\{#AppExeName}
|
|
UninstallDisplayName={#AppName} {#AppVersion}
|
|
|
|
Compression=lzma2/max
|
|
SolidCompression=yes
|
|
WizardStyle=modern
|
|
ShowLanguageDialog=no
|
|
|
|
[Languages]
|
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
|
|
|
[Tasks]
|
|
Name: "desktopicon"; Description: "Create a &desktop shortcut"; GroupDescription: "Shortcuts:"
|
|
Name: "startup"; Description: "Start {#AppName} when Windows starts"; GroupDescription: "Startup:"; Flags: unchecked
|
|
|
|
[Files]
|
|
Source: "bin\{#AppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
|
|
|
[Icons]
|
|
Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"
|
|
Name: "{group}\Uninstall {#AppName}"; Filename: "{uninstallexe}"
|
|
Name: "{userdesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Tasks: desktopicon
|
|
|
|
[Registry]
|
|
; --background suppresses the browser tab at login. A launch from a shortcut
|
|
; has no flag and does open the editor, which is the behaviour the shortcuts
|
|
; above rely on.
|
|
Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; \
|
|
ValueType: string; ValueName: "{#AppName}"; \
|
|
ValueData: """{app}\{#AppExeName}"" --background"; \
|
|
Flags: uninsdeletevalue; Tasks: startup
|
|
|
|
; Even when the startup task is not chosen, remove any Run entry the app itself
|
|
; wrote from its settings screen — otherwise uninstalling leaves Windows trying
|
|
; to launch a binary that is gone.
|
|
Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; \
|
|
ValueType: none; ValueName: "{#AppName}"; \
|
|
Flags: uninsdeletevalue; Tasks: not startup
|
|
|
|
[Run]
|
|
; No --background here: finishing setup should show the user the editor, which
|
|
; is also what starts the tray.
|
|
Filename: "{app}\{#AppExeName}"; Description: "Start {#AppName} and open the preset editor"; \
|
|
Flags: nowait postinstall skipifsilent
|
|
|
|
[UninstallDelete]
|
|
; The control channel's session file is runtime state, not user data.
|
|
Type: files; Name: "{app}\session.json"
|
|
|
|
; presets.json, config.json and ipswap.log are deliberately NOT listed. They are
|
|
; the user's data — preset packs represent real work — and Inno only removes
|
|
; files it installed, so {app} survives uninstall with that data intact.
|