diff --git a/.gitignore b/.gitignore index 8a30d25..f93df06 100644 --- a/.gitignore +++ b/.gitignore @@ -1,398 +1,4 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore - -# User-specific files -*.rsuser -*.suo +/.vs +bin +obj *.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ -[Ll]ogs/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.tlog -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - -# Visual Studio 6 workspace and project file (working project files containing files to include in project) -*.dsw -*.dsp - -# Visual Studio 6 technical files -*.ncb -*.aps - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# Visual Studio History (VSHistory) files -.vshistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -# Fody - auto-generated XML schema -FodyWeavers.xsd - -# VS Code files for those working on multiple tools -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -# Windows Installer files from build outputs -*.cab -*.msi -*.msix -*.msm -*.msp - -# JetBrains Rider -*.sln.iml diff --git a/Enums.cs b/Enums.cs new file mode 100644 index 0000000..d46879f --- /dev/null +++ b/Enums.cs @@ -0,0 +1,35 @@ +using System; + +namespace SwitchSound +{ + [Flags] + enum EDeviceState : uint + { + Active = 1, + Disabled = 2, + NotPresent = 4, + Unplugged = 8, + All = Active | Disabled | NotPresent | Unplugged + } + + enum EDataFlow + { + Render, + Capture, + All + } + + enum ERole + { + Console, + Multimedia, + Communications + } + + enum StorageAccessMode : uint + { + Read, + Write, + ReadWrite + } +} \ No newline at end of file diff --git a/MultimediaDevice.cs b/MultimediaDevice.cs new file mode 100644 index 0000000..a644bba --- /dev/null +++ b/MultimediaDevice.cs @@ -0,0 +1,20 @@ +using System; +using System.Runtime.InteropServices; + +namespace SwitchSound +{ + [Guid("D666063F-1587-4E43-81F1-B948E807363F")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + interface IMultimediaDevice + { + void Activate(); + IPropertyStore OpenPropertyStore(StorageAccessMode accessMode); + string Id { [return : MarshalAs(UnmanagedType.LPWStr)] get; } + } + + static class MultimediaDevice + { + static PropertyKey PKEY_DEVICE_FRIENDLY_NAME = new PropertyKey(new Guid("A45C254E-DF1C-4EFD-8020-67D146A850E0"), 14); + public static string Name(this IMultimediaDevice device) => device.OpenPropertyStore(StorageAccessMode.Read).GetValue(ref PKEY_DEVICE_FRIENDLY_NAME).AsString; + } +} \ No newline at end of file diff --git a/MultimediaDeviceCollection.cs b/MultimediaDeviceCollection.cs new file mode 100644 index 0000000..dbb99d5 --- /dev/null +++ b/MultimediaDeviceCollection.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace SwitchSound +{ + [Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + interface IMultimediaDeviceCollection + { + uint Count { get; } + IMultimediaDevice this[uint index] { get; } + } + + static class MultimediaDeviceCollection + { + public static IEnumerable ToEnumerable(this IMultimediaDeviceCollection collection) + { + for (uint i = 0, n = collection.Count; i < n; ++i) + yield return collection[i]; + } + } +} \ No newline at end of file diff --git a/MultimediaDeviceEnumerator.cs b/MultimediaDeviceEnumerator.cs new file mode 100644 index 0000000..33aeb51 --- /dev/null +++ b/MultimediaDeviceEnumerator.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace SwitchSound +{ + [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + interface IMultimediaDeviceEnumerator + { + IMultimediaDeviceCollection EnumAudioEndpoints(EDataFlow dataFlow, EDeviceState stateMask); + IMultimediaDevice GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role); + } + + [ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] + class MultimediaDeviceEnumerator + { + } +} diff --git a/PolicyConfig.cs b/PolicyConfig.cs new file mode 100644 index 0000000..5b52316 --- /dev/null +++ b/PolicyConfig.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.InteropServices; + +namespace SwitchSound +{ + [Guid("F8679F50-850A-41CF-9C72-430F290290C8")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IPolicyConfig + { + void GetMixFormat(); + void GetDeviceFormat(); + void ResetDeviceFormat(); + void SetDeviceFormat(); + void GetProcessingPeriod(); + void SetProcessingPeriod(); + void GetShareMode(); + void SetShareMode(); + void GetPropertyValue(); + void SetPropertyValue(); + void SetDefaultEndpoint([MarshalAs(UnmanagedType.LPWStr)] string pszDeviceName, ERole role); + } + + [ComImport, Guid("870AF99C-171D-4F9E-AF0D-E63DF40C2BC9")] + class PolicyConfig + { + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..3cfff18 --- /dev/null +++ b/Program.cs @@ -0,0 +1,37 @@ +using System; + +namespace SwitchSound +{ + class Program + { + static void Main(string[] args) + { + try + { + var devices = (IMultimediaDeviceEnumerator)new MultimediaDeviceEnumerator(); + var default_id = devices.GetDefaultAudioEndpoint(EDataFlow.Render, ERole.Multimedia).Id; + + if (args.Length < 1) + foreach (var device in devices.EnumAudioEndpoints(EDataFlow.Render, EDeviceState.Active).ToEnumerable()) + { + var id = device.Id; + Console.WriteLine("{0}{1}\n {2}\n", id == default_id ? "*" : " ", device.Name(), id); + } + else + { + var new_default_id = args[0]; + if (new_default_id == default_id) + { + if (args.Length < 2) return; + new_default_id = args[1]; + } + ((IPolicyConfig)new PolicyConfig()).SetDefaultEndpoint(new_default_id, ERole.Multimedia); + } + } + catch (Exception e) + { + Console.Error.WriteLine(e.Message); + } + } + } +} diff --git a/PropVariant.cs b/PropVariant.cs new file mode 100644 index 0000000..349d4d1 --- /dev/null +++ b/PropVariant.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.InteropServices; + +namespace SwitchSound +{ + [StructLayout(LayoutKind.Sequential)] + struct PropVariant + { + short vt; + short wReserved1; + short wReserved2; + short wReserved3; + IntPtr LPWSTR; + + public string AsString => (VarEnum)vt == VarEnum.VT_LPWSTR ? Marshal.PtrToStringUni(LPWSTR) : null; + } +} \ No newline at end of file diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e3e0d09 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +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("SwitchSound")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SwitchSound")] +[assembly: AssemblyCopyright("Copyright © 2017 Sergey Nozhenko")] +[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("71716191-d69b-4409-bfca-d3a9579e03ca")] + +// 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/PropertyKey.cs b/PropertyKey.cs new file mode 100644 index 0000000..bd78e32 --- /dev/null +++ b/PropertyKey.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.InteropServices; + +namespace SwitchSound +{ + [StructLayout(LayoutKind.Sequential)] + struct PropertyKey + { + public Guid FormatId; + public uint PropertyId; + + public PropertyKey(Guid formatId, uint propertyId) + { + FormatId = formatId; + PropertyId = propertyId; + } + } +} \ No newline at end of file diff --git a/PropertyStore.cs b/PropertyStore.cs new file mode 100644 index 0000000..76e06e1 --- /dev/null +++ b/PropertyStore.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace SwitchSound +{ + [Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + interface IPropertyStore + { + void GetCount(); + void GetAt(); + PropVariant GetValue(ref PropertyKey propertyKey); + } +} \ No newline at end of file diff --git a/README.md b/README.md index 11f0de9..0e08206 100644 --- a/README.md +++ b/README.md @@ -1 +1,53 @@ -# SwitchSound \ No newline at end of file +# SwitchSound + +A tiny console tool for switching between audio output devices. + +#### Run: + + - Without agruments - list devices with their interface identifiers. + Currently active is marked with '*'. + + - With a list of interface identifiers as arguments - switch between devices in a circle: active -> next, last -> first. + +#### For example: + +``` +> SwitchSound.exe + +*S22E390 (HD Audio Driver for Display Audio) + {0.0.0.00000000}.{24a8b3c5-8ece-48eb-9980-cf104f3ff711} + + Digital Audio (S/PDIF) (High Definition Audio Device) + {0.0.0.00000000}.{42b8a793-61c3-430d-a214-d43c9b086421} + + Speakers (High Definition Audio Device) + {0.0.0.00000000}.{a4a19331-61d9-46d4-8118-33c5003c1c20} + + +> SwitchSound.exe {0.0.0.00000000}.{24a8b3c5-8ece-48eb-9980-cf104f3ff711} {0.0.0.00000000}.{a4a19331-61d9-46d4-8118-33c5003c1c20} +> SwitchSound.exe + + S22E390 (HD Audio Driver for Display Audio) + {0.0.0.00000000}.{24a8b3c5-8ece-48eb-9980-cf104f3ff711} + + Digital Audio (S/PDIF) (High Definition Audio Device) + {0.0.0.00000000}.{42b8a793-61c3-430d-a214-d43c9b086421} + +*Speakers (High Definition Audio Device) + {0.0.0.00000000}.{a4a19331-61d9-46d4-8118-33c5003c1c20} + + +> SwitchSound.exe {0.0.0.00000000}.{24a8b3c5-8ece-48eb-9980-cf104f3ff711} {0.0.0.00000000}.{a4a19331-61d9-46d4-8118-33c5003c1c20} +> SwitchSound.exe + +*S22E390 (HD Audio Driver for Display Audio) + {0.0.0.00000000}.{24a8b3c5-8ece-48eb-9980-cf104f3ff711} + + Digital Audio (S/PDIF) (High Definition Audio Device) + {0.0.0.00000000}.{42b8a793-61c3-430d-a214-d43c9b086421} + + Speakers (High Definition Audio Device) + {0.0.0.00000000}.{a4a19331-61d9-46d4-8118-33c5003c1c20} +``` + +A typical usage is to create a shortcut with arguments to switch between selected devices and assign it to a hotkey. diff --git a/SwitchSound.csproj b/SwitchSound.csproj new file mode 100644 index 0000000..88aa29c --- /dev/null +++ b/SwitchSound.csproj @@ -0,0 +1,64 @@ + + + + + Debug + AnyCPU + {71716191-D69B-4409-BFCA-D3A9579E03CA} + Exe + Properties + SwitchSound + SwitchSound + v4.0 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + none + true + bin\Release\ + TRACE + prompt + 4 + + + SwitchSound.ico + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SwitchSound.ico b/SwitchSound.ico new file mode 100644 index 0000000..9cd8efe Binary files /dev/null and b/SwitchSound.ico differ diff --git a/SwitchSound.sln b/SwitchSound.sln new file mode 100644 index 0000000..b7f1f14 --- /dev/null +++ b/SwitchSound.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwitchSound", "SwitchSound.csproj", "{71716191-D69B-4409-BFCA-D3A9579E03CA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {71716191-D69B-4409-BFCA-D3A9579E03CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71716191-D69B-4409-BFCA-D3A9579E03CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71716191-D69B-4409-BFCA-D3A9579E03CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71716191-D69B-4409-BFCA-D3A9579E03CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal