blob: 6fa4d182f823b5183c72a5231ca9dbac776d45a0 [file] [log] [blame]
// 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/linux/pipewire_mouse_cursor_capturer.h"
#include <algorithm>
#include <memory>
#include <optional>
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/sequence_checker.h"
#include "remoting/base/constants.h"
#include "remoting/host/linux/gnome_display_config.h"
#include "remoting/host/linux/pipewire_capture_stream.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
#include "third_party/webrtc/modules/desktop_capture/mouse_cursor.h"
#include "third_party/webrtc/modules/desktop_capture/mouse_cursor_monitor.h"
#include "third_party/webrtc/modules/desktop_capture/shared_desktop_frame.h"
namespace remoting {
namespace {
float CalculateFractionalCoordinate(int val, int size) {
if (size <= 1) {
return 0.f;
}
// Clamp to prevent bogus values, in case the PipeWire coordinates are
// out-of-sync with the display config.
return std::clamp(static_cast<float>(val) / (size - 1), 0.f, 1.f);
}
} // namespace
PipewireMouseCursorCapturer::PipewireMouseCursorCapturer(
base::WeakPtr<GnomeDisplayConfigMonitor> display_config_monitor,
base::WeakPtr<PipewireCaptureStreamManager> stream_manager)
: stream_manager_(std::move(stream_manager)) {
if (display_config_monitor) {
// Display config is used to calculate monitor DPIs.
display_config_subscription_ = display_config_monitor->AddCallback(
base::BindRepeating(&PipewireMouseCursorCapturer::OnDisplayConfig,
GetWeakPtr()),
/*call_with_current_config=*/true);
}
}
PipewireMouseCursorCapturer::~PipewireMouseCursorCapturer() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void PipewireMouseCursorCapturer::SetCallback(Callback* callback, Mode mode) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
callback_ = callback;
report_position_ = mode == Mode::SHAPE_AND_POSITION;
want_latest_cursor_ = callback_ != nullptr;
}
void PipewireMouseCursorCapturer::Capture() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!stream_manager_ || !callback_) {
return;
}
auto active_streams = stream_manager_->GetActiveStreams();
bool need_position = report_position_;
bool need_cursor = true;
for (auto [screen_id, stream] : active_streams) {
if (!stream) {
continue;
}
auto monitor_it = monitors_.find(screen_id);
if (need_position) {
if (monitor_it != monitors_.end()) {
std::optional<webrtc::DesktopVector> cursor_position =
stream->CaptureCursorPosition();
if (cursor_position.has_value()) {
callback_->OnMouseCursorFractionalPosition(
screen_id,
CalculateFractionalCoordinate(cursor_position->x(),
monitor_it->second.width),
CalculateFractionalCoordinate(cursor_position->y(),
monitor_it->second.height));
need_position = false;
}
} else {
// This is potentially spammy so we don't log at WARNING level.
VLOG(1) << "Cannot provide fractional position since monitor "
<< screen_id << " is not found.";
}
}
if (need_cursor) {
// `cursor` will be nullptr if it hasn't changed since the last call of
// CaptureCursor().
std::unique_ptr<webrtc::MouseCursor> cursor = stream->CaptureCursor();
if (cursor && cursor->image()->data()) {
latest_cursor_frame_ =
webrtc::SharedDesktopFrame::Wrap(cursor->TakeImage());
if (monitor_it != monitors_.end()) {
latest_cursor_frame_->set_dpi(
{monitor_it->second.dpi, monitor_it->second.dpi});
}
latest_cursor_hotspot_ = cursor->hotspot();
callback_->OnMouseCursor(ShareLatestCursor().release());
need_cursor = false;
}
}
if (!need_position && !need_cursor) {
break;
}
}
if (need_cursor && want_latest_cursor_ && latest_cursor_frame_) {
callback_->OnMouseCursor(ShareLatestCursor().release());
}
want_latest_cursor_ = false;
}
base::WeakPtr<PipewireMouseCursorCapturer>
PipewireMouseCursorCapturer::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void PipewireMouseCursorCapturer::OnDisplayConfig(
const GnomeDisplayConfig& config) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto physical_config = config;
physical_config.SwitchLayoutMode(GnomeDisplayConfig::LayoutMode::kPhysical);
monitors_.clear();
for (const auto& [name, monitor] : physical_config.monitors) {
const auto* current_mode = monitor.GetCurrentMode();
if (!current_mode) {
LOG(WARNING) << "Ignored monitor without current mode: " << name;
continue;
}
monitors_[GnomeDisplayConfig::GetScreenId(name)] = {
.dpi = static_cast<int>(kDefaultDpi * monitor.scale),
.width = current_mode->width,
.height = current_mode->height,
};
}
}
std::unique_ptr<webrtc::MouseCursor>
PipewireMouseCursorCapturer::ShareLatestCursor() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(latest_cursor_frame_);
return std::make_unique<webrtc::MouseCursor>(
latest_cursor_frame_->Share().release(), latest_cursor_hotspot_);
}
} // namespace remoting