| // 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::OnDisplayModeChanged( |
| 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 (auto* 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 (auto* 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::OnDisplayModeChangeFailed( |
| 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 |