chromium/ui/display/win/audio_edid_scan.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ui/display/win/audio_edid_scan.h"

#include <objbase.h>

#include <oleauto.h>
#include <string.h>

#include "base/logging.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_variant.h"
#include "base/win/wmi.h"
#include "ui/display/util/edid_parser.h"

namespace display {
namespace win {

namespace {

// Use 'wmi_services' to execute the WmiGetMonitorRawEEdidV1Block method of
// 'get_edid_block' for the 'path' and 'id'.  The result will be EDID data
// which will be appended to the 'edid_blob'.  A single device (or monitor)
// will have one 'path' with multiple 'id' values.  To get all 'id' values
// for a given 'path', start with 'id' of zero and stop when 'id' cannot be
// read.
bool AppendBlock(Microsoft::WRL::ComPtr<IWbemServices>& wmi_services,
                 Microsoft::WRL::ComPtr<IWbemClassObject>& get_edid_block,
                 base::win::ScopedVariant& path,
                 int id,
                 std::vector<uint8_t>& edid_blob) {
  base::win::ScopedVariant block_id(id);

  if (FAILED(get_edid_block->Put(L"BlockId", 0, block_id.AsInput(), 0)))
    return false;

  Microsoft::WRL::ComPtr<IWbemClassObject> out_params;
  HRESULT hr = wmi_services->ExecMethod(
      V_BSTR(path.ptr()),
      base::win::ScopedBstr(L"WmiGetMonitorRawEEdidV1Block").Get(), 0, nullptr,
      get_edid_block.Get(), &out_params, nullptr);
  if (FAILED(hr))
    return false;

  base::win::ScopedVariant block_type;
  if (FAILED(
          out_params->Get(L"BlockType", 0, block_type.Receive(), nullptr, 0))) {
    return false;
  }

  base::win::ScopedVariant block_content;
  if (FAILED(out_params->Get(L"BlockContent", 0, block_content.Receive(),
                             nullptr, 0))) {
    return false;
  }

  // Block type: 1=EDID base block, 2=EDID block map, 255=Other
  constexpr int allowed_types[] = {1, 2, 255};
  const int found_type = V_I4(block_type.ptr());
  if (std::none_of(std::begin(allowed_types), std::end(allowed_types),
                   [found_type](int i) { return i == found_type; })) {
    return false;
  }

  if (block_content.type() != (VT_ARRAY | VT_UI1))
    return false;

  SAFEARRAY* array = V_ARRAY(block_content.ptr());
  if (SafeArrayGetDim(array) != 1)
    return false;

  long lower_bound = 0;
  long upper_bound = 0;
  SafeArrayGetLBound(array, 1, &lower_bound);
  SafeArrayGetUBound(array, 1, &upper_bound);
  if (lower_bound || upper_bound <= lower_bound)
    return false;

  uint8_t* block = nullptr;
  SafeArrayAccessData(array, reinterpret_cast<void**>(&block));
  edid_blob.insert(edid_blob.end(), block, block + upper_bound + 1);
  SafeArrayUnaccessData(array);

  return true;
}

}  // namespace

uint32_t ScanEdidBitstreams() {
  // The WMI service allows the querying of monitor-type devices which report
  // Extended Display Identification Data (EDID).  The WMI service can be
  // queried for a list of COM objects which represent the "paths" which
  // are associated with individual EDID devices.  Querying each of those
  // paths using the WmiGetMonitorRawEEdidV1Block method returns the EDID
  // blocks for those devices.  We query the extended blocks which contain
  // the Short Audio Descriptor (SAD), and parse them to obtain a bitmask
  // indicating which audio content is supported.  The mask consists of
  // AudioParameters::Format flags.  If multiple EDID devices are present,
  // the intersection of flags is reported.
  Microsoft::WRL::ComPtr<IWbemServices> wmi_services =
      base::win::CreateWmiConnection(true, L"ROOT\\WMI");
  if (!wmi_services)
    return 0;

  Microsoft::WRL::ComPtr<IWbemClassObject> get_edid_block;
  if (!base::win::CreateWmiClassMethodObject(
          wmi_services.Get(), L"WmiMonitorDescriptorMethods",
          L"WmiGetMonitorRawEEdidV1Block", &get_edid_block)) {
    return 0;
  }

  Microsoft::WRL::ComPtr<IEnumWbemClassObject> wmi_enumerator;
  HRESULT hr = wmi_services->CreateInstanceEnum(
      base::win::ScopedBstr(L"WmiMonitorDescriptorMethods").Get(),
      WBEM_FLAG_FORWARD_ONLY, nullptr, &wmi_enumerator);
  if (FAILED(hr))
    return 0;

  bool first = true;
  uint32_t bitstream_mask = 0;

  while (true) {
    Microsoft::WRL::ComPtr<IWbemClassObject> class_object;
    ULONG items_returned = 0;
    hr = wmi_enumerator->Next(WBEM_INFINITE, 1, &class_object, &items_returned);
    if (FAILED(hr) || hr == WBEM_S_FALSE || items_returned == 0)
      break;

    // Path refers to the display for which EDID will be read.
    base::win::ScopedVariant path;
    class_object->Get(L"__PATH", 0, path.Receive(), nullptr, nullptr);

    // Reading a single EDID block at a time, assemble all blocks for the path.
    int block_id = 0;
    std::vector<uint8_t> edid_blob;
    while (AppendBlock(wmi_services, get_edid_block, path, block_id, edid_blob))
      block_id++;

    if (first) {
      first = false;
      bitstream_mask =
          display::EdidParser(std::move(edid_blob)).audio_formats();
    } else {
      bitstream_mask &=
          display::EdidParser(std::move(edid_blob)).audio_formats();
    }
  }

  return bitstream_mask;
}

}  // namespace win
}  // namespace display