initial: nsSplashPNG plugin 1.0.0

This commit is contained in:
2026-04-29 23:19:34 +02:00
commit db4c1b0f1e
41 changed files with 4400 additions and 0 deletions
+24
View File
@@ -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
+51
View File
@@ -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
+76
View File
@@ -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.)
+8
View File
@@ -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
+31
View File
@@ -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. -->
+66
View File
@@ -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
+99
View File
@@ -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
View File
@@ -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
+19
View File
@@ -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
View File
@@ -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`.
+21
View File
@@ -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.
+348
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+1
View File
@@ -0,0 +1 @@
1.0.0
+679
View File
@@ -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())
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
+157
View File
@@ -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
View File
@@ -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
+47
View File
@@ -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
+39
View File
@@ -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
+50
View File
@@ -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
+43
View File
@@ -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
+34
View File
@@ -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

+553
View File
@@ -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"));
}
+83
View File
@@ -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_ */
+4
View File
@@ -0,0 +1,4 @@
LIBRARY nsSplashPNG
EXPORTS
show @1
stop @2
+187
View File
@@ -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>
+243
View File
@@ -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
View File
@@ -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
View File
@@ -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___
+137
View File
@@ -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]')
+80
View File
@@ -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]')
+121
View File
@@ -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.')