blob: c04cf7757f175817bf95ef449b67cdd51ee740cb [file]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/persistent_display_layout_manager.h"
#include <memory>
#include "base/files/file.h"
#include "base/files/file_error_or.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "remoting/base/async_file_util.h"
#include "remoting/base/constants.h"
#include "remoting/base/logging.h"
#include "remoting/proto/control.pb.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
namespace remoting {
namespace {
// Delay to throttle writes to the display layout file.
constexpr base::TimeDelta kWriteDisplayLayoutDelay = base::Seconds(10);
bool IsLayoutValid(const protocol::VideoLayout& layout) {
if (layout.video_track_size() < 1 || layout.video_track_size() > 4) {
LOG(ERROR) << "Invalid track count: " << layout.video_track_size();
return false;
}
for (const auto& track : layout.video_track()) {
if (track.position_x() < 0 || track.position_y() < 0) {
LOG(ERROR) << "Invalid position: " << track.position_x() << ","
<< track.position_y();
return false;
}
if (track.width() < 1 || track.width() > 32767 || track.height() < 1 ||
track.height() > 32767) {
LOG(ERROR) << "Invalid size: " << track.width() << "x" << track.height();
return false;
}
if (track.x_dpi() < 1) {
LOG(ERROR) << "Invalid x_dpi: " << track.x_dpi();
return false;
}
if (track.has_y_dpi() && track.y_dpi() != track.x_dpi()) {
LOG(ERROR) << "Invalid y_dpi: " << track.y_dpi()
<< " (must match x_dpi or be unset)";
return false;
}
}
return true;
}
std::unique_ptr<protocol::VideoLayout> CreateDefaultLayout() {
auto default_layout = std::make_unique<protocol::VideoLayout>();
default_layout->set_pixel_type(
protocol::VideoLayout::PixelType::VideoLayout_PixelType_LOGICAL);
protocol::VideoTrackLayout* track = default_layout->add_video_track();
track->set_position_x(0);
track->set_position_y(0);
track->set_width(1280);
track->set_height(960);
track->set_x_dpi(kDefaultDpi);
track->set_y_dpi(kDefaultDpi);
return default_layout;
}
} // namespace
PersistentDisplayLayoutManager::PersistentDisplayLayoutManager(
const base::FilePath& display_layout_file_path,
std::unique_ptr<DesktopDisplayInfoMonitor> display_info_monitor,
base::WeakPtr<DesktopResizer> desktop_resizer)
: display_layout_file_path_(display_layout_file_path),
display_info_monitor_(std::move(display_info_monitor)),
desktop_resizer_(std::move(desktop_resizer)),
write_display_layout_timer_(
FROM_HERE,
kWriteDisplayLayoutDelay,
base::BindRepeating(
&PersistentDisplayLayoutManager::WriteDisplayLayout,
base::Unretained(this))) {}
PersistentDisplayLayoutManager::~PersistentDisplayLayoutManager() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void PersistentDisplayLayoutManager::Start(base::OnceClosure on_done) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ReadFileAsync(
display_layout_file_path_,
base::BindOnce(&PersistentDisplayLayoutManager::OnDisplayLayoutFileLoaded,
weak_ptr_factory_.GetWeakPtr(), std::move(on_done)));
}
void PersistentDisplayLayoutManager::OnDisplayLayoutFileLoaded(
base::OnceClosure on_done,
base::FileErrorOr<std::string> load_file_result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ApplyDisplayLayout(load_file_result);
display_info_monitor_->AddCallback(base::BindRepeating(
&PersistentDisplayLayoutManager::OnDisplayInfoReceived,
weak_ptr_factory_.GetWeakPtr()));
display_info_monitor_->Start();
std::move(on_done).Run();
}
void PersistentDisplayLayoutManager::OnDisplayInfoReceived() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto* display_info = display_info_monitor_->GetLatestDisplayInfo();
DCHECK(display_info);
latest_display_layout_ = display_info->GetVideoLayoutProto();
// This either starts or delays the timer.
write_display_layout_timer_.Reset();
}
void PersistentDisplayLayoutManager::ApplyDisplayLayout(
const base::FileErrorOr<std::string>& load_file_result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bool success = false;
if (load_file_result.has_value()) {
auto display_layout_from_file = std::make_unique<protocol::VideoLayout>();
if (display_layout_from_file->ParseFromString(*load_file_result)) {
// Run some simple checks to make sure the file is not corrupted.
if (IsLayoutValid(*display_layout_from_file)) {
for (protocol::VideoTrackLayout& track :
*display_layout_from_file->mutable_video_track()) {
// Clear the screen ID to indicate that a new display should be
// created. See comment in protobuf.
track.clear_screen_id();
}
if (desktop_resizer_) {
HOST_LOG << "Applying display layout from file: "
<< display_layout_file_path_;
desktop_resizer_->SetVideoLayout(*display_layout_from_file);
success = true;
}
} else {
LOG(ERROR) << "Invalid display layout from file.";
}
} else {
LOG(ERROR) << "Failed to parse display layout.";
}
} else if (load_file_result.error() !=
base::File::Error::FILE_ERROR_NOT_FOUND) {
LOG(ERROR) << "Failed to read file " << display_layout_file_path_ << ": "
<< base::File::ErrorToString(load_file_result.error());
}
if (!success && desktop_resizer_) {
auto default_layout = CreateDefaultLayout();
HOST_LOG << "Applying standard default display layout ("
<< default_layout->video_track(0).width() << "x"
<< default_layout->video_track(0).height() << ").";
desktop_resizer_->SetVideoLayout(*default_layout);
}
}
void PersistentDisplayLayoutManager::WriteDisplayLayout() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
WriteFileAsync(
display_layout_file_path_, latest_display_layout_->SerializeAsString(),
base::BindOnce(
[](const base::FilePath& display_layout_file_path,
base::FileErrorOr<void> result) {
if (!result.has_value()) {
LOG(ERROR) << "Failed to write display layout to file "
<< display_layout_file_path << ": "
<< base::File::ErrorToString(result.error());
}
},
display_layout_file_path_));
}
} // namespace remoting