Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/upgrades/dotnet-upgrade-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# .NET 8.0 Upgrade Plan

## Execution Steps

Execute steps below sequentially one by one in the order they are listed.

1. Validate that a .NET 8.0 SDK required for this upgrade is installed on the machine and if not, help to get it installed.
2. Ensure that the SDK version specified in global.json files is compatible with the .NET 8.0 upgrade.
3. Upgrade autoShell.csproj

## Settings

This section contains settings and data used by execution steps.

### Excluded projects

No projects are excluded from this upgrade.

### Aggregate NuGet packages modifications across all projects

NuGet packages used across all selected projects or their dependencies that need version update in projects that reference them.

| Package Name | Current Version | New Version | Description |
|:------------------------------------|:---------------:|:-----------:|:---------------------------------------------------------|
| AudioSwitcher.AudioApi | 3.0.0 | | No supported version found for .NET 8.0 - needs removal |
| AudioSwitcher.AudioApi.CoreAudio | 3.0.3 | | No supported version found for .NET 8.0 - needs removal |
| Newtonsoft.Json | 13.0.3 | 13.0.4 | Recommended for .NET 8.0 |

### Project upgrade details

This section contains details about each project upgrade and modifications that need to be done in the project.

#### autoShell.csproj modifications

Project properties changes:
- Project file needs to be converted to SDK-style
- Target framework should be changed from `net48` to `net8.0-windows`

NuGet packages changes:
- AudioSwitcher.AudioApi should be removed (*no supported version for .NET 8.0*)
- AudioSwitcher.AudioApi.CoreAudio should be removed (*no supported version for .NET 8.0*)
- Newtonsoft.Json should be updated from `13.0.3` to `13.0.4` (*recommended for .NET 8.0*)

Other changes:
- Code using AudioSwitcher APIs will need to be updated or alternative audio control libraries found
47 changes: 47 additions & 0 deletions .github/upgrades/dotnet-upgrade-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# .NET 8.0 Upgrade Report

## Project target framework modifications

| Project name | Old Target Framework | New Target Framework | Commits |
|:------------------|:--------------------:|:--------------------:|:-----------------------------------------------------|
| autoShell.csproj | net48 | net8.0-windows | 8535806a, 872e2361, 1a5fd106, 68cf297b, 6265eccb |

## NuGet Packages

| Package Name | Old Version | New Version | Commit Id |
|:------------------------------------|:-----------:|:-----------:|:------------|
| AudioSwitcher.AudioApi | 3.0.0 | (removed) | 872e2361 |
| AudioSwitcher.AudioApi.CoreAudio | 3.0.3 | (removed) | 872e2361 |
| Newtonsoft.Json | 13.0.3 | 13.0.4 | 872e2361 |

## All commits

| Commit ID | Description |
|:------------|:-------------------------------------------------------------------------------------------------|
| 86a0cbb2 | Commit upgrade plan |
| 8535806a | Migrate autoShell project to SDK-style and .NET 8 |
| 872e2361 | Update dependencies in autoShell.csproj |
| 1a5fd106 | Removed AudioSwitcher.AudioApi.CoreAudio using directive |
| 68cf297b | Re-add AudioSwitcher.AudioApi.CoreAudio using directive with GetDefaultDevice method |
| c178dfa4 | Remove misplaced using directive and fully qualify AudioSwitcher types |
| 188289ae | Comment out AudioSwitcher code with instructions to restore |
| 6265eccb | Store final changes for step 'Upgrade autoShell.csproj' |

## Project feature upgrades

### autoShell.csproj

Here is what changed for the project during upgrade:

- **Project format conversion**: Converted from legacy .NET Framework 4.8 project format to modern SDK-style project
- **Target framework update**: Changed from `net48` to `net8.0-windows`
- **AudioSwitcher API replacement**: Replaced incompatible AudioSwitcher.AudioApi and AudioSwitcher.AudioApi.CoreAudio packages with Windows Core Audio COM interop implementation
- Added new file `CoreAudioInterop.cs` with COM interface definitions for `IMMDeviceEnumerator`, `IMMDevice`, and `IAudioEndpointVolume`
- Refactored `SetMasterVolume`, `RestoreMasterVolume`, and `SetMasterMute` methods to use native Windows Core Audio API
- **Assembly references cleanup**: Removed legacy assembly references that are now implicit in SDK-style projects (System, System.Core, System.Data, etc.)
- **Package update**: Updated Newtonsoft.Json from 13.0.3 to 13.0.4

## Next steps

- Test the audio volume control functionality to ensure the Windows Core Audio COM interop implementation works as expected
- Consider adding error handling for edge cases in audio device enumeration
59 changes: 48 additions & 11 deletions dotnet/autoShell/AutoShell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using AudioSwitcher.AudioApi.CoreAudio;
using System.IO;
using System.Collections;
using Newtonsoft.Json.Linq;
Expand All @@ -22,8 +21,8 @@ namespace autoShell
internal partial class AutoShell
{
// create a map of friendly names to executable paths
static Hashtable s_friendlyNameToPath = new Hashtable();
static Hashtable s_friendlyNameToId = new Hashtable();
static Hashtable s_friendlyNameToPath = [];
static Hashtable s_friendlyNameToId = [];
static double s_savedVolumePct = 0.0;

static AutoShell()
Expand Down Expand Up @@ -100,22 +99,60 @@ static SortedList<string, string> GetAllInstalledAppsIds()

static void SetMasterVolume(int pct)
{
CoreAudioDevice defaultPlaybackDevice = new CoreAudioController().DefaultPlaybackDevice;
s_savedVolumePct = defaultPlaybackDevice.Volume;
defaultPlaybackDevice.Volume = pct;
// Using Windows Core Audio API via COM interop
try
{
var deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out IMMDevice device);
var audioEndpointVolumeGuid = typeof(IAudioEndpointVolume).GUID;
device.Activate(ref audioEndpointVolumeGuid, 0, IntPtr.Zero, out object obj);
var audioEndpointVolume = (IAudioEndpointVolume)obj;
audioEndpointVolume.GetMasterVolumeLevelScalar(out float currentVolume);
s_savedVolumePct = currentVolume * 100.0;
audioEndpointVolume.SetMasterVolumeLevelScalar(pct / 100.0f, Guid.Empty);
}
catch (Exception ex)
{
Debug.WriteLine("Failed to set volume: " + ex.Message);
}
}

static void RestoreMasterVolume()
{
CoreAudioDevice defaultPlaybackDevice = new CoreAudioController().DefaultPlaybackDevice;
defaultPlaybackDevice.Volume = s_savedVolumePct;
// Using Windows Core Audio API via COM interop
try
{
var deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out IMMDevice device);
var audioEndpointVolumeGuid = typeof(IAudioEndpointVolume).GUID;
device.Activate(ref audioEndpointVolumeGuid, 0, IntPtr.Zero, out object obj);
var audioEndpointVolume = (IAudioEndpointVolume)obj;
audioEndpointVolume.SetMasterVolumeLevelScalar((float)(s_savedVolumePct / 100.0), Guid.Empty);
}
catch (Exception ex)
{
Debug.WriteLine("Failed to restore volume: " + ex.Message);
}
}

static void SetMasterMute(bool mute)
{
CoreAudioDevice defaultPlaybackDevice = new CoreAudioController().DefaultPlaybackDevice;
Debug.WriteLine("Current Mute:" + defaultPlaybackDevice.IsMuted);
defaultPlaybackDevice.Mute(mute);
// Using Windows Core Audio API via COM interop
try
{
var deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out IMMDevice device);
var audioEndpointVolumeGuid = typeof(IAudioEndpointVolume).GUID;
device.Activate(ref audioEndpointVolumeGuid, 0, IntPtr.Zero, out object obj);
var audioEndpointVolume = (IAudioEndpointVolume)obj;
audioEndpointVolume.GetMute(out bool currentMute);
Debug.WriteLine("Current Mute:" + currentMute);
audioEndpointVolume.SetMute(mute, Guid.Empty);
}
catch (Exception ex)
{
Debug.WriteLine("Failed to set mute: " + ex.Message);
}
}

static string ResolveProcessNameFromFriendlyName(string friendlyName)
Expand Down
39 changes: 0 additions & 39 deletions dotnet/autoShell/Properties/AssemblyInfo.cs

This file was deleted.

83 changes: 8 additions & 75 deletions dotnet/autoShell/autoShell.csproj
Original file line number Diff line number Diff line change
@@ -1,81 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{7D095B6C-7EC1-4127-B1EA-8D7DC91A1C1C}</ProjectGuid>
<TargetFramework>net8.0-windows</TargetFramework>
<OutputType>Exe</OutputType>
<RootNamespace>autoShell</RootNamespace>
<AssemblyName>autoShell</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>true</UseWPF>
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="AudioSwitcher.AudioApi, Version=3.0.0.209, Culture=neutral, PublicKeyToken=fda5729e2db3a64f, processorArchitecture=MSIL">
<HintPath>packages\AudioSwitcher.AudioApi.3.0.0\lib\net40\AudioSwitcher.AudioApi.dll</HintPath>
</Reference>
<Reference Include="AudioSwitcher.AudioApi.CoreAudio, Version=3.0.0.209, Culture=neutral, PublicKeyToken=fda5729e2db3a64f, processorArchitecture=MSIL">
<HintPath>packages\AudioSwitcher.AudioApi.CoreAudio.3.0.3\lib\net48\AudioSwitcher.AudioApi.CoreAudio.dll</HintPath>
</Reference>
<Reference Include="CustomMarshalers" />
<Reference Include="Microsoft.VisualBasic" />
<Reference Include="Microsoft.WindowsAPICodePack, Version=1.1.5.0, Culture=neutral, PublicKeyToken=8985beaab7ea3f04, processorArchitecture=MSIL">
<HintPath>packages\Microsoft-WindowsAPICodePack-Core.1.1.5\lib\net48\Microsoft.WindowsAPICodePack.dll</HintPath>
</Reference>
<Reference Include="Microsoft.WindowsAPICodePack.Shell, Version=1.1.5.0, Culture=neutral, PublicKeyToken=8985beaab7ea3f04, processorArchitecture=MSIL">
<HintPath>packages\Microsoft-WindowsAPICodePack-Shell.1.1.5\lib\net48\Microsoft.WindowsAPICodePack.Shell.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xaml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
<Reference Include="WindowsFormsIntegration" />
</ItemGroup>
<ItemGroup>
<Compile Include="AutoShell.cs" />
<Compile Include="AutoShell_Themes.cs" />
<Compile Include="AutoShell_Win32.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
<PackageReference Include="Microsoft-WindowsAPICodePack-Core" Version="1.1.5" />
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
79 changes: 79 additions & 0 deletions dotnet/autoShell/dotnet/autoShell/CoreAudioInterop.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Runtime.InteropServices;

namespace autoShell
{
// Windows Core Audio API COM interop definitions
// These replace the AudioSwitcher.AudioApi package for volume control

internal enum EDataFlow
{
eRender = 0,
eCapture = 1,
eAll = 2
}

internal enum ERole
{
eConsole = 0,
eMultimedia = 1,
eCommunications = 2
}

[ComImport]
[Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
internal class MMDeviceEnumerator
{
}

[ComImport]
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IMMDeviceEnumerator
{
int EnumAudioEndpoints(EDataFlow dataFlow, int dwStateMask, out IntPtr ppDevices);
int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppDevice);
int GetDevice(string pwstrId, out IMMDevice ppDevice);
int RegisterEndpointNotificationCallback(IntPtr pClient);
int UnregisterEndpointNotificationCallback(IntPtr pClient);
}

[ComImport]
[Guid("D666063F-1587-4E43-81F1-B948E807363F")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IMMDevice
{
int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
int OpenPropertyStore(int stgmAccess, out IntPtr ppProperties);
int GetId([MarshalAs(UnmanagedType.LPWStr)] out string ppstrId);
int GetState(out int pdwState);
}

[ComImport]
[Guid("5CDF2C82-841E-4546-9722-0CF74078229A")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IAudioEndpointVolume
{
int RegisterControlChangeNotify(IntPtr pNotify);
int UnregisterControlChangeNotify(IntPtr pNotify);
int GetChannelCount(out int pnChannelCount);
int SetMasterVolumeLevel(float fLevelDB, Guid pguidEventContext);
int SetMasterVolumeLevelScalar(float fLevel, Guid pguidEventContext);
int GetMasterVolumeLevel(out float pfLevelDB);
int GetMasterVolumeLevelScalar(out float pfLevel);
int SetChannelVolumeLevel(int nChannel, float fLevelDB, Guid pguidEventContext);
int SetChannelVolumeLevelScalar(int nChannel, float fLevel, Guid pguidEventContext);
int GetChannelVolumeLevel(int nChannel, out float pfLevelDB);
int GetChannelVolumeLevelScalar(int nChannel, out float pfLevel);
int SetMute([MarshalAs(UnmanagedType.Bool)] bool bMute, Guid pguidEventContext);
int GetMute([MarshalAs(UnmanagedType.Bool)] out bool pbMute);
int GetVolumeStepInfo(out int pnStep, out int pnStepCount);
int VolumeStepUp(Guid pguidEventContext);
int VolumeStepDown(Guid pguidEventContext);
int QueryHardwareSupport(out int pdwHardwareSupportMask);
int GetVolumeRange(out float pflVolumeMindB, out float pflVolumeMaxdB, out float pflVolumeIncrementdB);
}
}