commit c51365f97f932edbb23a2089caf3e0a161931f09 Author: Simone Date: Wed Apr 29 13:25:14 2026 +0200 initial: nsResize plugin 1.0.0 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..55629c5 --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e2cfe88 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,52 @@ +# 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 +# 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 + +*.rc binary +*.rc2 binary diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..7599f2a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -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.) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..925af5e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Domande generali + url: https://gitea.emulab.it/Simone/nsis-plugin-nsresize/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-nsresize/security/advisories/new + about: Segnala vulnerabilità di security in privato (non aprire issue pubbliche) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..fae6d8d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -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 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..3758b50 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ +## Descrizione + + + +## 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 + + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..4fe517a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,63 @@ +# 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: + +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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e4cf67d --- /dev/null +++ b/.github/workflows/release.yml @@ -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. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c3a730 --- /dev/null +++ b/.gitignore @@ -0,0 +1,63 @@ +# 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/automation scripts (temporary, contain tokens — never commit) +tools/t*.py +tools/check_*.py +tools/legacy/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..28872db --- /dev/null +++ b/CHANGELOG.md @@ -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-nsresize/compare/v1.0.0...HEAD +[1.0.0]: https://gitea.emulab.it/Simone/nsis-plugin-nsresize/releases/tag/v1.0.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ca1e34d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,137 @@ +# Contribuire a nsResize + +Grazie per il tuo interesse a contribuire! + +> Repository primario: [https://gitea.emulab.it/Simone/nsis-plugin-nsresize](https://gitea.emulab.it/Simone/nsis-plugin-nsresize) +> Mirror GitHub: [https://github.com/systempal/nsis-plugin-nsresize](https://github.com/systempal/nsis-plugin-nsresize) (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-nsresize.git +cd nsis-plugin-nsresize +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 `/{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-nsresize/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-nsresize/issues) con label `question`. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bc3fdc5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +zlib License + +Copyright (c) 2026 Simone + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f2f9e14 --- /dev/null +++ b/README.md @@ -0,0 +1,170 @@ +# nsResize NSIS Plugin + +**Personal modified version** + +--- + +## Original Description + +**Author:** Stuart 'Afrow UK' Welch +**Company:** Afrow Soft Ltd +**Date:** 13th April 2013 +**Version:** 1.0.0.0 + +A small NSIS plug-in for moving or resizing windows/controls. + +See Examples\nsResize\*. + +## Macros + +nsResize.nsh includes macros used by the LargeWindow example script. + +## Functions + +### nsResize::SetRTL + +```nsis +nsResize::SetRTL $(^RTL) +``` + +Sets right-to-left mode on or off. This is only needed when using the nsResize::Set function. + +### nsResize::Set + +```nsis +nsResize::Set control_HWND x y width height +``` + +Sets a control's new X and Y coordinates and a new width and height. You may use the same unit values as nsDialogs. An empty string for x or y will ensure the control is not moved. An empty string for width or height will ensure the control is not resized. + +### nsResize::Add + +```nsis +nsResize::Add control_HWND x y width height +``` + +Adds the given X, Y, width and height values. You may use the same unit values as nsDialogs. Note that special treatment must be done for right-to-left languages. + +### nsResize::Top + +```nsis +nsResize::Top control_HWND +``` + +Moves a control to the top of the Z-order. + +### nsResize::GetPos + +```nsis +nsResize::GetPos control_HWND +Pop $X +Pop $Y +``` + +Gets the control position in dialog units relative to its parent window. + +### nsResize::GetPosPx + +```nsis +nsResize::GetPosPx control_HWND +Pop $X +Pop $Y +``` + +Gets the control position in pixels relative to its parent window. + +### nsResize::GetSize + +```nsis +nsResize::GetSize control_HWND +Pop $Width +Pop $Height +``` + +Gets the control size in dialog units. + +### nsResize::GetSizePx + +```nsis +nsResize::GetSizePx control_HWND +Pop $Width +Pop $Height +``` + +Gets the control size in pixels. + +## License + +This plug-in is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages arising from the use of this plug-in. + +Permission is granted to anyone to use this plug-in for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this plug-in must not be misrepresented; you must not claim that you wrote the original plug-in. If you use this plug-in in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered versions must be plainly marked as such, and must not be misrepresented as being the original plug-in. +3. This notice may not be removed or altered from any distribution. + +--- + +## ⚠️ Differences in the Personal Version + +This version includes the following changes from the original distribution: + +### New supported architecture: x64 (amd64-unicode) + +The original supported only: +- x86-ansi +- x86-unicode + +The personal version adds support for: +- **amd64-unicode** (x64) + +### Visual Studio Project + +The original included: +- `Contrib/nsResize/nsResize.sln` - VS2012 Solution +- `Contrib/nsResize/nsResize.vcxproj` - VS2012 Project (x86 only) + +The personal version updates to VS2022 with x64 support: +- `Src/nsResize.sln` - VS2022 Solution +- `Src/nsResize.vcxproj` - VS2022 Project (x86-ansi, x86-unicode, amd64-unicode) + +### Changed Folder Structure + +Source files have been moved from `Contrib/nsResize/` to `Src/` for a cleaner structure. + +### Added Files + +- `build_plugin.py` - Python script to compile the plugin for all architectures +- `Src/api.h` - NSIS API header +- `Src/pluginapi.c` / `Src/pluginapi.h` - NSIS plugin API +- `Src/nsis_tchar.h` - Header for TCHAR support +- `Src/nsis_unicode/pluginapi_x64.lib` - Plugin API library for x64 +- `Src/build_pluginapi_x64.cmd` / `Src/build_pluginapi_x86.cmd` - Scripts to compile plugin API +- `Examples/nsResize/*.exe` - Compiled examples + +### Build + +```powershell +python build_plugin.py # Build all architectures +python build_plugin.py --config x86-unicode # Single architecture (x86-ansi|x86-unicode|amd64-unicode|all) +python build_plugin.py --toolset 2026 # Specific toolset (2022|2026|auto) +python build_plugin.py --jobs 4 # Number of parallel MSBuild jobs (default: CPU count) +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 --verbose # Extended MSBuild output +python build_plugin.py --version # Print version and exit +``` + +DLLs are copied to `dist/{platform}/nsResize.dll`. + +### Removed Files + +Pre-compiled DLL files have been removed from the distribution: +- `Plugins/nsResize.dll` +- `Unicode/Plugins/nsResize.dll` + +DLLs are generated by the build and copied to `plugins/{platform}/`. + +--- + +*See [README_IT.md](README_IT.md) for the Italian version.* diff --git a/README_IT.md b/README_IT.md new file mode 100644 index 0000000..f1d60ed --- /dev/null +++ b/README_IT.md @@ -0,0 +1,170 @@ +# nsResize NSIS Plugin + +**Versione personale modificata** + +--- + +## Descrizione Originale + +**Autore:** Stuart 'Afrow UK' Welch +**Azienda:** Afrow Soft Ltd +**Data:** 13 Aprile 2013 +**Versione:** 1.0.0.0 + +Un piccolo plug-in NSIS per spostare o ridimensionare finestre/controlli. + +Vedi Examples\nsResize\*. + +## Macro + +nsResize.nsh include macro usate dallo script di esempio LargeWindow. + +## Funzioni + +### nsResize::SetRTL + +```nsis +nsResize::SetRTL $(^RTL) +``` + +Abilita o disabilita la modalità da destra a sinistra. Necessario solo quando si usa nsResize::Set. + +### nsResize::Set + +```nsis +nsResize::Set control_HWND x y width height +``` + +Imposta le nuove coordinate X e Y e le nuove dimensioni di un controllo. Stringa vuota per x o y = non spostare. Stringa vuota per width o height = non ridimensionare. + +### nsResize::Add + +```nsis +nsResize::Add control_HWND x y width height +``` + +Aggiunge i valori X, Y, width e height. Per lingue da destra a sinistra è necessaria una gestione speciale. + +### nsResize::Top + +```nsis +nsResize::Top control_HWND +``` + +Sposta un controllo in cima all'ordine Z. + +### nsResize::GetPos + +```nsis +nsResize::GetPos control_HWND +Pop $X +Pop $Y +``` + +Ottiene la posizione del controllo in unità dialogo relative alla finestra padre. + +### nsResize::GetPosPx + +```nsis +nsResize::GetPosPx control_HWND +Pop $X +Pop $Y +``` + +Ottiene la posizione del controllo in pixel relative alla finestra padre. + +### nsResize::GetSize + +```nsis +nsResize::GetSize control_HWND +Pop $Width +Pop $Height +``` + +Ottiene le dimensioni del controllo in unità dialogo. + +### nsResize::GetSizePx + +```nsis +nsResize::GetSizePx control_HWND +Pop $Width +Pop $Height +``` + +Ottiene le dimensioni del controllo in pixel. + +## Licenza + +Questo plug-in viene fornito 'così com'è', senza alcuna garanzia esplicita o implicita. In nessun caso l'autore sarà responsabile per danni derivanti dall'uso di questo plug-in. + +È concessa l'autorizzazione a chiunque di usare questo plug-in per qualsiasi scopo, incluse applicazioni commerciali, e di modificarlo e ridistribuirlo liberamente, soggetto alle seguenti restrizioni: + +1. L'origine di questo plug-in non deve essere travisata; non si può affermare di avere scritto il plug-in originale. +2. Le versioni alterate devono essere chiaramente contrassegnate come tali. +3. Questo avviso non può essere rimosso o alterato da alcuna distribuzione. + +--- + +## ⚠️ Differenze nella versione personale + +Questa versione include le seguenti modifiche rispetto alla distribuzione originale: + +### Nuova architettura supportata: x64 (amd64-unicode) + +L'originale supportava solo: +- x86-ansi +- x86-unicode + +La versione personale aggiunge il supporto per: +- **amd64-unicode** (x64) + +### Progetto Visual Studio + +L'originale includeva: +- `Contrib/nsResize/nsResize.sln` - Solution VS2012 +- `Contrib/nsResize/nsResize.vcxproj` - Progetto VS2012 (solo x86) + +La versione personale aggiorna a VS2022 con supporto x64: +- `Src/nsResize.sln` - Solution VS2022 +- `Src/nsResize.vcxproj` - Progetto VS2022 (x86-ansi, x86-unicode, amd64-unicode) + +### Struttura cartelle modificata + +I file sorgenti sono stati spostati da `Contrib/nsResize/` a `Src/` per una struttura più pulita. + +### File aggiunti + +- `build_plugin.py` - Script Python per compilare il plugin per tutte le architetture +- `Src/api.h` - Header API NSIS +- `Src/pluginapi.c` / `Src/pluginapi.h` - API plugin NSIS +- `Src/nsis_tchar.h` - Header per supporto TCHAR +- `Src/nsis_unicode/pluginapi_x64.lib` - Libreria plugin API per x64 +- `Src/build_pluginapi_x64.cmd` / `Src/build_pluginapi_x86.cmd` - Script per compilare plugin API +- `Examples/nsResize/*.exe` - Esempi compilati + +### Compilazione + +```powershell +python build_plugin.py # Compila tutte le architetture +python build_plugin.py --config x86-unicode # Solo un'architettura (x86-ansi|x86-unicode|amd64-unicode|all) +python build_plugin.py --toolset 2026 # Toolset specifico (2022|2026|auto) +python build_plugin.py --jobs 4 # Numero di job MSBuild paralleli (default: CPU count) +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 --verbose # Output MSBuild esteso +python build_plugin.py --version # Stampa versione ed esce +``` + +I DLL vengono copiati in `dist/{platform}/nsResize.dll`. + +### File rimossi + +I file DLL precompilati sono stati rimossi dalla distribuzione: +- `Plugins/nsResize.dll` +- `Unicode/Plugins/nsResize.dll` + +I DLL vengono generati dalla compilazione e copiati in `plugins/{platform}/`. + +--- + +*See [README.md](README.md) for the English version.* diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..93034e3 --- /dev/null +++ b/SECURITY.md @@ -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-nsresize/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. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/build_plugin.py b/build_plugin.py new file mode 100644 index 0000000..c0bf830 --- /dev/null +++ b/build_plugin.py @@ -0,0 +1,679 @@ +#!/usr/bin/env python3 +""" +Build script for nsResize 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 = 'nsResize.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 / 'nsResize.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//).""" + 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// to plugins//.""" + 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 nsResize 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 / 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 nsResize 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()) diff --git a/dist/amd64-unicode/nsResize.dll b/dist/amd64-unicode/nsResize.dll new file mode 100644 index 0000000..e38f8df Binary files /dev/null and b/dist/amd64-unicode/nsResize.dll differ diff --git a/dist/x86-ansi/nsResize.dll b/dist/x86-ansi/nsResize.dll new file mode 100644 index 0000000..3fbbe1d Binary files /dev/null and b/dist/x86-ansi/nsResize.dll differ diff --git a/dist/x86-unicode/nsResize.dll b/dist/x86-unicode/nsResize.dll new file mode 100644 index 0000000..0eea29e Binary files /dev/null and b/dist/x86-unicode/nsResize.dll differ diff --git a/docs/Readme.txt b/docs/Readme.txt new file mode 100644 index 0000000..fe71746 --- /dev/null +++ b/docs/Readme.txt @@ -0,0 +1,108 @@ +nsResize NSIS plug-in + +Author: Stuart 'Afrow UK' Welch +Company: Afrow Soft Ltd +Date: 13th April 2013 +Version: 1.0.0.0 + +A small NSIS plug-in for moving or resizing windows/controls. + +See Examples\nsResize\*. + +------------------------------------------------------------------------ +Macros +------------------------------------------------------------------------ + +nsResize.nsh includes macros used by the LargeWindow example script. + +------------------------------------------------------------------------ +Functions +------------------------------------------------------------------------ + + nsResize::SetRTL $(^RTL) + + Sets right-to-left mode on or off. This is only needed when using the + nsResize::Set function. + +------------------------------------------------------------------------ + + nsResize::Set control_HWND x y width height + + Sets a control's new X and Y coordinates and a new width and height. + You may use the same unit values as nsDialogs. An empty string for x + or y will ensure the control is not moved. An empty string for width + or height will ensure the control is not resized. + +------------------------------------------------------------------------ + + nsResize::Add control_HWND x y width height + + Adds the given X, Y, width and height values. You may use the same + unit values as nsDialogs. Note that special treatment must be done for + right-to-left languages. + +------------------------------------------------------------------------ + + nsResize::Top control_HWND + + Moves a control to the top of the Z-order. + +------------------------------------------------------------------------ + + nsResize::GetPos control_HWND + Pop $X + Pop $Y + + Gets the control position in dialog units relative to its parent + window. + +------------------------------------------------------------------------ + + nsResize::GetPosPx control_HWND + Pop $X + Pop $Y + + Gets the control position in pixels relative to its parent window. + +------------------------------------------------------------------------ + + nsResize::GetSize control_HWND + Pop $Width + Pop $Height + + Gets the control size in dialog units. + +------------------------------------------------------------------------ + + nsResize::GetSizePx control_HWND + Pop $Width + Pop $Height + + Gets the control size in pixels. + +------------------------------------------------------------------------ +Change Log +------------------------------------------------------------------------ + +1.0.0.0 - 13th April 2013 +* First version. + +------------------------------------------------------------------------ +License +------------------------------------------------------------------------ + +This plug-in is provided 'as-is', without any express or implied +warranty. In no event will the author be held liable for any damages +arising from the use of this plug-in. + +Permission is granted to anyone to use this plug-in for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this plug-in must not be misrepresented; you must not + claim that you wrote the original plug-in. + If you use this plug-in in a product, an acknowledgment in the + product documentation would be appreciated but is not required. +2. Altered versions must be plainly marked as such, and must not be + misrepresented as being the original plug-in. +3. This notice may not be removed or altered from any distribution. \ No newline at end of file diff --git a/examples/Circle.nsi b/examples/Circle.nsi new file mode 100644 index 0000000..a5896cb --- /dev/null +++ b/examples/Circle.nsi @@ -0,0 +1,44 @@ + +!addplugindir "n:\code\\Workspace\\Launchers\\plugins\\x86-unicode\\" +!include MUI2.nsh + +Name Circle +OutFile Circle.exe +RequestExecutionLevel user + +!insertmacro MUI_PAGE_WELCOME +Page Custom CirclePage_Show + +!insertmacro MUI_LANGUAGE English + +Var Label + +Function CirclePage_Show + + !insertmacro MUI_HEADER_TEXT `Circle` `Moves a label in a circle using a timer and absolute positioning` + + nsDialogs::Create 1018 + Pop $R0 + + ${NSD_CreateLabel} 100% 100% 20u 20u `` + Pop $Label + SetCtlColors $Label 0x000000 0xFF0000 + + StrCpy $R0 0 + ${NSD_CreateTimer} CirclePage_TimerTick 50 + + nsDialogs::Show + + ${NSD_KillTimer} CirclePage_TimerTick + +FunctionEnd + +Function CirclePage_TimerTick + + Math::Script `R0 = R0 + 0.1; R1 = 140 + sin(R0) * 140; R2 = 55 + cos(R0) * 50` + nsResize::Set $Label $R1u $R2u `` `` + +FunctionEnd + +Section +SectionEnd \ No newline at end of file diff --git a/examples/LargeWindow.nsi b/examples/LargeWindow.nsi new file mode 100644 index 0000000..7da47cb --- /dev/null +++ b/examples/LargeWindow.nsi @@ -0,0 +1,118 @@ +/* + This script uses the nsResize plug-in to resize your NSIS installer + and uninstaller windows. +*/ +!addplugindir "n:\code\\Workspace\\Launchers\\plugins\\x86-unicode\\" +!addincludedir "n:\code\\Workspace\\Launchers\\include\\" +!include MUI2.nsh +!include nsResize.nsh + +Name LargeWindow +OutFile LargeWindow.exe +RequestExecutionLevel user +InstallDir $EXEDIR +ShowInstDetails show + +; Increase the width and height by these units: +!define AddWidth 100u +!define AddHeight 75u + +; Resizing still works with MUI interface customisation. +!define MUI_LICENSEPAGE_RADIOBUTTONS +!define MUI_COMPONENTSPAGE_SMALLDESC +!define MUI_FINISHPAGE_NOAUTOCLOSE +!define MUI_FINISHPAGE_RUN $INSTDIR\Uninstall.exe + +; These will do the initial window resizing. +!define MUI_CUSTOMFUNCTION_GUIINIT myGUIInit +!define MUI_CUSTOMFUNCTION_UNGUIINIT un.myGUIInit + +; Each MUI install page must have a show function assigned. +!define MUI_PAGE_CUSTOMFUNCTION_SHOW WelcomePage_Show +!insertmacro MUI_PAGE_WELCOME +!define MUI_PAGE_CUSTOMFUNCTION_SHOW LicensePage_Show +!insertmacro MUI_PAGE_LICENSE `${__FILE__}` +!define MUI_PAGE_CUSTOMFUNCTION_SHOW DirectoryPage_Show +!insertmacro MUI_PAGE_DIRECTORY +!define MUI_PAGE_CUSTOMFUNCTION_SHOW ComponentsPage_Show +!insertmacro MUI_PAGE_COMPONENTS +!define MUI_PAGE_CUSTOMFUNCTION_SHOW InstFilesPage_Show +!insertmacro MUI_PAGE_INSTFILES +!define MUI_PAGE_CUSTOMFUNCTION_SHOW FinishPage_Show +!insertmacro MUI_PAGE_FINISH + +; Each MUI uninstall page must have a show function assigned. +!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.WelcomePage_Show +!insertmacro MUI_UNPAGE_WELCOME +!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.ConfirmPage_Show +!insertmacro MUI_UNPAGE_CONFIRM +!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.InstFilesPage_Show +!insertmacro MUI_UNPAGE_INSTFILES +!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.FinishPage_Show +!insertmacro MUI_UNPAGE_FINISH + +!insertmacro MUI_LANGUAGE English + +LangString SECTION_WRITE_UNINSTALLER ${LANG_ENGLISH} `This will write an uninstaller executable into the current installation directory. The uninstaller UI has also increased in size!` + +Function myGUIInit + ${nsResize_Window} ${AddWidth} ${AddHeight} +FunctionEnd + +Function WelcomePage_Show + ${nsResize_WelcomePage} ${AddWidth} ${AddHeight} +FunctionEnd + +Function LicensePage_Show + ${nsResize_LicensePage} ${AddWidth} ${AddHeight} +FunctionEnd + +Function DirectoryPage_Show + ${nsResize_DirectoryPage} ${AddWidth} ${AddHeight} +FunctionEnd + +Function ComponentsPage_Show + ${nsResize_ComponentsPage} ${AddWidth} ${AddHeight} +FunctionEnd + +Function InstFilesPage_Show + ${nsResize_InstFilesPage} ${AddWidth} ${AddHeight} +FunctionEnd + +Function FinishPage_Show + ${nsResize_FinishPage} ${AddWidth} ${AddHeight} +FunctionEnd + +Function un.myGUIInit + ${nsResize_Window} ${AddWidth} ${AddHeight} +FunctionEnd + +Function un.WelcomePage_Show + ${nsResize_WelcomePage} ${AddWidth} ${AddHeight} +FunctionEnd + +Function un.ConfirmPage_Show + ${nsResize_ConfirmPage} ${AddWidth} ${AddHeight} +FunctionEnd + +Function un.InstFilesPage_Show + ${nsResize_InstFilesPage} ${AddWidth} ${AddHeight} +FunctionEnd + +Function un.FinishPage_Show + ${nsResize_FinishPage} ${AddWidth} ${AddHeight} +FunctionEnd + +Section `Write uninstaller` SECTION_WRITE_UNINSTALLER + SectionIn RO + WriteUninstaller $INSTDIR\Uninstall.exe + MessageBox MB_OK|MB_ICONINFORMATION `Wrote: $INSTDIR\Uninstall.exe` +SectionEnd + +Section Uninstall + Delete $INSTDIR\Uninstall.exe +SectionEnd + +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SECTION_WRITE_UNINSTALLER} $(SECTION_WRITE_UNINSTALLER) +!insertmacro MUI_FUNCTION_DESCRIPTION_END \ No newline at end of file diff --git a/include/nsResize.nsh b/include/nsResize.nsh new file mode 100644 index 0000000..7573f72 --- /dev/null +++ b/include/nsResize.nsh @@ -0,0 +1,187 @@ +!ifndef __nsResize_NSH__ +!define __nsResize_NSH__ + +!macro __nsResize_AddParent Id X Y W H + GetDlgItem $R0 $HWNDPARENT ${Id} + nsResize::Add $R0 ${X} ${Y} ${W} ${H} +!macroend + +!macro __nsResize_AddChild Id X Y W H + GetDlgItem $R1 $R0 ${Id} + nsResize::Add $R1 ${X} ${Y} ${W} ${H} +!macroend + +!macro _nsResize_Window AddWidth AddHeight + + Push $R0 + nsResize::Add $HWNDPARENT 0 0 ${AddWidth} ${AddHeight} + ${If} $(^RTL) == 1 + !insertmacro __nsResize_AddParent 3 0 ${AddHeight} 0 0 + !insertmacro __nsResize_AddParent 1 0 ${AddHeight} 0 0 + !insertmacro __nsResize_AddParent 2 0 ${AddHeight} 0 0 + ${Else} + !insertmacro __nsResize_AddParent 3 ${AddWidth} ${AddHeight} 0 0 + !insertmacro __nsResize_AddParent 1 ${AddWidth} ${AddHeight} 0 0 + !insertmacro __nsResize_AddParent 2 ${AddWidth} ${AddHeight} 0 0 + !insertmacro __nsResize_AddParent 1039 ${AddWidth} 0 0 0 + ${EndIf} + !insertmacro __nsResize_AddParent 1018 0 0 ${AddWidth} ${AddHeight} + !insertmacro __nsResize_AddParent 1044 0 0 ${AddWidth} ${AddHeight} + !insertmacro __nsResize_AddParent 1035 0 ${AddHeight} ${AddWidth} 0 + !insertmacro __nsResize_AddParent 1036 0 0 ${AddWidth} 0 + !insertmacro __nsResize_AddParent 1045 0 ${AddHeight} ${AddWidth} 0 + !insertmacro __nsResize_AddParent 1256 0 ${AddHeight} ${AddWidth} 0 + !insertmacro __nsResize_AddParent 1028 0 ${AddHeight} ${AddWidth} 0 + !insertmacro __nsResize_AddParent 1034 0 0 ${AddWidth} 0 + !insertmacro __nsResize_AddParent 1037 0 0 ${AddWidth} 0 + !insertmacro __nsResize_AddParent 1038 0 0 ${AddWidth} 0 + Pop $R0 + +!macroend +!define nsResize_Window `!insertmacro _nsResize_Window` + +!macro _nsResize_WelcomePage AddWidth AddHeight + + nsResize::Add $mui.WelcomePage.Image 0 0 0 ${AddHeight} + ${If} $(^RTL) == 1 + nsResize::Add $mui.WelcomePage.Title -${AddWidth} 0 ${AddWidth} 0 + nsResize::Add $mui.WelcomePage.Text -${AddWidth} 0 ${AddWidth} ${AddHeight} + ${Else} + nsResize::Add $mui.WelcomePage.Title 0 0 ${AddWidth} 0 + nsResize::Add $mui.WelcomePage.Text 0 0 ${AddWidth} ${AddHeight} + ${EndIf} + +!macroend +!define nsResize_WelcomePage `!insertmacro _nsResize_WelcomePage` + +!macro _nsResize_LicensePage AddWidth AddHeight + + Push $R0 + Push $R1 + FindWindow $R0 `#32770` `` $HWNDPARENT + nsResize::Add $R0 0 0 ${AddWidth} ${AddHeight} + !insertmacro __nsResize_AddChild 1040 0 0 ${AddWidth} 0 + !insertmacro __nsResize_AddChild 1000 0 0 ${AddWidth} ${AddHeight} + !insertmacro __nsResize_AddChild 1006 0 ${AddHeight} ${AddWidth} 0 + !insertmacro __nsResize_AddChild 1034 0 ${AddHeight} ${AddWidth} 0 + !insertmacro __nsResize_AddChild 1035 0 ${AddHeight} ${AddWidth} 0 + Pop $R1 + Pop $R0 + +!macroend +!define nsResize_LicensePage `!insertmacro _nsResize_LicensePage` + +!macro _nsResize_DirectoryPage AddWidth AddHeight + + Push $R0 + Push $R1 + FindWindow $R0 `#32770` `` $HWNDPARENT + nsResize::Add $R0 0 0 ${AddWidth} ${AddHeight} + !insertmacro __nsResize_AddChild 1019 0 0 ${AddWidth} 0 + ${If} $(^RTL) == 1 + !insertmacro __nsResize_AddChild 1024 ${AddWidth} 0 0 0 + !insertmacro __nsResize_AddChild 1023 ${AddWidth} 0 0 0 + ${Else} + !insertmacro __nsResize_AddChild 1001 ${AddWidth} 0 0 0 + !insertmacro __nsResize_AddChild 1008 ${AddWidth} 0 0 0 + ${EndIf} + !insertmacro __nsResize_AddChild 1006 0 0 ${AddWidth} 0 + !insertmacro __nsResize_AddChild 1020 0 0 ${AddWidth} 0 + Pop $R1 + Pop $R0 + +!macroend +!define nsResize_DirectoryPage `!insertmacro _nsResize_DirectoryPage` + +!macro _nsResize_ComponentsPage AddWidth AddHeight + + Push $R0 + Push $R1 + FindWindow $R0 `#32770` `` $HWNDPARENT + nsResize::Add $R0 0 0 ${AddWidth} ${AddHeight} + !insertmacro __nsResize_AddChild 1017 0 0 ${AddWidth} ${AddHeight} + ${If} $(^RTL) == 1 + !insertmacro __nsResize_AddChild 1022 ${AddWidth} 0 0 ${AddHeight} + !insertmacro __nsResize_AddChild 1021 ${AddWidth} 0 0 0 + !insertmacro __nsResize_AddChild 1023 ${AddWidth} ${AddHeight} 0 0 + ${Else} + !insertmacro __nsResize_AddChild 1022 0 0 0 ${AddHeight} + !insertmacro __nsResize_AddChild 1023 0 ${AddHeight} 0 0 + ${EndIf} + !insertmacro __nsResize_AddChild 1006 0 0 ${AddWidth} 0 + !insertmacro __nsResize_AddChild 1032 0 0 ${AddWidth} ${AddHeight} + !ifndef MUI_COMPONENTSPAGE_NODESC + !ifdef MUI_COMPONENTSPAGE_SMALLDESC + !insertmacro __nsResize_AddChild 1042 0 ${AddHeight} ${AddWidth} 0 + !insertmacro __nsResize_AddChild 1043 0 ${AddHeight} ${AddWidth} 0 + !else + !insertmacro __nsResize_AddChild 1042 ${AddWidth} 0 0 ${AddHeight} + !insertmacro __nsResize_AddChild 1043 ${AddWidth} 0 0 ${AddHeight} + !endif + !endif + Pop $R1 + Pop $R0 + +!macroend +!define nsResize_ComponentsPage `!insertmacro _nsResize_ComponentsPage` + +!macro _nsResize_InstFilesPage AddWidth AddHeight + + Push $R0 + Push $R1 + FindWindow $R0 `#32770` `` $HWNDPARENT + nsResize::Add $R0 0 0 ${AddWidth} ${AddHeight} + !insertmacro __nsResize_AddChild 1004 0 0 ${AddWidth} 0 + !insertmacro __nsResize_AddChild 1006 0 0 ${AddWidth} 0 + !insertmacro __nsResize_AddChild 1016 0 0 ${AddWidth} ${AddHeight} + Pop $R1 + Pop $R0 + +!macroend +!define nsResize_InstFilesPage `!insertmacro _nsResize_InstFilesPage` + +!macro _nsResize_FinishPage AddWidth AddHeight + + nsResize::Add $mui.FinishPage.Image 0 0 0 ${AddHeight} + ${If} $(^RTL) == 1 + nsResize::Add $mui.FinishPage.Title -${AddWidth} 0 ${AddWidth} 0 + nsResize::Add $mui.FinishPage.Text -${AddWidth} 0 ${AddWidth} ${AddHeight} + ${Else} + nsResize::Add $mui.FinishPage.Title 0 0 ${AddWidth} 0 + nsResize::Add $mui.FinishPage.Text 0 0 ${AddWidth} ${AddHeight} + ${EndIf} + !ifdef MUI_FINISHPAGE_RUN_VARIABLES + nsResize::Add $mui.FinishPage.Run 0 ${AddHeight} 0 0 + !endif + !ifdef MUI_FINISHPAGE_SHOREADME_VARAIBLES ;not a typo! + nsResize::Add $mui.FinishPage.ShowReadme 0 ${AddHeight} 0 0 + !endif + !ifdef MUI_FINISHPAGE_LINK + nsResize::Add $mui.FinishPage.Link 0 ${AddHeight} 0 0 + !endif + !ifdef MUI_FINISHPAGE_REBOOT_VARIABLES + nsResize::Add $mui.FinishPage.RebootNow 0 ${AddHeight} 0 0 + nsResize::Add $mui.FinishPage.RebootLater 0 ${AddHeight} 0 0 + !endif + +!macroend +!define nsResize_FinishPage `!insertmacro _nsResize_FinishPage` + +!macro _nsResize_ConfirmPage AddWidth AddHeight + + Push $R0 + Push $R1 + FindWindow $R0 `#32770` `` $HWNDPARENT + nsResize::Add $R0 0 0 ${AddWidth} ${AddHeight} + ${If} $(^RTL) == 1 + !insertmacro __nsResize_AddChild 1029 ${AddWidth} 0 0 0 + ${EndIf} + !insertmacro __nsResize_AddChild 1000 0 0 ${AddWidth} 0 + !insertmacro __nsResize_AddChild 1006 0 0 ${AddWidth} 0 + Pop $R1 + Pop $R0 + +!macroend +!define nsResize_ConfirmPage `!insertmacro _nsResize_ConfirmPage` + +!endif \ No newline at end of file diff --git a/src/api.h b/src/api.h new file mode 100644 index 0000000..0d6b55e --- /dev/null +++ b/src/api.h @@ -0,0 +1,85 @@ +/* + * apih + * + * This file is a part of NSIS. + * + * Copyright (C) 1999-2019 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 structure containing other interesting stuff +// besides the stack, variables and HWND passed on to plug-ins. +typedef struct +{ + int autoclose; // SetAutoClose + int all_user_var; // SetShellVarContext: User context = 0, Machine context = 1 + int exec_error; // IfErrors + int abort; // IfAbort + int exec_reboot; // IfRebootFlag (NSIS_SUPPORT_REBOOT) + int reboot_called; // NSIS_SUPPORT_REBOOT + int XXX_cur_insttype; // Deprecated + int plugin_api_version; // Plug-in ABI. See NSISPIAPIVER_CURR (Note: used to be XXX_insttype_changed) + int silent; // IfSilent (NSIS_CONFIG_SILENT_SUPPORT) + int instdir_error; // GetInstDirError + int rtl; // 1 if $LANGUAGE is a RTL language + int errlvl; // SetErrorLevel + int alter_reg_view; // SetRegView: Default View = 0, Alternative View = (sizeof(void*) > 4 ? KEY_WOW64_32KEY : KEY_WOW64_64KEY) + int status_update; // SetDetailsPrint +} exec_flags_t; + +#ifndef NSISCALL +# define NSISCALL __stdcall +#endif +#if !defined(_WIN32) && !defined(LPTSTR) +# define LPTSTR TCHAR* +#endif + +typedef struct { + exec_flags_t *exec_flags; + int (NSISCALL *ExecuteCodeSegment)(int, HWND); + void (NSISCALL *validate_filename)(LPTSTR); + 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 /* _NSIS_EXEHEAD_API_H_ */ diff --git a/src/build_pluginapi_x64.cmd b/src/build_pluginapi_x64.cmd new file mode 100644 index 0000000..797b1eb --- /dev/null +++ b/src/build_pluginapi_x64.cmd @@ -0,0 +1,7 @@ +@echo off +call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" +cd /d %~dp0 +cl /c /O2 /DUNICODE /D_UNICODE /GS- /TC pluginapi.c /Fo:pluginapi_x64.obj +lib /OUT:nsis_unicode\pluginapi_x64.lib pluginapi_x64.obj +del pluginapi_x64.obj +echo Done! diff --git a/src/build_pluginapi_x86.cmd b/src/build_pluginapi_x86.cmd new file mode 100644 index 0000000..9334f83 --- /dev/null +++ b/src/build_pluginapi_x86.cmd @@ -0,0 +1,15 @@ +@echo off +call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars32.bat" +cd /d %~dp0 + +REM Build Unicode version +cl /c /O2 /DUNICODE /D_UNICODE /GS- /TC pluginapi.c /Fo:pluginapi_unicode.obj +lib /OUT:nsis_unicode\pluginapi.lib pluginapi_unicode.obj +del pluginapi_unicode.obj + +REM Build ANSI version +cl /c /O2 /GS- /TC pluginapi.c /Fo:pluginapi_ansi.obj +lib /OUT:nsis_ansi\pluginapi.lib pluginapi_ansi.obj +del pluginapi_ansi.obj + +echo Done! diff --git a/src/nsResize.c b/src/nsResize.c new file mode 100644 index 0000000..106b320 --- /dev/null +++ b/src/nsResize.c @@ -0,0 +1,257 @@ +/* + +nsResize NSIS plug-in + +Author: Stuart 'Afrow UK' Welch +Company: Afrow Soft Ltd. +Website: http://www.afrowsoft.co.uk +E-mail: afrowuk@afrowsoft.co.uk +Date: 13th April 2013 +Version: 1.0.0.0 + +A small NSIS plug-in for resizing windows. Uses code from nsDialogs for unit/RTL conversion. + +*/ + +#include +#include "nsResize.h" + +#include "pluginapi.h" + +HANDLE g_hInstance; +HWND g_hWndParent; +BOOL g_rtl; + +BOOL WINAPI DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) +{ + g_hInstance = hInst; + return TRUE; +} + +static UINT_PTR PluginCallback(enum NSPIM msg) +{ + return 0; +} + +static int ConvertPlacement(TCHAR* str, int total, BOOL height, BOOL add) +{ + TCHAR unit = *CharPrev(str, str + lstrlen(str)); + int x = myatoi(str); + + if (unit == TEXT('%')) + { + if (x < 0 && !add) + return MulDiv(total, 100 + x, 100); + return MulDiv(total, x, 100); + } + + if (unit == TEXT('u')) + { + RECT r; + r.left = r.top = x; + r.right = r.bottom = 0; + MapDialogRect(g_hWndParent, &r); + + if (height) + return x >= 0 || add ? r.top : total + r.top; + return x >= 0 || add ? r.left : total + r.left; + } + + if (x < 0 && !add) + return total + x; + return x; +} + +static void ConvertPosToRTL(int *x, int width, int dialogWidth) +{ + if (!g_rtl) + return; + + *x = dialogWidth - width - *x; +} + +static BOOL PopPlacement(HWND hWndControl, int* x, int* y, int* width, int* height, UINT* uFlags, BOOL add) +{ + RECT dialogRect; + int dialogWidth; + int dialogHeight; + TCHAR buf[1024]; + + GetClientRect(GetParent(hWndControl), &dialogRect); + dialogWidth = dialogRect.right; + dialogHeight = dialogRect.bottom; + + if (popstringn(buf, 1024)) + return FALSE; + + if (!*buf) + *uFlags |= SWP_NOMOVE; + else + *x = ConvertPlacement(buf, dialogWidth, FALSE, add); + + if (popstringn(buf, 1024)) + return FALSE; + + if (!*buf) + *uFlags |= SWP_NOMOVE; + else + *y = ConvertPlacement(buf, dialogHeight, TRUE, add); + + if (popstringn(buf, 1024)) + return FALSE; + + if (!*buf) + *uFlags |= SWP_NOSIZE; + else + *width = ConvertPlacement(buf, dialogWidth, FALSE, add); + + if (popstringn(buf, 1024)) + return FALSE; + + if (!*buf) + *uFlags |= SWP_NOSIZE; + else + *height = ConvertPlacement(buf, dialogHeight, TRUE, add); + + if (*uFlags & SWP_NOMOVE) + *x = *y = 0; + + if (*uFlags & SWP_NOSIZE) + *width = *height = 0; + + if (!add) + { + if (*uFlags & SWP_NOSIZE) + { + GetClientRect(hWndControl, &dialogRect); + ConvertPosToRTL(x, dialogRect.right, dialogWidth); + } + else + { + ConvertPosToRTL(x, *width, dialogWidth); + } + } + + return TRUE; +} + +static void GetRect(HWND hWnd, RECT* r) +{ + GetWindowRect(hWnd, r); + MapWindowPoints(NULL, GetParent(hWnd), (LPPOINT)r, 2); +} + +NSISFUNC(SetRTL) +{ + DLL_INIT(); + extra->RegisterPluginCallback((HMODULE)g_hInstance, PluginCallback); + g_rtl = (BOOL)popint(); +} + +NSISFUNC(Set) +{ + DLL_INIT(); + { + HWND hWndCtl = (HWND)popintptr(); + if (IsWindow(hWndCtl)) + { + int x, y, width, height; + UINT uFlags = SWP_NOZORDER; + if (PopPlacement(hWndCtl, &x, &y, &width, &height, &uFlags, FALSE)) + SetWindowPos(hWndCtl, NULL, x, y, width, height, uFlags); + } + } +} + +NSISFUNC(Add) +{ + DLL_INIT(); + { + HWND hWndCtl = (HWND)popintptr(); + if (IsWindow(hWndCtl)) + { + int x, y, width, height; + UINT uFlags = SWP_NOZORDER; + if (PopPlacement(hWndCtl, &x, &y, &width, &height, &uFlags, TRUE)) + { + RECT pos; + GetRect(hWndCtl, &pos); + SetWindowPos(hWndCtl, NULL, pos.left + x, pos.top + y, (pos.right - pos.left) + width, (pos.bottom - pos.top) + height, uFlags); + } + } + } +} + +NSISFUNC(Top) +{ + DLL_INIT(); + { + HWND hWndCtl = (HWND)popintptr(); + if (IsWindow(hWndCtl)) + { + SetWindowPos(hWndCtl, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); + } + } +} + +NSISFUNC(GetPosPx) +{ + DLL_INIT(); + { + HWND hWndCtl = (HWND)popintptr(); + if (IsWindow(hWndCtl)) + { + RECT pos; + GetRect(hWndCtl, &pos); + MapDialogRect(g_hWndParent, &pos); + pushint(pos.top); + pushint(pos.left); + } + } +} + +NSISFUNC(GetPos) +{ + DLL_INIT(); + { + HWND hWndCtl = (HWND)popintptr(); + if (IsWindow(hWndCtl)) + { + RECT pos; + GetRect(hWndCtl, &pos); + pushint(pos.top); + pushint(pos.left); + } + } +} + +NSISFUNC(GetSizePx) +{ + DLL_INIT(); + { + HWND hWndCtl = (HWND)popintptr(); + if (IsWindow(hWndCtl)) + { + RECT pos; + GetRect(hWndCtl, &pos); + MapDialogRect(g_hWndParent, &pos); + pushint(pos.bottom); + pushint(pos.right); + } + } +} + +NSISFUNC(GetSize) +{ + DLL_INIT(); + { + HWND hWndCtl = (HWND)popintptr(); + if (IsWindow(hWndCtl)) + { + RECT pos; + GetRect(hWndCtl, &pos); + pushint(pos.bottom); + pushint(pos.right); + } + } +} \ No newline at end of file diff --git a/src/nsResize.h b/src/nsResize.h new file mode 100644 index 0000000..b02450f --- /dev/null +++ b/src/nsResize.h @@ -0,0 +1,7 @@ +#ifndef __NsThread_H__ +#define __NsThread_H__ + +#define NSISFUNC(name) void __declspec(dllexport) name(HWND hWndParent, int string_size, TCHAR* variables, stack_t** stacktop, extra_parameters* extra) +#define DLL_INIT() { EXDLL_INIT(); g_hWndParent = hWndParent; } + +#endif \ No newline at end of file diff --git a/src/nsResize.rc b/src/nsResize.rc new file mode 100644 index 0000000..4781c34 Binary files /dev/null and b/src/nsResize.rc differ diff --git a/src/nsResize.sln b/src/nsResize.sln new file mode 100644 index 0000000..5bff18c --- /dev/null +++ b/src/nsResize.sln @@ -0,0 +1,23 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{4977186E-7BE7-4DC5-809E-596BF31A2C8C}") = "nsResize", "nsResize.vcxproj", "{C5988C24-9154-4DE8-92E3-DF89FBFA72EC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release Unicode|Win32 = Release Unicode|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C5988C24-9154-4DE8-92E3-DF89FBFA72EC}.Debug|Win32.ActiveCfg = Debug|Win32 + {C5988C24-9154-4DE8-92E3-DF89FBFA72EC}.Debug|Win32.Build.0 = Debug|Win32 + {C5988C24-9154-4DE8-92E3-DF89FBFA72EC}.Release Unicode|Win32.ActiveCfg = Release Unicode|Win32 + {C5988C24-9154-4DE8-92E3-DF89FBFA72EC}.Release Unicode|Win32.Build.0 = Release Unicode|Win32 + {C5988C24-9154-4DE8-92E3-DF89FBFA72EC}.Release|Win32.ActiveCfg = Release|Win32 + {C5988C24-9154-4DE8-92E3-DF89FBFA72EC}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/nsResize.vcxproj b/src/nsResize.vcxproj new file mode 100644 index 0000000..c2df84b --- /dev/null +++ b/src/nsResize.vcxproj @@ -0,0 +1,165 @@ + + + + + Debug + Win32 + + + Release Unicode + Win32 + + + Release Unicode + x64 + + + Release + Win32 + + + + {C5988C24-9154-4DE8-92E3-DF89FBFA72EC} + nsResize + + + + DynamicLibrary + true + MultiByte + v110 + + + DynamicLibrary + false + true + MultiByte + v110 + + + DynamicLibrary + false + true + Unicode + v110 + + + DynamicLibrary + false + true + Unicode + v143 + + + + + + + + + + + + + + + + + + + $(SolutionDir)..\..\Unicode\Plugins\ + false + + + $(SolutionDir)..\..\Unicode\Plugins\x64\ + false + + + $(SolutionDir)..\..\Plugins\ + false + + + $(SolutionDir)..\..\Plugins\ + + + + Level3 + Disabled + + + true + nsis_ansi\pluginapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + DllMain + libc.lib + + + + + Level3 + MaxSpeed + true + true + false + + + true + true + nsis_ansi\pluginapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + true + libc.lib + false + DllMain + false + + + + + Level3 + MaxSpeed + true + true + false + + + false + true + true + nsis_unicode\pluginapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + true + libc.lib + DllMain + false + + + + + Level3 + MaxSpeed + true + true + false + + + false + true + true + nsis_unicode\pluginapi_x64.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + true + libc.lib + DllMain + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/nsResize.vcxproj.filters b/src/nsResize.vcxproj.filters new file mode 100644 index 0000000..864d061 --- /dev/null +++ b/src/nsResize.vcxproj.filters @@ -0,0 +1,35 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Header Files + + + Header Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/src/nsis_tchar.h b/src/nsis_tchar.h new file mode 100644 index 0000000..cc861b7 --- /dev/null +++ b/src/nsis_tchar.h @@ -0,0 +1,229 @@ +/* + * nsis_tchar.h + * + * This file is a part of NSIS. + * + * Copyright (C) 1999-2019 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||_MSC_FULL_VER<=140040310)) || 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 +# define _vstprintf _vswprintf +#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 +#define _tcstok wcstok + +// 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 +#define _tenviron environ +#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 + +#define _totupper toupper +#define _totlower tolower + +// 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) +#define _istgraph isgraph +#define _istascii __isascii + +#define __TFILE__ _T(__FILE__) +#define __TDATE__ _T(__DATE__) +#define __TTIME__ _T(__TIME__) diff --git a/src/pluginapi.c b/src/pluginapi.c new file mode 100644 index 0000000..110f70e --- /dev/null +++ b/src/pluginapi.c @@ -0,0 +1,305 @@ +#include +#include "pluginapi.h" + +#ifndef COUNTOF +#define COUNTOF(a) (sizeof(a)/sizeof(a[0])) +#endif + +// minimal tchar.h emulation +#ifndef _T +# define _T TEXT +#endif +#if !defined(TCHAR) && !defined(_TCHAR_DEFINED) +# ifdef UNICODE +# define TCHAR WCHAR +# else +# define TCHAR char +# endif +#endif + +#define isvalidnsisvarindex(varnum) ( ((unsigned int)(varnum)) < (__INST_LAST) ) + +unsigned int g_stringsize; +stack_t **g_stacktop; +LPTSTR g_variables; + +// utility functions (not required but often useful) + +int NSISCALL popstring(LPTSTR str) +{ + stack_t *th; + if (!g_stacktop || !*g_stacktop) return 1; + th=(*g_stacktop); + if (str) lstrcpy(str,th->text); + *g_stacktop = th->next; + GlobalFree((HGLOBAL)th); + return 0; +} + +int NSISCALL popstringn(LPTSTR 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(LPCTSTR str) +{ + stack_t *th; + if (!g_stacktop) return; + th=(stack_t*)GlobalAlloc(GPTR,(sizeof(stack_t)+(g_stringsize)*sizeof(*str))); + lstrcpyn(th->text,str,g_stringsize); + th->next=*g_stacktop; + *g_stacktop=th; +} + +LPTSTR NSISCALL getuservariable(const int varnum) +{ + if (!isvalidnsisvarindex(varnum)) return NULL; + return g_variables+varnum*g_stringsize; +} + +void NSISCALL setuservariable(const int varnum, LPCTSTR var) +{ + if (var && isvalidnsisvarindex(varnum)) + lstrcpy(g_variables + varnum*g_stringsize, var); +} + +#ifdef UNICODE +int NSISCALL PopStringA(LPSTR ansiStr) +{ + LPWSTR wideStr = (LPWSTR) GlobalAlloc(GPTR, g_stringsize*sizeof(WCHAR)); + int rval = popstring(wideStr); + WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL); + GlobalFree((HGLOBAL)wideStr); + return rval; +} + +int NSISCALL PopStringNA(LPSTR ansiStr, int maxlen) +{ + int realLen = maxlen ? maxlen : g_stringsize; + LPWSTR wideStr = (LPWSTR) GlobalAlloc(GPTR, realLen*sizeof(WCHAR)); + int rval = popstringn(wideStr, realLen); + WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, realLen, NULL, NULL); + GlobalFree((HGLOBAL)wideStr); + return rval; +} + +void NSISCALL PushStringA(LPCSTR ansiStr) +{ + LPWSTR wideStr = (LPWSTR) GlobalAlloc(GPTR, g_stringsize*sizeof(WCHAR)); + MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize); + pushstring(wideStr); + GlobalFree((HGLOBAL)wideStr); + return; +} + +void NSISCALL GetUserVariableW(const int varnum, LPWSTR wideStr) +{ + lstrcpyW(wideStr, getuservariable(varnum)); +} + +void NSISCALL GetUserVariableA(const int varnum, LPSTR ansiStr) +{ + LPWSTR wideStr = getuservariable(varnum); + WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL); +} + +void NSISCALL SetUserVariableA(const int varnum, LPCSTR ansiStr) +{ + if (ansiStr && isvalidnsisvarindex(varnum)) + { + LPWSTR wideStr = g_variables + varnum * g_stringsize; + MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize); + } +} + +#else +// ANSI defs +int NSISCALL PopStringW(LPWSTR wideStr) +{ + LPSTR ansiStr = (LPSTR) 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(LPWSTR wideStr, int maxlen) +{ + int realLen = maxlen ? maxlen : g_stringsize; + LPSTR ansiStr = (LPSTR) GlobalAlloc(GPTR, realLen); + int rval = popstringn(ansiStr, realLen); + MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, realLen); + GlobalFree((HGLOBAL)ansiStr); + return rval; +} + +void NSISCALL PushStringW(LPWSTR wideStr) +{ + LPSTR ansiStr = (LPSTR) 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, LPWSTR wideStr) +{ + LPSTR ansiStr = getuservariable(varnum); + MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize); +} + +void NSISCALL GetUserVariableA(const int varnum, LPSTR ansiStr) +{ + lstrcpyA(ansiStr, getuservariable(varnum)); +} + +void NSISCALL SetUserVariableW(const int varnum, LPCWSTR wideStr) +{ + if (wideStr && isvalidnsisvarindex(varnum)) + { + LPSTR 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(LPCTSTR 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(LPCTSTR 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(LPCTSTR 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); +} diff --git a/src/pluginapi.h b/src/pluginapi.h new file mode 100644 index 0000000..11ccfeb --- /dev/null +++ b/src/pluginapi.h @@ -0,0 +1,109 @@ +#ifndef ___NSIS_PLUGIN__H___ +#define ___NSIS_PLUGIN__H___ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "api.h" +#include "nsis_tchar.h" // BUGBUG: Why cannot our plugins use the compilers tchar.h? + +#ifndef NSISCALL +# define NSISCALL WINAPI +#endif + +#define EXDLL_INIT() { \ + g_stringsize=string_size; \ + g_stacktop=stacktop; \ + g_variables=variables; } + +typedef struct _stack_t { + struct _stack_t *next; +#ifdef UNICODE + WCHAR text[1]; // this should be the length of g_stringsize when allocating +#else + char text[1]; +#endif +} 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 LPTSTR g_variables; + +void NSISCALL pushstring(LPCTSTR str); +void NSISCALL pushintptr(INT_PTR value); +#define pushint(v) pushintptr((INT_PTR)(v)) +int NSISCALL popstring(LPTSTR str); // 0 on success, 1 on empty stack +int NSISCALL popstringn(LPTSTR 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(LPCTSTR s); +#define myatoi(s) ( (int) nsishelper_str_to_ptr(s) ) // converts a string to an integer +unsigned int NSISCALL myatou(LPCTSTR s); // converts a string to an unsigned integer, decimal only +int NSISCALL myatoi_or(LPCTSTR s); // with support for or'ing (2|4|8) +LPTSTR NSISCALL getuservariable(const int varnum); +void NSISCALL setuservariable(const int varnum, LPCTSTR var); + +#ifdef UNICODE +#define PopStringW(x) popstring(x) +#define PushStringW(x) pushstring(x) +#define SetUserVariableW(x,y) setuservariable(x,y) + +int NSISCALL PopStringA(LPSTR ansiStr); +int NSISCALL PopStringNA(LPSTR ansiStr, int maxlen); +void NSISCALL PushStringA(LPCSTR ansiStr); +void NSISCALL GetUserVariableW(const int varnum, LPWSTR wideStr); +void NSISCALL GetUserVariableA(const int varnum, LPSTR ansiStr); +void NSISCALL SetUserVariableA(const int varnum, LPCSTR ansiStr); + +#else +// ANSI defs + +#define PopStringA(x) popstring(x) +#define PushStringA(x) pushstring(x) +#define SetUserVariableA(x,y) setuservariable(x,y) + +int NSISCALL PopStringW(LPWSTR wideStr); +void NSISCALL PushStringW(LPWSTR wideStr); +void NSISCALL GetUserVariableW(const int varnum, LPWSTR wideStr); +void NSISCALL GetUserVariableA(const int varnum, LPSTR ansiStr); +void NSISCALL SetUserVariableW(const int varnum, LPCWSTR wideStr); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif//!___NSIS_PLUGIN__H___ diff --git a/src/resource.h b/src/resource.h new file mode 100644 index 0000000..147df92 --- /dev/null +++ b/src/resource.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by nsResize.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif