fix(audio-info): crash when device name contains special characters

This commit is contained in:
ReenigneArcher
2025-07-17 20:08:05 -04:00
parent a19312bbf1
commit 29d3bb8e77
11 changed files with 605 additions and 57 deletions

View File

@@ -59,6 +59,7 @@ set(PLATFORM_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/platform/windows/display_ram.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/windows/display_wgc.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/windows/audio.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/windows/tools/helper.h"
"${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/src/ViGEmClient.cpp"
"${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include/ViGEm/Client.h"
"${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include/ViGEm/Common.h"

View File

@@ -5,7 +5,7 @@
#define WINVER 0x0A00
// platform includes
#include <windows.h>
#include <Windows.h>
// standard includes
#include <cmath>

View File

@@ -24,7 +24,7 @@
#include <timeapi.h>
#include <userenv.h>
#include <winsock2.h>
#include <windows.h>
#include <Windows.h>
#include <winuser.h>
#include <wlanapi.h>
#include <ws2tcpip.h>

View File

@@ -9,7 +9,7 @@
#include <string_view>
// platform includes
#include <windows.h>
#include <Windows.h>
#include <winnt.h>
namespace platf {

View File

@@ -7,7 +7,7 @@
// platform includes
// disable clang-format header reordering
// clang-format off
#include <windows.h>
#include <Windows.h>
#include <aclapi.h>
// clang-format on

View File

@@ -6,7 +6,7 @@
// winsock2.h must be included before windows.h
// clang-format off
#include <winsock2.h>
#include <windows.h>
#include <Windows.h>
// clang-format on
#include <windns.h>
#include <winerror.h>

View File

@@ -0,0 +1,127 @@
/**
* @file src/platform/windows/tools/helper.h
* @brief Helpers for tools.
*/
#pragma once
// standard includes
#include <iostream>
#include <string>
// lib includes
#include <boost/locale.hpp>
// platform includes
#include <Windows.h>
/**
* @brief Safe console output utilities for Windows
* These functions prevent crashes when outputting strings with special characters.
* This is only used in tools/audio-info and tools/dxgi-info.
*/
namespace output {
// ASCII character range constants for safe output, https://www.ascii-code.com/
static constexpr int ASCII_PRINTABLE_START = 32;
static constexpr int ASCII_PRINTABLE_END = 127;
/**
* @brief Return a non-null wide string, defaulting to "Unknown" if null
* @param str The wide string to check
* @return A non-null wide string
*/
inline const wchar_t *no_null(const wchar_t *str) {
return str ? str : L"Unknown";
}
/**
* @brief Safely convert a wide string to console output using Windows API
* @param wstr The wide string to output
*/
inline void safe_wcout(const std::wstring &wstr) {
if (wstr.empty()) {
return;
}
// Try to use the Windows console API for proper Unicode output
if (const HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); hConsole != INVALID_HANDLE_VALUE) {
DWORD written;
if (WriteConsoleW(hConsole, wstr.c_str(), wstr.length(), &written, nullptr)) {
return; // Success with WriteConsoleW
}
}
// Fallback: convert to narrow string and output to std::cout
try {
const std::string narrow_str = boost::locale::conv::utf_to_utf<char>(wstr);
std::cout << narrow_str;
} catch (const boost::locale::conv::conversion_error &) {
// Final fallback: output character by character, replacing non-ASCII
for (const wchar_t wc : wstr) {
if (wc >= ASCII_PRINTABLE_START && wc < ASCII_PRINTABLE_END) { // Printable ASCII
std::cout << static_cast<char>(wc);
} else {
std::cout << '?';
}
}
}
}
/**
* @brief Safely convert a wide string literal to console encoding and output it
* @param wstr The wide string literal to output
*/
inline void safe_wcout(const wchar_t *wstr) {
if (wstr) {
safe_wcout(std::wstring(wstr));
} else {
std::cout << "Unknown";
}
}
/**
* @brief Safely convert a string to wide string and then to console output
* @param str The string to output
*/
inline void safe_cout(const std::string &str) {
if (str.empty()) {
return;
}
try {
// Convert string to wide string first, then to console output
const std::wstring wstr = boost::locale::conv::utf_to_utf<wchar_t>(str);
safe_wcout(wstr);
} catch (const boost::locale::conv::conversion_error &) {
// Fallback: output string directly, replacing problematic characters
for (const char c : str) {
if (c >= ASCII_PRINTABLE_START && c < ASCII_PRINTABLE_END) { // Printable ASCII
std::cout << c;
} else {
std::cout << '?';
}
}
}
}
/**
* @brief Output a label and value pair safely
* @param label The label to output
* @param value The wide string value to output
*/
inline void output_field(const std::string &label, const wchar_t *value) {
std::cout << label << " : ";
safe_wcout(value ? value : L"Unknown");
std::cout << std::endl;
}
/**
* @brief Output a label and string value pair
* @param label The label to output
* @param value The string value to output
*/
inline void output_field(const std::string &label, const std::string &value) {
std::cout << label << " : ";
safe_cout(value);
std::cout << std::endl;
}
} // namespace output

View File

@@ -0,0 +1,424 @@
/**
* @file tests/unit/platform/windows/tools/test_helper.cpp
* @brief Test src/platform/windows/tools/helper.cpp output functions.
*/
#include "../../../../tests_common.h"
#include <iostream>
#include <sstream>
#include <string>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#include <src/platform/windows/tools/helper.h>
#include <Windows.h>
#endif
namespace {
/**
* @brief Helper class to capture console output for testing
*/
class ConsoleCapture {
public:
ConsoleCapture() {
// Save original cout buffer
original_cout_buffer = std::cout.rdbuf();
// Redirect cout to our stringstream
std::cout.rdbuf(captured_output.rdbuf());
}
~ConsoleCapture() {
try {
// Restore original cout buffer
std::cout.rdbuf(original_cout_buffer);
} catch (std::exception &e) {
std::cerr << "Error restoring cout buffer: " << e.what() << std::endl;
}
}
std::string get_output() const {
return captured_output.str();
}
void clear() {
captured_output.str("");
captured_output.clear();
}
private:
std::streambuf *original_cout_buffer;
std::stringstream captured_output;
};
} // namespace
#ifdef _WIN32
/**
* @brief Test fixture for output namespace functions
*/
class UtilityOutputTest: public testing::Test { // NOSONAR
protected:
void SetUp() override {
capture = std::make_unique<ConsoleCapture>();
}
void TearDown() override {
capture.reset();
}
std::unique_ptr<ConsoleCapture> capture;
};
TEST_F(UtilityOutputTest, NoNullWithValidString) {
const wchar_t *test_string = L"Valid String";
const wchar_t *result = output::no_null(test_string);
EXPECT_EQ(result, test_string) << "Expected no change for valid string";
EXPECT_STREQ(result, L"Valid String") << "Expected exact match for valid string";
}
TEST_F(UtilityOutputTest, NoNullWithNullString) {
const wchar_t *null_string = nullptr;
const wchar_t *result = output::no_null(null_string);
EXPECT_NE(result, nullptr) << "Expected non-null result for null input";
EXPECT_STREQ(result, L"Unknown") << "Expected 'Unknown' for null input";
}
TEST_F(UtilityOutputTest, SafeWcoutWithValidWideString) {
std::wstring test_string = L"Hello World";
capture->clear();
output::safe_wcout(test_string);
const std::string output = capture->get_output();
// In test environment, WriteConsoleW will likely fail, so it should fall back to boost::locale conversion
EXPECT_EQ(output, "Hello World") << "Expected exact string output from safe_wcout";
}
TEST_F(UtilityOutputTest, SafeWcoutWithEmptyWideString) {
const std::wstring empty_string = L"";
capture->clear();
output::safe_wcout(empty_string);
const std::string output = capture->get_output();
// Empty string should return early without output
EXPECT_TRUE(output.empty()) << "Empty wide string should produce no output";
}
TEST_F(UtilityOutputTest, SafeWcoutWithValidWideStringPointer) {
const wchar_t *test_string = L"Test String";
capture->clear();
output::safe_wcout(test_string);
const std::string output = capture->get_output();
EXPECT_EQ(output, "Test String") << "Expected exact string output from safe_wcout with pointer";
}
TEST_F(UtilityOutputTest, SafeWcoutWithNullWideStringPointer) {
const wchar_t *null_string = nullptr;
capture->clear();
output::safe_wcout(null_string);
const std::string output = capture->get_output();
EXPECT_EQ(output, "Unknown") << "Expected 'Unknown' output from safe_wcout with null pointer";
}
TEST_F(UtilityOutputTest, SafeCoutWithValidString) {
const std::string test_string = "Hello World";
capture->clear();
output::safe_cout(test_string);
const std::string output = capture->get_output();
EXPECT_EQ(output, "Hello World") << "Expected exact string output from safe_cout";
}
TEST_F(UtilityOutputTest, SafeCoutWithEmptyString) {
std::string empty_string = "";
capture->clear();
output::safe_cout(empty_string);
const std::string output = capture->get_output();
// Empty string should return early
EXPECT_TRUE(output.empty()) << "Empty string should produce no output";
}
TEST_F(UtilityOutputTest, SafeCoutWithSpecialCharacters) {
const std::string special_string = "Test\x{01}\x{02}\x{03}String";
capture->clear();
output::safe_cout(special_string);
const std::string output = capture->get_output();
// Should handle special characters without crashing
EXPECT_FALSE(output.empty()) << "Expected some output from safe_cout with special chars";
// The function should either succeed with boost::locale conversion or fall back to character replacement
// In the fallback case, non-printable characters (\x{01}, \x{02}, \x{03}) should be replaced with '?'
// So we expect either the original string or "Test???String"
EXPECT_TRUE(output == "Test\x{01}\x{02}\x{03}String" || output == "Test???String")
<< "Expected either original string or fallback with '?' replacements, got: '" << output << "'";
}
TEST_F(UtilityOutputTest, OutputFieldWithWideStringPointer) {
const wchar_t *test_value = L"Test Value";
const std::string label = "Test Label";
capture->clear();
output::output_field(label, test_value);
const std::string output = capture->get_output();
EXPECT_TRUE(output.find("Test Label : ") != std::string::npos) << "Expected label in output";
EXPECT_TRUE(output.find("\n") != std::string::npos) << "Expected newline at the end of output";
}
TEST_F(UtilityOutputTest, OutputFieldWithNullWideStringPointer) {
const wchar_t *null_value = nullptr;
const std::string label = "Test Label";
capture->clear();
output::output_field(label, null_value);
const std::string output = capture->get_output();
EXPECT_TRUE(output.find("Test Label : ") != std::string::npos) << "Expected label in output";
EXPECT_TRUE(output.find("Unknown") != std::string::npos) << "Expected 'Unknown' for null value";
EXPECT_TRUE(output.find("\n") != std::string::npos) << "Expected newline at the end of output";
}
TEST_F(UtilityOutputTest, OutputFieldWithRegularString) {
const std::string test_value = "Test Value";
const std::string label = "Test Label";
capture->clear();
output::output_field(label, test_value);
const std::string output = capture->get_output();
EXPECT_TRUE(output.find("Test Label : ") != std::string::npos) << "Expected label in output";
EXPECT_TRUE(output.find("\n") != std::string::npos) << "Expected newline at the end of output";
}
TEST_F(UtilityOutputTest, OutputFieldWithEmptyString) {
const std::string empty_value = "";
const std::string label = "Empty Label";
capture->clear();
output::output_field(label, empty_value);
const std::string output = capture->get_output();
EXPECT_TRUE(output.find("Empty Label : ") != std::string::npos) << "Expected label in output";
EXPECT_TRUE(output.find("\n") != std::string::npos) << "Expected newline at the end of output";
}
TEST_F(UtilityOutputTest, OutputFieldWithSpecialCharactersInString) {
const std::string special_value = "Value\x{01}\x{02}\x{03}With\x{7F}Special";
const std::string label = "Special Label";
capture->clear();
output::output_field(label, special_value);
const std::string output = capture->get_output();
EXPECT_TRUE(output.find("Special Label : ") != std::string::npos) << "Expected label in output";
EXPECT_TRUE(output.find("\n") != std::string::npos) << "Expected newline at the end of output";
}
TEST_F(UtilityOutputTest, OutputFieldLabelFormatting) {
const std::string test_value = "Value";
const std::string label = "My Label";
capture->clear();
output::output_field(label, test_value);
const std::string output = capture->get_output();
// Check that the format is "Label : Value\n"
EXPECT_TRUE(output.find("My Label : ") == 0) << "Expected output to start with 'My Label : '";
EXPECT_TRUE(output.back() == '\n') << "Expected output to end with newline character";
}
// Test case for multiple consecutive calls
TEST_F(UtilityOutputTest, MultipleOutputFieldCalls) {
capture->clear();
output::output_field("Label1", "Value1");
output::output_field("Label2", L"Value2");
output::output_field("Label3", std::string("Value3"));
const std::string output = capture->get_output();
EXPECT_TRUE(output.find("Label1 : ") != std::string::npos) << "Expected 'Label1' in output";
EXPECT_TRUE(output.find("Label2 : ") != std::string::npos) << "Expected 'Label2' in output";
EXPECT_TRUE(output.find("Label3 : ") != std::string::npos) << "Expected 'Label3' in output";
// Count newlines - should have 3
const size_t newline_count = std::ranges::count(output, '\n');
EXPECT_EQ(newline_count, 3);
}
// Test cases for actual Unicode symbols and special characters
TEST_F(UtilityOutputTest, OutputFieldWithQuotationMarks) {
capture->clear();
// Test with various quotation marks
output::output_field("Single Quote", "Device 'Audio' Output");
output::output_field("Double Quote", "Device \"Audio\" Output");
output::output_field("Left Quote", "Device 'Audio' Output");
output::output_field("Right Quote", "Device 'Audio' Output");
output::output_field("Left Double Quote", "Device \u{201C}Audio\u{201D} Output");
output::output_field("Right Double Quote", "Device \u{201C}Audio\u{201D} Output");
const std::string output = capture->get_output();
EXPECT_TRUE(output.find("Single Quote : ") != std::string::npos) << "Expected 'Single Quote' in output";
EXPECT_TRUE(output.find("Double Quote : ") != std::string::npos) << "Expected 'Double Quote' in output";
EXPECT_TRUE(output.find("Left Quote : ") != std::string::npos) << "Expected 'Left Quote' in output";
EXPECT_TRUE(output.find("Right Quote : ") != std::string::npos) << "Expected 'Right Quote' in output";
EXPECT_TRUE(output.find("Left Double Quote : ") != std::string::npos) << "Expected 'Left Double Quote' in output";
EXPECT_TRUE(output.find("Right Double Quote : ") != std::string::npos) << "Expected 'Right Double Quote' in output";
}
TEST_F(UtilityOutputTest, OutputFieldWithTrademarkSymbols) {
capture->clear();
// Test with trademark and copyright symbols
output::output_field("Trademark", "Audio Device™");
output::output_field("Registered", "Audio Device®");
output::output_field("Copyright", "Audio Device©");
output::output_field("Combined", "Realtek® Audio™");
const std::string output = capture->get_output();
EXPECT_TRUE(output.find("Trademark : ") != std::string::npos) << "Expected 'Trademark' in output";
EXPECT_TRUE(output.find("Registered : ") != std::string::npos) << "Expected 'Registered' in output";
EXPECT_TRUE(output.find("Copyright : ") != std::string::npos) << "Expected 'Copyright' in output";
EXPECT_TRUE(output.find("Combined : ") != std::string::npos) << "Expected 'Combined' in output";
}
TEST_F(UtilityOutputTest, OutputFieldWithAccentedCharacters) {
capture->clear();
// Test with accented characters that might appear in device names
output::output_field("French Accents", "Haut-parleur à haute qualité");
output::output_field("Spanish Accents", "Altavoz ñáéíóú");
output::output_field("German Accents", "Lautsprecher äöü");
output::output_field("Mixed Accents", "àáâãäåæçèéêë");
const std::string output = capture->get_output();
EXPECT_TRUE(output.find("French Accents : ") != std::string::npos) << "Expected 'French Accents' in output";
EXPECT_TRUE(output.find("Spanish Accents : ") != std::string::npos) << "Expected 'Spanish Accents' in output";
EXPECT_TRUE(output.find("German Accents : ") != std::string::npos) << "Expected 'German Accents' in output";
EXPECT_TRUE(output.find("Mixed Accents : ") != std::string::npos) << "Expected 'Mixed Accents' in output";
}
TEST_F(UtilityOutputTest, OutputFieldWithSpecialSymbols) {
capture->clear();
// Test with various special symbols
output::output_field("Math Symbols", "Audio @ 44.1kHz ± 0.1%");
output::output_field("Punctuation", "Audio Device #1 & #2");
output::output_field("Programming", "Device $%^&*()");
output::output_field("Mixed Symbols", "Audio™ @#$%^&*()");
const std::string output = capture->get_output();
EXPECT_TRUE(output.find("Math Symbols : ") != std::string::npos) << "Expected 'Math Symbols' in output";
EXPECT_TRUE(output.find("Punctuation : ") != std::string::npos) << "Expected 'Punctuation' in output";
EXPECT_TRUE(output.find("Programming : ") != std::string::npos) << "Expected 'Programming' in output";
EXPECT_TRUE(output.find("Mixed Symbols : ") != std::string::npos) << "Expected 'Mixed Symbols' in output";
}
TEST_F(UtilityOutputTest, OutputFieldWithWideCharacterSymbols) {
capture->clear();
// Test with wide character symbols
const wchar_t *device_with_quotes = L"Device 'Audio' Output";
const wchar_t *device_with_trademark = L"Realtek® Audio™";
const wchar_t *device_with_accents = L"Haut-parleur àáâãäåæçèéêë";
const wchar_t *device_with_symbols = L"Audio ñáéíóú & symbols @#$%^&*()";
output::output_field("Wide Quotes", device_with_quotes);
output::output_field("Wide Trademark", device_with_trademark);
output::output_field("Wide Accents", device_with_accents);
output::output_field("Wide Symbols", device_with_symbols);
const std::string output = capture->get_output();
EXPECT_TRUE(output.find("Wide Quotes : ") != std::string::npos) << "Expected 'Wide Quotes' in output";
EXPECT_TRUE(output.find("Wide Trademark : ") != std::string::npos) << "Expected 'Wide Trademark' in output";
EXPECT_TRUE(output.find("Wide Accents : ") != std::string::npos) << "Expected 'Wide Accents' in output";
EXPECT_TRUE(output.find("Wide Symbols : ") != std::string::npos) << "Expected 'Wide Symbols' in output";
}
TEST_F(UtilityOutputTest, OutputFieldWithRealAudioDeviceNames) {
capture->clear();
// Test with realistic audio device names that might contain special characters
output::output_field("Realtek Device", "Realtek® High Definition Audio");
output::output_field("Creative Device", "Creative Sound Blaster™ X-Fi");
output::output_field("Logitech Device", "Logitech G533 Gaming Headset");
output::output_field("Bluetooth Device", "Sony WH-1000XM4 'Wireless' Headphones");
output::output_field("USB Device", "USB Audio Device @ 48kHz");
const std::string output = capture->get_output();
EXPECT_TRUE(output.find("Realtek Device : ") != std::string::npos) << "Expected 'Realtek Device' in output";
EXPECT_TRUE(output.find("Creative Device : ") != std::string::npos) << "Expected 'Creative Device' in output";
EXPECT_TRUE(output.find("Logitech Device : ") != std::string::npos) << "Expected 'Logitech Device' in output";
EXPECT_TRUE(output.find("Bluetooth Device : ") != std::string::npos) << "Expected 'Bluetooth Device' in output";
EXPECT_TRUE(output.find("USB Device : ") != std::string::npos) << "Expected 'USB Device' in output";
}
TEST_F(UtilityOutputTest, OutputFieldWithNullAndSpecialCharacters) {
capture->clear();
// Test null wide string with special characters in label
const wchar_t *null_value = nullptr;
output::output_field("Device™ with 'quotes'", null_value);
output::output_field("Device àáâãäåæçèéêë", null_value);
output::output_field("Device @#$%^&*()", null_value);
const std::string output = capture->get_output();
EXPECT_TRUE(output.find("Device™ with 'quotes' : ") != std::string::npos) << "Expected 'Device™ with quotes' in output";
EXPECT_TRUE(output.find("Device àáâãäåæçèéêë : ") != std::string::npos) << "Expected 'Device àáâãäåæçèéêë' in output";
EXPECT_TRUE(output.find("Device @#$%^&*() : ") != std::string::npos) << "Expected 'Device @#$%^&*()' in output";
// Should contain "Unknown" for null values
size_t unknown_count = 0;
size_t pos = 0;
while ((pos = output.find("Unknown", pos)) != std::string::npos) {
unknown_count++;
pos += 7; // length of "Unknown"
}
EXPECT_EQ(unknown_count, 3) << "Expected 'Unknown' to appear 3 times for null values";
}
TEST_F(UtilityOutputTest, OutputFieldWithEmptyAndSpecialCharacters) {
capture->clear();
// Test empty values with special character labels
output::output_field("Empty Device™", "");
output::output_field("Empty 'Quotes'", "");
output::output_field("Empty àáâãäåæçèéêë", "");
const std::string output = capture->get_output();
EXPECT_TRUE(output.find("Empty Device™ : ") != std::string::npos) << "Expected 'Empty Device™' in output";
EXPECT_TRUE(output.find("Empty 'Quotes' : ") != std::string::npos) << "Expected 'Empty Quotes' in output";
EXPECT_TRUE(output.find("Empty àáâãäåæçèéêë : ") != std::string::npos) << "Expected 'Empty àáâãäåæçèéêë' in output";
// Count newlines - should have 3
const size_t newline_count = std::ranges::count(output, '\n');
EXPECT_EQ(newline_count, 3) << "Expected 3 newlines for 3 output fields with empty values";
}
#else
// For non-Windows platforms, the output namespace doesn't exist
TEST(UtilityOutputTest, OutputNamespaceNotAvailableOnNonWindows) {
GTEST_SKIP() << "output namespace is Windows-specific";
}
#endif

View File

@@ -7,6 +7,7 @@ include_directories("${CMAKE_SOURCE_DIR}")
add_executable(dxgi-info dxgi.cpp)
set_target_properties(dxgi-info PROPERTIES CXX_STANDARD 23)
target_link_libraries(dxgi-info
${Boost_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
dxgi
${PLATFORM_LIBRARIES})

View File

@@ -6,17 +6,13 @@
// platform includes
#include <audioclient.h>
#include <codecvt>
#include <iostream>
#include <locale>
#include <mmdeviceapi.h>
#include <roapi.h>
#include <synchapi.h>
// lib includes
#include <boost/locale.hpp>
// local includes
#include "src/platform/windows/tools/helper.h"
#include "src/utility.h"
DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING
@@ -35,7 +31,7 @@ namespace audio {
template<class T>
void co_task_free(T *p) {
CoTaskMemFree((LPVOID) p);
CoTaskMemFree(static_cast<LPVOID>(p));
}
using device_enum_t = util::safe_ptr<IMMDeviceEnumerator, Release<IMMDeviceEnumerator>>;
@@ -63,10 +59,6 @@ namespace audio {
PROPVARIANT prop;
};
const wchar_t *no_null(const wchar_t *str) {
return str ? str : L"Unknown";
}
struct format_t {
std::string_view name;
int channels;
@@ -118,7 +110,11 @@ namespace audio {
wave_format->nAvgBytesPerSec = wave_format->nSamplesPerSec * wave_format->nBlockAlign;
if (wave_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
((PWAVEFORMATEXTENSIBLE) wave_format.get())->dwChannelMask = format.channel_mask;
// Access the extended format through proper offsetting
// WAVEFORMATEXTENSIBLE has WAVEFORMATEX as first member, so this is safe
const auto ext_format =
static_cast<PWAVEFORMATEXTENSIBLE>(static_cast<void *>(wave_format.get()));
ext_format->dwChannelMask = format.channel_mask;
}
}
@@ -128,7 +124,7 @@ namespace audio {
IID_IAudioClient,
CLSCTX_ALL,
nullptr,
(void **) &audio_client
static_cast<void **>(static_cast<void *>(&audio_client))
);
if (FAILED(status)) {
@@ -186,7 +182,7 @@ namespace audio {
return;
}
std::wstring device_state_string = L"Unknown"s;
std::wstring device_state_string;
switch (device_state) {
case DEVICE_STATE_ACTIVE:
device_state_string = L"Active"s;
@@ -200,28 +196,29 @@ namespace audio {
case DEVICE_STATE_NOTPRESENT:
device_state_string = L"Not present"s;
break;
default:
device_state_string = L"Unknown"s;
break;
}
std::wstring current_format = L"Unknown"s;
std::string current_format = "Unknown";
for (const auto &format : formats) {
// This will fail for any format that's not the mix format for this device,
// so we can take the first match as the current format to display.
auto audio_client = make_audio_client(device, format);
if (audio_client) {
current_format = boost::locale::conv::utf_to_utf<wchar_t>(format.name.data());
if (auto audio_client = make_audio_client(device, format)) {
current_format = std::string(format.name);
break;
}
}
std::wcout
<< L"===== Device ====="sv << std::endl
<< L"Device ID : "sv << wstring.get() << std::endl
<< L"Device name : "sv << no_null((LPWSTR) device_friendly_name.prop.pszVal) << std::endl
<< L"Adapter name : "sv << no_null((LPWSTR) adapter_friendly_name.prop.pszVal) << std::endl
<< L"Device description : "sv << no_null((LPWSTR) device_desc.prop.pszVal) << std::endl
<< L"Device state : "sv << device_state_string << std::endl
<< L"Current format : "sv << current_format << std::endl
<< std::endl;
std::cout << "===== Device =====" << std::endl;
output::output_field("Device ID ", wstring.get());
output::output_field("Device name ", output::no_null(device_friendly_name.prop.pwszVal));
output::output_field("Adapter name ", output::no_null(adapter_friendly_name.prop.pwszVal));
output::output_field("Device description ", output::no_null(device_desc.prop.pwszVal));
output::output_field("Device state ", device_state_string.c_str());
output::output_field("Current format ", current_format);
std::cout << std::endl;
}
} // namespace audio
@@ -268,15 +265,13 @@ int main(int argc, char *argv[]) {
}
}
HRESULT status;
audio::device_enum_t device_enum;
status = CoCreateInstance(
HRESULT status = CoCreateInstance(
CLSID_MMDeviceEnumerator,
nullptr,
CLSCTX_ALL,
IID_IMMDeviceEnumerator,
(void **) &device_enum
static_cast<void **>(static_cast<void *>(&device_enum))
);
if (FAILED(status)) {

View File

@@ -3,10 +3,12 @@
* @brief Displays information about connected displays and GPUs
*/
#define WINVER 0x0A00
#include "src/platform/windows/tools/helper.h"
#include "src/utility.h"
#include <d3dcommon.h>
#include <dxgi.h>
#include <format>
#include <iostream>
using namespace std::literals;
@@ -20,17 +22,14 @@ namespace dxgi {
using factory1_t = util::safe_ptr<IDXGIFactory1, Release<IDXGIFactory1>>;
using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter1>>;
using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
} // namespace dxgi
int main(int argc, char *argv[]) {
HRESULT status;
// Set ourselves as per-monitor DPI aware for accurate resolution values on High DPI systems
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
dxgi::factory1_t::pointer factory_p {};
status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **) &factory_p);
const HRESULT status = CreateDXGIFactory1(IID_IDXGIFactory1, static_cast<void **>(static_cast<void *>(&factory_p)));
dxgi::factory1_t factory {factory_p};
if (FAILED(status)) {
std::cout << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
@@ -44,21 +43,24 @@ int main(int argc, char *argv[]) {
DXGI_ADAPTER_DESC1 adapter_desc;
adapter->GetDesc1(&adapter_desc);
std::cout
<< "====== ADAPTER ====="sv << std::endl;
std::wcout
<< L"Device Name : "sv << adapter_desc.Description << std::endl;
std::cout
<< "Device Vendor ID : 0x"sv << util::hex(adapter_desc.VendorId).to_string_view() << std::endl
<< "Device Device ID : 0x"sv << util::hex(adapter_desc.DeviceId).to_string_view() << std::endl
<< "Device Video Mem : "sv << adapter_desc.DedicatedVideoMemory / 1048576 << " MiB"sv << std::endl
<< "Device Sys Mem : "sv << adapter_desc.DedicatedSystemMemory / 1048576 << " MiB"sv << std::endl
<< "Share Sys Mem : "sv << adapter_desc.SharedSystemMemory / 1048576 << " MiB"sv << std::endl
<< std::endl
<< " ====== OUTPUT ======"sv << std::endl;
std::cout << "====== ADAPTER =====" << std::endl;
output::output_field("Device Name ", adapter_desc.Description);
output::output_field("Device Vendor ID ", "0x" + util::hex(adapter_desc.VendorId).to_string());
output::output_field("Device Device ID ", "0x" + util::hex(adapter_desc.DeviceId).to_string());
output::output_field("Device Video Mem ", std::format("{} MiB", adapter_desc.DedicatedVideoMemory / 1048576));
output::output_field("Device Sys Mem ", std::format("{} MiB", adapter_desc.DedicatedSystemMemory / 1048576));
output::output_field("Share Sys Mem ", std::format("{} MiB", adapter_desc.SharedSystemMemory / 1048576));
dxgi::output_t::pointer output_p {};
bool has_outputs = false;
for (int y = 0; adapter->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) {
// Print the header only when we find the first output
if (!has_outputs) {
std::cout << std::endl
<< " ====== OUTPUT ======" << std::endl;
has_outputs = true;
}
dxgi::output_t output {output_p};
DXGI_OUTPUT_DESC desc;
@@ -67,13 +69,11 @@ int main(int argc, char *argv[]) {
auto width = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left;
auto height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top;
std::wcout
<< L" Output Name : "sv << desc.DeviceName << std::endl;
std::cout
<< " AttachedToDesktop : "sv << (desc.AttachedToDesktop ? "yes"sv : "no"sv) << std::endl
<< " Resolution : "sv << width << 'x' << height << std::endl
<< std::endl;
output::output_field(" Output Name ", desc.DeviceName);
output::output_field(" AttachedToDesktop ", desc.AttachedToDesktop ? "yes" : "no");
output::output_field(" Resolution ", std::format("{}x{}", width, height));
}
std::cout << std::endl;
}
return 0;