diff --git a/.github/actions/configure-appinstaller/action.yml b/.github/actions/configure-appinstaller/action.yml
new file mode 100644
index 000000000..4f02855cf
--- /dev/null
+++ b/.github/actions/configure-appinstaller/action.yml
@@ -0,0 +1,26 @@
+name: Configure AppInstaller
+inputs:
+ manifest:
+ required: true
+ bundle:
+ required: true
+ version:
+ required: true
+ branch:
+ required: true
+runs:
+ using: 'composite'
+ steps:
+ - name: Configure manifest
+ shell: powershell
+ run: |
+ $manifestPath = "${{ inputs.manifest }}"
+
+ $manifest = [xml](Get-Content $manifestPath)
+ $manifest.AppInstaller.Uri = "https://install.eartrumpet.app/${{inputs.branch}}/EarTrumpet.Package.appinstaller"
+ $manifest.AppInstaller.Version = [string](Get-Content "${{ inputs.version }}")
+
+ $bundleName = "${{inputs.bundle}}".Replace("*", $manifest.AppInstaller.Version)
+ $manifest.AppInstaller.MainBundle.Uri = "https://install.eartrumpet.app/${{inputs.branch}}/$bundleName"
+ $manifest.AppInstaller.MainBundle.Version = [string](Get-Content "${{ inputs.version }}")
+ $manifest.Save($manifestPath)
diff --git a/.github/actions/configure-manifest/action.yml b/.github/actions/configure-manifest/action.yml
new file mode 100644
index 000000000..16b40f0dd
--- /dev/null
+++ b/.github/actions/configure-manifest/action.yml
@@ -0,0 +1,36 @@
+name: Configure Manifest
+inputs:
+ manifest:
+ required: true
+ store-association:
+ required: true
+ publisher:
+ required: true
+ version:
+ required: true
+ prefix:
+ required: true
+ branch:
+ required: true
+runs:
+ using: 'composite'
+ steps:
+ - name: Configure manifest
+ shell: powershell
+ run: |
+ $manifestPath = "${{ inputs.manifest }}"
+ $associationPath = "${{ inputs.store-association }}"
+
+ $manifest = [xml](Get-Content $manifestPath)
+ $association = [xml](Get-Content $associationPath)
+
+ $manifest.Package.Identity.Publisher = "${{ inputs.publisher }}"
+ $manifest.Package.Identity.Version = [string](Get-Content "${{ inputs.version }}")
+ if ("${{ inputs.branch }}" -ne "master") {
+ $manifest.Package.Properties.DisplayName = $manifest.Package.Properties.DisplayName + " (${{ inputs.branch }})"
+ $manifest.Package.Applications.Application.VisualElements.DisplayName = $manifest.Package.Properties.DisplayName
+ $manifest.Package.Applications.Application.Extensions.Extension.StartupTask.DisplayName = $manifest.Package.Properties.DisplayName
+ $association.StoreAssociation.ProductReservedInfo.ReservedNames.ReservedName = $manifest.Package.Properties.DisplayName
+ }
+ $manifest.Save($manifestPath)
+ $association.Save($associationPath)
diff --git a/.github/actions/configure-nuspec/action.yml b/.github/actions/configure-nuspec/action.yml
new file mode 100644
index 000000000..ac901d48a
--- /dev/null
+++ b/.github/actions/configure-nuspec/action.yml
@@ -0,0 +1,18 @@
+name: Configure Nuspec
+inputs:
+ nuspec:
+ required: true
+ version:
+ required: true
+runs:
+ using: 'composite'
+ steps:
+ - name: Configure Nuspec
+ shell: pwsh
+ run: |
+ $Version = [Version](Get-Content "${{ inputs.version }}")
+ $NuspecPath = "${{ inputs.nuspec }}"
+
+ $nuspec = [xml](Get-Content -Path $NuspecPath)
+ $nuspec.package.metadata.version = $Version
+ $nuspec.Save($NuspecPath)
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 5196d009f..19550daae 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,473 +1,601 @@
-name: EarTrumpet-CI
+name: EarTrumpet
on:
push:
branches:
- master
- dev
- rafael/*
- - dave/*
- - david/*
paths-ignore:
- - "**/*.md"
- - ".github/ISSUE_TEMPLATE/*"
- - ".github/workflows/sponsors.yml"
- - "Graphics/*"
- pull_request:
- branches:
- - dev
- paths-ignore:
- - "**/*.md"
- - crowdin.yml
+ - '**/*.md'
+ - '.github/ISSUE_TEMPLATE/*'
+ - '.github/workflows/sponsors.yml'
+ - '.github/workflows/translators.yml'
+ - 'Graphics/*'
+
env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
- BUILD_CONFIGURATION: Release
- BUILD_PLATFORM: x86
- ARTIFACTS_BASE: '${{ github.workspace }}\artifacts'
+ GHA_HASH_PATHS: |
+ EarTrumpet/**
+ !EarTrumpet/**/bin/**
+ !EarTrumpet/**/obj/**
+ !EarTrumpet/**/.vs/**
+ EarTrumpet.Package/**
+ !EarTrumpet.Package/**/bin/**
+ !EarTrumpet.Package/**/obj/**
+ !EarTrumpet/**/.vs/**
+ Packaging/**
jobs:
- build:
- runs-on: windows-2019
- strategy:
- matrix:
- channel: [AppInstaller, Store, Chocolatey]
- include:
- - channel: AppInstaller
- publisher:
- "CN=File-New-Project, O=File-New-Project, L=Purcellville, S=Virginia, C=US"
- - channel: Store
- publisher: CN=6099D0EF-9374-47ED-BDFE-A82136831235
- - channel: Chocolatey
- publisher:
- "CN=File-New-Project, O=File-New-Project, L=Purcellville, S=Virginia, C=US"
- max-parallel: 3
+ #
+ # Base build
+ #
+
+ base:
+ name: 🔨 Build Base
+ runs-on: windows-latest
+
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
+
+ - name: Check for existing base
+ uses: actions/cache/restore@v4
+ id: base-exists
+ with:
+ path: .artifacts
+ key: base-${{ hashFiles(env.GHA_HASH_PATHS) }}
+ lookup-only: true
- name: Fetch all history for all tags and branches
run: git fetch --prune --unshallow
+ if: steps.base-exists.outputs.cache-hit != 'true'
- name: Install GitVersion
- uses: gittools/actions/gitversion/setup@v0.9.15
+ uses: gittools/actions/gitversion/setup@v1.2.0
with:
- versionSpec: "5.x"
+ versionSpec: '5.x'
includePrerelease: false
+ if: steps.base-exists.outputs.cache-hit != 'true'
- name: Use GitVersion
id: gitversion
- uses: gittools/actions/gitversion/execute@v0.9.15
+ uses: gittools/actions/gitversion/execute@v1.2.0
+ if: steps.base-exists.outputs.cache-hit != 'true'
- - name: Create artifact layout
- shell: powershell
+ - name: Create artifacts folder
+ shell: pwsh
run: |
- $ErrorActionPreference = 'Ignore'
- New-Item -ItemType Directory "$env:ARTIFACTS_BASE"
- New-Item -ItemType Directory "$env:ARTIFACTS_BASE\appxupload"
- New-Item -ItemType Directory "$env:ARTIFACTS_BASE\sideload"
- New-Item -ItemType Directory "$env:ARTIFACTS_BASE\chocolatey"
- New-Item -ItemType Directory "$env:ARTIFACTS_BASE\loose"
- New-Item -ItemType Directory "$env:ARTIFACTS_BASE\metadata"
-
- - name: Generate versioning metadata
- shell: powershell
+ New-Item -ItemType Directory -Force -Path .artifacts/
+ if: steps.base-exists.outputs.cache-hit != 'true'
+
+ - name: Generate versioning artifact
+ shell: pwsh
run: |
- Set-Content "$env:ARTIFACTS_BASE\metadata\semver.txt" "${{ steps.gitversion.outputs.semVer }}"
- Set-Content "$env:ARTIFACTS_BASE\metadata\branch.txt" "${{ steps.gitversion.outputs.branchName }}"
- Set-Content "$env:ARTIFACTS_BASE\metadata\commits.txt" "${{ steps.gitversion.outputs.commitsSinceVersionSource }}"
-
- if("${{ matrix.channel }}" -eq "Store") {
- $Version = "${{ steps.gitversion.outputs.majorMinorPatch }}.0"
- } else {
- $Version = "${{ steps.gitversion.outputs.majorMinorPatch }}.${{ steps.gitversion.outputs.commitsSinceVersionSource }}"
- }
+ "${{ steps.gitversion.outputs.majorMinorPatch }}.${{ steps.gitversion.outputs.commitsSinceVersionSource }}" |
+ Out-File .artifacts/version_4.txt
+ "${{ steps.gitversion.outputs.majorMinorPatch }}.0" |
+ Out-File .artifacts/version_3.txt
+ if: steps.base-exists.outputs.cache-hit != 'true'
+
+ - name: Restore packages
+ run: dotnet restore EarTrumpet.sln
+ shell: cmd
+ if: steps.base-exists.outputs.cache-hit != 'true'
- Set-Content "$env:ARTIFACTS_BASE\metadata\${{ matrix.channel }}.version.txt" $Version
+ - name: Build EarTrumpet
+ shell: cmd
+ run: >
+ dotnet publish
+ /p:PublishProfile=x86
+ /p:Platform=x86
+ EarTrumpet\EarTrumpet.csproj
+
+ dotnet publish
+ /p:PublishProfile=x64
+ /p:Platform=x64
+ EarTrumpet\EarTrumpet.csproj
+
+ dotnet publish
+ /p:PublishProfile=arm64
+ /p:Platform=arm64
+ EarTrumpet\EarTrumpet.csproj
+ if: steps.base-exists.outputs.cache-hit != 'true'
+
+ - name: Set Bugsnag API key
+ shell: pwsh
+ run: |
+ Get-ChildItem .artifacts\base\**\app.config | ForEach-Object {
+ $cfg = Get-Content $_
+ $cfg | ForEach-Object { $_.Replace("{bugsnag.apikey}", "${{ secrets.bugsnag_api_key }}") } | Set-Content $_
+ }
+ if: steps.base-exists.outputs.cache-hit != 'true'
- - name: Install NuGet
- uses: NuGet/setup-nuget@v1
+ - name: Sign all files
+ uses: azure/trusted-signing-action@v0.5.0
+ with:
+ azure-tenant-id: ${{ secrets.azure_tenant_id }}
+ azure-client-id: ${{ secrets.azure_client_id }}
+ azure-client-secret: ${{ secrets.azure_client_secret }}
+ endpoint: https://wus2.codesigning.azure.net/
+ trusted-signing-account-name: main
+ certificate-profile-name: profileTWN
+ files-catalog: packaging\catalog.txt
+ file-digest: SHA256
+ timestamp-rfc3161: http://timestamp.acs.microsoft.com
+ timestamp-digest: SHA256
+ if: steps.base-exists.outputs.cache-hit != 'true'
+
+ - name: Add to cache
+ uses: actions/cache/save@v4
with:
- nuget-version: latest
+ key: base-${{ hashFiles(env.GHA_HASH_PATHS) }}
+ path: .artifacts
+ if: steps.base-exists.outputs.cache-hit != 'true'
- - name: Restore NuGet Packages
- run: nuget restore EarTrumpet.vs15.sln
+ #
+ # Packaging: AppInstaller
+ #
- - name: Set Bugsnag API Key
- shell: powershell
- run: |
- $cfg = Get-Content ".\EarTrumpet\app.config"
- $cfg | ForEach-Object { $_.Replace("{bugsnag.apikey}", "${{ secrets.bugsnag_api_key }}") } | Set-Content ".\EarTrumpet\app.config"
+ appinstaller:
+ name: 📦 Package for AppInstaller
+ runs-on: windows-latest
+ needs: base
- - name: Adjust manifest and store association
- if: matrix.channel == 'Store' || matrix.channel == 'AppInstaller'
- shell: powershell
- run: |
- $manifestPath = ".\EarTrumpet.Package\Package.appxmanifest"
- $storeAssociationPath = ".\EarTrumpet.Package\Package.StoreAssociation.xml"
-
- $manifest = [xml](Get-Content $manifestPath)
- $manifest.Package.Identity.Publisher = "${{ matrix.publisher }}"
- if("${{ matrix.channel }}" -eq "AppInstaller") {
- if("${{ steps.gitversion.outputs.branchName }}" -eq "master") {
- $manifest.Package.Properties.DisplayName = "EarTrumpet"
- $manifest.Package.Applications.Application.VisualElements.DisplayName = "EarTrumpet"
- } else {
- $manifest.Package.Properties.DisplayName = $manifest.Package.Properties.DisplayName + " (${{ steps.gitversion.outputs.branchName }})"
- $manifest.Package.Applications.Application.VisualElements.DisplayName = "EarTrumpet (${{ steps.gitversion.outputs.branchName }})"
- }
- }
- $manifest.Save($manifestPath)
-
- $storeAssociation = [xml](Get-Content $storeAssociationPath)
- $storeAssociation.StoreAssociation.Publisher = "${{ matrix.publisher }}"
- if("${{ matrix.channel }}" -eq "AppInstaller") {
- if("${{ steps.gitversion.outputs.branchName }}" -eq "master") {
- $storeAssociation.StoreAssociation.ProductReservedInfo.ReservedNames.ReservedName = "EarTrumpet"
- } else {
- $storeAssociation.StoreAssociation.ProductReservedInfo.ReservedNames.ReservedName = "EarTrumpet (${{ steps.gitversion.outputs.branchName }})"
- }
- }
- $storeAssociation.Save($storeAssociationPath)
+ env:
+ channel: 'AppInstaller'
+ publisher: 'CN=File-New-Project, O=File-New-Project, L=Purcellville, S=Virginia, C=US'
- - name: Set up MSBuild
- uses: microsoft/setup-msbuild@v1
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
- - name: Build EarTrumpet appxupload package
- if: matrix.channel == 'Store'
- shell: cmd
- run:
- msbuild EarTrumpet.Package/EarTrumpet.Package.wapproj
- /p:Platform=%BUILD_PLATFORM% /p:Configuration=%BUILD_CONFIGURATION%
- /p:AppxBundle=Always /p:Channel=${{ matrix.channel }}
- /p:AppxPackageDir=%ARTIFACTS_BASE%\appxupload\
- /p:AppxPackageSigningEnabled=false /p:UapAppxPackageBuildMode=CI
- -maxcpucount
-
- - name: Upload appxupload artifact
- if: matrix.channel == 'Store' && github.event_name != 'pull_request'
- uses: actions/upload-artifact@v3
+ - name: Fetch all history for all tags and branches
+ run: git fetch --prune --unshallow
+
+ - name: Restore base
+ uses: actions/cache/restore@v4
with:
- name: appxupload
- path: artifacts/appxupload
+ path: .artifacts
+ key: base-${{ hashFiles(env.GHA_HASH_PATHS) }}
+ fail-on-cache-miss: true
- - name: Build EarTrumpet
- if: matrix.channel == 'Chocolatey'
- shell: cmd
- run:
- msbuild EarTrumpet/EarTrumpet.csproj /p:Platform=%BUILD_PLATFORM%
- /p:Configuration=%BUILD_CONFIGURATION% /p:Channel=${{ matrix.channel
- }} /p:OutputPath=%ARTIFACTS_BASE%\loose\ -maxcpucount
-
- - name: Upload loose artifacts
- if:
- matrix.channel == 'Chocolatey' && github.event_name != 'pull_request'
- uses: actions/upload-artifact@v3
+ - name: Configure manifest
+ uses: ./.github/actions/configure-manifest
with:
- name: loose
- path: artifacts/loose
+ manifest: EarTrumpet.Package/Package.appxmanifest
+ store-association: EarTrumpet.Package/Package.StoreAssociation.xml
+ publisher: ${{ env.publisher }}
+ branch: ${{ github.ref_name }}
+ prefix: 'EarTrumpet'
+ version: .artifacts/version_4.txt
- - name: Build EarTrumpet appinstaller/sideload package
- if: matrix.channel == 'AppInstaller' || matrix.channel == 'Chocolatey'
- shell: cmd
- run:
- msbuild EarTrumpet.Package/EarTrumpet.Package.wapproj
- /p:Platform=%BUILD_PLATFORM% /p:Configuration=%BUILD_CONFIGURATION%
- /p:AppxBundle=Always /p:Channel=${{ matrix.channel }}
- /p:AppxPackageDir=%ARTIFACTS_BASE%\sideload\
+ - name: Set up MSBuild
+ uses: microsoft/setup-msbuild@v2
+ with:
+ msbuild-architecture: x64
+
+ - name: Restore packages
+ shell: pwsh
+ run: >
+ dotnet restore
+ EarTrumpet.sln
+
+ - name: Create msixbundle
+ shell: pwsh
+ run: >
+ msbuild
+ /p:AppxBundle=Always
/p:AppxPackageSigningEnabled=false
- /p:UapAppxPackageBuildMode=SideloadOnly
+ /p:AppxPackageDir=..\.artifacts\sideload\
+ /p:AppxPackageTestDir=..\.artifacts\sideload\
+ /p:AppInstallerUri="https://install.eartrumpet.app"
+ /p:Configuration=Release
+ /p:Channel=AppInstaller
/p:GenerateAppInstallerFile=true
- /p:AppxPackageTestDir=%ARTIFACTS_BASE%\sideload\
- /p:AppInstallerUri="https://install.eartrumpet.app" -maxcpucount
-
- - name: Adjust appinstaller manifest
- if:
- matrix.channel == 'AppInstaller' && github.event_name !=
- 'pull_request'
- shell: powershell
- run: |
- $manifestPath = "$env:ARTIFACTS_BASE/sideload/EarTrumpet.Package.appinstaller"
- $manifest = [xml](Get-Content $manifestPath)
- $manifest.AppInstaller.Uri = "https://install.eartrumpet.app/${{ steps.gitversion.outputs.branchName }}/EarTrumpet.Package.appinstaller"
- $manifest.AppInstaller.MainBundle.Uri = "https://install.eartrumpet.app/${{ steps.gitversion.outputs.branchName }}/EarTrumpet.Package_${{ steps.gitversion.outputs.majorMinorPatch }}.${{ steps.gitversion.outputs.commitsSinceVersionSource }}_x86.appxbundle"
- $manifest.AppInstaller.MainBundle.Publisher = "${{ matrix.publisher }}"
-
- $fragment = [xml]''
- $manifest.AppInstaller.InsertAfter($manifest.ImportNode($fragment.AppInstaller.Dependencies, $true), $manifest.AppInstaller.MainBundle)
-
- $manifest.Save($manifestPath)
-
- - name: Upload appinstaller/sideload package artifacts
- if:
- matrix.channel == 'AppInstaller' && github.event_name !=
- 'pull_request'
- uses: actions/upload-artifact@v3
- with:
- name: sideload
- path: artifacts/sideload
+ /p:Platform=x86
+ /p:UapAppxPackageBuildMode=SideloadOnly
+ /p:WapAlwaysBuildDependentProjects=false
+ "/p:AppxBundlePlatforms=x86|x64|arm64"
+ EarTrumpet.Package/EarTrumpet.Package.wapproj
- - name: Fix up PDPs
- if: matrix.channel == 'Store' && github.event_name != 'pull_request'
+ - name: Stage AppInstaller manifest
shell: pwsh
- run: |
- Set-Location packaging\MicrosoftStore\PDPs
- Get-ChildItem | ForEach-Object {
- $locale = $_.Name
- $pdp = [xml](Get-Content "$locale\pdp.xml")
- $pdp.ProductDescription.language = $locale
- $pdp.ProductDescription.lang = $locale
- $pdp.ProductDescription
- $pdp.Save((Resolve-Path "$locale\pdp.xml"))
- }
+ run: >
+ Copy-Item Packaging\AppInstaller\EarTrumpet.Package.appinstaller .artifacts\sideload\
+ -Force
- - name: Stage msix packaging metadata
- if: matrix.channel == 'Store' && github.event_name != 'pull_request'
- shell: powershell
- run: |
- Copy-Item packaging\ -Recurse "$env:ARTIFACTS_BASE\metadata\"
+ - name: Configure AppInstaller manifest
+ uses: ./.github/actions/configure-appinstaller
+ with:
+ manifest: .artifacts\sideload\EarTrumpet.Package.appinstaller
+ bundle: 'EarTrumpet.Package_*_x86_x64_arm64.msixbundle'
+ version: .artifacts/version_4.txt
+ branch: ${{ github.ref_name }}
- - name: Upload metadata artifacts
- uses: actions/upload-artifact@v3
+ - name: Sign package
+ uses: azure/trusted-signing-action@v0.5.0
+ with:
+ azure-tenant-id: ${{ secrets.azure_tenant_id }}
+ azure-client-id: ${{ secrets.azure_client_id }}
+ azure-client-secret: ${{ secrets.azure_client_secret }}
+ endpoint: https://wus2.codesigning.azure.net/
+ trusted-signing-account-name: main
+ certificate-profile-name: profileTWN
+ files-folder: .artifacts/sideload/
+ files-folder-filter: msixbundle
+ file-digest: SHA256
+ timestamp-rfc3161: http://timestamp.acs.microsoft.com
+ timestamp-digest: SHA256
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
with:
- name: metadata
- path: artifacts/metadata
+ name: AppInstaller
+ path: |
+ .artifacts/sideload/EarTrumpet.Package_*_x86_x64_arm64.msixbundle
+ .artifacts/sideload/EarTrumpet.Package.appinstaller
- - name: Stage chocolatey packaging metadata
- if:
- matrix.channel == 'Chocolatey' && github.event_name != 'pull_request'
- shell: powershell
- run: |
- Copy-Item .chocolatey\* -Recurse "$env:ARTIFACTS_BASE\chocolatey\"
+ #
+ # Packaging: Chocolatey
+ #
+
+ chocolatey:
+ name: 📦 Package for Chocolatey
+ runs-on: windows-latest
+ needs: base
- - name: Upload chocolatey artifacts
- uses: actions/upload-artifact@v3
- with:
- name: chocolatey
- path: artifacts/chocolatey
- release:
- needs: build
- runs-on: windows-2019
- if: github.event_name != 'pull_request'
- strategy:
- matrix:
- channel: [AppInstaller, Store, Chocolatey]
- max-parallel: 3
env:
- AZURE_TENANT_ID: ${{ secrets.azure_tenant_id }}
- AZURE_CLIENT_ID: ${{ secrets.azure_client_id }}
- AZURE_CLIENT_SECRET: ${{ secrets.azure_client_secret }}
+ channel: 'Chocolatey'
+ publisher: 'CN=File-New-Project, O=File-New-Project, L=Purcellville, S=Virginia, C=US'
+
steps:
- - name: Download artifacts
- uses: actions/download-artifact@v3
- with:
- path: artifacts
+ - name: Checkout
+ uses: actions/checkout@v4
- - name: Install NuGet
- uses: NuGet/setup-nuget@v1
+ - name: Restore base
+ uses: actions/cache/restore@v4
with:
- nuget-version: latest
+ path: .artifacts
+ key: base-${{ hashFiles(env.GHA_HASH_PATHS) }}
+ fail-on-cache-miss: true
- - name: Install Build Tools
- run: nuget install Microsoft.Windows.SDK.BuildTools
+ - name: Adjust nuspec
+ uses: ./.github/actions/configure-nuspec
+ with:
+ nuspec: Packaging\Chocolatey\eartrumpet.nuspec
+ version: .artifacts/version_4.txt
- - name: Install Azure Codesigning
+ - name: Stage packaging
shell: pwsh
- env:
- ACS_PACKAGE_URI: ${{ secrets.acs_package_uri }}
- ACS_METADATA_URI: ${{ secrets.acs_metadata_uri }}
- GITHUB_RUN_ID: ${{ github.run_id }}
run: |
- Invoke-WebRequest $env:ACS_PACKAGE_URI -UseBasicParsing -OutFile package.zip
- Expand-Archive package.zip -DestinationPath acs
- Invoke-WebRequest $env:ACS_METADATA_URI -UseBasicParsing -OutFile acs\metadata.json
+ Copy-Item Packaging\Chocolatey -Recurse .artifacts
- - name: Sign and repackage Store artifacts
- if: matrix.channel == 'Store'
+ - name: Create release archive
shell: pwsh
- run: |
- $MetadataPath = "$env:ARTIFACTS_BASE\metadata"
- $Version = [Version](Get-Content "$MetadataPath\Store.version.txt")
- $AppxUploadPath = "$env:ARTIFACTS_BASE\AppxUpload"
- $BundleFilename = "EarTrumpet.Package_${Version}_x86.appxbundle"
- $SymbolsBundleFilename = "EarTrumpet.Package_${Version}_x86.appxsym"
- $AppxFilename = "EarTrumpet.Package_${Version}_x86.appx"
- $StoreBundleFilename = "EarTrumpet.Package_${Version}_x86_bundle.appxupload"
-
- ### Expand bundle and appx package within
- $ExtractedPath = "$env:ARTIFACTS_BASE\Extracted"
- Expand-Archive "$AppxUploadPath\$StoreBundleFilename" "$ExtractedPath\AppxUpload"
- Expand-Archive "$ExtractedPath\AppxUpload\$SymbolsBundleFilename" "$ExtractedPath\Symbols"
- Expand-Archive "$ExtractedPath\AppxUpload\$BundleFilename" "$ExtractedPath\Bundle"
- Expand-Archive "$ExtractedPath\Bundle\$AppxFilename" "$ExtractedPath\Package"
-
- ### Place symbols next to executable image
- Copy-Item "$ExtractedPath\Symbols\EarTrumpet.pdb" "$ExtractedPath\Package\EarTrumpet\"
-
- ### Sign executable image
- & (Resolve-Path "Microsoft.Windows.SDK.BuildTools.*\bin\*\x64\signtool.exe") sign /v /fd SHA256 /td SHA256 /tr http://timestamp.acs.microsoft.com /dlib "acs\bin\x64\Azure.CodeSigning.Dlib.dll" /dmdf "acs\metadata.json" "$ExtractedPath\Package\EarTrumpet\EarTrumpet.exe"
-
- $SignedPath = "$env:ARTIFACTS_BASE\Signed"
- New-Item -ItemType Directory "$SignedPath"
- New-Item -ItemType Directory "$SignedPath\Package"
- New-Item -ItemType Directory "$SignedPath\Bundle"
- New-Item -ItemType Directory "$SignedPath\AppxUpload"
-
- ### Repackage appx package
- & "C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x86\makeappx.exe" pack /l /h sha256 /d "$ExtractedPath\Package" /o /p "$SignedPath\Package\$AppxFilename"
-
- Set-ItemProperty "$SignedPath\Package\$AppxFilename" -Name IsReadOnly -Value $true
- Copy-Item "$ExtractedPath\Bundle\*.appx" "$SignedPath\Package\" -ErrorAction Ignore
- Set-ItemProperty "$SignedPath\Package\$AppxFilename" -Name IsReadOnly -Value $false
-
- ### Repackage appx bundle
- & "C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x86\makeappx.exe" bundle /d "$SignedPath\Package" /bv $Version /o /p "$SignedPath\Bundle\$BundleFilename"
-
- ### Repackage appxupload
- Copy-Item "$ExtractedPath\AppxUpload\$SymbolsBundleFilename" "$SignedPath\Bundle"
- Compress-Archive -Path "$SignedPath\Bundle\*" -DestinationPath "$SignedPath\AppxUpload\$StoreBundleFilename" -CompressionLevel Optimal
-
- - name: Sign AppInstaller artifacts
- if: matrix.channel == 'AppInstaller'
+ run: >
+ Compress-Archive
+ -Path .artifacts\base\x86\**
+ -DestinationPath .artifacts\chocolatey\tools\release.zip
+ -CompressionLevel Optimal
+
+ - name: Create package
shell: pwsh
- run: |
- $MetadataPath = "$env:ARTIFACTS_BASE\metadata"
- $Version = [Version](Get-Content "$MetadataPath\AppInstaller.version.txt")
- $Branch = Get-Content "$MetadataPath\branch.txt"
- $Semver= Get-Content "$MetadataPath\semver.txt"
- $BundleFilename = "EarTrumpet.Package_${Version}_x86.appxbundle"
- $SymbolsBundleFilename = "EarTrumpet.Package_${Version}_x86.appxsym"
- $AppxFilename = "EarTrumpet.Package_${Version}_x86.appx"
+ run: >
+ choco
+ pack ".artifacts\chocolatey\eartrumpet.nuspec"
+ --out ".artifacts\chocolatey"
- $SideloadPath = "$env:ARTIFACTS_BASE\sideload"
- $SignedPath = "$env:ARTIFACTS_BASE\sideload\signed"
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: Chocolatey
+ path: |
+ .artifacts/chocolatey/eartrumpet.*.nupkg
- ### Expand bundle and appx package within
- $ExtractedPath = "$env:TEMP\extracted"
- Expand-Archive "$SideloadPath\$SymbolsBundleFilename" "$ExtractedPath\Symbols"
- Expand-Archive "$SideloadPath\$BundleFilename" "$ExtractedPath\Bundle"
- Write-Output "Expand $ExtractedPath\Bundle\EarTrumpet.Package_${Version}_x86.appx"
- Expand-Archive "$ExtractedPath\Bundle\EarTrumpet.Package_${Version}_x86.appx" "$ExtractedPath\Package"
+ #
+ # Packaging: Store
+ #
- ### Place symbols next to executable image
- Copy-Item "$ExtractedPath\Symbols\EarTrumpet.pdb" "$ExtractedPath\Package\EarTrumpet\"
+ store:
+ name: 📦 Package for Microsoft Store
+ runs-on: windows-latest
+ needs: base
- ### Sign executable image
- Write-Output "Signing $ExtractedPath\Package\EarTrumpet\EarTrumpet.exe"
- & (Resolve-Path "Microsoft.Windows.SDK.BuildTools.*\bin\*\x64\signtool.exe") sign /v /fd SHA256 /td SHA256 /tr http://timestamp.acs.microsoft.com /dlib "acs\bin\x64\Azure.CodeSigning.Dlib.dll" /dmdf "acs\metadata.json" "$ExtractedPath\Package\EarTrumpet\EarTrumpet.exe"
+ env:
+ channel: 'Store'
+ publisher: 'CN=6099D0EF-9374-47ED-BDFE-A82136831235'
- New-Item -ItemType Directory "$SignedPath"
- New-Item -ItemType Directory "$SignedPath\Package"
- New-Item -ItemType Directory "$SignedPath\Bundle"
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
- ### Repackage appx package
- & "C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x86\makeappx.exe" pack /l /h sha256 /d "$ExtractedPath\Package" /o /p "$SignedPath\Package\$AppxFilename"
+ - name: Fetch all history for all tags and branches
+ run: git fetch --prune --unshallow
- Set-ItemProperty "$SignedPath\Package\EarTrumpet.Package_${Version}_x86.appx" -Name IsReadOnly -Value $true
- Copy-Item "$ExtractedPath\Bundle\*.appx" "$SignedPath\Package\" -ErrorAction Ignore
- Set-ItemProperty "$SignedPath\Package\EarTrumpet.Package_${Version}_x86.appx" -Name IsReadOnly -Value $false
+ - name: Restore base
+ uses: actions/cache/restore@v4
+ with:
+ path: .artifacts
+ key: base-${{ hashFiles(env.GHA_HASH_PATHS) }}
+ fail-on-cache-miss: true
- ### Repackage appx bundle
- & "C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x86\makeappx.exe" bundle /d "$SignedPath\Package" /bv $Version /o /p "$SignedPath\Bundle\$BundleFilename"
+ - name: Configure manifest
+ uses: ./.github/actions/configure-manifest
+ with:
+ manifest: EarTrumpet.Package/Package.appxmanifest
+ store-association: EarTrumpet.Package/Package.StoreAssociation.xml
+ publisher: ${{ env.publisher }}
+ branch: ${{ github.ref_name }}
+ prefix: 'EarTrumpet'
+ version: .artifacts/version_3.txt
- ### Sign appx bundle
- Write-Output "Signing $SignedPath\Bundle\$BundleFilename"
- & (Resolve-Path "Microsoft.Windows.SDK.BuildTools.*\bin\*\x64\signtool.exe") sign /v /fd SHA256 /td SHA256 /tr http://timestamp.acs.microsoft.com /dlib "acs\bin\x64\Azure.CodeSigning.Dlib.dll" /dmdf "acs\metadata.json" "$SignedPath\Bundle\$BundleFilename"
+ - name: Set up MSBuild
+ uses: microsoft/setup-msbuild@v2
+ with:
+ msbuild-architecture: x64
- Copy-Item "$SideloadPath\*.appinstaller" "$SignedPath\Bundle"
+ - name: Restore packages
+ shell: pwsh
+ run: >
+ dotnet restore
+ EarTrumpet.sln
- - name: Sign and repackage Chocolatey artifacts
- if: matrix.channel == 'Chocolatey'
+ - name: Create msixbundle
shell: pwsh
- run: |
- $MetadataPath = "$env:ARTIFACTS_BASE\metadata"
- $Branch = Get-Content "$MetadataPath\branch.txt"
- $Semver= Get-Content "$MetadataPath\semver.txt"
- $LooseFilesPath = "$env:ARTIFACTS_BASE\loose"
+ run: >
+ msbuild
+ /p:AppxBundle=Always
+ /p:AppxPackageSigningEnabled=false
+ /p:AppxPackageDir=..\.artifacts\store\
+ /p:Configuration=Release
+ /p:Channel=Store
+ /p:GenerateAppInstallerFile=false
+ /p:Platform=x86
+ /p:UapAppxPackageBuildMode=CI
+ /p:WapAlwaysBuildDependentProjects=false
+ "/p:AppxBundlePlatforms=x86|x64|arm64"
+ EarTrumpet.Package/EarTrumpet.Package.wapproj
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: MicrosoftStore
+ path: |
+ .artifacts/store/EarTrumpet.Package_*_x86_x64_arm64.msixbundle
+ .artifacts/store/EarTrumpet.Package_*_x86_x64_arm64_bundle.msixupload
- ### Sign executable image
- & (Resolve-Path "Microsoft.Windows.SDK.BuildTools.*\bin\*\x64\signtool.exe") sign /v /fd SHA256 /td SHA256 /tr http://timestamp.acs.microsoft.com /dlib "acs\bin\x64\Azure.CodeSigning.Dlib.dll" /dmdf "acs\metadata.json" "$LooseFilesPath\EarTrumpet.exe"
+ #
+ # Packaging: Loose
+ #
- ### Package for release
- Compress-Archive -Path "$LooseFilesPath\*" -DestinationPath "$env:ARTIFACTS_BASE\chocolatey\tools\release.zip" -CompressionLevel Optimal
+ loose:
+ name: 📦 Package for Loose scenarios
+ runs-on: windows-latest
+ needs: base
- - name: Adjust nuspec
- if: matrix.channel == 'Chocolatey'
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Restore base
+ uses: actions/cache/restore@v4
+ with:
+ path: .artifacts
+ key: base-${{ hashFiles(env.GHA_HASH_PATHS) }}
+ fail-on-cache-miss: true
+
+ - name: Create release archives
shell: pwsh
- run: |
- $MetadataPath = "$env:ARTIFACTS_BASE\metadata"
- $Version = [Version](Get-Content "$MetadataPath\Chocolatey.version.txt")
- $NuspecPath = "$env:ARTIFACTS_BASE\chocolatey\eartrumpet.nuspec"
+ run: >
+ New-Item
+ -ItemType Directory
+ -Path .artifacts\loose
+ -Force
+
+ @("x86", "x64", "arm64") | ForEach-Object {
+ Compress-Archive `
+ -Path .artifacts\base\$_\* `
+ -DestinationPath .artifacts\loose\EarTrumpet_$(Get-Content .artifacts/version_4.txt)_$($_).zip `
+ -CompressionLevel Optimal
+ }
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: Loose
+ path: |
+ .artifacts/loose/*.zip
+
+ #
+ # Deployment: AppInstaller
+ #
+ deploy-appinstaller:
+ name: Deploy AppInstaller package
+ runs-on: windows-latest
+ needs: appinstaller
+ if: |
+ github.event_name != 'pull_request' &&
+ (
+ startsWith(github.ref, 'refs/heads/dev') ||
+ startsWith(github.ref, 'refs/heads/master') ||
+ startsWith(github.ref, 'refs/heads/feature') ||
+ startsWith(github.ref, 'refs/heads/rafael')
+ )
- $nuspec = [xml](Get-Content -Path $NuspecPath)
- $nuspec.package.metadata.version = $Version
- $nuspec.Save($NuspecPath)
+ steps:
+ - name: Download artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: AppInstaller
+ path: .
- - name: Create chocolatey package
- if: matrix.channel == 'Chocolatey'
- shell: powershell
+ - name: Upload files to Cloudflare
+ shell: pwsh
+ env:
+ AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+ AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+ AWS_REQUEST_CHECKSUM_CALCULATION: when_required
run: |
- choco pack "$env:ARTIFACTS_BASE\chocolatey\eartrumpet.nuspec" --out "$env:ARTIFACTS_BASE\chocolatey"
+ # Upload MSIX bundle
+ Get-ChildItem -File EarTrumpet.Package_*_x86_x64_arm64.msixbundle | ForEach-Object {
+ aws s3 cp $_.FullName "s3://${{ secrets.R2_BUCKET_NAME }}/${{ github.ref_name }}/$($_.Name)" `
+ --content-type "application/msixbundle" `
+ --endpoint-url "https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com"
+ }
+
+ # Upload appinstaller file
+ aws s3 cp EarTrumpet.Package.appinstaller "s3://${{ secrets.R2_BUCKET_NAME }}/${{ github.ref_name }}/EarTrumpet.Package.appinstaller" `
+ --content-type "application/appinstaller" `
+ --endpoint-url "https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com"
+
+ #
+ # Deployment: Microsoft Store
+ #
+
+ deploy-store:
+ name: 🚀 Deploy Microsoft Store package
+ runs-on: windows-latest
+ needs: store
+ if: |
+ github.event_name != 'pull_request' &&
+ (
+ startsWith(github.ref, 'refs/heads/master')
+ )
- - name: Upload chocolatey artifact
- if: matrix.channel == 'Chocolatey'
- uses: actions/upload-artifact@v3
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Download artifact
+ uses: actions/download-artifact@v4
with:
- name: chocolatey-package
- path: artifacts/chocolatey/*.nupkg
+ name: MicrosoftStore
+ path: .
- - name: Install OpenSSH FOD
- if: matrix.channel == 'AppInstaller' || matrix.channel == 'Store'
+ - name: Push to Microsoft Store
shell: powershell
- run: |
- Set-Service -Name wuauserv -StartupType Manual
- Start-Service -Name wuauserv
- Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
+ run: >
+ Set-PSRepository
+ -Name "PSGallery"
+ -InstallationPolicy Trusted
+
+ Install-Module
+ -Name StoreBroker
+
+ $Password = ConvertTo-SecureString '${{ secrets.partnercenter_client_secret }}'
+ -AsPlainText
+ -Force
+
+ $Credentials = New-Object System.Management.Automation.PSCredential ('${{ secrets.partnercenter_client_id }}', $Password)
+
+ Set-StoreBrokerAuthentication
+ -TenantId '${{ secrets.partnercenter_tenant_id }}'
+ -Credential $Credentials
+ -Verbose
+
+ $SubmissionRoot = "$env:Temp\Packaging\Submission"
+
+ $Bundle = Resolve-Path "EarTrumpet.Package_*_x86_x64_arm64_bundle.msixupload" | Split-Path -Leaf
+
+ New-SubmissionPackage
+ -ConfigPath "Packaging\MicrosoftStore\SBConfig.json"
+ -PDPRootPath "Packaging\MicrosoftStore\PDPs"
+ -ImagesRootPath "Packaging\MicrosoftStore\PDPs"
+ -AppxPath "$Bundle"
+ -MediaFallbackLanguage en-US
+ -OutPath "$SubmissionRoot"
+ -OutName EarTrumpet
+ -Verbose
+
+ $SubmissionId, $SubmissionUrl =
+ Update-ApplicationSubmission
+ -AppId 9NBLGGH516XP
+ -SubmissionDataPath "$SubmissionRoot\EarTrumpet.json"
+ -PackagePath "$SubmissionRoot\EarTrumpet.zip"
+ -AddPackages
+ -UpdateListings
+ -UpdatePublishModeAndVisibility
+ -UpdatePricingAndAvailability
+ -UpdateAppProperties
+ -UpdateNotesForCertification
+ -TargetPublishMode Manual
+ -Force
+ -Verbose
+
+ Complete-ApplicationSubmission
+ -AppId 9NBLGGH516XP
+ -SubmissionId $SubmissionId
+ -Verbose
+
+ #
+ # Deployment: GitHub
+ #
+
+ deploy-github:
+ name: 🚀 Deploy GitHub Release
+ runs-on: windows-latest
+ needs: loose
+ if: |
+ github.event_name != 'pull_request' &&
+ (
+ startsWith(github.ref, 'refs/heads/master')
+ )
- - name: Prepare for staging
- if: matrix.channel == 'AppInstaller' || matrix.channel == 'Store'
- shell: powershell
- run: |
- "${{ secrets.staging_userkey }}" | Out-File -Encoding ascii staging.key | Out-Null
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ fetch-tags: true
+
+ - name: Restore base
+ uses: actions/cache/restore@v4
+ with:
+ path: .artifacts
+ key: base-${{ hashFiles(env.GHA_HASH_PATHS) }}
+ fail-on-cache-miss: true
+
+ - name: Download artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: Loose
+ path: .artifacts/loose
- - name: Stage AppInstaller artifacts via SCP
- if: matrix.channel == 'AppInstaller'
+ - name: Generate changelog
+ id: changelog
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: pwsh
- run: |
- icacls .\staging.key /inheritance:r
- icacls .\staging.key /grant:r "$env:USERNAME`:(R)"
- $Branch = Get-Content $env:ARTIFACTS_BASE\metadata\branch.txt
- ssh -i staging.key -o "StrictHostKeyChecking no" ${{ secrets.staging_username }}@${{ secrets.staging_host }} "mkdir -p /var/www/html/$Branch"
- scp -B -i staging.key -o "StrictHostKeyChecking no" $env:ARTIFACTS_BASE\sideload\signed\bundle\* ${{ secrets.staging_username }}@${{ secrets.staging_host }}:/var/www/html/$Branch
- del staging.key
-
- - name: Stage Store artifacts via SCP
- if: matrix.channel == 'Store'
+ run: >
+ $lastTagRef = git describe --match "[0-9]*.[0-9]*.[0-9]*.[0-9]*" --abbrev=0 --tags HEAD 2>$null
+
+ $lastTagDate = git show -s --format=%aI "$lastTagRef^{commit}"
+
+ $query = (Get-Content ".github/workflows/queries/commits.graphql" -Raw)
+ -replace '%%OWNER%%', '${{ github.repository_owner }}'
+ -replace '%%REPO%%', '${{ github.event.repository.name }}'
+
+ $template = Get-Content ".github/workflows/templates/commits.md" -Raw
+
+ $changelog = gh api graphql
+ --paginate
+ -F query="$query"
+ -F lastTagDate="$lastTagDate"
+ -F ref="HEAD"
+ --template "$template"
+
+ $changelog | Out-File -FilePath .artifacts/changelog.md
+
+ - name: Create Release
shell: pwsh
- run: |
- icacls .\staging.key /inheritance:r
- icacls .\staging.key /grant:r "$env:USERNAME`:(R)"
- $Branch = Get-Content $env:ARTIFACTS_BASE\metadata\branch.txt
- ssh -i staging.key -o "StrictHostKeyChecking no" ${{ secrets.staging_username }}@${{ secrets.staging_host }} "mkdir -p /var/www/html/store/$Branch"
- scp -B -i staging.key -o "StrictHostKeyChecking no" $env:ARTIFACTS_BASE\signed\appxupload\* ${{ secrets.staging_username }}@${{ secrets.staging_host }}:/var/www/html/store/$Branch
- del staging.key
-
- - name: Push release to Partner Center via StoreBroker
- if: matrix.channel == 'Store'
- shell: powershell
- run: |
- Set-PSRepository -Name "PSGallery" -InstallationPolicy Trusted
- Install-Module -Name StoreBroker
-
- $Password = ConvertTo-SecureString '${{ secrets.partnercenter_clientkey }}' -AsPlainText -Force
- $Credentials = New-Object System.Management.Automation.PSCredential ('${{ secrets.partnercenter_clientid }}', $Password)
- Set-StoreBrokerAuthentication -TenantId '${{ secrets.partnercenter_tenantid }}' -Credential $Credentials -Verbose
-
- $MetadataPath = "$env:ARTIFACTS_BASE\metadata"
- $PackagingRoot = "$MetadataPath\Packaging\MicrosoftStore"
- $SubmissionRoot = "$env:TEMP\Packaging\Submission"
- $Version = [Version](Get-Content "$MetadataPath\Store.version.txt")
- $StoreBundleFilename = "EarTrumpet.Package_${Version}_x86_bundle.appxupload"
-
- New-SubmissionPackage -ConfigPath "$PackagingRoot\SBConfig.json" -PDPRootPath "$PackagingRoot\PDPs" -ImagesRootPath "$PackagingRoot\PDPs" -AppxPath "$env:ARTIFACTS_BASE\Signed\AppxUpload\$StoreBundleFilename" -MediaFallbackLanguage en-US -OutPath "$SubmissionRoot" -OutName EarTrumpet -Verbose
- $submissionId, $submissionUrl = Update-ApplicationSubmission -AppId "${{ secrets.partnercenter_appid }}" -SubmissionDataPath "$SubmissionRoot\EarTrumpet.json" -PackagePath "$SubmissionRoot\EarTrumpet.zip" -AddPackages -UpdateListings -UpdatePublishModeAndVisibility -UpdatePricingAndAvailability -UpdateAppProperties -UpdateNotesForCertification -TargetPublishMode Manual -Force -Verbose
- Complete-ApplicationSubmission -AppId "${{ secrets.partnercenter_appid }}" -SubmissionId $submissionId -Verbose
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: >
+ $version = Get-Content .artifacts/version_4.txt
+
+ gh release create "v$version"
+ --draft
+ --title "$version"
+ --notes-file .artifacts/changelog.md
+ (Get-ChildItem ".artifacts/loose/*.zip").FullName
\ No newline at end of file
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
new file mode 100644
index 000000000..361e393fe
--- /dev/null
+++ b/.github/workflows/pr.yml
@@ -0,0 +1,46 @@
+name: EarTrumpet (PR Build)
+on:
+ pull_request:
+ branches:
+ - dev
+ paths-ignore:
+ - '**/*.md'
+ - crowdin.yml
+
+env:
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+
+jobs:
+ pr-build:
+ name: 🔨 Build Base
+ runs-on: windows-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Restore packages
+ run: dotnet restore EarTrumpet.sln
+ shell: cmd
+
+ - name: Build EarTrumpet (unsigned for PR validation)
+ shell: cmd
+ run: >
+ dotnet publish
+ /p:PublishProfile=x86
+ /p:Platform=x86
+ /p:DisableGitVersionTask=true
+ EarTrumpet\EarTrumpet.csproj
+
+ dotnet publish
+ /p:PublishProfile=x64
+ /p:Platform=x64
+ /p:DisableGitVersionTask=true
+ EarTrumpet\EarTrumpet.csproj
+
+ dotnet publish
+ /p:PublishProfile=arm64
+ /p:Platform=arm64
+ /p:DisableGitVersionTask=true
+ EarTrumpet\EarTrumpet.csproj
diff --git a/.github/workflows/queries/commits.graphql b/.github/workflows/queries/commits.graphql
new file mode 100644
index 000000000..df435c632
--- /dev/null
+++ b/.github/workflows/queries/commits.graphql
@@ -0,0 +1,20 @@
+query($lastTagDate: GitTimestamp!, $ref: String!) {
+ repository(owner: "%%OWNER%%", name: "%%REPO%%") {
+ object(expression: $ref) {
+ ... on Commit {
+ history(first: 100, since: $lastTagDate) {
+ nodes {
+ messageHeadline
+ oid
+ associatedPullRequests(first: 1) {
+ nodes {
+ title
+ number
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/.github/workflows/sponsors.yml b/.github/workflows/sponsors.yml
index 1fe2fbb99..228c14442 100644
--- a/.github/workflows/sponsors.yml
+++ b/.github/workflows/sponsors.yml
@@ -1,8 +1,8 @@
name: Generate Sponsors
on:
workflow_dispatch:
- schedule:
- - cron: 0 12 1-31 * *
+ # schedule:
+ # - cron: 0 12 1-31 * *
jobs:
deploy:
runs-on: ubuntu-latest
@@ -16,7 +16,7 @@ jobs:
token: ${{ secrets.SPONSORS_PAT }}
file: 'README.md'
organization: true
- template: '
'
+ template: '
'
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
diff --git a/.github/workflows/templates/commits.md b/.github/workflows/templates/commits.md
new file mode 100644
index 000000000..8cfe64bef
--- /dev/null
+++ b/.github/workflows/templates/commits.md
@@ -0,0 +1,8 @@
+{{ range .data.repository.object.history.nodes }}
+ {{ $headline := .messageHeadline }}
+ {{- range .associatedPullRequests.nodes }}
+* {{$headline}}
+ {{- else }}
+* {{$headline}} ({{ slice .oid 0 7 }})
+ {{- end }}
+{{- end }}
diff --git a/.github/workflows/translators.yml b/.github/workflows/translators.yml
new file mode 100644
index 000000000..11046de4d
--- /dev/null
+++ b/.github/workflows/translators.yml
@@ -0,0 +1,47 @@
+name: Update Translators List
+
+on:
+ schedule:
+ - cron: '0 0 * * 0' # Run weekly on Sunday at 00:00
+ workflow_dispatch:
+
+jobs:
+ update-translators:
+ runs-on: windows-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Update README.md
+ shell: pwsh
+ run: |
+ $headers = @{
+ "Authorization" = "Bearer ${{ secrets.CROWDIN_API_TOKEN }}"
+ }
+
+ $response = Invoke-RestMethod `
+ -Uri "https://api.crowdin.com/api/v2/projects/407880/members" `
+ -Headers $headers
+ $translators = $response.data.data
+
+ $html = ($translators | Sort-Object -Property @{Expression={ $_.fullName }}, @{Expression={ $_.username }} | ForEach-Object {
+ $translator = $_
+ $translatorName = if ($translator.fullName) { $translator.fullName } else { $translator.username }
+ $avatarUrl = $translator.avatarUrl
+ "
"
+ }) -join " "
+ $html = "`n$html`n"
+
+ $readmeContent = Get-Content -Path .\README.md -Raw
+ $readmeContent -match "(?[\s\S]*?)[\s\S]*?(?[\s\S]*)"
+ $readmeContent = $matches["sof"] + $html + $matches["eof"]
+
+ Set-Content -Path .\README.md -Value $readmeContent
+
+ - name: Commit changes
+ run: |
+ git config --local user.email "action@github.com"
+ git config --local user.name "GitHub Action"
+ git add README.md
+ git commit -m "Update translators via GitHub Actions" || exit 0
+ git push origin master
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd8f9f27f..46a3067d1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,24 @@
# Changelog
+## x.x.x.x
+- Added support for StartAllBack multitaskbar notification area (thanks @Simplestas!)
+- Fixed various process lifecycle trigger bugs (thanks @spacechase0!)
+- Fixed an issue with EarTrumpet tooltips not updating in some scenarios (thanks @Tester798!)
+- Fixed an issue with EarTrumpet Actions getting disconnected with updated packaged applications
+- Fixed an issue where volume mixer entries were not properly differentiated
+- Added setting to show the full mixer window on startup
+- Added support for adjusting volumes by 10% in one step from the flyout when the `Ctrl` key is pressed in combination with `Right`/`Left` or `+`/`-` (thanks @ryanspain!)
+- Migrated EarTrumpet from .NET Framework 4.6.2 to .NET 8.0
+- Fixed an issue with EarTrumpet packaging that affected the languages shown in the Microsoft Store
+- Dropped support for some end-of-life versions of Windows 10
+- Added a help dialog to assist when EarTrumpet can't start automatically due to missing Windows policies
+- Fixed the duplicate Windows Legacy > Volume Mixer entries that appear on Windows 11
+- Updated translations for Turkish, Russian, Croatian, German, Portuguese, Japanese, Swedish, Catalan, Korean, Hebrew, and Spanish
+- Fixed an issue with EarTrumpet Actions not recording packaged application IDs correctly
+- Added new and improved logarithmic volume implementation (thanks @Dwscdv3!)
+- Added logarithmic peak metering (thanks @Dwscdv3!)
+- Added adjustable logarithmic slider range (thanks @Dwscdv3!)
+
## 2.3.0.0
- Added setting to turn on/off ability to change volume with the scroll wheel anywhere (thanks @Tester798!)
- Added setting to turn on/off ability to change volume with the scroll wheel when hovering over the EarTrumpet icon (thanks @Tester798!)
@@ -118,7 +137,7 @@
- Added text to notification area icon tooltip to indicate mute state
- Re-added flyout window shadow and borders
- Added additional telemetry points
-- Removed Arabic, Hungarian, Korean, Norwegian Bokm�l, Portuguese, Romanian, and Turkish until we complete localization
+- Removed Arabic, Hungarian, Korean, Norwegian Bokmål, Portuguese, Romanian, and Turkish until we complete localization
- Additional bugfixes
## 2.0.8.0
diff --git a/COMPILING.md b/COMPILING.md
index ab0b020e1..4236e8ea2 100644
--- a/COMPILING.md
+++ b/COMPILING.md
@@ -1,18 +1,19 @@
# Compiling EarTrumpet
## Requirements
-* [Visual Studio 2017](https://visualstudio.microsoft.com/vs/community/) (or newer)
+* [Visual Studio 2022](https://visualstudio.microsoft.com/vs/community/) (or newer)
* [Git for Windows](https://git-scm.com/download/win)
-* [Windows 10 Anniversary Update](https://blogs.windows.com/windowsexperience/2016/08/02/how-to-get-the-windows-10-anniversary-update/#GD97Eq04wJA7S4P7.97) (or newer)
-* [.NET Framework 4.6.2 Developer Pack](https://www.microsoft.com/net/download/thank-you/net462-developer-pack)
-* [Windows 10 SDK (10.0.14393.0)](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive)
+* [Windows 10 November 2021 Update](https://learn.microsoft.com/windows/whats-new/whats-new-windows-10-version-21h2) (or newer)
+* [.NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
+* [Windows SDK for Windows 11 (10.0.26100.4188)](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive) (or newer)
+
## Step-by-step
-1. Install Visual Studio 2017 with the `.NET desktop development` and `Universal Windows Platform development` workloads.
-2. Install the `Windows 10 SDK (10.0.14393.0)` SDK.
-3. Install the .NET Framework 4.6.2 Developer Pack.
+1. Install Visual Studio 2022 with the `.NET desktop development` and `Universal Windows Platform development` workloads.
+2. Install the Windows SDK.
+3. Install the .NET 8 SDK.
4. Install Git for Windows.
5. Clone the EarTrumpet repository (`git clone https://github.com/File-New-Project/EarTrumpet.git`).
-6. Open `EarTrumpet.vs15.sln` in Visual Studio.
-7. Change the target platform to `x86` and build the `EarTrumpet.Package` project.
+6. Open `EarTrumpet.sln` in Visual Studio.
+7. Change the target platform as needed and build the `EarTrumpet.Package` project.
8. You're done. If you plan on submitting your changes to us, please review the [Contributing guide](https://github.com/File-New-Project/EarTrumpet/blob/master/CONTRIBUTING.md) first.
diff --git a/EarTrumpet.ColorTool/EarTrumpet.ColorTool.csproj b/EarTrumpet.ColorTool/EarTrumpet.ColorTool.csproj
index b0c2c11f2..585f1445b 100644
--- a/EarTrumpet.ColorTool/EarTrumpet.ColorTool.csproj
+++ b/EarTrumpet.ColorTool/EarTrumpet.ColorTool.csproj
@@ -9,7 +9,6 @@
EarTrumpet.ColorTool
EarTrumpet.ColorTool
v4.6.2
- MinimumRecommendedRules.ruleset
true
x86
prompt
diff --git a/EarTrumpet.Package/Directory.Build.props b/EarTrumpet.Package/Directory.Build.props
new file mode 100644
index 000000000..e7d63e32c
--- /dev/null
+++ b/EarTrumpet.Package/Directory.Build.props
@@ -0,0 +1,8 @@
+
+
+
+
+ $(NoWarn);NU1701
+
+
+
\ No newline at end of file
diff --git a/EarTrumpet.Package/EarTrumpet.Package.wapproj b/EarTrumpet.Package/EarTrumpet.Package.wapproj
index 1d7fa6f5a..0823a3537 100644
--- a/EarTrumpet.Package/EarTrumpet.Package.wapproj
+++ b/EarTrumpet.Package/EarTrumpet.Package.wapproj
@@ -2,8 +2,17 @@
15.0
+ MSB4011;$(NoWarn)
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
Debug
x86
@@ -12,6 +21,14 @@
Release
x86
+
+ Debug
+ ARM64
+
+
+ Release
+ ARM64
+
$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\
@@ -19,32 +36,52 @@
ea5510ed-f014-4587-a505-64c59d5b2627
- 10.0.14393.0
- 10.0.14393.0
+ 10.0.26100.0
+ 10.0.19041.0
en-US
EarTrumpet.Package_StoreKey.pfx
..\EarTrumpet\EarTrumpet.csproj
False
False
x86
- 1
+ 0
OnApplicationRun
false
- 3E10717CB4915430F2920FECAC4F20570137B9FC
+ NU1702
+ Always
+ SHA256
+ False
+ 0
-
+
+ Always
+
+
Always
Always
+
+ Always
+
+
+ Always
+
+
+ Always
+
Designer
-
+
+ Properties\PublishProfiles\x86.pubxml
+ Properties\PublishProfiles\x64.pubxml
+ Properties\PublishProfiles\ARM64.pubxml
+
@@ -98,6 +135,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $([System.Text.RegularExpressions.Regex]::Match('%(Filename)', 'Resources\.(.+)').Groups[1].Value)
+
+
+
+ Language=en-us;@(EarTrumpetReferencedResxFiles->'%(LanguageTag)', ';')|DXFeatureLevel=DX9|Scale=100|Scale=125|Scale=150|Scale=200|Scale=400|TargetSize=16|TargetSize=24|TargetSize=32|TargetSize=48|TargetSize=256|AlternateForm=UNPLATED|Contrast=standard|HomeRegion=001|LayoutDirection=LTR|Configuration=|Platform=UAP
+
+
+
+
+ %(FinalAppxManifest.Identity)
+
+ <Resource Language="en-us" />
+ @(EarTrumpetReferencedResxFiles->'<Resource Language="%(LanguageTag)" />', '')
+
+
+
+
\ No newline at end of file
diff --git a/EarTrumpet.Package/Package.appxmanifest b/EarTrumpet.Package/Package.appxmanifest
index 70d269b16..3cf70c606 100644
--- a/EarTrumpet.Package/Package.appxmanifest
+++ b/EarTrumpet.Package/Package.appxmanifest
@@ -1,38 +1,99 @@
-
-
-
- EarTrumpet
- File-New-Project
- Assets\StoreLogo.png
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+ EarTrumpet
+ File-New-Project
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/EarTrumpet.sln b/EarTrumpet.sln
new file mode 100644
index 000000000..873269e34
--- /dev/null
+++ b/EarTrumpet.sln
@@ -0,0 +1,96 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.32014.148
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8F56ECD6-00C6-4C4A-AB1F-C01ED14EF53B}"
+ ProjectSection(SolutionItems) = preProject
+ CHANGELOG.md = CHANGELOG.md
+ COMPILING.md = COMPILING.md
+ CONTRIBUTING.md = CONTRIBUTING.md
+ GitVersion.yml = GitVersion.yml
+ LICENSE = LICENSE
+ PRIVACY.md = PRIVACY.md
+ README.md = README.md
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EarTrumpet", "EarTrumpet\EarTrumpet.csproj", "{DF42BC76-DFB5-41C2-9308-1A1F13A66A38}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Chocolatey", "Chocolatey", "{EF7F607E-FD43-4BA4-B32B-128EF2944A26}"
+ ProjectSection(SolutionItems) = preProject
+ .chocolatey\tools\chocolateybeforemodify.ps1 = .chocolatey\tools\chocolateybeforemodify.ps1
+ .chocolatey\tools\chocolateyinstall.ps1 = .chocolatey\tools\chocolateyinstall.ps1
+ .chocolatey\tools\chocolateyuninstall.ps1 = .chocolatey\tools\chocolateyuninstall.ps1
+ EndProjectSection
+EndProject
+Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "EarTrumpet.Package", "EarTrumpet.Package\EarTrumpet.Package.wapproj", "{EA5510ED-F014-4587-A505-64C59D5B2627}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|ARM64 = Debug|ARM64
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|ARM64 = Release|ARM64
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ VSDebug|ARM64 = VSDebug|ARM64
+ VSDebug|x64 = VSDebug|x64
+ VSDebug|x86 = VSDebug|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.Debug|ARM64.Build.0 = Debug|ARM64
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.Debug|x64.ActiveCfg = Debug|x64
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.Debug|x64.Build.0 = Debug|x64
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.Debug|x86.ActiveCfg = Debug|x86
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.Debug|x86.Build.0 = Debug|x86
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.Release|ARM64.ActiveCfg = Release|ARM64
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.Release|ARM64.Build.0 = Release|ARM64
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.Release|x64.ActiveCfg = Release|x64
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.Release|x64.Build.0 = Release|x64
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.Release|x86.ActiveCfg = Release|x86
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.Release|x86.Build.0 = Release|x86
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.VSDebug|ARM64.ActiveCfg = VSDebug|ARM64
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.VSDebug|ARM64.Build.0 = VSDebug|ARM64
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.VSDebug|x64.ActiveCfg = VSDebug|x64
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.VSDebug|x64.Build.0 = VSDebug|x64
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.VSDebug|x86.ActiveCfg = VSDebug|x86
+ {DF42BC76-DFB5-41C2-9308-1A1F13A66A38}.VSDebug|x86.Build.0 = VSDebug|x86
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Debug|ARM64.Build.0 = Debug|ARM64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Debug|ARM64.Deploy.0 = Debug|ARM64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Debug|x64.ActiveCfg = Debug|x64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Debug|x64.Build.0 = Debug|x64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Debug|x64.Deploy.0 = Debug|x64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Debug|x86.ActiveCfg = Debug|x86
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Debug|x86.Build.0 = Debug|x86
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Debug|x86.Deploy.0 = Debug|x86
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Release|ARM64.ActiveCfg = Release|ARM64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Release|ARM64.Build.0 = Release|ARM64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Release|ARM64.Deploy.0 = Release|ARM64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Release|x64.ActiveCfg = Release|x64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Release|x64.Build.0 = Release|x64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Release|x64.Deploy.0 = Release|x64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Release|x86.ActiveCfg = Release|x86
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Release|x86.Build.0 = Release|x86
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.Release|x86.Deploy.0 = Release|x86
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.VSDebug|ARM64.ActiveCfg = Debug|ARM64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.VSDebug|ARM64.Build.0 = Debug|ARM64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.VSDebug|ARM64.Deploy.0 = Debug|ARM64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.VSDebug|x64.ActiveCfg = Debug|x64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.VSDebug|x64.Build.0 = Debug|x64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.VSDebug|x64.Deploy.0 = Debug|x64
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.VSDebug|x86.ActiveCfg = Debug|x86
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.VSDebug|x86.Build.0 = Debug|x86
+ {EA5510ED-F014-4587-A505-64C59D5B2627}.VSDebug|x86.Deploy.0 = Debug|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {EF7F607E-FD43-4BA4-B32B-128EF2944A26} = {8F56ECD6-00C6-4C4A-AB1F-C01ED14EF53B}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {228AD06A-DAC2-406B-8F21-3E7B980B1B37}
+ EndGlobalSection
+EndGlobal
diff --git a/EarTrumpet.vs15.sln b/EarTrumpet.vs15.sln
deleted file mode 100644
index 18536d925..000000000
--- a/EarTrumpet.vs15.sln
+++ /dev/null
@@ -1,58 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.29306.81
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EarTrumpet", "EarTrumpet\EarTrumpet.csproj", "{BA3C7B42-84B0-468C-8640-217E2A24CF81}"
-EndProject
-Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "EarTrumpet.Package", "EarTrumpet.Package\EarTrumpet.Package.wapproj", "{EA5510ED-F014-4587-A505-64C59D5B2627}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8F56ECD6-00C6-4C4A-AB1F-C01ED14EF53B}"
- ProjectSection(SolutionItems) = preProject
- CHANGELOG.md = CHANGELOG.md
- COMPILING.md = COMPILING.md
- CONTRIBUTING.md = CONTRIBUTING.md
- GitVersion.yml = GitVersion.yml
- LICENSE = LICENSE
- PRIVACY.md = PRIVACY.md
- README.md = README.md
- EndProjectSection
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EarTrumpet.ColorTool", "EarTrumpet.ColorTool\EarTrumpet.ColorTool.csproj", "{E5B2C3B5-4CED-4C82-8A82-D290A7E0FC5D}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|x86 = Debug|x86
- Release|x86 = Release|x86
- VSDebug|x86 = VSDebug|x86
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {BA3C7B42-84B0-468C-8640-217E2A24CF81}.Debug|x86.ActiveCfg = Debug|x86
- {BA3C7B42-84B0-468C-8640-217E2A24CF81}.Debug|x86.Build.0 = Debug|x86
- {BA3C7B42-84B0-468C-8640-217E2A24CF81}.Release|x86.ActiveCfg = Release|x86
- {BA3C7B42-84B0-468C-8640-217E2A24CF81}.Release|x86.Build.0 = Release|x86
- {BA3C7B42-84B0-468C-8640-217E2A24CF81}.VSDebug|x86.ActiveCfg = VSDebug|x86
- {BA3C7B42-84B0-468C-8640-217E2A24CF81}.VSDebug|x86.Build.0 = VSDebug|x86
- {EA5510ED-F014-4587-A505-64C59D5B2627}.Debug|x86.ActiveCfg = Debug|x86
- {EA5510ED-F014-4587-A505-64C59D5B2627}.Debug|x86.Build.0 = Debug|x86
- {EA5510ED-F014-4587-A505-64C59D5B2627}.Debug|x86.Deploy.0 = Debug|x86
- {EA5510ED-F014-4587-A505-64C59D5B2627}.Release|x86.ActiveCfg = Release|x86
- {EA5510ED-F014-4587-A505-64C59D5B2627}.Release|x86.Build.0 = Release|x86
- {EA5510ED-F014-4587-A505-64C59D5B2627}.Release|x86.Deploy.0 = Release|x86
- {EA5510ED-F014-4587-A505-64C59D5B2627}.VSDebug|x86.ActiveCfg = Debug|x86
- {EA5510ED-F014-4587-A505-64C59D5B2627}.VSDebug|x86.Build.0 = Debug|x86
- {EA5510ED-F014-4587-A505-64C59D5B2627}.VSDebug|x86.Deploy.0 = Debug|x86
- {E5B2C3B5-4CED-4C82-8A82-D290A7E0FC5D}.Debug|x86.ActiveCfg = Debug|x86
- {E5B2C3B5-4CED-4C82-8A82-D290A7E0FC5D}.Debug|x86.Build.0 = Debug|x86
- {E5B2C3B5-4CED-4C82-8A82-D290A7E0FC5D}.Release|x86.ActiveCfg = Release|x86
- {E5B2C3B5-4CED-4C82-8A82-D290A7E0FC5D}.Release|x86.Build.0 = Release|x86
- {E5B2C3B5-4CED-4C82-8A82-D290A7E0FC5D}.VSDebug|x86.ActiveCfg = Debug|x86
- {E5B2C3B5-4CED-4C82-8A82-D290A7E0FC5D}.VSDebug|x86.Build.0 = Debug|x86
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {228AD06A-DAC2-406B-8F21-3E7B980B1B37}
- EndGlobalSection
-EndGlobal
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/AddonResources.xaml b/EarTrumpet/Addons/EarTrumpet.Actions/AddonResources.xaml
index 482df820d..17a97f562 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/AddonResources.xaml
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/AddonResources.xaml
@@ -317,8 +317,9 @@
((LinkedTextBlock)d).DataItemChanged();
+ private static void DataItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((LinkedTextBlock)d).DataItemChanged();
- public string FormatText
- {
- get { return (string)this.GetValue(FormatTextProperty); }
- set { this.SetValue(FormatTextProperty, value); }
- }
- public static readonly DependencyProperty FormatTextProperty = DependencyProperty.Register(
- "FormatText", typeof(string), typeof(LinkedTextBlock), new PropertyMetadata("", new PropertyChangedCallback(FormatTextChanged)));
+ public string FormatText
+ {
+ get { return (string)this.GetValue(FormatTextProperty); }
+ set { this.SetValue(FormatTextProperty, value); }
+ }
+ public static readonly DependencyProperty FormatTextProperty = DependencyProperty.Register(
+ "FormatText", typeof(string), typeof(LinkedTextBlock), new PropertyMetadata("", new PropertyChangedCallback(FormatTextChanged)));
- private static void FormatTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((LinkedTextBlock)d).PropertiesChanged();
+ private static void FormatTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((LinkedTextBlock)d).PropertiesChanged();
- public Style HyperlinkStyle
- {
- get { return (Style)this.GetValue(HyperlinkStyleProperty); }
- set { this.SetValue(HyperlinkStyleProperty, value); }
- }
- public static readonly DependencyProperty HyperlinkStyleProperty = DependencyProperty.Register(
- "HyperlinkStyle", typeof(Style), typeof(LinkedTextBlock), new PropertyMetadata(null, new PropertyChangedCallback(HyperlinkStyleChanged)));
+ public Style HyperlinkStyle
+ {
+ get { return (Style)this.GetValue(HyperlinkStyleProperty); }
+ set { this.SetValue(HyperlinkStyleProperty, value); }
+ }
+ public static readonly DependencyProperty HyperlinkStyleProperty = DependencyProperty.Register(
+ "HyperlinkStyle", typeof(Style), typeof(LinkedTextBlock), new PropertyMetadata(null, new PropertyChangedCallback(HyperlinkStyleChanged)));
+
+ public Style RunStyle
+ {
+ get { return (Style)this.GetValue(RunStyleProperty); }
+ set { this.SetValue(RunStyleProperty, value); }
+ }
+ public static readonly DependencyProperty RunStyleProperty = DependencyProperty.Register(
+ "RunStyle", typeof(Style), typeof(LinkedTextBlock), new PropertyMetadata(null, new PropertyChangedCallback(RunStyleChanged)));
- public Style RunStyle
- {
- get { return (Style)this.GetValue(RunStyleProperty); }
- set { this.SetValue(RunStyleProperty, value); }
- }
- public static readonly DependencyProperty RunStyleProperty = DependencyProperty.Register(
- "RunStyle", typeof(Style), typeof(LinkedTextBlock), new PropertyMetadata(null, new PropertyChangedCallback(RunStyleChanged)));
+ private static void HyperlinkStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((LinkedTextBlock)d).PropertiesChanged();
+ private static void RunStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((LinkedTextBlock)d).PropertiesChanged();
- private static void HyperlinkStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((LinkedTextBlock)d).PropertiesChanged();
- private static void RunStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((LinkedTextBlock)d).PropertiesChanged();
+ private void DataItemChanged()
+ {
+ ((INotifyPropertyChanged)DataItem).PropertyChanged += (s, e) => PropertiesChanged();
+ }
+
+ private void PropertiesChanged()
+ {
+ this.Inlines.Clear();
- private void DataItemChanged()
+ if (ContextMenu != null && Popup != null)
{
- ((INotifyPropertyChanged)DataItem).PropertyChanged += (s, e) => PropertiesChanged();
+ ContextMenu.Loaded += ContextMenu_Loaded;
+ Popup.Loaded += Popup_Loaded;
}
- private void PropertiesChanged()
+ ReadLinksAndText(FormatText, (text, isLink) =>
{
- this.Inlines.Clear();
-
- if (ContextMenu != null && Popup != null)
+ text = text.Trim();
+ if (!isLink)
{
- ContextMenu.Loaded += ContextMenu_Loaded;
- Popup.Loaded += Popup_Loaded;
+ var run = new Run(text)
+ {
+ Style = RunStyle
+ };
+ this.Inlines.Add(run);
}
-
- ReadLinksAndText(FormatText, (text, isLink) =>
+ else
{
- text = text.Trim();
- if (!isLink)
+ var resolvedPropertyObject = DataItem.GetType().GetProperty(text).GetValue(DataItem, null);
+ var link = new Hyperlink(new Run(resolvedPropertyObject.ToString()))
{
- var run = new Run(text);
- run.Style = RunStyle;
- this.Inlines.Add(run);
- }
- else
+ NavigateUri = new Uri("about:none"),
+ Style = HyperlinkStyle
+ };
+
+ link.RequestNavigate += (s, e) =>
{
- var resolvedPropertyObject = DataItem.GetType().GetProperty(text).GetValue(DataItem, null);
- var link = new Hyperlink(new Run(resolvedPropertyObject.ToString()));
- link.NavigateUri = new Uri("about:none");
- link.Style = HyperlinkStyle;
+ // Take focus now so that we get return focus when the user leaves.
+ link.Focus();
+ var dpiX = Window.GetWindow(this).DpiX();
+ var dpiY = Window.GetWindow(this).DpiY();
- link.RequestNavigate += (s, e) =>
+ if (resolvedPropertyObject is IOptionViewModel)
{
- // Take focus now so that we get return focus when the user leaves.
- link.Focus();
- var dpiX = Window.GetWindow(this).DpiX();
- var dpiY = Window.GetWindow(this).DpiY();
-
- if (resolvedPropertyObject is IOptionViewModel)
+ ContextMenu2.Opacity = 0;
+ ContextMenu2.ItemsSource = GetContextMenuFromOptionViewModel((IOptionViewModel)resolvedPropertyObject).OrderBy(menu => menu.DisplayName);
+ ContextMenu2.UpdateLayout();
+ ContextMenu2.IsOpen = true;
+ ContextMenu2.Dispatcher.BeginInvoke((Action)(() =>
{
- ContextMenu2.Opacity = 0;
- ContextMenu2.ItemsSource = GetContextMenuFromOptionViewModel((IOptionViewModel)resolvedPropertyObject).OrderBy(menu => menu.DisplayName);
- ContextMenu2.UpdateLayout();
- ContextMenu2.IsOpen = true;
- ContextMenu2.Dispatcher.BeginInvoke((Action)(() =>
- {
- ContextMenu2.Opacity = 1;
- ContextMenu2.HorizontalOffset = -1 * (ContextMenu2.RenderSize.Width / dpiX) / 2;
- ContextMenu2.VerticalOffset = -1 * (ContextMenu2.RenderSize.Height / dpiY) / 2;
- ContextMenu2.Focus();
- }),
- System.Windows.Threading.DispatcherPriority.DataBind, null);
- }
- else
+ ContextMenu2.Opacity = 1;
+ ContextMenu2.HorizontalOffset = -1 * (ContextMenu2.RenderSize.Width / dpiX) / 2;
+ ContextMenu2.VerticalOffset = -1 * (ContextMenu2.RenderSize.Height / dpiY) / 2;
+ ContextMenu2.Focus();
+ }),
+ System.Windows.Threading.DispatcherPriority.DataBind, null);
+ }
+ else
+ {
+ Popup.PreviewKeyDown += (_, ee) =>
{
- Popup.PreviewKeyDown += (_, ee) =>
+ if (ee.Key == Key.Escape)
{
- if (ee.Key == Key.Escape)
- {
- Popup.IsOpen = false;
- }
- };
- Popup.Opacity = 0;
- Popup.DataContext = resolvedPropertyObject;
- Popup.UpdateLayout();
- Popup.Child.UpdateLayout();
- Popup.IsOpen = true;
- Popup.Dispatcher.BeginInvoke((Action)(() =>
- {
- Popup.Opacity = 1;
- Popup.HorizontalOffset = -1 * (Popup.Child.RenderSize.Width / dpiX) / 2;
- Popup.VerticalOffset = -1 * (Popup.Child.RenderSize.Height / dpiY) / 2;
- Keyboard.Focus(Popup.Child.FindVisualChild());
- }),
- System.Windows.Threading.DispatcherPriority.DataBind, null);
- }
- };
- this.Inlines.Add(link);
- }
- this.Inlines.Add(new Run(" "));
- });
- }
+ Popup.IsOpen = false;
+ }
+ };
+ Popup.Opacity = 0;
+ Popup.DataContext = resolvedPropertyObject;
+ Popup.UpdateLayout();
+ Popup.Child.UpdateLayout();
+ Popup.IsOpen = true;
+ Popup.Dispatcher.BeginInvoke((Action)(() =>
+ {
+ Popup.Opacity = 1;
+ Popup.HorizontalOffset = -1 * (Popup.Child.RenderSize.Width / dpiX) / 2;
+ Popup.VerticalOffset = -1 * (Popup.Child.RenderSize.Height / dpiY) / 2;
+ Keyboard.Focus(Popup.Child.FindVisualChild());
+ }),
+ System.Windows.Threading.DispatcherPriority.DataBind, null);
+ }
+ };
+ this.Inlines.Add(link);
+ }
+ this.Inlines.Add(new Run(" "));
+ });
+ }
- private void Popup_Loaded(object sender, RoutedEventArgs e)
- {
- Popup.UpdateLayout();
- Popup.Child.UpdateLayout();
- Popup.HorizontalOffset = -1 * Popup.Child.RenderSize.Width / 2;
- Popup.VerticalOffset = -1 * Popup.Child.RenderSize.Height / 2;
- }
+ private void Popup_Loaded(object sender, RoutedEventArgs e)
+ {
+ Popup.UpdateLayout();
+ Popup.Child.UpdateLayout();
+ Popup.HorizontalOffset = -1 * Popup.Child.RenderSize.Width / 2;
+ Popup.VerticalOffset = -1 * Popup.Child.RenderSize.Height / 2;
+ }
- private void ContextMenu_Loaded(object sender, RoutedEventArgs e)
- {
- ContextMenu2.UpdateLayout();
- ContextMenu2.HorizontalOffset = -1 * ContextMenu2.RenderSize.Width / 2;
- ContextMenu2.VerticalOffset = -1 * ContextMenu2.RenderSize.Height / 2;
- }
+ private void ContextMenu_Loaded(object sender, RoutedEventArgs e)
+ {
+ ContextMenu2.UpdateLayout();
+ ContextMenu2.HorizontalOffset = -1 * ContextMenu2.RenderSize.Width / 2;
+ ContextMenu2.VerticalOffset = -1 * ContextMenu2.RenderSize.Height / 2;
+ }
- private List GetContextMenuFromOptionViewModel(IOptionViewModel options)
+ private static List GetContextMenuFromOptionViewModel(IOptionViewModel options)
+ {
+ return options.All.Select(item => new ContextMenuItem
{
- return options.All.Select(item => new ContextMenuItem
- {
- DisplayName = item.DisplayName,
- IsChecked = (item == options.Selected),
- Command = new RelayCommand(() => options.Selected = item),
- }).ToList();
- }
+ DisplayName = item.DisplayName,
+ IsChecked = (item == options.Selected),
+ Command = new RelayCommand(() => options.Selected = item),
+ }).ToList();
+ }
- private void ReadLinksAndText(string text, Action callback)
+ private static void ReadLinksAndText(string text, Action callback)
+ {
+ var ptr = 0;
+ for (var i = 0; i < text.Length; i++)
{
- int ptr = 0;
- for (int i = 0; i < text.Length; i++)
+ if (text[i] == '{')
{
- if (text[i] == '{')
+ if (i > 0)
{
- if (i > 0)
- {
- callback(text.Substring(ptr, i - 1 - ptr), false);
- }
- ptr = i + 1;
- }
- else if (text[i] == '}')
- {
- callback(text.Substring(ptr, i - ptr), true);
- ptr = i + 1;
+ callback(text.Substring(ptr, i - 1 - ptr), false);
}
+ ptr = i + 1;
}
-
- if (ptr < text.Length - 1)
+ else if (text[i] == '}')
{
- callback(text.Substring(ptr, text.Length - ptr), false);
+ callback(text.Substring(ptr, i - ptr), true);
+ ptr = i + 1;
}
}
+
+ if (ptr < text.Length - 1)
+ {
+ callback(text.Substring(ptr, text.Length - ptr), false);
+ }
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/Controls/MenuButton.cs b/EarTrumpet/Addons/EarTrumpet.Actions/Controls/MenuButton.cs
index 88f630043..48bdedace 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/Controls/MenuButton.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/Controls/MenuButton.cs
@@ -1,27 +1,26 @@
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
-namespace EarTrumpet.Actions.Controls
+namespace EarTrumpet.Actions.Controls;
+
+public class MenuButton : Button
{
- public class MenuButton : Button
+ public MenuButton()
{
- public MenuButton()
- {
- Click += Button_Click;
- }
+ Click += Button_Click;
+ }
- private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
- {
- var btn = (Button)sender;
+ private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
+ {
+ var btn = (Button)sender;
- btn.ContextMenu.Opened += (_, __) =>
- {
- ((Popup)btn.ContextMenu.Parent).PopupAnimation = PopupAnimation.None;
- };
+ btn.ContextMenu.Opened += (_, __) =>
+ {
+ ((Popup)btn.ContextMenu.Parent).PopupAnimation = PopupAnimation.None;
+ };
- btn.ContextMenu.PlacementTarget = btn;
- btn.ContextMenu.Placement = PlacementMode.Bottom;
- btn.ContextMenu.IsOpen = true;
- }
+ btn.ContextMenu.PlacementTarget = btn;
+ btn.ContextMenu.Placement = PlacementMode.Bottom;
+ btn.ContextMenu.IsOpen = true;
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/AudioAppEventKind.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/AudioAppEventKind.cs
index 0ef769bf5..12fcfdd12 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/AudioAppEventKind.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/AudioAppEventKind.cs
@@ -1,12 +1,11 @@
-namespace EarTrumpet.Actions.DataModel.Enum
+namespace EarTrumpet.Actions.DataModel.Enum;
+
+public enum AudioAppEventKind
{
- public enum AudioAppEventKind
- {
- Added,
- Removed,
- PlayingSound,
- NotPlayingSound,
- Muted,
- Unmuted,
- }
+ Added,
+ Removed,
+ PlayingSound,
+ NotPlayingSound,
+ Muted,
+ Unmuted,
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/AudioDeviceEventKind.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/AudioDeviceEventKind.cs
index 87241790d..95cca187d 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/AudioDeviceEventKind.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/AudioDeviceEventKind.cs
@@ -1,10 +1,9 @@
-namespace EarTrumpet.Actions.DataModel.Enum
+namespace EarTrumpet.Actions.DataModel.Enum;
+
+public enum AudioDeviceEventKind
{
- public enum AudioDeviceEventKind
- {
- Added,
- Removed,
- BecomingDefault,
- LeavingDefault,
- }
+ Added,
+ Removed,
+ BecomingDefault,
+ LeavingDefault,
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/BoolValue.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/BoolValue.cs
index c5f0a3fa9..4f0d186ed 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/BoolValue.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/BoolValue.cs
@@ -1,8 +1,7 @@
-namespace EarTrumpet.Actions.DataModel.Enum
+namespace EarTrumpet.Actions.DataModel.Enum;
+
+public enum BoolValue
{
- public enum BoolValue
- {
- True,
- False,
- }
+ True,
+ False,
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/ComparisonBoolKind.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/ComparisonBoolKind.cs
index 4ab31bdeb..3b58dedbe 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/ComparisonBoolKind.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/ComparisonBoolKind.cs
@@ -1,8 +1,7 @@
-namespace EarTrumpet.Actions.DataModel.Enum
+namespace EarTrumpet.Actions.DataModel.Enum;
+
+public enum ComparisonBoolKind
{
- public enum ComparisonBoolKind
- {
- Is,
- IsNot,
- }
+ Is,
+ IsNot,
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/EarTrumpetEventKind.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/EarTrumpetEventKind.cs
index 5e54ec3db..a24ece309 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/EarTrumpetEventKind.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/EarTrumpetEventKind.cs
@@ -1,8 +1,7 @@
-namespace EarTrumpet.Actions.DataModel.Enum
+namespace EarTrumpet.Actions.DataModel.Enum;
+
+public enum EarTrumpetEventKind
{
- public enum EarTrumpetEventKind
- {
- Startup,
- Shutdown,
- };
-}
+ Startup,
+ Shutdown,
+};
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/MuteKind.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/MuteKind.cs
index e5ddec5b4..2c065fc64 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/MuteKind.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/MuteKind.cs
@@ -1,9 +1,8 @@
-namespace EarTrumpet.Actions.DataModel.Enum
+namespace EarTrumpet.Actions.DataModel.Enum;
+
+public enum MuteKind
{
- public enum MuteKind
- {
- Mute,
- Unmute,
- ToggleMute,
- }
+ Mute,
+ Unmute,
+ ToggleMute,
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/ProcessEventKind.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/ProcessEventKind.cs
index 319422b23..4f99534fe 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/ProcessEventKind.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/ProcessEventKind.cs
@@ -1,8 +1,7 @@
-namespace EarTrumpet.Actions.DataModel.Enum
+namespace EarTrumpet.Actions.DataModel.Enum;
+
+public enum ProcessEventKind
{
- public enum ProcessEventKind
- {
- Start,
- Stop,
- }
+ Start,
+ Stop,
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/ProcessStateKind.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/ProcessStateKind.cs
index fa75a24d9..7d2bd0986 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/ProcessStateKind.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/ProcessStateKind.cs
@@ -1,8 +1,7 @@
-namespace EarTrumpet.Actions.DataModel.Enum
+namespace EarTrumpet.Actions.DataModel.Enum;
+
+public enum ProcessStateKind
{
- public enum ProcessStateKind
- {
- Running,
- NotRunning
- }
+ Running,
+ NotRunning
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/SetVolumeKind.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/SetVolumeKind.cs
index c69d87f2b..e941b4fc4 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/SetVolumeKind.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/SetVolumeKind.cs
@@ -1,9 +1,8 @@
-namespace EarTrumpet.Actions.DataModel.Enum
+namespace EarTrumpet.Actions.DataModel.Enum;
+
+public enum SetVolumeKind
{
- public enum SetVolumeKind
- {
- Set,
- Increment,
- Decrement,
- }
+ Set,
+ Increment,
+ Decrement,
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/VolumeUnit.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/VolumeUnit.cs
new file mode 100644
index 000000000..e311ed5a3
--- /dev/null
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Enum/VolumeUnit.cs
@@ -0,0 +1,7 @@
+namespace EarTrumpet.Actions.DataModel.Enum;
+
+public enum VolumeUnit
+{
+ Percentage,
+ Decibel,
+}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/IPartWithApp.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/IPartWithApp.cs
index b8f3abefd..62fcd1009 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/IPartWithApp.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/IPartWithApp.cs
@@ -1,9 +1,8 @@
using EarTrumpet.Actions.DataModel.Serialization;
-namespace EarTrumpet.Actions.DataModel
+namespace EarTrumpet.Actions.DataModel;
+
+internal interface IPartWithApp
{
- interface IPartWithApp
- {
- AppRef App { get; set; }
- }
+ AppRef App { get; set; }
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/IPartWithDevice.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/IPartWithDevice.cs
index 81f65808c..faf75e259 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/IPartWithDevice.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/IPartWithDevice.cs
@@ -1,9 +1,8 @@
using EarTrumpet.Actions.DataModel.Serialization;
-namespace EarTrumpet.Actions.DataModel
+namespace EarTrumpet.Actions.DataModel;
+
+public interface IPartWithDevice
{
- public interface IPartWithDevice
- {
- Device Device { get; set; }
- }
+ Device Device { get; set; }
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/IPartWithText.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/IPartWithText.cs
index abf7e43cd..2ad0b49d1 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/IPartWithText.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/IPartWithText.cs
@@ -1,7 +1,6 @@
-namespace EarTrumpet.Actions.DataModel
+namespace EarTrumpet.Actions.DataModel;
+
+internal interface IPartWithText
{
- interface IPartWithText
- {
- string Text { get; set; }
- }
+ string Text { get; set; }
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/IPartWithVolume.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/IPartWithVolume.cs
index aae5e4b56..74eca4234 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/IPartWithVolume.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/IPartWithVolume.cs
@@ -1,7 +1,10 @@
-namespace EarTrumpet.Actions.DataModel
+using EarTrumpet.Actions.DataModel.Enum;
+
+namespace EarTrumpet.Actions.DataModel;
+
+public interface IPartWithVolume
{
- public interface IPartWithVolume
- {
- double Volume { get; set; }
- }
+ double Volume { get; set; }
+ VolumeUnit Unit { get; set; }
+ SetVolumeKind Option { get; set; }
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/LocalVariablesContainer.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/LocalVariablesContainer.cs
index 3d9ab8587..c7448e2fe 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/LocalVariablesContainer.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/LocalVariablesContainer.cs
@@ -1,21 +1,20 @@
using EarTrumpet.DataModel.Storage;
-namespace EarTrumpet.Actions.DataModel
+namespace EarTrumpet.Actions.DataModel;
+
+public class LocalVariablesContainer
{
- public class LocalVariablesContainer
+ public bool this[string key]
{
- public bool this[string key]
- {
- get => _settings.Get($"{s_localVariablePrefix}{key}", false);
- set => _settings.Set($"{s_localVariablePrefix}{key}", value);
- }
+ get => _settings.Get($"{s_localVariablePrefix}{key}", false);
+ set => _settings.Set($"{s_localVariablePrefix}{key}", value);
+ }
- private const string s_localVariablePrefix = "LocalVariable.";
- private readonly ISettingsBag _settings;
+ private const string s_localVariablePrefix = "LocalVariable.";
+ private readonly ISettingsBag _settings;
- public LocalVariablesContainer(ISettingsBag settings)
- {
- _settings = settings;
- }
+ public LocalVariablesContainer(ISettingsBag settings)
+ {
+ _settings = settings;
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Part.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Part.cs
index 0a02c809a..e1290201a 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Part.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Part.cs
@@ -1,4 +1,3 @@
-namespace EarTrumpet.Actions.DataModel
-{
- public abstract class Part { }
-}
+namespace EarTrumpet.Actions.DataModel;
+
+public abstract class Part { }
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/ProcessWatcher.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/ProcessWatcher.cs
index 1ecf313c9..7cf40f6d6 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/ProcessWatcher.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/ProcessWatcher.cs
@@ -1,150 +1,161 @@
-using EarTrumpet.Interop;
-using EarTrumpet.Actions.Interop.Helpers;
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Globalization;
using System.Linq;
using System.Threading;
+using EarTrumpet.Actions.Interop.Helpers;
+using Windows.Win32;
-namespace EarTrumpet.Actions.DataModel
+namespace EarTrumpet.Actions.DataModel;
+
+public sealed class ProcessWatcher : IDisposable
{
- public class ProcessWatcher
+ private class ProcessInfo
{
- class ProcessInfo
- {
- public List Windows = new List();
- }
+ public List Windows = [];
+ }
- class WatcherInfo
- {
- public List StartCallbacks = new List();
- public List StopCallbacks = new List();
- public Dictionary RunningProcesses = new Dictionary();
- }
+ private class WatcherInfo
+ {
+ public List StartCallbacks = [];
+ public List StopCallbacks = [];
+ public Dictionary RunningProcesses = [];
+ }
- public static ProcessWatcher Current { get; } = new ProcessWatcher();
+ public static ProcessWatcher Current { get; } = new ProcessWatcher();
- WindowWatcher _watcher = new WindowWatcher();
- Dictionary _info = new Dictionary();
+ private readonly WindowWatcher _watcher = new();
+ private Dictionary _info = [];
+
+ public ProcessWatcher()
+ {
+ _watcher.WindowCreated += OnWindowCreated;
+ }
- public ProcessWatcher()
+ // Used only by the condition processor, so we use realtime data only.
+ public static bool IsRunning(string procName)
+ {
+ try
{
- _watcher.WindowCreated += OnWindowCreated;
+ return Process.GetProcessesByName(procName).Length != 0;
}
-
- // Used only by the condition processor, so we use realtime data only.
- public bool IsRunning(string procName)
+ catch (Exception ex)
{
- try
- {
- return Process.GetProcessesByName(procName).Any();
- }
- catch (Exception ex)
- {
- Trace.WriteLine(ex);
- }
- return false;
+ Trace.WriteLine(ex);
}
+ return false;
+ }
- private void OnWindowCreated(IntPtr hwnd)
+ private void OnWindowCreated(IntPtr hwnd)
+ {
+ try
{
- try
+ var pid = 0U;
+ unsafe
{
- User32.GetWindowThreadProcessId(hwnd, out uint pid);
-
- using (var proc = Process.GetProcessById((int)pid))
- {
- if (_info.ContainsKey(proc.ProcessName.ToLower()))
- {
- FoundNewRelevantProcess(proc);
- }
- }
+ _ = PInvoke.GetWindowThreadProcessId(new HWND(hwnd.ToPointer()), &pid);
}
- catch (Exception ex)
+
+ using var proc = Process.GetProcessById((int)pid);
+ if (_info.ContainsKey(proc.ProcessName.ToLowerInvariant()))
{
- Trace.WriteLine(ex);
+ FoundNewRelevantProcess(proc);
}
}
+ catch (Exception ex)
+ {
+ Trace.WriteLine(ex);
+ }
+ }
+
+ private bool FoundNewRelevantProcess(Process proc)
+ {
+ var info = _info[proc.ProcessName.ToLowerInvariant()];
- bool FoundNewRelevantProcess(Process proc)
+ if (!info.RunningProcesses.ContainsKey(proc.Id))
{
- var info = _info[proc.ProcessName.ToLower()];
+ var procInfo = new ProcessInfo();
+ info.RunningProcesses[proc.Id] = procInfo;
+
+ var procId = proc.Id;
- if (!info.RunningProcesses.ContainsKey(proc.Id))
+ new Thread(() =>
{
- var procInfo = new ProcessInfo();
- info.RunningProcesses[proc.Id] = procInfo;
-
- new Thread(() =>
- {
- Thread.CurrentThread.IsBackground = true;
-
- var procName = proc.ProcessName;
- proc.WaitForExit();
- Trace.WriteLine($"ProcessWatcher STOP {procName}");
- info.StopCallbacks.ForEach(s => s.Invoke());
- }).Start();
-
- Trace.WriteLine($"ProcessWatcher START {proc.ProcessName}");
- info.StartCallbacks.ForEach(s => s.Invoke());
- return true;
- }
- return false;
+ Thread.CurrentThread.IsBackground = true;
+
+ var actualProc = Process.GetProcessById(procId);
+
+ var procName = actualProc.ProcessName;
+ actualProc.WaitForExit();
+ Trace.WriteLine($"ProcessWatcher STOP {procName}");
+ App.Current.Dispatcher.Invoke(() => info.StopCallbacks.ForEach(s => s.Invoke()));
+ }).Start();
+
+ Trace.WriteLine($"ProcessWatcher START {proc.ProcessName}");
+ info.StartCallbacks.ForEach(s => s.Invoke());
+ return true;
}
+ return false;
+ }
- public void RegisterStop(string text, Action callback)
- {
- Trace.WriteLine($"ProcessWatcher RegisterStop {text}");
- text = text.ToLower();
- WatcherInfo info = _info.ContainsKey(text) ? _info[text] : _info[text] = new WatcherInfo();
- info.StopCallbacks.Add(callback);
+ public void RegisterStop(string text, Action callback)
+ {
+ Trace.WriteLine($"ProcessWatcher RegisterStop {text}");
+ text = text.ToLower(CultureInfo.CurrentCulture);
+ var info = _info.TryGetValue(text, out var value) ? value : _info[text] = new WatcherInfo();
+ info.StopCallbacks.Add(callback);
- try
- {
- var runningProcs = Process.GetProcessesByName(text);
- foreach (var proc in runningProcs)
- {
- FoundNewRelevantProcess(proc);
- }
- }
- catch (Exception ex)
+ try
+ {
+ var runningProcs = Process.GetProcessesByName(text);
+ foreach (var proc in runningProcs)
{
- Trace.WriteLine(ex);
+ FoundNewRelevantProcess(proc);
}
}
-
- public void RegisterStart(string text, Action callback)
+ catch (Exception ex)
{
- Trace.WriteLine($"ProcessWatcher RegisterStart {text}");
- text = text.ToLower();
- WatcherInfo info = _info.ContainsKey(text) ? _info[text] : new WatcherInfo();
- info.StartCallbacks.Add(callback);
+ Trace.WriteLine(ex);
+ }
+ }
+
+ public void RegisterStart(string text, Action callback)
+ {
+ Trace.WriteLine($"ProcessWatcher RegisterStart {text}");
+ text = text.ToLower(CultureInfo.CurrentCulture);
+ var info = _info.TryGetValue(text, out var value) ? value : _info[text] = new WatcherInfo();
+ info.StartCallbacks.Add(callback);
- try
+ try
+ {
+ var didSignal = false;
+ var runningProcs = Process.GetProcessesByName(text);
+ foreach (var proc in runningProcs)
{
- bool didSignal = false;
- var runningProcs = Process.GetProcessesByName(text);
- foreach (var proc in runningProcs)
- {
- didSignal = didSignal || FoundNewRelevantProcess(proc);
- }
-
- if (runningProcs.Any() && !didSignal)
- {
- // We were already watching so we didn't signal but the process is running.
- callback();
- }
+ didSignal = didSignal || FoundNewRelevantProcess(proc);
}
- catch (Exception ex)
+
+ if (runningProcs.Length != 0 && !didSignal)
{
- Trace.WriteLine(ex);
+ // We were already watching so we didn't signal but the process is running.
+ callback();
}
}
-
- public void Clear()
+ catch (Exception ex)
{
- Trace.WriteLine("ProcessWatcher Clear");
- _info = new Dictionary();
+ Trace.WriteLine(ex);
}
}
+
+ public void Clear()
+ {
+ Trace.WriteLine("ProcessWatcher Clear");
+ _info = [];
+ }
+
+ public void Dispose()
+ {
+ _watcher.Dispose();
+ }
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Processing/ActionProcessor.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Processing/ActionProcessor.cs
index 95a38a93c..e9b24325c 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Processing/ActionProcessor.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Processing/ActionProcessor.cs
@@ -1,197 +1,225 @@
-using EarTrumpet.Interop;
-using EarTrumpet.Actions.DataModel.Serialization;
-using EarTrumpet.Actions.DataModel.Enum;
-using System;
+using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
-using System.Text;
+using EarTrumpet.Actions.DataModel.Enum;
+using EarTrumpet.Actions.DataModel.Serialization;
+using EarTrumpet.DataModel.AppInformation;
using EarTrumpet.DataModel.Audio;
using EarTrumpet.DataModel.WindowsAudio;
-using EarTrumpet.DataModel.AppInformation;
+using EarTrumpet.Extensions;
+using Windows.Win32;
-namespace EarTrumpet.Actions.DataModel.Processing
+namespace EarTrumpet.Actions.DataModel.Processing;
+
+internal class ActionProcessor
{
- class ActionProcessor
+ public static void Invoke(BaseAction a)
{
- public static void Invoke(BaseAction a)
+ Trace.WriteLine($"ActionProcessor Invoke: {a.GetType().Name}");
+ if (a is SetVariableAction setVariableAction)
+ {
+ EarTrumpetActionsAddon.Current.LocalVariables[setVariableAction.Text] = (setVariableAction.Value == BoolValue.True);
+ }
+ else if (a is SetDefaultDeviceAction setDefaultDeviceAction)
{
- Trace.WriteLine($"ActionProcessor Invoke: {a.GetType().Name}");
- if (a is SetVariableAction)
+ var mgr = WindowsAudioFactory.Create((AudioDeviceKind)System.Enum.Parse(typeof(AudioDeviceKind), setDefaultDeviceAction.Device.Kind));
+
+ var dev = mgr.Devices.FirstOrDefault(d => d.Id == setDefaultDeviceAction.Device.Id);
+ if (dev != null)
{
- EarTrumpetActionsAddon.Current.LocalVariables[((SetVariableAction)a).Text] = (((SetVariableAction)a).Value == BoolValue.True);
+ mgr.Default = dev;
}
- else if (a is SetDefaultDeviceAction)
- {
- var mgr = WindowsAudioFactory.Create((AudioDeviceKind)System.Enum.Parse(typeof(AudioDeviceKind), ((SetDefaultDeviceAction)a).Device.Kind));
+ }
+ else if (a is SetAppVolumeAction setAppVolumeAction)
+ {
+ var mgr = WindowsAudioFactory.Create((AudioDeviceKind)System.Enum.Parse(typeof(AudioDeviceKind), ((SetAppVolumeAction)a).Device.Kind));
- var dev = mgr.Devices.FirstOrDefault(d => d.Id == ((SetDefaultDeviceAction)a).Device.Id);
- if (dev != null)
- {
- mgr.Default = dev;
- }
- }
- else if (a is SetAppVolumeAction)
+ var device = (setAppVolumeAction.Device?.Id == null) ?
+ mgr.Default : mgr.Devices.FirstOrDefault(d => d.Id == setAppVolumeAction.Device.Id);
+ if (device != null)
{
- var action = (SetAppVolumeAction)a;
- var mgr = WindowsAudioFactory.Create((AudioDeviceKind)System.Enum.Parse(typeof(AudioDeviceKind), ((SetAppVolumeAction)a).Device.Kind));
-
- var device = (action.Device?.Id == null) ?
- mgr.Default : mgr.Devices.FirstOrDefault(d => d.Id == action.Device.Id);
- if (device != null)
+ if (setAppVolumeAction.App.Id == AppRef.ForegroundAppId)
{
- if (action.App.Id == AppRef.ForegroundAppId)
+ var app = FindForegroundApp(device.Groups);
+ if (app != null)
{
- var app = FindForegroundApp(device.Groups);
- if (app != null)
- {
- DoAudioAction(action.Option, app, action);
- }
+ DoAudioAction(setAppVolumeAction.Option, app, setAppVolumeAction);
}
- else
+ }
+ else
+ {
+ foreach (var app in device.Groups.Where(app => setAppVolumeAction.App.Id == AppRef.EveryAppId || app.AppId == setAppVolumeAction.App.Id))
{
- foreach (var app in device.Groups.Where(app => action.App.Id == AppRef.EveryAppId || app.AppId == action.App.Id))
- {
- DoAudioAction(action.Option, app, action);
- }
+ DoAudioAction(setAppVolumeAction.Option, app, setAppVolumeAction);
}
}
}
- else if (a is SetAppMuteAction)
- {
- var action = (SetAppMuteAction)a;
- var mgr = WindowsAudioFactory.Create((AudioDeviceKind)System.Enum.Parse(typeof(AudioDeviceKind), ((SetAppMuteAction)a).Device.Kind));
+ }
+ else if (a is SetAppMuteAction setAppMuteAction)
+ {
+ var mgr = WindowsAudioFactory.Create((AudioDeviceKind)System.Enum.Parse(typeof(AudioDeviceKind), ((SetAppMuteAction)a).Device.Kind));
- var device = (action.Device?.Id == null) ?
- mgr.Default : mgr.Devices.FirstOrDefault(d => d.Id == action.Device.Id);
- if (device != null)
+ var device = (setAppMuteAction.Device?.Id == null) ?
+ mgr.Default : mgr.Devices.FirstOrDefault(d => d.Id == setAppMuteAction.Device.Id);
+ if (device != null)
+ {
+ if (setAppMuteAction.App.Id == AppRef.ForegroundAppId)
{
- if (action.App.Id == AppRef.ForegroundAppId)
+ var app = FindForegroundApp(device.Groups);
+ if (app != null)
{
- var app = FindForegroundApp(device.Groups);
- if (app != null)
- {
- DoAudioAction(action.Option, app);
- }
+ DoAudioAction(setAppMuteAction.Option, app);
}
- else
+ }
+ else
+ {
+ foreach (var app in device.Groups.Where(app => setAppMuteAction.App.Id == AppRef.EveryAppId || app.AppId == setAppMuteAction.App.Id))
{
- foreach (var app in device.Groups.Where(app => action.App.Id == AppRef.EveryAppId || app.AppId == action.App.Id))
- {
- DoAudioAction(action.Option, app);
- }
+ DoAudioAction(setAppMuteAction.Option, app);
}
}
}
- else if (a is SetDeviceVolumeAction)
- {
- var action = (SetDeviceVolumeAction)a;
-
- var mgr = WindowsAudioFactory.Create((AudioDeviceKind)System.Enum.Parse(typeof(AudioDeviceKind), ((SetDeviceVolumeAction)a).Device.Kind));
+ }
+ else if (a is SetDeviceVolumeAction setDeviceVolumeAction)
+ {
+ var mgr = WindowsAudioFactory.Create((AudioDeviceKind)System.Enum.Parse(typeof(AudioDeviceKind), ((SetDeviceVolumeAction)a).Device.Kind));
- var device = (action.Device?.Id == null) ?
- mgr.Default : mgr.Devices.FirstOrDefault(d => d.Id == action.Device.Id);
- if (device != null)
- {
- DoAudioAction(action.Option, device, action);
- }
- }
- else if (a is SetDeviceMuteAction)
+ var device = (setDeviceVolumeAction.Device?.Id == null) ?
+ mgr.Default : mgr.Devices.FirstOrDefault(d => d.Id == setDeviceVolumeAction.Device.Id);
+ if (device != null)
{
- var action = (SetDeviceMuteAction)a;
- var mgr = WindowsAudioFactory.Create((AudioDeviceKind)System.Enum.Parse(typeof(AudioDeviceKind), ((SetDeviceMuteAction)a).Device.Kind));
-
- var device = (action.Device?.Id == null) ?
- mgr.Default : mgr.Devices.FirstOrDefault(d => d.Id == action.Device.Id);
- if (device != null)
- {
- DoAudioAction(action.Option, device);
- }
+ DoAudioAction(setDeviceVolumeAction.Option, device, setDeviceVolumeAction);
}
- else
+ }
+ else if (a is SetDeviceMuteAction setDeviceMuteAction)
+ {
+ var mgr = WindowsAudioFactory.Create((AudioDeviceKind)System.Enum.Parse(typeof(AudioDeviceKind), ((SetDeviceMuteAction)a).Device.Kind));
+
+ var device = (setDeviceMuteAction.Device?.Id == null) ?
+ mgr.Default : mgr.Devices.FirstOrDefault(d => d.Id == setDeviceMuteAction.Device.Id);
+ if (device != null)
{
- throw new NotImplementedException();
+ DoAudioAction(setDeviceMuteAction.Option, device);
}
}
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
- private static IAudioDeviceSession FindForegroundApp(ObservableCollection groups)
+ private static IAudioDeviceSession FindForegroundApp(ObservableCollection groups)
+ {
+ var hWnd = PInvoke.GetForegroundWindow();
+ if (hWnd == (HWND)null)
{
- var hWnd = User32.GetForegroundWindow();
- var foregroundClassName = new StringBuilder(User32.MAX_CLASSNAME_LENGTH);
- User32.GetClassName(hWnd, foregroundClassName, foregroundClassName.Capacity);
+ Trace.WriteLine($"ActionProcessor FindForegroundApp: No Window (1)");
+ return null;
+ }
- if (hWnd == IntPtr.Zero)
+ var className = string.Empty;
+ unsafe
+ {
+ Span classNameBuffer = stackalloc char[(int)PInvoke.MAX_CLASS_NAME_LEN];
+ fixed (char* pClassNameBuffer = classNameBuffer)
{
- Trace.WriteLine($"ActionProcessor FindForegroundApp: No Window (1)");
- return null;
+ _ = PInvoke.GetClassName(hWnd, pClassNameBuffer, classNameBuffer.Length);
+ className = new PWSTR(pClassNameBuffer).ToString();
}
+ }
- // ApplicationFrameWindow.exe, find the real hosted process in the child CoreWindow.
- if (foregroundClassName.ToString() == "ApplicationFrameWindow")
- {
- hWnd = User32.FindWindowEx(hWnd, IntPtr.Zero, "Windows.UI.Core.CoreWindow", IntPtr.Zero);
- }
+ // ApplicationFrameWindow.exe, find the real hosted process in the child CoreWindow.
+ if (className.ToString() == "ApplicationFrameWindow")
+ {
+ hWnd = PInvoke.FindWindowEx(hWnd, (HWND)null, "Windows.UI.Core.CoreWindow", null);
+ }
+ if (hWnd == (HWND)null)
+ {
+ Trace.WriteLine($"ActionProcessor FindForegroundApp: No Window (2)");
+ return null;
+ }
- if (hWnd == IntPtr.Zero)
- {
- Trace.WriteLine($"ActionProcessor FindForegroundApp: No Window (2)");
- return null;
- }
+ var processId = 0U;
+ unsafe
+ {
+ _ = PInvoke.GetWindowThreadProcessId(hWnd, &processId);
+ }
- User32.GetWindowThreadProcessId(hWnd, out uint processId);
+ try
+ {
+ var appInfo = AppInformationFactory.CreateForProcess(processId);
- try
+ foreach(var group in groups)
{
- var appInfo = AppInformationFactory.CreateForProcess((int)processId);
-
- foreach(var group in groups)
+ if (group.AppId == appInfo.PackageInstallPath || group.AppId == appInfo.AppId)
{
- if (group.AppId == appInfo.PackageInstallPath)
- {
- Trace.WriteLine($"ActionProcessor FindForegroundApp: {group.DisplayName}");
- return group;
- }
+ Trace.WriteLine($"ActionProcessor FindForegroundApp: {group.DisplayName}");
+ return group;
}
}
- catch(Exception ex)
- {
- Trace.WriteLine(ex);
- }
- Trace.WriteLine("ActionProcessor FindForegroundApp Didn't locate foreground app");
- return null;
}
+ catch(Exception ex)
+ {
+ Trace.WriteLine(ex);
+ }
+ Trace.WriteLine("ActionProcessor FindForegroundApp Didn't locate foreground app");
+ return null;
+ }
- private static void DoAudioAction(MuteKind action, IStreamWithVolumeControl stream)
+ private static void DoAudioAction(MuteKind action, IStreamWithVolumeControl stream)
+ {
+ switch (action)
{
- switch (action)
- {
- case MuteKind.Mute:
- stream.IsMuted = true;
- break;
- case MuteKind.ToggleMute:
- stream.IsMuted = !stream.IsMuted;
- break;
- case MuteKind.Unmute:
- stream.IsMuted = false;
- break;
- }
+ case MuteKind.Mute:
+ stream.IsMuted = true;
+ break;
+ case MuteKind.ToggleMute:
+ stream.IsMuted = !stream.IsMuted;
+ break;
+ case MuteKind.Unmute:
+ stream.IsMuted = false;
+ break;
}
+ }
- private static void DoAudioAction(SetVolumeKind action, IStreamWithVolumeControl stream, IPartWithVolume part)
+ private static void DoAudioAction(SetVolumeKind action, IStreamWithVolumeControl stream, IPartWithVolume part)
+ {
+ try
{
- var vol = (float)(part.Volume / 100f);
- switch (action)
+ switch (part.Unit)
{
- case SetVolumeKind.Set:
- stream.Volume = vol;
- break;
- case SetVolumeKind.Increment:
- stream.Volume += vol;
+ case VolumeUnit.Percentage:
+ {
+ var vol = (float)(part.Volume / 100);
+ var prevVol = stream.GetVolumeScalar();
+ stream.SetVolumeScalar(action switch {
+ SetVolumeKind.Set => vol,
+ SetVolumeKind.Increment => (prevVol + vol).Bound(0, 1),
+ SetVolumeKind.Decrement => (prevVol - vol).Bound(0, 1),
+ _ => throw new ArgumentException("Invalid volume action.")
+ });
+ }
break;
- case SetVolumeKind.Decrement:
- stream.Volume -= vol;
+ case VolumeUnit.Decibel:
+ {
+ var vol = (float)part.Volume;
+ var prevVol = stream.GetVolumeLogarithmic();
+ stream.SetVolumeLogarithmic(action switch {
+ SetVolumeKind.Set => vol,
+ SetVolumeKind.Increment => (prevVol + vol).Bound(App.Settings.LogarithmicVolumeMinDb, 0),
+ SetVolumeKind.Decrement => (prevVol - vol).Bound(App.Settings.LogarithmicVolumeMinDb, 0),
+ _ => throw new ArgumentException("Invalid volume action.")
+ });
+ }
break;
+ default:
+ throw new ArgumentException("Invalid volume unit.");
}
}
+ catch (Exception ex) when (ex.Is(HRESULT.AUDCLNT_E_DEVICE_INVALIDATED))
+ {
+ // Expected in some cases.
+ }
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Processing/AudioTriggerManager.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Processing/AudioTriggerManager.cs
index 1bda8c6c0..95e60056f 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Processing/AudioTriggerManager.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Processing/AudioTriggerManager.cs
@@ -6,160 +6,168 @@
using EarTrumpet.DataModel.WindowsAudio;
using EarTrumpet.Extensibility.Shared;
-namespace EarTrumpet.Actions.DataModel.Processing
+namespace EarTrumpet.Actions.DataModel.Processing;
+
+internal class AudioTriggerManager
{
- class AudioTriggerManager
- {
- public event Action Triggered;
+ public event Action Triggered;
+
+ private readonly PlaybackDataModelHost _playbackManager;
+ private readonly IAudioDeviceManager _recordingManager;
+ private readonly List _appTriggers = [];
+ private readonly List _deviceTriggers = [];
+ private IAudioDevice _defaultPlaybackDevice;
+ private IAudioDevice _defaultRecordingDevice;
- private readonly PlaybackDataModelHost _playbackManager;
- private readonly IAudioDeviceManager _recordingManager;
- private readonly List _appTriggers = new List();
- private readonly List _deviceTriggers = new List();
- private IAudioDevice _defaultPlaybackDevice;
- private IAudioDevice _defaultRecordingDevice;
+ public AudioTriggerManager()
+ {
+ _playbackManager = PlaybackDataModelHost.Current;
+ _playbackManager.AppPropertyChanged += OnAppPropertyChanged;
+ _playbackManager.AppAdded += (a) => OnAppAddOrRemove(a, AudioAppEventKind.Added);
+ _playbackManager.AppRemoved += (a) => OnAppAddOrRemove(a, AudioAppEventKind.Removed);
+ _playbackManager.DeviceAdded += (d) => OnDeviceAddOrRemove(d, AudioDeviceEventKind.Added);
+ _playbackManager.DeviceRemoved += (d) => OnDeviceAddOrRemove(d, AudioDeviceEventKind.Removed);
+ _playbackManager.DeviceManager.DefaultChanged += PlaybackDeviceManager_DefaultChanged;
+ _defaultPlaybackDevice = _playbackManager.DeviceManager.Default;
+
+ _recordingManager = WindowsAudioFactory.Create(AudioDeviceKind.Recording);
+ _recordingManager.DefaultChanged += RecordingMgr_DefaultChanged;
+ _defaultRecordingDevice = _recordingManager.Default;
+ }
- public AudioTriggerManager()
+ public void Register(BaseTrigger trigger)
+ {
+ if (trigger is DeviceEventTrigger)
{
- _playbackManager = PlaybackDataModelHost.Current;
- _playbackManager.AppPropertyChanged += OnAppPropertyChanged;
- _playbackManager.AppAdded += (a) => OnAppAddOrRemove(a, AudioAppEventKind.Added);
- _playbackManager.AppRemoved += (a) => OnAppAddOrRemove(a, AudioAppEventKind.Removed);
- _playbackManager.DeviceAdded += (d) => OnDeviceAddOrRemove(d, AudioDeviceEventKind.Added);
- _playbackManager.DeviceRemoved += (d) => OnDeviceAddOrRemove(d, AudioDeviceEventKind.Removed);
- _playbackManager.DeviceManager.DefaultChanged += PlaybackDeviceManager_DefaultChanged;
- _defaultPlaybackDevice = _playbackManager.DeviceManager.Default;
-
- _recordingManager = WindowsAudioFactory.Create(AudioDeviceKind.Recording);
- _recordingManager.DefaultChanged += RecordingMgr_DefaultChanged;
- _defaultRecordingDevice = _recordingManager.Default;
+ _deviceTriggers.Add((DeviceEventTrigger)trigger);
}
-
- public void Register(BaseTrigger trigger)
+ else if (trigger is AppEventTrigger)
{
- if (trigger is DeviceEventTrigger)
- {
- _deviceTriggers.Add((DeviceEventTrigger)trigger);
- }
- else if (trigger is AppEventTrigger)
- {
- _appTriggers.Add((AppEventTrigger)trigger);
- }
- else throw new NotImplementedException();
+ _appTriggers.Add((AppEventTrigger)trigger);
}
-
- public void Clear()
+ else
{
- _appTriggers.Clear();
- _deviceTriggers.Clear();
+ throw new NotImplementedException();
}
+ }
+
+ public void Clear()
+ {
+ _appTriggers.Clear();
+ _deviceTriggers.Clear();
+ }
- private void PlaybackDeviceManager_DefaultChanged(object sender, EarTrumpet.DataModel.Audio.IAudioDevice newDefault)
+ private void PlaybackDeviceManager_DefaultChanged(object sender, EarTrumpet.DataModel.Audio.IAudioDevice newDefault)
+ {
+ if (newDefault == null)
{
- if (newDefault == null) return;
+ return;
+ }
- ProcessDefaultChanged(newDefault);
+ ProcessDefaultChanged(newDefault);
- _defaultPlaybackDevice = newDefault;
- }
+ _defaultPlaybackDevice = newDefault;
+ }
- private void RecordingMgr_DefaultChanged(object sender, IAudioDevice newDefault)
+ private void RecordingMgr_DefaultChanged(object sender, IAudioDevice newDefault)
+ {
+ if (newDefault == null)
{
- if (newDefault == null) return;
+ return;
+ }
- ProcessDefaultChanged(newDefault);
+ ProcessDefaultChanged(newDefault);
- _defaultRecordingDevice = newDefault;
- }
+ _defaultRecordingDevice = newDefault;
+ }
- private void ProcessDefaultChanged(IAudioDevice newDefault)
+ private void ProcessDefaultChanged(IAudioDevice newDefault)
+ {
+ foreach (var trigger in _deviceTriggers)
{
- foreach (var trigger in _deviceTriggers)
+ if (trigger.Device.Id == _defaultPlaybackDevice?.Id &&
+ trigger.Option == AudioDeviceEventKind.LeavingDefault)
{
- if (trigger.Device.Id == _defaultPlaybackDevice?.Id &&
- trigger.Option == AudioDeviceEventKind.LeavingDefault)
- {
- Triggered?.Invoke(trigger);
- }
+ Triggered?.Invoke(trigger);
+ }
- if (trigger.Device.Id == newDefault.Id &&
- trigger.Option == AudioDeviceEventKind.BecomingDefault)
- {
- Triggered?.Invoke(trigger);
- }
+ if (trigger.Device.Id == newDefault.Id &&
+ trigger.Option == AudioDeviceEventKind.BecomingDefault)
+ {
+ Triggered?.Invoke(trigger);
}
}
+ }
- private void OnDeviceAddOrRemove(IAudioDevice device, AudioDeviceEventKind option)
+ private void OnDeviceAddOrRemove(IAudioDevice device, AudioDeviceEventKind option)
+ {
+ foreach (var trigger in _deviceTriggers)
{
- foreach (var trigger in _deviceTriggers)
+ if (trigger.Option == option)
{
- if (trigger.Option == option)
+ // Default device: not supported
+ if (trigger.Device.Id == device.Id)
{
- // Default device: not supported
- if (trigger.Device.Id == device.Id)
- {
- Triggered?.Invoke(trigger);
- }
+ Triggered?.Invoke(trigger);
}
}
}
+ }
- private void OnAppAddOrRemove(IAudioDeviceSession app, AudioAppEventKind option)
+ private void OnAppAddOrRemove(IAudioDeviceSession app, AudioAppEventKind option)
+ {
+ foreach (var trigger in _appTriggers)
{
- foreach (var trigger in _appTriggers)
+ if (trigger.Option == option)
{
- if (trigger.Option == option)
+ var device = app.Parent;
+ if ((trigger.Device?.Id == null && device == _playbackManager.DeviceManager.Default) ||
+ trigger.Device?.Id == device.Id)
{
- var device = app.Parent;
- if ((trigger.Device?.Id == null && device == _playbackManager.DeviceManager.Default) ||
- trigger.Device?.Id == device.Id)
+ if (trigger.App.Id == AppRef.EveryAppId || trigger.App.Id == app.AppId)
{
- if (trigger.App.Id == app.AppId)
- {
- Triggered?.Invoke(trigger);
- }
+ Triggered?.Invoke(trigger);
}
}
}
}
+ }
- private void OnAppPropertyChanged(IAudioDeviceSession app, string propertyName)
+ private void OnAppPropertyChanged(IAudioDeviceSession app, string propertyName)
+ {
+ foreach (var trigger in _appTriggers)
{
- foreach (var trigger in _appTriggers)
+ var device = app.Parent;
+ if ((trigger.Device?.Id == null && device == _playbackManager.DeviceManager.Default) || trigger.Device?.Id == device.Id)
{
- var device = app.Parent;
- if ((trigger.Device?.Id == null && device == _playbackManager.DeviceManager.Default) || trigger.Device?.Id == device.Id)
+ if (trigger.App.Id == app.AppId)
{
- if (trigger.App.Id == app.AppId)
+ switch (trigger.Option)
{
- switch (trigger.Option)
- {
- case AudioAppEventKind.Muted:
- if (propertyName == nameof(app.IsMuted) && app.IsMuted)
- {
- Triggered?.Invoke(trigger);
- }
- break;
- case AudioAppEventKind.Unmuted:
- if (propertyName == nameof(app.IsMuted) && !app.IsMuted)
- {
- Triggered?.Invoke(trigger);
- }
- break;
- case AudioAppEventKind.PlayingSound:
- if (propertyName == nameof(app.State) && app.State == SessionState.Active)
- {
- Triggered?.Invoke(trigger);
- }
- break;
- case AudioAppEventKind.NotPlayingSound:
- if (propertyName == nameof(app.State) && app.State != SessionState.Active)
- {
- Triggered?.Invoke(trigger);
- }
- break;
- }
+ case AudioAppEventKind.Muted:
+ if (propertyName == nameof(app.IsMuted) && app.IsMuted)
+ {
+ Triggered?.Invoke(trigger);
+ }
+ break;
+ case AudioAppEventKind.Unmuted:
+ if (propertyName == nameof(app.IsMuted) && !app.IsMuted)
+ {
+ Triggered?.Invoke(trigger);
+ }
+ break;
+ case AudioAppEventKind.PlayingSound:
+ if (propertyName == nameof(app.State) && app.State == SessionState.Active)
+ {
+ Triggered?.Invoke(trigger);
+ }
+ break;
+ case AudioAppEventKind.NotPlayingSound:
+ if (propertyName == nameof(app.State) && app.State != SessionState.Active)
+ {
+ Triggered?.Invoke(trigger);
+ }
+ break;
}
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Processing/ConditionProcessor.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Processing/ConditionProcessor.cs
index 46533879b..1baf2f486 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Processing/ConditionProcessor.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Processing/ConditionProcessor.cs
@@ -3,45 +3,44 @@
using System;
using EarTrumpet.DataModel.WindowsAudio;
-namespace EarTrumpet.Actions.DataModel.Processing
+namespace EarTrumpet.Actions.DataModel.Processing;
+
+internal class ConditionProcessor
{
- class ConditionProcessor
+ public static bool IsMet(BaseCondition condition)
{
- public static bool IsMet(BaseCondition condition)
+ if (condition is ProcessCondition)
{
- if (condition is ProcessCondition)
+ var isProcessRunning = ProcessWatcher.IsRunning(((ProcessCondition)condition).Text);
+ switch (((ProcessCondition)condition).Option)
{
- bool isProcessRunning = ProcessWatcher.Current.IsRunning(((ProcessCondition)condition).Text);
- switch (((ProcessCondition)condition).Option)
- {
- case ProcessStateKind.Running:
- return isProcessRunning;
- case ProcessStateKind.NotRunning:
- return !isProcessRunning;
- default:
- throw new NotImplementedException();
- }
+ case ProcessStateKind.Running:
+ return isProcessRunning;
+ case ProcessStateKind.NotRunning:
+ return !isProcessRunning;
+ default:
+ throw new NotImplementedException();
}
- else if (condition is DefaultDeviceCondition)
- {
- var mgr = WindowsAudioFactory.Create((AudioDeviceKind)System.Enum.Parse(typeof(AudioDeviceKind), ((DefaultDeviceCondition)condition).Device.Kind));
+ }
+ else if (condition is DefaultDeviceCondition)
+ {
+ var mgr = WindowsAudioFactory.Create((AudioDeviceKind)System.Enum.Parse(typeof(AudioDeviceKind), ((DefaultDeviceCondition)condition).Device.Kind));
- var isDeviceCurrentlyDefault = ((DefaultDeviceCondition)condition).Device.Id == mgr.Default?.Id;
- switch (((DefaultDeviceCondition)condition).Option)
- {
- case ComparisonBoolKind.Is:
- return isDeviceCurrentlyDefault;
- case ComparisonBoolKind.IsNot:
- return !isDeviceCurrentlyDefault;
- default:
- throw new NotImplementedException();
- }
- }
- else if (condition is VariableCondition)
+ var isDeviceCurrentlyDefault = ((DefaultDeviceCondition)condition).Device.Id == mgr.Default?.Id;
+ switch (((DefaultDeviceCondition)condition).Option)
{
- return (EarTrumpetActionsAddon.Current.LocalVariables[((VariableCondition)condition).Text] == (((VariableCondition)condition).Value == BoolValue.True));
+ case ComparisonBoolKind.Is:
+ return isDeviceCurrentlyDefault;
+ case ComparisonBoolKind.IsNot:
+ return !isDeviceCurrentlyDefault;
+ default:
+ throw new NotImplementedException();
}
- throw new NotImplementedException();
}
+ else if (condition is VariableCondition)
+ {
+ return (EarTrumpetActionsAddon.Current.LocalVariables[((VariableCondition)condition).Text] == (((VariableCondition)condition).Value == BoolValue.True));
+ }
+ throw new NotImplementedException();
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Processing/TriggerManager.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Processing/TriggerManager.cs
index a2e1da1aa..0af336648 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Processing/TriggerManager.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Processing/TriggerManager.cs
@@ -5,87 +5,89 @@
using System;
using System.Collections.Generic;
-namespace EarTrumpet.Actions.DataModel.Processing
+namespace EarTrumpet.Actions.DataModel.Processing;
+
+internal class TriggerManager
{
- class TriggerManager
- {
- public event Action Triggered;
+ public event Action Triggered;
- private List _eventTriggers = new List();
- private AudioTriggerManager _audioManager;
+ private List _eventTriggers = [];
+ private AudioTriggerManager _audioManager;
- public TriggerManager()
- {
- _audioManager = new AudioTriggerManager();
- _audioManager.Triggered += (t) => Triggered?.Invoke(t);
- }
+ public TriggerManager()
+ {
+ _audioManager = new AudioTriggerManager();
+ _audioManager.Triggered += (t) => Triggered?.Invoke(t);
+ }
- public void Clear()
- {
- ProcessWatcher.Current.Clear();
- _eventTriggers.Clear();
- _audioManager.Clear();
- }
+ public void Clear()
+ {
+ ProcessWatcher.Current.Clear();
+ _eventTriggers.Clear();
+ _audioManager.Clear();
+ }
- public void OnEvent(AddonEventKind evt)
+ public void OnEvent(AddonEventKind evt)
+ {
+ foreach (var trigger in _eventTriggers)
{
- foreach (var trigger in _eventTriggers)
+ if ((trigger.Option == EarTrumpetEventKind.Startup && evt == AddonEventKind.InitializeAddon) ||
+ (trigger.Option == EarTrumpetEventKind.Shutdown && evt == AddonEventKind.AppShuttingDown))
{
- if ((trigger.Option == EarTrumpetEventKind.Startup && evt == AddonEventKind.InitializeAddon) ||
- (trigger.Option == EarTrumpetEventKind.Shutdown && evt == AddonEventKind.AppShuttingDown))
- {
- Triggered?.Invoke(trigger);
- }
+ Triggered?.Invoke(trigger);
}
}
+ }
- public void Register(BaseTrigger trig)
+ public void Register(BaseTrigger trig)
+ {
+ if (trig is ProcessTrigger)
{
- if (trig is ProcessTrigger)
+ var trigger = (ProcessTrigger)trig;
+ if (!string.IsNullOrWhiteSpace(trigger.Text))
{
- var trigger = (ProcessTrigger)trig;
- if (!string.IsNullOrWhiteSpace(trigger.Text))
+ if (trigger.Option == ProcessEventKind.Start)
{
- if (trigger.Option == ProcessEventKind.Start)
- {
- ProcessWatcher.Current.RegisterStart(trigger.Text, () => Triggered?.Invoke(trig));
- }
- else
- {
- ProcessWatcher.Current.RegisterStop(trigger.Text, () => Triggered?.Invoke(trig));
- }
+ ProcessWatcher.Current.RegisterStart(trigger.Text, () => Triggered?.Invoke(trig));
}
- }
- else if (trig is EventTrigger)
- {
- _eventTriggers.Add((EventTrigger)trig);
- }
- else if (trig is DeviceEventTrigger)
- {
- _audioManager.Register(trig);
- }
- else if (trig is AppEventTrigger)
- {
- _audioManager.Register(trig);
- }
- else if (trig is HotkeyTrigger)
- {
- var trigger = (HotkeyTrigger)trig;
-
- HotkeyManager.Current.Register(trigger.Option);
- HotkeyManager.Current.KeyPressed += (data) =>
+ else
{
- if (data.Equals(trigger.Option))
- {
- Triggered?.Invoke(trig);
- }
- };
+ ProcessWatcher.Current.RegisterStop(trigger.Text, () => Triggered?.Invoke(trig));
+ }
}
- else if (trig is ContextMenuTrigger)
+ }
+ else if (trig is EventTrigger)
+ {
+ _eventTriggers.Add((EventTrigger)trig);
+ }
+ else if (trig is DeviceEventTrigger)
+ {
+ _audioManager.Register(trig);
+ }
+ else if (trig is AppEventTrigger)
+ {
+ _audioManager.Register(trig);
+ }
+ else if (trig is HotkeyTrigger)
+ {
+ var trigger = (HotkeyTrigger)trig;
+
+ HotkeyManager.Current.Register(trigger.Option);
+ HotkeyManager.Current.KeyPressed += (data) =>
{
- // Nothing to do.
- }
- else throw new NotImplementedException();
+ if (data.Equals(trigger.Option))
+ {
+ Triggered?.Invoke(trig);
+ }
+ };
+ }
+ else if (trig is ContextMenuTrigger)
+ {
+ // Nothing to do.
+ }
+ else
+ {
+ throw new NotImplementedException();
}
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/Actions.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/Actions.cs
index dae3b3b18..d02ff836d 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/Actions.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/Actions.cs
@@ -1,52 +1,53 @@
using EarTrumpet.Actions.DataModel.Enum;
using System.Xml.Serialization;
-namespace EarTrumpet.Actions.DataModel.Serialization
+namespace EarTrumpet.Actions.DataModel.Serialization;
+
+[XmlInclude(typeof(SetAppVolumeAction))]
+[XmlInclude(typeof(SetAppMuteAction))]
+[XmlInclude(typeof(SetDeviceVolumeAction))]
+[XmlInclude(typeof(SetDeviceMuteAction))]
+[XmlInclude(typeof(SetDefaultDeviceAction))]
+[XmlInclude(typeof(SetVariableAction))]
+public abstract class BaseAction : Part { }
+
+public class SetAppMuteAction : BaseAction, IPartWithDevice, IPartWithApp
+{
+ public Device Device { get; set; }
+ public AppRef App { get; set; }
+ public MuteKind Option { get; set; }
+}
+
+public class SetAppVolumeAction : BaseAction, IPartWithVolume, IPartWithDevice, IPartWithApp
+{
+ public Device Device { get; set; }
+ public AppRef App { get; set; }
+ public SetVolumeKind Option { get; set; }
+ public double Volume { get; set; }
+ public VolumeUnit Unit { get; set; }
+}
+
+public class SetDefaultDeviceAction : BaseAction, IPartWithDevice
+{
+ public Device Device { get; set; }
+}
+
+public class SetDeviceMuteAction : BaseAction, IPartWithDevice
+{
+ public Device Device { get; set; }
+ public MuteKind Option { get; set; }
+}
+
+public class SetDeviceVolumeAction : BaseAction, IPartWithDevice, IPartWithVolume
+{
+ public Device Device { get; set; }
+ public SetVolumeKind Option { get; set; }
+ public double Volume { get; set; }
+ public VolumeUnit Unit { get; set; }
+}
+
+public class SetVariableAction : BaseAction, IPartWithText
{
- [XmlInclude(typeof(SetAppVolumeAction))]
- [XmlInclude(typeof(SetAppMuteAction))]
- [XmlInclude(typeof(SetDeviceVolumeAction))]
- [XmlInclude(typeof(SetDeviceMuteAction))]
- [XmlInclude(typeof(SetDefaultDeviceAction))]
- [XmlInclude(typeof(SetVariableAction))]
- public abstract class BaseAction : Part { }
-
- public class SetAppMuteAction : BaseAction, IPartWithDevice, IPartWithApp
- {
- public Device Device { get; set; }
- public AppRef App { get; set; }
- public MuteKind Option { get; set; }
- }
-
- public class SetAppVolumeAction : BaseAction, IPartWithVolume, IPartWithDevice, IPartWithApp
- {
- public Device Device { get; set; }
- public AppRef App { get; set; }
- public SetVolumeKind Option { get; set; }
- public double Volume { get; set; }
- }
-
- public class SetDefaultDeviceAction : BaseAction, IPartWithDevice
- {
- public Device Device { get; set; }
- }
-
- public class SetDeviceMuteAction : BaseAction, IPartWithDevice
- {
- public Device Device { get; set; }
- public MuteKind Option { get; set; }
- }
-
- public class SetDeviceVolumeAction : BaseAction, IPartWithDevice, IPartWithVolume
- {
- public Device Device { get; set; }
- public SetVolumeKind Option { get; set; }
- public double Volume { get; set; }
- }
-
- public class SetVariableAction : BaseAction, IPartWithText
- {
- public string Text { get; set; }
- public BoolValue Value { get; set; }
- }
+ public string Text { get; set; }
+ public BoolValue Value { get; set; }
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/App.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/App.cs
index c71af15f4..ebf6f7a1b 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/App.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/App.cs
@@ -1,20 +1,19 @@
-namespace EarTrumpet.Actions.DataModel.Serialization
+namespace EarTrumpet.Actions.DataModel.Serialization;
+
+public class AppRef
{
- public class AppRef
- {
- public static readonly string EveryAppId = "EarTrumpet.EveryApp";
- public static readonly string ForegroundAppId = "EarTrumpet.ForegroundApp";
+ public static readonly string EveryAppId = "EarTrumpet.EveryApp";
+ public static readonly string ForegroundAppId = "EarTrumpet.ForegroundApp";
- public string Id { get; set; }
+ public string Id { get; set; }
- public override int GetHashCode()
- {
- return Id == null ? 0 : Id.GetHashCode();
- }
+ public override int GetHashCode()
+ {
+ return Id == null ? 0 : Id.GetHashCode();
+ }
- public bool Equals(AppRef other)
- {
- return other.Id == Id;
- }
+ public bool Equals(AppRef other)
+ {
+ return other.Id == Id;
}
}
\ No newline at end of file
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/Conditions.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/Conditions.cs
index 99b2ae84a..0fcca75f1 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/Conditions.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/Conditions.cs
@@ -1,28 +1,27 @@
using EarTrumpet.Actions.DataModel.Enum;
using System.Xml.Serialization;
-namespace EarTrumpet.Actions.DataModel.Serialization
-{
- [XmlInclude(typeof(DefaultDeviceCondition))]
- [XmlInclude(typeof(ProcessCondition))]
- [XmlInclude(typeof(VariableCondition))]
- public abstract class BaseCondition : Part { }
+namespace EarTrumpet.Actions.DataModel.Serialization;
- public class DefaultDeviceCondition : BaseCondition, IPartWithDevice
- {
- public Device Device { get; set; }
- public ComparisonBoolKind Option { get; set; }
- }
+[XmlInclude(typeof(DefaultDeviceCondition))]
+[XmlInclude(typeof(ProcessCondition))]
+[XmlInclude(typeof(VariableCondition))]
+public abstract class BaseCondition : Part { }
- public class ProcessCondition : BaseCondition, IPartWithText
- {
- public string Text { get; set; }
- public ProcessStateKind Option { get; set; }
- }
+public class DefaultDeviceCondition : BaseCondition, IPartWithDevice
+{
+ public Device Device { get; set; }
+ public ComparisonBoolKind Option { get; set; }
+}
- public class VariableCondition : BaseCondition, IPartWithText
- {
- public string Text { get; set; }
- public BoolValue Value { get; set; }
- }
+public class ProcessCondition : BaseCondition, IPartWithText
+{
+ public string Text { get; set; }
+ public ProcessStateKind Option { get; set; }
+}
+
+public class VariableCondition : BaseCondition, IPartWithText
+{
+ public string Text { get; set; }
+ public BoolValue Value { get; set; }
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/Device.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/Device.cs
index 736fd7bdd..acf5fe1cf 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/Device.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/Device.cs
@@ -1,19 +1,18 @@
-namespace EarTrumpet.Actions.DataModel.Serialization
+namespace EarTrumpet.Actions.DataModel.Serialization;
+
+public class Device
{
- public class Device
- {
- public string Id { get; set; }
+ public string Id { get; set; }
- public string Kind { get; set; }
+ public string Kind { get; set; }
- public override int GetHashCode()
- {
- return Id == null ? 0 : Id.GetHashCode();
- }
+ public override int GetHashCode()
+ {
+ return Id == null ? 0 : Id.GetHashCode();
+ }
- public bool Equals(Device other)
- {
- return other.Id == Id;
- }
+ public bool Equals(Device other)
+ {
+ return other.Id == Id;
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/EarTrumpetAction.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/EarTrumpetAction.cs
index 778e58a64..1ba1f9b4f 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/EarTrumpetAction.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/EarTrumpetAction.cs
@@ -1,14 +1,13 @@
using System;
using System.Collections.ObjectModel;
-namespace EarTrumpet.Actions.DataModel.Serialization
+namespace EarTrumpet.Actions.DataModel.Serialization;
+
+public class EarTrumpetAction
{
- public class EarTrumpetAction
- {
- public string DisplayName { get; set; }
- public Guid Id { get; set; } = Guid.NewGuid();
- public ObservableCollection Triggers { get; set; } = new ObservableCollection();
- public ObservableCollection Conditions { get; set; } = new ObservableCollection();
- public ObservableCollection Actions { get; set; } = new ObservableCollection();
- }
+ public string DisplayName { get; set; }
+ public Guid Id { get; set; } = Guid.NewGuid();
+ public ObservableCollection Triggers { get; set; } = [];
+ public ObservableCollection Conditions { get; set; } = [];
+ public ObservableCollection Actions { get; set; } = [];
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/Triggers.cs b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/Triggers.cs
index 8a15ef5b4..002737a99 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/Triggers.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/DataModel/Serialization/Triggers.cs
@@ -2,44 +2,43 @@
using EarTrumpet.Actions.DataModel.Enum;
using System.Xml.Serialization;
-namespace EarTrumpet.Actions.DataModel.Serialization
+namespace EarTrumpet.Actions.DataModel.Serialization;
+
+[XmlInclude(typeof(EventTrigger))]
+[XmlInclude(typeof(HotkeyTrigger))]
+[XmlInclude(typeof(DeviceEventTrigger))]
+[XmlInclude(typeof(AppEventTrigger))]
+[XmlInclude(typeof(ProcessTrigger))]
+[XmlInclude(typeof(ContextMenuTrigger))]
+public abstract class BaseTrigger : Part { }
+
+public class AppEventTrigger : BaseTrigger, IPartWithDevice, IPartWithApp
+{
+ public Device Device { get; set; }
+ public AppRef App { get; set; }
+ public AudioAppEventKind Option { get; set; }
+}
+
+public class ContextMenuTrigger : BaseTrigger { }
+
+public class DeviceEventTrigger : BaseTrigger, IPartWithDevice
+{
+ public Device Device { get; set; }
+ public AudioDeviceEventKind Option { get; set; }
+}
+
+public class EventTrigger : BaseTrigger
+{
+ public EarTrumpetEventKind Option { get; set; }
+}
+
+public class HotkeyTrigger : BaseTrigger
+{
+ public HotkeyData Option { get; set; } = new HotkeyData();
+}
+
+public class ProcessTrigger : BaseTrigger, IPartWithText
{
- [XmlInclude(typeof(EventTrigger))]
- [XmlInclude(typeof(HotkeyTrigger))]
- [XmlInclude(typeof(DeviceEventTrigger))]
- [XmlInclude(typeof(AppEventTrigger))]
- [XmlInclude(typeof(ProcessTrigger))]
- [XmlInclude(typeof(ContextMenuTrigger))]
- public abstract class BaseTrigger : Part { }
-
- public class AppEventTrigger : BaseTrigger, IPartWithDevice, IPartWithApp
- {
- public Device Device { get; set; }
- public AppRef App { get; set; }
- public AudioAppEventKind Option { get; set; }
- }
-
- public class ContextMenuTrigger : BaseTrigger { }
-
- public class DeviceEventTrigger : BaseTrigger, IPartWithDevice
- {
- public Device Device { get; set; }
- public AudioDeviceEventKind Option { get; set; }
- }
-
- public class EventTrigger : BaseTrigger
- {
- public EarTrumpetEventKind Option { get; set; }
- }
-
- public class HotkeyTrigger : BaseTrigger
- {
- public HotkeyData Option { get; set; } = new HotkeyData();
- }
-
- public class ProcessTrigger : BaseTrigger, IPartWithText
- {
- public string Text { get; set; }
- public ProcessEventKind Option { get; set; }
- }
+ public string Text { get; set; }
+ public ProcessEventKind Option { get; set; }
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/EarTrumpetActionsAddon.cs b/EarTrumpet/Addons/EarTrumpet.Actions/EarTrumpetActionsAddon.cs
index ad8a2ead6..b44b14e72 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/EarTrumpetActionsAddon.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/EarTrumpetActionsAddon.cs
@@ -8,121 +8,120 @@
using EarTrumpet.UI.ViewModels;
using System;
using System.Collections.Generic;
-using System.ComponentModel.Composition;
+using System.Composition;
using System.IO;
using System.Linq;
-namespace EarTrumpet.Actions
+namespace EarTrumpet.Actions;
+
+[Export(typeof(EarTrumpetAddon))]
+public class EarTrumpetActionsAddon : EarTrumpetAddon, IEarTrumpetAddonEvents, IEarTrumpetAddonSettingsPage, IEarTrumpetAddonNotificationAreaContextMenu
{
- [Export(typeof(EarTrumpetAddon))]
- public class EarTrumpetActionsAddon : EarTrumpetAddon, IEarTrumpetAddonEvents, IEarTrumpetAddonSettingsPage, IEarTrumpetAddonNotificationAreaContextMenu
- {
- public static EarTrumpetActionsAddon Current { get; private set; }
- public LocalVariablesContainer LocalVariables { get; private set; }
+ public static EarTrumpetActionsAddon Current { get; private set; }
+ public LocalVariablesContainer LocalVariables { get; private set; }
- public EarTrumpetActionsAddon() : base()
- {
- DisplayName = Properties.Resources.MyActionsText;
- }
+ public EarTrumpetActionsAddon() : base()
+ {
+ DisplayName = Properties.Resources.MyActionsText;
+ }
- public EarTrumpetAction[] Actions
+ public EarTrumpetAction[] Actions
+ {
+ get => _actions;
+ set
{
- get => _actions;
- set
- {
- Settings.Set(c_actionsSettingKey, value);
- LoadAndRegister();
- }
+ Settings.Set(c_actionsSettingKey, value);
+ LoadAndRegister();
}
+ }
- private readonly string c_actionsSettingKey = "ActionsData";
- private EarTrumpetAction[] _actions = new EarTrumpetAction[] { };
- private TriggerManager _triggerManager = new TriggerManager();
+ private readonly string c_actionsSettingKey = "ActionsData";
+ private EarTrumpetAction[] _actions = Array.Empty();
+ private TriggerManager _triggerManager = new();
- public void OnAddonEvent(AddonEventKind evt)
+ public void OnAddonEvent(AddonEventKind evt)
+ {
+ if (evt == AddonEventKind.AddonsInitialized)
{
- if (evt == AddonEventKind.AddonsInitialized)
- {
- Current = this;
- LocalVariables = new LocalVariablesContainer(Settings);
+ Current = this;
+ LocalVariables = new LocalVariablesContainer(Settings);
- _triggerManager.Triggered += OnTriggered;
- LoadAndRegister();
+ _triggerManager.Triggered += OnTriggered;
+ LoadAndRegister();
- _triggerManager.OnEvent(AddonEventKind.InitializeAddon);
- }
- else if (evt == AddonEventKind.AppShuttingDown)
- {
- _triggerManager.OnEvent(AddonEventKind.AppShuttingDown);
- }
+ _triggerManager.OnEvent(AddonEventKind.InitializeAddon);
}
-
- public SettingsCategoryViewModel GetSettingsCategory()
+ else if (evt == AddonEventKind.AppShuttingDown)
{
- LoadAddonResources();
- return new ActionsCategoryViewModel();
+ _triggerManager.OnEvent(AddonEventKind.AppShuttingDown);
}
+ }
- public IEnumerable NotificationAreaContextMenuItems
- {
- get
- {
- var ret = new List();
+ public SettingsCategoryViewModel GetSettingsCategory()
+ {
+ LoadAddonResources();
+ return new ActionsCategoryViewModel();
+ }
- if (EarTrumpetActionsAddon.Current == null)
- {
- return ret;
- }
+ public IEnumerable NotificationAreaContextMenuItems
+ {
+ get
+ {
+ var ret = new List();
- foreach (var item in EarTrumpetActionsAddon.Current.Actions.Where(a => a.Triggers.FirstOrDefault(ax => ax is ContextMenuTrigger) != null))
- {
- ret.Add(new ContextMenuItem
- {
- Glyph = "\xE1CE",
- IsChecked = true,
- DisplayName = item.DisplayName,
- Command = new RelayCommand(() => EarTrumpetActionsAddon.Current.TriggerAction(item))
- });
- }
+ if (EarTrumpetActionsAddon.Current == null)
+ {
return ret;
}
- }
-
- private void LoadAndRegister()
- {
- _triggerManager.Clear();
- _actions = Settings.Get(c_actionsSettingKey, new EarTrumpetAction[] { });
- _actions.SelectMany(a => a.Triggers).ToList().ForEach(t => _triggerManager.Register(t));
- }
- public void Import(string fileName)
- {
- var imported = Serializer.FromString(File.ReadAllText(fileName)).ToList();
- foreach(var imp in imported)
+ foreach (var item in EarTrumpetActionsAddon.Current.Actions.Where(a => a.Triggers.FirstOrDefault(ax => ax is ContextMenuTrigger) != null))
{
- imp.Id = Guid.NewGuid();
+ ret.Add(new ContextMenuItem
+ {
+ Glyph = "\xE1CE",
+ IsChecked = true,
+ DisplayName = item.DisplayName,
+ Command = new RelayCommand(() => TriggerAction(item))
+ });
}
- imported.AddRange(Actions);
- Actions = imported.ToArray();
+ return ret;
}
+ }
- public string Export()
- {
- return Settings.Get(c_actionsSettingKey, "");
- }
+ private void LoadAndRegister()
+ {
+ _triggerManager.Clear();
+ _actions = Settings.Get(c_actionsSettingKey, Array.Empty());
+ _actions.SelectMany(a => a.Triggers).ToList().ForEach(t => _triggerManager.Register(t));
+ }
- private void OnTriggered(BaseTrigger trigger)
+ public void Import(string fileName)
+ {
+ var imported = Serializer.FromString(File.ReadAllText(fileName)).ToList();
+ foreach(var imp in imported)
{
- var action = Actions.FirstOrDefault(a => a.Triggers.Contains(trigger));
- if (action != null && action.Conditions.All(c => ConditionProcessor.IsMet(c)))
- {
- TriggerAction(action);
- }
+ imp.Id = Guid.NewGuid();
}
+ imported.AddRange(Actions);
+ Actions = [.. imported];
+ }
+
+ public string Export()
+ {
+ return Settings.Get(c_actionsSettingKey, "");
+ }
- public void TriggerAction(EarTrumpetAction action)
+ private void OnTriggered(BaseTrigger trigger)
+ {
+ var action = Actions.FirstOrDefault(a => a.Triggers.Contains(trigger));
+ if (action != null && action.Conditions.All(c => ConditionProcessor.IsMet(c)))
{
- action.Actions.ToList().ForEach(a => ActionProcessor.Invoke(a));
+ TriggerAction(action);
}
}
+
+ public static void TriggerAction(EarTrumpetAction action)
+ {
+ action.Actions.ToList().ForEach(a => ActionProcessor.Invoke(a));
+ }
}
\ No newline at end of file
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/Interop/Helpers/WindowWatcher.cs b/EarTrumpet/Addons/EarTrumpet.Actions/Interop/Helpers/WindowWatcher.cs
index dd739d0d8..b89087a33 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/Interop/Helpers/WindowWatcher.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/Interop/Helpers/WindowWatcher.cs
@@ -3,39 +3,44 @@
using System.Diagnostics;
using System.Windows.Forms;
-namespace EarTrumpet.Actions.Interop.Helpers
+namespace EarTrumpet.Actions.Interop.Helpers;
+
+internal class WindowWatcher : IDisposable
{
- class WindowWatcher
- {
- public event Action WindowCreated;
- public event Action WindowDestroyed;
- readonly Win32Window _window;
- readonly uint _ShellNotifyMsg;
+ public event Action WindowCreated;
+ public event Action WindowDestroyed;
- public WindowWatcher()
+ private readonly Win32Window _window;
+ private readonly uint _ShellNotifyMsg;
+
+ public WindowWatcher()
+ {
+ _window = new Win32Window();
+ _window.Initialize(WndProc);
+ _ShellNotifyMsg = User32.RegisterWindowMessageW(User32.SHELLHOOK);
+ if (!User32.RegisterShellHookWindow(_window.Handle))
{
- _window = new Win32Window();
- _window.Initialize(WndProc);
- _ShellNotifyMsg = User32.RegisterWindowMessageW(User32.SHELLHOOK);
- if (!User32.RegisterShellHookWindow(_window.Handle))
- {
- Trace.WriteLine("Failed to register shell hook window");
- }
+ Trace.WriteLine("Failed to register shell hook window");
}
+ }
- void WndProc(Message m)
+ private void WndProc(Message m)
+ {
+ if (m.Msg == _ShellNotifyMsg)
{
- if (m.Msg == _ShellNotifyMsg)
+ if (m.WParam.ToInt32() == User32.HSHELL_WINDOWCREATED)
+ {
+ WindowCreated?.Invoke(m.LParam);
+ }
+ else if (m.WParam.ToInt32() == User32.HSHELL_WINDOWDESTROYED)
{
- if (m.WParam.ToInt32() == User32.HSHELL_WINDOWCREATED)
- {
- WindowCreated?.Invoke(m.LParam);
- }
- else if (m.WParam.ToInt32() == User32.HSHELL_WINDOWDESTROYED)
- {
- WindowDestroyed?.Invoke(m.LParam);
- }
+ WindowDestroyed?.Invoke(m.LParam);
}
}
}
+
+ public void Dispose()
+ {
+ _window.Dispose();
+ }
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/Interop/User32.cs b/EarTrumpet/Addons/EarTrumpet.Actions/Interop/User32.cs
index d6f1757a4..188fde177 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/Interop/User32.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/Interop/User32.cs
@@ -1,19 +1,18 @@
using System;
using System.Runtime.InteropServices;
-namespace EarTrumpet.Actions.Interop
+namespace EarTrumpet.Actions.Interop;
+
+internal class User32
{
- class User32
- {
- public static readonly string SHELLHOOK = "SHELLHOOK";
- public const int HSHELL_WINDOWCREATED = 1;
- public const int HSHELL_WINDOWDESTROYED = 2;
+ public static readonly string SHELLHOOK = "SHELLHOOK";
+ public const int HSHELL_WINDOWCREATED = 1;
+ public const int HSHELL_WINDOWDESTROYED = 2;
- [DllImport("user32.dll", PreserveSig = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool RegisterShellHookWindow(IntPtr hWnd);
+ [DllImport("user32.dll", PreserveSig = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool RegisterShellHookWindow(IntPtr hWnd);
- [DllImport("user32.dll", PreserveSig = true)]
- public static extern uint RegisterWindowMessageW([MarshalAs(UnmanagedType.LPWStr)] string msg);
- }
+ [DllImport("user32.dll", PreserveSig = true)]
+ public static extern uint RegisterWindowMessageW([MarshalAs(UnmanagedType.LPWStr)] string msg);
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetAppMuteActionViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetAppMuteActionViewModel.cs
index 1557a5772..2b90c78db 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetAppMuteActionViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetAppMuteActionViewModel.cs
@@ -1,26 +1,25 @@
using EarTrumpet.Actions.DataModel.Serialization;
-namespace EarTrumpet.Actions.ViewModel.Actions
+namespace EarTrumpet.Actions.ViewModel.Actions;
+
+internal class SetAppMuteActionViewModel : PartViewModel
{
- class SetAppMuteActionViewModel : PartViewModel
- {
- public OptionViewModel Option { get; }
- public DeviceListViewModel Device { get; }
- public AppListViewModel App { get; }
+ public OptionViewModel Option { get; }
+ public DeviceListViewModel Device { get; }
+ public AppListViewModel App { get; }
- private SetAppMuteAction _action;
+ private SetAppMuteAction _action;
- public SetAppMuteActionViewModel(SetAppMuteAction action) : base(action)
- {
- _action = action;
+ public SetAppMuteActionViewModel(SetAppMuteAction action) : base(action)
+ {
+ _action = action;
- Option = new OptionViewModel(action, nameof(action.Option));
- App = new AppListViewModel(action, AppListViewModel.AppKind.EveryApp | AppListViewModel.AppKind.ForegroundApp);
- Device = new DeviceListViewModel(action, DeviceListViewModel.DeviceListKind.DefaultPlayback);
+ Option = new OptionViewModel(action, nameof(action.Option));
+ App = new AppListViewModel(action, AppListViewModel.AppKind.EveryApp | AppListViewModel.AppKind.ForegroundApp);
+ Device = new DeviceListViewModel(action, DeviceListViewModel.DeviceListKind.DefaultPlayback);
- Attach(Option);
- Attach(App);
- Attach(Device);
- }
+ Attach(Option);
+ Attach(App);
+ Attach(Device);
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetAppVolumeActionViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetAppVolumeActionViewModel.cs
index 998033efc..3cb687c4a 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetAppVolumeActionViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetAppVolumeActionViewModel.cs
@@ -1,44 +1,71 @@
-using EarTrumpet.Actions.DataModel.Serialization;
-using EarTrumpet.Actions.DataModel.Enum;
+using EarTrumpet.Actions.DataModel.Enum;
+using EarTrumpet.Actions.DataModel.Serialization;
+using EarTrumpet.Extensions;
+using System;
-namespace EarTrumpet.Actions.ViewModel.Actions
+namespace EarTrumpet.Actions.ViewModel.Actions;
+
+internal class SetAppVolumeActionViewModel : PartViewModel
{
- class SetAppVolumeActionViewModel : PartViewModel
- {
- public OptionViewModel Option { get; }
- public DeviceListViewModel Device { get; }
- public AppListViewModel App { get; }
- public VolumeViewModel Volume { get; }
+ public OptionViewModel Option { get; }
+ public OptionViewModel Unit { get; }
+ public DeviceListViewModel Device { get; }
+ public AppListViewModel App { get; }
+ public VolumeViewModel Volume { get; }
- private SetAppVolumeAction _action;
+ private SetAppVolumeAction _action;
- public SetAppVolumeActionViewModel(SetAppVolumeAction action) : base(action)
- {
- _action = action;
+ public SetAppVolumeActionViewModel(SetAppVolumeAction action) : base(action)
+ {
+ _action = action;
- Option = new OptionViewModel(action, nameof(action.Option));
- App = new AppListViewModel(action, AppListViewModel.AppKind.EveryApp | AppListViewModel.AppKind.ForegroundApp);
- Device = new DeviceListViewModel(action, DeviceListViewModel.DeviceListKind.DefaultPlayback);
- Volume = new VolumeViewModel(action);
+ Option = new OptionViewModel(action, nameof(action.Option));
+ Unit = new OptionViewModel(action, nameof(action.Unit));
+ App = new AppListViewModel(action, AppListViewModel.AppKind.EveryApp | AppListViewModel.AppKind.ForegroundApp);
+ Device = new DeviceListViewModel(action, DeviceListViewModel.DeviceListKind.DefaultPlayback);
+ Volume = new VolumeViewModel(action);
- Attach(Option);
- Attach(App);
- Attach(Device);
- Attach(Volume);
- }
+ Attach(Option);
+ Attach(Unit);
+ Attach(App);
+ Attach(Device);
+ Attach(Volume);
- public override string LinkText
+ Option.PropertyChanged += (s, e) =>
{
- get
+ if (e.PropertyName == nameof(OptionViewModel.Selected))
{
- if (_action.Option == SetVolumeKind.Set)
- {
- return base.LinkText;
- }
- else
+ Volume.UpdateRange();
+ Volume.Volume = 0;
+ }
+ };
+
+ Unit.PropertyChanged += (s, e) =>
+ {
+ if (e.PropertyName == nameof(OptionViewModel.Selected))
+ {
+ Volume.UpdateRange();
+ Volume.Volume = (VolumeUnit)Unit.Selected.Value switch
{
- return Properties.Resources.SetAppVolumeAction_LinkTextIncrement;
- }
+ VolumeUnit.Percentage => Math.Round(Volume.Volume.LogToLinear() * 100),
+ VolumeUnit.Decibel => Math.Round((Volume.Volume / 100).LinearToLog(), 1),
+ _ => throw new ArgumentException("Invalid volume unit."),
+ };
+ }
+ };
+ }
+
+ public override string LinkText
+ {
+ get
+ {
+ if (_action.Option == SetVolumeKind.Set)
+ {
+ return base.LinkText;
+ }
+ else
+ {
+ return Properties.Resources.SetAppVolumeAction_LinkTextIncrement;
}
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetDefaultDeviceActionViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetDefaultDeviceActionViewModel.cs
index 78338b6b9..741dd7a6d 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetDefaultDeviceActionViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetDefaultDeviceActionViewModel.cs
@@ -1,15 +1,14 @@
using EarTrumpet.Actions.DataModel.Serialization;
-namespace EarTrumpet.Actions.ViewModel.Actions
+namespace EarTrumpet.Actions.ViewModel.Actions;
+
+internal class SetDefaultDeviceActionViewModel : PartViewModel
{
- class SetDefaultDeviceActionViewModel : PartViewModel
- {
- public DeviceListViewModel Device { get; }
+ public DeviceListViewModel Device { get; }
- public SetDefaultDeviceActionViewModel(SetDefaultDeviceAction action) : base(action)
- {
- Device = new DeviceListViewModel(action, DeviceListViewModel.DeviceListKind.Recording);
- Attach(Device);
- }
+ public SetDefaultDeviceActionViewModel(SetDefaultDeviceAction action) : base(action)
+ {
+ Device = new DeviceListViewModel(action, DeviceListViewModel.DeviceListKind.Recording);
+ Attach(Device);
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetDeviceMuteActionViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetDeviceMuteActionViewModel.cs
index e52f00466..a95f4e58c 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetDeviceMuteActionViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetDeviceMuteActionViewModel.cs
@@ -1,36 +1,35 @@
using EarTrumpet.Actions.DataModel.Serialization;
-namespace EarTrumpet.Actions.ViewModel.Actions
+namespace EarTrumpet.Actions.ViewModel.Actions;
+
+internal class SetDeviceMuteActionViewModel : PartViewModel
{
- class SetDeviceMuteActionViewModel : PartViewModel
- {
- public OptionViewModel Option { get; }
- public DeviceListViewModel Device { get; }
+ public OptionViewModel Option { get; }
+ public DeviceListViewModel Device { get; }
- private SetDeviceMuteAction _action;
+ private SetDeviceMuteAction _action;
- public SetDeviceMuteActionViewModel(SetDeviceMuteAction action) : base(action)
- {
- _action = action;
- Option = new OptionViewModel(action, nameof(action.Option));
- Device = new DeviceListViewModel(action, DeviceListViewModel.DeviceListKind.Recording | DeviceListViewModel.DeviceListKind.DefaultPlayback);
+ public SetDeviceMuteActionViewModel(SetDeviceMuteAction action) : base(action)
+ {
+ _action = action;
+ Option = new OptionViewModel(action, nameof(action.Option));
+ Device = new DeviceListViewModel(action, DeviceListViewModel.DeviceListKind.Recording | DeviceListViewModel.DeviceListKind.DefaultPlayback);
- Attach(Option);
- Attach(Device);
- }
+ Attach(Option);
+ Attach(Device);
+ }
- public override string LinkText
+ public override string LinkText
+ {
+ get
{
- get
+ if (_action.Option == DataModel.Enum.MuteKind.ToggleMute)
+ {
+ return Properties.Resources.SetDeviceMuteAction_LinkTextToggle;
+ }
+ else
{
- if (_action.Option == DataModel.Enum.MuteKind.ToggleMute)
- {
- return Properties.Resources.SetDeviceMuteAction_LinkTextToggle;
- }
- else
- {
- return base.LinkText;
- }
+ return base.LinkText;
}
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetDeviceVolumeActionViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetDeviceVolumeActionViewModel.cs
index b27139ee7..2e8a12271 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetDeviceVolumeActionViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetDeviceVolumeActionViewModel.cs
@@ -1,40 +1,67 @@
using EarTrumpet.Actions.DataModel.Enum;
using EarTrumpet.Actions.DataModel.Serialization;
+using EarTrumpet.Extensions;
+using System;
-namespace EarTrumpet.Actions.ViewModel.Actions
+namespace EarTrumpet.Actions.ViewModel.Actions;
+
+internal class SetDeviceVolumeActionViewModel : PartViewModel
{
- class SetDeviceVolumeActionViewModel : PartViewModel
+ public OptionViewModel Option { get; }
+ public OptionViewModel Unit { get; }
+ public DeviceListViewModel Device { get; }
+ public VolumeViewModel Volume { get; }
+
+ private SetDeviceVolumeAction _action;
+
+ public SetDeviceVolumeActionViewModel(SetDeviceVolumeAction action) : base(action)
{
- public OptionViewModel Option { get; }
- public DeviceListViewModel Device { get; }
- public VolumeViewModel Volume { get; }
+ _action = action;
+ Option = new OptionViewModel(action, nameof(action.Option));
+ Unit = new OptionViewModel(action, nameof(action.Unit));
+ Device = new DeviceListViewModel(action, DeviceListViewModel.DeviceListKind.Recording | DeviceListViewModel.DeviceListKind.DefaultPlayback);
+ Volume = new VolumeViewModel(action);
- private SetDeviceVolumeAction _action;
+ Attach(Option);
+ Attach(Unit);
+ Attach(Device);
+ Attach(Volume);
- public SetDeviceVolumeActionViewModel(SetDeviceVolumeAction action) : base(action)
+ Option.PropertyChanged += (s, e) =>
{
- _action = action;
- Option = new OptionViewModel(action, nameof(action.Option));
- Device = new DeviceListViewModel(action, DeviceListViewModel.DeviceListKind.Recording | DeviceListViewModel.DeviceListKind.DefaultPlayback);
- Volume = new VolumeViewModel(action);
-
- Attach(Option);
- Attach(Device);
- Attach(Volume);
- }
+ if (e.PropertyName == nameof(OptionViewModel.Selected))
+ {
+ Volume.UpdateRange();
+ Volume.Volume = 0;
+ }
+ };
- public override string LinkText
+ Unit.PropertyChanged += (s, e) =>
{
- get
+ if (e.PropertyName == nameof(OptionViewModel.Selected))
{
- if (_action.Option == SetVolumeKind.Set)
+ Volume.UpdateRange();
+ Volume.Volume = (VolumeUnit)Unit.Selected.Value switch
{
- return base.LinkText;
- }
- else
- {
- return Properties.Resources.SetDeviceVolumeAction_LinkTextIncrement;
- }
+ VolumeUnit.Percentage => 100,
+ VolumeUnit.Decibel => 0,
+ _ => throw new ArgumentException("Invalid volume unit."),
+ };
+ }
+ };
+ }
+
+ public override string LinkText
+ {
+ get
+ {
+ if (_action.Option == SetVolumeKind.Set)
+ {
+ return base.LinkText;
+ }
+ else
+ {
+ return Properties.Resources.SetDeviceVolumeAction_LinkTextIncrement;
}
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetVariableActionViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetVariableActionViewModel.cs
index 24fa32673..83c19a858 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetVariableActionViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Actions/SetVariableActionViewModel.cs
@@ -1,19 +1,18 @@
using EarTrumpet.Actions.DataModel.Serialization;
-namespace EarTrumpet.Actions.ViewModel.Actions
+namespace EarTrumpet.Actions.ViewModel.Actions;
+
+internal class SetVariableActionViewModel : PartViewModel
{
- class SetVariableActionViewModel : PartViewModel
- {
- public OptionViewModel Option { get; }
- public TextViewModel Text { get; }
+ public OptionViewModel Option { get; }
+ public TextViewModel Text { get; }
- public SetVariableActionViewModel(SetVariableAction action) : base(action)
- {
- Option = new OptionViewModel(action, nameof(action.Value));
- Text = new TextViewModel(action);
+ public SetVariableActionViewModel(SetVariableAction action) : base(action)
+ {
+ Option = new OptionViewModel(action, nameof(action.Value));
+ Text = new TextViewModel(action);
- Attach(Option);
- Attach(Text);
- }
+ Attach(Option);
+ Attach(Text);
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/ActionsCategoryViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/ActionsCategoryViewModel.cs
index aaa08526a..358b16c78 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/ActionsCategoryViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/ActionsCategoryViewModel.cs
@@ -1,115 +1,112 @@
-using EarTrumpet.Extensions;
+using System.Collections.ObjectModel;
+using System.Linq;
+using EarTrumpet.Actions.DataModel.Serialization;
+using EarTrumpet.Extensions;
using EarTrumpet.UI.Helpers;
using EarTrumpet.UI.ViewModels;
-using EarTrumpet.Actions.DataModel.Serialization;
-using System;
-using System.Collections.ObjectModel;
-using System.Linq;
-namespace EarTrumpet.Actions.ViewModel
+namespace EarTrumpet.Actions.ViewModel;
+
+public class ActionsCategoryViewModel : SettingsCategoryViewModel
{
- public class ActionsCategoryViewModel : SettingsCategoryViewModel
+ public ActionsCategoryViewModel()
+ : base(Properties.Resources.MyActionsText, "\xE950", Properties.Resources.AddonDescriptionText, EarTrumpetActionsAddon.Current.Manifest.Id, new ObservableCollection())
{
- public ActionsCategoryViewModel()
- : base(Properties.Resources.MyActionsText, "\xE950", Properties.Resources.AddonDescriptionText, EarTrumpetActionsAddon.Current.Manifest.Id, new ObservableCollection())
- {
- // Get a 'fresh' copy so that we can edit the objects and still go back later.
- var actions = EarTrumpetActionsAddon.Current.Actions;
- EarTrumpetActionsAddon.Current.Actions = EarTrumpetActionsAddon.Current.Actions;
+ // Get a 'fresh' copy so that we can edit the objects and still go back later.
+ var actions = EarTrumpetActionsAddon.Current.Actions;
+ EarTrumpetActionsAddon.Current.Actions = EarTrumpetActionsAddon.Current.Actions;
- Pages.AddRange(actions.Select(a => new EarTrumpetActionViewModel(this, a)));
- Pages.Add(new ImportExportPageViewModel(this));
+ Pages.AddRange(actions.Select(a => new EarTrumpetActionViewModel(this, a)));
+ Pages.Add(new ImportExportPageViewModel(this));
- Toolbar = new ToolbarItemViewModel[] { new ToolbarItemViewModel{
- Command = new RelayCommand(() =>
- {
- var vm = new EarTrumpetActionViewModel(this, new EarTrumpetAction { DisplayName = Properties.Resources.NewActionText });
- vm.IsWorkSaved = false;
- vm.IsPersisted = false;
+ Toolbar = [ new() {
+ Command = new RelayCommand(() =>
+ {
+ var vm = new EarTrumpetActionViewModel(this, new EarTrumpetAction { DisplayName = Properties.Resources.NewActionText }) { IsWorkSaved = false,
+ IsPersisted = false };
- vm.PropertyChanged += (_, e) =>
+ vm.PropertyChanged += (_, e) =>
+ {
+ if (e.PropertyName == nameof(vm.IsSelected) &&
+ vm.IsSelected && !Pages.Contains(vm))
{
- if (e.PropertyName == nameof(vm.IsSelected) &&
- vm.IsSelected && !Pages.Contains(vm))
- {
- Pages.Insert(0, vm);
- }
- };
+ Pages.Insert(0, vm);
+ }
+ };
- Selected = vm;
- }),
- DisplayName = Properties.Resources.NewActionText,
- Glyph = "\xE948",
- GlyphFontSize = 15,
- } };
-
- if (Pages.Count == 2)
- {
- Toolbar[0].Command.Execute(null);
- }
- }
+ Selected = vm;
+ }),
+ DisplayName = Properties.Resources.NewActionText,
+ Glyph = "\xE948",
+ GlyphFontSize = 15,
+ } ];
- internal void ReloadSavedPages()
+ if (Pages.Count == 2)
{
- foreach (var item in Pages.Where(p => p is EarTrumpetActionViewModel).ToList())
- {
- Pages.Remove(item);
- }
-
- Pages.InsertRange(0, new System.Collections.ObjectModel.ObservableCollection(EarTrumpetActionsAddon.Current.Actions.Select(a => new EarTrumpetActionViewModel(this, a))));
- Selected = Pages[0];
+ Toolbar[0].Command.Execute(null);
}
+ }
- public void Delete(EarTrumpetActionViewModel earTrumpetActionViewModel, bool promptOverride = false)
+ internal void ReloadSavedPages()
+ {
+ foreach (var item in Pages.Where(p => p is EarTrumpetActionViewModel).ToList())
{
- Action doRemove = () =>
- {
- var actions = EarTrumpetActionsAddon.Current.Actions.ToList();
- if (actions.Any(a => a.Id == earTrumpetActionViewModel.Id))
- {
- actions.Remove(item => item.Id == earTrumpetActionViewModel.Id);
- }
- EarTrumpetActionsAddon.Current.Actions = actions.ToArray();
-
- if (Pages.Any(a => a == earTrumpetActionViewModel))
- {
- Pages.Remove(earTrumpetActionViewModel);
- }
- };
-
- if (earTrumpetActionViewModel.IsPersisted && !promptOverride)
- {
- _parent.ShowDialog(Properties.Resources.DeleteActionDialogTitle, Properties.Resources.DeleteActionDialogText,
- Properties.Resources.DeleteActionDialogYesText, Properties.Resources.DeleteActionDialogNoText, doRemove, () => { });
- }
- else
- {
- doRemove();
- }
+ Pages.Remove(item);
}
- public void Save(EarTrumpetActionViewModel earTrumpetActionViewModel)
+ Pages.InsertRange(0, new System.Collections.ObjectModel.ObservableCollection(EarTrumpetActionsAddon.Current.Actions.Select(a => new EarTrumpetActionViewModel(this, a))));
+ Selected = Pages[0];
+ }
+
+ public void Delete(EarTrumpetActionViewModel earTrumpetActionViewModel, bool promptOverride = false)
+ {
+ void doRemove()
{
var actions = EarTrumpetActionsAddon.Current.Actions.ToList();
if (actions.Any(a => a.Id == earTrumpetActionViewModel.Id))
{
actions.Remove(item => item.Id == earTrumpetActionViewModel.Id);
}
- actions.Insert(0, earTrumpetActionViewModel.GetAction());
- EarTrumpetActionsAddon.Current.Actions = actions.ToArray();
- earTrumpetActionViewModel.IsWorkSaved = true;
+ EarTrumpetActionsAddon.Current.Actions = [.. actions];
if (Pages.Any(a => a == earTrumpetActionViewModel))
{
Pages.Remove(earTrumpetActionViewModel);
}
- Pages.Insert(0, earTrumpetActionViewModel);
- Selected = Pages[0];
}
- public void CompleteNavigation(NavigationCookie cookie)
+ if (earTrumpetActionViewModel.IsPersisted && !promptOverride)
{
- _parent.CompleteNavigation(cookie);
+ _parent.ShowDialog(Properties.Resources.DeleteActionDialogTitle, Properties.Resources.DeleteActionDialogText,
+ Properties.Resources.DeleteActionDialogYesText, Properties.Resources.DeleteActionDialogNoText, doRemove, () => { });
}
+ else
+ {
+ doRemove();
+ }
+ }
+
+ public void Save(EarTrumpetActionViewModel earTrumpetActionViewModel)
+ {
+ var actions = EarTrumpetActionsAddon.Current.Actions.ToList();
+ if (actions.Any(a => a.Id == earTrumpetActionViewModel.Id))
+ {
+ actions.Remove(item => item.Id == earTrumpetActionViewModel.Id);
+ }
+ actions.Insert(0, earTrumpetActionViewModel.GetAction());
+ EarTrumpetActionsAddon.Current.Actions = [.. actions];
+ earTrumpetActionViewModel.IsWorkSaved = true;
+
+ if (Pages.Any(a => a == earTrumpetActionViewModel))
+ {
+ Pages.Remove(earTrumpetActionViewModel);
+ }
+ Pages.Insert(0, earTrumpetActionViewModel);
+ Selected = Pages[0];
+ }
+
+ public void CompleteNavigation(NavigationCookie cookie)
+ {
+ _parent.CompleteNavigation(cookie);
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/AppListViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/AppListViewModel.cs
index d3348917d..d03f97cba 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/AppListViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/AppListViewModel.cs
@@ -10,70 +10,79 @@
using EarTrumpet.DataModel.WindowsAudio;
using EarTrumpet.DataModel.Audio;
-namespace EarTrumpet.Actions.ViewModel
+namespace EarTrumpet.Actions.ViewModel;
+
+internal class AppListViewModel : BindableBase
{
- class AppListViewModel : BindableBase
+ [Flags]
+ public enum AppKind
{
- [Flags]
- public enum AppKind
+ Default = 0,
+ EveryApp = 1,
+ ForegroundApp = 2,
+ }
+
+ public ObservableCollection All { get; }
+
+ private IPartWithApp _part;
+
+ public AppListViewModel(IPartWithApp part, AppKind flags)
+ {
+ _part = part;
+ All = [];
+
+ GetApps(flags);
+
+ if (part.App?.Id == null)
{
- Default = 0,
- EveryApp = 1,
- ForegroundApp = 2,
+ _part.App = new AppRef { Id = All[0].Id };
}
+ }
- public ObservableCollection All { get; }
+ public void OnInvoked(object sender, IAppItemViewModel viewModel)
+ {
+ _part.App = new AppRef { Id = viewModel.Id };
+ RaisePropertyChanged(""); // Signal change so ToString will be called.
- private IPartWithApp _part;
+ var popup = ((DependencyObject)sender).FindVisualParent();
+ popup.IsOpen = false;
+ }
- public AppListViewModel(IPartWithApp part, AppKind flags)
- {
- _part = part;
- All = new ObservableCollection();
+ public override string ToString()
+ {
+ var existing = All.FirstOrDefault(d => d.Id == _part.App?.Id);
- GetApps(flags);
+ // Fallback to checking against package full path, for compatibility with older actions
+ // that predate changes to how we track packaged applications.
+ // https://github.com/File-New-Project/EarTrumpet/issues/1524
- if (part.App?.Id == null)
- {
- _part.App = new AppRef { Id = All[0].Id };
- }
+ if (existing == null)
+ {
+ existing = All.FirstOrDefault(d => d.PackageInstallPath == _part.App?.Id);
}
- public void OnInvoked(object sender, IAppItemViewModel vivewModel)
+ if (existing != null)
{
- _part.App = new AppRef { Id = vivewModel.Id };
- RaisePropertyChanged(""); // Signal change so ToString will be called.
+ return existing.DisplayName;
+ }
+ return _part.App?.Id;
+ }
- var popup = ((DependencyObject)sender).FindVisualParent();
- popup.IsOpen = false;
+ public void GetApps(AppKind flags)
+ {
+ if ((flags & AppKind.EveryApp) == AppKind.EveryApp)
+ {
+ All.Add(new EveryAppViewModel());
}
- public override string ToString()
+ if ((flags & AppKind.ForegroundApp) == AppKind.ForegroundApp)
{
- var existing = All.FirstOrDefault(d => d.Id == _part.App?.Id);
- if (existing != null)
- {
- return existing.DisplayName;
- }
- return _part.App?.Id;
+ All.Add(new ForegroundAppViewModel());
}
- public void GetApps(AppKind flags)
+ foreach (var app in WindowsAudioFactory.Create(AudioDeviceKind.Playback).Devices.SelectMany(d => d.Groups).Distinct(IAudioDeviceSessionComparer.Instance).OrderBy(d => d.DisplayName).OrderBy(d => d.DisplayName))
{
- if ((flags & AppKind.EveryApp) == AppKind.EveryApp)
- {
- All.Add(new EveryAppViewModel());
- }
-
- if ((flags & AppKind.ForegroundApp) == AppKind.ForegroundApp)
- {
- All.Add(new ForegroundAppViewModel());
- }
-
- foreach (var app in WindowsAudioFactory.Create(AudioDeviceKind.Playback).Devices.SelectMany(d => d.Groups).Distinct(IAudioDeviceSessionComparer.Instance).OrderBy(d => d.DisplayName).OrderBy(d => d.DisplayName))
- {
- All.Add(new SettingsAppItemViewModel(app));
- }
+ All.Add(new SettingsAppItemViewModel(app));
}
}
}
\ No newline at end of file
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Conditions/DefaultDeviceConditionViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Conditions/DefaultDeviceConditionViewModel.cs
index 7a50995bb..af45c1a17 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Conditions/DefaultDeviceConditionViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Conditions/DefaultDeviceConditionViewModel.cs
@@ -1,20 +1,19 @@
using EarTrumpet.Actions.DataModel.Serialization;
-namespace EarTrumpet.Actions.ViewModel.Conditions
+namespace EarTrumpet.Actions.ViewModel.Conditions;
+
+internal class DefaultDeviceConditionViewModel : PartViewModel
{
- class DefaultDeviceConditionViewModel : PartViewModel
- {
- public DeviceListViewModel Device { get; }
+ public DeviceListViewModel Device { get; }
- public OptionViewModel Option { get; }
+ public OptionViewModel Option { get; }
- public DefaultDeviceConditionViewModel(DefaultDeviceCondition condition) : base(condition)
- {
- Option = new OptionViewModel(condition, nameof(condition.Option));
- Device = new DeviceListViewModel(condition, DeviceListViewModel.DeviceListKind.Recording);
+ public DefaultDeviceConditionViewModel(DefaultDeviceCondition condition) : base(condition)
+ {
+ Option = new OptionViewModel(condition, nameof(condition.Option));
+ Device = new DeviceListViewModel(condition, DeviceListViewModel.DeviceListKind.Recording);
- Attach(Option);
- Attach(Device);
- }
+ Attach(Option);
+ Attach(Device);
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Conditions/ProcessConditionViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Conditions/ProcessConditionViewModel.cs
index 6aea1a4e3..3a8bbe6ef 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Conditions/ProcessConditionViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Conditions/ProcessConditionViewModel.cs
@@ -1,20 +1,19 @@
using EarTrumpet.Actions.DataModel.Serialization;
-namespace EarTrumpet.Actions.ViewModel.Conditions
+namespace EarTrumpet.Actions.ViewModel.Conditions;
+
+internal class ProcessConditionViewModel : PartViewModel
{
- class ProcessConditionViewModel : PartViewModel
- {
- public OptionViewModel Option { get; }
+ public OptionViewModel Option { get; }
- public TextViewModel Text { get; }
+ public TextViewModel Text { get; }
- public ProcessConditionViewModel(ProcessCondition condition) : base(condition)
- {
- Option = new OptionViewModel(condition, nameof(condition.Option));
- Text = new TextViewModel(condition);
+ public ProcessConditionViewModel(ProcessCondition condition) : base(condition)
+ {
+ Option = new OptionViewModel(condition, nameof(condition.Option));
+ Text = new TextViewModel(condition);
- Attach(Option);
- Attach(Text);
- }
+ Attach(Option);
+ Attach(Text);
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Conditions/VariableConditionViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Conditions/VariableConditionViewModel.cs
index 6747d34db..19bb5bfbf 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Conditions/VariableConditionViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/Conditions/VariableConditionViewModel.cs
@@ -1,19 +1,18 @@
using EarTrumpet.Actions.DataModel.Serialization;
-namespace EarTrumpet.Actions.ViewModel.Conditions
+namespace EarTrumpet.Actions.ViewModel.Conditions;
+
+internal class VariableConditionViewModel : PartViewModel
{
- class VariableConditionViewModel : PartViewModel
- {
- public OptionViewModel Option { get; }
- public TextViewModel Text { get; }
+ public OptionViewModel Option { get; }
+ public TextViewModel Text { get; }
- public VariableConditionViewModel(VariableCondition condition) : base(condition)
- {
- Option = new OptionViewModel(condition, nameof(condition.Value));
- Text = new TextViewModel(condition);
+ public VariableConditionViewModel(VariableCondition condition) : base(condition)
+ {
+ Option = new OptionViewModel(condition, nameof(condition.Value));
+ Text = new TextViewModel(condition);
- Attach(Option);
- Attach(Text);
- }
+ Attach(Option);
+ Attach(Text);
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/DefaultPlaybackDeviceViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/DefaultPlaybackDeviceViewModel.cs
index 1a6b0e39e..5bc75b09a 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/DefaultPlaybackDeviceViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/DefaultPlaybackDeviceViewModel.cs
@@ -1,14 +1,13 @@
using EarTrumpet.DataModel.WindowsAudio;
-namespace EarTrumpet.Actions.ViewModel
+namespace EarTrumpet.Actions.ViewModel;
+
+internal class DefaultPlaybackDeviceViewModel : DeviceViewModelBase
{
- class DefaultPlaybackDeviceViewModel : DeviceViewModelBase
+ public DefaultPlaybackDeviceViewModel()
{
- public DefaultPlaybackDeviceViewModel()
- {
- DisplayName = Properties.Resources.DefaultPlaybackDeviceText;
- Kind = AudioDeviceKind.Playback.ToString();
- GroupName = Properties.Resources.PlaybackDeviceGroupText;
- }
+ DisplayName = Properties.Resources.DefaultPlaybackDeviceText;
+ Kind = AudioDeviceKind.Playback.ToString();
+ GroupName = Properties.Resources.PlaybackDeviceGroupText;
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/DeviceListViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/DeviceListViewModel.cs
index 59406f789..54eb7ba83 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/DeviceListViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/DeviceListViewModel.cs
@@ -8,74 +8,73 @@
using System.Windows.Controls.Primitives;
using EarTrumpet.DataModel.WindowsAudio;
-namespace EarTrumpet.Actions.ViewModel
+namespace EarTrumpet.Actions.ViewModel;
+
+public class DeviceListViewModel : BindableBase
{
- public class DeviceListViewModel : BindableBase
+ [Flags]
+ public enum DeviceListKind
{
- [Flags]
- public enum DeviceListKind
- {
- Playback = 0,
- Recording = 1,
- DefaultPlayback = 2
- }
+ Playback = 0,
+ Recording = 1,
+ DefaultPlayback = 2
+ }
- public ObservableCollection All { get; }
+ public ObservableCollection All { get; }
- public void OnInvoked(object sender, DeviceViewModelBase vivewModel)
- {
- _part.Device = new Device { Id = vivewModel.Id, Kind = vivewModel.Kind };
- RaisePropertyChanged(""); // Signal change so ToString will be called.
+ public void OnInvoked(object sender, DeviceViewModelBase vivewModel)
+ {
+ _part.Device = new Device { Id = vivewModel.Id, Kind = vivewModel.Kind };
+ RaisePropertyChanged(""); // Signal change so ToString will be called.
- var popup = ((DependencyObject)sender).FindVisualParent();
- popup.IsOpen = false;
- }
+ var popup = ((DependencyObject)sender).FindVisualParent();
+ popup.IsOpen = false;
+ }
- private IPartWithDevice _part;
+ private IPartWithDevice _part;
- public DeviceListViewModel(IPartWithDevice part, DeviceListKind flags)
- {
- _part = part;
- All = new ObservableCollection();
- GetDevices(flags);
+ public DeviceListViewModel(IPartWithDevice part, DeviceListKind flags)
+ {
+ _part = part;
+ All = [];
+ GetDevices(flags);
- if (_part.Device == null)
- {
- _part.Device = new Device { Id = All[0].Id, Kind = All[0].Kind };
- }
+ if (_part.Device == null)
+ {
+ _part.Device = new Device { Id = All[0].Id, Kind = All[0].Kind };
}
+ }
- public override string ToString()
+ public override string ToString()
+ {
+ var existing = All.FirstOrDefault(d => d.Id == _part.Device?.Id);
+ if (existing != null)
{
- var existing = All.FirstOrDefault(d => d.Id == _part.Device?.Id);
- if (existing != null)
- {
- return existing.DisplayName;
- }
- return _part.Device?.Id;
+ return existing.DisplayName;
}
+ return _part.Device?.Id;
+ }
- void GetDevices(DeviceListKind flags)
+ private void GetDevices(DeviceListKind flags)
+ {
+ var isRecording = (flags & DeviceListKind.Recording) == DeviceListKind.Recording;
+
+ if ((flags & DeviceListKind.DefaultPlayback) == DeviceListKind.DefaultPlayback)
{
- bool isRecording = (flags & DeviceListKind.Recording) == DeviceListKind.Recording;
+ All.Add(new DefaultPlaybackDeviceViewModel());
+ }
- if ((flags & DeviceListKind.DefaultPlayback) == DeviceListKind.DefaultPlayback)
- {
- All.Add(new DefaultPlaybackDeviceViewModel());
- }
+ foreach (var device in WindowsAudioFactory.Create(AudioDeviceKind.Playback).Devices.OrderBy(d => d.DisplayName))
+ {
+ All.Add(new DeviceViewModel(device));
+ }
- foreach (var device in WindowsAudioFactory.Create(AudioDeviceKind.Playback).Devices.OrderBy(d => d.DisplayName))
+ if (isRecording)
+ {
+ foreach (var device in WindowsAudioFactory.Create(AudioDeviceKind.Recording).Devices.OrderBy(d => d.DisplayName))
{
All.Add(new DeviceViewModel(device));
}
-
- if (isRecording)
- {
- foreach (var device in WindowsAudioFactory.Create(AudioDeviceKind.Recording).Devices.OrderBy(d => d.DisplayName))
- {
- All.Add(new DeviceViewModel(device));
- }
- }
}
}
}
\ No newline at end of file
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/DeviceViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/DeviceViewModel.cs
index 1911e1fc4..f072a2a73 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/DeviceViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/DeviceViewModel.cs
@@ -2,28 +2,27 @@
using EarTrumpet.DataModel.WindowsAudio;
using EarTrumpet.UI.Helpers;
-namespace EarTrumpet.Actions.ViewModel
+namespace EarTrumpet.Actions.ViewModel;
+
+public class DeviceViewModel : DeviceViewModelBase, IAppIconSource
{
- public class DeviceViewModel : DeviceViewModelBase, IAppIconSource
- {
- public bool IsDesktopApp => true;
- public string IconPath => _device.IconPath;
- public string DeviceDescription => ((IAudioDeviceWindowsAudio)_device).DeviceDescription;
- public string EnumeratorName => ((IAudioDeviceWindowsAudio)_device).EnumeratorName;
- public string InterfaceName => ((IAudioDeviceWindowsAudio)_device).InterfaceName;
+ public bool IsDesktopApp => true;
+ public string IconPath => _device.IconPath;
+ public string DeviceDescription => ((IAudioDeviceWindowsAudio)_device).DeviceDescription;
+ public string EnumeratorName => ((IAudioDeviceWindowsAudio)_device).EnumeratorName;
+ public string InterfaceName => ((IAudioDeviceWindowsAudio)_device).InterfaceName;
- private readonly IAudioDevice _device;
+ private readonly IAudioDevice _device;
- public DeviceViewModel(IAudioDevice device)
- {
- _device = device;
- Id = _device.Id;
- DisplayName = _device.DisplayName;
- Kind = _device.Parent.Kind;
+ public DeviceViewModel(IAudioDevice device)
+ {
+ _device = device;
+ Id = _device.Id;
+ DisplayName = _device.DisplayName;
+ Kind = _device.Parent.Kind;
- GroupName = _device.Parent.Kind == AudioDeviceKind.Playback.ToString() ?
- Properties.Resources.PlaybackDeviceGroupText :
- Properties.Resources.RecordingDeviceGroupText;
- }
+ GroupName = _device.Parent.Kind == AudioDeviceKind.Playback.ToString() ?
+ Properties.Resources.PlaybackDeviceGroupText :
+ Properties.Resources.RecordingDeviceGroupText;
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/DeviceViewModelBase.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/DeviceViewModelBase.cs
index 26bfdbb55..f92698c0c 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/DeviceViewModelBase.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/DeviceViewModelBase.cs
@@ -1,11 +1,10 @@
-namespace EarTrumpet.Actions.ViewModel
+namespace EarTrumpet.Actions.ViewModel;
+
+public class DeviceViewModelBase : BindableBase
{
- public class DeviceViewModelBase : BindableBase
- {
- public string DisplayName { get; set; }
- public string GroupName { get; set; }
- public string Id { get; set; }
- public string Kind { get; set; }
+ public string DisplayName { get; set; }
+ public string GroupName { get; set; }
+ public string Id { get; set; }
+ public string Kind { get; set; }
- }
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/EarTrumpetActionPageHeaderViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/EarTrumpetActionPageHeaderViewModel.cs
index f161f129f..9adf2b10f 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/EarTrumpetActionPageHeaderViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/EarTrumpetActionPageHeaderViewModel.cs
@@ -1,25 +1,24 @@
using EarTrumpet.UI.ViewModels;
using System.ComponentModel;
-namespace EarTrumpet.Actions.ViewModel
+namespace EarTrumpet.Actions.ViewModel;
+
+public class EarTrumpetActionPageHeaderViewModel : SettingsPageHeaderViewModel
{
- public class EarTrumpetActionPageHeaderViewModel : SettingsPageHeaderViewModel
- {
- EarTrumpetActionViewModel _parent;
+ private EarTrumpetActionViewModel _parent;
- public ToolbarItemViewModel[] Toolbar => _parent.Toolbar;
- public string DisplayName { get => _parent.DisplayName; set => _parent.DisplayName = value; }
- public bool IsEditClicked { get => _parent.IsEditClicked; set => _parent.IsEditClicked = value; }
- public bool IsWorkSaved => _parent.IsWorkSaved;
- public EarTrumpetActionPageHeaderViewModel(EarTrumpetActionViewModel parent) : base(parent)
- {
- _parent = parent;
- ((INotifyPropertyChanged)_parent).PropertyChanged += EarTrumpetActionPageHeaderViewModel_PropertyChanged;
- }
+ public ToolbarItemViewModel[] Toolbar => _parent.Toolbar;
+ public string DisplayName { get => _parent.DisplayName; set => _parent.DisplayName = value; }
+ public bool IsEditClicked { get => _parent.IsEditClicked; set => _parent.IsEditClicked = value; }
+ public bool IsWorkSaved => _parent.IsWorkSaved;
+ public EarTrumpetActionPageHeaderViewModel(EarTrumpetActionViewModel parent) : base(parent)
+ {
+ _parent = parent;
+ ((INotifyPropertyChanged)_parent).PropertyChanged += EarTrumpetActionPageHeaderViewModel_PropertyChanged;
+ }
- private void EarTrumpetActionPageHeaderViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
- {
- RaisePropertyChanged(e.PropertyName);
- }
+ private void EarTrumpetActionPageHeaderViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ RaisePropertyChanged(e.PropertyName);
}
}
\ No newline at end of file
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/EarTrumpetActionViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/EarTrumpetActionViewModel.cs
index e6f9fa94a..fb3ba91eb 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/EarTrumpetActionViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/EarTrumpetActionViewModel.cs
@@ -8,223 +8,220 @@
using System.Linq;
using System.Windows.Input;
-namespace EarTrumpet.Actions.ViewModel
+namespace EarTrumpet.Actions.ViewModel;
+
+public class EarTrumpetActionViewModel : SettingsPageViewModel
{
- public class EarTrumpetActionViewModel : SettingsPageViewModel
- {
- public ToolbarItemViewModel[] Toolbar { get; private set; }
- public ICommand Delete => new RelayCommand(() => _parent.Delete(this));
- public Guid Id => _action.Id;
+ public ToolbarItemViewModel[] Toolbar { get; private set; }
+ public ICommand Delete => new RelayCommand(() => _parent.Delete(this));
+ public Guid Id => _action.Id;
- public string DisplayName
+ public string DisplayName
+ {
+ get => _action.DisplayName;
+ set
{
- get => _action.DisplayName;
- set
+ if (DisplayName != value)
{
- if (DisplayName != value)
- {
- _action.DisplayName = value;
- RaisePropertyChanged(nameof(DisplayName));
- Title = DisplayName;
+ _action.DisplayName = value;
+ RaisePropertyChanged(nameof(DisplayName));
+ Title = DisplayName;
- IsWorkSaved = false;
- IsPersisted = true;
- }
+ IsWorkSaved = false;
+ IsPersisted = true;
}
}
+ }
- private bool _isEditClicked;
- public bool IsEditClicked
+ private bool _isEditClicked;
+ public bool IsEditClicked
+ {
+ get => _isEditClicked;
+ set
{
- get => _isEditClicked;
- set
+ if (_isEditClicked != value)
{
- if (_isEditClicked != value)
- {
- _isEditClicked = value;
- RaisePropertyChanged(nameof(IsEditClicked));
+ _isEditClicked = value;
+ RaisePropertyChanged(nameof(IsEditClicked));
- // Immediately unset the value so we can go again.
- _isEditClicked = false;
- RaisePropertyChanged(nameof(IsEditClicked));
- }
+ // Immediately unset the value so we can go again.
+ _isEditClicked = false;
+ RaisePropertyChanged(nameof(IsEditClicked));
}
}
+ }
- private bool _isWorkSaved;
- public bool IsWorkSaved
+ private bool _isWorkSaved;
+ public bool IsWorkSaved
+ {
+ get => _isWorkSaved;
+ set
{
- get => _isWorkSaved;
- set
+ if (_isWorkSaved != value)
{
- if (_isWorkSaved != value)
- {
- _isWorkSaved = value;
- RaisePropertyChanged(nameof(IsWorkSaved));
- }
+ _isWorkSaved = value;
+ RaisePropertyChanged(nameof(IsWorkSaved));
}
}
+ }
- public List NewTriggers => PartViewModelFactory.Create().Select(t => MakeItem(t)).OrderBy(t => t.DisplayName).ToList();
- public List NewConditions => PartViewModelFactory.Create().Select(t => MakeItem(t)).OrderBy(t => t.DisplayName).ToList();
- public List NewActions => PartViewModelFactory.Create().Select(t => MakeItem(t)).OrderBy(t => t.DisplayName).ToList();
+ public List NewTriggers => [.. PartViewModelFactory.Create().Select(t => MakeItem(t)).OrderBy(t => t.DisplayName)];
+ public List NewConditions => [.. PartViewModelFactory.Create().Select(t => MakeItem(t)).OrderBy(t => t.DisplayName)];
+ public List NewActions => [.. PartViewModelFactory.Create().Select(t => MakeItem(t)).OrderBy(t => t.DisplayName)];
- public ObservableCollection Triggers { get; private set; }
- public ObservableCollection Conditions { get; private set; }
- public ObservableCollection Actions { get; private set; }
- public bool IsPersisted { get; set; } = true;
+ public ObservableCollection Triggers { get; private set; }
+ public ObservableCollection Conditions { get; private set; }
+ public ObservableCollection Actions { get; private set; }
+ public bool IsPersisted { get; set; } = true;
- private EarTrumpetAction _action;
- private ActionsCategoryViewModel _parent;
+ private EarTrumpetAction _action;
+ private ActionsCategoryViewModel _parent;
- public EarTrumpetActionViewModel(ActionsCategoryViewModel parent, EarTrumpetAction action) : base("Saved Actions")
+ public EarTrumpetActionViewModel(ActionsCategoryViewModel parent, EarTrumpetAction action) : base("Saved Actions")
+ {
+ _parent = parent;
+ Reset(action);
+ Header = new EarTrumpetActionPageHeaderViewModel(this);
+ Toolbar =
+ [
+ new() {
+ Command = new RelayCommand(() =>
+ {
+ IsEditClicked = true;
+ }),
+ DisplayName = Properties.Resources.ToolbarEditText,
+ Glyph = "\xE70F",
+ GlyphFontSize = 15,
+ },
+ new() {
+ Command = new RelayCommand(() =>
+ {
+ IsPersisted = true;
+ _parent.Save(this);
+ }),
+ DisplayName = Properties.Resources.ToolbarSaveText,
+ Id = "Save",
+ Glyph = "\xE105",
+ GlyphFontSize = 15,
+ },
+ ];
+
+ Glyph = "\xE1CE";
+ Title = DisplayName;
+ }
+
+ public void Reset(EarTrumpetAction action)
+ {
+ _action = action;
+
+ Title = DisplayName;
+ Triggers = new ObservableCollection(action.Triggers.Select(t => CreatePartViewModel(t)));
+ Conditions = new ObservableCollection(action.Conditions.Select(t => CreatePartViewModel(t)));
+ Actions = new ObservableCollection(action.Actions.Select(t => CreatePartViewModel(t)));
+
+ Triggers.CollectionChanged += Parts_CollectionChanged;
+ Conditions.CollectionChanged += Parts_CollectionChanged;
+ Actions.CollectionChanged += Parts_CollectionChanged;
+
+ Parts_CollectionChanged(Triggers, null);
+ Parts_CollectionChanged(Conditions, null);
+ Parts_CollectionChanged(Actions, null);
+ RaisePropertyChanged(nameof(Triggers));
+ RaisePropertyChanged(nameof(Conditions));
+ RaisePropertyChanged(nameof(Actions));
+ RaisePropertyChanged(nameof(DisplayName));
+ IsWorkSaved = true;
+ }
+
+
+ public override bool NavigatingFrom(NavigationCookie cookie)
+ {
+ if (!IsWorkSaved && IsPersisted)
{
- _parent = parent;
- Reset(action);
- Header = new EarTrumpetActionPageHeaderViewModel(this);
- Toolbar = new ToolbarItemViewModel[]
+ _parent.ShowDialog(Properties.Resources.LeavingPageDialogTitle, Properties.Resources.LeavingPageDialogText, Properties.Resources.LeavingPageDialogYesText, () =>
{
- new ToolbarItemViewModel
+ _parent.CompleteNavigation(cookie);
+
+ var existing = EarTrumpetActionsAddon.Current.Actions.FirstOrDefault(a => a.Id == Id);
+ if (existing == null)
{
- Command = new RelayCommand(() =>
- {
- IsEditClicked = true;
- }),
- DisplayName = Properties.Resources.ToolbarEditText,
- Glyph = "\xE70F",
- GlyphFontSize = 15,
- },
- new ToolbarItemViewModel
+ _parent.Delete(this, true);
+ }
+ else
{
- Command = new RelayCommand(() =>
- {
- IsPersisted = true;
- _parent.Save(this);
- }),
- DisplayName = Properties.Resources.ToolbarSaveText,
- Id = "Save",
- Glyph = "\xE105",
- GlyphFontSize = 15,
- },
- };
-
- Glyph = "\xE1CE";
- Title = DisplayName;
+ Reset(existing);
+ }
+
+ }, Properties.Resources.LeavingPageDialogNoText, () => { });
+ return false;
}
+ return base.NavigatingFrom(cookie);
+ }
- public void Reset(EarTrumpetAction action)
- {
- _action = action;
-
- Title = DisplayName;
- Triggers = new ObservableCollection(action.Triggers.Select(t => CreatePartViewModel(t)));
- Conditions = new ObservableCollection(action.Conditions.Select(t => CreatePartViewModel(t)));
- Actions = new ObservableCollection(action.Actions.Select(t => CreatePartViewModel(t)));
-
- Triggers.CollectionChanged += Parts_CollectionChanged;
- Conditions.CollectionChanged += Parts_CollectionChanged;
- Actions.CollectionChanged += Parts_CollectionChanged;
-
- Parts_CollectionChanged(Triggers, null);
- Parts_CollectionChanged(Conditions, null);
- Parts_CollectionChanged(Actions, null);
- RaisePropertyChanged(nameof(Triggers));
- RaisePropertyChanged(nameof(Conditions));
- RaisePropertyChanged(nameof(Actions));
- RaisePropertyChanged(nameof(DisplayName));
- IsWorkSaved = true;
- }
+ public EarTrumpetAction GetAction()
+ {
+ _action.DisplayName = DisplayName;
+ _action.Triggers = new ObservableCollection(Triggers.Select(t => (BaseTrigger)t.Part));
+ _action.Conditions = new ObservableCollection(Conditions.Select(t => (BaseCondition)t.Part));
+ _action.Actions = new ObservableCollection(Actions.Select(t => (BaseAction)t.Part));
+ return _action;
+ }
+ private void Parts_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+ {
+ var col = (ObservableCollection)sender;
- public override bool NavigatingFrom(NavigationCookie cookie)
+ for (var i = 0; i < col.Count; i++)
{
- if (!IsWorkSaved && IsPersisted)
- {
- _parent.ShowDialog(Properties.Resources.LeavingPageDialogTitle, Properties.Resources.LeavingPageDialogText, Properties.Resources.LeavingPageDialogYesText, () =>
- {
- _parent.CompleteNavigation(cookie);
-
- var existing = EarTrumpetActionsAddon.Current.Actions.FirstOrDefault(a => a.Id == Id);
- if (existing == null)
- {
- _parent.Delete(this, true);
- }
- else
- {
- Reset(existing);
- }
-
- }, Properties.Resources.LeavingPageDialogNoText, () => { });
- return false;
- }
- return base.NavigatingFrom(cookie);
+ col[i].IsShowingAdditionalText = i != 0;
}
+ IsWorkSaved = false;
+ IsPersisted = true;
+ }
- public EarTrumpetAction GetAction()
+ private ContextMenuItem MakeItem(PartViewModel part)
+ {
+ return new ContextMenuItem
{
- _action.DisplayName = DisplayName;
- _action.Triggers = new ObservableCollection(Triggers.Select(t => (BaseTrigger)t.Part));
- _action.Conditions = new ObservableCollection(Conditions.Select(t => (BaseCondition)t.Part));
- _action.Actions = new ObservableCollection(Actions.Select(t => (BaseAction)t.Part));
- return _action;
- }
+ DisplayName = part.AddText,
+ Command = new RelayCommand(() =>
+ {
+ InitializeViewModel(part);
+ GetListFromPart(part).Add(part);
+ }),
+ };
+ }
- private void Parts_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
- {
- var col = (ObservableCollection)sender;
+ private PartViewModel CreatePartViewModel(Part part)
+ {
+ var ret = PartViewModelFactory.Create(part);
+ InitializeViewModel(ret);
+ return ret;
+ }
- for (var i = 0; i < col.Count; i++)
- {
- col[i].IsShowingAdditionalText = i != 0;
- }
- IsWorkSaved = false;
- IsPersisted = true;
- }
+ private void InitializeViewModel(PartViewModel part)
+ {
+ part.PropertyChanged += (_, __) => IsWorkSaved = false;
+ part.Remove = new RelayCommand(() => GetListFromPart(part).Remove(part));
+ }
- private ContextMenuItem MakeItem(PartViewModel part)
+ private ObservableCollection GetListFromPart(PartViewModel part)
+ {
+ if (part.Part is BaseTrigger)
{
- return new ContextMenuItem
- {
- DisplayName = part.AddText,
- Command = new RelayCommand(() =>
- {
- InitializeViewModel(part);
- GetListFromPart(part).Add(part);
- }),
- };
+ return Triggers;
}
-
- private PartViewModel CreatePartViewModel(Part part)
+ else if (part.Part is BaseCondition)
{
- var ret = PartViewModelFactory.Create(part);
- InitializeViewModel(ret);
- return ret;
+ return Conditions;
}
-
- private void InitializeViewModel(PartViewModel part)
+ else if (part.Part is BaseAction)
{
- part.PropertyChanged += (_, __) => IsWorkSaved = false;
- part.Remove = new RelayCommand(() => GetListFromPart(part).Remove(part));
+ return Actions;
}
-
- private ObservableCollection GetListFromPart(PartViewModel part)
+ else
{
- if (part.Part is BaseTrigger)
- {
- return Triggers;
- }
- else if (part.Part is BaseCondition)
- {
- return Conditions;
- }
- else if (part.Part is BaseAction)
- {
- return Actions;
- }
- else
- {
- throw new NotImplementedException();
- }
+ throw new NotImplementedException();
}
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/EveryAppViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/EveryAppViewModel.cs
index dedea8825..36b0ab244 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/EveryAppViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/EveryAppViewModel.cs
@@ -1,14 +1,13 @@
using EarTrumpet.UI.ViewModels;
using EarTrumpet.Actions.DataModel.Serialization;
-namespace EarTrumpet.Actions.ViewModel
+namespace EarTrumpet.Actions.ViewModel;
+
+internal class EveryAppViewModel : SettingsAppItemViewModel
{
- class EveryAppViewModel : SettingsAppItemViewModel
+ public EveryAppViewModel()
{
- public EveryAppViewModel()
- {
- DisplayName = Properties.Resources.EveryAppText;
- Id = AppRef.EveryAppId;
- }
+ DisplayName = Properties.Resources.EveryAppText;
+ Id = AppRef.EveryAppId;
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/ForegroundAppViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/ForegroundAppViewModel.cs
index e05d0ce73..0518ca308 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/ForegroundAppViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/ForegroundAppViewModel.cs
@@ -1,14 +1,13 @@
using EarTrumpet.UI.ViewModels;
using EarTrumpet.Actions.DataModel.Serialization;
-namespace EarTrumpet.Actions.ViewModel
+namespace EarTrumpet.Actions.ViewModel;
+
+internal class ForegroundAppViewModel : SettingsAppItemViewModel
{
- class ForegroundAppViewModel : SettingsAppItemViewModel
+ public ForegroundAppViewModel()
{
- public ForegroundAppViewModel()
- {
- Id = AppRef.ForegroundAppId;
- DisplayName = Properties.Resources.ForegroundAppText;
- }
+ Id = AppRef.ForegroundAppId;
+ DisplayName = Properties.Resources.ForegroundAppText;
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/HotkeyViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/HotkeyViewModel.cs
index 3d830d1c9..19ed9cd55 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/HotkeyViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/HotkeyViewModel.cs
@@ -1,45 +1,45 @@
using EarTrumpet.Actions.DataModel.Serialization;
using System;
+using System.Globalization;
-namespace EarTrumpet.Actions.ViewModel
+namespace EarTrumpet.Actions.ViewModel;
+
+public class HotkeyViewModel : BindableBase
{
- public class HotkeyViewModel : BindableBase
- {
- public EarTrumpet.UI.ViewModels.HotkeyViewModel Hotkey { get; }
+ public EarTrumpet.UI.ViewModels.HotkeyViewModel Hotkey { get; }
- private HotkeyTrigger _trigger;
+ private HotkeyTrigger _trigger;
- public HotkeyViewModel(HotkeyTrigger trigger)
+ public HotkeyViewModel(HotkeyTrigger trigger)
+ {
+ _trigger = trigger;
+ Hotkey = new EarTrumpet.UI.ViewModels.HotkeyViewModel(_trigger.Option, (newHotkey) =>
{
- _trigger = trigger;
- Hotkey = new EarTrumpet.UI.ViewModels.HotkeyViewModel(_trigger.Option, (newHotkey) =>
- {
- _trigger.Option = newHotkey;
- RaisePropertyChanged(nameof(Hotkey));
- });
- }
+ _trigger.Option = newHotkey;
+ RaisePropertyChanged(nameof(Hotkey));
+ });
+ }
- public override string ToString()
+ public override string ToString()
+ {
+ if (_trigger.Option.IsEmpty)
+ {
+ return ResolveResource("EmptyText");
+ }
+ else
{
- if (_trigger.Option.IsEmpty)
- {
- return ResolveResource("EmptyText");
- }
- else
- {
- return _trigger.Option.ToString();
- }
+ return _trigger.Option.ToString();
}
+ }
- private string ResolveResource(string suffix)
+ private string ResolveResource(string suffix)
+ {
+ var res = $"{_trigger.GetType().Name}_{suffix}";
+ var ret = Properties.Resources.ResourceManager.GetString(res, CultureInfo.CurrentCulture);
+ if (string.IsNullOrWhiteSpace(ret))
{
- var res = $"{_trigger.GetType().Name}_{suffix}";
- var ret = Properties.Resources.ResourceManager.GetString(res);
- if (string.IsNullOrWhiteSpace(ret))
- {
- throw new NotImplementedException($"Missing resource: {res}");
- }
- return ret;
+ throw new NotImplementedException($"Missing resource: {res}");
}
+ return ret;
}
}
diff --git a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/IOptionViewModel.cs b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/IOptionViewModel.cs
index 7e84d1053..915c2bfb8 100644
--- a/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/IOptionViewModel.cs
+++ b/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/IOptionViewModel.cs
@@ -1,10 +1,9 @@
using System.Collections.ObjectModel;
-namespace EarTrumpet.Actions.ViewModel
+namespace EarTrumpet.Actions.ViewModel;
+
+internal interface IOptionViewModel
{
- interface IOptionViewModel
- {
- ObservableCollection