chore: initial commit (extracted from Launchers monorepo)
Plugin: UniversalShellExt v1.0.0 Architectures: x64 License: MIT
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.{yml,yaml,json}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.{cmd,bat,ps1}]
|
||||||
|
end_of_line = crlf
|
||||||
|
|
||||||
|
[*.{nsi,nsh,sln,vcxproj,filters}]
|
||||||
|
end_of_line = crlf
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
@@ -0,0 +1,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
|
||||||
|
*.rc binary
|
||||||
|
*.rc2 binary
|
||||||
|
|
||||||
|
# Binari
|
||||||
|
*.dll binary
|
||||||
|
*.exe binary
|
||||||
|
*.lib binary
|
||||||
|
*.obj binary
|
||||||
|
*.pdb binary
|
||||||
|
*.zip binary
|
||||||
|
*.7z binary
|
||||||
|
*.png binary
|
||||||
|
*.ico binary
|
||||||
|
*.gif binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.bmp binary
|
||||||
|
*.pdf binary
|
||||||
|
|
||||||
|
# Linguistic detection (GitHub Linguist / Gitea analogo)
|
||||||
|
*.nsi linguist-language=NSIS
|
||||||
|
*.nsh linguist-language=NSIS
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Segnala un bug riproducibile
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels: ["bug", "triage"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Grazie per la segnalazione. Compila i campi sotto per aiutarci a riprodurre il problema.
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: plugin-version
|
||||||
|
attributes:
|
||||||
|
label: Versione plugin
|
||||||
|
description: Contenuto del file VERSION o tag della release scaricata
|
||||||
|
placeholder: "1.6.0"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: architecture
|
||||||
|
attributes:
|
||||||
|
label: Architettura DLL usata
|
||||||
|
options:
|
||||||
|
- x86-ansi
|
||||||
|
- x86-unicode
|
||||||
|
- amd64-unicode
|
||||||
|
- non so / multiple
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: nsis-version
|
||||||
|
attributes:
|
||||||
|
label: Versione NSIS
|
||||||
|
placeholder: "3.10"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: Sistema operativo
|
||||||
|
placeholder: "Windows 11 23H2 x64"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: repro
|
||||||
|
attributes:
|
||||||
|
label: Passi per riprodurre
|
||||||
|
description: Script .nsi minimale che riproduce il problema
|
||||||
|
render: nsis
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected
|
||||||
|
attributes:
|
||||||
|
label: Comportamento atteso
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: actual
|
||||||
|
attributes:
|
||||||
|
label: Comportamento osservato
|
||||||
|
description: Includi messaggi di errore esatti, output del log NSIS, screenshot
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: Note aggiuntive
|
||||||
|
description: Qualunque altra informazione utile (workaround tentati, ecc.)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Domande generali
|
||||||
|
url: https://gitea.emulab.it/Simone/universal-shell-ext/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/universal-shell-ext/security/advisories/new
|
||||||
|
about: Segnala vulnerabilità di security in privato (non aprire issue pubbliche)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Suggerisci una nuova funzionalità o miglioramento
|
||||||
|
title: "[Feature]: "
|
||||||
|
labels: ["enhancement"]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Problema / Caso d'uso
|
||||||
|
description: Quale problema vorresti risolvere? Quale workflow vuoi abilitare?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: proposal
|
||||||
|
attributes:
|
||||||
|
label: Proposta
|
||||||
|
description: Come dovrebbe funzionare? Includi esempio di API/uso negli script NSIS se possibile.
|
||||||
|
render: nsis
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Alternative considerate
|
||||||
|
description: Altri approcci che hai valutato e perché sono stati scartati
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: Note aggiuntive
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
## Descrizione
|
||||||
|
|
||||||
|
<!-- Descrivi cosa cambia questa PR e perché. Riferisci issue se applicabile (Closes #XXX). -->
|
||||||
|
|
||||||
|
## Tipo di Modifica
|
||||||
|
|
||||||
|
- [ ] Bug fix (modifica non breaking che risolve un problema)
|
||||||
|
- [ ] Feature (modifica non breaking che aggiunge funzionalità)
|
||||||
|
- [ ] Breaking change (fix o feature che rompe la compatibilità)
|
||||||
|
- [ ] Documentazione
|
||||||
|
- [ ] Build / CI
|
||||||
|
|
||||||
|
## Architetture Testate
|
||||||
|
|
||||||
|
- [ ] `x86-ansi`
|
||||||
|
- [ ] `x86-unicode`
|
||||||
|
- [ ] `amd64-unicode`
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
- [ ] Build completa senza warning: `python build_plugin.py`
|
||||||
|
- [ ] CHANGELOG.md aggiornato (sezione `[Unreleased]`)
|
||||||
|
- [ ] README / docs aggiornati se l'API è cambiata
|
||||||
|
- [ ] Esempi aggiornati se l'API è cambiata
|
||||||
|
- [ ] Conventional Commits rispettato
|
||||||
|
- [ ] Nessun file in `dist/` o `__pycache__/` committato
|
||||||
|
- [ ] Self-review completata
|
||||||
|
|
||||||
|
## Note Aggiuntive
|
||||||
|
|
||||||
|
<!-- Eventuali considerazioni per i reviewer, screenshot, output di test, ecc. -->
|
||||||
@@ -0,0 +1,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 }} --verbose
|
||||||
|
|
||||||
|
- name: Upload DLL
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ github.event.repository.name }}-${{ steps.version.outputs.version }}-${{ matrix.config }}
|
||||||
|
path: dist/**
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 30
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# Release (GitHub) — pipeline release primaria
|
||||||
|
#
|
||||||
|
# Triggerata quando un tag v* viene propagato dal mirror Gitea.
|
||||||
|
# Costruisce gli artifact per tutte le architetture e crea la GitHub Release.
|
||||||
|
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ['v*']
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: 'Tag versione (es. v1.7.0)'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: windows-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
config: [x86-ansi, x86-unicode, x64-unicode]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- uses: microsoft/setup-msbuild@v2
|
||||||
|
|
||||||
|
- name: Build ${{ matrix.config }}
|
||||||
|
run: python build_plugin.py --config ${{ matrix.config }}
|
||||||
|
|
||||||
|
- name: Determine version & arch
|
||||||
|
id: meta
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$tag = "${{ github.ref_name }}"
|
||||||
|
if ([string]::IsNullOrEmpty($tag) -or $tag -eq "main") { $tag = "${{ inputs.tag }}" }
|
||||||
|
$v = $tag -replace '^v',''
|
||||||
|
$map = @{ 'x86-ansi' = 'x86-ansi'; 'x86-unicode' = 'x86-unicode'; 'x64-unicode' = 'amd64-unicode' }
|
||||||
|
$dir = $map['${{ matrix.config }}']
|
||||||
|
echo "version=$v" >> $env:GITHUB_OUTPUT
|
||||||
|
echo "tag=$tag" >> $env:GITHUB_OUTPUT
|
||||||
|
echo "dir=$dir" >> $env:GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Package
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$name = "${{ github.event.repository.name }}-${{ steps.meta.outputs.version }}-${{ steps.meta.outputs.dir }}"
|
||||||
|
$stage = "stage/$name"
|
||||||
|
New-Item -ItemType Directory -Force -Path $stage | Out-Null
|
||||||
|
Copy-Item "dist/${{ steps.meta.outputs.dir }}/*" $stage -Recurse
|
||||||
|
Copy-Item README.md, LICENSE, VERSION, CHANGELOG.md $stage
|
||||||
|
if (Test-Path "include") { Copy-Item "include" $stage -Recurse }
|
||||||
|
if (Test-Path "examples") { Copy-Item "examples" $stage -Recurse }
|
||||||
|
Compress-Archive -Path "$stage/*" -DestinationPath "$name.zip" -Force
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: pkg-${{ matrix.config }}
|
||||||
|
path: '*.zip'
|
||||||
|
|
||||||
|
publish:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: SHA256SUMS
|
||||||
|
run: |
|
||||||
|
cd artifacts
|
||||||
|
sha256sum *.zip > SHA256SUMS.txt
|
||||||
|
cat SHA256SUMS.txt
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
artifacts/*.zip
|
||||||
|
artifacts/SHA256SUMS.txt
|
||||||
|
generate_release_notes: true
|
||||||
|
body: |
|
||||||
|
**Repository primario**: vedi link Gitea nel README.
|
||||||
|
|
||||||
|
Il mirror GitHub è generato automaticamente dal repo Gitea (sorgente di verità).
|
||||||
|
Per issue e PR aprire ticket sul repository primario Gitea.
|
||||||
+63
@@ -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/
|
||||||
@@ -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/universal-shell-ext/compare/v1.0.0...HEAD
|
||||||
|
[1.0.0]: https://gitea.emulab.it/Simone/universal-shell-ext/releases/tag/v1.0.0
|
||||||
+137
@@ -0,0 +1,137 @@
|
|||||||
|
# Contribuire a UniversalShellExt
|
||||||
|
|
||||||
|
Grazie per il tuo interesse a contribuire!
|
||||||
|
|
||||||
|
> Repository primario: [https://gitea.emulab.it/Simone/universal-shell-ext](https://gitea.emulab.it/Simone/universal-shell-ext)
|
||||||
|
> Mirror GitHub: [https://github.com/systempal/universal-shell-ext](https://github.com/systempal/universal-shell-ext) (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/universal-shell-ext.git
|
||||||
|
cd universal-shell-ext
|
||||||
|
python build_plugin.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Output in `dist/{x86-ansi,x86-unicode,amd64-unicode}/`.
|
||||||
|
|
||||||
|
## Workflow Sviluppo
|
||||||
|
|
||||||
|
### 1. Branch
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git checkout -b feat/nome-feature # nuova feature
|
||||||
|
git checkout -b fix/descrizione-bug # bugfix
|
||||||
|
git checkout -b docs/aggiornamento # solo doc
|
||||||
|
git checkout -b chore/manutenzione # build, dipendenze
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Commit
|
||||||
|
|
||||||
|
Segui [Conventional Commits](https://www.conventionalcommits.org/it/v1.0.0/):
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: aggiunge supporto per formato XYZ
|
||||||
|
fix: corregge crash su Windows 11 ARM
|
||||||
|
docs: aggiorna esempio di utilizzo
|
||||||
|
chore: aggiorna toolset MSVC
|
||||||
|
refactor: estrae logica parsing in funzione separata
|
||||||
|
test: aggiunge test per edge case
|
||||||
|
build: aggiorna script di build
|
||||||
|
ci: aggiorna workflow GitHub Actions
|
||||||
|
```
|
||||||
|
|
||||||
|
Breaking changes:
|
||||||
|
|
||||||
|
```
|
||||||
|
feat!: rinomina API Foo() in Bar()
|
||||||
|
|
||||||
|
BREAKING CHANGE: Foo() rinominata in Bar(); aggiornare tutti i chiamanti.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Pull Request
|
||||||
|
|
||||||
|
PR aperte verso il branch `main` su Gitea. Verifica:
|
||||||
|
|
||||||
|
- [ ] Build completa senza warning per tutte le architetture: `python build_plugin.py`
|
||||||
|
- [ ] Esempi aggiornati se cambia l'API
|
||||||
|
- [ ] `CHANGELOG.md` aggiornato (sezione `[Unreleased]`)
|
||||||
|
- [ ] Documentazione aggiornata (README, docs/API.md se esiste)
|
||||||
|
- [ ] Conventional Commits rispettato
|
||||||
|
- [ ] Nessun file in `dist/` o `__pycache__/` committato
|
||||||
|
|
||||||
|
## Build Script
|
||||||
|
|
||||||
|
```
|
||||||
|
python build_plugin.py [opzioni]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Opzione | Default | Descrizione |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| `--config` | `all` | Architettura: `x86-ansi`, `x86-unicode`, `x64-unicode`, `all` |
|
||||||
|
| `--toolset` | `auto` | Visual Studio: `2022`, `2026`, `auto` (rileva il più recente) |
|
||||||
|
| `--jobs` | numero CPU | Thread paralleli per la build |
|
||||||
|
| `--clean` | false | Pulisce `dist/` prima di compilare |
|
||||||
|
| `--install-dir` | none | Copia DLL anche in `<dir>/{arch}/` (es. NSIS plugin folder) |
|
||||||
|
| `--verbose` | false | Output dettagliato (mostra comandi MSBuild) |
|
||||||
|
|
||||||
|
Exit codes:
|
||||||
|
- `0` — Successo
|
||||||
|
- `1` — Errore di build
|
||||||
|
- `2` — Prerequisiti mancanti
|
||||||
|
- `3` — Argomenti invalidi
|
||||||
|
|
||||||
|
## Release Workflow
|
||||||
|
|
||||||
|
Le release sono automatiche via tag Git:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 1. Aggiorna VERSION e CHANGELOG.md
|
||||||
|
echo "1.7.0" > VERSION
|
||||||
|
# (modifica CHANGELOG.md spostando entry da [Unreleased] a [1.7.0])
|
||||||
|
|
||||||
|
# 2. Commit
|
||||||
|
git add VERSION CHANGELOG.md
|
||||||
|
git commit -m "chore(release): v1.7.0"
|
||||||
|
|
||||||
|
# 3. Tag annotato
|
||||||
|
git tag -a v1.7.0 -m "Release v1.7.0"
|
||||||
|
|
||||||
|
# 4. Push
|
||||||
|
git push origin main
|
||||||
|
git push origin v1.7.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Il workflow `.github/workflows/release.yml` compilerà tutte le architetture
|
||||||
|
e creerà la release su Gitea con gli artifact ZIP allegati. Il mirror GitHub
|
||||||
|
riceverà automaticamente il tag.
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
- C/C++: stile coerente con il file modificato
|
||||||
|
- Python: PEP 8 (4 spazi indent, line length flessibile)
|
||||||
|
- Header file: `#include` ordinati: stdlib → Win32 → progetto
|
||||||
|
- Encoding: UTF-8 senza BOM (eccetto `.rc` che richiede UTF-16 LE)
|
||||||
|
- Line endings: LF in repo (`.gitattributes` normalizza)
|
||||||
|
|
||||||
|
## Reporting Bug
|
||||||
|
|
||||||
|
Apri una [issue](https://gitea.emulab.it/Simone/universal-shell-ext/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/universal-shell-ext/issues) con label `question`.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Simone
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
# UniversalShellExt
|
||||||
|
|
||||||
|
Shell Extension COM generica per Windows che aggiunge una voce personalizzabile al menu contestuale di cartelle.
|
||||||
|
|
||||||
|
## Funzionalità
|
||||||
|
|
||||||
|
- ✅ **Menu Contestuale Personalizzabile**:
|
||||||
|
- Voce nel menu contestuale per cartelle (click destro su cartella)
|
||||||
|
- Voce nel menu contestuale per sfondo cartella (click destro su area vuota)
|
||||||
|
- Testo, icona e comando completamente configurabili via registro
|
||||||
|
- ✅ **Configurazione Flessibile**:
|
||||||
|
- Tutti i parametri letti dal registro di Windows
|
||||||
|
- Supporto per argomenti personalizzati con placeholder `%1`
|
||||||
|
- Icona estratta automaticamente dall'eseguibile
|
||||||
|
- ✅ **Sicurezza e Compatibilità**:
|
||||||
|
- Supporto UAC elevation automatico se necessario
|
||||||
|
- Compatibile con Windows 10/11 (x64)
|
||||||
|
- Accesso registro WOW64 corretto (64-bit)
|
||||||
|
- Thread-safe
|
||||||
|
|
||||||
|
## File del Progetto
|
||||||
|
|
||||||
|
| File | Descrizione |
|
||||||
|
|------|-------------|
|
||||||
|
| `UniversalShellExt.cpp` | Codice sorgente C++ della Shell Extension COM |
|
||||||
|
| `UniversalShellExt.def` | Definizioni esportazioni DLL |
|
||||||
|
| `UniversalShellExt.rc` | Risorse della DLL (Version Info) |
|
||||||
|
| `build.py` | Script Python per compilazione, firma e registrazione |
|
||||||
|
|
||||||
|
## Configurazione
|
||||||
|
|
||||||
|
La DLL legge la configurazione dal registro di Windows:
|
||||||
|
|
||||||
|
### Chiavi di Registro
|
||||||
|
|
||||||
|
**Percorso:** `HKEY_LOCAL_MACHINE\SOFTWARE\UniversalShellExt`
|
||||||
|
|
||||||
|
*(Fallback: `HKEY_CURRENT_USER\SOFTWARE\UniversalShellExt`)*
|
||||||
|
|
||||||
|
| Valore | Tipo | Descrizione | Esempio |
|
||||||
|
|--------|------|-------------|---------|
|
||||||
|
| `Command` | REG_SZ | Percorso completo dell'eseguibile | `N:\VSCode.exe` |
|
||||||
|
| `Arguments` | REG_SZ | Argomenti da passare (`%1` = path selezionato) | `"%1"` |
|
||||||
|
| `MenuText` | REG_SZ | Testo visualizzato nel menu | `Apri con VSCode` |
|
||||||
|
| `Icon` | REG_SZ | Percorso icona (exe o ico) | `N:\VSCode.exe` |
|
||||||
|
|
||||||
|
### Template Configurazione
|
||||||
|
|
||||||
|
```reg
|
||||||
|
Windows Registry Editor Version 5.00
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\SOFTWARE\UniversalShellExt]
|
||||||
|
"Command"="N:\\VSCode.exe"
|
||||||
|
"Arguments"="\"%1\""
|
||||||
|
"MenuText"="Apri con VSCode Portable"
|
||||||
|
"Icon"="N:\\VSCode.exe"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Valori Default
|
||||||
|
|
||||||
|
Se non configurati:
|
||||||
|
- `Arguments` → `"%1"` (passa il percorso selezionato)
|
||||||
|
- `MenuText` → `"Open with Application"`
|
||||||
|
- `Icon` → usa lo stesso percorso di `Command`
|
||||||
|
|
||||||
|
## Identificatori
|
||||||
|
|
||||||
|
| Tipo | Valore |
|
||||||
|
|------|--------|
|
||||||
|
| **CLSID** | `{5D849F86-F2A9-41FA-8A24-E01CD6D6696C}` |
|
||||||
|
| **Nome COM** | `Universal Portable Shell Extension` |
|
||||||
|
|
||||||
|
## Compilazione
|
||||||
|
|
||||||
|
### Prerequisiti
|
||||||
|
- **Visual Studio 2022** (con workload "Desktop development with C++")
|
||||||
|
- **Windows SDK** (per `signtool.exe` e `rc.exe`)
|
||||||
|
- **Python 3.x**
|
||||||
|
- **Certificato di firma** (opzionale, per firma digitale)
|
||||||
|
|
||||||
|
### Build Script
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Build completo (compila, copia, firma, registra)
|
||||||
|
python build.py
|
||||||
|
|
||||||
|
# Solo pulizia
|
||||||
|
python build.py --clean
|
||||||
|
|
||||||
|
# Solo deregistrazione
|
||||||
|
python build.py --unregister
|
||||||
|
|
||||||
|
# Build senza firma
|
||||||
|
python build.py --no-sign
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output
|
||||||
|
|
||||||
|
La DLL compilata viene copiata in:
|
||||||
|
```
|
||||||
|
plugins\UniversalShellExt64.dll
|
||||||
|
```
|
||||||
|
|
||||||
|
## Registrazione
|
||||||
|
|
||||||
|
### Metodo Consigliato (tramite Launcher)
|
||||||
|
|
||||||
|
Il launcher `VSCode.exe` gestisce automaticamente la registrazione:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
N:\VSCode.exe /register # Registra la shell extension
|
||||||
|
N:\VSCode.exe /unregister # Rimuove la shell extension
|
||||||
|
N:\VSCode.exe /register /silent # Modalità silenziosa
|
||||||
|
```
|
||||||
|
|
||||||
|
### Metodo Manuale (regsvr32)
|
||||||
|
|
||||||
|
Eseguire come **Amministratore**:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
:: Registrazione
|
||||||
|
regsvr32 "N:\Code\Workspace\Launchers\plugins\UniversalShellExt64.dll"
|
||||||
|
|
||||||
|
:: Rimozione
|
||||||
|
regsvr32 /u "N:\Code\Workspace\Launchers\plugins\UniversalShellExt64.dll"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Chiavi di Registro Create
|
||||||
|
|
||||||
|
Durante la registrazione (`DllRegisterServer`):
|
||||||
|
|
||||||
|
| Chiave | Scopo |
|
||||||
|
|--------|-------|
|
||||||
|
| `HKCR\CLSID\{5D849F86-...}` | Registrazione classe COM |
|
||||||
|
| `HKCR\CLSID\{5D849F86-...}\InprocServer32` | Percorso DLL |
|
||||||
|
| `HKCR\Directory\shellex\ContextMenuHandlers\UniversalShellExt` | Handler per cartelle |
|
||||||
|
| `HKCR\Directory\Background\shellex\ContextMenuHandlers\...` | Handler per sfondo |
|
||||||
|
| `HKLM\...\Shell Extensions\Approved` | Whitelist Microsoft |
|
||||||
|
|
||||||
|
## Come Funziona
|
||||||
|
|
||||||
|
1. **Inizializzazione**: Quando l'utente fa click destro, Explorer carica la DLL
|
||||||
|
2. **Configurazione**: La DLL legge `Command`, `Arguments`, `MenuText`, `Icon` dal registro
|
||||||
|
3. **Menu**: Inserisce la voce nel menu contestuale con l'icona configurata
|
||||||
|
4. **Esecuzione**: Al click, sostituisce `%1` con il percorso e lancia il comando
|
||||||
|
5. **UAC**: Se il comando richiede elevazione, la richiede automaticamente
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### La voce non appare nel menu
|
||||||
|
1. Verificare che la DLL sia registrata (`regsvr32`)
|
||||||
|
2. Controllare che le chiavi di registro siano configurate
|
||||||
|
3. Riavviare Explorer (`taskkill /F /IM explorer.exe && explorer.exe`)
|
||||||
|
|
||||||
|
### Errore di registrazione
|
||||||
|
- Eseguire come Amministratore
|
||||||
|
- Verificare che la DLL sia firmata (Windows potrebbe bloccare DLL non firmate)
|
||||||
|
|
||||||
|
### Il comando non viene eseguito
|
||||||
|
- Verificare il percorso in `Command` sia corretto
|
||||||
|
- Controllare che `Arguments` contenga `%1` per ricevere il percorso
|
||||||
|
|
||||||
|
## Versioni
|
||||||
|
|
||||||
|
| Versione | Note |
|
||||||
|
|----------|------|
|
||||||
|
| **1.0.0** | Prima release - Shell extension generica configurabile via registro |
|
||||||
|
|
||||||
|
## Licenza
|
||||||
|
|
||||||
|
Uso interno - emulab.it
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Versioni Supportate
|
||||||
|
|
||||||
|
Solo l'ultima versione minor riceve fix di security.
|
||||||
|
|
||||||
|
| Versione | Supporto |
|
||||||
|
|----------|----------|
|
||||||
|
| Latest minor (es. 1.0.0) | ✅ |
|
||||||
|
| Versioni precedenti | ❌ |
|
||||||
|
|
||||||
|
## Reporting di una Vulnerabilità
|
||||||
|
|
||||||
|
**Non aprire issue pubbliche per vulnerabilità di security.**
|
||||||
|
|
||||||
|
Per segnalare una vulnerabilità:
|
||||||
|
|
||||||
|
1. **Preferito**: usa la funzione [Security Advisory privato](https://gitea.emulab.it/Simone/universal-shell-ext/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.
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Build script per UniversalShellExt64.dll
|
||||||
|
Compila, copia, firma e registra la shell extension generica.
|
||||||
|
Richiede Visual Studio 2022 e Windows SDK.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import winreg
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Configurazione
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Percorsi
|
||||||
|
SCRIPT_DIR = Path(__file__).parent.resolve()
|
||||||
|
SOURCE_FILE = SCRIPT_DIR / "UniversalShellExt.cpp"
|
||||||
|
DEF_FILE = SCRIPT_DIR / "UniversalShellExt.def"
|
||||||
|
RC_FILE = SCRIPT_DIR / "UniversalShellExt.rc"
|
||||||
|
RES_FILE = SCRIPT_DIR / "UniversalShellExt.res"
|
||||||
|
|
||||||
|
OUTPUT_DIR = SCRIPT_DIR / "bin" / "x64"
|
||||||
|
OUTPUT_DLL = OUTPUT_DIR / "UniversalShellExt64.dll"
|
||||||
|
|
||||||
|
# Destinazione finale (cartella plugins)
|
||||||
|
DEST_DLL = SCRIPT_DIR.parent.parent / "plugins" / "UniversalShellExt64.dll"
|
||||||
|
|
||||||
|
# Visual Studio
|
||||||
|
VS_PATH = Path("C:/Program Files/Microsoft Visual Studio/2022/Community")
|
||||||
|
VCVARS_BAT = VS_PATH / "VC/Auxiliary/Build/vcvars64.bat"
|
||||||
|
|
||||||
|
# Signtool
|
||||||
|
SIGNTOOL = Path("C:/Program Files (x86)/Windows Kits/10/bin/10.0.22621.0/x64/signtool.exe")
|
||||||
|
CERT_THUMBPRINT = "DFF03ABA0E0156D8E7823A893F9588530A364410"
|
||||||
|
TIMESTAMP_URL = "http://timestamp.digicert.com"
|
||||||
|
|
||||||
|
# Librerie
|
||||||
|
LIBS = [
|
||||||
|
"ole32.lib",
|
||||||
|
"uuid.lib",
|
||||||
|
"shell32.lib",
|
||||||
|
"user32.lib",
|
||||||
|
"gdi32.lib",
|
||||||
|
"advapi32.lib",
|
||||||
|
"shlwapi.lib"
|
||||||
|
]
|
||||||
|
|
||||||
|
CLSID_STRING = "{5D849F86-F2A9-41FA-8A24-E01CD6D6696C}"
|
||||||
|
|
||||||
|
def run_command(cmd, shell=False, check=True):
|
||||||
|
print(f" > {cmd if isinstance(cmd, str) else ' '.join(cmd)}")
|
||||||
|
result = subprocess.run(cmd, shell=shell, capture_output=True, text=True)
|
||||||
|
if result.stdout: print(result.stdout)
|
||||||
|
if result.stderr: print(result.stderr)
|
||||||
|
if check and result.returncode != 0:
|
||||||
|
print(f"ERRORE: Comando fallito con codice {result.returncode}")
|
||||||
|
sys.exit(1)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def check_prerequisites():
|
||||||
|
print("\n=== Verifica prerequisiti ===")
|
||||||
|
errors = []
|
||||||
|
if not SOURCE_FILE.exists(): errors.append(f"Manca sorgente: {SOURCE_FILE}")
|
||||||
|
if not DEF_FILE.exists(): errors.append(f"Manca DEF: {DEF_FILE}")
|
||||||
|
if not VCVARS_BAT.exists(): errors.append(f"Manca VS: {VCVARS_BAT}")
|
||||||
|
if not SIGNTOOL.exists(): errors.append(f"Manca Signtool: {SIGNTOOL}")
|
||||||
|
if errors:
|
||||||
|
for e in errors: print(f" ERRORE: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
print(" Prerequisiti OK")
|
||||||
|
|
||||||
|
def update_version():
|
||||||
|
print("\n=== Incremento Versione ===")
|
||||||
|
import re
|
||||||
|
|
||||||
|
if not RC_FILE.exists():
|
||||||
|
print(" ERRORE: .rc non trovato!")
|
||||||
|
return
|
||||||
|
|
||||||
|
content = RC_FILE.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
# Cerca VER_FILEVERSION 1,0,0,X
|
||||||
|
# Regex per catturare i primi 3 numeri e l'ultimo
|
||||||
|
pattern = r'(#define VER_FILEVERSION\s+\d+,\d+,\d+,)(\d+)'
|
||||||
|
match = re.search(pattern, content)
|
||||||
|
|
||||||
|
if match:
|
||||||
|
prefix = match.group(1)
|
||||||
|
build_num = int(match.group(2))
|
||||||
|
new_build_num = build_num + 1
|
||||||
|
|
||||||
|
# Sostituisci VER_FILEVERSION
|
||||||
|
new_content = re.sub(pattern, f'{prefix}{new_build_num}', content)
|
||||||
|
|
||||||
|
# Aggiorna anche VER_FILEVERSION_STR "1.0.0.X\0"
|
||||||
|
str_pattern = r'(#define VER_FILEVERSION_STR\s+"[^"]*?)(\d+)(\\0")'
|
||||||
|
# Nota: questa regex assume che la stringa finisca col numero da incrementare.
|
||||||
|
# Se è "1.0.0.0\0", cattura "1.0.0." come gruppo 1, "0" come 2.
|
||||||
|
|
||||||
|
# Miglior approccio: ricostruire la stringa in base al nuovo numero
|
||||||
|
# Ma dobbiamo parsare i primi 3 numeri per essere sicuri
|
||||||
|
v_parts = prefix.strip().replace("#define VER_FILEVERSION", "").strip().split(',')
|
||||||
|
if len(v_parts) >= 3:
|
||||||
|
major, minor, patch = v_parts[0], v_parts[1], v_parts[2]
|
||||||
|
new_ver_str = f'{major}.{minor}.{patch}.{new_build_num}'
|
||||||
|
|
||||||
|
# Sostituisci la stringa intera con gestione corretta di virgolette e backslash
|
||||||
|
# La regex deve catturare tutto fino ai doppi apici finali
|
||||||
|
new_content = re.sub(r'(#define VER_FILEVERSION_STR\s+)"(.*?)"', f'#define VER_FILEVERSION_STR "{new_ver_str}\\\\0"', new_content)
|
||||||
|
|
||||||
|
# Aggiorna anche VER_PRODUCTVERSION (spesso uguale)
|
||||||
|
new_content = re.sub(r'(#define VER_PRODUCTVERSION\s+).*', f'#define VER_PRODUCTVERSION {major},{minor},{patch},{new_build_num}', new_content)
|
||||||
|
|
||||||
|
# Aggiorna VER_PRODUCTVERSION_STR (spesso solo 3 cifre, ma qui mettiamo 4 per coerenza o lasciamo stare?)
|
||||||
|
# Nel file attuale è "1.0.0\0". Se vogliamo incrementare anche quello:
|
||||||
|
# new_content = re.sub(r'#define VER_PRODUCTVERSION_STR\s+".*?"', f'#define VER_PRODUCTVERSION_STR "{major}.{minor}.{patch}\\0"', new_content)
|
||||||
|
|
||||||
|
RC_FILE.write_text(new_content, encoding="utf-8")
|
||||||
|
print(f" Versione aggiornata a: {new_ver_str}")
|
||||||
|
else:
|
||||||
|
print(" Non riesco a parsare la versione corrente.")
|
||||||
|
else:
|
||||||
|
print(" Pattern versione non trovato.")
|
||||||
|
|
||||||
|
def clean_build():
|
||||||
|
print("\n=== Clean build ===")
|
||||||
|
to_remove = [
|
||||||
|
SCRIPT_DIR / "bin",
|
||||||
|
SCRIPT_DIR / "Release",
|
||||||
|
SCRIPT_DIR / "Debug",
|
||||||
|
SCRIPT_DIR / "x64",
|
||||||
|
SCRIPT_DIR / "UniversalShellExt64.dll",
|
||||||
|
SCRIPT_DIR / "UniversalShellExt64.exp",
|
||||||
|
SCRIPT_DIR / "UniversalShellExt64.lib",
|
||||||
|
SCRIPT_DIR / "UniversalShellExt64.pdb",
|
||||||
|
SCRIPT_DIR / "UniversalShellExt.obj",
|
||||||
|
RES_FILE,
|
||||||
|
]
|
||||||
|
for path in to_remove:
|
||||||
|
if path.exists():
|
||||||
|
if path.is_dir():
|
||||||
|
try: shutil.rmtree(path)
|
||||||
|
except: pass
|
||||||
|
else:
|
||||||
|
try: path.unlink()
|
||||||
|
except: pass
|
||||||
|
print(" Clean completato")
|
||||||
|
|
||||||
|
def kill_explorer():
|
||||||
|
print("\n=== Termina Explorer ===")
|
||||||
|
subprocess.run(["taskkill", "/F", "/IM", "explorer.exe"], capture_output=True, check=False)
|
||||||
|
import time
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
def start_explorer():
|
||||||
|
print("\n=== Riavvia Explorer ===")
|
||||||
|
subprocess.Popen(["explorer.exe"])
|
||||||
|
import time
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
def unregister_dll():
|
||||||
|
print("\n=== Deregistra DLL ===")
|
||||||
|
if DEST_DLL.exists():
|
||||||
|
run_command(["regsvr32", "/u", "/s", str(DEST_DLL)], check=False)
|
||||||
|
print(" DLL deregistrata")
|
||||||
|
else:
|
||||||
|
print(" DLL non trovata, skip")
|
||||||
|
|
||||||
|
def compile_dll():
|
||||||
|
print("\n=== Compilazione DLL ===")
|
||||||
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Compile Resource
|
||||||
|
rc_cmd = f'''call "{VCVARS_BAT}" && cd /D "{SCRIPT_DIR}" && rc.exe /fo"{RES_FILE.name}" "{RC_FILE.name}"'''
|
||||||
|
subprocess.run(f'cmd /c "{rc_cmd}"', shell=True, check=True)
|
||||||
|
|
||||||
|
# Compile DLL
|
||||||
|
libs_str = " ".join(LIBS)
|
||||||
|
compile_cmd = f'''call "{VCVARS_BAT}" && cd /D "{SCRIPT_DIR}" && cl.exe /LD /EHsc /O2 /DUNICODE /D_UNICODE "{SOURCE_FILE.name}" "{RES_FILE.name}" /Fe:"{OUTPUT_DLL}" /link /DEF:"{DEF_FILE.name}" {libs_str}'''
|
||||||
|
|
||||||
|
result = subprocess.run(f'cmd /c "{compile_cmd}"', shell=True, capture_output=True, text=True)
|
||||||
|
if result.stdout:
|
||||||
|
for line in result.stdout.split('\n'):
|
||||||
|
if line.strip() and not line.startswith('**') and not line.startswith('['):
|
||||||
|
print(f" {line}")
|
||||||
|
if result.returncode != 0 or not OUTPUT_DLL.exists():
|
||||||
|
print("ERRORE: Compilazione fallita")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f" Compilata: {OUTPUT_DLL}")
|
||||||
|
|
||||||
|
def copy_dll():
|
||||||
|
print("\n=== Copia DLL ===")
|
||||||
|
shutil.copy2(OUTPUT_DLL, DEST_DLL)
|
||||||
|
print(f" Copiata in: {DEST_DLL}")
|
||||||
|
|
||||||
|
def sign_dll():
|
||||||
|
print("\n=== Firma DLL ===")
|
||||||
|
cmd = [str(SIGNTOOL), "sign", "/sha1", CERT_THUMBPRINT, "/fd", "SHA256", "/t", TIMESTAMP_URL, str(DEST_DLL)]
|
||||||
|
run_command(cmd)
|
||||||
|
print(" Firma OK")
|
||||||
|
|
||||||
|
def register_dll():
|
||||||
|
print("\n=== Registra DLL ===")
|
||||||
|
result = run_command(["regsvr32", "/s", str(DEST_DLL)], check=False)
|
||||||
|
if result.returncode == 0:
|
||||||
|
print(" Registrazione OK")
|
||||||
|
else:
|
||||||
|
print(f" FALLITA: {result.returncode} (Serve Admin?)")
|
||||||
|
|
||||||
|
def verify_registration():
|
||||||
|
print("\n=== Verifica Registrazione ===")
|
||||||
|
try:
|
||||||
|
key_path = fr"SOFTWARE\Classes\CLSID\{CLSID_STRING}\InprocServer32"
|
||||||
|
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path) as key:
|
||||||
|
val, _ = winreg.QueryValueEx(key, "")
|
||||||
|
print(f" CLSID registrato: {val}")
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(" ERRORE: CLSID non trovato")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
key_path = r"SOFTWARE\Classes\Directory\shellex\ContextMenuHandlers\UniversalShellExt"
|
||||||
|
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path) as key:
|
||||||
|
val, _ = winreg.QueryValueEx(key, "")
|
||||||
|
print(f" Handler directory registrato: {val}")
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(" ERRORE: Handler non trovato")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("UniversalShellExt Builder")
|
||||||
|
args = sys.argv[1:]
|
||||||
|
|
||||||
|
if "--clean" in args:
|
||||||
|
clean_build()
|
||||||
|
return
|
||||||
|
|
||||||
|
if "--unregister" in args:
|
||||||
|
unregister_dll()
|
||||||
|
return
|
||||||
|
|
||||||
|
# STEP 0: Clean pre-build
|
||||||
|
clean_build()
|
||||||
|
|
||||||
|
update_version()
|
||||||
|
check_prerequisites()
|
||||||
|
unregister_dll()
|
||||||
|
kill_explorer()
|
||||||
|
compile_dll()
|
||||||
|
copy_dll()
|
||||||
|
|
||||||
|
if "--no-sign" not in args:
|
||||||
|
sign_dll()
|
||||||
|
|
||||||
|
start_explorer()
|
||||||
|
register_dll()
|
||||||
|
verify_registration()
|
||||||
|
clean_build()
|
||||||
|
|
||||||
|
print("\nDONE!")
|
||||||
|
print(f"DLL: {DEST_DLL}")
|
||||||
|
print("Ricorda di configurare HKLM\\Software\\UniversalShellExt")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,700 @@
|
|||||||
|
// UniversalShellExt.cpp - Generic Shell Extension
|
||||||
|
// Configurable via Registry
|
||||||
|
// Version: 1.1.0
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <shlobj.h>
|
||||||
|
#include <shlwapi.h>
|
||||||
|
#include <strsafe.h>
|
||||||
|
#include <new>
|
||||||
|
|
||||||
|
#pragma comment(lib, "shlwapi.lib")
|
||||||
|
|
||||||
|
// DLL Version
|
||||||
|
#define DLL_VERSION L"1.1.0"
|
||||||
|
|
||||||
|
// GUID for UniversalShellExt
|
||||||
|
// {5D849F86-F2A9-41FA-8A24-E01CD6D6696C}
|
||||||
|
static const CLSID CLSID_UniversalShellExt =
|
||||||
|
{ 0x5d849f86, 0xf2a9, 0x41fa, { 0x8a, 0x24, 0xe0, 0x1c, 0xd6, 0xd6, 0x69, 0x6c } };
|
||||||
|
|
||||||
|
// CLSID as string
|
||||||
|
#define CLSID_STRING L"{5D849F86-F2A9-41FA-8A24-E01CD6D6696C}"
|
||||||
|
|
||||||
|
// Registry Configuration - now per-CLSID
|
||||||
|
#define REG_KEY_BASE L"Software\\UniversalShellExt\\"
|
||||||
|
#define REG_VALUE_COMMAND L"Command"
|
||||||
|
#define REG_VALUE_ARGS L"Arguments"
|
||||||
|
#define REG_VALUE_ICON L"Icon"
|
||||||
|
#define REG_VALUE_TEXT L"MenuText"
|
||||||
|
#define REG_VALUE_ENABLE_LOG L"EnableLog"
|
||||||
|
|
||||||
|
// Global Variables
|
||||||
|
HINSTANCE g_hInst = NULL;
|
||||||
|
LONG g_cDllRef = 0;
|
||||||
|
|
||||||
|
// Internal Helper for File Logging
|
||||||
|
void WriteLogFile(const wchar_t* buffer) {
|
||||||
|
FILE* f;
|
||||||
|
if (_wfopen_s(&f, L"C:\\Users\\Simone\\.gemini\\shellext_debug.log", L"a") == 0) {
|
||||||
|
SYSTEMTIME st;
|
||||||
|
GetLocalTime(&st);
|
||||||
|
fwprintf(f, L"[%02d:%02d:%02d.%03d] %s\n", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, buffer);
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global Logger (Legacy/Global scope)
|
||||||
|
void WriteLog(const wchar_t* format, ...) {
|
||||||
|
// Global logging is now MINIMAL or DISABLED by default unless needed.
|
||||||
|
// Uncomment for deep debugging of DLL loading.
|
||||||
|
// wchar_t buffer[1024];
|
||||||
|
// va_list args;
|
||||||
|
// va_start(args, format);
|
||||||
|
// _vswprintf_p(buffer, 1024, format, args);
|
||||||
|
// va_end(args);
|
||||||
|
// WriteLogFile(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration Cache globals removed.
|
||||||
|
// They are now member variables of the class.
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// Helper: Icon to Bitmap
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// Helper: Icon to Bitmap
|
||||||
|
//==============================================================================
|
||||||
|
HBITMAP IconToBitmap(HICON hIcon)
|
||||||
|
{
|
||||||
|
if (!hIcon) return NULL;
|
||||||
|
ICONINFO iconInfo;
|
||||||
|
if (!GetIconInfo(hIcon, &iconInfo)) return NULL;
|
||||||
|
|
||||||
|
BITMAP bm;
|
||||||
|
GetObject(iconInfo.hbmColor ? iconInfo.hbmColor : iconInfo.hbmMask, sizeof(bm), &bm);
|
||||||
|
|
||||||
|
int width = bm.bmWidth;
|
||||||
|
int height = bm.bmHeight;
|
||||||
|
|
||||||
|
if (iconInfo.hbmColor) DeleteObject(iconInfo.hbmColor);
|
||||||
|
if (iconInfo.hbmMask) DeleteObject(iconInfo.hbmMask);
|
||||||
|
|
||||||
|
HDC hdcScreen = GetDC(NULL);
|
||||||
|
HDC hdcMem = CreateCompatibleDC(hdcScreen);
|
||||||
|
|
||||||
|
BITMAPINFO bmi = {0};
|
||||||
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||||
|
bmi.bmiHeader.biWidth = width;
|
||||||
|
bmi.bmiHeader.biHeight = -height;
|
||||||
|
bmi.bmiHeader.biPlanes = 1;
|
||||||
|
bmi.bmiHeader.biBitCount = 32;
|
||||||
|
bmi.bmiHeader.biCompression = BI_RGB;
|
||||||
|
|
||||||
|
void* pBits;
|
||||||
|
HBITMAP hBitmap = CreateDIBSection(hdcScreen, &bmi, DIB_RGB_COLORS, &pBits, NULL, 0);
|
||||||
|
|
||||||
|
if (hBitmap)
|
||||||
|
{
|
||||||
|
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hdcMem, hBitmap);
|
||||||
|
RECT rc = {0, 0, width, height};
|
||||||
|
FillRect(hdcMem, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH)); // Transparent assumes 32bit alpha
|
||||||
|
DrawIconEx(hdcMem, 0, 0, hIcon, width, height, 0, NULL, DI_NORMAL);
|
||||||
|
SelectObject(hdcMem, hOldBitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteDC(hdcMem);
|
||||||
|
ReleaseDC(NULL, hdcScreen);
|
||||||
|
return hBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// Class UniversalShellExt
|
||||||
|
//==============================================================================
|
||||||
|
class UniversalShellExt : public IShellExtInit, public IContextMenu
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// IUnknown
|
||||||
|
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
|
||||||
|
{
|
||||||
|
static const QITAB qit[] =
|
||||||
|
{
|
||||||
|
QITABENT(UniversalShellExt, IContextMenu),
|
||||||
|
QITABENT(UniversalShellExt, IShellExtInit),
|
||||||
|
{ 0 },
|
||||||
|
};
|
||||||
|
return QISearch(this, qit, riid, ppv);
|
||||||
|
}
|
||||||
|
|
||||||
|
IFACEMETHODIMP_(ULONG) AddRef()
|
||||||
|
{
|
||||||
|
return InterlockedIncrement(&m_cRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
IFACEMETHODIMP_(ULONG) Release()
|
||||||
|
{
|
||||||
|
ULONG cRef = InterlockedDecrement(&m_cRef);
|
||||||
|
if (cRef == 0) delete this;
|
||||||
|
return cRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IShellExtInit
|
||||||
|
IFACEMETHODIMP Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID)
|
||||||
|
{
|
||||||
|
LPOLESTR szCLSID = NULL;
|
||||||
|
StringFromCLSID(m_clsid, &szCLSID);
|
||||||
|
Log(L"UniversalShellExt::Initialize called. CLSID: %s", szCLSID ? szCLSID : L"Unknown");
|
||||||
|
if (szCLSID) CoTaskMemFree(szCLSID);
|
||||||
|
|
||||||
|
if (pDataObj == NULL) {
|
||||||
|
Log(L"Initialize - Error: pDataObj is NULL");
|
||||||
|
return E_INVALIDARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
||||||
|
STGMEDIUM stm;
|
||||||
|
|
||||||
|
if (SUCCEEDED(pDataObj->GetData(&fe, &stm)))
|
||||||
|
{
|
||||||
|
UINT nFiles = DragQueryFile((HDROP)stm.hGlobal, 0xFFFFFFFF, NULL, 0);
|
||||||
|
Log(L"Initialize - Files selected: %d", nFiles);
|
||||||
|
if (nFiles > 0)
|
||||||
|
{
|
||||||
|
DragQueryFile((HDROP)stm.hGlobal, 0, m_szSelectedPath, MAX_PATH);
|
||||||
|
Log(L"Initialize - Selected Path: %s", m_szSelectedPath);
|
||||||
|
}
|
||||||
|
ReleaseStgMedium(&stm);
|
||||||
|
}
|
||||||
|
else if (pidlFolder != NULL)
|
||||||
|
{
|
||||||
|
SHGetPathFromIDList(pidlFolder, m_szSelectedPath);
|
||||||
|
Log(L"Initialize - From PIDL: %s", m_szSelectedPath);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log(L"Initialize - Failed to get path data.");
|
||||||
|
}
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IContextMenu
|
||||||
|
IFACEMETHODIMP QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
|
||||||
|
{
|
||||||
|
LPOLESTR szCLSID = NULL;
|
||||||
|
StringFromCLSID(m_clsid, &szCLSID);
|
||||||
|
Log(L"UniversalShellExt::QueryContextMenu called for CLSID: %s", szCLSID ? szCLSID : L"Unknown");
|
||||||
|
Log(L"QueryContextMenu - indexMenu: %d, idCmdFirst: %d, idCmdLast: %d, uFlags: 0x%X", indexMenu, idCmdFirst, idCmdLast, uFlags);
|
||||||
|
if (szCLSID) CoTaskMemFree(szCLSID);
|
||||||
|
|
||||||
|
if (uFlags & CMF_DEFAULTONLY) return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 0);
|
||||||
|
|
||||||
|
LoadConfiguration(); // Load config into member variables
|
||||||
|
|
||||||
|
// Fixed position at top
|
||||||
|
UINT targetPosition = 0;
|
||||||
|
|
||||||
|
Log(L"QueryContextMenu - indexMenu: %d, idCmdFirst: %d, idCmdLast: %d", indexMenu, idCmdFirst, idCmdLast);
|
||||||
|
Log(L"QueryContextMenu - Using fixed position: %d", targetPosition);
|
||||||
|
Log(L"QueryContextMenu called. MenuText: %s", m_szMenuText);
|
||||||
|
|
||||||
|
// Dont show if no command configured
|
||||||
|
if (m_szCommand[0] == L'\0') {
|
||||||
|
Log(L"QueryContextMenu - Error: Command is empty!");
|
||||||
|
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
HICON hIcon = NULL;
|
||||||
|
HBITMAP hBitmap = NULL;
|
||||||
|
|
||||||
|
// Extract icon
|
||||||
|
if (m_szIcon[0] != L'\0')
|
||||||
|
{
|
||||||
|
Log(L"QueryContextMenu - Icon configured: %s", m_szIcon);
|
||||||
|
// Try to find index if comma exists e.g. "dll,1"
|
||||||
|
// Simple extraction for now
|
||||||
|
ExtractIconEx(m_szIcon, 0, NULL, &hIcon, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hIcon)
|
||||||
|
{
|
||||||
|
hBitmap = IconToBitmap(hIcon);
|
||||||
|
DestroyIcon(hIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_hMenuBitmap) { DeleteObject(m_hMenuBitmap); m_hMenuBitmap = NULL; }
|
||||||
|
|
||||||
|
MENUITEMINFO mii = { sizeof(mii) };
|
||||||
|
mii.fMask = MIIM_STRING | MIIM_ID | MIIM_STATE | MIIM_BITMAP;
|
||||||
|
mii.wID = idCmdFirst;
|
||||||
|
mii.dwTypeData = m_szMenuText;
|
||||||
|
mii.fState = MFS_ENABLED;
|
||||||
|
mii.hbmpItem = hBitmap;
|
||||||
|
|
||||||
|
if (!InsertMenuItem(hMenu, targetPosition, TRUE, &mii))
|
||||||
|
{
|
||||||
|
if (hBitmap) DeleteObject(hBitmap);
|
||||||
|
Log(L"QueryContextMenu - InsertMenuItem FAILED. Error: %d", GetLastError());
|
||||||
|
return HRESULT_FROM_WIN32(GetLastError());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log(L"QueryContextMenu - InsertMenuItem Success. ID: %d", idCmdFirst);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_hMenuBitmap = hBitmap;
|
||||||
|
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
IFACEMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pici)
|
||||||
|
{
|
||||||
|
LPOLESTR szCLSID = NULL;
|
||||||
|
StringFromCLSID(m_clsid, &szCLSID);
|
||||||
|
Log(L"UniversalShellExt::InvokeCommand called for CLSID: %s", szCLSID ? szCLSID : L"Unknown");
|
||||||
|
|
||||||
|
// Debug MessageBox removed for production
|
||||||
|
|
||||||
|
|
||||||
|
// Debug Box (Temporary - UNCOMMENTED FOR DEBUG)
|
||||||
|
// MessageBox(NULL, L"InvokeCommand Called!", L"Debug", MB_OK);
|
||||||
|
|
||||||
|
bool isMatch = false;
|
||||||
|
|
||||||
|
// 1. Check for Integer Offset (Standard Right-Click)
|
||||||
|
if (HIWORD(pici->lpVerb) == 0)
|
||||||
|
{
|
||||||
|
// We only add 1 item, so offset 0 is the only valid one.
|
||||||
|
if (LOWORD(pici->lpVerb) == 0) isMatch = true;
|
||||||
|
}
|
||||||
|
// 2. Check for String Verb (Programmatic/Windows 11)
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LPCSTR pszVerb = pici->lpVerb;
|
||||||
|
// Compare with our CLSID string (which we return in GetCommandString)
|
||||||
|
// Need to convert our WCHAR CLSID to MultiByte for comparison
|
||||||
|
if (szCLSID) {
|
||||||
|
// Construct EXPECTED verb: Verb_XXXX (without braces)
|
||||||
|
WCHAR szVerbW[128];
|
||||||
|
StringCchPrintf(szVerbW, 128, L"Verb_%s", szCLSID + 1); // Skip '{'
|
||||||
|
size_t lenW = 0;
|
||||||
|
StringCchLength(szVerbW, 128, &lenW);
|
||||||
|
if (lenW > 0 && szVerbW[lenW-1] == L'}') szVerbW[lenW-1] = L'\0';
|
||||||
|
|
||||||
|
// Convert to ANSI for comparison
|
||||||
|
int lenA = WideCharToMultiByte(CP_ACP, 0, szVerbW, -1, NULL, 0, NULL, NULL);
|
||||||
|
char* pszVerbA = new (std::nothrow) char[lenA];
|
||||||
|
if (pszVerbA) {
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, szVerbW, -1, pszVerbA, lenA, NULL, NULL);
|
||||||
|
|
||||||
|
if (lstrcmpiA(pszVerb, pszVerbA) == 0) isMatch = true;
|
||||||
|
else if (lstrcmpiA(pszVerb, "UniversalShellExt") == 0) isMatch = true; // Fallback
|
||||||
|
|
||||||
|
delete[] pszVerbA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (szCLSID) CoTaskMemFree(szCLSID);
|
||||||
|
|
||||||
|
if (!isMatch) {
|
||||||
|
Log(L"InvokeCommand - Verb mismatch. Rejecting.");
|
||||||
|
return E_INVALIDARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If command is empty, try reloading config (fallback)
|
||||||
|
if (m_szCommand[0] == L'\0') {
|
||||||
|
Log(L"InvokeCommand - Command empty, reloading...");
|
||||||
|
LoadConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if command file exists
|
||||||
|
if (m_szCommand[0] == L'\0' || GetFileAttributes(m_szCommand) == INVALID_FILE_ATTRIBUTES)
|
||||||
|
{
|
||||||
|
WCHAR szMsg[MAX_PATH + 128];
|
||||||
|
if (m_szCommand[0] == L'\0')
|
||||||
|
{
|
||||||
|
StringCchCopy(szMsg, ARRAYSIZE(szMsg), L"Nessun comando configurato.\n\nConfigurare la chiave di registro:\nHKLM\\SOFTWARE\\UniversalShellExt\\Command");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StringCchPrintf(szMsg, ARRAYSIZE(szMsg), L"File non trovato:\n\n%s\n\nVerificare la configurazione nel registro.", m_szCommand);
|
||||||
|
}
|
||||||
|
Log(L"InvokeCommand - Error: %s", szMsg);
|
||||||
|
MessageBox(NULL, szMsg, L"UniversalShellExt - Errore", MB_OK | MB_ICONERROR);
|
||||||
|
return E_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare Parameters
|
||||||
|
// Replace %1 in m_szArguments with m_szSelectedPath (quoted)
|
||||||
|
WCHAR szArgsExpanded[MAX_PATH * 2];
|
||||||
|
WCHAR szPathQuoted[MAX_PATH + 2];
|
||||||
|
StringCchPrintf(szPathQuoted, MAX_PATH+2, L"\"%s\"", m_szSelectedPath);
|
||||||
|
|
||||||
|
// Simple Replace of %1
|
||||||
|
// Note: This is a basic implementation.
|
||||||
|
// It searches for %1 and replaces it.
|
||||||
|
|
||||||
|
// Copy arguments to buffer
|
||||||
|
WCHAR *pFound = wcsstr(m_szArguments, L"%1");
|
||||||
|
if (pFound)
|
||||||
|
{
|
||||||
|
size_t prefixLen = pFound - m_szArguments;
|
||||||
|
wcsncpy_s(szArgsExpanded, ARRAYSIZE(szArgsExpanded), m_szArguments, prefixLen);
|
||||||
|
szArgsExpanded[prefixLen] = L'\0';
|
||||||
|
StringCchCat(szArgsExpanded, ARRAYSIZE(szArgsExpanded), szPathQuoted); // Insert Path (quoted)
|
||||||
|
StringCchCat(szArgsExpanded, ARRAYSIZE(szArgsExpanded), pFound + 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No %1 found, just use arguments as is.
|
||||||
|
// The provided snippet had a different logic here, appending the path if no %1.
|
||||||
|
// Sticking to the original logic for now, which means if no %1, path is not appended.
|
||||||
|
StringCchCopy(szArgsExpanded, ARRAYSIZE(szArgsExpanded), m_szArguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
SHELLEXECUTEINFO sei = { sizeof(sei) };
|
||||||
|
sei.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||||
|
sei.lpVerb = L"open";
|
||||||
|
sei.lpFile = m_szCommand;
|
||||||
|
sei.lpParameters = szArgsExpanded;
|
||||||
|
sei.nShow = SW_SHOWNORMAL;
|
||||||
|
|
||||||
|
// Set Working Directory to the folder containing the executable
|
||||||
|
WCHAR szDir[MAX_PATH];
|
||||||
|
StringCchCopy(szDir, MAX_PATH, m_szCommand);
|
||||||
|
PathRemoveFileSpec(szDir); // Requires shlwapi.h and linking shlwapi.lib
|
||||||
|
sei.lpDirectory = szDir;
|
||||||
|
|
||||||
|
Log(L"InvokeCommand - Executing: %s Params: %s Dir: %s", sei.lpFile, sei.lpParameters, sei.lpDirectory);
|
||||||
|
|
||||||
|
if (!ShellExecuteEx(&sei))
|
||||||
|
{
|
||||||
|
DWORD lastError = GetLastError();
|
||||||
|
Log(L"InvokeCommand - ShellExecuteEx failed with error: %d", lastError);
|
||||||
|
if (lastError == ERROR_ELEVATION_REQUIRED)
|
||||||
|
{
|
||||||
|
sei.lpVerb = L"runas";
|
||||||
|
Log(L"InvokeCommand - Retrying with 'runas' verb.");
|
||||||
|
ShellExecuteEx(&sei);
|
||||||
|
} else {
|
||||||
|
MessageBox(NULL, L"Failed to launch command.", L"Error", MB_ICONERROR);
|
||||||
|
return E_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sei.hProcess) CloseHandle(sei.hProcess);
|
||||||
|
Log(L"InvokeCommand - Success");
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
IFACEMETHODIMP GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pReserved, CHAR *pszName, UINT cchMax)
|
||||||
|
{
|
||||||
|
Log(L"GetCommandString called. uType: %d", uType);
|
||||||
|
if (idCmd != 0) return E_INVALIDARG;
|
||||||
|
|
||||||
|
LoadConfiguration(); // Reload config
|
||||||
|
|
||||||
|
switch (uType)
|
||||||
|
{
|
||||||
|
case GCS_HELPTEXTW:
|
||||||
|
StringCchCopyW((LPWSTR)pszName, cchMax, m_szMenuText);
|
||||||
|
return S_OK;
|
||||||
|
|
||||||
|
case GCS_HELPTEXTA:
|
||||||
|
{
|
||||||
|
int len = WideCharToMultiByte(CP_ACP, 0, m_szMenuText, -1, NULL, 0, NULL, NULL);
|
||||||
|
if (len <= cchMax) WideCharToMultiByte(CP_ACP, 0, m_szMenuText, -1, pszName, cchMax, NULL, NULL);
|
||||||
|
}
|
||||||
|
return S_OK;
|
||||||
|
|
||||||
|
case GCS_VERBW:
|
||||||
|
case GCS_VALIDATEW:
|
||||||
|
{
|
||||||
|
LPOLESTR szCLSID = NULL;
|
||||||
|
StringFromCLSID(m_clsid, &szCLSID);
|
||||||
|
if (szCLSID)
|
||||||
|
{
|
||||||
|
// Sanitize Verb: replace {CLSID} with Verb_CLSID (no braces)
|
||||||
|
WCHAR szVerb[128];
|
||||||
|
StringCchPrintf(szVerb, 128, L"Verb_%s", szCLSID + 1); // Skip '{'
|
||||||
|
size_t len = 0;
|
||||||
|
StringCchLength(szVerb, 128, &len);
|
||||||
|
if (len > 0 && szVerb[len-1] == L'}') szVerb[len-1] = L'\0';
|
||||||
|
|
||||||
|
Log(L"GetCommandString - Returning VERB: %s", szVerb);
|
||||||
|
|
||||||
|
if (uType == GCS_VERBW && pszName) {
|
||||||
|
StringCchCopyW((LPWSTR)pszName, cchMax, szVerb);
|
||||||
|
}
|
||||||
|
CoTaskMemFree(szCLSID);
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
return E_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
case GCS_VERBA:
|
||||||
|
case GCS_VALIDATEA:
|
||||||
|
// Return "UniversalShellExt" for legacy ANSI, but maybe we should return Verb_GUID here too if possible?
|
||||||
|
// Sticking to simplified "Valid" response for now to minimize charset risk.
|
||||||
|
// If Verb is requested (ANSI), we return the generic string.
|
||||||
|
if (uType == GCS_VERBA && pszName) {
|
||||||
|
StringCchCopyA(pszName, cchMax, "UniversalShellExt");
|
||||||
|
}
|
||||||
|
return S_OK;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Log(L"GetCommandString - Unknown uType: %d", uType);
|
||||||
|
return E_INVALIDARG;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UniversalShellExt(REFCLSID clsid) : m_cRef(1), m_hMenuBitmap(NULL), m_clsid(clsid)
|
||||||
|
{
|
||||||
|
InterlockedIncrement(&g_cDllRef);
|
||||||
|
|
||||||
|
// Stringify CLSID for log
|
||||||
|
LPOLESTR szCLSID = NULL;
|
||||||
|
StringFromCLSID(m_clsid, &szCLSID);
|
||||||
|
Log(L"UniversalShellExt::Constructor - CLSID: %s", szCLSID ? szCLSID : L"Unknown");
|
||||||
|
if (szCLSID) CoTaskMemFree(szCLSID);
|
||||||
|
|
||||||
|
m_szSelectedPath[0] = L'\0';
|
||||||
|
m_szCommand[0] = L'\0';
|
||||||
|
m_szArguments[0] = L'\0';
|
||||||
|
m_szIcon[0] = L'\0';
|
||||||
|
m_szMenuText[0] = L'\0';
|
||||||
|
m_bEnableLog = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
~UniversalShellExt()
|
||||||
|
{
|
||||||
|
LPOLESTR szCLSID = NULL;
|
||||||
|
StringFromCLSID(m_clsid, &szCLSID);
|
||||||
|
Log(L"UniversalShellExt::Destructor - CLSID: %s", szCLSID ? szCLSID : L"Unknown");
|
||||||
|
if (szCLSID) CoTaskMemFree(szCLSID);
|
||||||
|
|
||||||
|
if (m_hMenuBitmap) DeleteObject(m_hMenuBitmap);
|
||||||
|
InterlockedDecrement(&g_cDllRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
LONG m_cRef;
|
||||||
|
WCHAR m_szSelectedPath[MAX_PATH];
|
||||||
|
HBITMAP m_hMenuBitmap;
|
||||||
|
CLSID m_clsid;
|
||||||
|
// Member variables to store configuration for this instance
|
||||||
|
WCHAR m_szCommand[MAX_PATH];
|
||||||
|
WCHAR m_szArguments[MAX_PATH];
|
||||||
|
WCHAR m_szIcon[MAX_PATH];
|
||||||
|
WCHAR m_szMenuText[128];
|
||||||
|
BOOL m_bEnableLog;
|
||||||
|
|
||||||
|
// Member helper for logging
|
||||||
|
void Log(const wchar_t* format, ...) {
|
||||||
|
if (!m_bEnableLog) return;
|
||||||
|
|
||||||
|
wchar_t buffer[1024];
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
_vswprintf_p(buffer, 1024, format, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
WriteLogFile(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal LoadConfiguration method
|
||||||
|
void LoadConfiguration()
|
||||||
|
{
|
||||||
|
m_szCommand[0] = L'\0';
|
||||||
|
m_szArguments[0] = L'\0';
|
||||||
|
m_szIcon[0] = L'\0';
|
||||||
|
m_szMenuText[0] = L'\0';
|
||||||
|
|
||||||
|
// Build registry path: Software\UniversalShellExt\{CLSID}
|
||||||
|
WCHAR szKeyPath[256];
|
||||||
|
WCHAR szClsid[64];
|
||||||
|
StringFromGUID2(m_clsid, szClsid, ARRAYSIZE(szClsid));
|
||||||
|
StringCchPrintf(szKeyPath, ARRAYSIZE(szKeyPath), L"%s%s", REG_KEY_BASE, szClsid);
|
||||||
|
|
||||||
|
HKEY hKey;
|
||||||
|
DWORD dwSize;
|
||||||
|
DWORD dwType = REG_SZ;
|
||||||
|
|
||||||
|
// Try to open key from HKLM (64-bit view)
|
||||||
|
LSTATUS status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szKeyPath, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
|
||||||
|
if (status != ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
status = RegOpenKeyEx(HKEY_CURRENT_USER, szKeyPath, 0, KEY_READ, &hKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
// Read Command
|
||||||
|
dwSize = sizeof(m_szCommand);
|
||||||
|
RegQueryValueEx(hKey, REG_VALUE_COMMAND, NULL, &dwType, (LPBYTE)m_szCommand, &dwSize);
|
||||||
|
|
||||||
|
// Read Arguments
|
||||||
|
dwSize = sizeof(m_szArguments);
|
||||||
|
RegQueryValueEx(hKey, REG_VALUE_ARGS, NULL, &dwType, (LPBYTE)m_szArguments, &dwSize);
|
||||||
|
|
||||||
|
// Read Icon
|
||||||
|
dwSize = sizeof(m_szIcon);
|
||||||
|
RegQueryValueEx(hKey, REG_VALUE_ICON, NULL, &dwType, (LPBYTE)m_szIcon, &dwSize);
|
||||||
|
|
||||||
|
// Read MenuText
|
||||||
|
dwSize = sizeof(m_szMenuText);
|
||||||
|
dwSize = sizeof(m_szMenuText);
|
||||||
|
RegQueryValueEx(hKey, REG_VALUE_TEXT, NULL, &dwType, (LPBYTE)m_szMenuText, &dwSize);
|
||||||
|
|
||||||
|
// Read EnableLog
|
||||||
|
DWORD dwLog = 0;
|
||||||
|
dwSize = sizeof(dwLog);
|
||||||
|
DWORD dwTypeDw = REG_DWORD;
|
||||||
|
if (RegQueryValueEx(hKey, REG_VALUE_ENABLE_LOG, NULL, &dwTypeDw, (LPBYTE)&dwLog, &dwSize) == ERROR_SUCCESS) {
|
||||||
|
m_bEnableLog = (dwLog != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
RegCloseKey(hKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defaults
|
||||||
|
if (m_szCommand[0] == L'\0') {
|
||||||
|
// Nothing to run?
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_szArguments[0] == L'\0') {
|
||||||
|
StringCchCopy(m_szArguments, MAX_PATH, L"\"%1\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_szIcon[0] == L'\0') {
|
||||||
|
// Default icon to command exe
|
||||||
|
StringCchCopy(m_szIcon, MAX_PATH, m_szCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_szMenuText[0] == L'\0') {
|
||||||
|
StringCchCopy(m_szMenuText, 128, L"Open with Application");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// Class Factory
|
||||||
|
//==============================================================================
|
||||||
|
class ClassFactory : public IClassFactory
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
|
||||||
|
{
|
||||||
|
static const QITAB qit[] = { QITABENT(ClassFactory, IClassFactory), { 0 } };
|
||||||
|
return QISearch(this, qit, riid, ppv);
|
||||||
|
}
|
||||||
|
IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_cRef); }
|
||||||
|
IFACEMETHODIMP_(ULONG) Release()
|
||||||
|
{
|
||||||
|
ULONG cRef = InterlockedDecrement(&m_cRef);
|
||||||
|
if (cRef == 0) delete this;
|
||||||
|
return cRef;
|
||||||
|
}
|
||||||
|
IFACEMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv)
|
||||||
|
{
|
||||||
|
if (pUnkOuter != NULL) return CLASS_E_NOAGGREGATION;
|
||||||
|
UniversalShellExt *pExt = new (std::nothrow) UniversalShellExt(m_clsid);
|
||||||
|
if (pExt == NULL) return E_OUTOFMEMORY;
|
||||||
|
HRESULT hr = pExt->QueryInterface(riid, ppv);
|
||||||
|
pExt->Release();
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
IFACEMETHODIMP LockServer(BOOL fLock)
|
||||||
|
{
|
||||||
|
if (fLock) InterlockedIncrement(&g_cDllRef);
|
||||||
|
else InterlockedDecrement(&g_cDllRef);
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
ClassFactory(const CLSID& clsid) : m_cRef(1), m_clsid(clsid) { }
|
||||||
|
protected:
|
||||||
|
~ClassFactory() { }
|
||||||
|
private:
|
||||||
|
LONG m_cRef;
|
||||||
|
CLSID m_clsid;
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// DLL Exports
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
|
||||||
|
{
|
||||||
|
LPOLESTR szCLSID = NULL;
|
||||||
|
StringFromCLSID(rclsid, &szCLSID);
|
||||||
|
WriteLog(L"DllGetClassObject - Requesting CLSID: %s", szCLSID ? szCLSID : L"Unknown");
|
||||||
|
if (szCLSID) CoTaskMemFree(szCLSID);
|
||||||
|
|
||||||
|
*ppv = NULL;
|
||||||
|
if (!IsEqualIID(riid, IID_IClassFactory))
|
||||||
|
return CLASS_E_CLASSNOTAVAILABLE;
|
||||||
|
|
||||||
|
ClassFactory* pFactory = new (std::nothrow) ClassFactory(rclsid); // Pass CLSID to factory
|
||||||
|
if (!pFactory)
|
||||||
|
return E_OUTOFMEMORY;
|
||||||
|
|
||||||
|
HRESULT hr = pFactory->QueryInterface(riid, ppv);
|
||||||
|
pFactory->Release();
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
STDAPI DllCanUnloadNow(void) { return g_cDllRef > 0 ? S_FALSE : S_OK; }
|
||||||
|
|
||||||
|
HRESULT SetRegistryValue(HKEY hKeyRoot, LPCWSTR pszSubKey, LPCWSTR pszValueName, LPCWSTR pszValue)
|
||||||
|
{
|
||||||
|
HKEY hKey;
|
||||||
|
HRESULT hr = HRESULT_FROM_WIN32(RegCreateKeyEx(hKeyRoot, pszSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL));
|
||||||
|
if (SUCCEEDED(hr))
|
||||||
|
{
|
||||||
|
if (pszValue != NULL)
|
||||||
|
{
|
||||||
|
DWORD cbData = (lstrlen(pszValue) + 1) * sizeof(WCHAR);
|
||||||
|
hr = HRESULT_FROM_WIN32(RegSetValueEx(hKey, pszValueName, 0, REG_SZ, (const BYTE*)pszValue, cbData));
|
||||||
|
}
|
||||||
|
RegCloseKey(hKey);
|
||||||
|
}
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
STDAPI DllRegisterServer(void)
|
||||||
|
{
|
||||||
|
WCHAR szModulePath[MAX_PATH];
|
||||||
|
if (!GetModuleFileName(g_hInst, szModulePath, ARRAYSIZE(szModulePath))) return HRESULT_FROM_WIN32(GetLastError());
|
||||||
|
|
||||||
|
HRESULT hr = S_OK;
|
||||||
|
hr = SetRegistryValue(HKEY_CLASSES_ROOT, L"CLSID\\" CLSID_STRING, NULL, L"Universal Portable Shell Extension");
|
||||||
|
if (FAILED(hr)) return hr;
|
||||||
|
hr = SetRegistryValue(HKEY_CLASSES_ROOT, L"CLSID\\" CLSID_STRING L"\\InprocServer32", NULL, szModulePath);
|
||||||
|
if (FAILED(hr)) return hr;
|
||||||
|
hr = SetRegistryValue(HKEY_CLASSES_ROOT, L"CLSID\\" CLSID_STRING L"\\InprocServer32", L"ThreadingModel", L"Apartment");
|
||||||
|
if (FAILED(hr)) return hr;
|
||||||
|
|
||||||
|
// Register Context Menu Handler
|
||||||
|
hr = SetRegistryValue(HKEY_CLASSES_ROOT, L"Directory\\shellex\\ContextMenuHandlers\\UniversalShellExt", NULL, CLSID_STRING);
|
||||||
|
if (FAILED(hr)) return hr;
|
||||||
|
hr = SetRegistryValue(HKEY_CLASSES_ROOT, L"Directory\\Background\\shellex\\ContextMenuHandlers\\UniversalShellExt", NULL, CLSID_STRING);
|
||||||
|
if (FAILED(hr)) return hr;
|
||||||
|
|
||||||
|
// Approve
|
||||||
|
hr = SetRegistryValue(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", CLSID_STRING, L"Universal Shell Extension");
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
STDAPI DllUnregisterServer(void)
|
||||||
|
{
|
||||||
|
RegDeleteTree(HKEY_CLASSES_ROOT, L"CLSID\\" CLSID_STRING);
|
||||||
|
RegDeleteTree(HKEY_CLASSES_ROOT, L"Directory\\shellex\\ContextMenuHandlers\\UniversalShellExt");
|
||||||
|
RegDeleteTree(HKEY_CLASSES_ROOT, L"Directory\\Background\\shellex\\ContextMenuHandlers\\UniversalShellExt");
|
||||||
|
|
||||||
|
HKEY hKey;
|
||||||
|
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", 0, KEY_WRITE, &hKey) == ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
RegDeleteValue(hKey, CLSID_STRING);
|
||||||
|
RegCloseKey(hKey);
|
||||||
|
}
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
LIBRARY UniversalShellExt
|
||||||
|
EXPORTS
|
||||||
|
DllGetClassObject PRIVATE
|
||||||
|
DllCanUnloadNow PRIVATE
|
||||||
|
DllRegisterServer PRIVATE
|
||||||
|
DllUnregisterServer PRIVATE
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// VSCodeShellExt.rc - Version Information Resource
|
||||||
|
|
||||||
|
#include <winver.h>
|
||||||
|
|
||||||
|
#define VER_FILEVERSION 1,0,0,6
|
||||||
|
#define VER_FILEVERSION_STR "1.0.0.6\0"
|
||||||
|
#define VER_PRODUCTVERSION 1,0,0,6
|
||||||
|
#define VER_PRODUCTVERSION_STR "1.0.0\0"
|
||||||
|
|
||||||
|
#define VER_COMPANYNAME_STR "Generic Shell Ext\0"
|
||||||
|
#define VER_FILEDESCRIPTION_STR "Universal Portable Shell Extension\0"
|
||||||
|
#define VER_INTERNALNAME_STR "UniversalShellExt\0"
|
||||||
|
#define VER_LEGALCOPYRIGHT_STR "Copyright (C) 2024\0"
|
||||||
|
#define VER_ORIGINALFILENAME_STR "UniversalShellExt64.dll\0"
|
||||||
|
#define VER_PRODUCTNAME_STR "Universal Portable Shell Extension\0"
|
||||||
|
|
||||||
|
VS_VERSION_INFO VERSIONINFO
|
||||||
|
FILEVERSION VER_FILEVERSION
|
||||||
|
PRODUCTVERSION VER_PRODUCTVERSION
|
||||||
|
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||||
|
FILEFLAGS 0
|
||||||
|
FILEOS VOS_NT_WINDOWS32
|
||||||
|
FILETYPE VFT_DLL
|
||||||
|
FILESUBTYPE VFT2_UNKNOWN
|
||||||
|
BEGIN
|
||||||
|
BLOCK "StringFileInfo"
|
||||||
|
BEGIN
|
||||||
|
BLOCK "040904B0"
|
||||||
|
BEGIN
|
||||||
|
VALUE "CompanyName", VER_COMPANYNAME_STR
|
||||||
|
VALUE "FileDescription", VER_FILEDESCRIPTION_STR
|
||||||
|
VALUE "FileVersion", VER_FILEVERSION_STR
|
||||||
|
VALUE "InternalName", VER_INTERNALNAME_STR
|
||||||
|
VALUE "LegalCopyright", VER_LEGALCOPYRIGHT_STR
|
||||||
|
VALUE "OriginalFilename", VER_ORIGINALFILENAME_STR
|
||||||
|
VALUE "ProductName", VER_PRODUCTNAME_STR
|
||||||
|
VALUE "ProductVersion", VER_PRODUCTVERSION_STR
|
||||||
|
END
|
||||||
|
END
|
||||||
|
BLOCK "VarFileInfo"
|
||||||
|
BEGIN
|
||||||
|
VALUE "Translation", 0x409, 1200
|
||||||
|
END
|
||||||
|
END
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<ProjectConfiguration Include="Release|Win32">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|x64">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<VCProjectVersion>17.0</VCProjectVersion>
|
||||||
|
<ProjectGuid>{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}</ProjectGuid>
|
||||||
|
<RootNamespace>VSCodeShellExt</RootNamespace>
|
||||||
|
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="PropertySheets">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<OutDir>$(SolutionDir)bin\x86\</OutDir>
|
||||||
|
<IntDir>$(Configuration)\x86\</IntDir>
|
||||||
|
<TargetName>VSCodeShellExt32</TargetName>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<OutDir>$(SolutionDir)bin\x64\</OutDir>
|
||||||
|
<IntDir>$(Configuration)\x64\</IntDir>
|
||||||
|
<TargetName>UniversalShellExt64</TargetName>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Windows</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<ModuleDefinitionFile>UniversalShellExt.def</ModuleDefinitionFile>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Windows</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<ModuleDefinitionFile>UniversalShellExt.def</ModuleDefinitionFile>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="UniversalShellExt.cpp" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="UniversalShellExt.def" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
</Project>
|
||||||
Reference in New Issue
Block a user