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