; ============================================================================================= ; LiveWall installer ; ; A dark, branded setup that matches the app's own look (Themes/Dark.xaml). No Modern UI wizard: ; every page is drawn by hand with nsDialogs so the whole window is the app's palette, and the ; stock Next/Back/Cancel row is replaced by flat buttons. ; ; Layout notes (they matter if you edit this): ; * the page measures the window at run time ($PU x $PV) and every control is placed as a ; percentage of that (see LayoutVars), so the layout keeps its proportions at any DPI; ; * SetCtlColors takes colours as 0xRRGGBB; ; * the wizard dialog itself is painted with SetCtlColors $HWNDPARENT, and the page dialog is ; stretched over it (MeasurePage) and painted too. Do NOT add a full-page background label: ; it ends up above the controls and swallows every click; ; * the install runs in the page's leave function, so there is no InstFiles page at all and the ; window never flashes a second, differently themed screen; ; * discrete sizes (the app mark, the tick boxes, the button insets) are plain pixels. ; ; Per-user install (no UAC): %LOCALAPPDATA%\Programs\LiveWall, shortcuts and the uninstall entry in ; HKCU. Build with installer/build-installer.sh, which passes the version in from LiveWall.csproj. ; ; Upgrade handling: an existing installation is detected through HKCU\Software\LiveWall ; ("InstallDir" + "Version"). An older version is updated in place and keeps %APPDATA%\LiveWall; ; the same version asks before reinstalling, a newer one asks before downgrading. ; ============================================================================================= Unicode true !include "LogicLib.nsh" !include "x64.nsh" !include "FileFunc.nsh" !include "nsDialogs.nsh" !include "WinMessages.nsh" !define APP_NAME "LiveWall" !define APP_EXE "LiveWall.exe" !define APP_PUBLISHER "LiveWall" !define APP_REGKEY "Software\LiveWall" !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\LiveWall" !define DOTNET_URL "https://dotnet.microsoft.com/download/dotnet/8.0/runtime/desktop" !define APP_TAGLINE "Animated wallpapers for Windows 11" ; ------------------------------------------------------------------ palette (Themes/Dark.xaml) !define C_BG "0x0E1116" ; BgWindow !define C_CARD "0x151A22" ; BgCard !define C_FIELD "0x1B222C" ; BgField !define C_HOVER "0x232C38" ; BgHover !define C_LINE "0x242C38" ; BorderBrushSoft !define C_TEXT "0xE7EBF3" ; TextBrush !define C_DIM "0x8C95A5" ; TextDim !define C_ACCENT "0x6C8CFF" ; AccentBrush (matches the icon's indigo) !define C_ACCENT_HI "0x865EF2" ; icon gradient end violet !define C_GREEN "0x35D0A5" ; GreenBrush (the icon's dot) !define C_ON_ACCENT "0x0E1116" !ifndef VERSION !define VERSION "1.7.0" !endif !ifndef VERSION4 !define VERSION4 "1.7.0.0" !endif !ifndef SOURCE_DIR !define SOURCE_DIR "..\prebuilt" !endif ; Output name suffix, e.g. "-zlib" when building the alternate installer used for antivirus triage. !ifndef OUT_SUFFIX !define OUT_SUFFIX "" !endif Name "${APP_NAME} ${VERSION}" OutFile "..\${APP_NAME}-Setup-${VERSION}${OUT_SUFFIX}.exe" InstallDir "$LOCALAPPDATA\Programs\${APP_NAME}" InstallDirRegKey HKCU "${APP_REGKEY}" "InstallDir" Icon "..\LiveWall\Assets\app.ico" UninstallIcon "..\LiveWall\Assets\app.ico" RequestExecutionLevel user BrandingText " " ; ------------------------------------------------------------------ compression ; ; Default: solid LZMA, the smallest installer. Some heuristic engines (VBA32 "TScope", Trapmine's ; ML score) react to solid NSIS archives, so the compressor can be switched at build time: ; ; makensis /DCOMPRESS=zlib (no solid block, lowest false-positive surface, bigger file) ; makensis /DCOMPRESS=lzma (LZMA, one stream per file) ; makensis /DCOMPRESS=lzma-solid (default) ; !ifndef COMPRESS !define COMPRESS "lzma-solid" !endif !if "${COMPRESS}" == "zlib" SetCompressor zlib !else if "${COMPRESS}" == "lzma" SetCompressor lzma SetCompressorDictSize 32 !else SetCompressor /SOLID lzma SetCompressorDictSize 32 !endif ; Sign with Authenticode when a signing command is supplied (see installer\sign.ps1): ; makensis /DSIGNTOOL='signtool sign /fd sha256 /tr http://timestamp.digicert.com /td sha256 $%1' ; NSIS then signs the generated uninstaller and the finished installer. !ifdef SIGNTOOL SignTool signtool "${SIGNTOOL}" !endif VIProductVersion "${VERSION4}" VIAddVersionKey "ProductName" "${APP_NAME}" VIAddVersionKey "FileDescription" "${APP_NAME} installer" VIAddVersionKey "FileVersion" "${VERSION4}" VIAddVersionKey "ProductVersion" "${VERSION}" VIAddVersionKey "CompanyName" "${APP_PUBLISHER}" VIAddVersionKey "LegalCopyright" "${APP_PUBLISHER}" VIAddVersionKey "LegalTrademarks" "${APP_NAME}" VIAddVersionKey "OriginalFilename" "${APP_NAME}-Setup-${VERSION}.exe" VIAddVersionKey "Comments" "Live wallpaper for Windows 11 - no bundled offers, no telemetry, installs for the current user only." ; ------------------------------------------------------------------ pages Page custom SetupPageShow SetupPageLeave Page custom DonePageShow DonePageLeave UninstPage custom un.ConfirmPageShow un.ConfirmPageLeave UninstPage instfiles ; ------------------------------------------------------------------ state Var InstalledDir Var InstalledVersion Var InstalledScore Var ThisScore Var UpgradeFrom ; non-empty => this run updates an existing copy Var DeleteSettings Var OptDesktop Var OptStartup Var OptStartApp Var OptOpenFolder Var StartAfter Var PageDlg ; the page dialog returned by nsDialogs::Create Var X0 Var X1 Var X_RIGHT Var X_BTN1 Var X_BTN2 Var W_TEXT Var W_RULE Var W_LEFT Var W_BTN Var Y_ICON Var Y_TITLE Var Y_TAG Var Y_RULE Var Y_NOTE Var Y_VERSION Var Y_PATH Var Y_OPT1 Var Y_OPT2 Var Y_OPT3 Var Y_FOOT Var Y_BTN Var H_TITLE Var H_LINE Var H_BTN Var STRIP_H Var UpgradeNote Var PathLabel Var InstallBtn Var FinishBtn Var Bx1 Var Tx1 Var S1 Var Bx2 Var Tx2 Var S2 Var Bx3 Var Tx3 Var S3 Var Bx4 Var Tx4 Var S4 Var Bx5 Var Tx5 Var S5 ; ------------------------------------------------------------------ layout helpers ; ; The page is measured at run time and every control is placed as a percentage of that size, so the ; layout keeps its proportions on high-DPI screens (the font sizes follow the system DPI on their own). ; $PU / $PV hold the client size of the wizard window in pixels. Var PU Var PV Var TmpX Var TmpY Var TmpW !macro Pct OUT BASE PERCENT IntOp ${OUT} ${BASE} * ${PERCENT} IntOp ${OUT} ${OUT} / 100 !macroend ; Sizes the page over the whole wizard client area and records $PU / $PV. Function MeasurePage System::Call '*(i,i,i,i) i .r5' System::Call 'user32::GetClientRect(p $HWNDPARENT, p r5) i .r4' System::Call '*$5(i .r6, i .r7, i .r8, i .r9)' StrCpy $PU $8 StrCpy $PV $9 ; SWP_NOZORDER|SWP_NOACTIVATE: pin the page to the client origin and stretch it System::Call 'user32::SetWindowPos(p $PageDlg, p 0, i 0, i 0, i $8, i $9, i 0x14)' FunctionEnd ; ------------------------------------------------------------------ helpers ; "1.6.3" / "1.6.3.0" / "1.6" -> a comparable integer (major*1e6 + minor*1e3 + patch). ; Input: $R0 Output: $R9 (all other registers restored) Function VersionScore Push $0 Push $1 Push $2 Push $3 Push $4 StrCpy $1 0 ; accumulated score StrCpy $2 0 ; current component value StrCpy $3 0 ; character index StrCpy $4 0 ; components seen vs_loop: StrCpy $0 $R0 1 $3 ${If} $0 == "" Goto vs_end ${EndIf} ${If} $0 == "." ${If} $4 >= 2 Goto vs_end ; ignore anything after the third component ${EndIf} IntOp $4 $4 + 1 IntOp $1 $1 * 1000 IntOp $1 $1 + $2 StrCpy $2 0 Goto vs_next ${EndIf} IntOp $0 $0 + 0 ; digit value (0 for anything that is not a number) IntOp $2 $2 * 10 IntOp $2 $2 + $0 vs_next: IntOp $3 $3 + 1 Goto vs_loop vs_end: IntOp $1 $1 * 1000 IntOp $1 $1 + $2 ; pad missing components ("1.6" must sort below "1.6.1") ${If} $4 < 2 IntOp $1 $1 * 1000 ${EndIf} StrCpy $R9 $1 Pop $4 Pop $3 Pop $2 Pop $1 Pop $0 FunctionEnd ; Finds an existing installation and decides what to do about it. Function DetectExisting StrCpy $InstalledDir "" StrCpy $InstalledVersion "" StrCpy $UpgradeFrom "" ReadRegStr $InstalledDir HKCU "${APP_REGKEY}" "InstallDir" ReadRegStr $InstalledVersion HKCU "${APP_REGKEY}" "Version" ; No registry entry (older installer or a copied folder): look in the default location. ${If} $InstalledDir == "" ${If} ${FileExists} "$LOCALAPPDATA\Programs\${APP_NAME}\${APP_EXE}" StrCpy $InstalledDir "$LOCALAPPDATA\Programs\${APP_NAME}" ${EndIf} ${EndIf} ; Installed, but the version was never recorded: treat it as an unknown older build. ${If} $InstalledDir != "" ${AndIf} $InstalledVersion == "" StrCpy $InstalledVersion "0.0.0" ${EndIf} ${If} $InstalledDir == "" Return ; nothing installed - a fresh install ${EndIf} StrCpy $R0 "$InstalledVersion" Call VersionScore StrCpy $InstalledScore $R9 StrCpy $R0 "${VERSION}" Call VersionScore StrCpy $ThisScore $R9 ${If} $InstalledScore >= $ThisScore Return ; same or newer - handled by the caller ${EndIf} StrCpy $UpgradeFrom "$InstalledVersion" StrCpy $INSTDIR "$InstalledDir" ; never install an update beside the old copy FunctionEnd ; ------------------------------------------------------------------ chrome ; Paints the wizard window itself, so nothing around the page is left white, and hides the stock ; Next / Back / Cancel row (the pages bring their own flat buttons; Enter and Esc still work). Function PaintChrome SetCtlColors $HWNDPARENT "${C_TEXT}" "${C_BG}" GetDlgItem $0 $HWNDPARENT 1 ShowWindow $0 ${SW_HIDE} GetDlgItem $0 $HWNDPARENT 2 ShowWindow $0 ${SW_HIDE} GetDlgItem $0 $HWNDPARENT 3 ShowWindow $0 ${SW_HIDE} FunctionEnd ; One flat surface: background, the icon's gradient strip, the app icon, the wordmark and a rule. !macro PageChrome ; The page dialog itself is the background. A full-page label would sit above the controls and ; swallow every click, which is why there is none. SetCtlColors $PageDlg "${C_TEXT}" "${C_BG}" ; the indigo-to-violet gradient of the app icon, as two flat halves ${NSD_CreateLabel} 0 0 50% $STRIP_H "" Pop $0 SetCtlColors $0 "${C_ACCENT}" "${C_ACCENT}" ${NSD_CreateLabel} 50% 0 50% $STRIP_H "" Pop $0 SetCtlColors $0 "${C_ACCENT_HI}" "${C_ACCENT_HI}" ; The app mark, drawn from flat blocks so it stays pixel-crisp at any DPI: the icon's ; indigo-to-violet square in two halves, with its green dot at the lower right. StrCpy $TmpX $X0 IntOp $TmpX $TmpX + 16 StrCpy $TmpY $Y_ICON IntOp $TmpY $TmpY + 24 ${NSD_CreateLabel} $X0 $Y_ICON 16 32 "" Pop $0 SetCtlColors $0 "${C_ACCENT}" "${C_ACCENT}" ${NSD_CreateLabel} $TmpX $Y_ICON 16 32 "" Pop $0 SetCtlColors $0 "${C_ACCENT_HI}" "${C_ACCENT_HI}" ${NSD_CreateLabel} $TmpX $TmpY 8 8 "" Pop $0 SetCtlColors $0 "${C_GREEN}" "${C_GREEN}" ${NSD_CreateLabel} $X1 $Y_TITLE $W_TEXT $H_TITLE "${APP_NAME}" Pop $0 SetCtlColors $0 "${C_TEXT}" "${C_BG}" CreateFont $1 "Segoe UI" 15 700 SendMessage $0 ${WM_SETFONT} $1 1 ${NSD_CreateLabel} $X1 $Y_TAG $W_TEXT $H_LINE "${APP_TAGLINE}" Pop $0 SetCtlColors $0 "${C_DIM}" "${C_BG}" CreateFont $1 "Segoe UI" 9 400 SendMessage $0 ${WM_SETFONT} $1 1 ${NSD_CreateLabel} $X0 $Y_RULE $W_RULE 1 "${C_LINE}" Pop $0 SetCtlColors $0 "${C_LINE}" "${C_LINE}" !macroend ; All of the geometry in one place, as percentages of the page size. !macro LayoutVars !insertmacro Pct $X0 $PU 4 IntOp $X1 $X0 + 46 IntOp $W_TEXT $PU - $X1 IntOp $W_TEXT $W_TEXT - $X0 !insertmacro Pct $W_RULE $PU 92 !insertmacro Pct $W_LEFT $PU 60 !insertmacro Pct $X_RIGHT $PU 78 !insertmacro Pct $Y_ICON $PV 5 !insertmacro Pct $Y_TITLE $PV 4 !insertmacro Pct $H_TITLE $PV 10 !insertmacro Pct $Y_TAG $PV 15 !insertmacro Pct $H_LINE $PV 6 !insertmacro Pct $Y_RULE $PV 25 !insertmacro Pct $Y_NOTE $PV 28 !insertmacro Pct $Y_VERSION $PV 36 !insertmacro Pct $Y_PATH $PV 42 !insertmacro Pct $Y_OPT1 $PV 51 !insertmacro Pct $Y_OPT2 $PV 59 !insertmacro Pct $Y_OPT3 $PV 67 !insertmacro Pct $Y_FOOT $PV 88 !insertmacro Pct $Y_BTN $PV 85 !insertmacro Pct $H_BTN $PV 9 !insertmacro Pct $W_BTN $PU 16 !insertmacro Pct $X_BTN2 $PU 62 !insertmacro Pct $X_BTN1 $PU 80 !macroend ; A tick box: a flat square plus a label, both clickable. $BX / $TX / $ST are the box handle, ; the label handle and the state (0/1). PaintOption repaints it, the toggles flip it. !macro DefPaintOption PFX Function ${PFX}PaintOption ${If} $R5 == 1 SetCtlColors $R3 "${C_ON_ACCENT}" "${C_ACCENT}" ${NSD_SetText} $R3 "✓" ${Else} SetCtlColors $R3 "${C_TEXT}" "${C_HOVER}" ${NSD_SetText} $R3 "" ${EndIf} FunctionEnd !macroend !insertmacro DefPaintOption "" !insertmacro DefPaintOption "un." !macro ToggleClick FUNC PAINT BX TX ST Function ${FUNC} StrCpy $R3 ${BX} StrCpy $R4 ${TX} StrCpy $R5 ${ST} ${If} $R5 == 1 StrCpy $R5 0 ${Else} StrCpy $R5 1 ${EndIf} Call ${PAINT} StrCpy ${ST} $R5 FunctionEnd !macroend ; Creates the square and the label of one option; the label sits $TXOFF to the right. !macro MakeOption BX TX FUNC X Y TEXT TXOFF ${NSD_CreateLabel} ${X} ${Y} 12 12 "" Pop ${BX} CreateFont $1 "Segoe UI Symbol" 8 700 SendMessage ${BX} ${WM_SETFONT} $1 1 SetCtlColors ${BX} "${C_TEXT}" "${C_HOVER}" ${NSD_OnClick} ${BX} ${FUNC} StrCpy $TmpX ${X} IntOp $TmpX $TmpX + ${TXOFF} StrCpy $TmpY ${Y} IntOp $TmpY $TmpY + 1 ${NSD_CreateLabel} $TmpX $TmpY 330 16 "${TEXT}" Pop ${TX} SetCtlColors ${TX} "${C_TEXT}" "${C_BG}" CreateFont $1 "Segoe UI" 9 400 SendMessage ${TX} ${WM_SETFONT} $1 1 ${NSD_OnClick} ${TX} ${FUNC} !macroend !insertmacro ToggleClick Toggle1 PaintOption $Bx1 $Tx1 $S1 !insertmacro ToggleClick Toggle2 PaintOption $Bx2 $Tx2 $S2 !insertmacro ToggleClick Toggle3 PaintOption $Bx3 $Tx3 $S3 !insertmacro ToggleClick Toggle4 PaintOption $Bx4 $Tx4 $S4 !insertmacro ToggleClick Toggle5 PaintOption $Bx5 $Tx5 $S5 !insertmacro ToggleClick un.Toggle1 un.PaintOption $Bx1 $Tx1 $S1 ; A flat button: a coloured block with a padded caption on top, both clickable. !macro MakeButton BLK TXT FUNC X Y W H TEXT BGFG FG WEIGHT ${NSD_CreateLabel} ${X} ${Y} ${W} ${H} "" Pop ${BLK} SetCtlColors ${BLK} ${BGFG} ${BGFG} ${NSD_OnClick} ${BLK} ${FUNC} StrCpy $TmpX ${X} IntOp $TmpX $TmpX + 9 StrCpy $TmpY ${H} IntOp $TmpY $TmpY - 15 IntOp $TmpY $TmpY / 2 IntOp $TmpY $TmpY + ${Y} StrCpy $TmpW ${W} IntOp $TmpW $TmpW - 18 ${NSD_CreateLabel} $TmpX $TmpY $TmpW 15 "${TEXT}" Pop ${TXT} SetCtlColors ${TXT} ${FG} ${BGFG} CreateFont $1 "Segoe UI" 9 ${WEIGHT} SendMessage ${TXT} ${WM_SETFONT} $1 1 ${NSD_OnClick} ${TXT} ${FUNC} !macroend ; ------------------------------------------------------------------ setup page Function SetupPageShow nsDialogs::Create 1018 Pop $PageDlg ${If} $PageDlg == error Abort ${EndIf} Call PaintChrome Call MeasurePage !insertmacro LayoutVars StrCpy $STRIP_H 2 !insertmacro PageChrome ; ---- what this run is going to do ----------------------------------------------------------- ${If} $UpgradeFrom != "" ${NSD_CreateLabel} $X0 $Y_NOTE $W_RULE $H_LINE \ "Updating in place - your wallpaper and settings are kept." Pop $UpgradeNote SetCtlColors $UpgradeNote "${C_GREEN}" "${C_BG}" CreateFont $1 "Segoe UI" 9 400 SendMessage $UpgradeNote ${WM_SETFONT} $1 1 ${If} $InstalledVersion == "0.0.0" ; an older install that never recorded its version ${NSD_CreateLabel} $X0 $Y_VERSION $W_LEFT $H_LINE \ "The LiveWall copy you already have → ${VERSION}" ${Else} ${NSD_CreateLabel} $X0 $Y_VERSION $W_LEFT $H_LINE \ "${APP_NAME} $UpgradeFrom → ${VERSION}" ${EndIf} ${Else} ${NSD_CreateLabel} $X0 $Y_NOTE $W_RULE $H_LINE \ "LiveWall animates your wallpaper behind the desktop icons." Pop $UpgradeNote SetCtlColors $UpgradeNote "${C_DIM}" "${C_BG}" CreateFont $1 "Segoe UI" 9 400 SendMessage $UpgradeNote ${WM_SETFONT} $1 1 ${NSD_CreateLabel} $X0 $Y_VERSION $W_LEFT $H_LINE \ "Version ${VERSION} · installs for your account only" ${EndIf} Pop $0 SetCtlColors $0 "${C_DIM}" "${C_BG}" CreateFont $1 "Segoe UI" 9 400 SendMessage $0 ${WM_SETFONT} $1 1 StrCpy $TmpW $X_RIGHT IntOp $TmpW $TmpW - $X0 IntOp $TmpW $TmpW - 10 ${NSD_CreateLabel} $X0 $Y_PATH $TmpW $H_LINE "$INSTDIR" Pop $PathLabel SetCtlColors $PathLabel "${C_TEXT}" "${C_BG}" CreateFont $1 "Segoe UI" 8 400 SendMessage $PathLabel ${WM_SETFONT} $1 1 ${If} $UpgradeFrom == "" ${NSD_CreateLabel} $X_RIGHT $Y_PATH 50 $H_LINE "Change" Pop $0 SetCtlColors $0 "${C_ACCENT}" "${C_BG}" CreateFont $1 "Segoe UI" 9 400 SendMessage $0 ${WM_SETFONT} $1 1 ${NSD_OnClick} $0 OnChangeFolder ${EndIf} ; ---- options ------------------------------------------------------------------------------- !insertmacro MakeOption $Bx1 $Tx1 Toggle1 $X0 $Y_OPT1 "Create a desktop shortcut" 16 ${If} $OptDesktop == 1 StrCpy $S1 1 ${Else} StrCpy $S1 0 ${EndIf} !insertmacro MakeOption $Bx2 $Tx2 Toggle2 $X0 $Y_OPT2 "Start ${APP_NAME} with Windows" 16 ${If} $OptStartup == 1 StrCpy $S2 1 ${Else} StrCpy $S2 0 ${EndIf} ${If} $UpgradeFrom == "" !insertmacro MakeOption $Bx3 $Tx3 Toggle3 $X0 $Y_OPT3 "Start ${APP_NAME} when Setup finishes" 16 StrCpy $S3 1 ${Else} !insertmacro MakeOption $Bx3 $Tx3 Toggle3 $X0 $Y_OPT3 "Also delete my ${APP_NAME} settings and start fresh" 16 StrCpy $S3 0 ${EndIf} StrCpy $R3 $Bx1 StrCpy $R5 $S1 Call PaintOption StrCpy $R3 $Bx2 StrCpy $R5 $S2 Call PaintOption StrCpy $R3 $Bx3 StrCpy $R5 $S3 Call PaintOption ; ---- footer -------------------------------------------------------------------------------- ${NSD_CreateLabel} $X0 $Y_FOOT $W_LEFT $H_LINE "No bundled offers · no telemetry" Pop $0 SetCtlColors $0 "${C_DIM}" "${C_BG}" CreateFont $1 "Segoe UI" 8 400 SendMessage $0 ${WM_SETFONT} $1 1 !insertmacro MakeButton $0 $2 OnCancelClick $X_BTN2 $Y_BTN $W_BTN $H_BTN "Cancel" "${C_FIELD}" "${C_TEXT}" 400 ${If} $UpgradeFrom == "" !insertmacro MakeButton $0 $InstallBtn OnInstallClick $X_BTN1 $Y_BTN $W_BTN $H_BTN "Install" "${C_ACCENT}" "${C_ON_ACCENT}" 700 ${Else} !insertmacro MakeButton $0 $InstallBtn OnInstallClick $X_BTN1 $Y_BTN $W_BTN $H_BTN "Update" "${C_ACCENT}" "${C_ON_ACCENT}" 700 ${EndIf} nsDialogs::Show FunctionEnd Function SetupPageLeave ; the third option means different things for a fresh install and for an update StrCpy $OptDesktop $S1 StrCpy $OptStartup $S2 ${If} $UpgradeFrom == "" StrCpy $OptStartApp $S3 StrCpy $DeleteSettings 0 ${Else} StrCpy $DeleteSettings $S3 ${EndIf} ; This wizard has no InstFiles page: the files are written here, while the first page is leaving. ; The second page follows straight away. ${NSD_SetText} $InstallBtn "Installing..." System::Call 'user32::UpdateWindow(p $HWNDPARENT)' Call DoInstall FunctionEnd Function OnChangeFolder ${If} $UpgradeFrom != "" Return ; an update always goes into the folder that is already there ${EndIf} nsDialogs::SelectFolderDialog "Where should ${APP_NAME} be installed?" "$INSTDIR" Pop $0 ${If} $0 == error Return ${EndIf} ${If} $0 == "" Return ${EndIf} StrCpy $INSTDIR "$0" ${If} $PathLabel != 0 ${NSD_SetText} $PathLabel "$INSTDIR" ${EndIf} FunctionEnd Function OnInstallClick ; the page's leave function does the work; this just moves the wizard on SendMessage $HWNDPARENT ${WM_COMMAND} 1 0 FunctionEnd Function OnCancelClick SendMessage $HWNDPARENT ${WM_COMMAND} 2 0 FunctionEnd ; ------------------------------------------------------------------ done page Function DonePageShow nsDialogs::Create 1018 Pop $PageDlg ${If} $PageDlg == error Abort ${EndIf} Call PaintChrome Call MeasurePage !insertmacro LayoutVars StrCpy $STRIP_H 2 !insertmacro Pct $Y_OPT1 $PV 60 !insertmacro Pct $Y_OPT2 $PV 69 !insertmacro PageChrome ${NSD_CreateLabel} $X0 $Y_NOTE $W_RULE $H_LINE \ "${APP_NAME} ${VERSION} is installed and ready." Pop $0 SetCtlColors $0 "${C_GREEN}" "${C_BG}" CreateFont $1 "Segoe UI" 10 700 SendMessage $0 ${WM_SETFONT} $1 1 StrCpy $TmpX $H_LINE IntOp $TmpX $TmpX * 2 ${NSD_CreateLabel} $X0 $Y_VERSION $W_RULE $TmpX \ "It sits in your notification area - right-click the tray icon for settings, wallpapers, pause, \ interactive mode and plugins." Pop $0 SetCtlColors $0 "${C_DIM}" "${C_BG}" CreateFont $1 "Segoe UI" 9 400 SendMessage $0 ${WM_SETFONT} $1 1 !insertmacro MakeOption $Bx4 $Tx4 Toggle4 $X0 $Y_OPT1 "Start ${APP_NAME} now" 16 StrCpy $S4 1 !insertmacro MakeOption $Bx5 $Tx5 Toggle5 $X0 $Y_OPT2 "Open the ${APP_NAME} folder" 16 StrCpy $S5 0 StrCpy $R3 $Bx4 StrCpy $R5 $S4 Call PaintOption StrCpy $R3 $Bx5 StrCpy $R5 $S5 Call PaintOption ${NSD_CreateLabel} $X0 $Y_FOOT $W_LEFT $H_LINE "Uninstall any time from Windows Settings." Pop $0 SetCtlColors $0 "${C_DIM}" "${C_BG}" CreateFont $1 "Segoe UI" 8 400 SendMessage $0 ${WM_SETFONT} $1 1 !insertmacro MakeButton $0 $FinishBtn OnFinishClick $X_BTN1 $Y_BTN $W_BTN $H_BTN "Finish" "${C_ACCENT}" "${C_ON_ACCENT}" 700 nsDialogs::Show FunctionEnd Function DonePageLeave StrCpy $StartAfter $S4 StrCpy $OptOpenFolder $S5 FunctionEnd Function OnFinishClick SendMessage $HWNDPARENT ${WM_COMMAND} 1 0 FunctionEnd ; ------------------------------------------------------------------ install ; All of the file work, called from the setup page's leave function (and from .onInit when silent). Function DoInstall SetShellVarContext current ; Replacing an older version: start from a clean folder so no stale dependency survives. ${If} ${FileExists} "$INSTDIR\${APP_EXE}" RMDir /r "$INSTDIR" ${EndIf} ${If} $DeleteSettings == 1 RMDir /r "$APPDATA\LiveWall" ${EndIf} SetOutPath "$INSTDIR" File /r "${SOURCE_DIR}\*.*" WriteUninstaller "$INSTDIR\Uninstall.exe" CreateShortcut "$SMPROGRAMS\${APP_NAME}.lnk" "$INSTDIR\${APP_EXE}" "" "$INSTDIR\${APP_EXE}" 0 ${If} $OptDesktop == 1 CreateShortcut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${APP_EXE}" "" "$INSTDIR\${APP_EXE}" 0 ${Else} Delete "$DESKTOP\${APP_NAME}.lnk" ${EndIf} ${If} $OptStartup == 1 CreateShortcut "$SMSTARTUP\${APP_NAME}.lnk" "$INSTDIR\${APP_EXE}" "" "$INSTDIR\${APP_EXE}" 0 ${Else} Delete "$SMSTARTUP\${APP_NAME}.lnk" ${EndIf} WriteRegStr HKCU "${APP_REGKEY}" "InstallDir" "$INSTDIR" WriteRegStr HKCU "${APP_REGKEY}" "Version" "${VERSION}" ${If} $UpgradeFrom != "" WriteRegStr HKCU "${APP_REGKEY}" "UpdatedFrom" "$UpgradeFrom" ${EndIf} WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "${APP_NAME}" WriteRegStr HKCU "${UNINST_KEY}" "DisplayVersion" "${VERSION}" WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${APP_EXE},0" WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "${APP_PUBLISHER}" WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR" WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\Uninstall.exe$\"" WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\Uninstall.exe$\" /S" WriteRegDWORD HKCU "${UNINST_KEY}" "NoModify" 1 WriteRegDWORD HKCU "${UNINST_KEY}" "NoRepair" 1 ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 IntFmt $0 "0x%08X" $0 WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0" ; the last thing before the page changes, so "Start now" points at the finished install ${If} $OptStartApp == 1 StrCpy $StartAfter 1 ${EndIf} FunctionEnd ; ------------------------------------------------------------------ sections ; ; The installer has no InstFiles page, so this section is never executed - DoInstall() does the work ; while the setup page is leaving. It exists because NSIS wants at least one regular section. Section "-Core" SectionEnd Section "Uninstall" SetShellVarContext current nsExec::Exec 'taskkill /IM ${APP_EXE} /F' Pop $0 Sleep 500 Delete "$SMPROGRAMS\${APP_NAME}.lnk" Delete "$DESKTOP\${APP_NAME}.lnk" Delete "$SMSTARTUP\${APP_NAME}.lnk" RMDir /r "$INSTDIR" DeleteRegKey HKCU "${UNINST_KEY}" DeleteRegKey HKCU "${APP_REGKEY}" ; Settings, logs and the WebView2 cache go only if the user asked for it on the first page. ${If} $DeleteSettings == 1 RMDir /r "$APPDATA\LiveWall" ${EndIf} SectionEnd Function un.ConfirmPageShow nsDialogs::Create 1018 Pop $PageDlg ${If} $PageDlg == error Abort ${EndIf} Call un.PaintChrome Call un.MeasurePage !insertmacro LayoutVars StrCpy $STRIP_H 2 !insertmacro Pct $Y_OPT1 $PV 64 ; the description needs three lines here !insertmacro PageChrome ${NSD_CreateLabel} $X0 $Y_NOTE $W_RULE $H_LINE "Remove ${APP_NAME}?" Pop $0 SetCtlColors $0 "${C_TEXT}" "${C_BG}" CreateFont $1 "Segoe UI" 10 700 SendMessage $0 ${WM_SETFONT} $1 1 StrCpy $TmpX $H_LINE IntOp $TmpX $TmpX * 3 ${NSD_CreateLabel} $X0 $Y_VERSION $W_RULE $TmpX \ "The program files, shortcuts and uninstall entry are removed. Your wallpaper, settings and the \ web-player cache in %APPDATA%\LiveWall stay unless you tick the box below." Pop $0 SetCtlColors $0 "${C_DIM}" "${C_BG}" CreateFont $1 "Segoe UI" 9 400 SendMessage $0 ${WM_SETFONT} $1 1 !insertmacro MakeOption $Bx1 $Tx1 un.Toggle1 $X0 $Y_OPT1 "Also delete my ${APP_NAME} settings, logs and cache" 16 StrCpy $S1 0 StrCpy $R3 $Bx1 StrCpy $R5 $S1 Call un.PaintOption !insertmacro MakeButton $0 $2 un.OnUninstallCancel $X_BTN2 $Y_BTN $W_BTN $H_BTN "Cancel" "${C_FIELD}" "${C_TEXT}" 400 !insertmacro MakeButton $0 $InstallBtn un.OnUninstallClick $X_BTN1 $Y_BTN $W_BTN $H_BTN "Remove" "${C_ACCENT}" "${C_ON_ACCENT}" 700 nsDialogs::Show FunctionEnd Function un.OnUninstallClick SendMessage $HWNDPARENT ${WM_COMMAND} 1 0 FunctionEnd Function un.OnUninstallCancel SendMessage $HWNDPARENT ${WM_COMMAND} 2 0 FunctionEnd Function un.ConfirmPageLeave StrCpy $DeleteSettings $S1 ; the uninstaller has one option, and it starts cleared FunctionEnd ; Duplicated for the uninstaller: NSIS compiles the two halves separately. Function un.PaintChrome SetCtlColors $HWNDPARENT "${C_TEXT}" "${C_BG}" GetDlgItem $0 $HWNDPARENT 1 ShowWindow $0 ${SW_HIDE} GetDlgItem $0 $HWNDPARENT 2 ShowWindow $0 ${SW_HIDE} GetDlgItem $0 $HWNDPARENT 3 ShowWindow $0 ${SW_HIDE} FunctionEnd Function un.MeasurePage System::Call '*(i,i,i,i) i .r5' System::Call 'user32::GetClientRect(p $HWNDPARENT, p r5) i .r4' System::Call '*$5(i .r6, i .r7, i .r8, i .r9)' StrCpy $PU $8 StrCpy $PV $9 System::Call 'user32::SetWindowPos(p $PageDlg, p 0, i 0, i 0, i $8, i $9, i 0x14)' FunctionEnd ; ------------------------------------------------------------------ callbacks Function .onInit ; The published build is win-x64. ${IfNot} ${RunningX64} MessageBox MB_ICONSTOP "${APP_NAME} needs 64-bit Windows." Abort ${EndIf} SetShellVarContext current ; Defaults: desktop shortcut on, autostart off (the app has its own switch), start after setup on. StrCpy $OptDesktop 1 StrCpy $OptStartup 0 StrCpy $OptStartApp 1 StrCpy $OptOpenFolder 0 StrCpy $StartAfter 0 StrCpy $DeleteSettings 0 Call DetectExisting ${If} $InstalledDir != "" ; A running copy would keep the exe locked. nsExec::Exec 'taskkill /IM ${APP_EXE} /F' Pop $0 Sleep 700 ${EndIf} ${If} $InstalledDir == "" Goto init_dotnet ${EndIf} ${If} $InstalledScore == $ThisScore IfSilent init_dotnet MessageBox MB_YESNO|MB_ICONQUESTION \ "${APP_NAME} ${VERSION} is already installed in:$\r$\n$INSTDIR$\r$\n$\r$\nReinstall it?" \ IDYES init_dotnet Abort ${EndIf} ${If} $InstalledScore > $ThisScore IfSilent init_dotnet MessageBox MB_YESNO|MB_ICONEXCLAMATION \ "A newer ${APP_NAME} is already installed:$\r$\n$\r$\n installed: $InstalledVersion$\r$\n this package: ${VERSION}$\r$\n$\r$\nDowngrade to ${VERSION} anyway?" \ IDYES init_dotnet Abort ${EndIf} init_dotnet: SetRegView 64 Call CheckDotNet SetRegView lastused ; Silent installs have no pages, so everything runs here and the process ends. ${If} ${Silent} Call DoInstall Quit ${EndIf} FunctionEnd ; Runs when the wizard closes: start the app, or open the folder, whichever was ticked. Function .onGUIEnd ${If} $StartAfter == 1 Exec '"$INSTDIR\${APP_EXE}"' ${ElseIf} $OptOpenFolder == 1 ExecShell "open" "$INSTDIR" ${EndIf} FunctionEnd Function un.onInit SetRegView 64 StrCpy $DeleteSettings 0 FunctionEnd ; Looks for the .NET Desktop Runtime 8 (or newer) and offers the download page if it is missing. Function CheckDotNet StrCpy $1 0 StrCpy $4 0 dotnet_loop: EnumRegKey $2 HKLM "SOFTWARE\dotnet\Setup\InstalledVersions\x64\sharedfx\Microsoft.WindowsDesktop.App" $1 StrCmp $2 "" dotnet_done StrCpy $3 $2 1 ${If} $3 == "8" ${OrIf} $3 == "9" StrCpy $4 1 Goto dotnet_done ${EndIf} ${If} $3 == "1" ; 10.x and later StrCpy $4 1 Goto dotnet_done ${EndIf} IntOp $1 $1 + 1 Goto dotnet_loop dotnet_done: ${If} $4 == 0 IfSilent dotnet_skip MessageBox MB_YESNO|MB_ICONINFORMATION \ "${APP_NAME} needs the .NET Desktop Runtime (8 or newer) and it does not look installed.$\r$\n$\r$\nOpen the Microsoft download page now? You can also continue and install it later - ${APP_NAME} will tell you what is missing." \ IDNO dotnet_skip ExecShell "open" "${DOTNET_URL}" dotnet_skip: ${EndIf} FunctionEnd