mirror of
https://github.com/onyx-and-iris/nvda-voicemeeter.git
synced 2026-03-20 17:59:12 +00:00
Compare commits
No commits in common. "dev" and "v1.0.0" have entirely different histories.
237
.github/workflows/release.yml
vendored
237
.github/workflows/release.yml
vendored
@ -1,237 +0,0 @@
|
|||||||
name: Build and Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*.*.*'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: windows-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.12'
|
|
||||||
|
|
||||||
- name: Install PDM
|
|
||||||
uses: pdm-project/setup-pdm@v4
|
|
||||||
with:
|
|
||||||
python-version: '3.12'
|
|
||||||
|
|
||||||
- name: Install Task
|
|
||||||
uses: go-task/setup-task@v1
|
|
||||||
with:
|
|
||||||
version: 3.x
|
|
||||||
|
|
||||||
- name: Download NVDA Controller Client
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
Write-Host "Downloading NVDA Controller Client..."
|
|
||||||
$url = "https://download.nvaccess.org/releases/stable/nvda_2025.3.3_controllerClient.zip"
|
|
||||||
$zipPath = "nvda_controllerClient.zip"
|
|
||||||
|
|
||||||
# Download the zip file
|
|
||||||
Invoke-WebRequest -Uri $url -OutFile $zipPath
|
|
||||||
Write-Host "Downloaded $zipPath"
|
|
||||||
|
|
||||||
# Extract to temp directory
|
|
||||||
$tempDir = "temp_controller"
|
|
||||||
Expand-Archive -Path $zipPath -DestinationPath $tempDir -Force
|
|
||||||
|
|
||||||
# Find and copy DLL files to correct locations
|
|
||||||
Write-Host "Extracting DLL files..."
|
|
||||||
|
|
||||||
# Create directories if they don't exist
|
|
||||||
New-Item -ItemType Directory -Path "controllerClient/x64" -Force | Out-Null
|
|
||||||
New-Item -ItemType Directory -Path "controllerClient/x86" -Force | Out-Null
|
|
||||||
|
|
||||||
# Find and copy the DLL files
|
|
||||||
$dllFiles = Get-ChildItem -Path $tempDir -Recurse -Name "*.dll" | Where-Object { $_ -like "*controllerClient*" }
|
|
||||||
|
|
||||||
foreach ($dll in $dllFiles) {
|
|
||||||
$fullPath = Join-Path $tempDir $dll
|
|
||||||
$dirName = (Get-Item $fullPath).Directory.Name
|
|
||||||
|
|
||||||
if ($dll -match "x64" -or $dirName -match "x64") {
|
|
||||||
Copy-Item $fullPath "controllerClient/x64/nvdaControllerClient.dll"
|
|
||||||
Write-Host "Copied x64 DLL: $dll"
|
|
||||||
} elseif ($dll -match "x86" -or $dirName -match "x86") {
|
|
||||||
Copy-Item $fullPath "controllerClient/x86/nvdaControllerClient.dll"
|
|
||||||
Write-Host "Copied x86 DLL: $dll"
|
|
||||||
} elseif ($dll -match "arm64" -or $dirName -match "arm64") {
|
|
||||||
Write-Host "Skipping ARM64 DLL: $dll (not needed)"
|
|
||||||
} else {
|
|
||||||
Write-Host "Skipping unknown architecture DLL: $dll"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
Remove-Item $zipPath -Force
|
|
||||||
Remove-Item $tempDir -Recurse -Force
|
|
||||||
|
|
||||||
# Verify files were copied
|
|
||||||
Write-Host "Verifying controller client files..."
|
|
||||||
if (Test-Path "controllerClient/x64/nvdaControllerClient.dll") {
|
|
||||||
Write-Host "[OK] x64 controller client found"
|
|
||||||
} else {
|
|
||||||
Write-Host "[ERROR] x64 controller client missing"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Test-Path "controllerClient/x86/nvdaControllerClient.dll") {
|
|
||||||
Write-Host "[OK] x86 controller client found"
|
|
||||||
} else {
|
|
||||||
Write-Host "[ERROR] x86 controller client missing"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Fix dependencies for CI
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
echo "Fixing local dependencies for CI build..."
|
|
||||||
|
|
||||||
# Remove local path dependency for voicemeeter-api
|
|
||||||
pdm remove -dG dev voicemeeter-api || true
|
|
||||||
|
|
||||||
echo "Updated dependencies for CI build"
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
# Install project dependencies
|
|
||||||
pdm install
|
|
||||||
|
|
||||||
# Verify PyInstaller is available
|
|
||||||
Write-Host "Verifying PyInstaller installation..."
|
|
||||||
pdm list | Select-String pyinstaller
|
|
||||||
|
|
||||||
- name: Get PDM executable path
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
$pdmPath = Get-Command pdm | Select-Object -ExpandProperty Source
|
|
||||||
Write-Host "PDM path: $pdmPath"
|
|
||||||
echo "PDM_BIN=$pdmPath" >> $env:GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Build artifacts with dynamic taskfile
|
|
||||||
shell: pwsh
|
|
||||||
env:
|
|
||||||
PDM_BIN: ${{ env.PDM_BIN }}
|
|
||||||
run: |
|
|
||||||
Write-Host "Building all executables using dynamic builder..."
|
|
||||||
task --taskfile Taskfile.dynamic.yml build-all
|
|
||||||
|
|
||||||
- name: Compress build artifacts
|
|
||||||
shell: pwsh
|
|
||||||
env:
|
|
||||||
PDM_BIN: ${{ env.PDM_BIN }}
|
|
||||||
run: |
|
|
||||||
Write-Host "Compressing build artifacts..."
|
|
||||||
task --taskfile Taskfile.dynamic.yml compress-all
|
|
||||||
|
|
||||||
- name: Verify build outputs
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
Write-Host "Verifying build outputs..."
|
|
||||||
|
|
||||||
$expectedFiles = @(
|
|
||||||
"dist/basic.zip",
|
|
||||||
"dist/banana.zip",
|
|
||||||
"dist/potato.zip"
|
|
||||||
)
|
|
||||||
|
|
||||||
$missingFiles = @()
|
|
||||||
$foundFiles = @()
|
|
||||||
|
|
||||||
foreach ($file in $expectedFiles) {
|
|
||||||
if (Test-Path $file) {
|
|
||||||
$size = [math]::Round((Get-Item $file).Length / 1MB, 2)
|
|
||||||
$foundFiles += "$file ($size MB)"
|
|
||||||
} else {
|
|
||||||
$missingFiles += $file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "Found files:"
|
|
||||||
$foundFiles | ForEach-Object { Write-Host " $_" }
|
|
||||||
|
|
||||||
if ($missingFiles.Count -gt 0) {
|
|
||||||
Write-Host -ForegroundColor Red "Missing files:"
|
|
||||||
$missingFiles | ForEach-Object { Write-Host " $_" }
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host -ForegroundColor Green "All expected files found!"
|
|
||||||
|
|
||||||
- name: Upload build artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: nvda-voicemeeter-builds
|
|
||||||
path: |
|
|
||||||
dist/basic.zip
|
|
||||||
dist/banana.zip
|
|
||||||
dist/potato.zip
|
|
||||||
|
|
||||||
- name: Build Summary
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
Write-Host -ForegroundColor Green "Build completed successfully!"
|
|
||||||
Write-Host ""
|
|
||||||
Write-Host "Built artifacts:"
|
|
||||||
Write-Host " - nvda-voicemeeter-basic.zip"
|
|
||||||
Write-Host " - nvda-voicemeeter-banana.zip"
|
|
||||||
Write-Host " - nvda-voicemeeter-potato.zip"
|
|
||||||
|
|
||||||
release:
|
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
|
||||||
needs: build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Download all artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
|
|
||||||
- name: Create Release
|
|
||||||
run: |
|
|
||||||
TAG_NAME=${GITHUB_REF#refs/tags/}
|
|
||||||
gh release create $TAG_NAME \
|
|
||||||
--title "NVDA-Voicemeeter $TAG_NAME" \
|
|
||||||
--notes "## NVDA-Voicemeeter Release $TAG_NAME
|
|
||||||
|
|
||||||
### Downloads
|
|
||||||
- **nvda-voicemeeter-basic.zip** - Basic version with dependencies
|
|
||||||
- **nvda-voicemeeter-banana.zip** - Banana version with dependencies
|
|
||||||
- **nvda-voicemeeter-potato.zip** - Potato version with dependencies
|
|
||||||
|
|
||||||
### Requirements
|
|
||||||
- Windows 10/11
|
|
||||||
- Voicemeeter (Basic/Banana/Potato) installed
|
|
||||||
- NVDA screen reader
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
1. Download the appropriate zip for your Voicemeeter version
|
|
||||||
2. Extract and run the executable - no installation required
|
|
||||||
3. The application will integrate with NVDA automatically
|
|
||||||
|
|
||||||
### Notes
|
|
||||||
- Built with dynamic build system using PyInstaller
|
|
||||||
- Includes NVDA Controller Client for screen reader integration"
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Upload release assets
|
|
||||||
run: |
|
|
||||||
TAG_NAME=${GITHUB_REF#refs/tags/}
|
|
||||||
find . -name "*.zip" -exec gh release upload $TAG_NAME {} \;
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
19
.github/workflows/ruff.yml
vendored
19
.github/workflows/ruff.yml
vendored
@ -1,19 +0,0 @@
|
|||||||
name: Ruff
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
ruff:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: astral-sh/ruff-action@v3
|
|
||||||
with:
|
|
||||||
args: 'format --check --diff'
|
|
||||||
76
.gitignore
vendored
76
.gitignore
vendored
@ -1,9 +1,6 @@
|
|||||||
# Generated by ignr: github.com/onyx-and-iris/ignr
|
|
||||||
|
|
||||||
## Python ##
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[codz]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
@ -49,7 +46,7 @@ htmlcov/
|
|||||||
nosetests.xml
|
nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
*.cover
|
*.cover
|
||||||
*.py.cover
|
*.py,cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
cover/
|
cover/
|
||||||
@ -88,7 +85,7 @@ ipython_config.py
|
|||||||
# pyenv
|
# pyenv
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
# intended to run in multiple environments; otherwise, check them in:
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
# .python-version
|
.python-version
|
||||||
|
|
||||||
# pipenv
|
# pipenv
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
@ -97,36 +94,23 @@ ipython_config.py
|
|||||||
# install all needed dependencies.
|
# install all needed dependencies.
|
||||||
#Pipfile.lock
|
#Pipfile.lock
|
||||||
|
|
||||||
# UV
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
#uv.lock
|
|
||||||
|
|
||||||
# poetry
|
# poetry
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
# commonly ignored for libraries.
|
# commonly ignored for libraries.
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
#poetry.lock
|
#poetry.lock
|
||||||
#poetry.toml
|
|
||||||
|
|
||||||
# pdm
|
# pdm
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
||||||
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
||||||
#pdm.lock
|
#pdm.lock
|
||||||
#pdm.toml
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
.pdm-python
|
.pdm-python
|
||||||
.pdm-build/
|
.pdm-build/
|
||||||
|
|
||||||
# pixi
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
||||||
#pixi.lock
|
|
||||||
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
||||||
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
||||||
.pixi
|
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
__pypackages__/
|
__pypackages__/
|
||||||
|
|
||||||
@ -139,7 +123,6 @@ celerybeat.pid
|
|||||||
|
|
||||||
# Environments
|
# Environments
|
||||||
.env
|
.env
|
||||||
.envrc
|
|
||||||
.venv
|
.venv
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
@ -178,44 +161,11 @@ cython_debug/
|
|||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
# Abstra
|
# spec files
|
||||||
# Abstra is an AI-powered process automation framework.
|
spec/
|
||||||
# Ignore directories containing user credentials, local state, and settings.
|
|
||||||
# Learn more at https://abstra.io/docs
|
|
||||||
.abstra/
|
|
||||||
|
|
||||||
# Visual Studio Code
|
# persistent storage
|
||||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
settings.json
|
||||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
||||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
||||||
# you could uncomment the following to ignore the entire vscode folder
|
|
||||||
# .vscode/
|
|
||||||
|
|
||||||
# Ruff stuff:
|
# quick test
|
||||||
.ruff_cache/
|
quick.py
|
||||||
|
|
||||||
# PyPI configuration file
|
|
||||||
.pypirc
|
|
||||||
|
|
||||||
# Cursor
|
|
||||||
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
||||||
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
||||||
# refer to https://docs.cursor.com/context/ignore-files
|
|
||||||
.cursorignore
|
|
||||||
.cursorindexingignore
|
|
||||||
|
|
||||||
# Marimo
|
|
||||||
marimo/_static/
|
|
||||||
marimo/_lsp/
|
|
||||||
__marimo__/
|
|
||||||
|
|
||||||
# End of ignr
|
|
||||||
|
|
||||||
# Controller Client
|
|
||||||
controllerClient/**/*
|
|
||||||
!controllerClient/*/
|
|
||||||
!controllerClient/**/.gitkeep
|
|
||||||
|
|
||||||
# Spec files
|
|
||||||
spec/*
|
|
||||||
!spec/.gitkeep
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
repos:
|
|
||||||
- repo: https://github.com/pdm-project/pdm
|
|
||||||
rev: 2.26.6
|
|
||||||
hooks:
|
|
||||||
- id: pdm-lock-check
|
|
||||||
@ -1 +0,0 @@
|
|||||||
3.10.11
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
[](https://pdm-project.org)
|
[](https://pdm.fming.dev)
|
||||||
[](https://github.com/astral-sh/ruff)
|
[](https://github.com/astral-sh/ruff)
|
||||||
|
|
||||||
# NVDA Voicemeeter
|
# NVDA Voicemeeter
|
||||||
|
|||||||
@ -1,76 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
# Dynamic build system - no spec files needed!
|
|
||||||
# Examples:
|
|
||||||
# - task -t Taskfile.dynamic.yml build KINDS="basic banana"
|
|
||||||
# - task -t Taskfile.dynamic.yml build-all
|
|
||||||
# KINDS can be a space-separated list of kinds to build, or "all" to build everything.
|
|
||||||
#
|
|
||||||
# Compression tasks are also dynamic, allowing you to specify which kind to compress or compress all kinds at once.
|
|
||||||
# Examples:
|
|
||||||
# - task -t Taskfile.dynamic.yml compress KIND=basic
|
|
||||||
# - task -t Taskfile.dynamic.yml compress-all
|
|
||||||
|
|
||||||
vars:
|
|
||||||
KINDS: '{{.KINDS | default "all"}}'
|
|
||||||
SHELL: pwsh
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
build:
|
|
||||||
desc: Build specified kinds dynamically (no spec files needed)
|
|
||||||
preconditions:
|
|
||||||
- sh: |
|
|
||||||
if [ ! -f controllerClient/x64/nvdaControllerClient.dll ] || [ ! -f controllerClient/x86/nvdaControllerClient.dll ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
msg: 'nvdaControllerClient.dll is missing. See https://github.com/nvaccess/nvda/blob/master/extras/controllerClient/readme.md for instructions on how to obtain it.'
|
|
||||||
cmds:
|
|
||||||
- ${PDM_BIN:-pdm} run python tools/dynamic_builder.py {{.KINDS}}
|
|
||||||
|
|
||||||
build-all:
|
|
||||||
desc: Build all kinds
|
|
||||||
preconditions:
|
|
||||||
- sh: |
|
|
||||||
if [ ! -f controllerClient/x64/nvdaControllerClient.dll ] || [ ! -f controllerClient/x86/nvdaControllerClient.dll ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
msg: 'nvdaControllerClient.dll is missing. See https://github.com/nvaccess/nvda/blob/master/extras/controllerClient/readme.md for instructions on how to obtain it.'
|
|
||||||
cmds:
|
|
||||||
- ${PDM_BIN:-pdm} run python tools/dynamic_builder.py all
|
|
||||||
|
|
||||||
compress:
|
|
||||||
desc: Compress artifacts for specified kind
|
|
||||||
cmds:
|
|
||||||
- task: compress-{{.KIND}}
|
|
||||||
|
|
||||||
compress-all:
|
|
||||||
desc: Compress artifacts for all kinds
|
|
||||||
cmds:
|
|
||||||
- for:
|
|
||||||
matrix:
|
|
||||||
KIND: [basic, banana, potato]
|
|
||||||
task: compress-{{.ITEM.KIND}}
|
|
||||||
|
|
||||||
compress-basic:
|
|
||||||
desc: Compress basic build artifacts
|
|
||||||
cmd: '{{.SHELL}} -Command "Compress-Archive -Path dist/basic -DestinationPath dist/basic.zip -Force"'
|
|
||||||
generates:
|
|
||||||
- dist/basic.zip
|
|
||||||
|
|
||||||
compress-banana:
|
|
||||||
desc: Compress banana build artifacts
|
|
||||||
cmd: '{{.SHELL}} -Command "Compress-Archive -Path dist/banana -DestinationPath dist/banana.zip -Force"'
|
|
||||||
generates:
|
|
||||||
- dist/banana.zip
|
|
||||||
|
|
||||||
compress-potato:
|
|
||||||
desc: Compress potato build artifacts
|
|
||||||
cmd: '{{.SHELL}} -Command "Compress-Archive -Path dist/potato -DestinationPath dist/potato.zip -Force"'
|
|
||||||
generates:
|
|
||||||
- dist/potato.zip
|
|
||||||
|
|
||||||
clean:
|
|
||||||
desc: Clean all build artifacts
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
{{.SHELL}} -Command "Remove-Item -Path build/*,dist/* -Recurse -Force -ErrorAction SilentlyContinue"
|
|
||||||
49
Taskfile.yml
49
Taskfile.yml
@ -1,49 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
vars:
|
|
||||||
SHELL: pwsh
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
default:
|
|
||||||
desc: Prepare artifacts for release
|
|
||||||
cmds:
|
|
||||||
- task: release
|
|
||||||
|
|
||||||
release:
|
|
||||||
desc: Build and compress all artifacts
|
|
||||||
cmds:
|
|
||||||
- task: build
|
|
||||||
- task: compress
|
|
||||||
|
|
||||||
generate-specs:
|
|
||||||
desc: Generate all spec files from templates
|
|
||||||
cmd: pdm run python tools/spec_generator.py
|
|
||||||
|
|
||||||
build:
|
|
||||||
desc: Build the project
|
|
||||||
deps: [generate-specs]
|
|
||||||
preconditions:
|
|
||||||
- sh: |
|
|
||||||
if [ ! -f controllerClient/x64/nvdaControllerClient.dll ] || [ ! -f controllerClient/x86/nvdaControllerClient.dll ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
msg: 'nvdaControllerClient.dll is missing. See https://github.com/nvaccess/nvda/blob/master/extras/controllerClient/readme.md for instructions on how to obtain it.'
|
|
||||||
cmds:
|
|
||||||
- for:
|
|
||||||
matrix:
|
|
||||||
KIND: [basic, banana, potato]
|
|
||||||
cmd: pdm run pyinstaller --noconfirm --distpath dist/{{.ITEM.KIND}} spec/{{.ITEM.KIND}}.spec
|
|
||||||
|
|
||||||
compress:
|
|
||||||
desc: Compress the build artifacts
|
|
||||||
cmds:
|
|
||||||
- for:
|
|
||||||
matrix:
|
|
||||||
KIND: [basic, banana, potato]
|
|
||||||
cmd: '{{.SHELL}} -Command "Compress-Archive -Path dist/{{.ITEM.KIND}} -DestinationPath dist/{{.ITEM.KIND}}.zip -Force"'
|
|
||||||
|
|
||||||
clean:
|
|
||||||
desc: Clean the project
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
{{.SHELL}} -Command "Remove-Item -Path build/*,dist/* -Recurse -Force -ErrorAction SilentlyContinue"
|
|
||||||
@ -2,7 +2,7 @@ import voicemeeterlib
|
|||||||
|
|
||||||
import nvda_voicemeeter
|
import nvda_voicemeeter
|
||||||
|
|
||||||
KIND_ID = 'potato'
|
KIND_ID = "potato"
|
||||||
|
|
||||||
with voicemeeterlib.api(KIND_ID) as vm:
|
with voicemeeterlib.api(KIND_ID) as vm:
|
||||||
with nvda_voicemeeter.draw(KIND_ID, vm) as window:
|
with nvda_voicemeeter.draw(KIND_ID, vm) as window:
|
||||||
|
|||||||
23
build.ps1
Normal file
23
build.ps1
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
function Compress-Builds {
|
||||||
|
$target = Join-Path -Path $PSScriptRoot -ChildPath "dist"
|
||||||
|
@("basic", "banana", "potato") | ForEach-Object {
|
||||||
|
Compress-Archive -Path $(Join-Path -Path $target -ChildPath $_) -DestinationPath $(Join-Path -Path $target -ChildPath "${_}.zip") -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-Builds {
|
||||||
|
@("basic", "banana", "potato") | ForEach-Object {
|
||||||
|
$specName = $_
|
||||||
|
|
||||||
|
Write-Host "building $specName"
|
||||||
|
|
||||||
|
pdm run pyinstaller --noconfirm --distpath (Join-Path -Path "dist" -ChildPath $specName) (Join-Path -Path "spec" -ChildPath "${specName}.spec")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function main {
|
||||||
|
Get-Builds
|
||||||
|
Compress-Builds
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($MyInvocation.InvocationName -ne '.') { main }
|
||||||
BIN
controllerClient/x64/nvdaControllerClient.dll
Normal file
BIN
controllerClient/x64/nvdaControllerClient.dll
Normal file
Binary file not shown.
BIN
controllerClient/x86/nvdaControllerClient.dll
Normal file
BIN
controllerClient/x86/nvdaControllerClient.dll
Normal file
Binary file not shown.
194
pdm.lock
generated
194
pdm.lock
generated
@ -2,37 +2,37 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
groups = ["default", "build", "dev", "format"]
|
groups = ["default", "build", "format"]
|
||||||
strategy = ["inherit_metadata"]
|
strategy = ["inherit_metadata"]
|
||||||
lock_version = "4.5.0"
|
lock_version = "4.5.0"
|
||||||
content_hash = "sha256:7f8d5a672b82b4ff9d793a255dcdf55415b79773d7cf7e2742f4531366daac58"
|
content_hash = "sha256:177dea67a7eadb6b161d3ac62fb09415d2d4f295c59914a9c5d0cbef7f0a5b65"
|
||||||
|
|
||||||
[[metadata.targets]]
|
[[metadata.targets]]
|
||||||
requires_python = ">=3.10,<3.13"
|
requires_python = ">=3.10,<3.13"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "altgraph"
|
name = "altgraph"
|
||||||
version = "0.17.5"
|
version = "0.17.4"
|
||||||
summary = "Python graph (network) package"
|
summary = "Python graph (network) package"
|
||||||
groups = ["build"]
|
groups = ["build"]
|
||||||
files = [
|
files = [
|
||||||
{file = "altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597"},
|
{file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"},
|
||||||
{file = "altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7"},
|
{file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "freesimplegui"
|
name = "freesimplegui"
|
||||||
version = "5.2.0.post1"
|
version = "5.1.1"
|
||||||
summary = "The free-forever Python GUI framework."
|
summary = "The free-forever Python GUI framework."
|
||||||
groups = ["default"]
|
groups = ["default"]
|
||||||
files = [
|
files = [
|
||||||
{file = "freesimplegui-5.2.0.post1-py3-none-any.whl", hash = "sha256:3d61eb519324503232f86b2f1bd7f5c6813ce225f6e189d0fd737ddb036af4d5"},
|
{file = "FreeSimpleGUI-5.1.1-py3-none-any.whl", hash = "sha256:d7629d5c94b55264d119bd2a89f52667d863ea7914d808e619aea29922ff842e"},
|
||||||
{file = "freesimplegui-5.2.0.post1.tar.gz", hash = "sha256:e58a0e6758e9a9e87152256911f94fcc3998356d1309973a9f4d9df2dc55f98a"},
|
{file = "freesimplegui-5.1.1.tar.gz", hash = "sha256:2f0946c7ac221c997929181cbe7526e342fff5fc291a26d1d726287a5dd964fb"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "macholib"
|
name = "macholib"
|
||||||
version = "1.16.4"
|
version = "1.16.3"
|
||||||
summary = "Mach-O header analysis and editing"
|
summary = "Mach-O header analysis and editing"
|
||||||
groups = ["build"]
|
groups = ["build"]
|
||||||
marker = "sys_platform == \"darwin\""
|
marker = "sys_platform == \"darwin\""
|
||||||
@ -40,37 +40,37 @@ dependencies = [
|
|||||||
"altgraph>=0.17",
|
"altgraph>=0.17",
|
||||||
]
|
]
|
||||||
files = [
|
files = [
|
||||||
{file = "macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea"},
|
{file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"},
|
||||||
{file = "macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362"},
|
{file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "26.0"
|
version = "24.2"
|
||||||
requires_python = ">=3.8"
|
requires_python = ">=3.8"
|
||||||
summary = "Core utilities for Python packages"
|
summary = "Core utilities for Python packages"
|
||||||
groups = ["build"]
|
groups = ["build"]
|
||||||
files = [
|
files = [
|
||||||
{file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"},
|
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
|
||||||
{file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"},
|
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pefile"
|
name = "pefile"
|
||||||
version = "2024.8.26"
|
version = "2023.2.7"
|
||||||
requires_python = ">=3.6.0"
|
requires_python = ">=3.6.0"
|
||||||
summary = "Python PE parsing module"
|
summary = "Python PE parsing module"
|
||||||
groups = ["build"]
|
groups = ["build"]
|
||||||
marker = "sys_platform == \"win32\""
|
marker = "sys_platform == \"win32\""
|
||||||
files = [
|
files = [
|
||||||
{file = "pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f"},
|
{file = "pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"},
|
||||||
{file = "pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632"},
|
{file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyinstaller"
|
name = "pyinstaller"
|
||||||
version = "6.19.0"
|
version = "6.11.1"
|
||||||
requires_python = "<3.15,>=3.8"
|
requires_python = "<3.14,>=3.8"
|
||||||
summary = "PyInstaller bundles a Python application and all its dependencies into a single package."
|
summary = "PyInstaller bundles a Python application and all its dependencies into a single package."
|
||||||
groups = ["build"]
|
groups = ["build"]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
@ -78,29 +78,29 @@ dependencies = [
|
|||||||
"importlib-metadata>=4.6; python_version < \"3.10\"",
|
"importlib-metadata>=4.6; python_version < \"3.10\"",
|
||||||
"macholib>=1.8; sys_platform == \"darwin\"",
|
"macholib>=1.8; sys_platform == \"darwin\"",
|
||||||
"packaging>=22.0",
|
"packaging>=22.0",
|
||||||
"pefile>=2022.5.30; sys_platform == \"win32\"",
|
"pefile!=2024.8.26,>=2022.5.30; sys_platform == \"win32\"",
|
||||||
"pyinstaller-hooks-contrib>=2026.0",
|
"pyinstaller-hooks-contrib>=2024.9",
|
||||||
"pywin32-ctypes>=0.2.1; sys_platform == \"win32\"",
|
"pywin32-ctypes>=0.2.1; sys_platform == \"win32\"",
|
||||||
"setuptools>=42.0.0",
|
"setuptools>=42.0.0",
|
||||||
]
|
]
|
||||||
files = [
|
files = [
|
||||||
{file = "pyinstaller-6.19.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:4190e76b74f0c4b5c5f11ac360928cd2e36ec8e3194d437bf6b8648c7bc0c134"},
|
{file = "pyinstaller-6.11.1-py3-none-macosx_10_13_universal2.whl", hash = "sha256:44e36172de326af6d4e7663b12f71dbd34e2e3e02233e181e457394423daaf03"},
|
||||||
{file = "pyinstaller-6.19.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8bd68abd812d8a6ba33b9f1810e91fee0f325969733721b78151f0065319ca11"},
|
{file = "pyinstaller-6.11.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:6d12c45a29add78039066a53fb05967afaa09a672426072b13816fe7676abfc4"},
|
||||||
{file = "pyinstaller-6.19.0-py3-none-manylinux2014_i686.whl", hash = "sha256:1ec54ef967996ca61dacba676227e2b23219878ccce5ee9d6f3aada7b8ed8abf"},
|
{file = "pyinstaller-6.11.1-py3-none-manylinux2014_i686.whl", hash = "sha256:ddc0fddd75f07f7e423da1f0822e389a42af011f9589e0269b87e0d89aa48c1f"},
|
||||||
{file = "pyinstaller-6.19.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:4ab2bb52e58448e14ddf9450601bdedd66800465043501c1d8f1cab87b60b122"},
|
{file = "pyinstaller-6.11.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:0d6475559c4939f0735122989611d7f739ed3bf02f666ce31022928f7a7e4fda"},
|
||||||
{file = "pyinstaller-6.19.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:da6d5c6391ccefe73554b9fa29b86001c8e378e0f20c2a4004f836ba537eff63"},
|
{file = "pyinstaller-6.11.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:e21c7806e34f40181e7606926a14579f848bfb1dc52cbca7eea66eccccbfe977"},
|
||||||
{file = "pyinstaller-6.19.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a0fc5f6b3c55aa54353f0c74ffa59b1115433c1850c6f655d62b461a2ed6cbbe"},
|
{file = "pyinstaller-6.11.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:32c742a24fe65d0702958fadf4040f76de85859c26bec0008766e5dbabc5b68f"},
|
||||||
{file = "pyinstaller-6.19.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:e649ba6bd1b0b89b210ad92adb5fbdc8a42dd2c5ca4f72ef3a0bfec83a424b83"},
|
{file = "pyinstaller-6.11.1-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:208c0ef6dab0837a0a273ea32d1a3619a208e3d1fe3fec3785eea71a77fd00ce"},
|
||||||
{file = "pyinstaller-6.19.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:481a909c8e60c8692fc60fcb1344d984b44b943f8bc9682f2fcdae305ad297e6"},
|
{file = "pyinstaller-6.11.1-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:ad84abf465bcda363c1d54eafa76745d77b6a8a713778348377dc98d12a452f7"},
|
||||||
{file = "pyinstaller-6.19.0-py3-none-win32.whl", hash = "sha256:3c5c251054fe4cfaa04c34a363dcfbf811545438cb7198304cd444756bc2edd2"},
|
{file = "pyinstaller-6.11.1-py3-none-win32.whl", hash = "sha256:2e8365276c5131c9bef98e358fbc305e4022db8bedc9df479629d6414021956a"},
|
||||||
{file = "pyinstaller-6.19.0-py3-none-win_amd64.whl", hash = "sha256:b5bb6536c6560330d364d91522250f254b107cf69129d9cbcd0e6727c570be33"},
|
{file = "pyinstaller-6.11.1-py3-none-win_amd64.whl", hash = "sha256:7ac83c0dc0e04357dab98c487e74ad2adb30e7eb186b58157a8faf46f1fa796f"},
|
||||||
{file = "pyinstaller-6.19.0-py3-none-win_arm64.whl", hash = "sha256:c2d5a539b0bfe6159d5522c8c70e1c0e487f22c2badae0f97d45246223b798ea"},
|
{file = "pyinstaller-6.11.1-py3-none-win_arm64.whl", hash = "sha256:35e6b8077d240600bb309ed68bb0b1453fd2b7ab740b66d000db7abae6244423"},
|
||||||
{file = "pyinstaller-6.19.0.tar.gz", hash = "sha256:ec73aeb8bd9b7f2f1240d328a4542e90b3c6e6fbc106014778431c616592a865"},
|
{file = "pyinstaller-6.11.1.tar.gz", hash = "sha256:491dfb4d9d5d1d9650d9507daec1ff6829527a254d8e396badd60a0affcb72ef"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyinstaller-hooks-contrib"
|
name = "pyinstaller-hooks-contrib"
|
||||||
version = "2026.3"
|
version = "2025.0"
|
||||||
requires_python = ">=3.8"
|
requires_python = ">=3.8"
|
||||||
summary = "Community maintained hooks for PyInstaller"
|
summary = "Community maintained hooks for PyInstaller"
|
||||||
groups = ["build"]
|
groups = ["build"]
|
||||||
@ -110,19 +110,19 @@ dependencies = [
|
|||||||
"setuptools>=42.0.0",
|
"setuptools>=42.0.0",
|
||||||
]
|
]
|
||||||
files = [
|
files = [
|
||||||
{file = "pyinstaller_hooks_contrib-2026.3-py3-none-any.whl", hash = "sha256:5ecd1068ad262afecadf07556279d2be52ca93a88b049fae17f1a2eb2969254a"},
|
{file = "pyinstaller_hooks_contrib-2025.0-py3-none-any.whl", hash = "sha256:3c0623799c3f81a37293127f485d65894c20fd718f722cb588785a3e52581ad1"},
|
||||||
{file = "pyinstaller_hooks_contrib-2026.3.tar.gz", hash = "sha256:800d3a198a49a6cd0de2d7fb795005fdca7a0222ed9cb47c0691abd1c27b9310"},
|
{file = "pyinstaller_hooks_contrib-2025.0.tar.gz", hash = "sha256:6dc0b55a1acaab2ffee36ed4a05b073aa0a22e46f25fb5c66a31e217454135ed"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyparsing"
|
name = "pyparsing"
|
||||||
version = "3.3.2"
|
version = "3.2.1"
|
||||||
requires_python = ">=3.9"
|
requires_python = ">=3.9"
|
||||||
summary = "pyparsing - Classes and methods to define and execute parsing grammars"
|
summary = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
||||||
groups = ["default"]
|
groups = ["default"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d"},
|
{file = "pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1"},
|
||||||
{file = "pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc"},
|
{file = "pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -139,80 +139,94 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.15.5"
|
version = "0.9.2"
|
||||||
requires_python = ">=3.7"
|
requires_python = ">=3.7"
|
||||||
summary = "An extremely fast Python linter and code formatter, written in Rust."
|
summary = "An extremely fast Python linter and code formatter, written in Rust."
|
||||||
groups = ["format"]
|
groups = ["format"]
|
||||||
files = [
|
files = [
|
||||||
{file = "ruff-0.15.5-py3-none-linux_armv6l.whl", hash = "sha256:4ae44c42281f42e3b06b988e442d344a5b9b72450ff3c892e30d11b29a96a57c"},
|
{file = "ruff-0.9.2-py3-none-linux_armv6l.whl", hash = "sha256:80605a039ba1454d002b32139e4970becf84b5fee3a3c3bf1c2af6f61a784347"},
|
||||||
{file = "ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6edd3792d408ebcf61adabc01822da687579a1a023f297618ac27a5b51ef0080"},
|
{file = "ruff-0.9.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b9aab82bb20afd5f596527045c01e6ae25a718ff1784cb92947bff1f83068b00"},
|
||||||
{file = "ruff-0.15.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:89f463f7c8205a9f8dea9d658d59eff49db05f88f89cc3047fb1a02d9f344010"},
|
{file = "ruff-0.9.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fbd337bac1cfa96be615f6efcd4bc4d077edbc127ef30e2b8ba2a27e18c054d4"},
|
||||||
{file = "ruff-0.15.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba786a8295c6574c1116704cf0b9e6563de3432ac888d8f83685654fe528fd65"},
|
{file = "ruff-0.9.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b35259b0cbf8daa22a498018e300b9bb0174c2bbb7bcba593935158a78054d"},
|
||||||
{file = "ruff-0.15.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd4b801e57955fe9f02b31d20375ab3a5c4415f2e5105b79fb94cf2642c91440"},
|
{file = "ruff-0.9.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b6a9701d1e371bf41dca22015c3f89769da7576884d2add7317ec1ec8cb9c3c"},
|
||||||
{file = "ruff-0.15.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391f7c73388f3d8c11b794dbbc2959a5b5afe66642c142a6effa90b45f6f5204"},
|
{file = "ruff-0.9.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cc53e68b3c5ae41e8faf83a3b89f4a5d7b2cb666dff4b366bb86ed2a85b481f"},
|
||||||
{file = "ruff-0.15.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc18f30302e379fe1e998548b0f5e9f4dff907f52f73ad6da419ea9c19d66c8"},
|
{file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8efd9da7a1ee314b910da155ca7e8953094a7c10d0c0a39bfde3fcfd2a015684"},
|
||||||
{file = "ruff-0.15.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc6e7f90087e2d27f98dc34ed1b3ab7c8f0d273cc5431415454e22c0bd2a681"},
|
{file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3292c5a22ea9a5f9a185e2d131dc7f98f8534a32fb6d2ee7b9944569239c648d"},
|
||||||
{file = "ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cb7169f53c1ddb06e71a9aebd7e98fc0fea936b39afb36d8e86d36ecc2636a"},
|
{file = "ruff-0.9.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a605fdcf6e8b2d39f9436d343d1f0ff70c365a1e681546de0104bef81ce88df"},
|
||||||
{file = "ruff-0.15.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9b037924500a31ee17389b5c8c4d88874cc6ea8e42f12e9c61a3d754ff72f1ca"},
|
{file = "ruff-0.9.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c547f7f256aa366834829a08375c297fa63386cbe5f1459efaf174086b564247"},
|
||||||
{file = "ruff-0.15.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65bb414e5b4eadd95a8c1e4804f6772bbe8995889f203a01f77ddf2d790929dd"},
|
{file = "ruff-0.9.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d18bba3d3353ed916e882521bc3e0af403949dbada344c20c16ea78f47af965e"},
|
||||||
{file = "ruff-0.15.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d20aa469ae3b57033519c559e9bc9cd9e782842e39be05b50e852c7c981fa01d"},
|
{file = "ruff-0.9.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b338edc4610142355ccf6b87bd356729b62bf1bc152a2fad5b0c7dc04af77bfe"},
|
||||||
{file = "ruff-0.15.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:15388dd28c9161cdb8eda68993533acc870aa4e646a0a277aa166de9ad5a8752"},
|
{file = "ruff-0.9.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:492a5e44ad9b22a0ea98cf72e40305cbdaf27fac0d927f8bc9e1df316dcc96eb"},
|
||||||
{file = "ruff-0.15.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b30da330cbd03bed0c21420b6b953158f60c74c54c5f4c1dabbdf3a57bf355d2"},
|
{file = "ruff-0.9.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:af1e9e9fe7b1f767264d26b1075ac4ad831c7db976911fa362d09b2d0356426a"},
|
||||||
{file = "ruff-0.15.5-py3-none-win32.whl", hash = "sha256:732e5ee1f98ba5b3679029989a06ca39a950cced52143a0ea82a2102cb592b74"},
|
{file = "ruff-0.9.2-py3-none-win32.whl", hash = "sha256:71cbe22e178c5da20e1514e1e01029c73dc09288a8028a5d3446e6bba87a5145"},
|
||||||
{file = "ruff-0.15.5-py3-none-win_amd64.whl", hash = "sha256:821d41c5fa9e19117616c35eaa3f4b75046ec76c65e7ae20a333e9a8696bc7fe"},
|
{file = "ruff-0.9.2-py3-none-win_amd64.whl", hash = "sha256:c5e1d6abc798419cf46eed03f54f2e0c3adb1ad4b801119dedf23fcaf69b55b5"},
|
||||||
{file = "ruff-0.15.5-py3-none-win_arm64.whl", hash = "sha256:b498d1c60d2fe5c10c45ec3f698901065772730b411f164ae270bb6bfcc4740b"},
|
{file = "ruff-0.9.2-py3-none-win_arm64.whl", hash = "sha256:a1b63fa24149918f8b37cef2ee6fff81f24f0d74b6f0bdc37bc3e1f2143e41c6"},
|
||||||
{file = "ruff-0.15.5.tar.gz", hash = "sha256:7c3601d3b6d76dce18c5c824fc8d06f4eef33d6df0c21ec7799510cde0f159a2"},
|
{file = "ruff-0.9.2.tar.gz", hash = "sha256:b5eceb334d55fae5f316f783437392642ae18e16dcf4f1858d55d3c2a0f8f5d0"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "setuptools"
|
name = "setuptools"
|
||||||
version = "82.0.1"
|
version = "75.8.0"
|
||||||
requires_python = ">=3.9"
|
requires_python = ">=3.9"
|
||||||
summary = "Most extensible Python build backend with support for C/C++ extension modules"
|
summary = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||||
groups = ["build"]
|
groups = ["build"]
|
||||||
files = [
|
files = [
|
||||||
{file = "setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb"},
|
{file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"},
|
||||||
{file = "setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9"},
|
{file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tomli"
|
name = "tomli"
|
||||||
version = "2.4.0"
|
version = "2.2.1"
|
||||||
requires_python = ">=3.8"
|
requires_python = ">=3.8"
|
||||||
summary = "A lil' TOML parser"
|
summary = "A lil' TOML parser"
|
||||||
groups = ["default", "dev"]
|
groups = ["default"]
|
||||||
marker = "python_version < \"3.11\""
|
marker = "python_version < \"3.11\""
|
||||||
files = [
|
files = [
|
||||||
{file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"},
|
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
|
||||||
{file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"},
|
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
|
||||||
{file = "tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95"},
|
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
|
||||||
{file = "tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76"},
|
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
|
||||||
{file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d"},
|
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
|
||||||
{file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576"},
|
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
|
||||||
{file = "tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a"},
|
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
|
||||||
{file = "tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa"},
|
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
|
||||||
{file = "tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614"},
|
{file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
|
||||||
{file = "tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1"},
|
{file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
|
||||||
{file = "tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8"},
|
{file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
|
||||||
{file = "tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a"},
|
{file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
|
||||||
{file = "tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1"},
|
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
|
||||||
{file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b"},
|
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
|
||||||
{file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51"},
|
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
|
||||||
{file = "tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729"},
|
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
|
||||||
{file = "tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da"},
|
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
|
||||||
{file = "tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3"},
|
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
|
||||||
{file = "tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a"},
|
{file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
|
||||||
{file = "tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c"},
|
{file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
|
||||||
|
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
|
||||||
|
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "voicemeeter-api"
|
name = "voicemeeter-api"
|
||||||
version = "2.7.2"
|
version = "2.6.1"
|
||||||
requires_python = ">=3.10"
|
requires_python = "<4.0,>=3.10"
|
||||||
editable = true
|
|
||||||
path = "../voicemeeter-api-python"
|
|
||||||
summary = "A Python wrapper for the Voiceemeter API"
|
summary = "A Python wrapper for the Voiceemeter API"
|
||||||
groups = ["default", "dev"]
|
groups = ["default"]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tomli<3.0,>=2.0.1; python_version < \"3.11\"",
|
"tomli<3.0,>=2.0.1; python_version < \"3.11\"",
|
||||||
]
|
]
|
||||||
|
files = [
|
||||||
|
{file = "voicemeeter_api-2.6.1-py3-none-any.whl", hash = "sha256:8ae3bce0f9ad6bbad78f2f69f522b6fb2e229d314918a075ad83d4009aff7020"},
|
||||||
|
{file = "voicemeeter_api-2.6.1.tar.gz", hash = "sha256:34d8672603ec66197f2d61fd8f038f46d8451759c81fbe222b00e7d3ccccd1f5"},
|
||||||
|
]
|
||||||
|
|||||||
@ -1,25 +1,27 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "nvda-voicemeeter"
|
name = "nvda-voicemeeter"
|
||||||
version = "1.1.2"
|
version = "1.0.0"
|
||||||
description = "A Voicemeeter app compatible with NVDA"
|
description = "A Voicemeeter app compatible with NVDA"
|
||||||
authors = [{ name = "Onyx and Iris", email = "code@onyxandiris.online" }]
|
authors = [
|
||||||
dependencies = [
|
{name = "Onyx and Iris", email = "code@onyxandiris.online"},
|
||||||
"pyparsing>=3.2.1",
|
|
||||||
"voicemeeter-api>=2.7.2",
|
|
||||||
"freesimplegui>=5.1.1",
|
|
||||||
]
|
]
|
||||||
|
dependencies = ["pyparsing>=3.2.1", "voicemeeter-api>=2.6.1", "freesimplegui>=5.1.1"]
|
||||||
requires-python = ">=3.10,<3.13"
|
requires-python = ">=3.10,<3.13"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = {text = "MIT"}
|
license = {text = "MIT"}
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
format = ["ruff>=0.9.2"]
|
format = [
|
||||||
build = ["pyinstaller>=6.3.0"]
|
"ruff>=0.9.2",
|
||||||
|
]
|
||||||
|
build = [
|
||||||
|
"pyinstaller>=6.3.0",
|
||||||
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
nvda-voicemeeter-basic = "nvda_voicemeeter.gui.basic:run"
|
gui-basic = "nvda_voicemeeter.gui.basic:run"
|
||||||
nvda-voicemeeter-banana = "nvda_voicemeeter.gui.banana:run"
|
gui-banana = "nvda_voicemeeter.gui.banana:run"
|
||||||
nvda-voicemeeter-potato = "nvda_voicemeeter.gui.potato:run"
|
gui-potato = "nvda_voicemeeter.gui.potato:run"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["pdm-backend"]
|
requires = ["pdm-backend"]
|
||||||
@ -28,10 +30,8 @@ build-backend = "pdm.backend"
|
|||||||
[tool.pdm]
|
[tool.pdm]
|
||||||
distribution = true
|
distribution = true
|
||||||
|
|
||||||
[tool.pdm.dev-dependencies]
|
[tool.pdm.scripts]
|
||||||
dev = [
|
build = {shell = "pwsh build.ps1"}
|
||||||
"-e file:///${PROJECT_ROOT}/../voicemeeter-api-python#egg=voicemeeter-api",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
exclude = [
|
exclude = [
|
||||||
@ -110,4 +110,7 @@ docstring-code-line-length = "dynamic"
|
|||||||
max-complexity = 10
|
max-complexity = 10
|
||||||
|
|
||||||
[tool.ruff.lint.per-file-ignores]
|
[tool.ruff.lint.per-file-ignores]
|
||||||
"__init__.py" = ["E402", "F401"]
|
"__init__.py" = [
|
||||||
|
"E402",
|
||||||
|
"F401",
|
||||||
|
]
|
||||||
|
|||||||
@ -438,20 +438,13 @@ class Builder:
|
|||||||
def make_tab3_button_row(self, i) -> psg.Frame:
|
def make_tab3_button_row(self, i) -> psg.Frame:
|
||||||
"""tab3 row represents bus composite toggle"""
|
"""tab3 row represents bus composite toggle"""
|
||||||
|
|
||||||
def add_bus_buttons(layout):
|
def add_strip_outputs(layout):
|
||||||
busmono = util.get_bus_mono()
|
params = ['MONO', 'EQ', 'MUTE']
|
||||||
params = ['EQ', 'MUTE']
|
|
||||||
if self.kind.name == 'basic':
|
if self.kind.name == 'basic':
|
||||||
params.remove('EQ')
|
params.remove('EQ')
|
||||||
busmodes = [util._bus_mode_map[mode] for mode in util.get_bus_modes(self.vm)]
|
busmodes = [util._bus_mode_map[mode] for mode in util.get_bus_modes(self.vm)]
|
||||||
layout.append(
|
layout.append(
|
||||||
[
|
[
|
||||||
psg.ButtonMenu(
|
|
||||||
'Mono',
|
|
||||||
size=(6, 2),
|
|
||||||
menu_def=['', busmono],
|
|
||||||
key=f'BUS {i}||MONO',
|
|
||||||
),
|
|
||||||
*[
|
*[
|
||||||
psg.Button(
|
psg.Button(
|
||||||
param.capitalize(),
|
param.capitalize(),
|
||||||
@ -461,7 +454,7 @@ class Builder:
|
|||||||
for param in params
|
for param in params
|
||||||
],
|
],
|
||||||
psg.ButtonMenu(
|
psg.ButtonMenu(
|
||||||
'Bus Mode',
|
'BUSMODE',
|
||||||
size=(12, 2),
|
size=(12, 2),
|
||||||
menu_def=['', busmodes],
|
menu_def=['', busmodes],
|
||||||
key=f'BUS {i}||MODE',
|
key=f'BUS {i}||MODE',
|
||||||
@ -470,7 +463,7 @@ class Builder:
|
|||||||
)
|
)
|
||||||
|
|
||||||
outputs = []
|
outputs = []
|
||||||
[step(outputs) for step in (add_bus_buttons,)]
|
[step(outputs) for step in (add_strip_outputs,)]
|
||||||
return psg.Frame(
|
return psg.Frame(
|
||||||
self.window.cache['labels'][f'BUS {i}||LABEL'],
|
self.window.cache['labels'][f'BUS {i}||LABEL'],
|
||||||
outputs,
|
outputs,
|
||||||
|
|||||||
@ -1,21 +1,15 @@
|
|||||||
import ctypes as ct
|
import ctypes as ct
|
||||||
import platform
|
import platform
|
||||||
|
import winreg
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .errors import NVDAVMError
|
from .errors import NVDAVMError
|
||||||
|
|
||||||
try:
|
BITS = 64 if ct.sizeof(ct.c_void_p) == 8 else 32
|
||||||
import winreg
|
|
||||||
except ImportError as e:
|
|
||||||
ERR_MSG = 'winreg module not found, only Windows OS supported'
|
|
||||||
raise NVDAVMError(ERR_MSG) from e
|
|
||||||
|
|
||||||
# Defense against edge cases where winreg imports but we're not on Windows
|
|
||||||
if platform.system() != 'Windows':
|
if platform.system() != 'Windows':
|
||||||
raise NVDAVMError('Only Windows OS supported')
|
raise NVDAVMError('Only Windows OS supported')
|
||||||
|
|
||||||
BITS = 64 if ct.sizeof(ct.c_void_p) == 8 else 32
|
|
||||||
|
|
||||||
REG_KEY = '\\'.join(
|
REG_KEY = '\\'.join(
|
||||||
filter(
|
filter(
|
||||||
None,
|
None,
|
||||||
@ -49,4 +43,4 @@ if not controller_path.exists():
|
|||||||
|
|
||||||
DLL_PATH = controller_path / f'x{64 if BITS == 64 else 86}' / 'nvdaControllerClient.dll'
|
DLL_PATH = controller_path / f'x{64 if BITS == 64 else 86}' / 'nvdaControllerClient.dll'
|
||||||
|
|
||||||
libc = ct.WinDLL(str(DLL_PATH))
|
libc = ct.CDLL(str(DLL_PATH))
|
||||||
|
|||||||
@ -1,14 +1,7 @@
|
|||||||
from enum import IntEnum
|
|
||||||
|
|
||||||
from .cdll import libc
|
from .cdll import libc
|
||||||
from .errors import NVDAVMCAPIError
|
from .errors import NVDAVMCAPIError
|
||||||
|
|
||||||
|
|
||||||
class ServerState(IntEnum):
|
|
||||||
RUNNING = 0
|
|
||||||
UNAVAILABLE = 1722
|
|
||||||
|
|
||||||
|
|
||||||
class CBindings:
|
class CBindings:
|
||||||
bind_test_if_running = libc.nvdaController_testIfRunning
|
bind_test_if_running = libc.nvdaController_testIfRunning
|
||||||
bind_speak_text = libc.nvdaController_speakText
|
bind_speak_text = libc.nvdaController_speakText
|
||||||
@ -25,10 +18,7 @@ class CBindings:
|
|||||||
class Nvda(CBindings):
|
class Nvda(CBindings):
|
||||||
@property
|
@property
|
||||||
def is_running(self):
|
def is_running(self):
|
||||||
return (
|
return self.call(self.bind_test_if_running) == 0
|
||||||
self.call(self.bind_test_if_running, ok=(ServerState.RUNNING, ServerState.UNAVAILABLE))
|
|
||||||
== ServerState.RUNNING
|
|
||||||
)
|
|
||||||
|
|
||||||
def speak(self, text):
|
def speak(self, text):
|
||||||
self.call(self.bind_speak_text, text)
|
self.call(self.bind_speak_text, text)
|
||||||
|
|||||||
@ -35,7 +35,7 @@ class Popup:
|
|||||||
self.logger.debug(f'values::{values}')
|
self.logger.debug(f'values::{values}')
|
||||||
if event in (psg.WIN_CLOSED, 'Cancel'):
|
if event in (psg.WIN_CLOSED, 'Cancel'):
|
||||||
break
|
break
|
||||||
match parsed_cmd := self.window.parser.match.parse_string(event):
|
match parsed_cmd := self.window.parser.match.parseString(event):
|
||||||
case [[button], ['FOCUS', 'IN']]:
|
case [[button], ['FOCUS', 'IN']]:
|
||||||
if values['Browse']:
|
if values['Browse']:
|
||||||
filepath = values['Browse']
|
filepath = values['Browse']
|
||||||
@ -105,7 +105,7 @@ class Popup:
|
|||||||
self.logger.debug(f'values::{values}')
|
self.logger.debug(f'values::{values}')
|
||||||
if event in (psg.WIN_CLOSED, 'Cancel'):
|
if event in (psg.WIN_CLOSED, 'Cancel'):
|
||||||
break
|
break
|
||||||
match parsed_cmd := self.window.parser.match.parse_string(event):
|
match parsed_cmd := self.window.parser.match.parseString(event):
|
||||||
case [[button], ['FOCUS', 'IN']]:
|
case [[button], ['FOCUS', 'IN']]:
|
||||||
self.window.nvda.speak(button)
|
self.window.nvda.speak(button)
|
||||||
case [_, ['KEY', 'ENTER']]:
|
case [_, ['KEY', 'ENTER']]:
|
||||||
@ -227,7 +227,7 @@ class Popup:
|
|||||||
self.logger.debug(f'values::{values}')
|
self.logger.debug(f'values::{values}')
|
||||||
if event in (psg.WIN_CLOSED, 'Exit'):
|
if event in (psg.WIN_CLOSED, 'Exit'):
|
||||||
break
|
break
|
||||||
match parsed_cmd := self.window.parser.match.parse_string(event):
|
match parsed_cmd := self.window.parser.match.parseString(event):
|
||||||
case [['ASIO', 'INPUT', 'SPINBOX'], [in_num, channel]]:
|
case [['ASIO', 'INPUT', 'SPINBOX'], [in_num, channel]]:
|
||||||
index = util.get_asio_input_spinbox_index(int(channel), int(in_num[-1]))
|
index = util.get_asio_input_spinbox_index(int(channel), int(in_num[-1]))
|
||||||
val = values[f'ASIO INPUT SPINBOX||{in_num} {channel}']
|
val = values[f'ASIO INPUT SPINBOX||{in_num} {channel}']
|
||||||
@ -310,16 +310,14 @@ class Popup:
|
|||||||
f'<Shift-{event}-{direction}>', f'||KEY SHIFT {direction.upper()} {event_id}'
|
f'<Shift-{event}-{direction}>', f'||KEY SHIFT {direction.upper()} {event_id}'
|
||||||
)
|
)
|
||||||
self.popup[f'COMPRESSOR||SLIDER {param}'].bind(
|
self.popup[f'COMPRESSOR||SLIDER {param}'].bind(
|
||||||
f'<Control-{event}-{direction}>', f'||KEY CTRL {direction.upper()} {event_id}', propagate=False
|
f'<Control-{event}-{direction}>', f'||KEY CTRL {direction.upper()} {event_id}'
|
||||||
)
|
)
|
||||||
if param == 'RELEASE':
|
if param == 'RELEASE':
|
||||||
self.popup[f'COMPRESSOR||SLIDER {param}'].bind(
|
self.popup[f'COMPRESSOR||SLIDER {param}'].bind(
|
||||||
f'<Alt-{event}-{direction}>', f'||KEY ALT {direction.upper()} {event_id}'
|
f'<Alt-{event}-{direction}>', f'||KEY ALT {direction.upper()} {event_id}'
|
||||||
)
|
)
|
||||||
self.popup[f'COMPRESSOR||SLIDER {param}'].bind(
|
self.popup[f'COMPRESSOR||SLIDER {param}'].bind(
|
||||||
f'<Control-Alt-{event}-{direction}>',
|
f'<Control-Alt-{event}-{direction}>', f'||KEY CTRL ALT {direction.upper()} {event_id}'
|
||||||
f'||KEY CTRL ALT {direction.upper()} {event_id}',
|
|
||||||
propagate=False,
|
|
||||||
)
|
)
|
||||||
self.popup[f'COMPRESSOR||SLIDER {param}'].bind('<Control-Shift-KeyPress-R>', '||KEY CTRL SHIFT R')
|
self.popup[f'COMPRESSOR||SLIDER {param}'].bind('<Control-Shift-KeyPress-R>', '||KEY CTRL SHIFT R')
|
||||||
self.popup['MAKEUP'].bind('<FocusIn>', '||FOCUS IN')
|
self.popup['MAKEUP'].bind('<FocusIn>', '||FOCUS IN')
|
||||||
@ -333,7 +331,7 @@ class Popup:
|
|||||||
self.logger.debug(f'values::{values}')
|
self.logger.debug(f'values::{values}')
|
||||||
if event in (psg.WIN_CLOSED, 'Exit'):
|
if event in (psg.WIN_CLOSED, 'Exit'):
|
||||||
break
|
break
|
||||||
match parsed_cmd := self.window.parser.match.parse_string(event):
|
match parsed_cmd := self.window.parser.match.parseString(event):
|
||||||
case [['COMPRESSOR'], ['SLIDER', param]]:
|
case [['COMPRESSOR'], ['SLIDER', param]]:
|
||||||
setattr(self.window.vm.strip[index].comp, param.lower(), values[event])
|
setattr(self.window.vm.strip[index].comp, param.lower(), values[event])
|
||||||
case [['COMPRESSOR'], ['SLIDER', param], ['FOCUS', 'IN']]:
|
case [['COMPRESSOR'], ['SLIDER', param], ['FOCUS', 'IN']]:
|
||||||
@ -644,16 +642,14 @@ class Popup:
|
|||||||
f'<Shift-{event}-{direction}>', f'||KEY SHIFT {direction.upper()} {event_id}'
|
f'<Shift-{event}-{direction}>', f'||KEY SHIFT {direction.upper()} {event_id}'
|
||||||
)
|
)
|
||||||
self.popup[f'GATE||SLIDER {param}'].bind(
|
self.popup[f'GATE||SLIDER {param}'].bind(
|
||||||
f'<Control-{event}-{direction}>', f'||KEY CTRL {direction.upper()} {event_id}', propagate=False
|
f'<Control-{event}-{direction}>', f'||KEY CTRL {direction.upper()} {event_id}'
|
||||||
)
|
)
|
||||||
if param in ('BPSIDECHAIN', 'ATTACK', 'HOLD', 'RELEASE'):
|
if param in ('BPSIDECHAIN', 'ATTACK', 'HOLD', 'RELEASE'):
|
||||||
self.popup[f'GATE||SLIDER {param}'].bind(
|
self.popup[f'GATE||SLIDER {param}'].bind(
|
||||||
f'<Alt-{event}-{direction}>', f'||KEY ALT {direction.upper()} {event_id}'
|
f'<Alt-{event}-{direction}>', f'||KEY ALT {direction.upper()} {event_id}'
|
||||||
)
|
)
|
||||||
self.popup[f'GATE||SLIDER {param}'].bind(
|
self.popup[f'GATE||SLIDER {param}'].bind(
|
||||||
f'<Control-Alt-{event}-{direction}>',
|
f'<Control-Alt-{event}-{direction}>', f'||KEY CTRL ALT {direction.upper()} {event_id}'
|
||||||
f'||KEY CTRL ALT {direction.upper()} {event_id}',
|
|
||||||
propagate=False,
|
|
||||||
)
|
)
|
||||||
self.popup[f'GATE||SLIDER {param}'].bind('<Control-Shift-KeyPress-R>', '||KEY CTRL SHIFT R')
|
self.popup[f'GATE||SLIDER {param}'].bind('<Control-Shift-KeyPress-R>', '||KEY CTRL SHIFT R')
|
||||||
self.popup['Exit'].bind('<FocusIn>', '||FOCUS IN')
|
self.popup['Exit'].bind('<FocusIn>', '||FOCUS IN')
|
||||||
@ -665,7 +661,7 @@ class Popup:
|
|||||||
self.logger.debug(f'values::{values}')
|
self.logger.debug(f'values::{values}')
|
||||||
if event in (psg.WIN_CLOSED, 'Exit'):
|
if event in (psg.WIN_CLOSED, 'Exit'):
|
||||||
break
|
break
|
||||||
match parsed_cmd := self.window.parser.match.parse_string(event):
|
match parsed_cmd := self.window.parser.match.parseString(event):
|
||||||
case [['GATE'], ['SLIDER', param]]:
|
case [['GATE'], ['SLIDER', param]]:
|
||||||
setattr(self.window.vm.strip[index].gate, param.lower(), values[event])
|
setattr(self.window.vm.strip[index].gate, param.lower(), values[event])
|
||||||
case [['GATE'], ['SLIDER', param], ['FOCUS', 'IN']]:
|
case [['GATE'], ['SLIDER', param], ['FOCUS', 'IN']]:
|
||||||
|
|||||||
@ -155,10 +155,6 @@ def get_bus_modes(vm) -> list:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_bus_mono() -> list:
|
|
||||||
return ['off', 'on', 'stereo reverse']
|
|
||||||
|
|
||||||
|
|
||||||
def check_bounds(val, bounds: tuple) -> int | float:
|
def check_bounds(val, bounds: tuple) -> int | float:
|
||||||
lower, upper = bounds
|
lower, upper = bounds
|
||||||
if val > upper:
|
if val > upper:
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import FreeSimpleGUI as psg
|
|||||||
|
|
||||||
from . import configuration, models, util
|
from . import configuration, models, util
|
||||||
from .builder import Builder
|
from .builder import Builder
|
||||||
from .errors import NVDAVMError
|
|
||||||
from .nvda import Nvda
|
from .nvda import Nvda
|
||||||
from .parser import Parser
|
from .parser import Parser
|
||||||
from .popup import Popup
|
from .popup import Popup
|
||||||
@ -26,10 +25,6 @@ class NVDAVMWindow(psg.Window):
|
|||||||
self.kind = self.vm.kind
|
self.kind = self.vm.kind
|
||||||
self.logger = logger.getChild(type(self).__name__)
|
self.logger = logger.getChild(type(self).__name__)
|
||||||
self.logger.debug(f'loaded with theme: {psg.theme()}')
|
self.logger.debug(f'loaded with theme: {psg.theme()}')
|
||||||
self.nvda = Nvda()
|
|
||||||
if not self.nvda.is_running:
|
|
||||||
self.logger.error('NVDA is not running. Exiting...')
|
|
||||||
raise NVDAVMError('NVDA is not running')
|
|
||||||
self.cache = {
|
self.cache = {
|
||||||
'hw_ins': models._make_hardware_ins_cache(self.vm),
|
'hw_ins': models._make_hardware_ins_cache(self.vm),
|
||||||
'hw_outs': models._make_hardware_outs_cache(self.vm),
|
'hw_outs': models._make_hardware_outs_cache(self.vm),
|
||||||
@ -39,6 +34,7 @@ class NVDAVMWindow(psg.Window):
|
|||||||
'asio': models._make_patch_asio_cache(self.vm),
|
'asio': models._make_patch_asio_cache(self.vm),
|
||||||
'insert': models._make_patch_insert_cache(self.vm),
|
'insert': models._make_patch_insert_cache(self.vm),
|
||||||
}
|
}
|
||||||
|
self.nvda = Nvda()
|
||||||
self.parser = Parser()
|
self.parser = Parser()
|
||||||
self.popup = Popup(self)
|
self.popup = Popup(self)
|
||||||
self.builder = Builder(self)
|
self.builder = Builder(self)
|
||||||
@ -62,7 +58,6 @@ class NVDAVMWindow(psg.Window):
|
|||||||
self[f'STRIP {i}||SLIDER LIMIT'].Widget.config(**slider_opts)
|
self[f'STRIP {i}||SLIDER LIMIT'].Widget.config(**slider_opts)
|
||||||
for i in range(self.kind.num_bus):
|
for i in range(self.kind.num_bus):
|
||||||
self[f'BUS {i}||SLIDER GAIN'].Widget.config(**slider_opts)
|
self[f'BUS {i}||SLIDER GAIN'].Widget.config(**slider_opts)
|
||||||
self[f'BUS {i}||MONO'].Widget.config(**buttonmenu_opts)
|
|
||||||
self[f'BUS {i}||MODE'].Widget.config(**buttonmenu_opts)
|
self[f'BUS {i}||MODE'].Widget.config(**buttonmenu_opts)
|
||||||
|
|
||||||
self.register_events()
|
self.register_events()
|
||||||
@ -251,23 +246,18 @@ class NVDAVMWindow(psg.Window):
|
|||||||
f'<Shift-{event}-{direction}>', f'||KEY SHIFT {direction.upper()} {event_id}'
|
f'<Shift-{event}-{direction}>', f'||KEY SHIFT {direction.upper()} {event_id}'
|
||||||
)
|
)
|
||||||
self[f'STRIP {i}||SLIDER {param}'].bind(
|
self[f'STRIP {i}||SLIDER {param}'].bind(
|
||||||
f'<Control-{event}-{direction}>',
|
f'<Control-{event}-{direction}>', f'||KEY CTRL {direction.upper()} {event_id}'
|
||||||
f'||KEY CTRL {direction.upper()} {event_id}',
|
|
||||||
propagate=False,
|
|
||||||
)
|
)
|
||||||
self[f'STRIP {i}||SLIDER {param}'].bind('<Control-Shift-KeyPress-R>', '||KEY CTRL SHIFT R')
|
self[f'STRIP {i}||SLIDER {param}'].bind('<Control-Shift-KeyPress-R>', '||KEY CTRL SHIFT R')
|
||||||
|
|
||||||
# Bus Params
|
# Bus Params
|
||||||
params = ['EQ', 'MUTE']
|
params = ['MONO', 'EQ', 'MUTE']
|
||||||
if self.kind.name == 'basic':
|
if self.kind.name == 'basic':
|
||||||
params.remove('EQ')
|
params.remove('EQ')
|
||||||
for i in range(self.kind.num_bus):
|
for i in range(self.kind.num_bus):
|
||||||
for param in params:
|
for param in params:
|
||||||
self[f'BUS {i}||{param}'].bind('<FocusIn>', '||FOCUS IN')
|
self[f'BUS {i}||{param}'].bind('<FocusIn>', '||FOCUS IN')
|
||||||
self[f'BUS {i}||{param}'].bind('<Return>', '||KEY ENTER')
|
self[f'BUS {i}||{param}'].bind('<Return>', '||KEY ENTER')
|
||||||
self[f'BUS {i}||MONO'].bind('<FocusIn>', '||FOCUS IN')
|
|
||||||
self[f'BUS {i}||MONO'].bind('<space>', '||KEY SPACE', propagate=False)
|
|
||||||
self[f'BUS {i}||MONO'].bind('<Return>', '||KEY ENTER')
|
|
||||||
self[f'BUS {i}||MODE'].bind('<FocusIn>', '||FOCUS IN')
|
self[f'BUS {i}||MODE'].bind('<FocusIn>', '||FOCUS IN')
|
||||||
self[f'BUS {i}||MODE'].bind('<space>', '||KEY SPACE', propagate=False)
|
self[f'BUS {i}||MODE'].bind('<space>', '||KEY SPACE', propagate=False)
|
||||||
self[f'BUS {i}||MODE'].bind('<Return>', '||KEY ENTER', propagate=False)
|
self[f'BUS {i}||MODE'].bind('<Return>', '||KEY ENTER', propagate=False)
|
||||||
@ -286,7 +276,7 @@ class NVDAVMWindow(psg.Window):
|
|||||||
f'<Shift-{event}-{direction}>', f'||KEY SHIFT {direction.upper()} {event_id}'
|
f'<Shift-{event}-{direction}>', f'||KEY SHIFT {direction.upper()} {event_id}'
|
||||||
)
|
)
|
||||||
self[f'BUS {i}||SLIDER GAIN'].bind(
|
self[f'BUS {i}||SLIDER GAIN'].bind(
|
||||||
f'<Control-{event}-{direction}>', f'||KEY CTRL {direction.upper()} {event_id}', propagate=False
|
f'<Control-{event}-{direction}>', f'||KEY CTRL {direction.upper()} {event_id}'
|
||||||
)
|
)
|
||||||
self[f'BUS {i}||SLIDER GAIN'].bind('<Control-Shift-KeyPress-R>', '||KEY CTRL SHIFT R')
|
self[f'BUS {i}||SLIDER GAIN'].bind('<Control-Shift-KeyPress-R>', '||KEY CTRL SHIFT R')
|
||||||
|
|
||||||
@ -316,7 +306,7 @@ class NVDAVMWindow(psg.Window):
|
|||||||
mode = None
|
mode = None
|
||||||
continue
|
continue
|
||||||
|
|
||||||
match parsed_cmd := self.parser.match.parse_string(event):
|
match parsed_cmd := self.parser.match.parseString(event):
|
||||||
# Slider mode
|
# Slider mode
|
||||||
case [['ALT', 'LEFT' | 'RIGHT' | 'UP' | 'DOWN' as direction], ['PRESS' | 'RELEASE' as e]]:
|
case [['ALT', 'LEFT' | 'RIGHT' | 'UP' | 'DOWN' as direction], ['PRESS' | 'RELEASE' as e]]:
|
||||||
if mode:
|
if mode:
|
||||||
@ -982,7 +972,7 @@ class NVDAVMWindow(psg.Window):
|
|||||||
self.nvda.speak,
|
self.nvda.speak,
|
||||||
'on' if val else 'off',
|
'on' if val else 'off',
|
||||||
)
|
)
|
||||||
case 'MUTE':
|
case 'MONO' | 'MUTE':
|
||||||
val = not val
|
val = not val
|
||||||
setattr(self.vm.bus[int(index)], param.lower(), val)
|
setattr(self.vm.bus[int(index)], param.lower(), val)
|
||||||
self.cache['bus'][event] = val
|
self.cache['bus'][event] = val
|
||||||
@ -991,15 +981,6 @@ class NVDAVMWindow(psg.Window):
|
|||||||
self.nvda.speak,
|
self.nvda.speak,
|
||||||
'on' if val else 'off',
|
'on' if val else 'off',
|
||||||
)
|
)
|
||||||
case 'MONO':
|
|
||||||
chosen = values[event]
|
|
||||||
self.vm.bus[int(index)].mono = util.get_bus_mono().index(chosen)
|
|
||||||
self.cache['bus'][event] = chosen
|
|
||||||
self.TKroot.after(
|
|
||||||
200,
|
|
||||||
self.nvda.speak,
|
|
||||||
f'mono {chosen}',
|
|
||||||
)
|
|
||||||
case 'MODE':
|
case 'MODE':
|
||||||
chosen = util._bus_mode_map_reversed[values[event]]
|
chosen = util._bus_mode_map_reversed[values[event]]
|
||||||
setattr(self.vm.bus[int(index)].mode, chosen, True)
|
setattr(self.vm.bus[int(index)].mode, chosen, True)
|
||||||
@ -1015,19 +996,11 @@ class NVDAVMWindow(psg.Window):
|
|||||||
val = self.cache['bus'][f'BUS {index}||{param}']
|
val = self.cache['bus'][f'BUS {index}||{param}']
|
||||||
if param == 'MODE':
|
if param == 'MODE':
|
||||||
self.nvda.speak(f'{label} bus {param} {util._bus_mode_map[val]}')
|
self.nvda.speak(f'{label} bus {param} {util._bus_mode_map[val]}')
|
||||||
elif param == 'MONO':
|
|
||||||
busmode = util.get_bus_mono()[val]
|
|
||||||
if busmode in ('on', 'off'):
|
|
||||||
self.nvda.speak(f'{label} {param} {busmode}')
|
|
||||||
else:
|
|
||||||
self.nvda.speak(f'{label} {busmode}')
|
|
||||||
else:
|
else:
|
||||||
self.nvda.speak(f'{label} {param} {"on" if val else "off"}')
|
self.nvda.speak(f'{label} {param} {"on" if val else "off"}')
|
||||||
case [['BUS', index], [param], ['KEY', 'SPACE' | 'ENTER']]:
|
case [['BUS', index], [param], ['KEY', 'SPACE' | 'ENTER']]:
|
||||||
if param == 'MODE':
|
if param == 'MODE':
|
||||||
util.open_context_menu_for_buttonmenu(self, f'BUS {index}||MODE')
|
util.open_context_menu_for_buttonmenu(self, f'BUS {index}||MODE')
|
||||||
elif param == 'MONO':
|
|
||||||
util.open_context_menu_for_buttonmenu(self, f'BUS {index}||MONO')
|
|
||||||
else:
|
else:
|
||||||
self.find_element_with_focus().click()
|
self.find_element_with_focus().click()
|
||||||
|
|
||||||
|
|||||||
@ -1,302 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Dynamic build system for nvda-voicemeeter.
|
|
||||||
|
|
||||||
This script generates PyInstaller spec files on-the-fly and builds executables
|
|
||||||
without storing intermediate files in the repository.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python tools/dynamic_builder.py # Build all kinds
|
|
||||||
python tools/dynamic_builder.py basic # Build basic only
|
|
||||||
python tools/dynamic_builder.py basic banana # Build specific kinds
|
|
||||||
python tools/dynamic_builder.py all # Build all kinds (explicit)
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
- PDM environment with PyInstaller installed
|
|
||||||
- controllerClient DLL files in controllerClient/{x64,x86}/
|
|
||||||
- nvda_voicemeeter package installed in environment
|
|
||||||
|
|
||||||
Environment Variables:
|
|
||||||
- PDM_BIN: Path to PDM executable (default: 'pdm')
|
|
||||||
|
|
||||||
Exit Codes:
|
|
||||||
- 0: All builds successful
|
|
||||||
- 1: One or more builds failed
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
# Build configuration
|
|
||||||
KINDS = ['basic', 'banana', 'potato']
|
|
||||||
|
|
||||||
# Templates
|
|
||||||
PYTHON_TEMPLATE = """import voicemeeterlib
|
|
||||||
|
|
||||||
import nvda_voicemeeter
|
|
||||||
|
|
||||||
KIND_ID = '{kind}'
|
|
||||||
|
|
||||||
with voicemeeterlib.api(KIND_ID) as vm:
|
|
||||||
with nvda_voicemeeter.draw(KIND_ID, vm) as window:
|
|
||||||
window.run()
|
|
||||||
"""
|
|
||||||
|
|
||||||
SPEC_TEMPLATE = """# -*- mode: python ; coding: utf-8 -*-
|
|
||||||
|
|
||||||
block_cipher = None
|
|
||||||
|
|
||||||
added_files = [
|
|
||||||
( '{controller_x64_path}', 'controllerClient/x64' ),
|
|
||||||
( '{controller_x86_path}', 'controllerClient/x86' ),
|
|
||||||
]
|
|
||||||
|
|
||||||
a = Analysis(
|
|
||||||
['{script_path}'],
|
|
||||||
pathex=[],
|
|
||||||
binaries=[],
|
|
||||||
datas=added_files,
|
|
||||||
hiddenimports=[],
|
|
||||||
hookspath=[],
|
|
||||||
hooksconfig={{}},
|
|
||||||
runtime_hooks=[],
|
|
||||||
excludes=[],
|
|
||||||
win_no_prefer_redirects=False,
|
|
||||||
win_private_assemblies=False,
|
|
||||||
cipher=block_cipher,
|
|
||||||
noarchive=False,
|
|
||||||
)
|
|
||||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
|
||||||
|
|
||||||
exe = EXE(
|
|
||||||
pyz,
|
|
||||||
a.scripts,
|
|
||||||
[],
|
|
||||||
exclude_binaries=True,
|
|
||||||
name='{kind}',
|
|
||||||
debug=False,
|
|
||||||
bootloader_ignore_signals=False,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
console=False,
|
|
||||||
disable_windowed_traceback=False,
|
|
||||||
argv_emulation=False,
|
|
||||||
target_arch=None,
|
|
||||||
codesign_identity=None,
|
|
||||||
entitlements_file=None,
|
|
||||||
)
|
|
||||||
coll = COLLECT(
|
|
||||||
exe,
|
|
||||||
a.binaries,
|
|
||||||
a.zipfiles,
|
|
||||||
a.datas,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
upx_exclude=[],
|
|
||||||
name='{kind}',
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class DynamicBuilder:
|
|
||||||
def __init__(self, base_dir: Path, dist_dir: Path):
|
|
||||||
self.base_dir = base_dir
|
|
||||||
self.dist_dir = dist_dir
|
|
||||||
self.temp_dir = None
|
|
||||||
|
|
||||||
def validate_environment(self) -> bool:
|
|
||||||
"""Validate that all required files and dependencies are present."""
|
|
||||||
|
|
||||||
# Check for controller client DLLs
|
|
||||||
x64_dll = self.base_dir / 'controllerClient' / 'x64' / 'nvdaControllerClient.dll'
|
|
||||||
x86_dll = self.base_dir / 'controllerClient' / 'x86' / 'nvdaControllerClient.dll'
|
|
||||||
|
|
||||||
if not x64_dll.exists():
|
|
||||||
print(f'[ERROR] Missing x64 controller client: {x64_dll}')
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not x86_dll.exists():
|
|
||||||
print(f'[ERROR] Missing x86 controller client: {x86_dll}')
|
|
||||||
return False
|
|
||||||
|
|
||||||
print('[OK] Controller client DLLs found')
|
|
||||||
|
|
||||||
# Check PyInstaller availability
|
|
||||||
try:
|
|
||||||
result = subprocess.run(['pdm', 'list'], capture_output=True, text=True)
|
|
||||||
if 'pyinstaller' not in result.stdout.lower():
|
|
||||||
print('[ERROR] PyInstaller not found in PDM environment')
|
|
||||||
return False
|
|
||||||
print('[OK] PyInstaller available')
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
print('[ERROR] Failed to check PDM environment')
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
# Validate environment first
|
|
||||||
if not self.validate_environment():
|
|
||||||
print('[ERROR] Environment validation failed!')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
self.temp_dir = Path(tempfile.mkdtemp(prefix='nvda_voicemeeter_build_'))
|
|
||||||
print(f'Using temp directory: {self.temp_dir}')
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
if self.temp_dir and self.temp_dir.exists():
|
|
||||||
shutil.rmtree(self.temp_dir)
|
|
||||||
print(f'Cleaned up temp directory: {self.temp_dir}')
|
|
||||||
|
|
||||||
def create_python_file(self, kind: str) -> Path:
|
|
||||||
"""Create a temporary Python launcher file."""
|
|
||||||
content = PYTHON_TEMPLATE.format(kind=kind)
|
|
||||||
|
|
||||||
py_file = self.temp_dir / f'{kind}.py'
|
|
||||||
with open(py_file, 'w') as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
return py_file
|
|
||||||
|
|
||||||
def create_spec_file(self, kind: str, py_file: Path) -> Path:
|
|
||||||
"""Create a temporary PyInstaller spec file."""
|
|
||||||
controller_x64_path = (self.base_dir / 'controllerClient' / 'x64').as_posix()
|
|
||||||
controller_x86_path = (self.base_dir / 'controllerClient' / 'x86').as_posix()
|
|
||||||
|
|
||||||
content = SPEC_TEMPLATE.format(
|
|
||||||
script_path=py_file.as_posix(),
|
|
||||||
controller_x64_path=controller_x64_path,
|
|
||||||
controller_x86_path=controller_x86_path,
|
|
||||||
kind=kind,
|
|
||||||
)
|
|
||||||
|
|
||||||
spec_file = self.temp_dir / f'{kind}.spec'
|
|
||||||
with open(spec_file, 'w') as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
return spec_file
|
|
||||||
|
|
||||||
def build_variant(self, kind: str) -> bool:
|
|
||||||
"""Build a single kind variant."""
|
|
||||||
print(f'Building {kind}...')
|
|
||||||
|
|
||||||
# Validate kind
|
|
||||||
if kind not in KINDS:
|
|
||||||
print(f'[ERROR] Unknown kind: {kind}. Valid kinds: {", ".join(KINDS)}')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Create temporary files
|
|
||||||
py_file = self.create_python_file(kind)
|
|
||||||
spec_file = self.create_spec_file(kind, py_file)
|
|
||||||
|
|
||||||
# Build with PyInstaller
|
|
||||||
dist_path = self.dist_dir / kind
|
|
||||||
pdm_bin = os.getenv('PDM_BIN', 'pdm')
|
|
||||||
cmd = [
|
|
||||||
pdm_bin,
|
|
||||||
'run',
|
|
||||||
'pyinstaller',
|
|
||||||
'--noconfirm',
|
|
||||||
'--distpath',
|
|
||||||
str(dist_path.parent),
|
|
||||||
str(spec_file),
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = subprocess.run(cmd, cwd=self.base_dir, capture_output=True, text=True)
|
|
||||||
if result.returncode == 0:
|
|
||||||
print(f'[OK] Built {kind}')
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print(f'[FAIL] Failed to build {kind}')
|
|
||||||
print(f'Error: {result.stderr}')
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print(f'[ERROR] Exception building {kind}: {e}')
|
|
||||||
return False
|
|
||||||
|
|
||||||
def build_all_kinds(self) -> Dict[str, bool]:
|
|
||||||
"""Build all kind variants."""
|
|
||||||
results = {}
|
|
||||||
|
|
||||||
for kind in KINDS:
|
|
||||||
success = self.build_variant(kind)
|
|
||||||
results[kind] = success
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(description='Dynamic build system for nvda-voicemeeter')
|
|
||||||
parser.add_argument(
|
|
||||||
'kinds',
|
|
||||||
nargs='*',
|
|
||||||
choices=KINDS + ['all'],
|
|
||||||
help='Kinds to build (default: all)',
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--dist-dir',
|
|
||||||
type=Path,
|
|
||||||
default=Path('dist'),
|
|
||||||
help='Distribution output directory',
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if not args.kinds or 'all' in args.kinds:
|
|
||||||
kinds_to_build = KINDS
|
|
||||||
else:
|
|
||||||
kinds_to_build = args.kinds
|
|
||||||
|
|
||||||
base_dir = Path.cwd()
|
|
||||||
args.dist_dir.mkdir(exist_ok=True)
|
|
||||||
|
|
||||||
print(f'Building kinds: {", ".join(kinds_to_build)}')
|
|
||||||
|
|
||||||
all_results = {}
|
|
||||||
|
|
||||||
with DynamicBuilder(base_dir, args.dist_dir) as builder:
|
|
||||||
if 'all' in kinds_to_build or len(kinds_to_build) == len(KINDS):
|
|
||||||
# Build all kinds
|
|
||||||
results = builder.build_all_kinds()
|
|
||||||
all_results.update(results)
|
|
||||||
else:
|
|
||||||
# Build specific kinds
|
|
||||||
for kind in kinds_to_build:
|
|
||||||
success = builder.build_variant(kind)
|
|
||||||
all_results[kind] = success
|
|
||||||
|
|
||||||
# Report results
|
|
||||||
print('\n' + '=' * 50)
|
|
||||||
print('BUILD SUMMARY')
|
|
||||||
print('=' * 50)
|
|
||||||
|
|
||||||
success_count = 0
|
|
||||||
total_count = 0
|
|
||||||
|
|
||||||
for build_name, success in all_results.items():
|
|
||||||
status = '[OK]' if success else '[FAIL]'
|
|
||||||
print(f'{status} {build_name}')
|
|
||||||
if success:
|
|
||||||
success_count += 1
|
|
||||||
total_count += 1
|
|
||||||
|
|
||||||
print(f'\nSuccess: {success_count}/{total_count}')
|
|
||||||
|
|
||||||
if success_count == total_count:
|
|
||||||
print('All builds completed successfully!')
|
|
||||||
sys.exit(0)
|
|
||||||
else:
|
|
||||||
print('Some builds failed!')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@ -1,280 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
PyInstaller spec file generator for nvda-voicemeeter project.
|
|
||||||
|
|
||||||
This script generates .spec files for different Voicemeeter kinds (basic, banana, potato).
|
|
||||||
Based on the existing spec file patterns in the spec/ directory.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python tools/spec_generator.py [kind]
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
kind: The Voicemeeter kind to generate spec for (basic, banana, potato).
|
|
||||||
If not provided, generates all spec files.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
python tools/spec_generator.py basic
|
|
||||||
python tools/spec_generator.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
|
|
||||||
class SpecGenerator:
|
|
||||||
"""Generate PyInstaller spec files for nvda-voicemeeter project."""
|
|
||||||
|
|
||||||
# Supported Voicemeeter kinds
|
|
||||||
KINDS = ['basic', 'banana', 'potato']
|
|
||||||
|
|
||||||
def __init__(self, project_root: Path = None):
|
|
||||||
"""Initialize the spec generator.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
project_root: Path to the project root. If None, uses current directory.
|
|
||||||
"""
|
|
||||||
if project_root is None:
|
|
||||||
# Assume we're running from tools/ directory
|
|
||||||
project_root = Path(__file__).parent.parent
|
|
||||||
|
|
||||||
self.project_root = Path(project_root)
|
|
||||||
self.spec_dir = self.project_root / 'spec'
|
|
||||||
|
|
||||||
# Ensure spec directory exists
|
|
||||||
self.spec_dir.mkdir(exist_ok=True)
|
|
||||||
|
|
||||||
def generate_spec_template(self, kind: str) -> str:
|
|
||||||
"""Generate a PyInstaller spec file template for the given kind.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
kind: The Voicemeeter kind (basic, banana, potato).
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The generated spec file content as a string.
|
|
||||||
"""
|
|
||||||
if kind not in self.KINDS:
|
|
||||||
raise ValueError(f'Unsupported kind: {kind}. Supported kinds: {self.KINDS}')
|
|
||||||
|
|
||||||
template = f"""# -*- mode: python ; coding: utf-8 -*-
|
|
||||||
|
|
||||||
|
|
||||||
block_cipher = None
|
|
||||||
|
|
||||||
added_files = [
|
|
||||||
( '../controllerClient/x64', 'controllerClient/x64' ),
|
|
||||||
( '../controllerClient/x86', 'controllerClient/x86' ),
|
|
||||||
]
|
|
||||||
|
|
||||||
a = Analysis(
|
|
||||||
['{kind}.py'],
|
|
||||||
pathex=[],
|
|
||||||
binaries=[],
|
|
||||||
datas=added_files,
|
|
||||||
hiddenimports=[],
|
|
||||||
hookspath=[],
|
|
||||||
hooksconfig={{}},
|
|
||||||
runtime_hooks=[],
|
|
||||||
excludes=[],
|
|
||||||
win_no_prefer_redirects=False,
|
|
||||||
win_private_assemblies=False,
|
|
||||||
cipher=block_cipher,
|
|
||||||
noarchive=False,
|
|
||||||
)
|
|
||||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
|
||||||
|
|
||||||
exe = EXE(
|
|
||||||
pyz,
|
|
||||||
a.scripts,
|
|
||||||
[],
|
|
||||||
exclude_binaries=True,
|
|
||||||
name='{kind}',
|
|
||||||
debug=False,
|
|
||||||
bootloader_ignore_signals=False,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
console=False,
|
|
||||||
disable_windowed_traceback=False,
|
|
||||||
argv_emulation=False,
|
|
||||||
target_arch=None,
|
|
||||||
codesign_identity=None,
|
|
||||||
entitlements_file=None,
|
|
||||||
)
|
|
||||||
coll = COLLECT(
|
|
||||||
exe,
|
|
||||||
a.binaries,
|
|
||||||
a.zipfiles,
|
|
||||||
a.datas,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
upx_exclude=[],
|
|
||||||
name='{kind}',
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
return template
|
|
||||||
|
|
||||||
def generate_entry_point(self, kind: str) -> str:
|
|
||||||
"""Generate a Python entry point script for the given kind.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
kind: The Voicemeeter kind (basic, banana, potato).
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The generated Python script content as a string.
|
|
||||||
"""
|
|
||||||
if kind not in self.KINDS:
|
|
||||||
raise ValueError(f'Unsupported kind: {kind}. Supported kinds: {self.KINDS}')
|
|
||||||
|
|
||||||
entry_point = f"""import voicemeeterlib
|
|
||||||
|
|
||||||
import nvda_voicemeeter
|
|
||||||
|
|
||||||
KIND_ID = '{kind}'
|
|
||||||
|
|
||||||
with voicemeeterlib.api(KIND_ID) as vm:
|
|
||||||
with nvda_voicemeeter.draw(KIND_ID, vm) as window:
|
|
||||||
window.run()
|
|
||||||
"""
|
|
||||||
return entry_point
|
|
||||||
|
|
||||||
def write_spec_file(self, kind: str, overwrite: bool = False) -> Path:
|
|
||||||
"""Write a spec file for the given kind.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
kind: The Voicemeeter kind (basic, banana, potato).
|
|
||||||
overwrite: Whether to overwrite existing files.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Path to the created spec file.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
FileExistsError: If file exists and overwrite is False.
|
|
||||||
"""
|
|
||||||
spec_file = self.spec_dir / f'{kind}.spec'
|
|
||||||
entry_file = self.spec_dir / f'{kind}.py'
|
|
||||||
|
|
||||||
if spec_file.exists() and not overwrite:
|
|
||||||
raise FileExistsError(f'Spec file already exists: {spec_file}')
|
|
||||||
|
|
||||||
if entry_file.exists() and not overwrite:
|
|
||||||
raise FileExistsError(f'Entry point file already exists: {entry_file}')
|
|
||||||
|
|
||||||
# Write spec file
|
|
||||||
spec_content = self.generate_spec_template(kind)
|
|
||||||
spec_file.write_text(spec_content, encoding='utf-8')
|
|
||||||
print(f'Generated spec file: {spec_file}')
|
|
||||||
|
|
||||||
# Write entry point script
|
|
||||||
entry_content = self.generate_entry_point(kind)
|
|
||||||
entry_file.write_text(entry_content, encoding='utf-8')
|
|
||||||
print(f'Generated entry point: {entry_file}')
|
|
||||||
|
|
||||||
return spec_file
|
|
||||||
|
|
||||||
def generate_all_specs(self, overwrite: bool = False) -> List[Path]:
|
|
||||||
"""Generate spec files for all supported kinds.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
overwrite: Whether to overwrite existing files.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of paths to the created spec files.
|
|
||||||
"""
|
|
||||||
spec_files = []
|
|
||||||
for kind in self.KINDS:
|
|
||||||
try:
|
|
||||||
spec_file = self.write_spec_file(kind, overwrite=overwrite)
|
|
||||||
spec_files.append(spec_file)
|
|
||||||
except FileExistsError as e:
|
|
||||||
print(f'Skipped {kind}: {e}')
|
|
||||||
continue
|
|
||||||
|
|
||||||
return spec_files
|
|
||||||
|
|
||||||
def list_existing_specs(self) -> List[str]:
|
|
||||||
"""List existing spec files in the spec directory.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of existing spec file kinds.
|
|
||||||
"""
|
|
||||||
existing = []
|
|
||||||
for kind in self.KINDS:
|
|
||||||
spec_file = self.spec_dir / f'{kind}.spec'
|
|
||||||
if spec_file.exists():
|
|
||||||
existing.append(kind)
|
|
||||||
|
|
||||||
return existing
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Main entry point for the spec generator."""
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description='Generate PyInstaller spec files for nvda-voicemeeter project',
|
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
||||||
epilog="""
|
|
||||||
Examples:
|
|
||||||
python tools/spec_generator.py basic # Generate basic.spec
|
|
||||||
python tools/spec_generator.py # Generate all spec files
|
|
||||||
python tools/spec_generator.py --list # List existing spec files
|
|
||||||
python tools/spec_generator.py --force # Overwrite existing files
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'kind',
|
|
||||||
nargs='?',
|
|
||||||
choices=SpecGenerator.KINDS,
|
|
||||||
help=f'Voicemeeter kind to generate spec for ({", ".join(SpecGenerator.KINDS)})',
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument('--force', '-f', action='store_true', help='Overwrite existing spec files')
|
|
||||||
|
|
||||||
parser.add_argument('--list', '-l', action='store_true', help='List existing spec files')
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'--project-root', type=Path, help='Path to project root (default: detect from script location)'
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Initialize generator
|
|
||||||
generator = SpecGenerator(project_root=args.project_root)
|
|
||||||
|
|
||||||
# List existing files if requested
|
|
||||||
if args.list:
|
|
||||||
existing = generator.list_existing_specs()
|
|
||||||
if existing:
|
|
||||||
print('Existing spec files:')
|
|
||||||
for kind in existing:
|
|
||||||
spec_file = generator.spec_dir / f'{kind}.spec'
|
|
||||||
print(f' {kind}: {spec_file}')
|
|
||||||
else:
|
|
||||||
print('No existing spec files found.')
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
if args.kind:
|
|
||||||
# Generate specific kind
|
|
||||||
generator.write_spec_file(args.kind, overwrite=args.force)
|
|
||||||
print(f'Successfully generated spec for: {args.kind}')
|
|
||||||
else:
|
|
||||||
# Generate all kinds
|
|
||||||
spec_files = generator.generate_all_specs(overwrite=args.force)
|
|
||||||
if spec_files:
|
|
||||||
print(f'Successfully generated {len(spec_files)} spec files.')
|
|
||||||
else:
|
|
||||||
print('No spec files were generated (use --force to overwrite existing files).')
|
|
||||||
|
|
||||||
except ValueError as e:
|
|
||||||
print(f'Error: {e}')
|
|
||||||
return 1
|
|
||||||
except FileExistsError as e:
|
|
||||||
print(f'Error: {e}')
|
|
||||||
print('Use --force to overwrite existing files.')
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
exit(main())
|
|
||||||
Loading…
x
Reference in New Issue
Block a user