diff --git a/.github/upgrades/dotnet-upgrade-plan.md b/.github/upgrades/dotnet-upgrade-plan.md new file mode 100644 index 000000000..a297f4bdf --- /dev/null +++ b/.github/upgrades/dotnet-upgrade-plan.md @@ -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 diff --git a/.github/upgrades/dotnet-upgrade-report.md b/.github/upgrades/dotnet-upgrade-report.md new file mode 100644 index 000000000..441437ce3 --- /dev/null +++ b/.github/upgrades/dotnet-upgrade-report.md @@ -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 diff --git a/dotnet/autoShell/AutoShell.cs b/dotnet/autoShell/AutoShell.cs index 71a38cf4a..3b86210f9 100644 --- a/dotnet/autoShell/AutoShell.cs +++ b/dotnet/autoShell/AutoShell.cs @@ -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; @@ -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() @@ -100,22 +99,60 @@ static SortedList 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) diff --git a/dotnet/autoShell/Properties/AssemblyInfo.cs b/dotnet/autoShell/Properties/AssemblyInfo.cs deleted file mode 100644 index bf86218d5..000000000 --- a/dotnet/autoShell/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("autoShell")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("autoShell")] -[assembly: AssemblyCopyright("Copyright © 2024 Microsoft Corporation")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("7d095b6c-7ec1-4127-b1ea-8d7dc91a1c1c")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/dotnet/autoShell/autoShell.csproj b/dotnet/autoShell/autoShell.csproj index c6d1b6226..708b04ee7 100644 --- a/dotnet/autoShell/autoShell.csproj +++ b/dotnet/autoShell/autoShell.csproj @@ -1,81 +1,14 @@ - - - + - Debug - AnyCPU - {7D095B6C-7EC1-4127-B1EA-8D7DC91A1C1C} + net8.0-windows Exe - autoShell - autoShell - v4.8 - 512 - true - true - + true + true + true - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - packages\AudioSwitcher.AudioApi.3.0.0\lib\net40\AudioSwitcher.AudioApi.dll - - - packages\AudioSwitcher.AudioApi.CoreAudio.3.0.3\lib\net48\AudioSwitcher.AudioApi.CoreAudio.dll - - - - - packages\Microsoft-WindowsAPICodePack-Core.1.1.5\lib\net48\Microsoft.WindowsAPICodePack.dll - - - packages\Microsoft-WindowsAPICodePack-Shell.1.1.5\lib\net48\Microsoft.WindowsAPICodePack.Shell.dll - - - packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll - - - - - - - - - - - - - - - - - - - - - - - - - + + + - \ No newline at end of file diff --git a/dotnet/autoShell/dotnet/autoShell/CoreAudioInterop.cs b/dotnet/autoShell/dotnet/autoShell/CoreAudioInterop.cs new file mode 100644 index 000000000..4c3b32b3f --- /dev/null +++ b/dotnet/autoShell/dotnet/autoShell/CoreAudioInterop.cs @@ -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); + } +}