chromium/ui/display/manager/display_port_observer.cc

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

#include "ui/display/manager/display_port_observer.h"

#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "third_party/re2/src/re2/re2.h"
#include "ui/display/types/display_snapshot.h"

namespace display {

namespace {

const LazyRE2 kTypecConnUeventPattern = {R"(TYPEC_PORT=port(\d+))"};

std::vector<uint32_t> ParseDrmSysfsAndFindPort(
    const std::vector<std::pair<uint64_t, base::FilePath>>&
        base_connector_id_and_syspath) {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);
  std::vector<uint32_t> port_nums;
  // Each pair is from each DisplaySnapshot.
  for (const auto& pair : base_connector_id_and_syspath) {
    auto base_connector_id = pair.first;
    const auto& sys_path = pair.second;
    // `sys_path` of a DRM device, i.e. /sys/class/drm/cardX.
    base::FileEnumerator enumerator(sys_path, false,
                                    base::FileEnumerator::DIRECTORIES,
                                    FILE_PATH_LITERAL("card*-DP-*"));
    // Each directory in `sys_path` represents a connector, so we fetch each
    // connector's ID by reading the `/sys/class/drm/*/connector_id` file
    // (e.g. /sys/class/drm/card0/card0-DP-1/connector_id stores 256). We use
    // the fetched connector id to compare and match the corresponding base
    // connector (root of MST hub tree) per each DisplaySnapshot.
    for (auto path = enumerator.Next(); !path.empty();
         path = enumerator.Next()) {
      std::string connector_id_str;
      uint64_t connector_id_int;
      if (!base::ReadFileToString(path.Append("connector_id"),
                                  &connector_id_str)) {
        continue;
      }
      base::TrimWhitespaceASCII(connector_id_str, base::TRIM_ALL,
                                &connector_id_str);
      if (!base::StringToUint64(connector_id_str, &connector_id_int)) {
        LOG(WARNING) << "Invalid connector id " << connector_id_str << " at "
                     << path.value();
        continue;
      }
      if (connector_id_int != base_connector_id) {
        continue;
      }
      // A connector with a matching id is found at this point, so break the
      // loop beyond here as there is no need to further look for a connector.

      // We find the USB-C port which the DP connector is routed to by
      // referring to the symlink to Type C connector at `typec_connector`
      // (i.e. /sys/class/drm/cardX/cardX-DP-X/typec_connector). This symlink
      // may not be present for older kernel and firmware.
      std::string typec_conn_uevent;
      uint32_t port_num;
      if (!base::ReadFileToString(path.Append("typec_connector/uevent"),
                                  &typec_conn_uevent)) {
        break;
      }
      if (!RE2::PartialMatch(typec_conn_uevent, *kTypecConnUeventPattern,
                             &port_num)) {
        break;
      }
      port_nums.push_back(port_num);
      break;
    }
  }
  return port_nums;
}

}  // namespace

DisplayPortObserver::DisplayPortObserver(
    DisplayConfigurator* configurator,
    base::RepeatingCallback<void(const std::vector<uint32_t>&)>
        on_port_change_callback)
    : configurator_(configurator),
      on_port_change_callback_(on_port_change_callback) {
  if (configurator_) {
    configurator_->AddObserver(this);
  }
}

DisplayPortObserver::~DisplayPortObserver() {
  if (configurator_) {
    configurator_->RemoveObserver(this);
  }
}

void DisplayPortObserver::OnDisplayConfigurationChanged(
    const DisplayConfigurator::DisplayStateList& display_states) {
  // If no changes in base connector ids, then assume no changes on ports, so
  // return early to prevent overkill.
  std::set<uint64_t> base_connector_ids_;
  for (display::DisplaySnapshot* state : display_states) {
    base_connector_ids_.insert(state->base_connector_id());
  }
  if (base_connector_ids_ == prev_base_connector_ids_) {
    return;
  }
  prev_base_connector_ids_ = base_connector_ids_;

  // For each DisplaySnapshot, extract base_connector_id and sys_path.
  std::vector<std::pair<uint64_t, base::FilePath>>
      base_connector_id_and_syspath;
  for (display::DisplaySnapshot* state : display_states) {
    base_connector_id_and_syspath.push_back(
        std::make_pair(state->base_connector_id(), state->sys_path()));
  }

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
      base::BindOnce(&ParseDrmSysfsAndFindPort, base_connector_id_and_syspath),
      base::BindOnce(&DisplayPortObserver::SetTypeCPortsUsingDisplays,
                     weak_ptr_factory_.GetWeakPtr()));
}

void DisplayPortObserver::OnDisplayConfigurationChangeFailed(
    const DisplayConfigurator::DisplayStateList& displays,
    MultipleDisplayState failed_new_state) {}

void DisplayPortObserver::SetTypeCPortsUsingDisplays(
    std::vector<uint32_t> port_nums) {
  on_port_change_callback_.Run(port_nums);
}

}  // namespace display