initial: nsSplashPNG plugin 1.0.0
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.{yml,yaml,json}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.{cmd,bat,ps1}]
|
||||||
|
end_of_line = crlf
|
||||||
|
|
||||||
|
[*.{nsi,nsh,sln,vcxproj,filters}]
|
||||||
|
end_of_line = crlf
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Normalizzazione line endings
|
||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
# File di codice
|
||||||
|
*.c text eol=lf
|
||||||
|
*.cpp text eol=lf
|
||||||
|
*.h text eol=lf
|
||||||
|
*.hpp text eol=lf
|
||||||
|
*.def text eol=lf
|
||||||
|
*.py text eol=lf
|
||||||
|
*.ps1 text eol=lf
|
||||||
|
*.cmd text eol=crlf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
|
||||||
|
# NSIS
|
||||||
|
*.nsi text eol=crlf
|
||||||
|
*.nsh text eol=crlf
|
||||||
|
|
||||||
|
# Markdown / docs / config
|
||||||
|
*.md text eol=lf
|
||||||
|
*.yml text eol=lf
|
||||||
|
*.yaml text eol=lf
|
||||||
|
*.json text eol=lf
|
||||||
|
*.txt text eol=lf
|
||||||
|
*.xml text eol=lf
|
||||||
|
|
||||||
|
# Visual Studio (CRLF richiesto)
|
||||||
|
*.sln text eol=crlf
|
||||||
|
*.vcxproj text eol=crlf merge=union
|
||||||
|
*.filters text eol=crlf merge=union
|
||||||
|
*.rc text eol=crlf working-tree-encoding=UTF-16LE
|
||||||
|
|
||||||
|
# Binari
|
||||||
|
*.dll binary
|
||||||
|
*.exe binary
|
||||||
|
*.lib binary
|
||||||
|
*.obj binary
|
||||||
|
*.pdb binary
|
||||||
|
*.zip binary
|
||||||
|
*.7z binary
|
||||||
|
*.png binary
|
||||||
|
*.ico binary
|
||||||
|
*.gif binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.bmp binary
|
||||||
|
*.pdf binary
|
||||||
|
|
||||||
|
# Linguistic detection (GitHub Linguist / Gitea analogo)
|
||||||
|
*.nsi linguist-language=NSIS
|
||||||
|
*.nsh linguist-language=NSIS
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Segnala un bug riproducibile
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels: ["bug", "triage"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Grazie per la segnalazione. Compila i campi sotto per aiutarci a riprodurre il problema.
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: plugin-version
|
||||||
|
attributes:
|
||||||
|
label: Versione plugin
|
||||||
|
description: Contenuto del file VERSION o tag della release scaricata
|
||||||
|
placeholder: "1.6.0"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: architecture
|
||||||
|
attributes:
|
||||||
|
label: Architettura DLL usata
|
||||||
|
options:
|
||||||
|
- x86-ansi
|
||||||
|
- x86-unicode
|
||||||
|
- amd64-unicode
|
||||||
|
- non so / multiple
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: nsis-version
|
||||||
|
attributes:
|
||||||
|
label: Versione NSIS
|
||||||
|
placeholder: "3.10"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: Sistema operativo
|
||||||
|
placeholder: "Windows 11 23H2 x64"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: repro
|
||||||
|
attributes:
|
||||||
|
label: Passi per riprodurre
|
||||||
|
description: Script .nsi minimale che riproduce il problema
|
||||||
|
render: nsis
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected
|
||||||
|
attributes:
|
||||||
|
label: Comportamento atteso
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: actual
|
||||||
|
attributes:
|
||||||
|
label: Comportamento osservato
|
||||||
|
description: Includi messaggi di errore esatti, output del log NSIS, screenshot
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: Note aggiuntive
|
||||||
|
description: Qualunque altra informazione utile (workaround tentati, ecc.)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Domande generali
|
||||||
|
url: https://gitea.emulab.it/Simone/nsis-plugin-nssplashpng/issues?type=question
|
||||||
|
about: Per domande di utilizzo che non sono bug, apri una issue con label "question"
|
||||||
|
- name: Security
|
||||||
|
url: https://gitea.emulab.it/Simone/nsis-plugin-nssplashpng/security/advisories/new
|
||||||
|
about: Segnala vulnerabilità di security in privato (non aprire issue pubbliche)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Suggerisci una nuova funzionalità o miglioramento
|
||||||
|
title: "[Feature]: "
|
||||||
|
labels: ["enhancement"]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Problema / Caso d'uso
|
||||||
|
description: Quale problema vorresti risolvere? Quale workflow vuoi abilitare?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: proposal
|
||||||
|
attributes:
|
||||||
|
label: Proposta
|
||||||
|
description: Come dovrebbe funzionare? Includi esempio di API/uso negli script NSIS se possibile.
|
||||||
|
render: nsis
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Alternative considerate
|
||||||
|
description: Altri approcci che hai valutato e perché sono stati scartati
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: Note aggiuntive
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
## Descrizione
|
||||||
|
|
||||||
|
<!-- Descrivi cosa cambia questa PR e perché. Riferisci issue se applicabile (Closes #XXX). -->
|
||||||
|
|
||||||
|
## Tipo di Modifica
|
||||||
|
|
||||||
|
- [ ] Bug fix (modifica non breaking che risolve un problema)
|
||||||
|
- [ ] Feature (modifica non breaking che aggiunge funzionalità)
|
||||||
|
- [ ] Breaking change (fix o feature che rompe la compatibilità)
|
||||||
|
- [ ] Documentazione
|
||||||
|
- [ ] Build / CI
|
||||||
|
|
||||||
|
## Architetture Testate
|
||||||
|
|
||||||
|
- [ ] `x86-ansi`
|
||||||
|
- [ ] `x86-unicode`
|
||||||
|
- [ ] `amd64-unicode`
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
- [ ] Build completa senza warning: `python build_plugin.py`
|
||||||
|
- [ ] CHANGELOG.md aggiornato (sezione `[Unreleased]`)
|
||||||
|
- [ ] README / docs aggiornati se l'API è cambiata
|
||||||
|
- [ ] Esempi aggiornati se l'API è cambiata
|
||||||
|
- [ ] Conventional Commits rispettato
|
||||||
|
- [ ] Nessun file in `dist/` o `__pycache__/` committato
|
||||||
|
- [ ] Self-review completata
|
||||||
|
|
||||||
|
## Note Aggiuntive
|
||||||
|
|
||||||
|
<!-- Eventuali considerazioni per i reviewer, screenshot, output di test, ecc. -->
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# Build (GitHub) — pipeline CI primaria
|
||||||
|
#
|
||||||
|
# Strategia hosting:
|
||||||
|
# - Repo primario su Gitea (push iniziale)
|
||||||
|
# - Push mirror automatico Gitea -> GitHub
|
||||||
|
# - GitHub esegue TUTTA la CI (Gitea non esegue Actions)
|
||||||
|
#
|
||||||
|
# Trigger:
|
||||||
|
# - push su main (incluso quello propagato dal mirror Gitea)
|
||||||
|
# - PR aperte su GitHub
|
||||||
|
# - run manuale via UI
|
||||||
|
|
||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md'
|
||||||
|
- 'docs/**'
|
||||||
|
- 'examples/**'
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: windows-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
config: [x86-ansi, x86-unicode, x64-unicode]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Setup MSBuild
|
||||||
|
uses: microsoft/setup-msbuild@v2
|
||||||
|
|
||||||
|
- name: Read VERSION
|
||||||
|
id: version
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$v = (Get-Content VERSION -Raw).Trim()
|
||||||
|
"version=$v" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||||
|
|
||||||
|
- name: Build ${{ matrix.config }}
|
||||||
|
run: python build_plugin.py --config ${{ matrix.config }} --verbosity minimal
|
||||||
|
|
||||||
|
- name: Upload DLL
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ github.event.repository.name }}-${{ steps.version.outputs.version }}-${{ matrix.config }}
|
||||||
|
path: dist/**
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 30
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# Release (GitHub) — pipeline release primaria
|
||||||
|
#
|
||||||
|
# Triggerata quando un tag v* viene propagato dal mirror Gitea.
|
||||||
|
# Costruisce gli artifact per tutte le architetture e crea la GitHub Release.
|
||||||
|
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ['v*']
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: 'Tag versione (es. v1.7.0)'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: windows-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
config: [x86-ansi, x86-unicode, x64-unicode]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- uses: microsoft/setup-msbuild@v2
|
||||||
|
|
||||||
|
- name: Build ${{ matrix.config }}
|
||||||
|
run: python build_plugin.py --config ${{ matrix.config }}
|
||||||
|
|
||||||
|
- name: Determine version & arch
|
||||||
|
id: meta
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$tag = "${{ github.ref_name }}"
|
||||||
|
if ([string]::IsNullOrEmpty($tag) -or $tag -eq "main") { $tag = "${{ inputs.tag }}" }
|
||||||
|
$v = $tag -replace '^v',''
|
||||||
|
$map = @{ 'x86-ansi' = 'x86-ansi'; 'x86-unicode' = 'x86-unicode'; 'x64-unicode' = 'amd64-unicode' }
|
||||||
|
$dir = $map['${{ matrix.config }}']
|
||||||
|
echo "version=$v" >> $env:GITHUB_OUTPUT
|
||||||
|
echo "tag=$tag" >> $env:GITHUB_OUTPUT
|
||||||
|
echo "dir=$dir" >> $env:GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Package
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$name = "${{ github.event.repository.name }}-${{ steps.meta.outputs.version }}-${{ steps.meta.outputs.dir }}"
|
||||||
|
$stage = "stage/$name"
|
||||||
|
New-Item -ItemType Directory -Force -Path $stage | Out-Null
|
||||||
|
Copy-Item "dist/${{ steps.meta.outputs.dir }}/*" $stage -Recurse
|
||||||
|
Copy-Item README.md, LICENSE, VERSION, CHANGELOG.md $stage
|
||||||
|
if (Test-Path "include") { Copy-Item "include" $stage -Recurse }
|
||||||
|
if (Test-Path "examples") { Copy-Item "examples" $stage -Recurse }
|
||||||
|
Compress-Archive -Path "$stage/*" -DestinationPath "$name.zip" -Force
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: pkg-${{ matrix.config }}
|
||||||
|
path: '*.zip'
|
||||||
|
|
||||||
|
publish:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: SHA256SUMS
|
||||||
|
run: |
|
||||||
|
cd artifacts
|
||||||
|
sha256sum *.zip > SHA256SUMS.txt
|
||||||
|
cat SHA256SUMS.txt
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
artifacts/*.zip
|
||||||
|
artifacts/SHA256SUMS.txt
|
||||||
|
generate_release_notes: true
|
||||||
|
body: |
|
||||||
|
**Repository primario**: vedi link Gitea nel README.
|
||||||
|
|
||||||
|
Il mirror GitHub è generato automaticamente dal repo Gitea (sorgente di verità).
|
||||||
|
Per issue e PR aprire ticket sul repository primario Gitea.
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
# Build output (src/ subdirs only — dist/ is tracked, contains pre-built DLLs)
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
Build/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Visual Studio
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
|
*.ncb
|
||||||
|
*.sdf
|
||||||
|
.vs/
|
||||||
|
ipch/
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
*.opendb
|
||||||
|
*.aps
|
||||||
|
*.tlog
|
||||||
|
*.idb
|
||||||
|
*.pdb
|
||||||
|
*.ilk
|
||||||
|
*.exp
|
||||||
|
*.lib
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.pytest_cache/
|
||||||
|
*.egg-info/
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
|
||||||
|
# Editor / IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
Thumbs.db
|
||||||
|
desktop.ini
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# NSIS
|
||||||
|
*.nsh.bak
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
.logs/
|
||||||
|
|
||||||
|
# Sensitive
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
|
||||||
|
# Migration/legacy scripts (temporary, not part of the plugin)
|
||||||
|
tools/t*.py
|
||||||
|
tools/check_*.py
|
||||||
|
tools/legacy/
|
||||||
|
|
||||||
|
# MSBuild output directories in src/ (build artifacts, not source)
|
||||||
|
src/**/Release/
|
||||||
|
src/**/Release Unicode/
|
||||||
|
src/**/x64/
|
||||||
|
*.recipe
|
||||||
|
*.iobj
|
||||||
|
*.ipdb
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
Tutte le modifiche rilevanti a questo progetto sono documentate in questo file.
|
||||||
|
|
||||||
|
Il formato segue [Keep a Changelog](https://keepachangelog.com/it/1.0.0/),
|
||||||
|
e il progetto aderisce al [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.0.0] — 2026-04-29
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Prima release come repository indipendente
|
||||||
|
- Build script unificato (`build_plugin.py`) con CLI canonica
|
||||||
|
- CI/CD via GitHub Actions (mirror automatico Gitea → GitHub)
|
||||||
|
- Documentazione completa (README, CONTRIBUTING, SECURITY)
|
||||||
|
|
||||||
|
[Unreleased]: https://gitea.emulab.it/Simone/nsis-plugin-nssplashpng/compare/v1.0.0...HEAD
|
||||||
|
[1.0.0]: https://gitea.emulab.it/Simone/nsis-plugin-nssplashpng/releases/tag/v1.0.0
|
||||||
+137
@@ -0,0 +1,137 @@
|
|||||||
|
# Contribuire a nsSplashPNG
|
||||||
|
|
||||||
|
Grazie per il tuo interesse a contribuire!
|
||||||
|
|
||||||
|
> Repository primario: [https://gitea.emulab.it/Simone/nsis-plugin-nssplashpng](https://gitea.emulab.it/Simone/nsis-plugin-nssplashpng)
|
||||||
|
> Mirror GitHub: [https://github.com/systempal/nsis-plugin-nssplashpng](https://github.com/systempal/nsis-plugin-nssplashpng) (read-only — apri PR su Gitea)
|
||||||
|
|
||||||
|
## Prerequisiti
|
||||||
|
|
||||||
|
| Strumento | Versione | Note |
|
||||||
|
|-----------|----------|------|
|
||||||
|
| Visual Studio | 2022 / 2026 | Workload: Desktop development with C++ (toolset v143/v145) |
|
||||||
|
| Python | 3.7+ | Per `build_plugin.py` |
|
||||||
|
| Git | 2.30+ | Con supporto submoduli se applicabile |
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git clone https://gitea.emulab.it/Simone/nsis-plugin-nssplashpng.git
|
||||||
|
cd nsis-plugin-nssplashpng
|
||||||
|
python build_plugin.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Output in `dist/{x86-ansi,x86-unicode,amd64-unicode}/`.
|
||||||
|
|
||||||
|
## Workflow Sviluppo
|
||||||
|
|
||||||
|
### 1. Branch
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git checkout -b feat/nome-feature # nuova feature
|
||||||
|
git checkout -b fix/descrizione-bug # bugfix
|
||||||
|
git checkout -b docs/aggiornamento # solo doc
|
||||||
|
git checkout -b chore/manutenzione # build, dipendenze
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Commit
|
||||||
|
|
||||||
|
Segui [Conventional Commits](https://www.conventionalcommits.org/it/v1.0.0/):
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: aggiunge supporto per formato XYZ
|
||||||
|
fix: corregge crash su Windows 11 ARM
|
||||||
|
docs: aggiorna esempio di utilizzo
|
||||||
|
chore: aggiorna toolset MSVC
|
||||||
|
refactor: estrae logica parsing in funzione separata
|
||||||
|
test: aggiunge test per edge case
|
||||||
|
build: aggiorna script di build
|
||||||
|
ci: aggiorna workflow GitHub Actions
|
||||||
|
```
|
||||||
|
|
||||||
|
Breaking changes:
|
||||||
|
|
||||||
|
```
|
||||||
|
feat!: rinomina API Foo() in Bar()
|
||||||
|
|
||||||
|
BREAKING CHANGE: Foo() rinominata in Bar(); aggiornare tutti i chiamanti.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Pull Request
|
||||||
|
|
||||||
|
PR aperte verso il branch `main` su Gitea. Verifica:
|
||||||
|
|
||||||
|
- [ ] Build completa senza warning per tutte le architetture: `python build_plugin.py`
|
||||||
|
- [ ] Esempi aggiornati se cambia l'API
|
||||||
|
- [ ] `CHANGELOG.md` aggiornato (sezione `[Unreleased]`)
|
||||||
|
- [ ] Documentazione aggiornata (README, docs/API.md se esiste)
|
||||||
|
- [ ] Conventional Commits rispettato
|
||||||
|
- [ ] Nessun file in `dist/` o `__pycache__/` committato
|
||||||
|
|
||||||
|
## Build Script
|
||||||
|
|
||||||
|
```
|
||||||
|
python build_plugin.py [opzioni]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Opzione | Default | Descrizione |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| `--config` | `all` | Architettura: `x86-ansi`, `x86-unicode`, `x64-unicode`, `all` |
|
||||||
|
| `--toolset` | `auto` | Visual Studio: `2022`, `2026`, `auto` (rileva il più recente) |
|
||||||
|
| `--jobs` | numero CPU | Thread paralleli per la build |
|
||||||
|
| `--clean` | false | Pulisce `dist/` prima di compilare |
|
||||||
|
| `--install-dir` | none | Copia DLL anche in `<dir>/{arch}/` (es. NSIS plugin folder) |
|
||||||
|
| `--verbose` | false | Output dettagliato (mostra comandi MSBuild) |
|
||||||
|
|
||||||
|
Exit codes:
|
||||||
|
- `0` — Successo
|
||||||
|
- `1` — Errore di build
|
||||||
|
- `2` — Prerequisiti mancanti
|
||||||
|
- `3` — Argomenti invalidi
|
||||||
|
|
||||||
|
## Release Workflow
|
||||||
|
|
||||||
|
Le release sono automatiche via tag Git:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 1. Aggiorna VERSION e CHANGELOG.md
|
||||||
|
echo "1.7.0" > VERSION
|
||||||
|
# (modifica CHANGELOG.md spostando entry da [Unreleased] a [1.7.0])
|
||||||
|
|
||||||
|
# 2. Commit
|
||||||
|
git add VERSION CHANGELOG.md
|
||||||
|
git commit -m "chore(release): v1.7.0"
|
||||||
|
|
||||||
|
# 3. Tag annotato
|
||||||
|
git tag -a v1.7.0 -m "Release v1.7.0"
|
||||||
|
|
||||||
|
# 4. Push
|
||||||
|
git push origin main
|
||||||
|
git push origin v1.7.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Il workflow `.github/workflows/release.yml` compilerà tutte le architetture
|
||||||
|
e creerà la release su Gitea con gli artifact ZIP allegati. Il mirror GitHub
|
||||||
|
riceverà automaticamente il tag.
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
- C/C++: stile coerente con il file modificato
|
||||||
|
- Python: PEP 8 (4 spazi indent, line length flessibile)
|
||||||
|
- Header file: `#include` ordinati: stdlib → Win32 → progetto
|
||||||
|
- Encoding: UTF-8 senza BOM (eccetto `.rc` che richiede UTF-16 LE)
|
||||||
|
- Line endings: LF in repo (`.gitattributes` normalizza)
|
||||||
|
|
||||||
|
## Reporting Bug
|
||||||
|
|
||||||
|
Apri una [issue](https://gitea.emulab.it/Simone/nsis-plugin-nssplashpng/issues/new) usando il template "Bug Report".
|
||||||
|
Includi:
|
||||||
|
- Versione plugin e architettura DLL usata
|
||||||
|
- Versione NSIS
|
||||||
|
- Sistema operativo
|
||||||
|
- Script `.nsi` minimale che riproduce il problema
|
||||||
|
- Output / errore esatto
|
||||||
|
|
||||||
|
## Domande
|
||||||
|
|
||||||
|
Per domande generali, apri una [issue](https://gitea.emulab.it/Simone/nsis-plugin-nssplashpng/issues) con label `question`.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Simone
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,348 @@
|
|||||||
|
# nsSplashPNG - PNG Splash Screen Plugin for NSIS
|
||||||
|
|
||||||
|
A modern NSIS plugin that displays splash screens with full alpha transparency support using WIC (Windows Imaging Component).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Multiple Image Formats**: PNG, JPEG, BMP, GIF, TIFF, ICO via WIC decoder
|
||||||
|
- **Full Alpha Transparency**: Proper PNG/GIF alpha channel support via WIC and layered windows
|
||||||
|
- **Configurable Fade Speed**: Customizable fade in/out speed (1-255 steps, 30ms-7650ms duration)
|
||||||
|
- **Non-Blocking Execution**: Script continues after fade-in while splash stays visible
|
||||||
|
- **Auto-Close Timer**: Configurable automatic close after specified milliseconds
|
||||||
|
- **Click-to-Close**: Optional user interaction to close splash
|
||||||
|
- **No Cancel Option**: Prevent users from closing splash during critical operations
|
||||||
|
- **Manual Control**: Programmatic show/stop with fade effects
|
||||||
|
- **Multi-Monitor Support**: Center splash on primary, current (mouse), specific index, or nearest to a screen coordinate
|
||||||
|
- **Zero Warnings**: Clean compilation with Visual Studio 2022
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Copy `nssplashpng.dll` to your NSIS plugins directory:
|
||||||
|
- `x86-unicode`: For 32-bit NSIS Unicode builds
|
||||||
|
- `x64-unicode`: For 64-bit NSIS Unicode builds
|
||||||
|
- `x86-ansi`: For 32-bit NSIS ANSI builds
|
||||||
|
|
||||||
|
2. Include the plugin directory in your script:
|
||||||
|
```nsis
|
||||||
|
!addplugindir "path\to\plugins\x86-unicode"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building from Source
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Visual Studio 2022 or later with C++ build tools
|
||||||
|
- Python 3.x
|
||||||
|
|
||||||
|
### Build Steps
|
||||||
|
```powershell
|
||||||
|
cd nsSplashPNG
|
||||||
|
python build_plugin.py # Build all architectures
|
||||||
|
python build_plugin.py --config x86-unicode # Single architecture (x86-ansi|x86-unicode|x64-unicode|all)
|
||||||
|
python build_plugin.py --vs-version 2026 # Specific VS version (2022|2026|auto)
|
||||||
|
python build_plugin.py --clean # Clean dist/ before build
|
||||||
|
python build_plugin.py --install-dir "C:\NSIS\Plugins" # Copy to additional NSIS directory
|
||||||
|
python build_plugin.py --verbosity minimal # MSBuild verbosity (quiet|minimal|normal|detailed|diagnostic)
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will:
|
||||||
|
- Build for x86-unicode, x64-unicode, and x86-ansi configurations
|
||||||
|
- Copy DLLs to `dist/{x86-unicode|amd64-unicode|x86-ansi}/` directories
|
||||||
|
- Clean up intermediate build artifacts from `src/Build/`
|
||||||
|
- Supports parallel builds with automatic CPU optimization
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Important: Plugin Memory Management
|
||||||
|
|
||||||
|
**CRITICAL**: Always use `/NOUNLOAD` flag on the first `show` call to keep the plugin loaded in memory. Without this, NSIS unloads the plugin after each call, losing all state (window handles, threads, etc.), causing `stop` to fail.
|
||||||
|
|
||||||
|
```nsis
|
||||||
|
; Correct - keeps plugin loaded
|
||||||
|
nssplashpng::show /NOUNLOAD 5000 /FADEIN /FADEOUT "$TEMP\splash.png"
|
||||||
|
nssplashpng::stop /FADEOUT
|
||||||
|
|
||||||
|
; Wrong - plugin unloaded after show, stop will fail
|
||||||
|
nssplashpng::show 5000 /FADEIN /FADEOUT "$TEMP\splash.png"
|
||||||
|
nssplashpng::stop /FADEOUT
|
||||||
|
```
|
||||||
|
|
||||||
|
### Syntax
|
||||||
|
|
||||||
|
```nsis
|
||||||
|
nsSplashPNG::show [/NOUNLOAD] <milliseconds> [/FADEIN [step]] [/FADEOUT [step]] [/NOCANCEL] [/MONITOR <target>] <image_path>
|
||||||
|
nsSplashPNG::stop [/FADEOUT [step]]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: The timeout must be specified immediately after `/NOUNLOAD` (if present), followed by optional flags, with the image path as the last parameter.
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
#### show function
|
||||||
|
- `/NOUNLOAD` - **Required for stop() to work**: Keeps plugin in memory between calls
|
||||||
|
- `/FADEIN [step]` - Enable fade in effect. Optional step (1-255) controls speed (default: 15)
|
||||||
|
- Higher step = faster fade (255=instant, 51=fast, 15=normal, 5=slow)
|
||||||
|
- Duration formula: `(255 / step) × 30ms`
|
||||||
|
- `/FADEOUT [step]` - Enable fade out effect when auto-closing via timer (optional step)
|
||||||
|
- `/NOCANCEL` - Disable click-to-close (splash can only be closed programmatically or by timer)
|
||||||
|
- `/MONITOR <target>` - Select the monitor on which to center the splash (default: primary monitor)
|
||||||
|
- `PRIMARY` - Primary monitor (default)
|
||||||
|
- `CURRENT` or `MOUSE` - Monitor where the mouse cursor is
|
||||||
|
- `1`, `2`, `3`, ... - Monitor by 1-based index (enumeration order)
|
||||||
|
- `POINT x y` - Monitor nearest to the given screen coordinates (uses `MONITOR_DEFAULTTONEAREST`); useful to target the monitor where an application window was last open
|
||||||
|
- `<milliseconds>` - Auto-close timer in milliseconds (0 = manual close only)
|
||||||
|
- `<image_path>` - Path to image file (PNG, JPEG, BMP, GIF, TIFF, ICO)
|
||||||
|
|
||||||
|
#### stop function
|
||||||
|
- `/FADEOUT [step]` - Enable fade out effect when closing. Optional step (1-255) controls speed (default: 15)
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
#### Basic Usage
|
||||||
|
```nsis
|
||||||
|
; Show splash for 3 seconds (PNG with alpha) - no /NOUNLOAD needed for auto-close only
|
||||||
|
nsSplashPNG::show 3000 "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Show JPEG splash (opaque, no transparency)
|
||||||
|
nsSplashPNG::show 3000 "$EXEDIR\splash.jpg"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fade Effects
|
||||||
|
```nsis
|
||||||
|
; Default fade speed (step 15, ~510ms)
|
||||||
|
nsSplashPNG::show 3000 /FADEIN /FADEOUT "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Fast fade (step 51, ~150ms)
|
||||||
|
nsSplashPNG::show 3000 /FADEIN 51 /FADEOUT 51 "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Slow fade (step 5, ~1530ms)
|
||||||
|
nsSplashPNG::show 5000 /FADEIN 5 /FADEOUT 5 "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Instant (step 255, ~30ms)
|
||||||
|
nsSplashPNG::show 3000 /FADEIN 255 /FADEOUT 255 "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Different speeds for fade-in and fade-out
|
||||||
|
nsSplashPNG::show 3000 /FADEIN 51 /FADEOUT 5 "$EXEDIR\splash.png"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Manual Control
|
||||||
|
```nsis
|
||||||
|
; CRITICAL: Use /NOUNLOAD when using stop() to close manually
|
||||||
|
nsSplashPNG::show /NOUNLOAD 0 /NOCANCEL "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; ... do installation work ...
|
||||||
|
|
||||||
|
; Close with fade out
|
||||||
|
nsSplashPNG::stop /FADEOUT
|
||||||
|
|
||||||
|
; Or close with custom fade speed
|
||||||
|
nsSplashPNG::stop /FADEOUT 51
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Real Installer Example
|
||||||
|
```nsis
|
||||||
|
Name "My Application"
|
||||||
|
OutFile "setup.exe"
|
||||||
|
InstallDir "$PROGRAMFILES\MyApp"
|
||||||
|
|
||||||
|
!addplugindir "plugins\x86-unicode"
|
||||||
|
|
||||||
|
Page instfiles
|
||||||
|
|
||||||
|
Section "Main Installation"
|
||||||
|
; Show splash with fade in, prevent user closing, no auto-close
|
||||||
|
; CRITICAL: /NOUNLOAD required for stop() to work
|
||||||
|
nsSplashPNG::show /NOUNLOAD 0 /NOCANCEL /FADEIN "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Install files
|
||||||
|
SetOutPath "$INSTDIR"
|
||||||
|
File /r "app\*.*"
|
||||||
|
|
||||||
|
; Create shortcuts
|
||||||
|
CreateDirectory "$SMPROGRAMS\MyApp"
|
||||||
|
CreateShortcut "$SMPROGRAMS\MyApp\MyApp.lnk" "$INSTDIR\myapp.exe"
|
||||||
|
|
||||||
|
; Close splash with fade out
|
||||||
|
nsSplashPNG::stop /FADEOUT
|
||||||
|
SectionEnd
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Monitor Selection
|
||||||
|
```nsis
|
||||||
|
; Show on primary monitor (default)
|
||||||
|
nsSplashPNG::show /NOUNLOAD 0 /NOCANCEL "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Show on the monitor where the mouse cursor is
|
||||||
|
nsSplashPNG::show /NOUNLOAD 0 /NOCANCEL /MONITOR CURRENT "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Show on the second monitor (by 1-based index)
|
||||||
|
nsSplashPNG::show /NOUNLOAD 0 /NOCANCEL /MONITOR 2 "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Show on the monitor nearest to a known screen coordinate
|
||||||
|
; (e.g. where an application window was previously displayed)
|
||||||
|
nsSplashPNG::show /NOUNLOAD 0 /MONITOR POINT -1906 200 /FADEIN "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
nsSplashPNG::stop /FADEOUT
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note on parameter order**: `/MONITOR POINT x y` must be placed **before** `/FADEIN`/`/FADEOUT`.
|
||||||
|
> The `/FADEIN` parser reads ahead one token to check for a custom step; if that token
|
||||||
|
> starts with `/`, it is requeued — but a subsequent `popstring()` in the loop will
|
||||||
|
> overwrite it before `/MONITOR` can be processed.
|
||||||
|
|
||||||
|
#### Additional Examples
|
||||||
|
|
||||||
|
See the `examples/` folder for more usage demonstrations:
|
||||||
|
- **example_basic.nsi** - Simple splash with auto-close timer
|
||||||
|
- **example_advanced.nsi** - Manual control with fade effects
|
||||||
|
- **example_formats.nsi** - Multi-format support (PNG, JPEG, BMP, GIF)
|
||||||
|
- **example_fade_speed.nsi** - Customizable fade speed demonstrations
|
||||||
|
- **example_nonblocking.nsi** - Non-blocking behavior with background processing
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
### Implementation
|
||||||
|
- **Graphics Library**: WIC (Windows Imaging Component) via windowscodecs.lib
|
||||||
|
- **Image Format**: 32bppPBGRA (pre-multiplied alpha) for proper transparency
|
||||||
|
- **Window Type**: Layered windows with `WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW`
|
||||||
|
- **Transparency**: `UpdateLayeredWindow` with per-pixel alpha blending
|
||||||
|
- **Rendering**: Hardware-accelerated via `UpdateLayeredWindow` with `ULW_ALPHA`
|
||||||
|
- **Animation**: 30ms timer intervals for smooth fade (0-255 alpha range)
|
||||||
|
- Configurable step size (default: 15 steps = ~510ms duration)
|
||||||
|
- Duration formula: `(255 / step) × 30ms`
|
||||||
|
- **Threading**: Background window thread for non-blocking execution
|
||||||
|
- `show()` returns after fade-in completes
|
||||||
|
- Window stays open in background
|
||||||
|
- `stop()` waits for fade-out to complete before returning
|
||||||
|
- **Plugin State**: Requires `/NOUNLOAD` to maintain global variables (window handles, threads) between calls
|
||||||
|
|
||||||
|
### Comparison with nsAdvsplash
|
||||||
|
|
||||||
|
| Feature | nsAdvsplash | nsSplashPNG |
|
||||||
|
|----------------------|-------------|-----------------|
|
||||||
|
| PNG Alpha Support | Partial | Full |
|
||||||
|
| Fade Effects | Yes | Yes |
|
||||||
|
| Transparency Method | Color key | Per-pixel alpha |
|
||||||
|
| Graphics API | GDI | WIC |
|
||||||
|
| Window Type | Standard | Layered |
|
||||||
|
| Click-to-Close | Yes | Yes |
|
||||||
|
| Compilation Warnings | Some | None |
|
||||||
|
|
||||||
|
### Supported Image Formats
|
||||||
|
|
||||||
|
WIC automatically detects and decodes the following formats:
|
||||||
|
- **PNG** - Full alpha transparency support
|
||||||
|
- **JPEG/JPG** - No transparency (opaque)
|
||||||
|
- **BMP** - Alpha transparency if 32-bit, otherwise opaque
|
||||||
|
- **GIF** - Basic transparency support
|
||||||
|
- **TIFF** - Alpha transparency if supported by variant
|
||||||
|
- **ICO** - Icon format with alpha
|
||||||
|
- **WMP** - Windows Media Photo
|
||||||
|
|
||||||
|
**Note**: Formats without alpha channels (JPEG, 24-bit BMP) will display as opaque images.
|
||||||
|
|
||||||
|
### System Requirements
|
||||||
|
- Windows Vista or later (for layered window support)
|
||||||
|
- WIC - Windows Imaging Component (included with Windows Vista+)
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Exported Functions
|
||||||
|
|
||||||
|
#### `show()`
|
||||||
|
Creates and displays a splash screen window in a background thread.
|
||||||
|
|
||||||
|
**Parameters** (parsed from NSIS stack in reverse order):
|
||||||
|
1. Timeout in milliseconds (integer, 0 = manual close only)
|
||||||
|
2. `/FADEIN [step]` - Optional fade in with configurable step (1-255, default: 15)
|
||||||
|
3. `/FADEOUT [step]` - Optional auto fade out with configurable step (1-255, default: 15)
|
||||||
|
4. `/NOCANCEL` - Disable click-to-close
|
||||||
|
5. `/MONITOR <target>` - Monitor selection (see parameter docs); must appear **before** `/FADEIN`
|
||||||
|
6. `/NOUNLOAD` - **Required for stop() to work**: Keeps plugin in memory
|
||||||
|
7. Image path (string) - last parameter
|
||||||
|
|
||||||
|
**Behavior**:
|
||||||
|
- Cleans up any previous window/thread
|
||||||
|
- Loads image with WIC (`IWICImagingFactory`, `IWICBitmapDecoder`)
|
||||||
|
- Supports PNG, JPEG, BMP, GIF, TIFF, ICO, WMP formats
|
||||||
|
- Converts to 32bppPBGRA format for proper alpha handling
|
||||||
|
- Creates centered topmost layered window on the selected monitor:
|
||||||
|
- `MONITOR_MODE_PRIMARY` (default) — `GetSystemMetrics(SM_CX/CYSCREEN)`
|
||||||
|
- `MONITOR_MODE_MOUSE` — `GetCursorPos` + `MonitorFromPoint`
|
||||||
|
- `MONITOR_MODE_INDEX` — `EnumDisplayMonitors` by 0-based index
|
||||||
|
- `MONITOR_MODE_POINT` — `MonitorFromPoint(x, y, MONITOR_DEFAULTTONEAREST)`
|
||||||
|
- Uses `UpdateLayeredWindow` for per-pixel alpha transparency
|
||||||
|
- Starts background thread with window message loop
|
||||||
|
- Applies fade in effect if `/FADEIN` specified (30ms timer with configurable steps)
|
||||||
|
- Sets auto-close timer if timeout > 0
|
||||||
|
- Enables click-to-close unless `/NOCANCEL` specified
|
||||||
|
- **Returns control after fade-in completes** (non-blocking)
|
||||||
|
- Without `/NOUNLOAD`, plugin unloads and loses all state
|
||||||
|
|
||||||
|
**Return value**: Pushes "success" to NSIS stack
|
||||||
|
|
||||||
|
#### `stop()`
|
||||||
|
Closes the splash screen window with optional fade-out.
|
||||||
|
|
||||||
|
**Parameters** (parsed from NSIS stack):
|
||||||
|
1. `/FADEOUT [step]` - Optional fade out with configurable step (1-255, default: 15)
|
||||||
|
|
||||||
|
**Behavior**:
|
||||||
|
- If `/FADEOUT` specified:
|
||||||
|
- Starts fade-out timer (30ms intervals)
|
||||||
|
- Waits for fade to complete: `(255 / step) × 30ms`
|
||||||
|
- Destroys window after fade completes
|
||||||
|
- Otherwise:
|
||||||
|
- Immediately destroys window
|
||||||
|
- **Requires show() to have used /NOUNLOAD** or window handle will be NULL
|
||||||
|
|
||||||
|
**Return value**: Pushes "success" to NSIS stack
|
||||||
|
|
||||||
|
**Behavior**:
|
||||||
|
- Applies fade out effect if `/FADEOUT` specified (30ms timer, 15 steps)
|
||||||
|
- Destroys window and releases WIC resources
|
||||||
|
- Terminates splash thread and cleans up handles
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Image not showing
|
||||||
|
- Verify image file path is correct and accessible
|
||||||
|
- Check that image file is in a supported format (PNG, JPEG, BMP, GIF, TIFF, ICO)
|
||||||
|
- Ensure NSIS can read the file at install time
|
||||||
|
- Verify WIC can decode the specific image file variant
|
||||||
|
|
||||||
|
### No transparency
|
||||||
|
- Confirm PNG has alpha channel (not just RGB)
|
||||||
|
- Verify you're using the correct plugin DLL for your NSIS version
|
||||||
|
- Check Windows version supports layered windows (Vista+)
|
||||||
|
|
||||||
|
### Fade effects not smooth
|
||||||
|
- Normal behavior - fade uses 30ms timer with 15-step alpha increments
|
||||||
|
- Total fade duration ≈ 450-500ms
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Created for NSIS installer system.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- NSIS Plugin API
|
||||||
|
- WIC - Windows Imaging Component
|
||||||
|
- Windows Layered Windows API (`UpdateLayeredWindow`)
|
||||||
|
- Build system inspired by nsProcess plugin
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*See [README_IT.md](README_IT.md) for the Italian version.*
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
|
||||||
|
### 1.0.0 (Current)
|
||||||
|
- Full PNG alpha transparency support via WIC
|
||||||
|
- Per-pixel alpha blending with `UpdateLayeredWindow`
|
||||||
|
- Fade in/out effects (30ms, 15 steps)
|
||||||
|
- Auto-close timer with configurable timeout
|
||||||
|
- Click-to-close with optional disable (`/NOCANCEL`)
|
||||||
|
- Blocks installer until splash closes
|
||||||
|
- x86-unicode, x64-unicode, and x86-ansi builds
|
||||||
|
- Zero compilation warnings with guarded macro definitions
|
||||||
|
- Optimized build system with parallel compilation support
|
||||||
+211
@@ -0,0 +1,211 @@
|
|||||||
|
# nsSplashPNG - Plugin NSIS per Splash Screen PNG
|
||||||
|
|
||||||
|
Plugin NSIS moderno che visualizza splash screen con supporto completo alla trasparenza alfa tramite WIC (Windows Imaging Component).
|
||||||
|
|
||||||
|
## Funzionalità
|
||||||
|
|
||||||
|
- **Formati Immagine Multipli**: PNG, JPEG, BMP, GIF, TIFF, ICO tramite WIC decoder
|
||||||
|
- **Trasparenza Alfa Completa**: Supporto canale alfa per PNG/GIF tramite WIC e layered windows
|
||||||
|
- **Velocità Fade Configurabile**: Velocità fade in/out personalizzabile (1-255 step, durata 30ms-7650ms)
|
||||||
|
- **Esecuzione Non Bloccante**: Lo script continua dopo il fade-in mentre lo splash rimane visibile
|
||||||
|
- **Timer Auto-Close**: Chiusura automatica configurabile dopo un numero specificato di millisecondi
|
||||||
|
- **Click per Chiudere**: Interazione utente opzionale per chiudere lo splash
|
||||||
|
- **No Cancel**: Impedisce all'utente di chiudere lo splash durante operazioni critiche
|
||||||
|
- **Controllo Manuale**: Show/stop programmabile con effetti fade
|
||||||
|
- **Supporto Multi-Monitor**: Centra lo splash sul monitor primario, corrente (mouse), per indice, o il più vicino a una coordinata
|
||||||
|
- **Zero Warning**: Compilazione pulita con Visual Studio 2022
|
||||||
|
|
||||||
|
## Installazione
|
||||||
|
|
||||||
|
1. Copia `nssplashpng.dll` nella cartella dei plugin NSIS:
|
||||||
|
- `x86-unicode`: Per build NSIS Unicode a 32-bit
|
||||||
|
- `x64-unicode`: Per build NSIS Unicode a 64-bit
|
||||||
|
- `x86-ansi`: Per build NSIS ANSI a 32-bit
|
||||||
|
|
||||||
|
2. Includi la cartella del plugin nello script:
|
||||||
|
```nsis
|
||||||
|
!addplugindir "path\to\plugins\x86-unicode"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compilazione dai Sorgenti
|
||||||
|
|
||||||
|
### Prerequisiti
|
||||||
|
- Visual Studio 2022 o successivo con strumenti C++
|
||||||
|
- Python 3.x
|
||||||
|
|
||||||
|
### Passaggi
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd nsSplashPNG
|
||||||
|
python build_plugin.py # Compila tutte le architetture
|
||||||
|
python build_plugin.py --config x86-unicode # Solo un'architettura (x86-ansi|x86-unicode|x64-unicode|all)
|
||||||
|
python build_plugin.py --vs-version 2026 # Versione VS specifica (2022|2026|auto)
|
||||||
|
python build_plugin.py --clean # Pulizia dist/ prima della build
|
||||||
|
python build_plugin.py --install-dir "C:\NSIS\Plugins" # Copia in directory NSIS aggiuntiva
|
||||||
|
python build_plugin.py --verbosity minimal # Verbosita MSBuild (quiet|minimal|normal|detailed|diagnostic)
|
||||||
|
```
|
||||||
|
|
||||||
|
Lo script:
|
||||||
|
- Compila per x86-unicode, x64-unicode e x86-ansi
|
||||||
|
- Copia le DLL in `dist/{x86-unicode|amd64-unicode|x86-ansi}/`
|
||||||
|
- Pulisce i file intermedi dalla cartella `src/Build/`
|
||||||
|
- Supporta build parallele con ottimizzazione automatica CPU
|
||||||
|
|
||||||
|
## Utilizzo
|
||||||
|
|
||||||
|
### Importante: Gestione Memoria del Plugin
|
||||||
|
|
||||||
|
**CRITICO**: Usare sempre il flag `/NOUNLOAD` alla prima chiamata a `show` per mantenere il plugin caricato in memoria. Senza questo, NSIS scarica il plugin dopo ogni chiamata, perdendo tutto lo stato (handle finestra, thread, ecc.), causando il fallimento di `stop`.
|
||||||
|
|
||||||
|
```nsis
|
||||||
|
; Corretto - mantiene il plugin caricato
|
||||||
|
nssplashpng::show /NOUNLOAD 5000 /FADEIN /FADEOUT "$TEMP\splash.png"
|
||||||
|
nssplashpng::stop /FADEOUT
|
||||||
|
|
||||||
|
; Sbagliato - plugin scaricato dopo show, stop fallirà
|
||||||
|
nssplashpng::show 5000 /FADEIN /FADEOUT "$TEMP\splash.png"
|
||||||
|
nssplashpng::stop /FADEOUT
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sintassi
|
||||||
|
|
||||||
|
```nsis
|
||||||
|
nsSplashPNG::show [/NOUNLOAD] <millisecondi> [/FADEIN [step]] [/FADEOUT [step]] [/NOCANCEL] [/MONITOR <target>] <percorso_immagine>
|
||||||
|
nsSplashPNG::stop [/FADEOUT [step]]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Nota**: Il timeout deve essere specificato subito dopo `/NOUNLOAD` (se presente), seguito da flag opzionali, con il percorso immagine come ultimo parametro.
|
||||||
|
|
||||||
|
### Parametri
|
||||||
|
|
||||||
|
#### Funzione show
|
||||||
|
- `/NOUNLOAD` - **Richiesto perché stop() funzioni**: Mantiene il plugin in memoria tra le chiamate
|
||||||
|
- `/FADEIN [step]` - Abilita effetto fade in. Step opzionale (1-255) controlla la velocità (default: 15)
|
||||||
|
- Step più alto = fade più veloce (255=istantaneo, 51=veloce, 15=normale, 5=lento)
|
||||||
|
- Formula durata: `(255 / step) × 30ms`
|
||||||
|
- `/FADEOUT [step]` - Abilita effetto fade out alla chiusura automatica tramite timer (step opzionale)
|
||||||
|
- `/NOCANCEL` - Disabilita click per chiudere (lo splash può essere chiuso solo programmaticamente o dal timer)
|
||||||
|
- `/MONITOR <target>` - Seleziona il monitor su cui centrare lo splash (default: monitor primario)
|
||||||
|
- `PRIMARY` - Monitor primario (default)
|
||||||
|
- `CURRENT` o `MOUSE` - Monitor dove si trova il cursore del mouse
|
||||||
|
- `1`, `2`, `3`, ... - Monitor per indice 1-based (ordine di enumerazione)
|
||||||
|
- `POINT x y` - Monitor più vicino alle coordinate schermo fornite
|
||||||
|
- `<millisecondi>` - Timer auto-close in millisecondi (0 = solo chiusura manuale)
|
||||||
|
- `<percorso_immagine>` - Percorso al file immagine (PNG, JPEG, BMP, GIF, TIFF, ICO)
|
||||||
|
|
||||||
|
#### Funzione stop
|
||||||
|
- `/FADEOUT [step]` - Abilita effetto fade out alla chiusura. Step opzionale (1-255) controlla la velocità (default: 15)
|
||||||
|
|
||||||
|
### Esempi
|
||||||
|
|
||||||
|
#### Utilizzo Base
|
||||||
|
```nsis
|
||||||
|
; Mostra splash per 3 secondi (PNG con alfa) - /NOUNLOAD non necessario per solo auto-close
|
||||||
|
nsSplashPNG::show 3000 "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Mostra splash JPEG (opaco, senza trasparenza)
|
||||||
|
nsSplashPNG::show 3000 "$EXEDIR\splash.jpg"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Effetti Fade
|
||||||
|
```nsis
|
||||||
|
; Velocità fade default (step 15, ~510ms)
|
||||||
|
nsSplashPNG::show 3000 /FADEIN /FADEOUT "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Fade veloce (step 51, ~150ms)
|
||||||
|
nsSplashPNG::show 3000 /FADEIN 51 /FADEOUT 51 "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Fade lento (step 5, ~1530ms)
|
||||||
|
nsSplashPNG::show 5000 /FADEIN 5 /FADEOUT 5 "$EXEDIR\splash.png"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Controllo Manuale
|
||||||
|
```nsis
|
||||||
|
; CRITICO: Usare /NOUNLOAD quando si usa stop() per chiudere manualmente
|
||||||
|
nsSplashPNG::show /NOUNLOAD 0 /NOCANCEL "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; ... operazioni di installazione ...
|
||||||
|
|
||||||
|
; Chiudi con fade out
|
||||||
|
nsSplashPNG::stop /FADEOUT
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dettagli Tecnici
|
||||||
|
|
||||||
|
### Implementazione
|
||||||
|
- **Libreria Grafica**: WIC (Windows Imaging Component) tramite windowscodecs.lib
|
||||||
|
- **Formato Immagine**: 32bppPBGRA (alfa pre-moltiplicato) per corretta trasparenza
|
||||||
|
- **Tipo Finestra**: Layered window con `WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW`
|
||||||
|
- **Trasparenza**: `UpdateLayeredWindow` con blending alfa per pixel
|
||||||
|
- **Rendering**: Accelerato hardware tramite `UpdateLayeredWindow` con `ULW_ALPHA`
|
||||||
|
- **Animazione**: Timer da 30ms per fade fluido (range alfa 0-255)
|
||||||
|
- **Threading**: Thread finestra in background per esecuzione non bloccante
|
||||||
|
|
||||||
|
### Confronto con nsAdvsplash
|
||||||
|
|
||||||
|
| Funzionalità | nsAdvsplash | nsSplashPNG |
|
||||||
|
|--------------|-------------|-------------|
|
||||||
|
| Supporto Alfa PNG | Parziale | Completo |
|
||||||
|
| Effetti Fade | Sì | Sì |
|
||||||
|
| Metodo Trasparenza | Color key | Alfa per pixel |
|
||||||
|
| API Grafica | GDI | WIC |
|
||||||
|
| Tipo Finestra | Standard | Layered |
|
||||||
|
| Click per Chiudere | Sì | Sì |
|
||||||
|
| Warning Compilazione | Alcuni | Nessuno |
|
||||||
|
|
||||||
|
### Formati Immagine Supportati
|
||||||
|
|
||||||
|
WIC rileva e decodifica automaticamente i seguenti formati:
|
||||||
|
- **PNG** - Supporto completo trasparenza alfa
|
||||||
|
- **JPEG/JPG** - Senza trasparenza (opaco)
|
||||||
|
- **BMP** - Trasparenza alfa se a 32-bit, altrimenti opaco
|
||||||
|
- **GIF** - Supporto trasparenza base
|
||||||
|
- **TIFF** - Trasparenza alfa se supportata dalla variante
|
||||||
|
- **ICO** - Formato icona con alfa
|
||||||
|
- **WMP** - Windows Media Photo
|
||||||
|
|
||||||
|
### Requisiti di Sistema
|
||||||
|
- Windows Vista o successivo (per supporto layered window)
|
||||||
|
- WIC - Windows Imaging Component (incluso da Windows Vista in poi)
|
||||||
|
|
||||||
|
## Risoluzione Problemi
|
||||||
|
|
||||||
|
### Immagine non visibile
|
||||||
|
- Verificare che il percorso del file immagine sia corretto e accessibile
|
||||||
|
- Controllare che il file sia in un formato supportato
|
||||||
|
- Verificare che WIC possa decodificare il file immagine specifico
|
||||||
|
|
||||||
|
### Nessuna trasparenza
|
||||||
|
- Confermare che il PNG abbia canale alfa (non solo RGB)
|
||||||
|
- Verificare di usare la DLL plugin corretta per la versione NSIS
|
||||||
|
|
||||||
|
### Effetti fade non fluidi
|
||||||
|
- Comportamento normale - il fade usa timer 30ms con incrementi alfa di 15 step
|
||||||
|
- Durata totale fade ≈ 450-500ms
|
||||||
|
|
||||||
|
## Licenza
|
||||||
|
|
||||||
|
Creato per il sistema di installazione NSIS.
|
||||||
|
|
||||||
|
## Crediti
|
||||||
|
|
||||||
|
- NSIS Plugin API
|
||||||
|
- WIC - Windows Imaging Component
|
||||||
|
- Windows Layered Windows API (`UpdateLayeredWindow`)
|
||||||
|
- Sistema di build ispirato al plugin nsProcess
|
||||||
|
|
||||||
|
## Cronologia Versioni
|
||||||
|
|
||||||
|
### 1.0.0 (Corrente)
|
||||||
|
- Supporto trasparenza alfa PNG completo tramite WIC
|
||||||
|
- Blending alfa per pixel con `UpdateLayeredWindow`
|
||||||
|
- Effetti fade in/out (30ms, 15 step)
|
||||||
|
- Timer auto-close con timeout configurabile
|
||||||
|
- Click per chiudere con disabilitazione opzionale (`/NOCANCEL`)
|
||||||
|
- Build x86-unicode, x64-unicode e x86-ansi
|
||||||
|
- Zero warning di compilazione
|
||||||
|
- Sistema di build ottimizzato con compilazione parallela
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*See [README.md](README.md) for the English version.*
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Versioni Supportate
|
||||||
|
|
||||||
|
Solo l'ultima versione minor riceve fix di security.
|
||||||
|
|
||||||
|
| Versione | Supporto |
|
||||||
|
|----------|----------|
|
||||||
|
| Latest minor (es. 1.0.0) | ✅ |
|
||||||
|
| Versioni precedenti | ❌ |
|
||||||
|
|
||||||
|
## Reporting di una Vulnerabilità
|
||||||
|
|
||||||
|
**Non aprire issue pubbliche per vulnerabilità di security.**
|
||||||
|
|
||||||
|
Per segnalare una vulnerabilità:
|
||||||
|
|
||||||
|
1. **Preferito**: usa la funzione [Security Advisory privato](https://gitea.emulab.it/Simone/nsis-plugin-nssplashpng/security/advisories/new) di Gitea
|
||||||
|
2. **Alternativa**: invia email a `security@example.com` (sostituire con email reale prima del primo release)
|
||||||
|
|
||||||
|
Includi:
|
||||||
|
- Descrizione della vulnerabilità
|
||||||
|
- Passi per riprodurla
|
||||||
|
- Versione/i affette
|
||||||
|
- Possibile mitigazione (se nota)
|
||||||
|
|
||||||
|
## Tempo di Risposta
|
||||||
|
|
||||||
|
- Acknowledgment iniziale: entro 7 giorni
|
||||||
|
- Valutazione: entro 14 giorni
|
||||||
|
- Patch (se confermata): tempistiche dipendono dalla severità
|
||||||
|
|
||||||
|
## Hardening
|
||||||
|
|
||||||
|
Questo plugin gira con i permessi del processo NSIS chiamante (tipicamente
|
||||||
|
l'utente che esegue l'installer). Non richiede privilegi elevati per la
|
||||||
|
compilazione né per l'esecuzione, salvo dove esplicitamente documentato.
|
||||||
|
|
||||||
|
DLL distribuite tramite Releases sono compilate da CI GitHub Actions con
|
||||||
|
toolchain pinnata. Verificare hash SHA256 pubblicati nelle release.
|
||||||
+679
@@ -0,0 +1,679 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Build script for nsSplashPNG plugin - All configurations
|
||||||
|
Supports multiple build configurations with flexible parameters
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import errno
|
||||||
|
import multiprocessing
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import stat
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
|
# Ensure stdout/stderr handle Unicode on Windows CI (cp1252 default breaks non-ASCII)
|
||||||
|
if sys.stdout.encoding and sys.stdout.encoding.lower() not in ('utf-8', 'utf_8'):
|
||||||
|
import io
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||||
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Filesystem helpers
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _on_rmtree_error(func, path, exc_info):
|
||||||
|
if func in (os.unlink, os.rmdir) and exc_info[1].errno == errno.EACCES:
|
||||||
|
try:
|
||||||
|
os.chmod(path, stat.S_IWRITE)
|
||||||
|
func(path)
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
for i in range(5):
|
||||||
|
try:
|
||||||
|
time.sleep(0.1 * (i + 1))
|
||||||
|
if os.path.isdir(path):
|
||||||
|
shutil.rmtree(path)
|
||||||
|
else:
|
||||||
|
os.unlink(path)
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
raise exc_info[1]
|
||||||
|
|
||||||
|
|
||||||
|
def robust_rmtree(path):
|
||||||
|
if not os.path.exists(path):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
shutil.rmtree(path, onexc=_on_rmtree_error)
|
||||||
|
except TypeError:
|
||||||
|
try:
|
||||||
|
shutil.rmtree(path, onerror=_on_rmtree_error)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
shutil.rmtree(path, ignore_errors=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Colors & Spinner
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class Colors:
|
||||||
|
CYAN = "\033[36m"
|
||||||
|
GREEN = "\033[32m"
|
||||||
|
YELLOW = "\033[33m"
|
||||||
|
RED = "\033[31m"
|
||||||
|
GRAY = "\033[90m"
|
||||||
|
BLUE = "\033[34m"
|
||||||
|
RESET = "\033[0m"
|
||||||
|
BOLD = "\033[1m"
|
||||||
|
BRIGHT_GREEN = "\033[92m"
|
||||||
|
BRIGHT_CYAN = "\033[96m"
|
||||||
|
BRIGHT_WHITE = "\033[97m"
|
||||||
|
BRIGHT_YELLOW = "\033[93m"
|
||||||
|
BRIGHT_RED = "\033[91m"
|
||||||
|
|
||||||
|
|
||||||
|
class Spinner:
|
||||||
|
def __init__(self, message: str = "Building...", delay: float = 0.1, total: int = 0):
|
||||||
|
self.spinner = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f']
|
||||||
|
self.delay = delay
|
||||||
|
self.message = message
|
||||||
|
self.running = False
|
||||||
|
self.thread = None
|
||||||
|
self.start_time = time.time()
|
||||||
|
|
||||||
|
def update(self, current=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _spin(self):
|
||||||
|
idx = 0
|
||||||
|
_block = '\u28ff'
|
||||||
|
while self.running:
|
||||||
|
elapsed = time.time() - self.start_time
|
||||||
|
n_blocks = int(elapsed // 2)
|
||||||
|
time_blocks = f"{Colors.YELLOW}{_block * n_blocks}{Colors.RESET}"
|
||||||
|
spin_char = f"{Colors.YELLOW}{self.spinner[idx % len(self.spinner)]}{Colors.RESET}"
|
||||||
|
msg = f"{Colors.BOLD}{Colors.CYAN}{self.message}{Colors.RESET}"
|
||||||
|
time_str = f"{Colors.GREEN}{int(elapsed)}s{Colors.RESET}"
|
||||||
|
sys.stdout.write(f"\r{msg} {time_str} {time_blocks}{spin_char} ")
|
||||||
|
sys.stdout.flush()
|
||||||
|
idx += 1
|
||||||
|
time.sleep(self.delay)
|
||||||
|
sys.stdout.write("\r" + " " * (len(self.message) + 80) + "\r")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
if sys.stdout.isatty():
|
||||||
|
self.running = True
|
||||||
|
self.thread = threading.Thread(target=self._spin, daemon=True)
|
||||||
|
self.thread.start()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
if self.running:
|
||||||
|
self.running = False
|
||||||
|
if self.thread:
|
||||||
|
self.thread.join()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Build configuration
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class BuildConfig:
|
||||||
|
def __init__(self, name: str, config: str, platform: str, dest_dir: str):
|
||||||
|
self.name = name
|
||||||
|
self.config = config
|
||||||
|
self.platform = platform
|
||||||
|
self.dest_dir = dest_dir
|
||||||
|
|
||||||
|
|
||||||
|
CONFIGS = {
|
||||||
|
'x86-ansi': BuildConfig('x86-ansi', 'Release', 'Win32', 'x86-ansi'),
|
||||||
|
'x86-unicode': BuildConfig('x86-unicode', 'Release Unicode', 'Win32', 'x86-unicode'),
|
||||||
|
'x64-unicode': BuildConfig('x64-unicode', 'Release Unicode', 'x64', 'amd64-unicode'),
|
||||||
|
}
|
||||||
|
|
||||||
|
DLL_NAME = 'nsSplashPNG.dll'
|
||||||
|
_VSWHERE = Path(r'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe')
|
||||||
|
_VS_RANGES = {'2026': '[18.0,19.0)', '2022': '[17.0,18.0)'}
|
||||||
|
_VS_TOOLSETS = {'2026': 'v145', '2022': 'v143'}
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# MSBuild discovery
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _find_msbuild_vswhere(vs_version: str) -> Optional[Tuple[Path, str, str]]:
|
||||||
|
if not _VSWHERE.exists():
|
||||||
|
return None
|
||||||
|
to_try = ['2026', '2022'] if vs_version == 'auto' else [vs_version]
|
||||||
|
for ver in to_try:
|
||||||
|
if ver not in _VS_RANGES:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
[str(_VSWHERE), '-version', _VS_RANGES[ver], '-latest',
|
||||||
|
'-requires', 'Microsoft.Component.MSBuild',
|
||||||
|
'-find', r'MSBuild\**\Bin\MSBuild.exe'],
|
||||||
|
capture_output=True, text=True, timeout=15,
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
lines = [ln.strip() for ln in result.stdout.splitlines() if ln.strip()]
|
||||||
|
if lines:
|
||||||
|
p = Path(lines[0])
|
||||||
|
if p.exists():
|
||||||
|
return p, _VS_TOOLSETS[ver], ver
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def find_msbuild(vs_version: str = 'auto') -> Optional[Tuple[Path, str, str]]:
|
||||||
|
result = _find_msbuild_vswhere(vs_version)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
# Fallback: well-known paths
|
||||||
|
to_try = ['2026', '2022'] if vs_version == 'auto' else [vs_version]
|
||||||
|
for ver in to_try:
|
||||||
|
for edition in ('Community', 'Professional', 'Enterprise', 'BuildTools'):
|
||||||
|
p = Path(rf'C:\Program Files\Microsoft Visual Studio\{ver}\{edition}\MSBuild\Current\Bin\MSBuild.exe')
|
||||||
|
if p.exists():
|
||||||
|
return p, _VS_TOOLSETS[ver], ver
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Project paths & version
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_project_paths() -> Tuple[Path, Path, Path]:
|
||||||
|
script_dir = Path(__file__).parent.absolute()
|
||||||
|
project_dir = script_dir / 'src'
|
||||||
|
project_file = project_dir / 'nsSplashPNG.vcxproj'
|
||||||
|
dist_dir = script_dir / 'dist'
|
||||||
|
return project_dir, project_file, dist_dir
|
||||||
|
|
||||||
|
|
||||||
|
def get_plugins_dir() -> Optional[Path]:
|
||||||
|
"""Return workspace-level plugins/ dir (4 levels above src/modules/<plugin>/)."""
|
||||||
|
p = Path(__file__).parent.parent.parent.parent / 'plugins'
|
||||||
|
return p if p.is_dir() else None
|
||||||
|
|
||||||
|
|
||||||
|
def read_version() -> str:
|
||||||
|
vf = Path(__file__).parent / 'VERSION'
|
||||||
|
try:
|
||||||
|
return vf.read_text(encoding='utf-8-sig').strip()
|
||||||
|
except Exception:
|
||||||
|
return '0.0.0'
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# CPU info & build optimizations
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_cpu_info() -> dict:
|
||||||
|
try:
|
||||||
|
import psutil
|
||||||
|
logical = psutil.cpu_count(logical=True)
|
||||||
|
physical = psutil.cpu_count(logical=False)
|
||||||
|
return {'logical_cores': logical, 'physical_cores': physical,
|
||||||
|
'has_hyperthreading': logical > physical, 'has_psutil': True}
|
||||||
|
except ImportError:
|
||||||
|
n = multiprocessing.cpu_count()
|
||||||
|
return {'logical_cores': n, 'physical_cores': n,
|
||||||
|
'has_hyperthreading': False, 'has_psutil': False}
|
||||||
|
|
||||||
|
|
||||||
|
def get_optimal_threads() -> int:
|
||||||
|
info = get_cpu_info()
|
||||||
|
return info['physical_cores'] if info['has_hyperthreading'] and info['physical_cores'] > 1 else info['logical_cores']
|
||||||
|
|
||||||
|
|
||||||
|
def get_build_optimizations() -> List[str]:
|
||||||
|
return [
|
||||||
|
'/p:BuildInParallel=true',
|
||||||
|
'/p:MultiProcessorCompilation=true',
|
||||||
|
'/p:PreferredToolArchitecture=x64',
|
||||||
|
'/p:UseSharedCompilation=true',
|
||||||
|
'/nodeReuse:true',
|
||||||
|
'/p:GenerateResourceUsePreserializedResources=true',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_memory_optimizations() -> List[str]:
|
||||||
|
try:
|
||||||
|
import psutil
|
||||||
|
gb = psutil.virtual_memory().total / (1024 ** 3)
|
||||||
|
if gb >= 16:
|
||||||
|
return ['/p:DisableFastUpToDateCheck=false', '/p:BuildProjectReferences=true',
|
||||||
|
'/p:UseCommonOutputDirectory=false']
|
||||||
|
elif gb >= 8:
|
||||||
|
return ['/p:DisableFastUpToDateCheck=false']
|
||||||
|
return []
|
||||||
|
except ImportError:
|
||||||
|
return ['/p:DisableFastUpToDateCheck=false']
|
||||||
|
|
||||||
|
|
||||||
|
def print_cpu_info(use_parallel: bool, use_optimizations: bool = True) -> None:
|
||||||
|
if not use_parallel:
|
||||||
|
print(f"{Colors.CYAN}Build mode: {Colors.RESET} {Colors.BOLD}Single-threaded{Colors.RESET}")
|
||||||
|
if use_optimizations:
|
||||||
|
print(f"{Colors.CYAN}Optimizations: {Colors.RESET} {Colors.GREEN}ENABLED{Colors.RESET} {Colors.GRAY}(memory, caching){Colors.RESET}")
|
||||||
|
return
|
||||||
|
|
||||||
|
info = get_cpu_info()
|
||||||
|
optimal = get_optimal_threads()
|
||||||
|
print(f"{Colors.CYAN}Build mode: {Colors.RESET} {Colors.BRIGHT_WHITE}{'Parallel' if use_parallel else 'Sequential'}{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Logical cores: {Colors.RESET} {Colors.BRIGHT_WHITE}{info['logical_cores']}{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Physical cores: {Colors.RESET} {Colors.BRIGHT_WHITE}{info['physical_cores']}{Colors.RESET}")
|
||||||
|
if info['has_hyperthreading']:
|
||||||
|
print(f"{Colors.CYAN}Hyperthreading: {Colors.RESET} {Colors.BRIGHT_GREEN}ENABLED{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Optimal threads: {Colors.RESET} {Colors.BRIGHT_WHITE}{optimal}{Colors.RESET} {Colors.GRAY}(using physical cores){Colors.RESET}")
|
||||||
|
else:
|
||||||
|
print(f"{Colors.CYAN}Hyperthreading: {Colors.RESET} {Colors.GRAY}NOT AVAILABLE{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Optimal threads: {Colors.RESET} {Colors.BRIGHT_WHITE}{optimal}{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}MSBuild threads: {Colors.RESET} {Colors.BRIGHT_WHITE}{optimal}{Colors.RESET}")
|
||||||
|
if use_optimizations:
|
||||||
|
print(f"{Colors.CYAN}Optimizations: {Colors.RESET} {Colors.BRIGHT_GREEN}ENABLED{Colors.RESET} {Colors.GRAY}(parallel, memory, caching){Colors.RESET}")
|
||||||
|
try:
|
||||||
|
import psutil
|
||||||
|
gb = psutil.virtual_memory().total / (1024 ** 3)
|
||||||
|
print(f"{Colors.CYAN}Available memory: {Colors.RESET} {Colors.BRIGHT_WHITE}{gb:.1f} GB{Colors.RESET}")
|
||||||
|
except ImportError:
|
||||||
|
print(f"{Colors.CYAN}Available memory: {Colors.RESET} {Colors.GRAY}Unknown (install psutil){Colors.RESET}")
|
||||||
|
else:
|
||||||
|
print(f"{Colors.CYAN}Optimizations: {Colors.RESET} {Colors.BRIGHT_RED}DISABLED{Colors.RESET}")
|
||||||
|
if not info['has_psutil']:
|
||||||
|
print(f"{Colors.GRAY}Note: Install 'psutil' for detailed CPU/memory info (pip install psutil){Colors.RESET}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Build helpers
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def format_time(seconds: float) -> str:
|
||||||
|
if seconds < 60:
|
||||||
|
return f"{seconds:.2f}s"
|
||||||
|
elif seconds < 3600:
|
||||||
|
return f"{int(seconds // 60)}m {seconds % 60:.1f}s"
|
||||||
|
return f"{int(seconds // 3600)}h {int((seconds % 3600) // 60)}m {seconds % 60:.0f}s"
|
||||||
|
|
||||||
|
|
||||||
|
def build_configuration(
|
||||||
|
msbuild_path: Path,
|
||||||
|
project_file: Path,
|
||||||
|
config: BuildConfig,
|
||||||
|
platform_toolset: str,
|
||||||
|
*,
|
||||||
|
rebuild: bool = True,
|
||||||
|
verbosity: str = 'quiet',
|
||||||
|
parallel: bool = True,
|
||||||
|
optimizations: bool = True,
|
||||||
|
counter: str = "",
|
||||||
|
capture_output: bool = False,
|
||||||
|
) -> Tuple[bool, float, str]:
|
||||||
|
optimal = get_optimal_threads()
|
||||||
|
cmd = [
|
||||||
|
str(msbuild_path),
|
||||||
|
str(project_file),
|
||||||
|
f'/t:{"Rebuild" if rebuild else "Build"}',
|
||||||
|
f'/p:Configuration={config.config}',
|
||||||
|
f'/p:Platform={config.platform}',
|
||||||
|
f'/p:OutDir=Build\\{config.name}\\',
|
||||||
|
f'/p:IntDir=Build\\{config.name}\\obj\\',
|
||||||
|
'/p:WindowsTargetPlatformVersion=10.0',
|
||||||
|
f'/p:PlatformToolset={platform_toolset}',
|
||||||
|
f'/v:{verbosity}',
|
||||||
|
]
|
||||||
|
if parallel:
|
||||||
|
cmd += [f'/maxcpucount:{optimal}', '/p:UseMultiToolTask=true', f'/p:CL_MPCount={optimal}']
|
||||||
|
cmd += ['/p:Optimization=MaxSpeed', '/p:ExceptionHandling=Sync']
|
||||||
|
if optimizations:
|
||||||
|
cmd += get_build_optimizations() + get_memory_optimizations()
|
||||||
|
|
||||||
|
if not capture_output:
|
||||||
|
color = Colors.BRIGHT_YELLOW
|
||||||
|
print(f"\n{color}{'='*60}{Colors.RESET}")
|
||||||
|
label = f"[{counter}] " if counter else ""
|
||||||
|
print(f"{color}Building {Colors.BRIGHT_WHITE}{config.name:15s}{Colors.RESET} {color}{label}({'rebuild' if rebuild else 'incremental'}){Colors.RESET}")
|
||||||
|
print(f"{color}{'='*60}{Colors.RESET}")
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
try:
|
||||||
|
if capture_output:
|
||||||
|
result = subprocess.run(cmd, check=False, capture_output=True, text=True)
|
||||||
|
return result.returncode == 0, time.time() - start, result.stdout + result.stderr
|
||||||
|
else:
|
||||||
|
result = subprocess.run(cmd, check=False)
|
||||||
|
return result.returncode == 0, time.time() - start, ""
|
||||||
|
except Exception as e:
|
||||||
|
elapsed = time.time() - start
|
||||||
|
msg = f"ERROR: Build failed with exception: {e}"
|
||||||
|
if capture_output:
|
||||||
|
return False, elapsed, msg
|
||||||
|
print(msg)
|
||||||
|
return False, elapsed, ""
|
||||||
|
|
||||||
|
|
||||||
|
def copy_output(project_dir: Path, dist_dir: Path, config: BuildConfig) -> Tuple[bool, int, Optional[Path]]:
|
||||||
|
output_file = project_dir / 'Build' / config.name / DLL_NAME
|
||||||
|
if not output_file.exists():
|
||||||
|
print(f"ERROR: DLL not found: {output_file}")
|
||||||
|
return False, 0, None
|
||||||
|
dest = dist_dir / config.dest_dir
|
||||||
|
dest.mkdir(parents=True, exist_ok=True)
|
||||||
|
try:
|
||||||
|
dst = dest / DLL_NAME
|
||||||
|
shutil.copy2(output_file, dst)
|
||||||
|
return True, output_file.stat().st_size, dst
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: Failed to copy {config.name}: {e}")
|
||||||
|
return False, 0, None
|
||||||
|
|
||||||
|
|
||||||
|
def copy_to_plugins(dist_dir: Path, config: BuildConfig, plugins_dir: Path) -> Optional[Path]:
|
||||||
|
"""Copy DLL from dist/<dest_dir>/ to plugins/<dest_dir>/."""
|
||||||
|
src = dist_dir / config.dest_dir / DLL_NAME
|
||||||
|
if not src.exists():
|
||||||
|
return None
|
||||||
|
dst_dir = plugins_dir / config.dest_dir
|
||||||
|
dst_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
dst = dst_dir / DLL_NAME
|
||||||
|
shutil.copy2(src, dst)
|
||||||
|
return dst
|
||||||
|
|
||||||
|
|
||||||
|
def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None:
|
||||||
|
print(f"\n{Colors.CYAN}Cleaning build artifacts...{Colors.RESET}")
|
||||||
|
build_base = project_dir / 'Build'
|
||||||
|
if not build_base.exists():
|
||||||
|
return
|
||||||
|
for cfg in configs:
|
||||||
|
d = build_base / cfg.name
|
||||||
|
if d.exists():
|
||||||
|
try:
|
||||||
|
robust_rmtree(d)
|
||||||
|
print(f" {Colors.GRAY}- Cleaned:{Colors.RESET} {cfg.name}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" {Colors.RED}- Failed to clean {cfg.name}: {e}{Colors.RESET}")
|
||||||
|
try:
|
||||||
|
if build_base.exists() and not any(build_base.iterdir()):
|
||||||
|
build_base.rmdir()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
print(f"{Colors.BRIGHT_GREEN}Build artifacts cleaned successfully.{Colors.RESET}")
|
||||||
|
|
||||||
|
|
||||||
|
def print_available_configurations(project_file: Path) -> None:
|
||||||
|
try:
|
||||||
|
tree = ET.parse(project_file)
|
||||||
|
root = tree.getroot()
|
||||||
|
ns = {'ms': 'http://schemas.microsoft.com/developer/msbuild/2003'}
|
||||||
|
configs = []
|
||||||
|
for ig in root.findall('.//ms:ItemGroup', ns):
|
||||||
|
for pc in ig.findall('ms:ProjectConfiguration', ns):
|
||||||
|
inc = pc.get('Include', '')
|
||||||
|
parts = inc.split('|')
|
||||||
|
if len(parts) == 2:
|
||||||
|
configs.append(tuple(parts))
|
||||||
|
print("Available configurations in project:")
|
||||||
|
print("-" * 60)
|
||||||
|
from collections import defaultdict
|
||||||
|
grouped: dict = defaultdict(list)
|
||||||
|
for cfg, plat in configs:
|
||||||
|
grouped[cfg].append(plat)
|
||||||
|
for cfg_name in sorted(grouped):
|
||||||
|
print(f" {cfg_name:25s} - {', '.join(sorted(grouped[cfg_name]))}")
|
||||||
|
print(f"\nTotal: {len(configs)} configuration(s)\n")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Could not parse project file: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
_print_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def _build_configs_parallel(
|
||||||
|
msbuild_path: Path,
|
||||||
|
project_file: Path,
|
||||||
|
configs: List[BuildConfig],
|
||||||
|
platform_toolset: str,
|
||||||
|
*,
|
||||||
|
rebuild: bool,
|
||||||
|
verbosity: str,
|
||||||
|
parallel: bool,
|
||||||
|
optimizations: bool,
|
||||||
|
project_dir: Path,
|
||||||
|
dist_dir: Path,
|
||||||
|
) -> list:
|
||||||
|
n = len(configs)
|
||||||
|
print(f"\nParallel: launching {n} builds simultaneously...")
|
||||||
|
print("=" * 50)
|
||||||
|
total_start = time.time()
|
||||||
|
results_by_idx: dict = {}
|
||||||
|
idx_lock = threading.Lock()
|
||||||
|
|
||||||
|
def _build_one(idx: int, config: BuildConfig):
|
||||||
|
import contextlib
|
||||||
|
import io as _io
|
||||||
|
success, build_time, captured = build_configuration(
|
||||||
|
msbuild_path, project_file, config, platform_toolset,
|
||||||
|
rebuild=rebuild, verbosity=verbosity,
|
||||||
|
parallel=parallel, optimizations=optimizations,
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
copy_buf = _io.StringIO()
|
||||||
|
if success:
|
||||||
|
with contextlib.redirect_stdout(copy_buf):
|
||||||
|
copy_ok, file_size, dest_path = copy_output(project_dir, dist_dir, config)
|
||||||
|
else:
|
||||||
|
copy_ok, file_size, dest_path = False, 0, None
|
||||||
|
|
||||||
|
with _print_lock:
|
||||||
|
sys.stdout.write("\r" + " " * 80 + "\r")
|
||||||
|
sys.stdout.flush()
|
||||||
|
tag = "OK" if (success and copy_ok) else "FAILED"
|
||||||
|
size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A"
|
||||||
|
copy_out = copy_buf.getvalue()
|
||||||
|
if copy_out.strip():
|
||||||
|
print(copy_out.rstrip())
|
||||||
|
if captured.strip():
|
||||||
|
lines = [ln for ln in captured.splitlines() if "MSBuild" not in ln and "Copyright" not in ln]
|
||||||
|
filtered = "\n".join(lines).strip()
|
||||||
|
if filtered:
|
||||||
|
print(filtered)
|
||||||
|
color = Colors.BRIGHT_GREEN if (success and copy_ok) else Colors.RED
|
||||||
|
print(f"{color}[{tag}]{Colors.RESET} {config.name} ({format_time(build_time)}) {size_str}")
|
||||||
|
if dest_path:
|
||||||
|
print(f" -> {dest_path}")
|
||||||
|
|
||||||
|
with idx_lock:
|
||||||
|
results_by_idx[idx] = (config, success and copy_ok, build_time, file_size, dest_path)
|
||||||
|
|
||||||
|
with Spinner("Building configurations...", total=n) as s:
|
||||||
|
with ThreadPoolExecutor(max_workers=n) as executor:
|
||||||
|
futures = {executor.submit(_build_one, i, cfg): i for i, cfg in enumerate(configs)}
|
||||||
|
for fut in as_completed(futures):
|
||||||
|
s.update()
|
||||||
|
exc = fut.exception()
|
||||||
|
if exc:
|
||||||
|
idx = futures[fut]
|
||||||
|
with _print_lock:
|
||||||
|
print(f"ERROR: worker for config index {idx} raised: {exc}")
|
||||||
|
with idx_lock:
|
||||||
|
results_by_idx[idx] = (configs[idx], False, 0.0, 0, None)
|
||||||
|
|
||||||
|
wall = time.time() - total_start
|
||||||
|
print(f"\nAll {n} configs finished in {format_time(wall)} (wall clock)")
|
||||||
|
return [results_by_idx[i] for i in range(n)]
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# main
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description='Build nsSplashPNG plugin for NSIS')
|
||||||
|
parser.add_argument('--configs', nargs='+',
|
||||||
|
choices=list(CONFIGS.keys()) + ['all'], default=None)
|
||||||
|
parser.add_argument('--config', # singular alias used by CI
|
||||||
|
choices=list(CONFIGS.keys()) + ['all'], default=None)
|
||||||
|
parser.add_argument('--rebuild', action='store_true', default=True)
|
||||||
|
parser.add_argument('--no-rebuild', action='store_false', dest='rebuild')
|
||||||
|
parser.add_argument('--parallel', action='store_true', default=True)
|
||||||
|
parser.add_argument('--no-parallel', action='store_false', dest='parallel')
|
||||||
|
parser.add_argument('--verbosity',
|
||||||
|
choices=['quiet', 'minimal', 'normal', 'detailed', 'diagnostic'],
|
||||||
|
default='quiet')
|
||||||
|
parser.add_argument('--clean', action='store_true', default=False)
|
||||||
|
parser.add_argument('--list', action='store_true')
|
||||||
|
parser.add_argument('--list-project', action='store_true')
|
||||||
|
parser.add_argument('--no-optimizations', action='store_true')
|
||||||
|
parser.add_argument('--vs-version', choices=['auto', '2026', '2022'], default='auto')
|
||||||
|
parser.add_argument('--final', action='store_true', default=False,
|
||||||
|
help='Force rebuild + clean (pre-release build)')
|
||||||
|
parser.add_argument('--install-dir', type=Path, default=None,
|
||||||
|
help='Copy built DLLs to this directory (in <config>/ subdirs)')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Merge --config (singular) into --configs list
|
||||||
|
if args.config and not args.configs:
|
||||||
|
args.configs = [args.config]
|
||||||
|
|
||||||
|
if args.final:
|
||||||
|
args.rebuild = True
|
||||||
|
args.clean = True
|
||||||
|
|
||||||
|
project_dir, project_file, dist_dir = get_project_paths()
|
||||||
|
|
||||||
|
if args.list_project:
|
||||||
|
print_available_configurations(project_file)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if args.list:
|
||||||
|
for name, config in CONFIGS.items():
|
||||||
|
print(f" {name:15s} -> {config.config} / {config.platform}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not project_file.exists():
|
||||||
|
print(f"ERROR: project file not found: {project_file}", file=sys.stderr)
|
||||||
|
return 3
|
||||||
|
|
||||||
|
msbuild_result = find_msbuild(args.vs_version)
|
||||||
|
if not msbuild_result:
|
||||||
|
print("ERROR: MSBuild not found. Install Visual Studio 2022 or 2026.", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
msbuild_path, platform_toolset, vs_version_name = msbuild_result
|
||||||
|
|
||||||
|
version = read_version()
|
||||||
|
|
||||||
|
if not args.configs:
|
||||||
|
configs_to_build = list(CONFIGS.values())
|
||||||
|
elif 'all' in args.configs:
|
||||||
|
configs_to_build = list(CONFIGS.values())
|
||||||
|
else:
|
||||||
|
configs_to_build = [CONFIGS[n] for n in args.configs]
|
||||||
|
|
||||||
|
# Banner
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{'='*60}")
|
||||||
|
print(f"Building nsSplashPNG plugin v{version} - {Colors.BRIGHT_WHITE}{len(configs_to_build)}{Colors.RESET} {Colors.BOLD}{Colors.BRIGHT_CYAN}configuration(s)")
|
||||||
|
print(f"{'='*60}{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}VS: {Colors.RESET} {Colors.BRIGHT_WHITE}{vs_version_name}{Colors.RESET} {Colors.GRAY}({platform_toolset}){Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}MSBuild: {Colors.RESET} {Colors.BRIGHT_WHITE}{msbuild_path}{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Project: {Colors.RESET} {Colors.BRIGHT_WHITE}{project_file}{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Dist: {Colors.RESET} {Colors.BRIGHT_WHITE}{dist_dir}{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Rebuild: {Colors.RESET} {Colors.BRIGHT_WHITE}{args.rebuild}{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}Verbosity: {Colors.RESET} {Colors.BRIGHT_WHITE}{args.verbosity}{Colors.RESET}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
use_optimizations = not args.no_optimizations
|
||||||
|
print_cpu_info(args.parallel, use_optimizations)
|
||||||
|
|
||||||
|
build_results = []
|
||||||
|
total_start = time.time()
|
||||||
|
|
||||||
|
if args.parallel and len(configs_to_build) > 1:
|
||||||
|
build_results = _build_configs_parallel(
|
||||||
|
msbuild_path, project_file, configs_to_build, platform_toolset,
|
||||||
|
rebuild=args.rebuild, verbosity=args.verbosity,
|
||||||
|
parallel=True, optimizations=use_optimizations,
|
||||||
|
project_dir=project_dir, dist_dir=dist_dir,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
for i, config in enumerate(configs_to_build, 1):
|
||||||
|
success, b_time, _ = build_configuration(
|
||||||
|
msbuild_path, project_file, config, platform_toolset,
|
||||||
|
rebuild=args.rebuild, verbosity=args.verbosity,
|
||||||
|
parallel=args.parallel, optimizations=use_optimizations,
|
||||||
|
counter=f"{i}/{len(configs_to_build)}",
|
||||||
|
)
|
||||||
|
if success:
|
||||||
|
ok, size, path = copy_output(project_dir, dist_dir, config)
|
||||||
|
build_results.append((config, ok, b_time, size, path))
|
||||||
|
else:
|
||||||
|
build_results.append((config, False, b_time, 0, None))
|
||||||
|
|
||||||
|
total_time = time.time() - total_start
|
||||||
|
all_success = all(res[1] for res in build_results)
|
||||||
|
|
||||||
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_GREEN if all_success else Colors.RED}{'='*50}")
|
||||||
|
print("ALL BUILDS SUCCESSFUL!" if all_success else "SOME BUILDS FAILED!")
|
||||||
|
print(f"{'='*50}{Colors.RESET}")
|
||||||
|
|
||||||
|
# Copy to workspace plugins/ dir if present
|
||||||
|
plugins_dir = get_plugins_dir()
|
||||||
|
plugins_copied = []
|
||||||
|
if plugins_dir and all_success:
|
||||||
|
for cfg, ok, _, _, _ in build_results:
|
||||||
|
if ok:
|
||||||
|
dst = copy_to_plugins(dist_dir, cfg, plugins_dir)
|
||||||
|
if dst:
|
||||||
|
plugins_copied.append(str(dst))
|
||||||
|
if plugins_copied:
|
||||||
|
print(f"\n{Colors.CYAN}Installed to plugins/:{Colors.RESET}")
|
||||||
|
for p in plugins_copied:
|
||||||
|
print(f" {Colors.GRAY}->{Colors.RESET} {p}")
|
||||||
|
|
||||||
|
if args.install_dir and all_success:
|
||||||
|
print(f"\n{Colors.CYAN}Installing to {args.install_dir}...{Colors.RESET}")
|
||||||
|
for cfg, ok, _, _, path in build_results:
|
||||||
|
if ok and path:
|
||||||
|
dest = args.install_dir / cfg.dest_dir
|
||||||
|
dest.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copy2(path, dest / DLL_NAME)
|
||||||
|
print(f" {Colors.GRAY}- Installed:{Colors.RESET} {dest / DLL_NAME}")
|
||||||
|
|
||||||
|
print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}{'-'*50}")
|
||||||
|
print(f"Build Summary - VS {vs_version_name} ({platform_toolset}):")
|
||||||
|
for cfg, ok, b_time, size, _ in build_results:
|
||||||
|
color = Colors.GREEN if ok else Colors.RED
|
||||||
|
tag = "OK " if ok else "FAIL"
|
||||||
|
print(f" {color}{tag}{Colors.RESET} {cfg.name:15s} - {format_time(b_time):8s} - {size:11,d} bytes")
|
||||||
|
print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{'-'*50}{Colors.RESET}")
|
||||||
|
print(f"Total time: {format_time(total_time)}\n")
|
||||||
|
|
||||||
|
if args.clean:
|
||||||
|
clean_build_artifacts(project_dir, configs_to_build)
|
||||||
|
|
||||||
|
return 0 if all_success else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,157 @@
|
|||||||
|
# nsSplashPNG Quick Reference
|
||||||
|
|
||||||
|
## Basic Syntax
|
||||||
|
|
||||||
|
```nsis
|
||||||
|
nsSplashPNG::show [/NOUNLOAD] <timeout> [/FADEIN [step]] [/FADEOUT [step]] [/NOCANCEL] <image_path>
|
||||||
|
nsSplashPNG::stop [/FADEOUT [step]]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Critical Requirements
|
||||||
|
|
||||||
|
⚠️ **ALWAYS use `/NOUNLOAD` when calling `stop()` manually**
|
||||||
|
|
||||||
|
Without `/NOUNLOAD`, the plugin unloads after `show()` and loses all state, causing `stop()` to fail.
|
||||||
|
|
||||||
|
## Common Use Cases
|
||||||
|
|
||||||
|
### Auto-Close Only (No /NOUNLOAD needed)
|
||||||
|
```nsis
|
||||||
|
; Splash closes automatically after 3 seconds
|
||||||
|
nssplashpng::show 3000 "$TEMP\splash.png"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Close (Requires /NOUNLOAD)
|
||||||
|
```nsis
|
||||||
|
; Show splash, no auto-close
|
||||||
|
nssplashpng::show /NOUNLOAD 0 "$TEMP\splash.png"
|
||||||
|
|
||||||
|
; ... do work ...
|
||||||
|
|
||||||
|
; Close manually
|
||||||
|
nssplashpng::stop
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Fade Effects (Auto-close)
|
||||||
|
```nsis
|
||||||
|
; Fade in, wait 3 seconds, fade out
|
||||||
|
nssplashpng::show 3000 /FADEIN /FADEOUT "$TEMP\splash.png"
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Fade Effects (Manual close - Requires /NOUNLOAD)
|
||||||
|
```nsis
|
||||||
|
; Fade in, no auto-close
|
||||||
|
nssplashpng::show /NOUNLOAD 0 /FADEIN "$TEMP\splash.png"
|
||||||
|
|
||||||
|
; ... do work ...
|
||||||
|
|
||||||
|
; Fade out when closing
|
||||||
|
nssplashpng::stop /FADEOUT
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prevent User Closing
|
||||||
|
```nsis
|
||||||
|
; User cannot click to close, must wait for timer or stop()
|
||||||
|
nssplashpng::show /NOUNLOAD 0 /NOCANCEL "$TEMP\splash.png"
|
||||||
|
nssplashpng::stop
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Fade Speed
|
||||||
|
```nsis
|
||||||
|
; Fast fade (step 51, ~150ms)
|
||||||
|
nssplashpng::show /NOUNLOAD 0 /FADEIN 51 "$TEMP\splash.png"
|
||||||
|
nssplashpng::stop /FADEOUT 51
|
||||||
|
|
||||||
|
; Slow fade (step 5, ~1530ms)
|
||||||
|
nssplashpng::show /NOUNLOAD 0 /FADEIN 5 "$TEMP\splash.png"
|
||||||
|
nssplashpng::stop /FADEOUT 5
|
||||||
|
|
||||||
|
; Instant (step 255, ~30ms)
|
||||||
|
nssplashpng::show /NOUNLOAD 0 /FADEIN 255 "$TEMP\splash.png"
|
||||||
|
nssplashpng::stop /FADEOUT 255
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fade Speed Reference
|
||||||
|
|
||||||
|
| Step Value | Duration | Description |
|
||||||
|
|------------|----------|-------------|
|
||||||
|
| 1 | ~7650ms | Very slow |
|
||||||
|
| 5 | ~1530ms | Slow |
|
||||||
|
| 15 | ~510ms | **Default**, smooth |
|
||||||
|
| 51 | ~150ms | Fast |
|
||||||
|
| 85 | ~90ms | Very fast |
|
||||||
|
| 255 | ~30ms | Instant |
|
||||||
|
|
||||||
|
**Formula**: Duration = (255 / step) × 30ms
|
||||||
|
|
||||||
|
## Parameter Order
|
||||||
|
|
||||||
|
The order matters! Parameters must appear in this sequence:
|
||||||
|
|
||||||
|
```nsis
|
||||||
|
nssplashpng::show [flags] <timeout> [more_flags] <image_path>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct**:
|
||||||
|
```nsis
|
||||||
|
nssplashpng::show /NOUNLOAD 5000 /FADEIN /FADEOUT "$TEMP\splash.png"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Wrong** (image_path must be last):
|
||||||
|
```nsis
|
||||||
|
nssplashpng::show /FADEIN "$TEMP\splash.png" 5000 /FADEOUT
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported Image Formats
|
||||||
|
|
||||||
|
- **PNG** - Full alpha transparency ✅
|
||||||
|
- **JPEG/JPG** - Opaque (no transparency)
|
||||||
|
- **BMP** - Alpha if 32-bit, otherwise opaque
|
||||||
|
- **GIF** - Basic transparency support
|
||||||
|
- **TIFF** - Alpha if supported by variant
|
||||||
|
- **ICO** - Icon format with alpha
|
||||||
|
- **WMP** - Windows Media Photo
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Problem: stop() doesn't close the splash
|
||||||
|
**Solution**: Add `/NOUNLOAD` to the `show()` call
|
||||||
|
|
||||||
|
### Problem: Splash closes too quickly/slowly
|
||||||
|
**Solution**: Adjust the step parameter:
|
||||||
|
- Faster: Increase step value (e.g., 51, 85, 255)
|
||||||
|
- Slower: Decrease step value (e.g., 5, 3, 1)
|
||||||
|
|
||||||
|
### Problem: User can close splash during installation
|
||||||
|
**Solution**: Add `/NOCANCEL` flag
|
||||||
|
|
||||||
|
### Problem: Script waits at show() forever
|
||||||
|
**Solution**: Either:
|
||||||
|
- Use a timeout value > 0 for auto-close, OR
|
||||||
|
- Use timeout = 0 and call `stop()` manually (requires `/NOUNLOAD`)
|
||||||
|
|
||||||
|
## Non-Blocking Behavior
|
||||||
|
|
||||||
|
The `show()` function returns control to your script **after the fade-in completes**, allowing you to:
|
||||||
|
1. Show the splash
|
||||||
|
2. Continue executing your script
|
||||||
|
3. Close the splash when ready
|
||||||
|
|
||||||
|
```nsis
|
||||||
|
nssplashpng::show /NOUNLOAD 0 /FADEIN "$TEMP\splash.png"
|
||||||
|
; Control returns here after fade-in
|
||||||
|
DetailPrint "Installing..."
|
||||||
|
; Splash is still visible while we work
|
||||||
|
Sleep 2000
|
||||||
|
nssplashpng::stop /FADEOUT
|
||||||
|
; Control returns here after fade-out
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
See the `examples/` directory for complete working examples:
|
||||||
|
- `example_basic.nsi` - Simple usage patterns
|
||||||
|
- `example_advanced.nsi` - Real installer with progress
|
||||||
|
- `example_formats.nsi` - Multi-format demonstrations
|
||||||
|
- `example_fade_speed.nsi` - All fade speed options
|
||||||
|
- `example_nonblocking.nsi` - Non-blocking execution pattern
|
||||||
+306
@@ -0,0 +1,306 @@
|
|||||||
|
# nsSplashPNG - PNG Splash Screen Plugin for NSIS
|
||||||
|
|
||||||
|
A modern NSIS plugin that displays splash screens with full alpha transparency support using WIC (Windows Imaging Component).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Multiple Image Formats**: PNG, JPEG, BMP, GIF, TIFF, ICO via WIC decoder
|
||||||
|
- **Full Alpha Transparency**: Proper PNG/GIF alpha channel support via WIC and layered windows
|
||||||
|
- **Configurable Fade Speed**: Customizable fade in/out speed (1-255 steps, 30ms-7650ms duration)
|
||||||
|
- **Non-Blocking Execution**: Script continues after fade-in while splash stays visible
|
||||||
|
- **Auto-Close Timer**: Configurable automatic close after specified milliseconds
|
||||||
|
- **Click-to-Close**: Optional user interaction to close splash
|
||||||
|
- **No Cancel Option**: Prevent users from closing splash during critical operations
|
||||||
|
- **Manual Control**: Programmatic show/stop with fade effects
|
||||||
|
- **Zero Warnings**: Clean compilation with Visual Studio 2022
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Copy `nssplashpng.dll` to your NSIS plugins directory:
|
||||||
|
- `x86-unicode`: For 32-bit NSIS Unicode builds
|
||||||
|
- `x64-unicode`: For 64-bit NSIS Unicode builds
|
||||||
|
- `x86-ansi`: For 32-bit NSIS ANSI builds
|
||||||
|
|
||||||
|
2. Include the plugin directory in your script:
|
||||||
|
```nsis
|
||||||
|
!addplugindir "path\to\plugins\x86-unicode"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building from Source
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Visual Studio 2022 or later with C++ build tools
|
||||||
|
- Python 3.x
|
||||||
|
|
||||||
|
### Build Steps
|
||||||
|
```bash
|
||||||
|
cd nsSplashPNG
|
||||||
|
python build_plugin.py # Builds all configurations
|
||||||
|
python build_plugin.py --configs x86-unicode # Build specific config
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will:
|
||||||
|
- Build for x86-unicode, x64-unicode, and x86-ansi configurations
|
||||||
|
- Copy DLLs to `../plugins/{x86-unicode|amd64-unicode|x86-ansi}/` directories
|
||||||
|
- Clean up intermediate build artifacts from `src/Build/`
|
||||||
|
- Supports parallel builds with automatic CPU optimization
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Important: Plugin Memory Management
|
||||||
|
|
||||||
|
**CRITICAL**: Always use `/NOUNLOAD` flag on the first `show` call to keep the plugin loaded in memory. Without this, NSIS unloads the plugin after each call, losing all state (window handles, threads, etc.), causing `stop` to fail.
|
||||||
|
|
||||||
|
```nsis
|
||||||
|
; Correct - keeps plugin loaded
|
||||||
|
nssplashpng::show /NOUNLOAD 5000 /FADEIN /FADEOUT "$TEMP\splash.png"
|
||||||
|
nssplashpng::stop /FADEOUT
|
||||||
|
|
||||||
|
; Wrong - plugin unloaded after show, stop will fail
|
||||||
|
nssplashpng::show 5000 /FADEIN /FADEOUT "$TEMP\splash.png"
|
||||||
|
nssplashpng::stop /FADEOUT
|
||||||
|
```
|
||||||
|
|
||||||
|
### Syntax
|
||||||
|
|
||||||
|
```nsis
|
||||||
|
nsSplashPNG::show [/NOUNLOAD] <milliseconds> [/FADEIN [step]] [/FADEOUT [step]] [/NOCANCEL] <image_path>
|
||||||
|
nsSplashPNG::stop [/FADEOUT [step]]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: The timeout must be specified immediately after `/NOUNLOAD` (if present), followed by optional flags, with the image path as the last parameter.
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
#### show function
|
||||||
|
- `/NOUNLOAD` - **Required for stop() to work**: Keeps plugin in memory between calls
|
||||||
|
- `/FADEIN [step]` - Enable fade in effect. Optional step (1-255) controls speed (default: 15)
|
||||||
|
- Higher step = faster fade (255=instant, 51=fast, 15=normal, 5=slow)
|
||||||
|
- Duration formula: `(255 / step) × 30ms`
|
||||||
|
- `/FADEOUT [step]` - Enable fade out effect when auto-closing via timer (optional step)
|
||||||
|
- `/NOCANCEL` - Disable click-to-close (splash can only be closed programmatically or by timer)
|
||||||
|
- `<milliseconds>` - Auto-close timer in milliseconds (0 = manual close only)
|
||||||
|
- `<image_path>` - Path to image file (PNG, JPEG, BMP, GIF, TIFF, ICO)
|
||||||
|
|
||||||
|
#### stop function
|
||||||
|
- `/FADEOUT [step]` - Enable fade out effect when closing. Optional step (1-255) controls speed (default: 15)
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
#### Basic Usage
|
||||||
|
```nsis
|
||||||
|
; Show splash for 3 seconds (PNG with alpha) - no /NOUNLOAD needed for auto-close only
|
||||||
|
nsSplashPNG::show 3000 "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Show JPEG splash (opaque, no transparency)
|
||||||
|
nsSplashPNG::show 3000 "$EXEDIR\splash.jpg"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fade Effects
|
||||||
|
```nsis
|
||||||
|
; Default fade speed (step 15, ~510ms)
|
||||||
|
nsSplashPNG::show 3000 /FADEIN /FADEOUT "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Fast fade (step 51, ~150ms)
|
||||||
|
nsSplashPNG::show 3000 /FADEIN 51 /FADEOUT 51 "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Slow fade (step 5, ~1530ms)
|
||||||
|
nsSplashPNG::show 5000 /FADEIN 5 /FADEOUT 5 "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Instant (step 255, ~30ms)
|
||||||
|
nsSplashPNG::show 3000 /FADEIN 255 /FADEOUT 255 "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Different speeds for fade-in and fade-out
|
||||||
|
nsSplashPNG::show 3000 /FADEIN 51 /FADEOUT 5 "$EXEDIR\splash.png"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Manual Control
|
||||||
|
```nsis
|
||||||
|
; CRITICAL: Use /NOUNLOAD when using stop() to close manually
|
||||||
|
nsSplashPNG::show /NOUNLOAD 0 /NOCANCEL "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; ... do installation work ...
|
||||||
|
|
||||||
|
; Close with fade out
|
||||||
|
nsSplashPNG::stop /FADEOUT
|
||||||
|
|
||||||
|
; Or close with custom fade speed
|
||||||
|
nsSplashPNG::stop /FADEOUT 51
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Real Installer Example
|
||||||
|
```nsis
|
||||||
|
Name "My Application"
|
||||||
|
OutFile "setup.exe"
|
||||||
|
InstallDir "$PROGRAMFILES\MyApp"
|
||||||
|
|
||||||
|
!addplugindir "plugins\x86-unicode"
|
||||||
|
|
||||||
|
Page instfiles
|
||||||
|
|
||||||
|
Section "Main Installation"
|
||||||
|
; Show splash with fade in, prevent user closing, no auto-close
|
||||||
|
; CRITICAL: /NOUNLOAD required for stop() to work
|
||||||
|
nsSplashPNG::show /NOUNLOAD 0 /NOCANCEL /FADEIN "$EXEDIR\splash.png"
|
||||||
|
|
||||||
|
; Install files
|
||||||
|
SetOutPath "$INSTDIR"
|
||||||
|
File /r "app\*.*"
|
||||||
|
|
||||||
|
; Create shortcuts
|
||||||
|
CreateDirectory "$SMPROGRAMS\MyApp"
|
||||||
|
CreateShortcut "$SMPROGRAMS\MyApp\MyApp.lnk" "$INSTDIR\myapp.exe"
|
||||||
|
|
||||||
|
; Close splash with fade out
|
||||||
|
nsSplashPNG::stop /FADEOUT
|
||||||
|
SectionEnd
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Additional Examples
|
||||||
|
|
||||||
|
See the `examples/` folder for more usage demonstrations:
|
||||||
|
- **example_basic.nsi** - Simple splash with auto-close timer
|
||||||
|
- **example_advanced.nsi** - Manual control with fade effects
|
||||||
|
- **example_formats.nsi** - Multi-format support (PNG, JPEG, BMP, GIF)
|
||||||
|
- **example_fade_speed.nsi** - Customizable fade speed demonstrations
|
||||||
|
- **example_nonblocking.nsi** - Non-blocking behavior with background processing
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
### Implementation
|
||||||
|
- **Graphics Library**: WIC (Windows Imaging Component) via windowscodecs.lib
|
||||||
|
- **Image Format**: 32bppPBGRA (pre-multiplied alpha) for proper transparency
|
||||||
|
- **Window Type**: Layered windows with `WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW`
|
||||||
|
- **Transparency**: `UpdateLayeredWindow` with per-pixel alpha blending
|
||||||
|
- **Rendering**: Hardware-accelerated via `UpdateLayeredWindow` with `ULW_ALPHA`
|
||||||
|
- **Animation**: 30ms timer intervals for smooth fade (0-255 alpha range)
|
||||||
|
- Configurable step size (default: 15 steps = ~510ms duration)
|
||||||
|
- Duration formula: `(255 / step) × 30ms`
|
||||||
|
- **Threading**: Background window thread for non-blocking execution
|
||||||
|
- `show()` returns after fade-in completes
|
||||||
|
- Window stays open in background
|
||||||
|
- `stop()` waits for fade-out to complete before returning
|
||||||
|
- **Plugin State**: Requires `/NOUNLOAD` to maintain global variables (window handles, threads) between calls
|
||||||
|
|
||||||
|
### Comparison with nsAdvsplash
|
||||||
|
|
||||||
|
| Feature | nsAdvsplash | nsSplashPNG |
|
||||||
|
|----------------------|-------------|-----------------|
|
||||||
|
| PNG Alpha Support | Partial | Full |
|
||||||
|
| Fade Effects | Yes | Yes |
|
||||||
|
| Transparency Method | Color key | Per-pixel alpha |
|
||||||
|
| Graphics API | GDI | WIC |
|
||||||
|
| Window Type | Standard | Layered |
|
||||||
|
| Click-to-Close | Yes | Yes |
|
||||||
|
| Compilation Warnings | Some | None |
|
||||||
|
|
||||||
|
### Supported Image Formats
|
||||||
|
|
||||||
|
WIC automatically detects and decodes the following formats:
|
||||||
|
- **PNG** - Full alpha transparency support
|
||||||
|
- **JPEG/JPG** - No transparency (opaque)
|
||||||
|
- **BMP** - Alpha transparency if 32-bit, otherwise opaque
|
||||||
|
- **GIF** - Basic transparency support
|
||||||
|
- **TIFF** - Alpha transparency if supported by variant
|
||||||
|
- **ICO** - Icon format with alpha
|
||||||
|
- **WMP** - Windows Media Photo
|
||||||
|
|
||||||
|
**Note**: Formats without alpha channels (JPEG, 24-bit BMP) will display as opaque images.
|
||||||
|
|
||||||
|
### System Requirements
|
||||||
|
- Windows Vista or later (for layered window support)
|
||||||
|
- WIC - Windows Imaging Component (included with Windows Vista+)
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Exported Functions
|
||||||
|
|
||||||
|
#### `show()`
|
||||||
|
Creates and displays a splash screen window in a background thread.
|
||||||
|
|
||||||
|
**Parameters** (parsed from NSIS stack in reverse order):
|
||||||
|
1. Timeout in milliseconds (integer, 0 = manual close only)
|
||||||
|
2. `/FADEIN [step]` - Optional fade in with configurable step (1-255, default: 15)
|
||||||
|
3. `/FADEOUT [step]` - Optional auto fade out with configurable step (1-255, default: 15)
|
||||||
|
4. `/NOCANCEL` - Disable click-to-close
|
||||||
|
5. `/NOUNLOAD` - **Required for stop() to work**: Keeps plugin in memory
|
||||||
|
6. Image path (string) - last parameter
|
||||||
|
|
||||||
|
**Behavior**:
|
||||||
|
- Cleans up any previous window/thread
|
||||||
|
- Loads image with WIC (`IWICImagingFactory`, `IWICBitmapDecoder`)
|
||||||
|
- Supports PNG, JPEG, BMP, GIF, TIFF, ICO, WMP formats
|
||||||
|
- Converts to 32bppPBGRA format for proper alpha handling
|
||||||
|
- Creates centered topmost layered window
|
||||||
|
- Uses `UpdateLayeredWindow` for per-pixel alpha transparency
|
||||||
|
- Starts background thread with window message loop
|
||||||
|
- Applies fade in effect if `/FADEIN` specified (30ms timer with configurable steps)
|
||||||
|
- Sets auto-close timer if timeout > 0
|
||||||
|
- Enables click-to-close unless `/NOCANCEL` specified
|
||||||
|
- **Returns control after fade-in completes** (non-blocking)
|
||||||
|
- Without `/NOUNLOAD`, plugin unloads and loses all state
|
||||||
|
|
||||||
|
**Return value**: Pushes "success" to NSIS stack
|
||||||
|
|
||||||
|
#### `stop()`
|
||||||
|
Closes the splash screen window with optional fade-out.
|
||||||
|
|
||||||
|
**Parameters** (parsed from NSIS stack):
|
||||||
|
1. `/FADEOUT [step]` - Optional fade out with configurable step (1-255, default: 15)
|
||||||
|
|
||||||
|
**Behavior**:
|
||||||
|
- If `/FADEOUT` specified:
|
||||||
|
- Starts fade-out timer (30ms intervals)
|
||||||
|
- Waits for fade to complete: `(255 / step) × 30ms`
|
||||||
|
- Destroys window after fade completes
|
||||||
|
- Otherwise:
|
||||||
|
- Immediately destroys window
|
||||||
|
- **Requires show() to have used /NOUNLOAD** or window handle will be NULL
|
||||||
|
|
||||||
|
**Return value**: Pushes "success" to NSIS stack
|
||||||
|
|
||||||
|
**Behavior**:
|
||||||
|
- Applies fade out effect if `/FADEOUT` specified (30ms timer, 15 steps)
|
||||||
|
- Destroys window and releases WIC resources
|
||||||
|
- Terminates splash thread and cleans up handles
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Image not showing
|
||||||
|
- Verify image file path is correct and accessible
|
||||||
|
- Check that image file is in a supported format (PNG, JPEG, BMP, GIF, TIFF, ICO)
|
||||||
|
- Ensure NSIS can read the file at install time
|
||||||
|
- Verify WIC can decode the specific image file variant
|
||||||
|
|
||||||
|
### No transparency
|
||||||
|
- Confirm PNG has alpha channel (not just RGB)
|
||||||
|
- Verify you're using the correct plugin DLL for your NSIS version
|
||||||
|
- Check Windows version supports layered windows (Vista+)
|
||||||
|
|
||||||
|
### Fade effects not smooth
|
||||||
|
- Normal behavior - fade uses 30ms timer with 15-step alpha increments
|
||||||
|
- Total fade duration ≈ 450-500ms
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Created for NSIS installer system.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- NSIS Plugin API
|
||||||
|
- WIC - Windows Imaging Component
|
||||||
|
- Windows Layered Windows API (`UpdateLayeredWindow`)
|
||||||
|
- Build system inspired by nsProcess plugin
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
|
||||||
|
### 1.0.0 (Current)
|
||||||
|
- Full PNG alpha transparency support via WIC
|
||||||
|
- Per-pixel alpha blending with `UpdateLayeredWindow`
|
||||||
|
- Fade in/out effects (30ms, 15 steps)
|
||||||
|
- Auto-close timer with configurable timeout
|
||||||
|
- Click-to-close with optional disable (`/NOCANCEL`)
|
||||||
|
- Blocks installer until splash closes
|
||||||
|
- x86-unicode, x64-unicode, and x86-ansi builds
|
||||||
|
- Zero compilation warnings with guarded macro definitions
|
||||||
|
- Optimized build system with parallel compilation support
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
; nsSplashPNG Advanced Example
|
||||||
|
; Shows how to use nsSplashPNG in a real installer with progress updates
|
||||||
|
|
||||||
|
Name "nsSplashPNG Advanced Test"
|
||||||
|
OutFile "nsSplashPNG_Advanced.exe"
|
||||||
|
InstallDir "$PROGRAMFILES\TestApp"
|
||||||
|
RequestExecutionLevel admin
|
||||||
|
|
||||||
|
!addplugindir "..\plugins\x86-unicode"
|
||||||
|
|
||||||
|
Page instfiles
|
||||||
|
|
||||||
|
Section "Main Installation"
|
||||||
|
; Copy splash image to temp directory
|
||||||
|
SetOutPath $TEMP
|
||||||
|
File "images\splash.png"
|
||||||
|
|
||||||
|
; Show splash with fade in, no cancel button
|
||||||
|
; CRITICAL: /NOUNLOAD required since we call stop() later
|
||||||
|
nsSplashPNG::show /NOUNLOAD 0 /NOCANCEL /FADEIN "$TEMP\splash.png"
|
||||||
|
|
||||||
|
; Simulate installation steps
|
||||||
|
DetailPrint "Installing files..."
|
||||||
|
SetOutPath "$INSTDIR"
|
||||||
|
Sleep 1000
|
||||||
|
|
||||||
|
DetailPrint "Configuring application..."
|
||||||
|
Sleep 1000
|
||||||
|
|
||||||
|
DetailPrint "Creating shortcuts..."
|
||||||
|
Sleep 1000
|
||||||
|
|
||||||
|
DetailPrint "Finalizing installation..."
|
||||||
|
Sleep 1000
|
||||||
|
|
||||||
|
; Close splash with fade out
|
||||||
|
nsSplashPNG::stop /FADEOUT
|
||||||
|
|
||||||
|
; Clean up
|
||||||
|
Delete "$TEMP\splash.png"
|
||||||
|
|
||||||
|
DetailPrint "Installation complete!"
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section "Uninstall"
|
||||||
|
RMDir /r "$INSTDIR"
|
||||||
|
SectionEnd
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
; nsSplashPNG Example Script
|
||||||
|
; Demonstrates the usage of the nsSplashPNG plugin with various options
|
||||||
|
|
||||||
|
Name "nsSplashPNG Test"
|
||||||
|
OutFile "nsSplashPNG_Example.exe"
|
||||||
|
SilentInstall silent
|
||||||
|
RequestExecutionLevel user
|
||||||
|
|
||||||
|
!addplugindir "..\plugins\x86-unicode"
|
||||||
|
|
||||||
|
Section
|
||||||
|
; Copy splash image to temp directory
|
||||||
|
SetOutPath $TEMP
|
||||||
|
File "images\splash.png"
|
||||||
|
|
||||||
|
; Example 1: Simple splash with auto-close after 3 seconds
|
||||||
|
; nsSplashPNG::show 3000 "test_image.png"
|
||||||
|
|
||||||
|
; Example 2: Splash with fade in effect, auto-close after 3 seconds
|
||||||
|
; nsSplashPNG::show /FADEIN 3000 "test_image.png"
|
||||||
|
|
||||||
|
; Example 3: Splash with fade out effect, auto-close after 3 seconds
|
||||||
|
; nsSplashPNG::show /FADEOUT 3000 "test_image.png"
|
||||||
|
|
||||||
|
; Example 4: Splash with fade in, manual close (no auto-close)
|
||||||
|
; CRITICAL: /NOUNLOAD required since we call stop() later
|
||||||
|
nsSplashPNG::show /NOUNLOAD 0 /FADEIN "$TEMP\splash.png"
|
||||||
|
|
||||||
|
; Simulate some installation work
|
||||||
|
Sleep 2000
|
||||||
|
|
||||||
|
; Example 5: Manual close with fade out
|
||||||
|
nsSplashPNG::stop /FADEOUT
|
||||||
|
|
||||||
|
; Clean up
|
||||||
|
Delete "$TEMP\splash.png"
|
||||||
|
|
||||||
|
MessageBox MB_OK "Installation complete!"
|
||||||
|
SectionEnd
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
; Test fade speed customization
|
||||||
|
Unicode true
|
||||||
|
!addplugindir "N:\Code\Workspace\Launchers\plugins\x86-unicode"
|
||||||
|
|
||||||
|
Name "Fade Speed Test"
|
||||||
|
OutFile "test_fade_speed.exe"
|
||||||
|
SilentInstall silent
|
||||||
|
RequestExecutionLevel user
|
||||||
|
|
||||||
|
Function .onInit
|
||||||
|
SetOutPath $TEMP
|
||||||
|
File "images\splash.png"
|
||||||
|
|
||||||
|
; Test 1: Default speed (step 15, ~510ms fade)
|
||||||
|
MessageBox MB_OK "Test 1: Default fade speed (step 15)$\n$\nFade duration: ~510ms"
|
||||||
|
nssplashpng::show /NOUNLOAD 60000 /FADEIN "$TEMP\splash.png"
|
||||||
|
Pop $0
|
||||||
|
Sleep 3000
|
||||||
|
nssplashpng::stop /FADEOUT
|
||||||
|
; No need to sleep - stop now waits for fade-out internally
|
||||||
|
|
||||||
|
; Test 2: Fast fade (step 51, ~150ms fade)
|
||||||
|
MessageBox MB_OK "Test 2: Fast fade (step 51)$\n$\nFade duration: ~150ms"
|
||||||
|
nssplashpng::show /NOUNLOAD 60000 /FADEIN 51 "$TEMP\splash.png"
|
||||||
|
Pop $0
|
||||||
|
Sleep 3000
|
||||||
|
nssplashpng::stop /FADEOUT 51
|
||||||
|
; No need to sleep - stop now waits for fade-out internally
|
||||||
|
|
||||||
|
; Test 3: Slow fade (step 5, ~1530ms fade)
|
||||||
|
MessageBox MB_OK "Test 3: Slow fade (step 5)$\n$\nFade duration: ~1530ms"
|
||||||
|
nssplashpng::show /NOUNLOAD 60000 /FADEIN 5 "$TEMP\splash.png"
|
||||||
|
Pop $0
|
||||||
|
Sleep 5000
|
||||||
|
nssplashpng::stop /FADEOUT 5
|
||||||
|
; No need to sleep - stop now waits for fade-out internally
|
||||||
|
|
||||||
|
; Test 4: Instant (step 255, ~30ms fade)
|
||||||
|
MessageBox MB_OK "Test 4: Instant fade (step 255)$\n$\nFade duration: ~30ms"
|
||||||
|
nssplashpng::show /NOUNLOAD 60000 /FADEIN 255 "$TEMP\splash.png"
|
||||||
|
Pop $0
|
||||||
|
Sleep 2000
|
||||||
|
nssplashpng::stop /FADEOUT 255
|
||||||
|
; No need to sleep - stop now waits for fade-out internally Delete "$TEMP\splash.png"
|
||||||
|
MessageBox MB_OK "All fade speed tests completed!"
|
||||||
|
Quit
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Section
|
||||||
|
SectionEnd
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
; Test different image formats with nsSplashPNG
|
||||||
|
Unicode true
|
||||||
|
|
||||||
|
Name "Format Test"
|
||||||
|
OutFile "test_formats.exe"
|
||||||
|
InstallDir "$TEMP\test"
|
||||||
|
|
||||||
|
!addplugindir "N:\Code\Workspace\Launchers\plugins\x86-unicode"
|
||||||
|
|
||||||
|
Section
|
||||||
|
SetOutPath $TEMP
|
||||||
|
File "images\splash.png"
|
||||||
|
File "images\splash.jpg"
|
||||||
|
File "images\splash.bmp"
|
||||||
|
File "images\splash.gif"
|
||||||
|
|
||||||
|
; Test PNG (with alpha)
|
||||||
|
MessageBox MB_OK "Testing PNG format (with alpha transparency)"
|
||||||
|
nssplashpng::show /NOUNLOAD 2000 /FADEIN /FADEOUT "$TEMP\splash.png"
|
||||||
|
Pop $0
|
||||||
|
|
||||||
|
; Test JPEG (no alpha)
|
||||||
|
MessageBox MB_OK "Testing JPEG format (no transparency)"
|
||||||
|
nssplashpng::show /NOUNLOAD 2000 /FADEIN /FADEOUT "$TEMP\splash.jpg"
|
||||||
|
Pop $0
|
||||||
|
|
||||||
|
; Test BMP (no alpha)
|
||||||
|
MessageBox MB_OK "Testing BMP format (no transparency)"
|
||||||
|
nssplashpng::show /NOUNLOAD 2000 /FADEIN /FADEOUT "$TEMP\splash.bmp"
|
||||||
|
Pop $0
|
||||||
|
|
||||||
|
; Test GIF (with transparency)
|
||||||
|
MessageBox MB_OK "Testing GIF format (basic transparency)"
|
||||||
|
nssplashpng::show /NOUNLOAD 2000 /FADEIN /FADEOUT "$TEMP\splash.gif"
|
||||||
|
Pop $0
|
||||||
|
|
||||||
|
Delete "$TEMP\splash.png"
|
||||||
|
Delete "$TEMP\splash.jpg"
|
||||||
|
Delete "$TEMP\splash.bmp"
|
||||||
|
Delete "$TEMP\splash.gif"
|
||||||
|
|
||||||
|
MessageBox MB_OK "All format tests completed!"
|
||||||
|
SectionEnd
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
; Test non-blocking behavior
|
||||||
|
Unicode true
|
||||||
|
!addplugindir "N:\Code\Workspace\Launchers\plugins\x86-unicode"
|
||||||
|
|
||||||
|
Name "Non-Blocking Test"
|
||||||
|
OutFile "test_nonblocking.exe"
|
||||||
|
SilentInstall silent
|
||||||
|
RequestExecutionLevel user
|
||||||
|
|
||||||
|
Function .onInit
|
||||||
|
SetOutPath $TEMP
|
||||||
|
File "images\splash.png"
|
||||||
|
|
||||||
|
; Show splash with fade-in, manual close (0 timeout)
|
||||||
|
; CRITICAL: /NOUNLOAD required since we call stop() later
|
||||||
|
MessageBox MB_OK "Showing splash with fade-in. Script should continue immediately after fade-in completes."
|
||||||
|
nssplashpng::show /NOUNLOAD 0 /FADEIN "$TEMP\splash.png"
|
||||||
|
Pop $0
|
||||||
|
|
||||||
|
; This should appear right after fade-in, while splash is still visible
|
||||||
|
MessageBox MB_OK "Splash is visible! Result: $0$\n$\nNow doing some work..."
|
||||||
|
Sleep 2000
|
||||||
|
|
||||||
|
MessageBox MB_OK "Work done. Now closing splash with fade-out..."
|
||||||
|
nssplashpng::stop /FADEOUT
|
||||||
|
Pop $0
|
||||||
|
|
||||||
|
MessageBox MB_OK "Splash closed! Stop result: $0"
|
||||||
|
Delete "$TEMP\splash.png"
|
||||||
|
Quit
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Section
|
||||||
|
SectionEnd
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 256 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,553 @@
|
|||||||
|
// nsSplashPNG - PNG Splash Screen Plugin for NSIS
|
||||||
|
// Full PNG alpha transparency support using WIC and layered windows
|
||||||
|
|
||||||
|
#define _WIN32_WINNT 0x0500
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <objbase.h>
|
||||||
|
#include <wincodec.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <tchar.h>
|
||||||
|
#include "pluginapi.h"
|
||||||
|
|
||||||
|
#pragma comment(lib, "windowscodecs.lib")
|
||||||
|
#pragma comment(lib, "ole32.lib")
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
#define RESOLUTION 30 // 30ms timer for smooth animation
|
||||||
|
#define FADEIN_DURATION 500 // 500ms fade in (255 / 15 * 30ms ≈ 510ms)
|
||||||
|
#define FADEOUT_DURATION 500 // 500ms fade out
|
||||||
|
#define CLASS_NAME _T("_nsSplashPNG")
|
||||||
|
|
||||||
|
// Global variables
|
||||||
|
HWND g_hWnd = NULL;
|
||||||
|
HINSTANCE g_hInstance = NULL;
|
||||||
|
HBITMAP g_hBitmap = NULL;
|
||||||
|
HDC g_hdcMem = NULL;
|
||||||
|
int g_imageWidth = 0;
|
||||||
|
int g_imageHeight = 0;
|
||||||
|
int g_timeoutMs = 0;
|
||||||
|
int g_fadeAlpha = 0;
|
||||||
|
bool g_fadeIn = false;
|
||||||
|
bool g_fadeOut = false;
|
||||||
|
bool g_noCancel = false;
|
||||||
|
int g_fadeInStep = 15; // Default: 15 alpha units per timer tick
|
||||||
|
int g_fadeOutStep = 15; // Default: 15 alpha units per timer tick
|
||||||
|
HANDLE g_hThread = NULL;
|
||||||
|
HANDLE g_hEvent = NULL;
|
||||||
|
|
||||||
|
// Monitor selection
|
||||||
|
#define MONITOR_MODE_PRIMARY 0 // Primary monitor (default)
|
||||||
|
#define MONITOR_MODE_MOUSE 1 // Monitor where mouse cursor is
|
||||||
|
#define MONITOR_MODE_INDEX 2 // Monitor by 1-based index
|
||||||
|
#define MONITOR_MODE_POINT 3 // Monitor nearest to a specific screen point
|
||||||
|
int g_monitorMode = MONITOR_MODE_PRIMARY;
|
||||||
|
int g_monitorIndex = 0; // 0-based index used internally
|
||||||
|
int g_monitorX = 0; // Screen X coordinate for MONITOR_MODE_POINT
|
||||||
|
int g_monitorY = 0; // Screen Y coordinate for MONITOR_MODE_POINT
|
||||||
|
|
||||||
|
struct MonitorEnumData {
|
||||||
|
int targetIndex;
|
||||||
|
int currentIndex;
|
||||||
|
HMONITOR hMonitor;
|
||||||
|
MONITORINFO info;
|
||||||
|
};
|
||||||
|
|
||||||
|
static BOOL CALLBACK GetMonitorByIndexProc(HMONITOR hMonitor, HDC, LPRECT, LPARAM dwData) {
|
||||||
|
MonitorEnumData* pData = (MonitorEnumData*)dwData;
|
||||||
|
if (pData->currentIndex == pData->targetIndex) {
|
||||||
|
pData->hMonitor = hMonitor;
|
||||||
|
pData->info.cbSize = sizeof(MONITORINFO);
|
||||||
|
GetMonitorInfo(hMonitor, &pData->info);
|
||||||
|
return FALSE; // Stop enumeration
|
||||||
|
}
|
||||||
|
pData->currentIndex++;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef BOOL (WINAPI *SetLayeredWindowAttributesProc)(HWND, COLORREF, BYTE, DWORD);
|
||||||
|
SetLayeredWindowAttributesProc g_SetLayeredWindowAttributes = NULL;
|
||||||
|
|
||||||
|
// Window procedure
|
||||||
|
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||||
|
switch (uMsg) {
|
||||||
|
case WM_CREATE:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case WM_PAINT: {
|
||||||
|
PAINTSTRUCT ps;
|
||||||
|
BeginPaint(hwnd, &ps);
|
||||||
|
// Don't draw anything - layered window uses UpdateLayeredWindow
|
||||||
|
EndPaint(hwnd, &ps);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WM_LBUTTONDOWN:
|
||||||
|
if (g_noCancel)
|
||||||
|
return 0;
|
||||||
|
// Intentional fallthrough to close splash on click
|
||||||
|
DestroyWindow(hwnd);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case WM_TIMER:
|
||||||
|
{
|
||||||
|
if (wParam == 1) { // Fade in timer
|
||||||
|
if (g_fadeAlpha < 255) {
|
||||||
|
g_fadeAlpha += g_fadeInStep;
|
||||||
|
if (g_fadeAlpha > 255) g_fadeAlpha = 255;
|
||||||
|
|
||||||
|
// Update alpha using UpdateLayeredWindow
|
||||||
|
if (g_hBitmap && g_hdcMem) {
|
||||||
|
HDC hdcScreen = GetDC(NULL);
|
||||||
|
POINT ptSrc = {0, 0};
|
||||||
|
SIZE sizeWnd = {g_imageWidth, g_imageHeight};
|
||||||
|
BLENDFUNCTION blend = {AC_SRC_OVER, 0, (BYTE)g_fadeAlpha, AC_SRC_ALPHA};
|
||||||
|
UpdateLayeredWindow(hwnd, hdcScreen, NULL, &sizeWnd, g_hdcMem, &ptSrc, 0, &blend, ULW_ALPHA);
|
||||||
|
ReleaseDC(NULL, hdcScreen);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
KillTimer(hwnd, 1);
|
||||||
|
}
|
||||||
|
} else if (wParam == 2) { // Close timer
|
||||||
|
KillTimer(hwnd, 2);
|
||||||
|
if (g_fadeOut) {
|
||||||
|
// Start fade out
|
||||||
|
SetTimer(hwnd, 3, RESOLUTION, NULL);
|
||||||
|
} else {
|
||||||
|
DestroyWindow(hwnd);
|
||||||
|
}
|
||||||
|
} else if (wParam == 3) { // Fade out timer
|
||||||
|
if (g_fadeAlpha > 0) {
|
||||||
|
g_fadeAlpha -= g_fadeOutStep;
|
||||||
|
if (g_fadeAlpha < 0) g_fadeAlpha = 0;
|
||||||
|
|
||||||
|
// Update alpha using UpdateLayeredWindow
|
||||||
|
if (g_hBitmap && g_hdcMem) {
|
||||||
|
HDC hdcScreen = GetDC(NULL);
|
||||||
|
POINT ptSrc = {0, 0};
|
||||||
|
SIZE sizeWnd = {g_imageWidth, g_imageHeight};
|
||||||
|
BLENDFUNCTION blend = {AC_SRC_OVER, 0, (BYTE)g_fadeAlpha, AC_SRC_ALPHA};
|
||||||
|
UpdateLayeredWindow(hwnd, hdcScreen, NULL, &sizeWnd, g_hdcMem, &ptSrc, 0, &blend, ULW_ALPHA);
|
||||||
|
ReleaseDC(NULL, hdcScreen);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
KillTimer(hwnd, 3);
|
||||||
|
DestroyWindow(hwnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WM_CLOSE:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case WM_DESTROY:
|
||||||
|
{
|
||||||
|
if (g_hdcMem) {
|
||||||
|
DeleteDC(g_hdcMem);
|
||||||
|
g_hdcMem = NULL;
|
||||||
|
}
|
||||||
|
if (g_hBitmap) {
|
||||||
|
DeleteObject(g_hBitmap);
|
||||||
|
g_hBitmap = NULL;
|
||||||
|
}
|
||||||
|
g_hWnd = NULL;
|
||||||
|
PostQuitMessage(0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DLL entry point
|
||||||
|
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) {
|
||||||
|
if (dwReason == DLL_PROCESS_ATTACH) {
|
||||||
|
g_hInstance = hInstance;
|
||||||
|
// This is acceptable for NSIS plugins which are loaded/unloaded by the installer process
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thread function to create and manage the splash window
|
||||||
|
DWORD WINAPI SplashThreadProc(LPVOID lpParam) {
|
||||||
|
// Register window class
|
||||||
|
WNDCLASS wc = {0};
|
||||||
|
wc.lpfnWndProc = WndProc;
|
||||||
|
wc.hInstance = g_hInstance;
|
||||||
|
wc.lpszClassName = CLASS_NAME;
|
||||||
|
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
||||||
|
RegisterClass(&wc);
|
||||||
|
|
||||||
|
// Calculate centered position on the target monitor
|
||||||
|
RECT monitorRect = {0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)};
|
||||||
|
if (g_monitorMode == MONITOR_MODE_MOUSE) {
|
||||||
|
POINT pt;
|
||||||
|
if (GetCursorPos(&pt)) {
|
||||||
|
HMONITOR hMon = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
|
||||||
|
MONITORINFO mi = {sizeof(mi)};
|
||||||
|
if (GetMonitorInfo(hMon, &mi))
|
||||||
|
monitorRect = mi.rcMonitor;
|
||||||
|
}
|
||||||
|
} else if (g_monitorMode == MONITOR_MODE_INDEX) {
|
||||||
|
MonitorEnumData data = {};
|
||||||
|
data.targetIndex = g_monitorIndex;
|
||||||
|
data.info.cbSize = sizeof(MONITORINFO);
|
||||||
|
EnumDisplayMonitors(NULL, NULL, GetMonitorByIndexProc, (LPARAM)&data);
|
||||||
|
if (data.hMonitor)
|
||||||
|
monitorRect = data.info.rcMonitor;
|
||||||
|
} else if (g_monitorMode == MONITOR_MODE_POINT) {
|
||||||
|
POINT pt = {g_monitorX, g_monitorY};
|
||||||
|
HMONITOR hMon = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
|
||||||
|
MONITORINFO mi = {sizeof(mi)};
|
||||||
|
if (GetMonitorInfo(hMon, &mi))
|
||||||
|
monitorRect = mi.rcMonitor;
|
||||||
|
}
|
||||||
|
int x = monitorRect.left + (monitorRect.right - monitorRect.left - g_imageWidth) / 2;
|
||||||
|
int y = monitorRect.top + (monitorRect.bottom - monitorRect.top - g_imageHeight) / 2;
|
||||||
|
|
||||||
|
// Create layered window
|
||||||
|
g_hWnd = CreateWindowEx(
|
||||||
|
WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW,
|
||||||
|
CLASS_NAME,
|
||||||
|
_T(""),
|
||||||
|
WS_POPUP,
|
||||||
|
x, y, g_imageWidth, g_imageHeight,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
g_hInstance,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!g_hWnd) {
|
||||||
|
SetEvent(g_hEvent);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display using UpdateLayeredWindow
|
||||||
|
g_fadeAlpha = g_fadeIn ? 0 : 255;
|
||||||
|
|
||||||
|
HDC hdcScreen = GetDC(NULL);
|
||||||
|
POINT ptSrc = {0, 0};
|
||||||
|
SIZE sizeWnd = {g_imageWidth, g_imageHeight};
|
||||||
|
BLENDFUNCTION blend = {AC_SRC_OVER, 0, (BYTE)g_fadeAlpha, AC_SRC_ALPHA};
|
||||||
|
UpdateLayeredWindow(g_hWnd, hdcScreen, NULL, &sizeWnd, g_hdcMem, &ptSrc, 0, &blend, ULW_ALPHA);
|
||||||
|
ReleaseDC(NULL, hdcScreen);
|
||||||
|
|
||||||
|
ShowWindow(g_hWnd, SW_SHOW);
|
||||||
|
SetForegroundWindow(g_hWnd);
|
||||||
|
|
||||||
|
// Start timers
|
||||||
|
if (g_fadeIn) {
|
||||||
|
SetTimer(g_hWnd, 1, RESOLUTION, NULL);
|
||||||
|
}
|
||||||
|
if (g_timeoutMs > 0) {
|
||||||
|
SetTimer(g_hWnd, 2, g_timeoutMs, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signal that window is created
|
||||||
|
SetEvent(g_hEvent);
|
||||||
|
|
||||||
|
// Message loop
|
||||||
|
MSG msg;
|
||||||
|
while (GetMessage(&msg, NULL, 0, 0)) {
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessage(&msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
UnregisterClass(CLASS_NAME, g_hInstance);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load PNG using Windows Imaging Component (WIC)
|
||||||
|
HBITMAP LoadPNGWithWIC(const TCHAR* filename, int* width, int* height) {
|
||||||
|
|
||||||
|
HRESULT hr;
|
||||||
|
IWICImagingFactory* pFactory = NULL;
|
||||||
|
IWICBitmapDecoder* pDecoder = NULL;
|
||||||
|
IWICBitmapFrameDecode* pFrame = NULL;
|
||||||
|
IWICFormatConverter* pConverter = NULL;
|
||||||
|
HBITMAP hBitmap = NULL;
|
||||||
|
|
||||||
|
// Initialize COM (don't check failure - might already be initialized by NSIS)
|
||||||
|
CoInitialize(NULL);
|
||||||
|
|
||||||
|
hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
|
||||||
|
IID_IWICImagingFactory, (LPVOID*)&pFactory);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr = pFactory->CreateDecoderFromFilename((LPCWSTR)filename, NULL, GENERIC_READ,
|
||||||
|
WICDecodeMetadataCacheOnLoad, &pDecoder);
|
||||||
|
if (FAILED(hr)) goto cleanup;
|
||||||
|
|
||||||
|
hr = pDecoder->GetFrame(0, &pFrame);
|
||||||
|
if (FAILED(hr)) goto cleanup;
|
||||||
|
|
||||||
|
UINT imgWidth, imgHeight;
|
||||||
|
pFrame->GetSize(&imgWidth, &imgHeight);
|
||||||
|
*width = imgWidth;
|
||||||
|
*height = imgHeight;
|
||||||
|
|
||||||
|
hr = pFactory->CreateFormatConverter(&pConverter);
|
||||||
|
if (FAILED(hr)) goto cleanup;
|
||||||
|
|
||||||
|
hr = pConverter->Initialize(pFrame, GUID_WICPixelFormat32bppPBGRA,
|
||||||
|
WICBitmapDitherTypeNone, NULL, 0.0,
|
||||||
|
WICBitmapPaletteTypeMedianCut);
|
||||||
|
if (FAILED(hr)) goto cleanup;
|
||||||
|
|
||||||
|
// Declare variables before goto
|
||||||
|
BITMAPINFO bmi;
|
||||||
|
ZeroMemory(&bmi, sizeof(bmi));
|
||||||
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||||
|
bmi.bmiHeader.biWidth = imgWidth;
|
||||||
|
bmi.bmiHeader.biHeight = -(int)imgHeight;
|
||||||
|
bmi.bmiHeader.biPlanes = 1;
|
||||||
|
bmi.bmiHeader.biBitCount = 32;
|
||||||
|
bmi.bmiHeader.biCompression = BI_RGB;
|
||||||
|
|
||||||
|
void* pBits;
|
||||||
|
pBits = NULL;
|
||||||
|
HDC hdc;
|
||||||
|
hdc = GetDC(NULL);
|
||||||
|
hBitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &pBits, NULL, 0);
|
||||||
|
ReleaseDC(NULL, hdc);
|
||||||
|
|
||||||
|
if (!hBitmap) goto cleanup;
|
||||||
|
|
||||||
|
hr = pConverter->CopyPixels(NULL, imgWidth * 4, imgWidth * imgHeight * 4, (BYTE*)pBits);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
DeleteObject(hBitmap);
|
||||||
|
hBitmap = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (pConverter) pConverter->Release();
|
||||||
|
if (pFrame) pFrame->Release();
|
||||||
|
if (pDecoder) pDecoder->Release();
|
||||||
|
if (pFactory) pFactory->Release();
|
||||||
|
// Don't call CoUninitialize - let NSIS handle COM cleanup
|
||||||
|
// CoUninitialize();
|
||||||
|
|
||||||
|
return hBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugin exported function: show
|
||||||
|
extern "C" void __declspec(dllexport) show(HWND hwndParent, int string_size, TCHAR *variables, stack_t **stacktop) {
|
||||||
|
EXDLL_INIT();
|
||||||
|
|
||||||
|
// Close any existing window and thread first
|
||||||
|
if (g_hWnd && IsWindow(g_hWnd)) {
|
||||||
|
DestroyWindow(g_hWnd);
|
||||||
|
// Give thread time to process WM_DESTROY and exit message loop
|
||||||
|
Sleep(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for previous thread to terminate if it exists
|
||||||
|
if (g_hThread) {
|
||||||
|
// Wait briefly for thread to terminate
|
||||||
|
WaitForSingleObject(g_hThread, 500);
|
||||||
|
CloseHandle(g_hThread);
|
||||||
|
g_hThread = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure window handle is cleared
|
||||||
|
g_hWnd = NULL;
|
||||||
|
|
||||||
|
// Parameters come from stack in order: timeout, flags..., imagepath
|
||||||
|
// Read timeout first
|
||||||
|
TCHAR param[MAX_PATH];
|
||||||
|
TCHAR imagePath[MAX_PATH] = {0};
|
||||||
|
|
||||||
|
popstring(param);
|
||||||
|
g_timeoutMs = myatoi(param);
|
||||||
|
|
||||||
|
// Reset flags
|
||||||
|
g_fadeIn = false;
|
||||||
|
g_fadeOut = false;
|
||||||
|
g_noCancel = false;
|
||||||
|
g_fadeInStep = 15; // Default step
|
||||||
|
g_fadeOutStep = 15; // Default step
|
||||||
|
g_monitorMode = MONITOR_MODE_PRIMARY;
|
||||||
|
g_monitorIndex = 0;
|
||||||
|
g_monitorX = 0;
|
||||||
|
g_monitorY = 0;
|
||||||
|
|
||||||
|
// Read flags and image path
|
||||||
|
while (popstring(param) == 0 && param[0] == _T('/')) {
|
||||||
|
if (_tcsicmp(param, _T("/FADEIN")) == 0) {
|
||||||
|
g_fadeIn = true;
|
||||||
|
// Check if next parameter is a number (custom step)
|
||||||
|
TCHAR nextParam[MAX_PATH];
|
||||||
|
if (popstring(nextParam) == 0 && nextParam[0] != _T('/')) {
|
||||||
|
int customStep = myatoi(nextParam);
|
||||||
|
if (customStep > 0 && customStep <= 255) {
|
||||||
|
g_fadeInStep = customStep;
|
||||||
|
} else {
|
||||||
|
// Not a valid step, push it back (it's probably the image path)
|
||||||
|
lstrcpy(param, nextParam);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Push it back for next iteration
|
||||||
|
lstrcpy(param, nextParam);
|
||||||
|
}
|
||||||
|
} else if (_tcsicmp(param, _T("/FADEOUT")) == 0) {
|
||||||
|
g_fadeOut = true;
|
||||||
|
// Check if next parameter is a number (custom step)
|
||||||
|
TCHAR nextParam[MAX_PATH];
|
||||||
|
if (popstring(nextParam) == 0 && nextParam[0] != _T('/')) {
|
||||||
|
int customStep = myatoi(nextParam);
|
||||||
|
if (customStep > 0 && customStep <= 255) {
|
||||||
|
g_fadeOutStep = customStep;
|
||||||
|
} else {
|
||||||
|
// Not a valid step, push it back
|
||||||
|
lstrcpy(param, nextParam);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Push it back for next iteration
|
||||||
|
lstrcpy(param, nextParam);
|
||||||
|
}
|
||||||
|
} else if (_tcsicmp(param, _T("/NOCANCEL")) == 0) {
|
||||||
|
g_noCancel = true;
|
||||||
|
} else if (_tcsicmp(param, _T("/MONITOR")) == 0) {
|
||||||
|
TCHAR monParam[64] = {0};
|
||||||
|
if (popstring(monParam) == 0) {
|
||||||
|
if (_tcsicmp(monParam, _T("CURRENT")) == 0 || _tcsicmp(monParam, _T("MOUSE")) == 0) {
|
||||||
|
g_monitorMode = MONITOR_MODE_MOUSE;
|
||||||
|
} else if (_tcsicmp(monParam, _T("PRIMARY")) == 0) {
|
||||||
|
g_monitorMode = MONITOR_MODE_PRIMARY;
|
||||||
|
} else if (_tcsicmp(monParam, _T("POINT")) == 0) {
|
||||||
|
TCHAR xParam[64] = {0}, yParam[64] = {0};
|
||||||
|
if (popstring(xParam) == 0 && popstring(yParam) == 0) {
|
||||||
|
g_monitorMode = MONITOR_MODE_POINT;
|
||||||
|
g_monitorX = myatoi(xParam);
|
||||||
|
g_monitorY = myatoi(yParam);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int idx = myatoi(monParam);
|
||||||
|
if (idx >= 1) {
|
||||||
|
g_monitorMode = MONITOR_MODE_INDEX;
|
||||||
|
g_monitorIndex = idx - 1; // Convert to 0-based
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last parameter read (not starting with /) is the image path
|
||||||
|
lstrcpy(imagePath, param);
|
||||||
|
|
||||||
|
// Validate image path
|
||||||
|
if (imagePath[0] == 0) {
|
||||||
|
pushstring(_T("error: no image path"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load PNG image with WIC
|
||||||
|
g_hBitmap = LoadPNGWithWIC(imagePath, &g_imageWidth, &g_imageHeight);
|
||||||
|
if (!g_hBitmap) {
|
||||||
|
pushstring(_T("error: cannot load image"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create memory DC for the bitmap
|
||||||
|
HDC hdc = GetDC(NULL);
|
||||||
|
g_hdcMem = CreateCompatibleDC(hdc);
|
||||||
|
ReleaseDC(NULL, hdc);
|
||||||
|
SelectObject(g_hdcMem, g_hBitmap);
|
||||||
|
|
||||||
|
// Create event for synchronization
|
||||||
|
g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||||
|
if (!g_hEvent) {
|
||||||
|
pushstring(_T("error: cannot create event"));
|
||||||
|
if (g_hdcMem) DeleteDC(g_hdcMem);
|
||||||
|
if (g_hBitmap) DeleteObject(g_hBitmap);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create thread to manage the window
|
||||||
|
g_hThread = CreateThread(NULL, 0, SplashThreadProc, NULL, 0, NULL);
|
||||||
|
if (!g_hThread) {
|
||||||
|
pushstring(_T("error: cannot create thread"));
|
||||||
|
CloseHandle(g_hEvent);
|
||||||
|
if (g_hdcMem) DeleteDC(g_hdcMem);
|
||||||
|
if (g_hBitmap) DeleteObject(g_hBitmap);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for window to be created (with timeout)
|
||||||
|
DWORD result = WaitForSingleObject(g_hEvent, 2000);
|
||||||
|
CloseHandle(g_hEvent);
|
||||||
|
g_hEvent = NULL;
|
||||||
|
|
||||||
|
if (result != WAIT_OBJECT_0 || !g_hWnd) {
|
||||||
|
pushstring(_T("error: window not created"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If fade-in is enabled, wait for it to complete before returning
|
||||||
|
if (g_fadeIn) {
|
||||||
|
// Calculate fade-in duration: (255 / step) * 30ms + 100ms margin
|
||||||
|
int fadeSteps = (255 + g_fadeInStep - 1) / g_fadeInStep; // Round up
|
||||||
|
int fadeDuration = fadeSteps * RESOLUTION + 100;
|
||||||
|
Sleep(fadeDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return control to NSIS immediately - window stays open in background thread
|
||||||
|
// Window will close either:
|
||||||
|
// 1. When timeout expires (if g_timeoutMs > 0)
|
||||||
|
// 2. When user clicks (if !g_noCancel)
|
||||||
|
// 3. When stop() is called explicitly
|
||||||
|
pushstring(_T("success"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugin exported function: stop
|
||||||
|
extern "C" void __declspec(dllexport) stop(HWND hwndParent, int string_size, TCHAR *variables, stack_t **stacktop) {
|
||||||
|
EXDLL_INIT();
|
||||||
|
|
||||||
|
// Parse flags
|
||||||
|
TCHAR param[256] = {0};
|
||||||
|
bool fadeOut = false;
|
||||||
|
|
||||||
|
while (popstring(param) == 0) {
|
||||||
|
if (_tcsicmp(param, _T("/FADEOUT")) == 0) {
|
||||||
|
fadeOut = true;
|
||||||
|
// Check if next parameter is a number (custom step)
|
||||||
|
TCHAR nextParam[MAX_PATH];
|
||||||
|
if (popstring(nextParam) == 0 && nextParam[0] != _T('/')) {
|
||||||
|
int customStep = myatoi(nextParam);
|
||||||
|
if (customStep > 0 && customStep <= 255) {
|
||||||
|
g_fadeOutStep = customStep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_hWnd && IsWindow(g_hWnd)) {
|
||||||
|
if (fadeOut) {
|
||||||
|
// Start fade out timer
|
||||||
|
SetTimer(g_hWnd, 3, RESOLUTION, NULL);
|
||||||
|
|
||||||
|
// Calculate expected fade-out duration and wait
|
||||||
|
int fadeSteps = (255 + g_fadeOutStep - 1) / g_fadeOutStep;
|
||||||
|
int fadeDuration = fadeSteps * RESOLUTION + 200;
|
||||||
|
|
||||||
|
Sleep(fadeDuration);
|
||||||
|
|
||||||
|
// Give extra time for window destruction and thread cleanup
|
||||||
|
Sleep(100);
|
||||||
|
} else {
|
||||||
|
// No fade-out, destroy immediately
|
||||||
|
DestroyWindow(g_hWnd);
|
||||||
|
Sleep(100); // Give thread time to cleanup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pushstring(_T("success"));
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* apih
|
||||||
|
*
|
||||||
|
* This file is a part of NSIS.
|
||||||
|
*
|
||||||
|
* Copyright (C) 1999-2013 Nullsoft and Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the zlib/libpng license (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
*
|
||||||
|
* Licence details can be found in the file COPYING.
|
||||||
|
*
|
||||||
|
* This software is provided 'as-is', without any express or implied
|
||||||
|
* warranty.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _NSIS_EXEHEAD_API_H_
|
||||||
|
#define _NSIS_EXEHEAD_API_H_
|
||||||
|
|
||||||
|
// Starting with NSIS 2.42, you can check the version of the plugin API in exec_flags->plugin_api_version
|
||||||
|
// The format is 0xXXXXYYYY where X is the major version and Y is the minor version (MAKELONG(y,x))
|
||||||
|
// When doing version checks, always remember to use >=, ex: if (pX->exec_flags->plugin_api_version >= NSISPIAPIVER_1_0) {}
|
||||||
|
|
||||||
|
#define NSISPIAPIVER_1_0 0x00010000
|
||||||
|
#define NSISPIAPIVER_CURR NSISPIAPIVER_1_0
|
||||||
|
|
||||||
|
// NSIS Plug-In Callback Messages
|
||||||
|
enum NSPIM
|
||||||
|
{
|
||||||
|
NSPIM_UNLOAD, // This is the last message a plugin gets, do final cleanup
|
||||||
|
NSPIM_GUIUNLOAD, // Called after .onGUIEnd
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prototype for callbacks registered with extra_parameters->RegisterPluginCallback()
|
||||||
|
// Return NULL for unknown messages
|
||||||
|
// Should always be __cdecl for future expansion possibilities
|
||||||
|
typedef UINT_PTR (*NSISPLUGINCALLBACK)(enum NSPIM);
|
||||||
|
|
||||||
|
// extra_parameters data structures containing other interesting stuff
|
||||||
|
// but the stack, variables and HWND passed on to plug-ins.
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int autoclose;
|
||||||
|
int all_user_var;
|
||||||
|
int exec_error;
|
||||||
|
int abort;
|
||||||
|
int exec_reboot; // NSIS_SUPPORT_REBOOT
|
||||||
|
int reboot_called; // NSIS_SUPPORT_REBOOT
|
||||||
|
int XXX_cur_insttype; // depreacted
|
||||||
|
int plugin_api_version; // see NSISPIAPIVER_CURR
|
||||||
|
// used to be XXX_insttype_changed
|
||||||
|
int silent; // NSIS_CONFIG_SILENT_SUPPORT
|
||||||
|
int instdir_error;
|
||||||
|
int rtl;
|
||||||
|
int errlvl;
|
||||||
|
int alter_reg_view;
|
||||||
|
int status_update;
|
||||||
|
} exec_flags_t;
|
||||||
|
|
||||||
|
#ifndef NSISCALL
|
||||||
|
# define NSISCALL __stdcall
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
exec_flags_t *exec_flags;
|
||||||
|
int (NSISCALL *ExecuteCodeSegment)(int, HWND);
|
||||||
|
void (NSISCALL *validate_filename)(TCHAR *);
|
||||||
|
int (NSISCALL *RegisterPluginCallback)(HMODULE, NSISPLUGINCALLBACK); // returns 0 on success, 1 if already registered and < 0 on errors
|
||||||
|
} extra_parameters;
|
||||||
|
|
||||||
|
// Definitions for page showing plug-ins
|
||||||
|
// See Ui.c to understand better how they're used
|
||||||
|
|
||||||
|
// sent to the outer window to tell it to go to the next inner window
|
||||||
|
#define WM_NOTIFY_OUTER_NEXT (WM_USER+0x8)
|
||||||
|
|
||||||
|
// custom pages should send this message to let NSIS know they're ready
|
||||||
|
#define WM_NOTIFY_CUSTOM_READY (WM_USER+0xd)
|
||||||
|
|
||||||
|
// sent as wParam with WM_NOTIFY_OUTER_NEXT when user cancels - heed its warning
|
||||||
|
#define NOTIFY_BYE_BYE 'x'
|
||||||
|
|
||||||
|
#endif /* _PLUGIN_H_ */
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
LIBRARY nsSplashPNG
|
||||||
|
EXPORTS
|
||||||
|
show @1
|
||||||
|
stop @2
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<ProjectConfiguration Include="Release|Win32">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release Unicode|Win32">
|
||||||
|
<Configuration>Release Unicode</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|x64">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release Unicode|x64">
|
||||||
|
<Configuration>Release Unicode</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<VCProjectVersion>17.0</VCProjectVersion>
|
||||||
|
<ProjectGuid>{B8F3A9D1-4C5E-4F2A-9B1C-8D7E6F5A4B3C}</ProjectGuid>
|
||||||
|
<Keyword>Win32Proj</Keyword>
|
||||||
|
<RootNamespace>nsSplashPNG</RootNamespace>
|
||||||
|
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>MultiByte</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Unicode|Win32'"
|
||||||
|
Label="Configuration">
|
||||||
|
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>MultiByte</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Unicode|x64'"
|
||||||
|
Label="Configuration">
|
||||||
|
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="Shared">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"
|
||||||
|
Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"
|
||||||
|
Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets"
|
||||||
|
Condition="'$(Configuration)|$(Platform)'=='Release Unicode|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"
|
||||||
|
Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"
|
||||||
|
Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"
|
||||||
|
Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"
|
||||||
|
Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets"
|
||||||
|
Condition="'$(Configuration)|$(Platform)'=='Release Unicode|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"
|
||||||
|
Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"
|
||||||
|
Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>false</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>
|
||||||
|
WIN32;NDEBUG;_WINDOWS;_USRDLL;EXDLL_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||||
|
<Optimization>MaxSpeed</Optimization>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Windows</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||||
|
<AdditionalDependencies>
|
||||||
|
kernel32.lib;user32.lib;gdi32.lib;ole32.lib;vfw32.lib;winmm.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
|
<ModuleDefinitionFile>nsSplashPNG.def</ModuleDefinitionFile>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release Unicode|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>false</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>
|
||||||
|
WIN32;NDEBUG;_WINDOWS;_USRDLL;EXDLL_EXPORTS;UNICODE;_UNICODE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||||
|
<Optimization>MaxSpeed</Optimization>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Windows</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||||
|
<AdditionalDependencies>
|
||||||
|
kernel32.lib;user32.lib;gdi32.lib;ole32.lib;vfw32.lib;winmm.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
|
<ModuleDefinitionFile>nsSplashPNG.def</ModuleDefinitionFile>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>false</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>
|
||||||
|
NDEBUG;_WINDOWS;_USRDLL;EXDLL_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||||
|
<Optimization>MaxSpeed</Optimization>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Windows</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||||
|
<AdditionalDependencies>
|
||||||
|
kernel32.lib;user32.lib;gdi32.lib;ole32.lib;vfw32.lib;winmm.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
|
<ModuleDefinitionFile>nsSplashPNG.def</ModuleDefinitionFile>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release Unicode|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>false</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>
|
||||||
|
NDEBUG;_WINDOWS;_USRDLL;EXDLL_EXPORTS;UNICODE;_UNICODE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||||
|
<Optimization>MaxSpeed</Optimization>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Windows</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||||
|
<AdditionalDependencies>
|
||||||
|
windowscodecs.lib;ole32.lib;kernel32.lib;user32.lib;gdi32.lib;msimg32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
|
<ModuleDefinitionFile>nsSplashPNG.def</ModuleDefinitionFile>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="SplashPNG.cpp" />
|
||||||
|
<ClCompile Include="pluginapi.c" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="pluginapi.h" />
|
||||||
|
<ClInclude Include="api.h" />
|
||||||
|
<ClInclude Include="nsis_tchar.h" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
</ImportGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
/*
|
||||||
|
* nsis_tchar.h
|
||||||
|
*
|
||||||
|
* This file is a part of NSIS.
|
||||||
|
*
|
||||||
|
* Copyright (C) 1999-2013 Nullsoft and Contributors
|
||||||
|
*
|
||||||
|
* This software is provided 'as-is', without any express or implied
|
||||||
|
* warranty.
|
||||||
|
*
|
||||||
|
* For Unicode support by Jim Park -- 08/30/2007
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Jim Park: Only those we use are listed here.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef _UNICODE
|
||||||
|
|
||||||
|
#ifndef _T
|
||||||
|
#define __T(x) L ## x
|
||||||
|
#define _T(x) __T(x)
|
||||||
|
#define _TEXT(x) __T(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _TCHAR_DEFINED
|
||||||
|
#define _TCHAR_DEFINED
|
||||||
|
#if !defined(_NATIVE_WCHAR_T_DEFINED) && !defined(_WCHAR_T_DEFINED)
|
||||||
|
typedef unsigned short TCHAR;
|
||||||
|
#else
|
||||||
|
typedef wchar_t TCHAR;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// program
|
||||||
|
#define _tenviron _wenviron
|
||||||
|
#define __targv __wargv
|
||||||
|
|
||||||
|
// printfs
|
||||||
|
#define _ftprintf fwprintf
|
||||||
|
#define _sntprintf _snwprintf
|
||||||
|
#if (defined(_MSC_VER) && (_MSC_VER<=1310)) || defined(__MINGW32__)
|
||||||
|
# define _stprintf swprintf
|
||||||
|
#else
|
||||||
|
# define _stprintf _swprintf
|
||||||
|
#endif
|
||||||
|
#define _tprintf wprintf
|
||||||
|
#define _vftprintf vfwprintf
|
||||||
|
#define _vsntprintf _vsnwprintf
|
||||||
|
#if defined(_MSC_VER) && (_MSC_VER<=1310)
|
||||||
|
# define _vstprintf vswprintf
|
||||||
|
#else
|
||||||
|
# ifndef _vstprintf
|
||||||
|
# define _vstprintf _vswprintf
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// scanfs
|
||||||
|
#define _tscanf wscanf
|
||||||
|
#define _stscanf swscanf
|
||||||
|
|
||||||
|
// string manipulations
|
||||||
|
#define _tcscat wcscat
|
||||||
|
#define _tcschr wcschr
|
||||||
|
#define _tcsclen wcslen
|
||||||
|
#define _tcscpy wcscpy
|
||||||
|
#define _tcsdup _wcsdup
|
||||||
|
#define _tcslen wcslen
|
||||||
|
#define _tcsnccpy wcsncpy
|
||||||
|
#define _tcsncpy wcsncpy
|
||||||
|
#define _tcsrchr wcsrchr
|
||||||
|
#define _tcsstr wcsstr
|
||||||
|
#ifndef _tcstok
|
||||||
|
#define _tcstok wcstok
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// string comparisons
|
||||||
|
#define _tcscmp wcscmp
|
||||||
|
#define _tcsicmp _wcsicmp
|
||||||
|
#define _tcsncicmp _wcsnicmp
|
||||||
|
#define _tcsncmp wcsncmp
|
||||||
|
#define _tcsnicmp _wcsnicmp
|
||||||
|
|
||||||
|
// upper / lower
|
||||||
|
#define _tcslwr _wcslwr
|
||||||
|
#define _tcsupr _wcsupr
|
||||||
|
#define _totlower towlower
|
||||||
|
#define _totupper towupper
|
||||||
|
|
||||||
|
// conversions to numbers
|
||||||
|
#define _tcstoi64 _wcstoi64
|
||||||
|
#define _tcstol wcstol
|
||||||
|
#define _tcstoul wcstoul
|
||||||
|
#define _tstof _wtof
|
||||||
|
#define _tstoi _wtoi
|
||||||
|
#define _tstoi64 _wtoi64
|
||||||
|
#define _ttoi _wtoi
|
||||||
|
#define _ttoi64 _wtoi64
|
||||||
|
#define _ttol _wtol
|
||||||
|
|
||||||
|
// conversion from numbers to strings
|
||||||
|
#define _itot _itow
|
||||||
|
#define _ltot _ltow
|
||||||
|
#define _i64tot _i64tow
|
||||||
|
#define _ui64tot _ui64tow
|
||||||
|
|
||||||
|
// file manipulations
|
||||||
|
#define _tfopen _wfopen
|
||||||
|
#define _topen _wopen
|
||||||
|
#define _tremove _wremove
|
||||||
|
#define _tunlink _wunlink
|
||||||
|
|
||||||
|
// reading and writing to i/o
|
||||||
|
#define _fgettc fgetwc
|
||||||
|
#define _fgetts fgetws
|
||||||
|
#define _fputts fputws
|
||||||
|
#define _gettchar getwchar
|
||||||
|
|
||||||
|
// directory
|
||||||
|
#define _tchdir _wchdir
|
||||||
|
|
||||||
|
// environment
|
||||||
|
#define _tgetenv _wgetenv
|
||||||
|
#define _tsystem _wsystem
|
||||||
|
|
||||||
|
// time
|
||||||
|
#define _tcsftime wcsftime
|
||||||
|
|
||||||
|
#else // ANSI
|
||||||
|
|
||||||
|
#ifndef _T
|
||||||
|
#define _T(x) x
|
||||||
|
#define _TEXT(x) x
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _TCHAR_DEFINED
|
||||||
|
#define _TCHAR_DEFINED
|
||||||
|
typedef char TCHAR;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// program
|
||||||
|
#ifndef _tenviron
|
||||||
|
#define _tenviron environ
|
||||||
|
#endif
|
||||||
|
#define __targv __argv
|
||||||
|
|
||||||
|
// printfs
|
||||||
|
#define _ftprintf fprintf
|
||||||
|
#define _sntprintf _snprintf
|
||||||
|
#define _stprintf sprintf
|
||||||
|
#define _tprintf printf
|
||||||
|
#define _vftprintf vfprintf
|
||||||
|
#define _vsntprintf _vsnprintf
|
||||||
|
#define _vstprintf vsprintf
|
||||||
|
|
||||||
|
// scanfs
|
||||||
|
#define _tscanf scanf
|
||||||
|
#define _stscanf sscanf
|
||||||
|
|
||||||
|
// string manipulations
|
||||||
|
#define _tcscat strcat
|
||||||
|
#define _tcschr strchr
|
||||||
|
#define _tcsclen strlen
|
||||||
|
#define _tcscnlen strnlen
|
||||||
|
#define _tcscpy strcpy
|
||||||
|
#define _tcsdup _strdup
|
||||||
|
#define _tcslen strlen
|
||||||
|
#define _tcsnccpy strncpy
|
||||||
|
#define _tcsrchr strrchr
|
||||||
|
#define _tcsstr strstr
|
||||||
|
#define _tcstok strtok
|
||||||
|
|
||||||
|
// string comparisons
|
||||||
|
#define _tcscmp strcmp
|
||||||
|
#define _tcsicmp _stricmp
|
||||||
|
#define _tcsncmp strncmp
|
||||||
|
#define _tcsncicmp _strnicmp
|
||||||
|
#define _tcsnicmp _strnicmp
|
||||||
|
|
||||||
|
// upper / lower
|
||||||
|
#define _tcslwr _strlwr
|
||||||
|
#define _tcsupr _strupr
|
||||||
|
|
||||||
|
#ifndef _totupper
|
||||||
|
#define _totupper toupper
|
||||||
|
#endif
|
||||||
|
#ifndef _totlower
|
||||||
|
#define _totlower tolower
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// conversions to numbers
|
||||||
|
#define _tcstol strtol
|
||||||
|
#define _tcstoul strtoul
|
||||||
|
#define _tstof atof
|
||||||
|
#define _tstoi atoi
|
||||||
|
#define _tstoi64 _atoi64
|
||||||
|
#define _tstoi64 _atoi64
|
||||||
|
#define _ttoi atoi
|
||||||
|
#define _ttoi64 _atoi64
|
||||||
|
#define _ttol atol
|
||||||
|
|
||||||
|
// conversion from numbers to strings
|
||||||
|
#define _i64tot _i64toa
|
||||||
|
#define _itot _itoa
|
||||||
|
#define _ltot _ltoa
|
||||||
|
#define _ui64tot _ui64toa
|
||||||
|
|
||||||
|
// file manipulations
|
||||||
|
#define _tfopen fopen
|
||||||
|
#define _topen _open
|
||||||
|
#define _tremove remove
|
||||||
|
#define _tunlink _unlink
|
||||||
|
|
||||||
|
// reading and writing to i/o
|
||||||
|
#define _fgettc fgetc
|
||||||
|
#define _fgetts fgets
|
||||||
|
#define _fputts fputs
|
||||||
|
#define _gettchar getchar
|
||||||
|
|
||||||
|
// directory
|
||||||
|
#define _tchdir _chdir
|
||||||
|
|
||||||
|
// environment
|
||||||
|
#define _tgetenv getenv
|
||||||
|
#define _tsystem system
|
||||||
|
|
||||||
|
// time
|
||||||
|
#define _tcsftime strftime
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// is functions (the same in Unicode / ANSI)
|
||||||
|
#ifndef _istgraph
|
||||||
|
#define _istgraph isgraph
|
||||||
|
#endif
|
||||||
|
#ifndef _istascii
|
||||||
|
#define _istascii __isascii
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define __TFILE__ _T(__FILE__)
|
||||||
|
#define __TDATE__ _T(__DATE__)
|
||||||
|
#define __TTIME__ _T(__TIME__)
|
||||||
+298
@@ -0,0 +1,298 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <tchar.h>
|
||||||
|
|
||||||
|
#include "pluginapi.h"
|
||||||
|
|
||||||
|
#ifdef _countof
|
||||||
|
#define COUNTOF _countof
|
||||||
|
#else
|
||||||
|
#define COUNTOF(a) (sizeof(a)/sizeof(a[0]))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
unsigned int g_stringsize;
|
||||||
|
stack_t **g_stacktop;
|
||||||
|
TCHAR *g_variables;
|
||||||
|
|
||||||
|
// utility functions (not required but often useful)
|
||||||
|
|
||||||
|
int NSISCALL popstring(TCHAR *str)
|
||||||
|
{
|
||||||
|
stack_t *th;
|
||||||
|
if (!g_stacktop || !*g_stacktop) return 1;
|
||||||
|
th=(*g_stacktop);
|
||||||
|
|
||||||
|
if (str) _tcscpy(str,th->text);
|
||||||
|
|
||||||
|
*g_stacktop = th->next;
|
||||||
|
GlobalFree((HGLOBAL)th);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NSISCALL popstringn(TCHAR *str, int maxlen)
|
||||||
|
{
|
||||||
|
stack_t *th;
|
||||||
|
if (!g_stacktop || !*g_stacktop) return 1;
|
||||||
|
th=(*g_stacktop);
|
||||||
|
if (str) lstrcpyn(str,th->text,maxlen?maxlen:g_stringsize);
|
||||||
|
*g_stacktop = th->next;
|
||||||
|
GlobalFree((HGLOBAL)th);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL pushstring(const TCHAR *str)
|
||||||
|
{
|
||||||
|
stack_t *th;
|
||||||
|
if (!g_stacktop) return;
|
||||||
|
th=(stack_t*)GlobalAlloc(GPTR,(sizeof(stack_t)+(g_stringsize)*sizeof(TCHAR)));
|
||||||
|
lstrcpyn(th->text,str,g_stringsize);
|
||||||
|
th->next=*g_stacktop;
|
||||||
|
*g_stacktop=th;
|
||||||
|
}
|
||||||
|
|
||||||
|
TCHAR* NSISCALL getuservariable(const int varnum)
|
||||||
|
{
|
||||||
|
if (varnum < 0 || varnum >= __INST_LAST) return NULL;
|
||||||
|
return g_variables+varnum*g_stringsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL setuservariable(const int varnum, const TCHAR *var)
|
||||||
|
{
|
||||||
|
if (var != NULL && varnum >= 0 && varnum < __INST_LAST)
|
||||||
|
lstrcpy(g_variables + varnum*g_stringsize, var);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _UNICODE
|
||||||
|
int NSISCALL PopStringA(char* ansiStr)
|
||||||
|
{
|
||||||
|
wchar_t* wideStr = (wchar_t*) GlobalAlloc(GPTR, g_stringsize*sizeof(wchar_t));
|
||||||
|
int rval = popstring(wideStr);
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL);
|
||||||
|
GlobalFree((HGLOBAL)wideStr);
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NSISCALL PopStringNA(char* ansiStr, int maxlen)
|
||||||
|
{
|
||||||
|
int realLen = maxlen ? maxlen : g_stringsize;
|
||||||
|
wchar_t* wideStr = (wchar_t*) GlobalAlloc(GPTR, realLen*sizeof(wchar_t));
|
||||||
|
int rval = popstringn(wideStr, realLen);
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, realLen, NULL, NULL);
|
||||||
|
GlobalFree((HGLOBAL)wideStr);
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL PushStringA(const char* ansiStr)
|
||||||
|
{
|
||||||
|
wchar_t* wideStr = (wchar_t*) GlobalAlloc(GPTR, g_stringsize*sizeof(wchar_t));
|
||||||
|
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize);
|
||||||
|
pushstring(wideStr);
|
||||||
|
GlobalFree((HGLOBAL)wideStr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr)
|
||||||
|
{
|
||||||
|
lstrcpyW(wideStr, getuservariable(varnum));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL GetUserVariableA(const int varnum, char* ansiStr)
|
||||||
|
{
|
||||||
|
wchar_t* wideStr = getuservariable(varnum);
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL SetUserVariableA(const int varnum, const char* ansiStr)
|
||||||
|
{
|
||||||
|
if (ansiStr != NULL && varnum >= 0 && varnum < __INST_LAST)
|
||||||
|
{
|
||||||
|
wchar_t* wideStr = g_variables + varnum * g_stringsize;
|
||||||
|
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
// ANSI defs
|
||||||
|
int NSISCALL PopStringW(wchar_t* wideStr)
|
||||||
|
{
|
||||||
|
char* ansiStr = (char*) GlobalAlloc(GPTR, g_stringsize);
|
||||||
|
int rval = popstring(ansiStr);
|
||||||
|
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize);
|
||||||
|
GlobalFree((HGLOBAL)ansiStr);
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NSISCALL PopStringNW(wchar_t* wideStr, int maxlen)
|
||||||
|
{
|
||||||
|
int realLen = maxlen ? maxlen : g_stringsize;
|
||||||
|
char* ansiStr = (char*) GlobalAlloc(GPTR, realLen);
|
||||||
|
int rval = popstringn(ansiStr, realLen);
|
||||||
|
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, realLen);
|
||||||
|
GlobalFree((HGLOBAL)ansiStr);
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL PushStringW(wchar_t* wideStr)
|
||||||
|
{
|
||||||
|
char* ansiStr = (char*) GlobalAlloc(GPTR, g_stringsize);
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL);
|
||||||
|
pushstring(ansiStr);
|
||||||
|
GlobalFree((HGLOBAL)ansiStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr)
|
||||||
|
{
|
||||||
|
char* ansiStr = getuservariable(varnum);
|
||||||
|
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL GetUserVariableA(const int varnum, char* ansiStr)
|
||||||
|
{
|
||||||
|
lstrcpyA(ansiStr, getuservariable(varnum));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL SetUserVariableW(const int varnum, const wchar_t* wideStr)
|
||||||
|
{
|
||||||
|
if (wideStr != NULL && varnum >= 0 && varnum < __INST_LAST)
|
||||||
|
{
|
||||||
|
char* ansiStr = g_variables + varnum * g_stringsize;
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// playing with integers
|
||||||
|
|
||||||
|
INT_PTR NSISCALL nsishelper_str_to_ptr(const TCHAR *s)
|
||||||
|
{
|
||||||
|
INT_PTR v=0;
|
||||||
|
if (*s == _T('0') && (s[1] == _T('x') || s[1] == _T('X')))
|
||||||
|
{
|
||||||
|
s++;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int c=*(++s);
|
||||||
|
if (c >= _T('0') && c <= _T('9')) c-=_T('0');
|
||||||
|
else if (c >= _T('a') && c <= _T('f')) c-=_T('a')-10;
|
||||||
|
else if (c >= _T('A') && c <= _T('F')) c-=_T('A')-10;
|
||||||
|
else break;
|
||||||
|
v<<=4;
|
||||||
|
v+=c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (*s == _T('0') && s[1] <= _T('7') && s[1] >= _T('0'))
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int c=*(++s);
|
||||||
|
if (c >= _T('0') && c <= _T('7')) c-=_T('0');
|
||||||
|
else break;
|
||||||
|
v<<=3;
|
||||||
|
v+=c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int sign=0;
|
||||||
|
if (*s == _T('-')) sign++; else s--;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int c=*(++s) - _T('0');
|
||||||
|
if (c < 0 || c > 9) break;
|
||||||
|
v*=10;
|
||||||
|
v+=c;
|
||||||
|
}
|
||||||
|
if (sign) v = -v;
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NSISCALL myatou(const TCHAR *s)
|
||||||
|
{
|
||||||
|
unsigned int v=0;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
unsigned int c=*s++;
|
||||||
|
if (c >= _T('0') && c <= _T('9')) c-=_T('0');
|
||||||
|
else break;
|
||||||
|
v*=10;
|
||||||
|
v+=c;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NSISCALL myatoi_or(const TCHAR *s)
|
||||||
|
{
|
||||||
|
int v=0;
|
||||||
|
if (*s == _T('0') && (s[1] == _T('x') || s[1] == _T('X')))
|
||||||
|
{
|
||||||
|
s++;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int c=*(++s);
|
||||||
|
if (c >= _T('0') && c <= _T('9')) c-=_T('0');
|
||||||
|
else if (c >= _T('a') && c <= _T('f')) c-=_T('a')-10;
|
||||||
|
else if (c >= _T('A') && c <= _T('F')) c-=_T('A')-10;
|
||||||
|
else break;
|
||||||
|
v<<=4;
|
||||||
|
v+=c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (*s == _T('0') && s[1] <= _T('7') && s[1] >= _T('0'))
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int c=*(++s);
|
||||||
|
if (c >= _T('0') && c <= _T('7')) c-=_T('0');
|
||||||
|
else break;
|
||||||
|
v<<=3;
|
||||||
|
v+=c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int sign=0;
|
||||||
|
if (*s == _T('-')) sign++; else s--;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int c=*(++s) - _T('0');
|
||||||
|
if (c < 0 || c > 9) break;
|
||||||
|
v*=10;
|
||||||
|
v+=c;
|
||||||
|
}
|
||||||
|
if (sign) v = -v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Support for simple ORed expressions
|
||||||
|
if (*s == _T('|'))
|
||||||
|
{
|
||||||
|
v |= myatoi_or(s+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
INT_PTR NSISCALL popintptr()
|
||||||
|
{
|
||||||
|
TCHAR buf[128];
|
||||||
|
if (popstringn(buf,COUNTOF(buf)))
|
||||||
|
return 0;
|
||||||
|
return nsishelper_str_to_ptr(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NSISCALL popint_or()
|
||||||
|
{
|
||||||
|
TCHAR buf[128];
|
||||||
|
if (popstringn(buf,COUNTOF(buf)))
|
||||||
|
return 0;
|
||||||
|
return myatoi_or(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSISCALL pushintptr(INT_PTR value)
|
||||||
|
{
|
||||||
|
TCHAR buffer[30];
|
||||||
|
wsprintf(buffer, sizeof(void*) > 4 ? _T("%Id") : _T("%d"), value);
|
||||||
|
pushstring(buffer);
|
||||||
|
}
|
||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
#ifndef ___NSIS_PLUGIN__H___
|
||||||
|
#define ___NSIS_PLUGIN__H___
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "api.h"
|
||||||
|
#include "nsis_tchar.h"
|
||||||
|
|
||||||
|
#ifndef NSISCALL
|
||||||
|
# define NSISCALL __stdcall
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define EXDLL_INIT() { \
|
||||||
|
g_stringsize=string_size; \
|
||||||
|
g_stacktop=stacktop; \
|
||||||
|
g_variables=variables; }
|
||||||
|
|
||||||
|
typedef struct _stack_t {
|
||||||
|
struct _stack_t *next;
|
||||||
|
TCHAR text[1]; // this should be the length of string_size
|
||||||
|
} stack_t;
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
INST_0, // $0
|
||||||
|
INST_1, // $1
|
||||||
|
INST_2, // $2
|
||||||
|
INST_3, // $3
|
||||||
|
INST_4, // $4
|
||||||
|
INST_5, // $5
|
||||||
|
INST_6, // $6
|
||||||
|
INST_7, // $7
|
||||||
|
INST_8, // $8
|
||||||
|
INST_9, // $9
|
||||||
|
INST_R0, // $R0
|
||||||
|
INST_R1, // $R1
|
||||||
|
INST_R2, // $R2
|
||||||
|
INST_R3, // $R3
|
||||||
|
INST_R4, // $R4
|
||||||
|
INST_R5, // $R5
|
||||||
|
INST_R6, // $R6
|
||||||
|
INST_R7, // $R7
|
||||||
|
INST_R8, // $R8
|
||||||
|
INST_R9, // $R9
|
||||||
|
INST_CMDLINE, // $CMDLINE
|
||||||
|
INST_INSTDIR, // $INSTDIR
|
||||||
|
INST_OUTDIR, // $OUTDIR
|
||||||
|
INST_EXEDIR, // $EXEDIR
|
||||||
|
INST_LANG, // $LANGUAGE
|
||||||
|
__INST_LAST
|
||||||
|
};
|
||||||
|
|
||||||
|
extern unsigned int g_stringsize;
|
||||||
|
extern stack_t **g_stacktop;
|
||||||
|
extern TCHAR *g_variables;
|
||||||
|
|
||||||
|
void NSISCALL pushstring(const TCHAR *str);
|
||||||
|
void NSISCALL pushintptr(INT_PTR value);
|
||||||
|
#define pushint(v) pushintptr((INT_PTR)(v))
|
||||||
|
int NSISCALL popstring(TCHAR *str); // 0 on success, 1 on empty stack
|
||||||
|
int NSISCALL popstringn(TCHAR *str, int maxlen); // with length limit, pass 0 for g_stringsize
|
||||||
|
INT_PTR NSISCALL popintptr();
|
||||||
|
#define popint() ( (int) popintptr() )
|
||||||
|
int NSISCALL popint_or(); // with support for or'ing (2|4|8)
|
||||||
|
INT_PTR NSISCALL nsishelper_str_to_ptr(const TCHAR *s);
|
||||||
|
#define myatoi(s) ( (int) nsishelper_str_to_ptr(s) ) // converts a string to an integer
|
||||||
|
unsigned int NSISCALL myatou(const TCHAR *s); // converts a string to an unsigned integer, decimal only
|
||||||
|
int NSISCALL myatoi_or(const TCHAR *s); // with support for or'ing (2|4|8)
|
||||||
|
TCHAR* NSISCALL getuservariable(const int varnum);
|
||||||
|
void NSISCALL setuservariable(const int varnum, const TCHAR *var);
|
||||||
|
|
||||||
|
#ifdef _UNICODE
|
||||||
|
#define PopStringW(x) popstring(x)
|
||||||
|
#define PushStringW(x) pushstring(x)
|
||||||
|
#define SetUserVariableW(x,y) setuservariable(x,y)
|
||||||
|
|
||||||
|
int NSISCALL PopStringA(char* ansiStr);
|
||||||
|
void NSISCALL PushStringA(const char* ansiStr);
|
||||||
|
void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr);
|
||||||
|
void NSISCALL GetUserVariableA(const int varnum, char* ansiStr);
|
||||||
|
void NSISCALL SetUserVariableA(const int varnum, const char* ansiStr);
|
||||||
|
|
||||||
|
#else
|
||||||
|
// ANSI defs
|
||||||
|
|
||||||
|
#define PopStringA(x) popstring(x)
|
||||||
|
#define PushStringA(x) pushstring(x)
|
||||||
|
#define SetUserVariableA(x,y) setuservariable(x,y)
|
||||||
|
|
||||||
|
int NSISCALL PopStringW(wchar_t* wideStr);
|
||||||
|
void NSISCALL PushStringW(wchar_t* wideStr);
|
||||||
|
void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr);
|
||||||
|
void NSISCALL GetUserVariableA(const int varnum, char* ansiStr);
|
||||||
|
void NSISCALL SetUserVariableW(const int varnum, const wchar_t* wideStr);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif//!___NSIS_PLUGIN__H___
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
"""
|
||||||
|
Fix nsSplashPNG repo:
|
||||||
|
1. Rimuove dal tracking i file di build MSBuild in src/ (artifacts + DLL grezze)
|
||||||
|
2. Sposta le DLL pre-built in dist/ (struttura canonica)
|
||||||
|
3. Aggiunge regole .gitignore per escludere le cartelle Release in src/
|
||||||
|
4. Aggiunge dist/ al tracking (con DLL binarie)
|
||||||
|
5. Commit + push Gitea + push GitHub
|
||||||
|
"""
|
||||||
|
import os, sys, shutil, subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
REPO = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# --- Load .env ---
|
||||||
|
env_path = REPO.parent.parent / '.env' # Launchers/.env
|
||||||
|
if env_path.exists():
|
||||||
|
for line in env_path.read_text(encoding='utf-8').splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if line and not line.startswith('#') and '=' in line:
|
||||||
|
k, v = line.split('=', 1)
|
||||||
|
os.environ.setdefault(k.strip(), v.strip())
|
||||||
|
|
||||||
|
GITEA_URL = os.environ['GITEA_URL']
|
||||||
|
GITEA_USER = os.environ['GITEA_USER']
|
||||||
|
GITEA_TOK = os.environ['GITEA_TOKEN']
|
||||||
|
GH_USER = os.environ['GITHUB_USER']
|
||||||
|
GH_TOK = os.environ['GITHUB_TOKEN']
|
||||||
|
REPO_NAME = 'nsis-plugin-nssplashpng'
|
||||||
|
|
||||||
|
def git(*args):
|
||||||
|
r = subprocess.run(['git', '-C', str(REPO)] + list(args), capture_output=True, text=True)
|
||||||
|
if r.returncode != 0:
|
||||||
|
print(f'[git] stderr: {r.stderr.strip()}')
|
||||||
|
return r
|
||||||
|
|
||||||
|
# 1. Rimuovi dal tracking tutti i file di build in src/
|
||||||
|
print('[1] Rimuovo artifacts di build dal tracking...')
|
||||||
|
tracked_artifacts = [
|
||||||
|
'src/nsSplashPNG/Release Unicode/nsSplashPNG.dll.recipe',
|
||||||
|
'src/nsSplashPNG/Release Unicode/nsSplashPNG.iobj',
|
||||||
|
'src/nsSplashPNG/Release Unicode/nsSplashPNG.ipdb',
|
||||||
|
'src/nsSplashPNG/Release/nsSplashPNG.dll.recipe',
|
||||||
|
'src/nsSplashPNG/Release/nsSplashPNG.iobj',
|
||||||
|
'src/nsSplashPNG/Release/nsSplashPNG.ipdb',
|
||||||
|
'src/nsSplashPNG/x64/Release Unicode/nsSplashPNG.dll.recipe',
|
||||||
|
'src/nsSplashPNG/x64/Release Unicode/nsSplashPNG.iobj',
|
||||||
|
'src/nsSplashPNG/x64/Release Unicode/nsSplashPNG.ipdb',
|
||||||
|
'src/Release Unicode/nsSplashPNG.dll',
|
||||||
|
'src/Release/nsSplashPNG.dll',
|
||||||
|
'src/x64/Release Unicode/nsSplashPNG.dll',
|
||||||
|
]
|
||||||
|
for f in tracked_artifacts:
|
||||||
|
r = git('rm', '--cached', f)
|
||||||
|
if r.returncode == 0:
|
||||||
|
print(f' [rm cached] {f}')
|
||||||
|
else:
|
||||||
|
print(f' [skip] {f} — not tracked or already removed')
|
||||||
|
|
||||||
|
# 2. Sposta DLL in dist/ (struttura canonica)
|
||||||
|
print('[2] Sposto DLL in dist/...')
|
||||||
|
moves = [
|
||||||
|
('src/Release/nsSplashPNG.dll', 'dist/x86-ansi/nsSplashPNG.dll'),
|
||||||
|
('src/Release Unicode/nsSplashPNG.dll', 'dist/x86-unicode/nsSplashPNG.dll'),
|
||||||
|
('src/x64/Release Unicode/nsSplashPNG.dll', 'dist/amd64-unicode/nsSplashPNG.dll'),
|
||||||
|
]
|
||||||
|
for src_rel, dst_rel in moves:
|
||||||
|
src = REPO / Path(src_rel)
|
||||||
|
dst = REPO / Path(dst_rel)
|
||||||
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
if src.exists():
|
||||||
|
shutil.copy2(src, dst)
|
||||||
|
print(f' [copied] {src_rel} → {dst_rel}')
|
||||||
|
elif dst.exists():
|
||||||
|
print(f' [exists] {dst_rel} (src già rimosso)')
|
||||||
|
else:
|
||||||
|
print(f' [warn] {src_rel} non trovato — DLL mancante')
|
||||||
|
|
||||||
|
# 3. Aggiungi .gitignore rules per le cartelle MSBuild in src/
|
||||||
|
print('[3] Aggiorno .gitignore...')
|
||||||
|
gi_path = REPO / '.gitignore'
|
||||||
|
gi_content = gi_path.read_text(encoding='utf-8')
|
||||||
|
extra = (
|
||||||
|
'\n# MSBuild output directories in src/ (build artifacts, not source)\n'
|
||||||
|
'src/**/Release/\n'
|
||||||
|
'src/**/Release Unicode/\n'
|
||||||
|
'src/**/x64/\n'
|
||||||
|
'*.recipe\n'
|
||||||
|
'*.iobj\n'
|
||||||
|
'*.ipdb\n'
|
||||||
|
)
|
||||||
|
if 'MSBuild output directories' not in gi_content:
|
||||||
|
gi_path.write_text(gi_content + extra, encoding='utf-8')
|
||||||
|
print(' [ok] .gitignore aggiornato')
|
||||||
|
else:
|
||||||
|
print(' [skip] .gitignore già aggiornato')
|
||||||
|
|
||||||
|
# 4. Stage dist/ e .gitignore
|
||||||
|
print('[4] Stage dist/ e .gitignore...')
|
||||||
|
git('add', 'dist/')
|
||||||
|
git('add', '.gitignore')
|
||||||
|
|
||||||
|
# Verifica status
|
||||||
|
r = git('status', '--short')
|
||||||
|
print(r.stdout.strip() or '(nothing staged)')
|
||||||
|
|
||||||
|
# 5. Commit
|
||||||
|
print('[5] Commit...')
|
||||||
|
r = git('commit', '-m',
|
||||||
|
'fix: move pre-built DLLs to dist/, remove MSBuild artifacts from tracking\n\n'
|
||||||
|
'- src/Release*/ and src/nsSplashPNG/Release*/ were committed by mistake\n'
|
||||||
|
' (MSBuild artifacts: .iobj, .ipdb, .recipe + raw DLL outputs)\n'
|
||||||
|
'- DLLs moved to canonical dist/{x86-ansi,x86-unicode,amd64-unicode}/\n'
|
||||||
|
'- .gitignore updated to exclude src/**/Release*/ going forward'
|
||||||
|
)
|
||||||
|
if r.returncode != 0:
|
||||||
|
print(f'[FAIL] commit: {r.stderr}', file=sys.stderr); sys.exit(1)
|
||||||
|
print(f'[ok] {r.stdout.strip()}')
|
||||||
|
|
||||||
|
# 6. Push Gitea
|
||||||
|
print('[6] Push Gitea...')
|
||||||
|
gitea_url = f"{GITEA_URL.replace('://', f'://{GITEA_USER}:{GITEA_TOK}@')}/{GITEA_USER}/{REPO_NAME}.git"
|
||||||
|
r = subprocess.run(['git', '-C', str(REPO), 'push', gitea_url, 'main'],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
if r.returncode != 0:
|
||||||
|
print(f'[FAIL] Gitea: {r.stderr}', file=sys.stderr); sys.exit(1)
|
||||||
|
print(f'[ok] Gitea: {r.stderr.strip() or "up to date"}')
|
||||||
|
|
||||||
|
# 7. Push GitHub
|
||||||
|
print('[7] Push GitHub...')
|
||||||
|
gh_url = f'https://{GH_USER}:{GH_TOK}@github.com/{GH_USER}/{REPO_NAME}.git'
|
||||||
|
r = subprocess.run(['git', '-C', str(REPO), 'push', gh_url, 'main'],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
if r.returncode != 0:
|
||||||
|
print(f'[FAIL] GitHub: {r.stderr}', file=sys.stderr); sys.exit(1)
|
||||||
|
print(f'[ok] GitHub: {r.stderr.strip() or "up to date"}')
|
||||||
|
|
||||||
|
print('[DONE]')
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
"""
|
||||||
|
Push latest commit to Gitea + GitHub directly,
|
||||||
|
then disable Gitea Actions on the repo (keep GitHub Actions enabled).
|
||||||
|
"""
|
||||||
|
import urllib.request, urllib.error, json, os, sys, subprocess
|
||||||
|
|
||||||
|
# --- Load .env from workspace root ---
|
||||||
|
env_path = os.path.join(os.path.dirname(__file__), '..', '..', '..', '.env')
|
||||||
|
env_path = os.path.normpath(env_path)
|
||||||
|
if os.path.exists(env_path):
|
||||||
|
with open(env_path, encoding='utf-8') as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if line and not line.startswith('#') and '=' in line:
|
||||||
|
k, v = line.split('=', 1)
|
||||||
|
os.environ.setdefault(k.strip(), v.strip())
|
||||||
|
|
||||||
|
GITEA_URL = os.environ['GITEA_URL']
|
||||||
|
GITEA_USER = os.environ['GITEA_USER']
|
||||||
|
GITEA_TOK = os.environ['GITEA_TOKEN']
|
||||||
|
GH_USER = os.environ['GITHUB_USER']
|
||||||
|
GH_TOK = os.environ['GITHUB_TOKEN']
|
||||||
|
REPO = 'nsis-plugin-nssplashpng'
|
||||||
|
|
||||||
|
STAGING = os.path.normpath(os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
|
||||||
|
def api_call(url, method='GET', data=None, headers=None):
|
||||||
|
body = json.dumps(data).encode() if data else None
|
||||||
|
req = urllib.request.Request(url, data=body, headers=headers or {}, method=method)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req) as r:
|
||||||
|
raw = r.read()
|
||||||
|
return r.status, json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
raw = e.read()
|
||||||
|
return e.code, json.loads(raw) if raw else {}
|
||||||
|
|
||||||
|
# --- 1. Push to Gitea ---
|
||||||
|
print('[1] Push to Gitea...')
|
||||||
|
gitea_push_url = f"{GITEA_URL.replace('://', f'://{GITEA_USER}:{GITEA_TOK}@')}/{GITEA_USER}/{REPO}.git"
|
||||||
|
r = subprocess.run(['git', '-C', STAGING, 'push', gitea_push_url, 'main'],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
if r.returncode != 0:
|
||||||
|
print(f'[FAIL] Gitea push: {r.stderr}', file=sys.stderr); sys.exit(1)
|
||||||
|
print(f'[ok] Gitea push: {r.stderr.strip() or "up to date"}')
|
||||||
|
|
||||||
|
# --- 2. Push to GitHub ---
|
||||||
|
print('[2] Push to GitHub...')
|
||||||
|
gh_push_url = f'https://{GH_USER}:{GH_TOK}@github.com/{GH_USER}/{REPO}.git'
|
||||||
|
r = subprocess.run(['git', '-C', STAGING, 'push', gh_push_url, 'main'],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
if r.returncode != 0:
|
||||||
|
print(f'[FAIL] GitHub push: {r.stderr}', file=sys.stderr); sys.exit(1)
|
||||||
|
print(f'[ok] GitHub push: {r.stderr.strip() or "up to date"}')
|
||||||
|
|
||||||
|
# --- 3. Disable Gitea Actions ---
|
||||||
|
print('[3] Disabling Gitea Actions...')
|
||||||
|
gitea_hdrs = {'Authorization': f'token {GITEA_TOK}', 'Content-Type': 'application/json'}
|
||||||
|
s, resp = api_call(
|
||||||
|
f'{GITEA_URL}/api/v1/repos/{GITEA_USER}/{REPO}',
|
||||||
|
method='PATCH',
|
||||||
|
data={'has_actions': False},
|
||||||
|
headers=gitea_hdrs
|
||||||
|
)
|
||||||
|
if s in (200, 201, 204):
|
||||||
|
actions_status = resp.get('has_actions', '?')
|
||||||
|
print(f'[ok] Gitea Actions disabled (has_actions={actions_status})')
|
||||||
|
else:
|
||||||
|
print(f'[warn] PATCH repo returned {s}: {resp}')
|
||||||
|
print('[warn] Trying via repo settings API...')
|
||||||
|
# Alternative: some Gitea versions use a different field name
|
||||||
|
s2, resp2 = api_call(
|
||||||
|
f'{GITEA_URL}/api/v1/repos/{GITEA_USER}/{REPO}',
|
||||||
|
method='PATCH',
|
||||||
|
data={'actions': False},
|
||||||
|
headers=gitea_hdrs
|
||||||
|
)
|
||||||
|
print(f'[info] actions=False attempt: {s2}: {resp2.get("has_actions", resp2.get("actions", "?"))}')
|
||||||
|
|
||||||
|
print('[DONE]')
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
"""
|
||||||
|
Squash everything in the nsSplashPNG repo into a single initial commit,
|
||||||
|
then force-push to Gitea and GitHub.
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
1. Create orphan branch 'init'
|
||||||
|
2. git add -A (all currently tracked + untracked non-ignored files)
|
||||||
|
3. Commit with the canonical initial message
|
||||||
|
4. Replace 'main' with 'init'
|
||||||
|
5. Delete old v1.0.0 tag, recreate on new commit
|
||||||
|
6. Force-push main + tag to Gitea and GitHub
|
||||||
|
"""
|
||||||
|
import os, sys, subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
REPO = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# --- Load .env ---
|
||||||
|
env_path = REPO.parent.parent / '.env'
|
||||||
|
if env_path.exists():
|
||||||
|
for line in env_path.read_text(encoding='utf-8').splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if line and not line.startswith('#') and '=' in line:
|
||||||
|
k, v = line.split('=', 1)
|
||||||
|
os.environ.setdefault(k.strip(), v.strip())
|
||||||
|
|
||||||
|
GITEA_URL = os.environ['GITEA_URL']
|
||||||
|
GITEA_USER = os.environ['GITEA_USER']
|
||||||
|
GITEA_TOK = os.environ['GITEA_TOKEN']
|
||||||
|
GH_USER = os.environ['GITHUB_USER']
|
||||||
|
GH_TOK = os.environ['GITHUB_TOKEN']
|
||||||
|
REPO_NAME = 'nsis-plugin-nssplashpng'
|
||||||
|
|
||||||
|
GITEA_PUSH = f"{GITEA_URL.replace('://', f'://{GITEA_USER}:{GITEA_TOK}@')}/{GITEA_USER}/{REPO_NAME}.git"
|
||||||
|
GH_PUSH = f'https://{GH_USER}:{GH_TOK}@github.com/{GH_USER}/{REPO_NAME}.git'
|
||||||
|
|
||||||
|
COMMIT_MSG = 'chore: initial commit (extracted from Launchers monorepo)'
|
||||||
|
TAG = 'v1.0.0'
|
||||||
|
|
||||||
|
def git(*args, check=True):
|
||||||
|
r = subprocess.run(['git', '-C', str(REPO)] + list(args),
|
||||||
|
capture_output=True, text=True)
|
||||||
|
if check and r.returncode != 0:
|
||||||
|
print(f'[git {" ".join(args)}] stderr: {r.stderr.strip()}', file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
return r
|
||||||
|
|
||||||
|
# 1. Crea branch orfano
|
||||||
|
print('[1] Creo branch orfano "init"...')
|
||||||
|
git('checkout', '--orphan', 'init')
|
||||||
|
|
||||||
|
# 2. Stage tutto (include file attualmente tracciati + non-ignorati)
|
||||||
|
print('[2] Stage di tutti i file...')
|
||||||
|
git('add', '-A')
|
||||||
|
|
||||||
|
# Verifica cosa verrà committato
|
||||||
|
r = git('status', '--short')
|
||||||
|
print(r.stdout.strip())
|
||||||
|
|
||||||
|
# 3. Commit
|
||||||
|
print('[3] Commit unico...')
|
||||||
|
# Configura autore se non già globale
|
||||||
|
r = git('commit', '-m', COMMIT_MSG)
|
||||||
|
print(f'[ok] {r.stdout.strip()}')
|
||||||
|
|
||||||
|
# 4. Sostituisci main con init
|
||||||
|
print('[4] Sostituisco branch main...')
|
||||||
|
git('branch', '-D', 'main')
|
||||||
|
git('branch', '-m', 'init', 'main')
|
||||||
|
|
||||||
|
# 5. Elimina vecchio tag e ricrealo sul nuovo commit
|
||||||
|
print(f'[5] Ricreo tag {TAG}...')
|
||||||
|
git('tag', '-d', TAG, check=False) # potrebbe non esistere
|
||||||
|
git('tag', TAG)
|
||||||
|
|
||||||
|
# Verifica finale
|
||||||
|
r = git('log', '--oneline')
|
||||||
|
print(f'[log] {r.stdout.strip()}')
|
||||||
|
r = git('tag', '--list')
|
||||||
|
print(f'[tags] {r.stdout.strip()}')
|
||||||
|
|
||||||
|
# 6. Force push a Gitea
|
||||||
|
print('[6] Force push a Gitea...')
|
||||||
|
r = subprocess.run(
|
||||||
|
['git', '-C', str(REPO), 'push', '--force', GITEA_PUSH, 'main', TAG],
|
||||||
|
capture_output=True, text=True
|
||||||
|
)
|
||||||
|
if r.returncode != 0:
|
||||||
|
print(f'[FAIL] Gitea: {r.stderr}', file=sys.stderr); sys.exit(1)
|
||||||
|
print(f'[ok] Gitea: {r.stderr.strip()}')
|
||||||
|
|
||||||
|
# 7. Force push a GitHub
|
||||||
|
print('[7] Force push a GitHub...')
|
||||||
|
r = subprocess.run(
|
||||||
|
['git', '-C', str(REPO), 'push', '--force', GH_PUSH, 'main', TAG],
|
||||||
|
capture_output=True, text=True
|
||||||
|
)
|
||||||
|
if r.returncode != 0:
|
||||||
|
print(f'[FAIL] GitHub: {r.stderr}', file=sys.stderr); sys.exit(1)
|
||||||
|
print(f'[ok] GitHub: {r.stderr.strip()}')
|
||||||
|
|
||||||
|
# 8. Aggiorna origin/main
|
||||||
|
print('[8] Aggiorno remote tracking...')
|
||||||
|
r = subprocess.run(
|
||||||
|
['git', '-C', str(REPO), 'remote', 'set-head', 'origin', 'main'],
|
||||||
|
capture_output=True, text=True
|
||||||
|
)
|
||||||
|
# Fetch per sincronizzare origin/main con il nuovo HEAD
|
||||||
|
gitea_fetch = GITEA_PUSH
|
||||||
|
r2 = subprocess.run(
|
||||||
|
['git', '-C', str(REPO), 'fetch', '--force', gitea_fetch, 'main:refs/remotes/origin/main'],
|
||||||
|
capture_output=True, text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verifica stato finale
|
||||||
|
r = git('log', '--oneline', '-3')
|
||||||
|
print(f'\n[FINAL LOG]\n{r.stdout.strip()}')
|
||||||
|
r = git('status', '-sb')
|
||||||
|
print(f'[FINAL STATUS]\n{r.stdout.strip()}')
|
||||||
|
|
||||||
|
print('\n[DONE] Repository ridotto a singolo commit iniziale.')
|
||||||
Reference in New Issue
Block a user