initial commit
This commit is contained in:
@@ -0,0 +1,987 @@
|
||||
; =============================================================================================
|
||||
; 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
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds LiveWall-Setup-<version>.exe from prebuilt/ with NSIS (makensis).
|
||||
#
|
||||
# ./installer/build-installer.sh # uses ../prebuilt
|
||||
# SOURCE_DIR=/abs/path ./installer/build-installer.sh
|
||||
#
|
||||
# On Debian/Ubuntu: sudo apt-get install nsis
|
||||
set -euo pipefail
|
||||
|
||||
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
root="$(dirname "$here")"
|
||||
csproj="$root/LiveWall/LiveWall.csproj"
|
||||
|
||||
version="$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' "$csproj" | head -1)"
|
||||
[ -n "$version" ] || { echo "Could not read <Version> from $csproj" >&2; exit 1; }
|
||||
|
||||
# NSIS VIProductVersion wants four numbers.
|
||||
version4="$version"
|
||||
case "$(awk -F. '{print NF}' <<< "$version")" in
|
||||
1) version4="$version.0.0.0" ;;
|
||||
2) version4="$version.0.0" ;;
|
||||
3) version4="$version.0" ;;
|
||||
esac
|
||||
|
||||
source_dir="${SOURCE_DIR:-$root/prebuilt}"
|
||||
[ -f "$source_dir/LiveWall.exe" ] || { echo "No published build in $source_dir" >&2; exit 1; }
|
||||
|
||||
command -v makensis >/dev/null || { echo "makensis not found - install the 'nsis' package" >&2; exit 1; }
|
||||
|
||||
# Optional: COMPRESS=zlib|lzma|lzma-solid (default lzma-solid), OUT_SUFFIX, SIGNTOOL
|
||||
compress="${COMPRESS:-lzma-solid}"
|
||||
suffix="${OUT_SUFFIX:-}"
|
||||
extra=()
|
||||
[ -n "${SIGNTOOL:-}" ] && extra+=(-DSIGNTOOL="$SIGNTOOL")
|
||||
[ -n "$suffix" ] && extra+=(-DOUT_SUFFIX="$suffix")
|
||||
|
||||
echo "Building LiveWall $version installer from $source_dir (compressor: $compress)"
|
||||
makensis -V2 \
|
||||
-DVERSION="$version" \
|
||||
-DVERSION4="$version4" \
|
||||
-DSOURCE_DIR="$source_dir" \
|
||||
-DCOMPRESS="$compress" \
|
||||
"${extra[@]}" \
|
||||
"$here/LiveWall.nsi"
|
||||
|
||||
out="$root/LiveWall-Setup-$version$suffix.exe"
|
||||
ls -lh "$out"
|
||||
echo
|
||||
echo "sha256 $(sha256sum "$out" | cut -d' ' -f1) $(basename "$out")"
|
||||
[ -n "${SIGNTOOL:-}" ] || echo "note not signed - see docs/antivirus.md"
|
||||
@@ -0,0 +1,123 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
Signs the LiveWall binaries and installer with Authenticode.
|
||||
|
||||
Why: an unsigned download is the single biggest reason antivirus engines and SmartScreen treat a
|
||||
small installer as suspicious. Signing does not guarantee a clean scan, but it is what the vendors
|
||||
ask for when they answer a false-positive report, and it fixes SmartScreen's "unknown publisher".
|
||||
|
||||
You need a code-signing certificate:
|
||||
* EV certificate - immediate SmartScreen/reputation trust, hardware token, ~$300-500/year.
|
||||
* OV certificate - works, but reputation builds up over downloads and weeks.
|
||||
* Self-signed - useless for this. Do not bother: an untrusted signature is no better than none.
|
||||
|
||||
Since June 2023 all public code-signing keys must live on FIPS 140-2 hardware, so most setups mean
|
||||
a token/HSM in the machine and the certificate visible in the store:
|
||||
|
||||
.\installer\sign.ps1 -Thumbprint 1A2B3C... # cert from the Windows certificate store
|
||||
.\installer\sign.ps1 -Pfx .\codesign.pfx # file-based cert (asks for the password)
|
||||
.\installer\sign.ps1 -Thumbprint ... -WhatIf # show the commands only
|
||||
|
||||
Everything is signed with SHA-256 and an RFC 3161 timestamp, so the signature stays valid after the
|
||||
certificate expires.
|
||||
#>
|
||||
param(
|
||||
[string]$Thumbprint,
|
||||
[string]$Pfx,
|
||||
[SecureString]$Password,
|
||||
[string]$TimestampUrl = 'http://timestamp.digicert.com',
|
||||
[string]$Signtool,
|
||||
[switch]$WhatIf
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = $PSScriptRoot | Split-Path -Parent
|
||||
|
||||
function Find-Signtool {
|
||||
if ($Signtool) { return $Signtool }
|
||||
|
||||
$cmd = Get-Command signtool.exe -ErrorAction SilentlyContinue
|
||||
if ($cmd) { return $cmd.Source }
|
||||
|
||||
$roots = @(
|
||||
"${env:ProgramFiles(x86)}\Windows Kits\10\bin",
|
||||
"$env:ProgramFiles\Windows Kits\10\bin"
|
||||
) | Where-Object { Test-Path $_ }
|
||||
|
||||
foreach ($r in $roots) {
|
||||
$candidate = Get-ChildItem $r -Recurse -Filter signtool.exe -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.FullName -match 'x64' } |
|
||||
Sort-Object FullName -Descending |
|
||||
Select-Object -First 1
|
||||
if ($candidate) { return $candidate.FullName }
|
||||
}
|
||||
|
||||
throw 'signtool.exe not found. Install the Windows SDK ("Windows SDK Signing Tools") or pass -Signtool <path>.'
|
||||
}
|
||||
|
||||
$signtool = Find-Signtool
|
||||
|
||||
# Files that end up on the user's machine. LiveWall.exe and the SDK matter most: those are what an
|
||||
# engine inspects after the installer has run.
|
||||
$files = @(
|
||||
(Join-Path $root 'prebuilt\LiveWall.exe'),
|
||||
(Join-Path $root 'prebuilt\LiveWall.dll'),
|
||||
(Join-Path $root 'prebuilt\LiveWall.Sdk.dll'),
|
||||
(Join-Path $root 'prebuilt\WebView2Loader.dll')
|
||||
) | Where-Object { Test-Path $_ }
|
||||
|
||||
$installers = Get-ChildItem $root -Filter 'LiveWall-Setup-*.exe' |
|
||||
Where-Object { $_.Name -notlike '*-old*' }
|
||||
|
||||
if (-not $files -and -not $installers) {
|
||||
throw "Nothing to sign: publish first (.\build.ps1 -Task publish) and build the installer."
|
||||
}
|
||||
|
||||
if ($Pfx) {
|
||||
if (-not (Test-Path $Pfx)) { throw "Certificate file not found: $Pfx" }
|
||||
if (-not $Password) {
|
||||
$Password = Read-Host -AsSecureString "Password for $Pfx"
|
||||
}
|
||||
$plain = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
|
||||
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password))
|
||||
$certArgs = @('/f', (Resolve-Path $Pfx).Path, '/p', $plain)
|
||||
}
|
||||
elseif ($Thumbprint) {
|
||||
$certArgs = @('/sha1', ($Thumbprint -replace '\s', ''))
|
||||
}
|
||||
else {
|
||||
throw 'Pass -Thumbprint <sha1 of the code-signing cert> or -Pfx <file>. Run with -WhatIf to preview.'
|
||||
}
|
||||
|
||||
Write-Host "signtool : $signtool"
|
||||
Write-Host "files : $($files.Count) binaries, $($installers.Count) installer(s)"
|
||||
Write-Host ''
|
||||
|
||||
foreach ($file in @($files) + @($installers.FullName)) {
|
||||
$signArgs = @('sign', '/fd', 'sha256', '/tr', $TimestampUrl, '/td', 'sha256') + $certArgs + @($file)
|
||||
|
||||
if ($WhatIf) {
|
||||
Write-Host "would run: signtool $($signArgs -join ' ')"
|
||||
continue
|
||||
}
|
||||
|
||||
Write-Host "signing $([IO.Path]::GetFileName($file))"
|
||||
& $signtool @signArgs
|
||||
if ($LASTEXITCODE -ne 0) { throw "signtool failed for $file (exit $LASTEXITCODE)" }
|
||||
}
|
||||
|
||||
if (-not $WhatIf) {
|
||||
Write-Host ''
|
||||
Write-Host 'Verifying…'
|
||||
foreach ($file in @($files) + @($installers.FullName)) {
|
||||
& $signtool verify /pa /q $file
|
||||
if ($LASTEXITCODE -ne 0) { throw "Signature verification failed for $file" }
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host 'Signed and verified. Rebuild the hashes for your release notes:' -ForegroundColor Green
|
||||
foreach ($file in @($files) + @($installers.FullName)) {
|
||||
$hash = (Get-FileHash $file -Algorithm SHA256).Hash.ToLower()
|
||||
Write-Host (" {0} {1}" -f $hash, [IO.Path]::GetFileName($file))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user