0

[crd] Remove remoting/client

All CRD native client code has been removed from Chromium, so there is
no more dependency on remoting/client. This CL just removes it. This
will simplify future development such that developers don't need to fix
remoting/client to keep it building.

Bug: 349891802
Change-Id: I190a2a72db863ac19eaa568ef1aa595294d76316
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5939398
Reviewed-by: Wanda Mora <morawand@google.com>
Reviewed-by: Side YILMAZ <sideyilmaz@chromium.org>
Auto-Submit: Yuwei Huang <yuweih@chromium.org>
Commit-Queue: Side YILMAZ <sideyilmaz@chromium.org>
Reviewed-by: Hans Wennborg <hans@chromium.org>
Reviewed-by: Joe Downing <joedow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1372364}
This commit is contained in:
Yuwei Huang
2024-10-22 22:06:25 +00:00
committed by Chromium LUCI CQ
parent a010d009b6
commit 3b9d445ece
150 changed files with 0 additions and 15878 deletions
BUILD.gn
infra/config
remoting
BUILD.gn
client
BUILD.gnDEPS
audio
chromoting_client.ccchromoting_client.hchromoting_client_runtime.ccchromoting_client_runtime.hchromoting_client_runtime_unittest.ccchromoting_session.ccchromoting_session.hclient_context.ccclient_context.hclient_telemetry_logger.ccclient_telemetry_logger.hclient_telemetry_logger_unittest.ccclient_user_interface.hconnect_to_host_info.ccconnect_to_host_info.hcursor_shape_stub_proxy.cccursor_shape_stub_proxy.h
display
dual_buffer_frame_consumer.ccdual_buffer_frame_consumer.hdual_buffer_frame_consumer_unittest.ccempty_cursor_filter.ccempty_cursor_filter.hempty_cursor_filter_unittest.ccfeedback_data.ccfeedback_data.hgesture_interpreter.ccgesture_interpreter.hhost_experiment_sender.cchost_experiment_sender.hin_memory_log_handler.ccin_memory_log_handler.h
input
notification
software_video_renderer.ccsoftware_video_renderer.hsoftware_video_renderer_unittest.cc
ui
test
testing/buildbot
tools/traffic_annotation/summary

@ -915,7 +915,6 @@ if (enable_remoting && ((is_linux && ozone_platform_x11) ||
group("traffic_annotation_auditor_dependencies") {
deps = [
"//chrome:chrome",
"//remoting/client",
"//remoting/host:host",
"//tools/traffic_annotation:annotations_xml",
]

@ -1519,10 +1519,6 @@
"label": "//tools/android/push_apps_to_background:push_apps_to_background_apk",
"type": "additional_compile_target",
},
"remoting/client:client": {
"label": "//remoting/client:client",
"type": "additional_compile_target",
},
"remoting/host:host": {
"label": "//remoting/host:host",
"type": "additional_compile_target",

@ -357,11 +357,6 @@ targets.compile_target(
label = "//tools/android/push_apps_to_background:push_apps_to_background_apk",
)
targets.compile_target(
name = "remoting/client:client",
label = "//remoting/client:client",
)
targets.compile_target(
name = "remoting/host:host",
label = "//remoting/host:host",

@ -68,7 +68,6 @@ group("test_support") {
"//base",
"//net",
"//remoting/base:test_support",
"//remoting/client",
"//remoting/codec:decoder",
"//remoting/codec:encoder",
"//remoting/protocol:test_support",
@ -96,11 +95,6 @@ test("remoting_unittests") {
"//base/test:test_support",
"//google_apis",
"//remoting/base:unit_tests",
"//remoting/client:unit_tests",
"//remoting/client/audio:unit_tests",
"//remoting/client/input:unit_tests",
"//remoting/client/notification:unit_tests",
"//remoting/client/ui:unit_tests",
"//remoting/protocol:unit_tests",
"//remoting/signaling:unit_tests",
"//testing/gmock",
@ -132,11 +126,6 @@ test("remoting_unittests") {
"wtsapi32.lib",
]
}
# TODO(crbug.com/40118868): Change to !is_chromeos once lacros-chrome is switched to target_os=chromeos.
if (!is_chromeos) {
deps += [ "//remoting/client/display:unit_tests" ]
}
}
if (enable_remoting_host) {
@ -156,8 +145,6 @@ if (enable_remoting_host) {
"//components/webrtc:thread_wrapper",
"//net:test_support",
"//remoting/base",
"//remoting/client:client",
"//remoting/client/audio:audio",
"//remoting/codec:encoder",
"//remoting/host:common",
"//remoting/host:test_support",

@ -1,109 +0,0 @@
# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/config/chromeos/ui_mode.gni")
static_library("client") {
sources = [
"chromoting_client.cc",
"chromoting_client.h",
"chromoting_client_runtime.cc",
"chromoting_client_runtime.h",
"chromoting_session.cc",
"chromoting_session.h",
"client_context.cc",
"client_context.h",
"client_telemetry_logger.cc",
"client_telemetry_logger.h",
"client_user_interface.h",
"connect_to_host_info.cc",
"connect_to_host_info.h",
"cursor_shape_stub_proxy.cc",
"cursor_shape_stub_proxy.h",
"dual_buffer_frame_consumer.cc",
"dual_buffer_frame_consumer.h",
"empty_cursor_filter.cc",
"empty_cursor_filter.h",
"feedback_data.cc",
"feedback_data.h",
"host_experiment_sender.cc",
"host_experiment_sender.h",
"in_memory_log_handler.cc",
"in_memory_log_handler.h",
"software_video_renderer.cc",
"software_video_renderer.h",
]
configs += [
"//build/config/compiler:wexit_time_destructors",
"//remoting/build/config:version",
]
public_deps = [ "//remoting/base" ]
deps = [
"//components/webrtc:thread_wrapper",
"//mojo/core/embedder",
"//remoting/base:authorization",
"//remoting/base:name_value_map",
"//remoting/client/audio",
"//remoting/client/input",
"//remoting/client/notification",
"//remoting/client/ui",
"//remoting/client/ui:ui_manipulation",
"//remoting/codec:decoder",
"//remoting/protocol",
"//remoting/signaling",
"//services/network:network_service",
"//services/network/public/cpp",
"//services/network/public/mojom",
"//third_party/libjingle_xmpp:rtc_xmllite",
"//third_party/libyuv",
"//third_party/webrtc_overrides:webrtc_component",
"//ui/events:dom_keycode_converter",
]
libs = []
if (!is_chromeos) {
# GestureInterpreter depends on //remoting/client/display, which currently
# doesn't build on CrOS. crbug.com/869578
sources += [
"gesture_interpreter.cc",
"gesture_interpreter.h",
]
deps += [ "//remoting/client/display" ]
}
if (is_android) {
libs += [ "android" ]
}
}
source_set("unit_tests") {
testonly = true
sources = [
"chromoting_client_runtime_unittest.cc",
"client_telemetry_logger_unittest.cc",
"dual_buffer_frame_consumer_unittest.cc",
"empty_cursor_filter_unittest.cc",
"software_video_renderer_unittest.cc",
]
configs += [ "//remoting/build/config:version" ]
deps = [
":client",
"//base/test:test_support",
"//remoting/codec:encoder",
"//remoting/proto",
"//remoting/protocol",
"//remoting/protocol:test_support",
"//testing/gmock",
"//testing/gtest",
"//third_party/webrtc_overrides:webrtc_component",
]
}

@ -1,11 +0,0 @@
include_rules = [
"+components/webrtc",
"+mojo/core/embedder",
"+net",
"+ui/events/keycodes/dom",
"+remoting/codec",
"+remoting/protocol",
"+remoting/signaling",
"+services/network",
]

@ -1,73 +0,0 @@
# Copyright 2017 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
source_set("audio") {
sources = [
"async_audio_data_supplier.cc",
"async_audio_data_supplier.h",
"audio_jitter_buffer.cc",
"audio_jitter_buffer.h",
"audio_playback_sink.h",
"audio_playback_stream.cc",
"audio_playback_stream.h",
"audio_player.cc",
"audio_player.h",
"audio_stream_format.cc",
"audio_stream_format.h",
]
configs += [ "//remoting/build/config:version" ]
deps = [
"//base",
"//remoting/base",
"//remoting/protocol",
]
libs = []
if (is_android) {
sources += [
"audio_player_android.cc",
"audio_player_android.h",
]
libs += [
"android",
"OpenSLES",
]
}
}
source_set("test_support") {
testonly = true
sources = [
"fake_async_audio_data_supplier.cc",
"fake_async_audio_data_supplier.h",
]
deps = [
"//base",
"//remoting/client/audio",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"audio_jitter_buffer_unittest.cc",
"audio_player_unittest.cc",
]
configs += [ "//remoting/build/config:version" ]
deps = [
":audio",
"//base",
"//remoting/proto",
"//testing/gmock",
"//testing/gtest",
]
}

@ -1,24 +0,0 @@
// Copyright 2018 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/client/audio/async_audio_data_supplier.h"
#include "base/check_op.h"
namespace remoting {
AsyncAudioDataSupplier::GetDataRequest::GetDataRequest(void* data_arg,
size_t bytes_needed_arg)
: data(data_arg), bytes_needed(bytes_needed_arg) {
DCHECK(data);
DCHECK_GT(bytes_needed, 0u);
}
AsyncAudioDataSupplier::GetDataRequest::~GetDataRequest() = default;
AsyncAudioDataSupplier::AsyncAudioDataSupplier() = default;
AsyncAudioDataSupplier::~AsyncAudioDataSupplier() = default;
} // namespace remoting

@ -1,49 +0,0 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_AUDIO_ASYNC_AUDIO_DATA_SUPPLIER_H_
#define REMOTING_CLIENT_AUDIO_ASYNC_AUDIO_DATA_SUPPLIER_H_
#include <memory>
#include "base/memory/raw_ptr.h"
namespace remoting {
// This interface allows caller to asynchronously request for audio data.
class AsyncAudioDataSupplier {
public:
class GetDataRequest {
public:
// |data| must outlive |this|.
GetDataRequest(void* data, size_t bytes_needed);
virtual ~GetDataRequest();
// Called when |data| has been filled with |bytes_needed| bytes of data.
//
// Caution: Do not add or drop requests (i.e. calling AsyncGetData() or
// ClearGetDataRequests()) directly inside OnDataFilled(), which has
// undefined behavior. Consider posting a task when necessary.
virtual void OnDataFilled() = 0;
const raw_ptr<void> data;
const size_t bytes_needed;
size_t bytes_extracted = 0;
};
AsyncAudioDataSupplier();
virtual ~AsyncAudioDataSupplier();
// Requests for more data from the supplier.
virtual void AsyncGetData(std::unique_ptr<GetDataRequest> request) = 0;
// Drops all pending get-data requests. You may want to call this before the
// caller is destroyed if the caller has a shorter lifetime than the supplier.
virtual void ClearGetDataRequests() = 0;
};
} // namespace remoting
#endif // REMOTING_CLIENT_AUDIO_ASYNC_AUDIO_DATA_SUPPLIER_H_

@ -1,184 +0,0 @@
// Copyright 2018 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/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "remoting/client/audio/audio_jitter_buffer.h"
#include <algorithm>
#include <string>
#include "base/check_op.h"
namespace {
// AudioJitterBuffer maintains a list of AudioPackets whose total playback
// duration <= |kMaxQueueLatency|.
// Once the buffer has run out of AudioPackets (latency reaches 0), it waits
// until the total latency reaches |kUnderrunRecoveryLatency| before it starts
// feeding the get-data requests. This helps reduce the frequency of stopping
// when the buffer underruns.
// If the total latency has reached |kMaxQueueLatency|, the oldest packets
// will get dropped until the latency is reduced to no more than
// |kOverrunRecoveryLatency|. This helps reduce the number of glitches when
// the buffer overruns.
// Otherwise the total latency can freely fluctuate between 0 and
// |kMaxQueueLatency|.
constexpr base::TimeDelta kMaxQueueLatency = base::Milliseconds(150);
constexpr base::TimeDelta kUnderrunRecoveryLatency = base::Milliseconds(60);
constexpr base::TimeDelta kOverrunRecoveryLatency = base::Milliseconds(90);
} // namespace
namespace remoting {
AudioJitterBuffer::AudioJitterBuffer(
OnFormatChangedCallback on_format_changed) {
DETACH_FROM_THREAD(thread_checker_);
on_format_changed_ = std::move(on_format_changed);
}
AudioJitterBuffer::~AudioJitterBuffer() = default;
void AudioJitterBuffer::AddAudioPacket(std::unique_ptr<AudioPacket> packet) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
CHECK_EQ(1, packet->data_size());
DCHECK_EQ(AudioPacket::ENCODING_RAW, packet->encoding());
DCHECK_NE(AudioPacket::SAMPLING_RATE_INVALID, packet->sampling_rate());
AudioStreamFormat stream_format;
stream_format.bytes_per_sample = packet->bytes_per_sample();
stream_format.channels = packet->channels();
stream_format.sample_rate = packet->sampling_rate();
DCHECK_GT(stream_format.bytes_per_sample, 0);
DCHECK_GT(stream_format.channels, 0);
DCHECK_GT(stream_format.sample_rate, 0);
DCHECK_EQ(packet->data(0).size() %
(stream_format.channels * stream_format.bytes_per_sample),
0u);
if (!stream_format_ || *stream_format_ != stream_format) {
ResetBuffer(stream_format);
}
// Push the new data to the back of the queue.
queued_bytes_ += packet->data(0).size();
queued_packets_.push_back(std::move(packet));
if (underrun_protection_mode_ &&
queued_bytes_ > GetBufferSizeFromTime(kUnderrunRecoveryLatency)) {
// The buffer has enough data to start feeding the requests.
underrun_protection_mode_ = false;
}
DropOverrunPackets();
ProcessGetDataRequests();
}
void AudioJitterBuffer::AsyncGetData(std::unique_ptr<GetDataRequest> request) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(stream_format_);
DCHECK_EQ(request->bytes_needed % stream_format_->bytes_per_sample, 0u);
queued_requests_.push_back(std::move(request));
ProcessGetDataRequests();
}
void AudioJitterBuffer::ClearGetDataRequests() {
queued_requests_.clear();
}
void AudioJitterBuffer::ResetBuffer(const AudioStreamFormat& new_format) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
queued_packets_.clear();
queued_bytes_ = 0;
first_packet_offset_ = 0;
ClearGetDataRequests();
stream_format_ = std::make_unique<AudioStreamFormat>(new_format);
underrun_protection_mode_ = true;
if (on_format_changed_) {
on_format_changed_.Run(*stream_format_);
}
}
void AudioJitterBuffer::ProcessGetDataRequests() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (underrun_protection_mode_) {
return;
}
// Get the active request if there is one.
while (!queued_requests_.empty() && !queued_packets_.empty()) {
auto& active_request = queued_requests_.front();
// Copy any available data into the active request up to as much requested.
while (active_request->bytes_extracted < active_request->bytes_needed &&
!queued_packets_.empty()) {
uint8_t* next_data = static_cast<uint8_t*>(active_request->data) +
active_request->bytes_extracted;
const std::string& packet_data = queued_packets_.front()->data(0);
size_t bytes_to_copy = std::min(
packet_data.size() - first_packet_offset_,
active_request->bytes_needed - active_request->bytes_extracted);
memcpy(next_data, packet_data.data() + first_packet_offset_,
bytes_to_copy);
first_packet_offset_ += bytes_to_copy;
active_request->bytes_extracted += bytes_to_copy;
queued_bytes_ -= bytes_to_copy;
DCHECK_GE(queued_bytes_, 0u);
// Pop off the packet if we've already consumed all its bytes.
if (queued_packets_.front()->data(0).size() == first_packet_offset_) {
queued_packets_.pop_front();
first_packet_offset_ = 0;
}
}
// If this request is fulfilled, call the callback and pop it off the queue.
if (active_request->bytes_extracted == active_request->bytes_needed) {
active_request->OnDataFilled();
queued_requests_.pop_front();
}
}
if (queued_packets_.empty()) {
// Buffer overrun.
underrun_protection_mode_ = true;
}
}
size_t AudioJitterBuffer::GetBufferSizeFromTime(
base::TimeDelta duration) const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(stream_format_);
return duration.InMilliseconds() * stream_format_->sample_rate *
stream_format_->bytes_per_sample * stream_format_->channels /
base::Time::kMillisecondsPerSecond;
}
void AudioJitterBuffer::DropOverrunPackets() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (queued_bytes_ <= GetBufferSizeFromTime(kMaxQueueLatency)) {
return;
}
size_t new_size = GetBufferSizeFromTime(kOverrunRecoveryLatency);
while (queued_bytes_ > new_size) {
queued_bytes_ -=
queued_packets_.front()->data(0).size() - first_packet_offset_;
DCHECK_GE(queued_bytes_, 0u);
queued_packets_.pop_front();
first_packet_offset_ = 0;
}
}
} // namespace remoting

@ -1,95 +0,0 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_AUDIO_AUDIO_JITTER_BUFFER_H_
#define REMOTING_CLIENT_AUDIO_AUDIO_JITTER_BUFFER_H_
#include <list>
#include <memory>
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "remoting/client/audio/async_audio_data_supplier.h"
#include "remoting/client/audio/audio_stream_format.h"
#include "remoting/proto/audio.pb.h"
namespace remoting {
// This is a jitter buffer that queues up audio packets and get-data requests
// and feeds the requests with the data when the buffer has enough data.
class AudioJitterBuffer : public AsyncAudioDataSupplier {
public:
using OnFormatChangedCallback =
base::RepeatingCallback<void(const AudioStreamFormat& format)>;
// |callback| is called once the jitter buffer gets the first packet or the
// stream format has been changed.
// Pending get-data requests will be dropped when the stream format is
// changed.
explicit AudioJitterBuffer(OnFormatChangedCallback on_format_changed);
AudioJitterBuffer(const AudioJitterBuffer&) = delete;
AudioJitterBuffer& operator=(const AudioJitterBuffer&) = delete;
~AudioJitterBuffer() override;
void AddAudioPacket(std::unique_ptr<AudioPacket> packet);
// AsyncAudioDataSupplier implementations.
void AsyncGetData(std::unique_ptr<GetDataRequest> request) override;
void ClearGetDataRequests() override;
private:
friend class AudioJitterBufferTest;
// Clears the jitter buffer, drops all pending requests, and notify
// |on_format_changed_| that the format has been changed.
void ResetBuffer(const AudioStreamFormat& new_format);
// Feeds data from the jitter buffer into the pending requests. OnDataFilled()
// will be called and request will be removed from the queue when a request
// has been filled up.
void ProcessGetDataRequests();
// Calculates the number of bytes needed to store audio data of the given
// duration based on |stream_format_|.
size_t GetBufferSizeFromTime(base::TimeDelta duration) const;
// Drops audio packets in |queued_packets_| such that the total latency
// doesn't exceed |kMaxQueueLatency|.
void DropOverrunPackets();
// The stream format of the last audio packet. This is nullptr if the buffer
// has never received any packet.
std::unique_ptr<AudioStreamFormat> stream_format_;
// AudioPackets queued up by the jitter buffer before they are consumed by
// GetDataRequests.
std::list<std::unique_ptr<AudioPacket>> queued_packets_;
// Number of bytes that is queued in |queued_packets_|.
size_t queued_bytes_ = 0;
// The byte offset when reading data from the first packet of
// |queued_packets_|. Equal to the number of bytes consumed from the first
// packet.
size_t first_packet_offset_ = 0;
// Called when the stream format is changed.
OnFormatChangedCallback on_format_changed_;
// GetDataRequests that are not yet fulfilled.
std::list<std::unique_ptr<GetDataRequest>> queued_requests_;
// The buffer will not feed data to the requests if this is true.
bool underrun_protection_mode_ = true;
THREAD_CHECKER(thread_checker_);
};
} // namespace remoting
#endif // REMOTING_CLIENT_AUDIO_AUDIO_JITTER_BUFFER_H_

@ -1,358 +0,0 @@
// Copyright 2018 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/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include <cstdint>
#include <list>
#include <memory>
#include <string>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "remoting/client/audio/audio_jitter_buffer.h"
#include "remoting/client/audio/audio_stream_format.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace remoting {
namespace {
constexpr AudioPacket::BytesPerSample kBytesPerSample =
AudioPacket::BYTES_PER_SAMPLE_2;
constexpr AudioPacket::Channels kChannels = AudioPacket::CHANNELS_STEREO;
constexpr uint32_t kAudioSampleBytes = uint32_t{kChannels} * kBytesPerSample;
constexpr uint32_t kNumConsumerBuffers = 3;
constexpr uint32_t kConsumerBufferMaxByteSize = 5000 * kAudioSampleBytes;
constexpr uint8_t kDefaultBufferData = 0x5A;
constexpr uint8_t kDummyAudioData = 0x8B;
std::unique_ptr<AudioPacket> CreateAudioPacketWithSamplingRate(
AudioPacket::SamplingRate rate,
size_t bytes) {
std::unique_ptr<AudioPacket> packet = std::make_unique<AudioPacket>();
packet->set_encoding(AudioPacket::ENCODING_RAW);
packet->set_sampling_rate(rate);
packet->set_bytes_per_sample(kBytesPerSample);
packet->set_channels(kChannels);
std::string data;
data.resize(bytes, kDummyAudioData);
packet->add_data(data);
return packet;
}
// Check that the first |bytes_written| bytes are filled with audio data and
// the rest of the buffer is unchanged.
void CheckDataBytes(const uint8_t* buffer, size_t bytes_written) {
uint32_t i = 0;
for (; i < bytes_written; i++) {
ASSERT_EQ(kDummyAudioData, *(buffer + i));
}
// Rest of audio frame must be unchanged.
for (; i < kConsumerBufferMaxByteSize; i++) {
ASSERT_EQ(kDefaultBufferData, *(buffer + i));
}
}
} // namespace
class AudioJitterBufferTest : public ::testing::Test {
protected:
void SetUp() override;
void TearDown() override;
void SetSampleRate(AudioPacket::SamplingRate sample_rate);
std::unique_ptr<AudioPacket> CreatePacket(int time_ms);
void AsyncConsumeData(size_t duration);
void VerifyStreamFormat();
void VerifyBuffersNotLost();
size_t ByteFromTime(int time_ms) const;
size_t GetNumQueuedPackets() const;
int GetNumQueuedTime() const;
size_t GetNumQueuedRequests() const;
std::unique_ptr<AudioJitterBuffer> audio_;
std::list<std::unique_ptr<uint8_t[]>> consumer_buffers_;
private:
class SimpleGetDataRequest;
void OnFormatChanged(const AudioStreamFormat& format);
AudioPacket::SamplingRate sample_rate_;
std::unique_ptr<AudioStreamFormat> stream_format_;
};
class AudioJitterBufferTest::SimpleGetDataRequest
: public AsyncAudioDataSupplier::GetDataRequest {
public:
SimpleGetDataRequest(AudioJitterBufferTest* test, size_t bytes_to_write);
~SimpleGetDataRequest() override;
void OnDataFilled() override;
private:
raw_ptr<AudioJitterBufferTest> test_;
std::unique_ptr<uint8_t[]> buffer_;
size_t bytes_to_write_;
};
// Test fixture definitions
void AudioJitterBufferTest::SetUp() {
audio_ = std::make_unique<AudioJitterBuffer>(base::BindRepeating(
&AudioJitterBufferTest::OnFormatChanged, base::Unretained(this)));
consumer_buffers_.clear();
for (uint32_t i = 0u; i < kNumConsumerBuffers; i++) {
consumer_buffers_.push_back(
std::make_unique<uint8_t[]>(kConsumerBufferMaxByteSize));
}
SetSampleRate(AudioPacket::SAMPLING_RATE_48000);
}
void AudioJitterBufferTest::TearDown() {
VerifyBuffersNotLost();
audio_.reset();
consumer_buffers_.clear();
}
void AudioJitterBufferTest::SetSampleRate(
AudioPacket::SamplingRate sample_rate) {
sample_rate_ = sample_rate;
}
std::unique_ptr<AudioPacket> AudioJitterBufferTest::CreatePacket(int time_ms) {
return CreateAudioPacketWithSamplingRate(sample_rate_, ByteFromTime(time_ms));
}
void AudioJitterBufferTest::AsyncConsumeData(size_t duration) {
size_t bytes_to_write = ByteFromTime(duration);
ASSERT_LE(bytes_to_write, kConsumerBufferMaxByteSize);
ASSERT_FALSE(consumer_buffers_.empty());
audio_->AsyncGetData(
std::make_unique<SimpleGetDataRequest>(this, bytes_to_write));
}
void AudioJitterBufferTest::VerifyStreamFormat() {
ASSERT_TRUE(stream_format_);
ASSERT_EQ(kBytesPerSample, stream_format_->bytes_per_sample);
ASSERT_EQ(kChannels, stream_format_->channels);
ASSERT_EQ(sample_rate_, stream_format_->sample_rate);
}
void AudioJitterBufferTest::VerifyBuffersNotLost() {
size_t queued_requests = GetNumQueuedRequests();
ASSERT_EQ(kNumConsumerBuffers, queued_requests + consumer_buffers_.size());
}
size_t AudioJitterBufferTest::ByteFromTime(int time_ms) const {
return time_ms * sample_rate_ * kAudioSampleBytes /
base::Time::kMillisecondsPerSecond;
}
size_t AudioJitterBufferTest::GetNumQueuedPackets() const {
return audio_->queued_packets_.size();
}
int AudioJitterBufferTest::GetNumQueuedTime() const {
return audio_->queued_bytes_ * base::Time::kMillisecondsPerSecond /
kAudioSampleBytes / sample_rate_;
}
size_t AudioJitterBufferTest::GetNumQueuedRequests() const {
return audio_->queued_requests_.size();
}
void AudioJitterBufferTest::OnFormatChanged(const AudioStreamFormat& format) {
stream_format_ = std::make_unique<AudioStreamFormat>(format);
}
// SimpleGetDataRequest definitions
AudioJitterBufferTest::SimpleGetDataRequest::SimpleGetDataRequest(
AudioJitterBufferTest* test,
size_t bytes_to_write)
: GetDataRequest(test->consumer_buffers_.front().get(), bytes_to_write),
test_(test),
buffer_(std::move(test->consumer_buffers_.front())),
bytes_to_write_(bytes_to_write) {
test_->consumer_buffers_.pop_front();
memset(buffer_.get(), kDefaultBufferData, kConsumerBufferMaxByteSize);
}
AudioJitterBufferTest::SimpleGetDataRequest::~SimpleGetDataRequest() {
if (buffer_) {
test_->consumer_buffers_.push_back(std::move(buffer_));
}
}
void AudioJitterBufferTest::SimpleGetDataRequest::OnDataFilled() {
CheckDataBytes(buffer_.get(), bytes_to_write_);
test_->consumer_buffers_.push_back(std::move(buffer_));
}
// Test cases
TEST_F(AudioJitterBufferTest, Init) {
ASSERT_EQ(0u, GetNumQueuedPackets());
audio_->AddAudioPacket(CreatePacket(20));
ASSERT_EQ(1u, GetNumQueuedPackets());
VerifyStreamFormat();
}
TEST_F(AudioJitterBufferTest, MultipleSamples) {
audio_->AddAudioPacket(CreatePacket(10));
ASSERT_EQ(10, GetNumQueuedTime());
ASSERT_EQ(1u, GetNumQueuedPackets());
audio_->AddAudioPacket(CreatePacket(20));
ASSERT_EQ(30, GetNumQueuedTime());
ASSERT_EQ(2u, GetNumQueuedPackets());
}
TEST_F(AudioJitterBufferTest, ExceedLatency) {
// Push about 4 seconds worth of samples.
for (uint32_t i = 0; i < 100; ++i) {
audio_->AddAudioPacket(CreatePacket(40));
}
// Verify that we don't have more than 0.5s.
ASSERT_LT(GetNumQueuedTime(), 500);
}
TEST_F(AudioJitterBufferTest, SingleAsyncRequest_UnderrunProtection) {
// Add samples that are enough to fulfill one request but still doesn't get
// passed the underrun protection.
audio_->AddAudioPacket(CreatePacket(10));
// Create an Audio Request.
AsyncConsumeData(10);
// The request is not fulfilled.
ASSERT_EQ(1u, GetNumQueuedPackets());
ASSERT_EQ(1u, GetNumQueuedRequests());
}
TEST_F(AudioJitterBufferTest, SingleAsyncRequest_Fulfilled) {
// Add samples that are enough to bypass underrun protection.
audio_->AddAudioPacket(CreatePacket(80));
// Create an Audio Request.
AsyncConsumeData(10);
// Request is fulfilled and buffer is returned.
ASSERT_EQ(1u, GetNumQueuedPackets());
ASSERT_EQ(0u, GetNumQueuedRequests());
}
TEST_F(AudioJitterBufferTest, TwoAsyncRequest_FulfillOneByOne) {
// Add just enough samples to fulfill one request.
audio_->AddAudioPacket(CreatePacket(80));
ASSERT_EQ(1u, GetNumQueuedPackets());
AsyncConsumeData(80);
// Request is immediately fulfilled.
ASSERT_EQ(0u, GetNumQueuedPackets());
ASSERT_EQ(0u, GetNumQueuedRequests());
VerifyBuffersNotLost();
// Add another request.
AsyncConsumeData(80);
ASSERT_EQ(0u, GetNumQueuedPackets());
ASSERT_EQ(1u, GetNumQueuedRequests());
VerifyBuffersNotLost();
// Add packet fulfill the request.
audio_->AddAudioPacket(CreatePacket(80));
ASSERT_EQ(0u, GetNumQueuedPackets());
ASSERT_EQ(0u, GetNumQueuedRequests());
}
TEST_F(AudioJitterBufferTest, TwoAsyncRequest_OnePacketFulfillsTwoRequests) {
// Add packet big enough to fulfill two requests.
audio_->AddAudioPacket(CreatePacket(100));
ASSERT_EQ(1u, GetNumQueuedPackets());
AsyncConsumeData(50);
// Request is immediately fulfilled.
ASSERT_EQ(1u, GetNumQueuedPackets());
ASSERT_EQ(0u, GetNumQueuedRequests());
VerifyBuffersNotLost();
// Add another request.
AsyncConsumeData(50);
ASSERT_EQ(0u, GetNumQueuedPackets());
ASSERT_EQ(0u, GetNumQueuedRequests());
}
TEST_F(AudioJitterBufferTest, TwoAsyncRequest_UnderrunProtectionKicksIn) {
audio_->AddAudioPacket(CreatePacket(80));
ASSERT_EQ(1u, GetNumQueuedPackets());
// Consumes all packets while still waiting for 20ms of more data.
AsyncConsumeData(100);
ASSERT_EQ(0u, GetNumQueuedPackets());
ASSERT_EQ(1u, GetNumQueuedRequests());
VerifyBuffersNotLost();
// The package does not get pass underrun protection.
audio_->AddAudioPacket(CreatePacket(20));
ASSERT_EQ(1u, GetNumQueuedPackets());
ASSERT_EQ(1u, GetNumQueuedRequests());
// Add a bigger packet, which bypasses underrun protection.
audio_->AddAudioPacket(CreatePacket(100));
ASSERT_EQ(1u, GetNumQueuedPackets());
ASSERT_EQ(0u, GetNumQueuedRequests());
}
TEST_F(AudioJitterBufferTest, TwoAsyncRequest_TwoPacketsFulfillTwoRequests) {
// Add sample that doesn't fulfill the first request.
audio_->AddAudioPacket(CreatePacket(70));
// Create two requests.
AsyncConsumeData(80);
AsyncConsumeData(80);
// The first packet has been used to fill the first request.
ASSERT_EQ(0u, GetNumQueuedPackets());
ASSERT_EQ(2u, GetNumQueuedRequests());
VerifyBuffersNotLost();
// Add the rest to fulfill both requests.
audio_->AddAudioPacket(CreatePacket(90));
ASSERT_EQ(0u, GetNumQueuedPackets());
ASSERT_EQ(0u, GetNumQueuedRequests());
}
TEST_F(AudioJitterBufferTest, ChangeSampleRate) {
ASSERT_EQ(0u, GetNumQueuedPackets());
audio_->AddAudioPacket(CreatePacket(20));
AsyncConsumeData(80);
ASSERT_EQ(1u, GetNumQueuedPackets());
ASSERT_EQ(1u, GetNumQueuedRequests());
VerifyBuffersNotLost();
VerifyStreamFormat();
SetSampleRate(AudioPacket::SAMPLING_RATE_44100);
audio_->AddAudioPacket(CreatePacket(20));
// Previous packet has been removed.
ASSERT_EQ(1u, GetNumQueuedPackets());
// Previous pending requests are cleared and callbacks has been run.
ASSERT_EQ(0u, GetNumQueuedRequests());
VerifyStreamFormat();
}
} // namespace remoting

@ -1,34 +0,0 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_AUDIO_AUDIO_PLAYBACK_SINK_H_
#define REMOTING_CLIENT_AUDIO_AUDIO_PLAYBACK_SINK_H_
namespace remoting {
class AsyncAudioDataSupplier;
struct AudioStreamFormat;
// This is an interface acting as the downstream of AsyncAudioDataSupplier.
class AudioPlaybackSink {
public:
AudioPlaybackSink() = default;
AudioPlaybackSink(const AudioPlaybackSink&) = delete;
AudioPlaybackSink& operator=(const AudioPlaybackSink&) = delete;
virtual ~AudioPlaybackSink() = default;
// Sets the data supplier to be used by the sink to request for more audio
// data.
// |supplier| must outlive |this|.
virtual void SetDataSupplier(AsyncAudioDataSupplier* supplier) = 0;
// Called whenever the stream format is first received or has been changed.
virtual void ResetStreamFormat(const AudioStreamFormat& format) = 0;
};
} // namespace remoting
#endif // REMOTING_CLIENT_AUDIO_AUDIO_PLAYBACK_SINK_H_

@ -1,81 +0,0 @@
// Copyright 2018 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/client/audio/audio_playback_stream.h"
#include <utility>
#include "base/functional/bind.h"
#include "base/task/single_thread_task_runner.h"
#include "remoting/client/audio/audio_jitter_buffer.h"
#include "remoting/client/audio/audio_playback_sink.h"
namespace remoting {
class AudioPlaybackStream::Core {
public:
explicit Core(std::unique_ptr<AudioPlaybackSink> audio_sink);
Core(const Core&) = delete;
Core& operator=(const Core&) = delete;
~Core();
void AddAudioPacket(std::unique_ptr<AudioPacket> packet);
private:
void ResetStreamFormat(const AudioStreamFormat& format);
// |jitter_buffer_| must outlive |audio_sink_|.
std::unique_ptr<AudioJitterBuffer> jitter_buffer_;
std::unique_ptr<AudioPlaybackSink> audio_sink_;
};
AudioPlaybackStream::Core::Core(std::unique_ptr<AudioPlaybackSink> audio_sink) {
jitter_buffer_ = std::make_unique<AudioJitterBuffer>(base::BindRepeating(
&AudioPlaybackStream::Core::ResetStreamFormat, base::Unretained(this)));
audio_sink_ = std::move(audio_sink);
audio_sink_->SetDataSupplier(jitter_buffer_.get());
}
AudioPlaybackStream::Core::~Core() = default;
void AudioPlaybackStream::Core::AddAudioPacket(
std::unique_ptr<AudioPacket> packet) {
jitter_buffer_->AddAudioPacket(std::move(packet));
}
void AudioPlaybackStream::Core::ResetStreamFormat(
const AudioStreamFormat& format) {
audio_sink_->ResetStreamFormat(format);
}
// AudioPlaybackStream implementations.
AudioPlaybackStream::AudioPlaybackStream(
std::unique_ptr<AudioPlaybackSink> audio_sink,
scoped_refptr<base::SingleThreadTaskRunner> audio_task_runner)
: audio_task_runner_(audio_task_runner) {
DETACH_FROM_THREAD(thread_checker_);
core_ = std::make_unique<Core>(std::move(audio_sink));
}
AudioPlaybackStream::~AudioPlaybackStream() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
audio_task_runner_->DeleteSoon(FROM_HERE, core_.release());
}
void AudioPlaybackStream::ProcessAudioPacket(
std::unique_ptr<AudioPacket> packet,
base::OnceClosure done) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
audio_task_runner_->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&Core::AddAudioPacket, base::Unretained(core_.get()),
std::move(packet)),
std::move(done));
}
} // namespace remoting

@ -1,54 +0,0 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_AUDIO_AUDIO_PLAYBACK_STREAM_H_
#define REMOTING_CLIENT_AUDIO_AUDIO_PLAYBACK_STREAM_H_
#include <memory>
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_checker.h"
#include "remoting/protocol/audio_stub.h"
namespace remoting {
class AudioPlaybackSink;
// An AudioStub implementation that buffers AudioPackets and feeds them to
// an AudioPlaybackSink.
// AudioPlaybackStream must be used and destroyed on the same thread after it
// is created, while it will use and destroy |audio_sink| on the thread of
// |audio_task_runner|.
class AudioPlaybackStream : public protocol::AudioStub {
public:
// |audio_sink|: The AudioPlaybackSink that receives audio data.
// |audio_task_runner|: The task runner where |audio_sink| will be run.
AudioPlaybackStream(
std::unique_ptr<AudioPlaybackSink> audio_sink,
scoped_refptr<base::SingleThreadTaskRunner> audio_task_runner);
AudioPlaybackStream(const AudioPlaybackStream&) = delete;
AudioPlaybackStream& operator=(const AudioPlaybackStream&) = delete;
~AudioPlaybackStream() override;
// AudioStub implementations.
void ProcessAudioPacket(std::unique_ptr<AudioPacket> packet,
base::OnceClosure done) override;
private:
class Core;
THREAD_CHECKER(thread_checker_);
std::unique_ptr<Core> core_;
scoped_refptr<base::SingleThreadTaskRunner> audio_task_runner_;
};
} // namespace remoting
#endif // REMOTING_CLIENT_AUDIO_AUDIO_PLAYBACK_STREAM_H_

@ -1,137 +0,0 @@
// Copyright 2012 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/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "remoting/client/audio/audio_player.h"
#include <algorithm>
#include <string>
#include <utility>
#include "base/check_op.h"
#include "base/functional/callback_helpers.h"
#include "base/time/time.h"
// If queue grows bigger than 150ms we start dropping packets.
const int kMaxQueueLatencyMs = 150;
namespace remoting {
// TODO(nicholss): Update legacy audio player to use new audio buffer code.
AudioPlayer::AudioPlayer()
: sampling_rate_(AudioPacket::SAMPLING_RATE_INVALID),
start_failed_(false),
queued_bytes_(0),
bytes_consumed_(0) {}
AudioPlayer::~AudioPlayer() = default;
void AudioPlayer::ProcessAudioPacket(std::unique_ptr<AudioPacket> packet,
base::OnceClosure done) {
CHECK_EQ(1, packet->data_size());
DCHECK_EQ(AudioPacket::ENCODING_RAW, packet->encoding());
DCHECK_NE(AudioPacket::SAMPLING_RATE_INVALID, packet->sampling_rate());
DCHECK_EQ(kSampleSizeBytes, static_cast<int>(packet->bytes_per_sample()));
DCHECK_EQ(kChannels, static_cast<int>(packet->channels()));
DCHECK_EQ(packet->data(0).size() % (kChannels * kSampleSizeBytes), 0u);
base::ScopedClosureRunner done_runner(std::move(done));
// No-op if the Pepper player won't start.
if (start_failed_) {
return;
}
// Start the Pepper audio player if this is the first packet.
if (sampling_rate_ != packet->sampling_rate()) {
// Drop all packets currently in the queue, since they are sampled at the
// wrong rate.
{
base::AutoLock auto_lock(lock_);
ResetQueue();
}
sampling_rate_ = packet->sampling_rate();
bool success = ResetAudioPlayer(sampling_rate_);
if (!success) {
start_failed_ = true;
return;
}
}
base::AutoLock auto_lock(lock_);
queued_bytes_ += packet->data(0).size();
queued_packets_.push_back(std::move(packet));
int max_buffer_size_ = kMaxQueueLatencyMs * sampling_rate_ *
kSampleSizeBytes * kChannels /
base::Time::kMillisecondsPerSecond;
while (queued_bytes_ > max_buffer_size_) {
queued_bytes_ -= queued_packets_.front()->data(0).size() - bytes_consumed_;
DCHECK_GE(queued_bytes_, 0);
queued_packets_.pop_front();
bytes_consumed_ = 0;
}
}
// static
void AudioPlayer::AudioPlayerCallback(void* samples,
uint32_t buffer_size,
void* data) {
AudioPlayer* audio_player = static_cast<AudioPlayer*>(data);
audio_player->FillWithSamples(samples, buffer_size);
}
void AudioPlayer::ResetQueue() {
lock_.AssertAcquired();
queued_packets_.clear();
queued_bytes_ = 0;
bytes_consumed_ = 0;
}
void AudioPlayer::FillWithSamples(void* samples, uint32_t buffer_size) {
base::AutoLock auto_lock(lock_);
const size_t bytes_needed =
kChannels * kSampleSizeBytes * GetSamplesPerFrame();
// Make sure we don't overrun the buffer.
CHECK_EQ(buffer_size, bytes_needed);
char* next_sample = static_cast<char*>(samples);
size_t bytes_extracted = 0;
while (bytes_extracted < bytes_needed) {
// Check if we've run out of samples for this packet.
if (queued_packets_.empty()) {
memset(next_sample, 0, bytes_needed - bytes_extracted);
return;
}
// Pop off the packet if we've already consumed all its bytes.
if (queued_packets_.front()->data(0).size() == bytes_consumed_) {
queued_packets_.pop_front();
bytes_consumed_ = 0;
continue;
}
const std::string& packet_data = queued_packets_.front()->data(0);
size_t bytes_to_copy = std::min(packet_data.size() - bytes_consumed_,
bytes_needed - bytes_extracted);
memcpy(next_sample, packet_data.data() + bytes_consumed_, bytes_to_copy);
next_sample += bytes_to_copy;
bytes_consumed_ += bytes_to_copy;
bytes_extracted += bytes_to_copy;
queued_bytes_ -= bytes_to_copy;
DCHECK_GE(queued_bytes_, 0);
}
}
} // namespace remoting

@ -1,79 +0,0 @@
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_AUDIO_AUDIO_PLAYER_H_
#define REMOTING_CLIENT_AUDIO_AUDIO_PLAYER_H_
#include <cstdint>
#include <list>
#include <memory>
#include "base/synchronization/lock.h"
#include "remoting/proto/audio.pb.h"
#include "remoting/protocol/audio_stub.h"
namespace remoting {
// TODO(nicholss): Update legacy audio player to use new audio buffer code.
class AudioPlayer : public protocol::AudioStub {
public:
// The number of channels in the audio stream (only supporting stereo audio
// for now).
static const int kChannels = 2;
static const int kSampleSizeBytes = 2;
AudioPlayer(const AudioPlayer&) = delete;
AudioPlayer& operator=(const AudioPlayer&) = delete;
~AudioPlayer() override;
// protocol::AudioStub implementation.
void ProcessAudioPacket(std::unique_ptr<AudioPacket> packet,
base::OnceClosure done) override;
protected:
AudioPlayer();
// Return the recommended number of samples to include in a frame.
virtual uint32_t GetSamplesPerFrame() = 0;
// Resets the audio player and starts playback.
// Returns true on success.
virtual bool ResetAudioPlayer(AudioPacket::SamplingRate sampling_rate) = 0;
// Function called by the browser when it needs more audio samples.
static void AudioPlayerCallback(void* samples,
uint32_t buffer_size,
void* data);
// Function called by the subclass when it needs more audio samples to fill
// its buffer. Will fill the buffer with 0's if no sample is available.
void FillWithSamples(void* samples, uint32_t buffer_size);
private:
friend class AudioPlayerTest;
typedef std::list<std::unique_ptr<AudioPacket>> AudioPacketQueue;
void ResetQueue();
AudioPacket::SamplingRate sampling_rate_;
bool start_failed_;
// Protects |queued_packets_|, |queued_samples_ and |bytes_consumed_|. This is
// necessary to prevent races, because Pepper will call the callback on a
// separate thread.
base::Lock lock_;
AudioPacketQueue queued_packets_;
int queued_bytes_;
// The number of bytes from |queued_packets_| that have been consumed.
size_t bytes_consumed_;
};
} // namespace remoting
#endif // REMOTING_CLIENT_AUDIO_AUDIO_PLAYER_H_

@ -1,161 +0,0 @@
// Copyright 2016 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/client/audio/audio_player_android.h"
#include "base/logging.h"
#include "base/time/time.h"
#include "build/build_config.h"
namespace remoting {
const int kFrameSizeMs = 40;
const int kNumOfBuffers = 1;
static_assert(AudioPlayer::kChannels == 2,
"AudioPlayer must be feeding 2 channels data.");
const int kChannelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
// TODO(nicholss): Update legacy audio player to use new audio buffer code.
AudioPlayerAndroid::AudioPlayerAndroid() {
if (slCreateEngine(&engine_object_, 0, nullptr, 0, nullptr, nullptr) !=
SL_RESULT_SUCCESS ||
(*engine_object_)->Realize(engine_object_, SL_BOOLEAN_FALSE) !=
SL_RESULT_SUCCESS ||
(*engine_object_)
->GetInterface(engine_object_, SL_IID_ENGINE, &engine_) !=
SL_RESULT_SUCCESS ||
(*engine_)->CreateOutputMix(engine_, &output_mix_object_, 0, nullptr,
nullptr) != SL_RESULT_SUCCESS ||
(*output_mix_object_)->Realize(output_mix_object_, SL_BOOLEAN_FALSE) !=
SL_RESULT_SUCCESS) {
LOG(ERROR) << "Failed to initialize OpenSL ES.";
}
}
AudioPlayerAndroid::~AudioPlayerAndroid() {
DestroyPlayer();
if (output_mix_object_) {
(*output_mix_object_)->Destroy(output_mix_object_);
}
if (engine_object_) {
(*engine_object_)->Destroy(engine_object_);
}
}
base::WeakPtr<AudioPlayerAndroid> AudioPlayerAndroid::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
uint32_t AudioPlayerAndroid::GetSamplesPerFrame() {
return sample_per_frame_;
}
bool AudioPlayerAndroid::ResetAudioPlayer(
AudioPacket::SamplingRate sampling_rate) {
if (!output_mix_object_) {
// output mixer not successfully created in ctor.
return false;
}
DestroyPlayer();
sample_per_frame_ =
kFrameSizeMs * sampling_rate / base::Time::kMillisecondsPerSecond;
buffer_size_ = kChannels * kSampleSizeBytes * sample_per_frame_;
frame_buffer_.reset(new uint8_t[buffer_size_]);
FillWithSamples(frame_buffer_.get(), buffer_size_);
SLDataLocator_AndroidSimpleBufferQueue locator_bufqueue;
locator_bufqueue.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
locator_bufqueue.numBuffers = kNumOfBuffers;
SLDataFormat_PCM format = CreatePcmFormat(sampling_rate);
SLDataSource source = {&locator_bufqueue, &format};
SLDataLocator_OutputMix locator_out;
locator_out.locatorType = SL_DATALOCATOR_OUTPUTMIX;
locator_out.outputMix = output_mix_object_;
SLDataSink sink;
sink.pLocator = &locator_out;
sink.pFormat = nullptr;
const SLInterfaceID ids[] = {SL_IID_BUFFERQUEUE};
const SLboolean reqs[] = {SL_BOOLEAN_TRUE};
if ((*engine_)->CreateAudioPlayer(engine_, &player_object_, &source, &sink,
std::size(ids), ids,
reqs) != SL_RESULT_SUCCESS ||
(*player_object_)->Realize(player_object_, SL_BOOLEAN_FALSE) !=
SL_RESULT_SUCCESS ||
(*player_object_)->GetInterface(player_object_, SL_IID_PLAY, &player_) !=
SL_RESULT_SUCCESS ||
(*player_object_)
->GetInterface(player_object_, SL_IID_BUFFERQUEUE,
&buffer_queue_) != SL_RESULT_SUCCESS ||
(*buffer_queue_)
->RegisterCallback(buffer_queue_,
&AudioPlayerAndroid::BufferQueueCallback,
this) != SL_RESULT_SUCCESS ||
(*player_)->SetPlayState(player_, SL_PLAYSTATE_PLAYING) !=
SL_RESULT_SUCCESS ||
// The player will only ask for more data after it consumes all its
// buffers. Having an empty queue will not trigger it to ask for more
// data.
(*buffer_queue_)
->Enqueue(buffer_queue_, frame_buffer_.get(), buffer_size_) !=
SL_RESULT_SUCCESS) {
LOG(ERROR) << "Failed to initialize the player.";
return false;
}
return true;
}
// static
void AudioPlayerAndroid::BufferQueueCallback(
SLAndroidSimpleBufferQueueItf caller,
void* args) {
AudioPlayerAndroid* player = static_cast<AudioPlayerAndroid*>(args);
player->FillWithSamples(player->frame_buffer_.get(), player->buffer_size_);
if ((*caller)->Enqueue(caller, player->frame_buffer_.get(),
player->buffer_size_) != SL_RESULT_SUCCESS) {
LOG(ERROR) << "Failed to enqueue the frame.";
}
}
// static
SLDataFormat_PCM AudioPlayerAndroid::CreatePcmFormat(int sampling_rate) {
SLDataFormat_PCM format;
format.formatType = SL_DATAFORMAT_PCM;
format.numChannels = kChannels;
switch (sampling_rate) {
case AudioPacket::SAMPLING_RATE_44100:
format.samplesPerSec = SL_SAMPLINGRATE_44_1;
break;
case AudioPacket::SAMPLING_RATE_48000:
format.samplesPerSec = SL_SAMPLINGRATE_48;
break;
default:
LOG(FATAL) << "Unsupported audio sampling rate: " << sampling_rate;
} // samplesPerSec is in mHz. OpenSL doesn't name this field well.
format.bitsPerSample = kSampleSizeBytes * 8;
format.containerSize = kSampleSizeBytes * 8;
#if defined(ARCH_CPU_LITTLE_ENDIAN)
format.endianness = SL_BYTEORDER_LITTLEENDIAN;
#else
format.endianness = SL_BYTEORDER_BIGENDIAN;
#endif
format.channelMask = kChannelMask;
return format;
}
void AudioPlayerAndroid::DestroyPlayer() {
if (player_object_) {
(*player_object_)->Destroy(player_object_);
player_object_ = nullptr;
}
frame_buffer_.reset();
buffer_size_ = 0;
player_ = nullptr;
buffer_queue_ = nullptr;
}
} // namespace remoting

@ -1,58 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_AUDIO_AUDIO_PLAYER_ANDROID_H_
#define REMOTING_CLIENT_AUDIO_AUDIO_PLAYER_ANDROID_H_
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include "base/memory/weak_ptr.h"
#include "remoting/client/audio/audio_player.h"
namespace remoting {
// TODO(nicholss): Update legacy audio player to use new audio buffer code.
class AudioPlayerAndroid : public AudioPlayer {
public:
AudioPlayerAndroid();
AudioPlayerAndroid(const AudioPlayerAndroid&) = delete;
AudioPlayerAndroid& operator=(const AudioPlayerAndroid&) = delete;
~AudioPlayerAndroid() override;
base::WeakPtr<AudioPlayerAndroid> GetWeakPtr();
// AudioPlayer overrides.
uint32_t GetSamplesPerFrame() override;
bool ResetAudioPlayer(AudioPacket::SamplingRate sampling_rate) override;
private:
// Called when new data is needed for the buffer queue.
static void BufferQueueCallback(SLAndroidSimpleBufferQueueItf caller,
void* args);
static SLDataFormat_PCM CreatePcmFormat(int sampling_rate);
// Destroys the player and releases the buffer. Do nothing if the player is
// nullptr.
void DestroyPlayer();
SLObjectItf engine_object_ = nullptr;
SLEngineItf engine_ = nullptr;
SLObjectItf output_mix_object_ = nullptr;
SLObjectItf player_object_ = nullptr;
SLPlayItf player_ = nullptr;
SLAndroidSimpleBufferQueueItf buffer_queue_ = nullptr;
std::unique_ptr<uint8_t[]> frame_buffer_;
size_t buffer_size_ = 0;
uint32_t sample_per_frame_ = 0;
base::WeakPtrFactory<AudioPlayerAndroid> weak_factory_{this};
};
} // namespace remoting
#endif // REMOTING_CLIENT_AUDIO_AUDIO_PLAYER_ANDROID_H_

@ -1,317 +0,0 @@
// Copyright 2012 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/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "remoting/client/audio/audio_player.h"
#include <cstdint>
#include <memory>
#include "base/compiler_specific.h"
#include "base/functional/callback.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const int kAudioSamplesPerFrame = 25;
const int kAudioSampleBytes = 4;
const int kAudioFrameBytes = kAudioSamplesPerFrame * kAudioSampleBytes;
const int kPaddingBytes = 16;
// TODO(nicholss): Update legacy audio player to use new audio buffer code.
// TODO(garykac): Generate random audio data in the tests rather than having
// a single constant value.
const uint8_t kDefaultBufferData = 0x5A;
const uint8_t kDummyAudioData = 0x8B;
} // namespace
namespace remoting {
class FakeAudioPlayer : public AudioPlayer {
public:
FakeAudioPlayer() = default;
bool ResetAudioPlayer(AudioPacket::SamplingRate) override { return true; }
uint32_t GetSamplesPerFrame() override { return kAudioSamplesPerFrame; }
};
class AudioPlayerTest : public ::testing::Test {
protected:
void SetUp() override {
audio_ = std::make_unique<FakeAudioPlayer>();
buffer_.reset(new char[kAudioFrameBytes + kPaddingBytes]);
}
void TearDown() override {}
void ConsumeAudioFrame() {
uint8_t* buffer = reinterpret_cast<uint8_t*>(buffer_.get());
memset(buffer, kDefaultBufferData, kAudioFrameBytes + kPaddingBytes);
AudioPlayer::AudioPlayerCallback(reinterpret_cast<void*>(buffer_.get()),
kAudioFrameBytes,
reinterpret_cast<void*>(audio_.get()));
// Verify we haven't written beyond the end of the buffer.
for (int i = 0; i < kPaddingBytes; i++)
ASSERT_EQ(kDefaultBufferData, *(buffer + kAudioFrameBytes + i));
}
// Check that the first |num_bytes| bytes are filled with audio data and
// the rest of the buffer is zero-filled.
void CheckAudioFrameBytes(int num_bytes) {
uint8_t* buffer = reinterpret_cast<uint8_t*>(buffer_.get());
int i = 0;
for (; i < num_bytes; i++) {
ASSERT_EQ(kDummyAudioData, *(buffer + i));
}
// Rest of audio frame must be filled with '0's.
for (; i < kAudioFrameBytes; i++) {
ASSERT_EQ(0, *(buffer + i));
}
}
int GetNumQueuedSamples() {
return audio_->queued_bytes_ / kAudioSampleBytes;
}
int GetNumQueuedPackets() {
return static_cast<int>(audio_->queued_packets_.size());
}
int GetBytesConsumed() { return static_cast<int>(audio_->bytes_consumed_); }
std::unique_ptr<AudioPlayer> audio_;
std::unique_ptr<char[]> buffer_;
};
std::unique_ptr<AudioPacket> CreatePacketWithSamplingRate(
AudioPacket::SamplingRate rate,
int samples) {
std::unique_ptr<AudioPacket> packet(new AudioPacket());
packet->set_encoding(AudioPacket::ENCODING_RAW);
packet->set_sampling_rate(rate);
packet->set_bytes_per_sample(AudioPacket::BYTES_PER_SAMPLE_2);
packet->set_channels(AudioPacket::CHANNELS_STEREO);
// The data must be a multiple of 4 bytes (channels x bytes_per_sample).
std::string data;
data.resize(samples * kAudioSampleBytes, kDummyAudioData);
packet->add_data(data);
return packet;
}
std::unique_ptr<AudioPacket> CreatePacket44100Hz(int samples) {
return CreatePacketWithSamplingRate(AudioPacket::SAMPLING_RATE_44100,
samples);
}
std::unique_ptr<AudioPacket> CreatePacket48000Hz(int samples) {
return CreatePacketWithSamplingRate(AudioPacket::SAMPLING_RATE_48000,
samples);
}
TEST_F(AudioPlayerTest, Init) {
ASSERT_EQ(0, GetNumQueuedPackets());
audio_->ProcessAudioPacket(CreatePacket44100Hz(10), {});
ASSERT_EQ(1, GetNumQueuedPackets());
}
TEST_F(AudioPlayerTest, MultipleSamples) {
audio_->ProcessAudioPacket(CreatePacket44100Hz(10), {});
ASSERT_EQ(10, GetNumQueuedSamples());
ASSERT_EQ(1, GetNumQueuedPackets());
audio_->ProcessAudioPacket(CreatePacket44100Hz(20), {});
ASSERT_EQ(30, GetNumQueuedSamples());
ASSERT_EQ(2, GetNumQueuedPackets());
}
TEST_F(AudioPlayerTest, ChangeSampleRate) {
audio_->ProcessAudioPacket(CreatePacket44100Hz(10), {});
ASSERT_EQ(10, GetNumQueuedSamples());
ASSERT_EQ(1, GetNumQueuedPackets());
// New packet with different sampling rate causes previous samples to
// be removed.
audio_->ProcessAudioPacket(CreatePacket48000Hz(20), {});
ASSERT_EQ(20, GetNumQueuedSamples());
ASSERT_EQ(1, GetNumQueuedPackets());
}
TEST_F(AudioPlayerTest, ExceedLatency) {
// Push about 4 seconds worth of samples.
for (int i = 0; i < 100; ++i) {
audio_->ProcessAudioPacket(CreatePacket48000Hz(2000), {});
}
// Verify that we don't have more than 0.5s.
EXPECT_LT(GetNumQueuedSamples(), 24000);
}
// Incoming packets: 100
// Consume: 25 (w/ 75 remaining, offset 25 into packet)
TEST_F(AudioPlayerTest, ConsumePartialPacket) {
int total_samples = 0;
int bytes_consumed = 0;
// Process 100 samples.
int packet1_samples = 100;
total_samples += packet1_samples;
audio_->ProcessAudioPacket(CreatePacket44100Hz(packet1_samples), {});
ASSERT_EQ(total_samples, GetNumQueuedSamples());
ASSERT_EQ(1, GetNumQueuedPackets());
ASSERT_EQ(bytes_consumed, GetBytesConsumed());
// Consume one frame (=25) of samples.
ConsumeAudioFrame();
total_samples -= kAudioSamplesPerFrame;
bytes_consumed += kAudioFrameBytes;
ASSERT_EQ(total_samples, GetNumQueuedSamples());
ASSERT_EQ(1, GetNumQueuedPackets());
ASSERT_EQ(bytes_consumed, GetBytesConsumed());
CheckAudioFrameBytes(kAudioFrameBytes);
// Remaining samples.
ASSERT_EQ(75, total_samples);
ASSERT_EQ(25 * kAudioSampleBytes, bytes_consumed);
}
// Incoming packets: 20, 70
// Consume: 25, 25 (w/ 40 remaining, offset 30 into packet)
TEST_F(AudioPlayerTest, ConsumeAcrossPackets) {
int total_samples = 0;
int bytes_consumed = 0;
// Packet 1.
int packet1_samples = 20;
total_samples += packet1_samples;
audio_->ProcessAudioPacket(CreatePacket44100Hz(packet1_samples), {});
ASSERT_EQ(total_samples, GetNumQueuedSamples());
// Packet 2.
int packet2_samples = 70;
total_samples += packet2_samples;
audio_->ProcessAudioPacket(CreatePacket44100Hz(packet2_samples), {});
ASSERT_EQ(total_samples, GetNumQueuedSamples());
ASSERT_EQ(bytes_consumed, GetBytesConsumed());
// Consume 1st frame of 25 samples.
// This will consume the entire 1st packet.
ConsumeAudioFrame();
total_samples -= kAudioSamplesPerFrame;
bytes_consumed += kAudioFrameBytes - (packet1_samples * kAudioSampleBytes);
ASSERT_EQ(total_samples, GetNumQueuedSamples());
ASSERT_EQ(1, GetNumQueuedPackets());
ASSERT_EQ(bytes_consumed, GetBytesConsumed());
CheckAudioFrameBytes(kAudioFrameBytes);
// Consume 2nd frame of 25 samples.
ConsumeAudioFrame();
total_samples -= kAudioSamplesPerFrame;
bytes_consumed += kAudioFrameBytes;
ASSERT_EQ(total_samples, GetNumQueuedSamples());
ASSERT_EQ(1, GetNumQueuedPackets());
ASSERT_EQ(bytes_consumed, GetBytesConsumed());
CheckAudioFrameBytes(kAudioFrameBytes);
// Remaining samples.
ASSERT_EQ(40, total_samples);
ASSERT_EQ(30 * kAudioSampleBytes, bytes_consumed);
}
// Incoming packets: 50, 30
// Consume: 25, 25, 25 (w/ 5 remaining, offset 25 into packet)
TEST_F(AudioPlayerTest, ConsumeEntirePacket) {
int total_samples = 0;
int bytes_consumed = 0;
// Packet 1.
int packet1_samples = 50;
total_samples += packet1_samples;
audio_->ProcessAudioPacket(CreatePacket44100Hz(packet1_samples), {});
ASSERT_EQ(total_samples, GetNumQueuedSamples());
ASSERT_EQ(bytes_consumed, GetBytesConsumed());
// Packet 2.
int packet2_samples = 30;
total_samples += packet2_samples;
audio_->ProcessAudioPacket(CreatePacket44100Hz(packet2_samples), {});
ASSERT_EQ(total_samples, GetNumQueuedSamples());
ASSERT_EQ(bytes_consumed, GetBytesConsumed());
// Consume 1st frame of 25 samples.
ConsumeAudioFrame();
total_samples -= kAudioSamplesPerFrame;
bytes_consumed += kAudioFrameBytes;
ASSERT_EQ(total_samples, GetNumQueuedSamples());
ASSERT_EQ(2, GetNumQueuedPackets());
ASSERT_EQ(bytes_consumed, GetBytesConsumed());
CheckAudioFrameBytes(kAudioFrameBytes);
// Consume 2nd frame of 25 samples.
// This will consume the entire first packet (exactly), but the entry for
// this packet will stick around (empty) until the next audio chunk is
// consumed.
ConsumeAudioFrame();
total_samples -= kAudioSamplesPerFrame;
bytes_consumed += kAudioFrameBytes;
ASSERT_EQ(total_samples, GetNumQueuedSamples());
ASSERT_EQ(2, GetNumQueuedPackets());
ASSERT_EQ(bytes_consumed, GetBytesConsumed());
CheckAudioFrameBytes(kAudioFrameBytes);
// Consume 3rd frame of 25 samples.
ConsumeAudioFrame();
total_samples -= kAudioSamplesPerFrame;
bytes_consumed += kAudioFrameBytes - (packet1_samples * kAudioSampleBytes);
ASSERT_EQ(total_samples, GetNumQueuedSamples());
ASSERT_EQ(1, GetNumQueuedPackets());
ASSERT_EQ(bytes_consumed, GetBytesConsumed());
CheckAudioFrameBytes(kAudioFrameBytes);
// Remaining samples.
ASSERT_EQ(5, total_samples);
ASSERT_EQ(25 * kAudioSampleBytes, bytes_consumed);
}
// Incoming packets: <none>
// Consume: 25
TEST_F(AudioPlayerTest, NoDataToConsume) {
// Attempt to consume a frame of 25 samples.
ConsumeAudioFrame();
ASSERT_EQ(0, GetNumQueuedSamples());
ASSERT_EQ(0, GetNumQueuedPackets());
ASSERT_EQ(0, GetBytesConsumed());
CheckAudioFrameBytes(0);
}
// Incoming packets: 10
// Consume: 25
TEST_F(AudioPlayerTest, NotEnoughDataToConsume) {
int total_samples = 0;
int bytes_consumed = 0;
// Packet 1.
int packet1_samples = 10;
total_samples += packet1_samples;
audio_->ProcessAudioPacket(CreatePacket44100Hz(packet1_samples), {});
ASSERT_EQ(total_samples, GetNumQueuedSamples());
ASSERT_EQ(bytes_consumed, GetBytesConsumed());
// Attempt to consume a frame of 25 samples.
ConsumeAudioFrame();
ASSERT_EQ(0, GetNumQueuedSamples());
ASSERT_EQ(0, GetNumQueuedPackets());
ASSERT_EQ(0, GetBytesConsumed());
CheckAudioFrameBytes(packet1_samples * kAudioSampleBytes);
}
} // namespace remoting

@ -1,18 +0,0 @@
// Copyright 2018 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/client/audio/audio_stream_format.h"
namespace remoting {
bool AudioStreamFormat::operator==(const AudioStreamFormat& other) const {
return bytes_per_sample == other.bytes_per_sample &&
channels == other.channels && sample_rate == other.sample_rate;
}
bool AudioStreamFormat::operator!=(const AudioStreamFormat& other) const {
return !(*this == other);
}
} // namespace remoting

@ -1,21 +0,0 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_AUDIO_AUDIO_STREAM_FORMAT_H_
#define REMOTING_CLIENT_AUDIO_AUDIO_STREAM_FORMAT_H_
namespace remoting {
struct AudioStreamFormat {
bool operator==(const AudioStreamFormat& other) const;
bool operator!=(const AudioStreamFormat& other) const;
int bytes_per_sample = 0;
int channels = 0;
int sample_rate = 0;
};
} // namespace remoting
#endif // REMOTING_CLIENT_AUDIO_AUDIO_STREAM_FORMAT_H_

@ -1,50 +0,0 @@
// Copyright 2018 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/client/audio/fake_async_audio_data_supplier.h"
#include <string.h>
#include <utility>
#include "base/check_op.h"
namespace remoting {
FakeAsyncAudioDataSupplier::FakeAsyncAudioDataSupplier() = default;
FakeAsyncAudioDataSupplier::~FakeAsyncAudioDataSupplier() = default;
void FakeAsyncAudioDataSupplier::AsyncGetData(
std::unique_ptr<GetDataRequest> request) {
pending_requests_.push_back(std::move(request));
if (fulfill_requests_immediately_) {
FulfillAllRequests();
}
}
void FakeAsyncAudioDataSupplier::ClearGetDataRequests() {
pending_requests_.clear();
}
void FakeAsyncAudioDataSupplier::FulfillNextRequest() {
DCHECK_GT(pending_requests_count(), 0u);
auto& request = pending_requests_.front();
memset(request->data, kDummyAudioData, request->bytes_needed);
request->OnDataFilled();
pending_requests_.pop_front();
fulfilled_requests_count_++;
}
void FakeAsyncAudioDataSupplier::FulfillAllRequests() {
while (pending_requests_count() > 0) {
FulfillNextRequest();
}
}
void FakeAsyncAudioDataSupplier::ResetFulfilledRequestsCounter() {
fulfilled_requests_count_ = 0u;
}
} // namespace remoting

@ -1,66 +0,0 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_AUDIO_FAKE_ASYNC_AUDIO_DATA_SUPPLIER_H_
#define REMOTING_CLIENT_AUDIO_FAKE_ASYNC_AUDIO_DATA_SUPPLIER_H_
#include <cstdint>
#include <list>
#include <memory>
#include "remoting/client/audio/async_audio_data_supplier.h"
namespace remoting {
// A fake AsyncAudioDataSupplier implementation for testing.
class FakeAsyncAudioDataSupplier : public AsyncAudioDataSupplier {
public:
// Dummy audio data that will be filled into pending requests.
const uint8_t kDummyAudioData = 0x8b;
FakeAsyncAudioDataSupplier();
FakeAsyncAudioDataSupplier(const FakeAsyncAudioDataSupplier&) = delete;
FakeAsyncAudioDataSupplier& operator=(const FakeAsyncAudioDataSupplier&) =
delete;
~FakeAsyncAudioDataSupplier() override;
// AsyncAudioDataSupplier implementations.
void AsyncGetData(std::unique_ptr<GetDataRequest> request) override;
void ClearGetDataRequests() override;
// Fulfills the next pending request by filling it with |kDummyAudioData|.
void FulfillNextRequest();
// Fulfills all pending requests.
void FulfillAllRequests();
// Resets fulfilled_requests_count() to 0.
void ResetFulfilledRequestsCounter();
// Returns number of requests that are not fulfilled.
size_t pending_requests_count() const { return pending_requests_.size(); }
// Returns number of requests that have been fulfilled. Can be reset to 0 by
// calling ResetFulfilledRequestsCounter().
size_t fulfilled_requests_count() const { return fulfilled_requests_count_; }
// If this is true, the instance will immediately fulfill get-data requests
// when AsyncGetData is called, otherwise the caller needs to call
// FulfillNextRequest() or FulfillAllRequests() to fulfill the requests.
// The default value is false.
void set_fulfill_requests_immediately(bool fulfill_requests_immediately) {
fulfill_requests_immediately_ = fulfill_requests_immediately;
}
private:
bool fulfill_requests_immediately_ = false;
std::list<std::unique_ptr<GetDataRequest>> pending_requests_;
size_t fulfilled_requests_count_ = 0u;
};
} // namespace remoting
#endif // REMOTING_CLIENT_AUDIO_FAKE_ASYNC_AUDIO_DATA_SUPPLIER_H_

@ -1,305 +0,0 @@
// Copyright 2012 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/client/chromoting_client.h"
#include <memory>
#include <utility>
#include "base/logging.h"
#include "remoting/base/capabilities.h"
#include "remoting/base/constants.h"
#include "remoting/client/client_context.h"
#include "remoting/client/client_user_interface.h"
#include "remoting/protocol/authenticator.h"
#include "remoting/protocol/connection_to_host.h"
#include "remoting/protocol/host_stub.h"
#include "remoting/protocol/ice_connection_to_host.h"
#include "remoting/protocol/jingle_session_manager.h"
#include "remoting/protocol/negotiating_client_authenticator.h"
#include "remoting/protocol/session_config.h"
#include "remoting/protocol/transport_context.h"
#include "remoting/protocol/video_renderer.h"
#include "remoting/protocol/webrtc_connection_to_host.h"
#include "remoting/signaling/signaling_address.h"
#include "remoting/signaling/signaling_id_util.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
namespace remoting {
ChromotingClient::ChromotingClient(
ClientContext* client_context,
ClientUserInterface* user_interface,
protocol::VideoRenderer* video_renderer,
base::WeakPtr<protocol::AudioStub> audio_stream_consumer)
: user_interface_(user_interface), video_renderer_(video_renderer) {
DCHECK(client_context->main_task_runner()->BelongsToCurrentThread());
audio_decode_task_runner_ = client_context->audio_decode_task_runner();
audio_stream_consumer_ = audio_stream_consumer;
}
ChromotingClient::~ChromotingClient() {
DCHECK(thread_checker_.CalledOnValidThread());
if (signal_strategy_)
signal_strategy_->RemoveListener(this);
}
void ChromotingClient::set_protocol_config(
std::unique_ptr<protocol::CandidateSessionConfig> config) {
DCHECK(!connection_)
<< "set_protocol_config() cannot be called after Start().";
protocol_config_ = std::move(config);
}
void ChromotingClient::set_host_experiment_config(
const std::string& experiment_config) {
DCHECK(!connection_)
<< "set_host_experiment_config() cannot be called after Start().";
host_experiment_sender_ =
std::make_unique<HostExperimentSender>(experiment_config);
}
void ChromotingClient::SetConnectionToHostForTests(
std::unique_ptr<protocol::ConnectionToHost> connection_to_host) {
connection_ = std::move(connection_to_host);
}
void ChromotingClient::Start(
SignalStrategy* signal_strategy,
const protocol::ClientAuthenticationConfig& client_auth_config,
scoped_refptr<protocol::TransportContext> transport_context,
const std::string& host_jid,
const std::string& capabilities) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!session_manager_); // Start must not be called more than once.
host_jid_ = NormalizeSignalingId(host_jid);
local_capabilities_ = capabilities;
if (!protocol_config_) {
protocol_config_ = protocol::CandidateSessionConfig::CreateDefault();
}
if (!connection_) {
if (protocol_config_->webrtc_supported()) {
LOG(FATAL) << "WebRTC is not supported.";
} else {
DCHECK(protocol_config_->ice_supported());
connection_ = std::make_unique<protocol::IceConnectionToHost>();
}
}
connection_->set_client_stub(this);
connection_->set_clipboard_stub(this);
connection_->set_video_renderer(video_renderer_);
if (audio_stream_consumer_) {
connection_->InitializeAudio(audio_decode_task_runner_,
audio_stream_consumer_);
} else {
protocol_config_->DisableAudioChannel();
}
session_manager_ =
std::make_unique<protocol::JingleSessionManager>(signal_strategy);
session_manager_->set_protocol_config(std::move(protocol_config_));
client_auth_config_ = client_auth_config;
transport_context_ = transport_context;
signal_strategy_ = signal_strategy;
signal_strategy_->AddListener(this);
switch (signal_strategy_->GetState()) {
case SignalStrategy::CONNECTING:
// Nothing to do here. Just need to wait until |signal_strategy_| becomes
// connected.
break;
case SignalStrategy::CONNECTED:
StartConnection();
break;
case SignalStrategy::DISCONNECTED:
signal_strategy_->Connect();
break;
}
}
void ChromotingClient::Close() {
DCHECK(thread_checker_.CalledOnValidThread());
connection_->Disconnect(ErrorCode::OK);
}
void ChromotingClient::SetCapabilities(
const protocol::Capabilities& capabilities) {
DCHECK(thread_checker_.CalledOnValidThread());
// Only accept the first |protocol::Capabilities| message.
if (host_capabilities_received_) {
LOG(WARNING) << "protocol::Capabilities has been received already.";
return;
}
host_capabilities_received_ = true;
if (capabilities.has_capabilities())
host_capabilities_ = capabilities.capabilities();
VLOG(1) << "Host capabilities: " << host_capabilities_;
// Calculate the set of capabilities enabled by both client and host and pass
// it to the webapp.
user_interface_->SetCapabilities(
IntersectCapabilities(local_capabilities_, host_capabilities_));
}
void ChromotingClient::SetPairingResponse(
const protocol::PairingResponse& pairing_response) {
DCHECK(thread_checker_.CalledOnValidThread());
user_interface_->SetPairingResponse(pairing_response);
}
void ChromotingClient::DeliverHostMessage(
const protocol::ExtensionMessage& message) {
DCHECK(thread_checker_.CalledOnValidThread());
user_interface_->DeliverHostMessage(message);
}
void ChromotingClient::SetVideoLayout(const protocol::VideoLayout& layout) {
int num_video_tracks = layout.video_track_size();
if (num_video_tracks < 1) {
LOG(ERROR) << "Received VideoLayout message with 0 tracks.";
return;
}
if (num_video_tracks > 2) {
LOG(WARNING) << "Received VideoLayout message with " << num_video_tracks
<< " tracks. Only one track is supported.";
}
const protocol::VideoTrackLayout& track_layout = layout.video_track(0);
int x_dpi = track_layout.has_x_dpi() ? track_layout.x_dpi() : kDefaultDpi;
int y_dpi = track_layout.has_y_dpi() ? track_layout.y_dpi() : kDefaultDpi;
if (x_dpi != y_dpi) {
LOG(WARNING) << "Mismatched x,y dpi. x=" << x_dpi << " y=" << y_dpi;
}
webrtc::DesktopSize size_dips(track_layout.width(), track_layout.height());
webrtc::DesktopSize size_px(size_dips.width() * x_dpi / kDefaultDpi,
size_dips.height() * y_dpi / kDefaultDpi);
user_interface_->SetDesktopSize(size_px, webrtc::DesktopVector(x_dpi, y_dpi));
mouse_input_scaler_.set_input_size(size_px.width(), size_px.height());
if (connection_->config().protocol() ==
protocol::SessionConfig::Protocol::ICE) {
mouse_input_scaler_.set_output_size(size_px.width(), size_px.height());
} else {
mouse_input_scaler_.set_output_size(size_dips.width(), size_dips.height());
}
}
void ChromotingClient::SetTransportInfo(
const protocol::TransportInfo& transport_info) {}
void ChromotingClient::SetActiveDisplay(
const protocol::ActiveDisplay& active_display) {}
void ChromotingClient::InjectClipboardEvent(
const protocol::ClipboardEvent& event) {
DCHECK(thread_checker_.CalledOnValidThread());
user_interface_->GetClipboardStub()->InjectClipboardEvent(event);
}
void ChromotingClient::SetCursorShape(
const protocol::CursorShapeInfo& cursor_shape) {
DCHECK(thread_checker_.CalledOnValidThread());
user_interface_->GetCursorShapeStub()->SetCursorShape(cursor_shape);
}
void ChromotingClient::SetKeyboardLayout(
const protocol::KeyboardLayout& layout) {
DCHECK(thread_checker_.CalledOnValidThread());
user_interface_->GetKeyboardLayoutStub()->SetKeyboardLayout(layout);
}
void ChromotingClient::OnConnectionState(
protocol::ConnectionToHost::State state,
protocol::ErrorCode error) {
DCHECK(thread_checker_.CalledOnValidThread());
VLOG(1) << "ChromotingClient::OnConnectionState(" << state << ")";
if (state == protocol::ConnectionToHost::CONNECTED) {
OnChannelsConnected();
}
user_interface_->OnConnectionState(state, error);
}
void ChromotingClient::OnConnectionReady(bool ready) {
VLOG(1) << "ChromotingClient::OnConnectionReady(" << ready << ")";
user_interface_->OnConnectionReady(ready);
}
void ChromotingClient::OnRouteChanged(const std::string& channel_name,
const protocol::TransportRoute& route) {
VLOG(0) << "Using " << protocol::TransportRoute::GetTypeString(route.type)
<< " connection for " << channel_name << " channel";
user_interface_->OnRouteChanged(channel_name, route);
}
void ChromotingClient::OnSignalStrategyStateChange(
SignalStrategy::State state) {
DCHECK(thread_checker_.CalledOnValidThread());
if (state == SignalStrategy::CONNECTED) {
VLOG(1) << "Connected as: " << signal_strategy_->GetLocalAddress().id();
// After signaling has been connected we can try connecting to the host.
if (connection_ &&
connection_->state() == protocol::ConnectionToHost::INITIALIZING) {
StartConnection();
}
} else if (state == SignalStrategy::DISCONNECTED) {
VLOG(1) << "Signaling connection closed.";
mouse_input_scaler_.set_input_stub(nullptr);
connection_.reset();
user_interface_->OnConnectionState(protocol::ConnectionToHost::FAILED,
ErrorCode::SIGNALING_ERROR);
}
}
bool ChromotingClient::OnSignalStrategyIncomingStanza(
const jingle_xmpp::XmlElement* stanza) {
return false;
}
void ChromotingClient::StartConnection() {
DCHECK(thread_checker_.CalledOnValidThread());
auto session = session_manager_->Connect(
SignalingAddress(host_jid_),
std::make_unique<protocol::NegotiatingClientAuthenticator>(
signal_strategy_->GetLocalAddress().id(), host_jid_,
client_auth_config_));
if (host_experiment_sender_) {
session->AddPlugin(host_experiment_sender_.get());
}
connection_->Connect(std::move(session), transport_context_, this);
}
void ChromotingClient::OnChannelsConnected() {
DCHECK(thread_checker_.CalledOnValidThread());
// Negotiate capabilities with the host.
VLOG(1) << "Client capabilities: " << local_capabilities_;
protocol::Capabilities capabilities;
capabilities.set_capabilities(local_capabilities_);
connection_->host_stub()->SetCapabilities(capabilities);
mouse_input_scaler_.set_input_stub(connection_->input_stub());
}
} // namespace remoting

@ -1,161 +0,0 @@
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ChromotingClient is the controller for the Client implementation.
#ifndef REMOTING_CLIENT_CHROMOTING_CLIENT_H_
#define REMOTING_CLIENT_CHROMOTING_CLIENT_H_
#include <memory>
#include <string>
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h"
#include "remoting/client/host_experiment_sender.h"
#include "remoting/protocol/client_authentication_config.h"
#include "remoting/protocol/client_stub.h"
#include "remoting/protocol/clipboard_stub.h"
#include "remoting/protocol/connection_to_host.h"
#include "remoting/protocol/input_stub.h"
#include "remoting/protocol/mouse_input_filter.h"
#include "remoting/protocol/session_config.h"
#include "remoting/protocol/video_stub.h"
#include "remoting/signaling/signal_strategy.h"
namespace base {
class SingleThreadTaskRunner;
} // namespace base
namespace remoting {
namespace protocol {
class CandidateSessionConfig;
class SessionManager;
class TransportContext;
class VideoRenderer;
} // namespace protocol
class ClientContext;
class ClientUserInterface;
class ChromotingClient : public SignalStrategy::Listener,
public protocol::ConnectionToHost::HostEventCallback,
public protocol::ClientStub {
public:
// |client_context|, |user_interface| and |video_renderer| must outlive the
// client. |audio_stream_consumer| may be null, in which case audio will not
// be requested.
ChromotingClient(ClientContext* client_context,
ClientUserInterface* user_interface,
protocol::VideoRenderer* video_renderer,
base::WeakPtr<protocol::AudioStub> audio_stream_consumer);
ChromotingClient(const ChromotingClient&) = delete;
ChromotingClient& operator=(const ChromotingClient&) = delete;
~ChromotingClient() override;
void set_protocol_config(
std::unique_ptr<protocol::CandidateSessionConfig> config);
void set_host_experiment_config(const std::string& experiment_config);
// Used to set fake/mock objects for tests which use the ChromotingClient.
void SetConnectionToHostForTests(
std::unique_ptr<protocol::ConnectionToHost> connection_to_host);
// Start the client. Must be called on the main thread. |signal_strategy|
// must outlive the client.
void Start(SignalStrategy* signal_strategy,
const protocol::ClientAuthenticationConfig& client_auth_config,
scoped_refptr<protocol::TransportContext> transport_context,
const std::string& host_jid,
const std::string& capabilities);
// Closes the client and notifies the host of the closure.
void Close();
protocol::ConnectionToHost::State connection_state() const {
return connection_->state();
}
protocol::ClipboardStub* clipboard_forwarder() {
return connection_->clipboard_forwarder();
}
protocol::HostStub* host_stub() { return connection_->host_stub(); }
protocol::InputStub* input_stub() { return &mouse_input_scaler_; }
// ClientStub implementation.
void SetCapabilities(const protocol::Capabilities& capabilities) override;
void SetPairingResponse(
const protocol::PairingResponse& pairing_response) override;
void DeliverHostMessage(const protocol::ExtensionMessage& message) override;
void SetVideoLayout(const protocol::VideoLayout& layout) override;
void SetTransportInfo(const protocol::TransportInfo& transport_info) override;
void SetActiveDisplay(const protocol::ActiveDisplay& active_display) override;
// ClipboardStub implementation for receiving clipboard data from host.
void InjectClipboardEvent(const protocol::ClipboardEvent& event) override;
// CursorShapeStub implementation for receiving cursor shape updates.
void SetCursorShape(const protocol::CursorShapeInfo& cursor_shape) override;
// KeyboardLayoutStub implementation for sending keyboard layout to client.
void SetKeyboardLayout(const protocol::KeyboardLayout& layout) override;
// ConnectionToHost::HostEventCallback implementation.
void OnConnectionState(protocol::ConnectionToHost::State state,
protocol::ErrorCode error) override;
void OnConnectionReady(bool ready) override;
void OnRouteChanged(const std::string& channel_name,
const protocol::TransportRoute& route) override;
private:
// SignalStrategy::StatusObserver interface.
void OnSignalStrategyStateChange(SignalStrategy::State state) override;
bool OnSignalStrategyIncomingStanza(const jingle_xmpp::XmlElement* stanza) override;
// Starts connection once |signal_strategy_| is connected.
void StartConnection();
// Called when all channels are connected.
void OnChannelsConnected();
base::ThreadChecker thread_checker_;
scoped_refptr<base::SingleThreadTaskRunner> audio_decode_task_runner_;
std::unique_ptr<protocol::CandidateSessionConfig> protocol_config_;
// The following are not owned by this class.
raw_ptr<ClientUserInterface> user_interface_ = nullptr;
raw_ptr<protocol::VideoRenderer> video_renderer_ = nullptr;
base::WeakPtr<protocol::AudioStub> audio_stream_consumer_;
raw_ptr<SignalStrategy> signal_strategy_ = nullptr;
std::string host_jid_;
protocol::ClientAuthenticationConfig client_auth_config_;
scoped_refptr<protocol::TransportContext> transport_context_;
std::unique_ptr<protocol::SessionManager> session_manager_;
std::unique_ptr<protocol::ConnectionToHost> connection_;
protocol::MouseInputFilter mouse_input_scaler_;
std::string local_capabilities_;
// The set of all capabilities supported by the host.
std::string host_capabilities_;
// True if |protocol::Capabilities| message has been received.
bool host_capabilities_received_ = false;
std::unique_ptr<HostExperimentSender> host_experiment_sender_;
};
} // namespace remoting
#endif // REMOTING_CLIENT_CHROMOTING_CLIENT_H_

@ -1,126 +0,0 @@
// Copyright 2016 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/client/chromoting_client_runtime.h"
#include <memory>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/message_loop/message_pump_type.h"
#include "base/task/current_thread.h"
#include "base/task/single_thread_task_executor.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "build/build_config.h"
#include "mojo/core/embedder/embedder.h"
#include "remoting/base/directory_service_client.h"
#include "remoting/base/oauth_token_getter_proxy.h"
#include "remoting/base/telemetry_log_writer.h"
#include "remoting/base/url_request_context_getter.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/transitional_url_loader_factory_owner.h"
namespace remoting {
// static
ChromotingClientRuntime* ChromotingClientRuntime::GetInstance() {
return base::Singleton<ChromotingClientRuntime>::get();
}
ChromotingClientRuntime::ChromotingClientRuntime() {
base::ThreadPoolInstance::CreateAndStartWithDefaultParams("Remoting");
DCHECK(!base::CurrentThread::Get());
VLOG(1) << "Starting main message loop";
ui_task_executor_ = std::make_unique<base::SingleThreadTaskExecutor>(
base::MessagePumpType::UI);
// |ui_task_executor_| runs on the main thread, so |ui_task_runner_| will run
// on the main thread. We can not kill the main thread when the message loop
// becomes idle so the callback function does nothing (as opposed to the
// typical base::MessageLoop::QuitClosure())
ui_task_runner_ = new AutoThreadTaskRunner(ui_task_executor_->task_runner(),
base::DoNothing());
audio_task_runner_ = AutoThread::Create("native_audio", ui_task_runner_);
display_task_runner_ = AutoThread::Create("native_disp", ui_task_runner_);
network_task_runner_ = AutoThread::CreateWithType(
"native_net", ui_task_runner_, base::MessagePumpType::IO);
mojo::core::Init();
}
ChromotingClientRuntime::~ChromotingClientRuntime() {
if (delegate_) {
delegate_->RuntimeWillShutdown();
} else {
DLOG(ERROR) << "ClientRuntime Delegate is null.";
}
// Block until tasks blocking shutdown have completed their execution.
base::ThreadPoolInstance::Get()->Shutdown();
if (delegate_) {
delegate_->RuntimeDidShutdown();
}
}
void ChromotingClientRuntime::Init(
ChromotingClientRuntime::Delegate* delegate) {
DCHECK(delegate);
DCHECK(!delegate_);
delegate_ = delegate;
url_requester_ = new URLRequestContextGetter(network_task_runner_);
log_writer_ = std::make_unique<TelemetryLogWriter>(CreateOAuthTokenGetter());
network_task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&ChromotingClientRuntime::InitializeOnNetworkThread,
base::Unretained(this)));
}
std::unique_ptr<OAuthTokenGetter>
ChromotingClientRuntime::CreateOAuthTokenGetter() {
return std::make_unique<OAuthTokenGetterProxy>(
delegate_->oauth_token_getter(), ui_task_runner());
}
base::SequenceBound<DirectoryServiceClient>
ChromotingClientRuntime::CreateDirectoryServiceClient() {
// A DirectoryServiceClient subclass that calls url_loader_factory() in its
// constructor, as we can't call it on a non-network thread then pass it via
// base::SequenceBound.
class ClientDirectoryServiceClient : public DirectoryServiceClient {
public:
ClientDirectoryServiceClient(ChromotingClientRuntime* runtime,
std::unique_ptr<OAuthTokenGetter> token_getter)
: DirectoryServiceClient(token_getter.get(),
runtime->url_loader_factory()),
token_getter_(std::move(token_getter)) {}
~ClientDirectoryServiceClient() override = default;
private:
std::unique_ptr<OAuthTokenGetter> token_getter_;
};
return base::SequenceBound<ClientDirectoryServiceClient>(
network_task_runner(), this, CreateOAuthTokenGetter());
}
scoped_refptr<network::SharedURLLoaderFactory>
ChromotingClientRuntime::url_loader_factory() {
DCHECK(network_task_runner()->BelongsToCurrentThread());
return url_loader_factory_owner_->GetURLLoaderFactory();
}
void ChromotingClientRuntime::InitializeOnNetworkThread() {
DCHECK(network_task_runner()->BelongsToCurrentThread());
url_loader_factory_owner_ =
std::make_unique<network::TransitionalURLLoaderFactoryOwner>(
url_requester_);
log_writer_->Init(url_loader_factory_owner_->GetURLLoaderFactory());
}
} // namespace remoting

@ -1,132 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_CHROMOTING_CLIENT_RUNTIME_H_
#define REMOTING_CLIENT_CHROMOTING_CLIENT_RUNTIME_H_
#include <memory>
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/sequence_bound.h"
#include "net/url_request/url_request_context_getter.h"
#include "remoting/base/auto_thread.h"
#include "remoting/base/oauth_token_getter.h"
#include "remoting/base/telemetry_log_writer.h"
namespace base {
class SingleThreadTaskExecutor;
template <typename T>
struct DefaultSingletonTraits;
} // namespace base
namespace network {
class SharedURLLoaderFactory;
class TransitionalURLLoaderFactoryOwner;
} // namespace network
// Houses the global resources on which the Chromoting components run
// (e.g. message loops and task runners).
namespace remoting {
class DirectoryServiceClient;
class ChromotingClientRuntime {
public:
class Delegate {
public:
virtual ~Delegate() {}
// RuntimeWillShutdown will be called on the delegate when the runtime
// enters into the destructor. This is a good time for the delegate to
// start shutting down on threads while they exist.
virtual void RuntimeWillShutdown() = 0;
// RuntimeDidShutdown will be called after task managers and threads
// have been stopped.
virtual void RuntimeDidShutdown() = 0;
// For fetching auth token. Called on the UI thread.
virtual base::WeakPtr<OAuthTokenGetter> oauth_token_getter() = 0;
};
static ChromotingClientRuntime* GetInstance();
ChromotingClientRuntime(const ChromotingClientRuntime&) = delete;
ChromotingClientRuntime& operator=(const ChromotingClientRuntime&) = delete;
// Must be called before calling any other methods on this object.
void Init(ChromotingClientRuntime::Delegate* delegate);
std::unique_ptr<OAuthTokenGetter> CreateOAuthTokenGetter();
base::SequenceBound<DirectoryServiceClient> CreateDirectoryServiceClient();
scoped_refptr<AutoThreadTaskRunner> network_task_runner() {
return network_task_runner_;
}
scoped_refptr<AutoThreadTaskRunner> audio_task_runner() {
return audio_task_runner_;
}
scoped_refptr<AutoThreadTaskRunner> ui_task_runner() {
return ui_task_runner_;
}
scoped_refptr<AutoThreadTaskRunner> display_task_runner() {
return display_task_runner_;
}
scoped_refptr<net::URLRequestContextGetter> url_requester() {
return url_requester_;
}
// Must be called from the network thread.
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory();
ChromotingEventLogWriter* log_writer() { return log_writer_.get(); }
private:
ChromotingClientRuntime();
virtual ~ChromotingClientRuntime();
// Initializes URL loader factory owner, log writer, and other resources on
// the network thread.
void InitializeOnNetworkThread();
// Chromium code's connection to the app message loop. Once created the
// SingleThreadTaskExecutor will live for the life of the program.
std::unique_ptr<base::SingleThreadTaskExecutor> ui_task_executor_;
// References to native threads.
scoped_refptr<AutoThreadTaskRunner> ui_task_runner_;
// TODO(nicholss): AutoThreads will be leaked because they depend on the main
// thread. We should update this class to use regular threads like the client
// plugin does.
// Longer term we should migrate most of these to background tasks except the
// network thread to ThreadPool, removing the need for threads.
scoped_refptr<AutoThreadTaskRunner> audio_task_runner_;
scoped_refptr<AutoThreadTaskRunner> display_task_runner_;
scoped_refptr<AutoThreadTaskRunner> network_task_runner_;
scoped_refptr<net::URLRequestContextGetter> url_requester_;
std::unique_ptr<network::TransitionalURLLoaderFactoryOwner>
url_loader_factory_owner_;
// For logging session stage changes and stats.
std::unique_ptr<TelemetryLogWriter> log_writer_;
raw_ptr<ChromotingClientRuntime::Delegate> delegate_ = nullptr;
friend struct base::DefaultSingletonTraits<ChromotingClientRuntime>;
};
} // namespace remoting
#endif // REMOTING_CLIENT_CHROMOTING_CLIENT_RUNTIME_H_

@ -1,37 +0,0 @@
// Copyright 2016 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/client/chromoting_client_runtime.h"
#include "base/run_loop.h"
#include "build/build_config.h"
#include "remoting/base/auto_thread_task_runner.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace remoting {
#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID)
// A simple test that starts and stop the runtime. This tests the runtime
// operates properly and all threads and message loops are valid.
// TODO(crbug.com/40259505): Failing on iOS.
#if BUILDFLAG(IS_IOS)
#define MAYBE_StartAndStop DISABLED_StartAndStop
#else
#define MAYBE_StartAndStop StartAndStop
#endif
TEST(ChromotingClientRuntimeTest, MAYBE_StartAndStop) {
ChromotingClientRuntime* runtime = ChromotingClientRuntime::GetInstance();
ASSERT_TRUE(runtime);
EXPECT_TRUE(runtime->network_task_runner().get());
EXPECT_TRUE(runtime->ui_task_runner().get());
EXPECT_TRUE(runtime->display_task_runner().get());
EXPECT_TRUE(runtime->url_requester().get());
EXPECT_TRUE(runtime->log_writer());
}
#endif
} // namespace remoting

@ -1,717 +0,0 @@
// Copyright 2013 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/client/chromoting_session.h"
#include <stdint.h>
#include <algorithm>
#include <memory>
#include <utility>
#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/timer/timer.h"
#include "components/webrtc/thread_wrapper.h"
#include "net/socket/client_socket_factory.h"
#include "remoting/base/chromoting_event.h"
#include "remoting/base/service_urls.h"
#include "remoting/client/audio/audio_player.h"
#include "remoting/client/chromoting_client_runtime.h"
#include "remoting/client/client_telemetry_logger.h"
#include "remoting/client/input/native_device_keymap.h"
#include "remoting/protocol/chromium_port_allocator_factory.h"
#include "remoting/protocol/chromium_socket_factory.h"
#include "remoting/protocol/client_authentication_config.h"
#include "remoting/protocol/frame_consumer.h"
#include "remoting/protocol/host_stub.h"
#include "remoting/protocol/ice_config_fetcher.h"
#include "remoting/protocol/network_settings.h"
#include "remoting/protocol/performance_tracker.h"
#include "remoting/protocol/transport_context.h"
#include "remoting/protocol/video_renderer.h"
#include "remoting/signaling/ftl_client_uuid_device_id_provider.h"
#include "remoting/signaling/ftl_signal_strategy.h"
#include "remoting/signaling/server_log_entry.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
namespace remoting {
namespace {
// Default DPI to assume for old clients that use notifyClientResolution.
const int kDefaultDPI = 96;
// Used by NormalizeclientResolution. See comment below.
const int kMinDimension = 640;
// Interval at which to log performance statistics, if enabled.
constexpr base::TimeDelta kPerfStatsInterval = base::Minutes(1);
// Delay to destroy the signal strategy, so that the session-terminate event can
// still be sent out.
constexpr base::TimeDelta kDestroySignalingDelay = base::Seconds(2);
bool IsClientResolutionValid(int width_pixels, int height_pixels) {
// This prevents sending resolution on a portrait mode small phone screen
// because resizing the remote desktop to portrait will mess with icons and
// such on the desktop and it probably isn't what the user wants.
return (width_pixels >= height_pixels) || (width_pixels >= kMinDimension);
}
// Normalizes the resolution so that both dimensions are not smaller than
// kMinDimension.
void NormalizeClientResolution(protocol::ClientResolution* resolution) {
int min_dimension =
std::min(resolution->width_pixels(), resolution->height_pixels());
if (min_dimension >= kMinDimension) {
return;
}
// Always scale by integer to prevent blurry interpolation.
int scale = std::ceil(((float)kMinDimension) / min_dimension);
resolution->set_width_pixels(resolution->width_pixels() * scale);
resolution->set_height_pixels(resolution->height_pixels() * scale);
}
struct SessionContext {
base::WeakPtr<ChromotingSession::Delegate> delegate;
std::unique_ptr<protocol::AudioStub> audio_player;
std::unique_ptr<base::WeakPtrFactory<protocol::AudioStub>>
audio_player_weak_factory;
std::unique_ptr<protocol::CursorShapeStub> cursor_shape_stub;
std::unique_ptr<protocol::VideoRenderer> video_renderer;
ConnectToHostInfo info;
};
} // namespace
class ChromotingSession::Core : public ClientUserInterface,
public protocol::ClipboardStub,
public protocol::KeyboardLayoutStub {
public:
Core(ChromotingClientRuntime* runtime,
std::unique_ptr<ClientTelemetryLogger> logger,
std::unique_ptr<SessionContext> session_context);
Core(const Core&) = delete;
Core& operator=(const Core&) = delete;
~Core() override;
void RequestPairing(const std::string& device_name);
void SendMouseEvent(int x,
int y,
protocol::MouseEvent_MouseButton button,
bool button_down);
void SendMouseWheelEvent(int delta_x, int delta_y);
void SendKeyEvent(int usb_key_code, bool key_down);
void SendTextEvent(const std::string& text);
void SendTouchEvent(const protocol::TouchEvent& touch_event);
void SendClientResolution(int width_pixels, int height_pixels, float scale);
void EnableVideoChannel(bool enable);
void SendClientMessage(const std::string& type, const std::string& data);
// This function is still valid after Invalidate() is called.
std::unique_ptr<FeedbackData> GetFeedbackData();
// Logs the disconnect event and invalidates the instance.
void Disconnect();
// ClientUserInterface implementation.
void OnConnectionState(protocol::ConnectionToHost::State state,
protocol::ErrorCode error) override;
void OnConnectionReady(bool ready) override;
void OnRouteChanged(const std::string& channel_name,
const protocol::TransportRoute& route) override;
void SetCapabilities(const std::string& capabilities) override;
void SetPairingResponse(const protocol::PairingResponse& response) override;
void DeliverHostMessage(const protocol::ExtensionMessage& message) override;
void SetDesktopSize(const webrtc::DesktopSize& size,
const webrtc::DesktopVector& dpi) override;
protocol::ClipboardStub* GetClipboardStub() override;
protocol::CursorShapeStub* GetCursorShapeStub() override;
protocol::KeyboardLayoutStub* GetKeyboardLayoutStub() override;
// ClipboardStub implementation.
void InjectClipboardEvent(const protocol::ClipboardEvent& event) override;
// KeyboardLayoutStub implementation.
void SetKeyboardLayout(const protocol::KeyboardLayout& layout) override;
base::WeakPtr<Core> GetWeakPtr();
private:
// Destroys the client and invalidates weak pointers. This doesn't destroy the
// instance itself.
void Invalidate();
void ConnectOnNetworkThread();
void LogPerfStats();
// Pops up a UI to fetch the PIN.
void FetchSecret(
bool pairing_supported,
const protocol::SecretFetchedCallback& secret_fetched_callback);
void HandleOnSecretFetched(const protocol::SecretFetchedCallback& callback,
const std::string secret);
scoped_refptr<AutoThreadTaskRunner> ui_task_runner() {
return runtime_->ui_task_runner();
}
scoped_refptr<AutoThreadTaskRunner> network_task_runner() {
return runtime_->network_task_runner();
}
// |runtime_| and |logger_| are stored separately from |session_context_| so
// that they won't be destroyed after the core is invalidated.
const raw_ptr<ChromotingClientRuntime> runtime_;
std::unique_ptr<ClientTelemetryLogger> logger_;
std::unique_ptr<SessionContext> session_context_;
std::unique_ptr<ClientContext> client_context_;
std::unique_ptr<protocol::PerformanceTracker> perf_tracker_;
// |signaling_| must outlive |client_|.
std::unique_ptr<SignalStrategy> signaling_;
std::unique_ptr<OAuthTokenGetter> token_getter_;
std::unique_ptr<ChromotingClient> client_;
// Empty string if client doesn't request for pairing.
std::string device_name_for_pairing_;
// The current session state.
protocol::ConnectionToHost::State session_state_ =
protocol::ConnectionToHost::INITIALIZING;
base::RepeatingTimer perf_stats_logging_timer_;
// weak_factory_.GetWeakPtr() creates new valid WeakPtrs after
// weak_factory_.InvalidateWeakPtrs() is called. We store and return
// |weak_ptr_| in GetWeakPtr() so that its copies are still invalidated once
// InvalidateWeakPtrs() is called.
base::WeakPtr<Core> weak_ptr_;
base::WeakPtrFactory<Core> weak_factory_{this};
};
ChromotingSession::Core::Core(ChromotingClientRuntime* runtime,
std::unique_ptr<ClientTelemetryLogger> logger,
std::unique_ptr<SessionContext> session_context)
: runtime_(runtime),
logger_(std::move(logger)),
session_context_(std::move(session_context)) {
DCHECK(ui_task_runner()->BelongsToCurrentThread());
DCHECK(runtime_);
DCHECK(logger_);
DCHECK(session_context_);
weak_ptr_ = weak_factory_.GetWeakPtr();
network_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&Core::ConnectOnNetworkThread, GetWeakPtr()));
}
ChromotingSession::Core::~Core() {
DCHECK(network_task_runner()->BelongsToCurrentThread());
// Make sure we log a close event if the session has not been disconnected
// yet.
Disconnect();
}
void ChromotingSession::Core::RequestPairing(const std::string& device_name) {
DCHECK(!device_name.empty());
DCHECK(network_task_runner()->BelongsToCurrentThread());
device_name_for_pairing_ = device_name;
}
void ChromotingSession::Core::SendMouseEvent(
int x,
int y,
protocol::MouseEvent_MouseButton button,
bool button_down) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
protocol::MouseEvent event;
event.set_x(x);
event.set_y(y);
event.set_button(button);
if (button != protocol::MouseEvent::BUTTON_UNDEFINED)
event.set_button_down(button_down);
client_->input_stub()->InjectMouseEvent(event);
}
void ChromotingSession::Core::SendMouseWheelEvent(int delta_x, int delta_y) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
protocol::MouseEvent event;
event.set_wheel_delta_x(delta_x);
event.set_wheel_delta_y(delta_y);
client_->input_stub()->InjectMouseEvent(event);
}
void ChromotingSession::Core::SendKeyEvent(int usb_key_code, bool key_down) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
protocol::KeyEvent event;
event.set_usb_keycode(usb_key_code);
event.set_pressed(key_down);
client_->input_stub()->InjectKeyEvent(event);
}
void ChromotingSession::Core::SendTextEvent(const std::string& text) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
protocol::TextEvent event;
event.set_text(text);
client_->input_stub()->InjectTextEvent(event);
}
void ChromotingSession::Core::SendTouchEvent(
const protocol::TouchEvent& touch_event) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
client_->input_stub()->InjectTouchEvent(touch_event);
}
void ChromotingSession::Core::SendClientResolution(int width_pixels,
int height_pixels,
float scale) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
if (!IsClientResolutionValid(width_pixels, height_pixels)) {
return;
}
protocol::ClientResolution client_resolution;
client_resolution.set_width_pixels(width_pixels);
client_resolution.set_height_pixels(height_pixels);
client_resolution.set_x_dpi(scale * kDefaultDPI);
client_resolution.set_y_dpi(scale * kDefaultDPI);
NormalizeClientResolution(&client_resolution);
client_->host_stub()->NotifyClientResolution(client_resolution);
}
void ChromotingSession::Core::EnableVideoChannel(bool enable) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
protocol::VideoControl video_control;
video_control.set_enable(enable);
client_->host_stub()->ControlVideo(video_control);
}
void ChromotingSession::Core::SendClientMessage(const std::string& type,
const std::string& data) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
protocol::ExtensionMessage extension_message;
extension_message.set_type(type);
extension_message.set_data(data);
client_->host_stub()->DeliverClientMessage(extension_message);
}
std::unique_ptr<FeedbackData> ChromotingSession::Core::GetFeedbackData() {
DCHECK(network_task_runner()->BelongsToCurrentThread());
auto data = std::make_unique<FeedbackData>();
data->FillWithChromotingEvent(logger_->current_session_state_event());
return data;
}
void ChromotingSession::Core::Disconnect() {
DCHECK(network_task_runner()->BelongsToCurrentThread());
// Do not log session state change if the connection is already closed.
if (session_state_ != protocol::ConnectionToHost::INITIALIZING &&
session_state_ != protocol::ConnectionToHost::FAILED &&
session_state_ != protocol::ConnectionToHost::CLOSED) {
ChromotingEvent::SessionState session_state_to_log;
if (session_state_ == protocol::ConnectionToHost::CONNECTED) {
session_state_to_log = ChromotingEvent::SessionState::CLOSED;
} else {
session_state_to_log = ChromotingEvent::SessionState::CONNECTION_CANCELED;
}
logger_->LogSessionStateChange(session_state_to_log,
ChromotingEvent::ConnectionError::NONE);
session_state_ = protocol::ConnectionToHost::CLOSED;
// Make sure we send a session-terminate to the host.
client_->Close();
Invalidate();
}
}
void ChromotingSession::Core::OnConnectionState(
protocol::ConnectionToHost::State state,
protocol::ErrorCode error) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
if (state == protocol::ConnectionToHost::CONNECTED) {
perf_stats_logging_timer_.Start(
FROM_HERE, kPerfStatsInterval,
base::BindRepeating(&Core::LogPerfStats, GetWeakPtr()));
if (!device_name_for_pairing_.empty()) {
protocol::PairingRequest request;
request.set_client_name(device_name_for_pairing_);
client_->host_stub()->RequestPairing(request);
}
} else if (perf_stats_logging_timer_.IsRunning()) {
perf_stats_logging_timer_.Stop();
}
logger_->LogSessionStateChange(
ClientTelemetryLogger::TranslateState(state, session_state_),
ClientTelemetryLogger::TranslateError(error));
session_state_ = state;
ui_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&ChromotingSession::Delegate::OnConnectionState,
session_context_->delegate, state, error));
if (state == protocol::ConnectionToHost::CLOSED ||
state == protocol::ConnectionToHost::FAILED) {
Invalidate();
}
}
void ChromotingSession::Core::OnConnectionReady(bool ready) {
// We ignore this message, since OnConnectionState tells us the same thing.
}
void ChromotingSession::Core::OnRouteChanged(
const std::string& channel_name,
const protocol::TransportRoute& route) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
std::string message = "Channel " + channel_name + " using " +
protocol::TransportRoute::GetTypeString(route.type) +
" connection.";
VLOG(1) << "Route: " << message;
logger_->SetTransportRoute(route);
}
void ChromotingSession::Core::SetCapabilities(const std::string& capabilities) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
ui_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&ChromotingSession::Delegate::SetCapabilities,
session_context_->delegate, capabilities));
}
void ChromotingSession::Core::SetPairingResponse(
const protocol::PairingResponse& response) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
ui_task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&ChromotingSession::Delegate::CommitPairingCredentials,
session_context_->delegate, session_context_->info.host_id,
response.client_id(), response.shared_secret()));
}
void ChromotingSession::Core::DeliverHostMessage(
const protocol::ExtensionMessage& message) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
ui_task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&ChromotingSession::Delegate::HandleExtensionMessage,
session_context_->delegate, message.type(),
message.data()));
}
void ChromotingSession::Core::SetDesktopSize(const webrtc::DesktopSize& size,
const webrtc::DesktopVector& dpi) {
// ChromotingSession's VideoRenderer gets size from the frames and it doesn't
// use DPI, so this call can be ignored.
}
protocol::ClipboardStub* ChromotingSession::Core::GetClipboardStub() {
return this;
}
protocol::CursorShapeStub* ChromotingSession::Core::GetCursorShapeStub() {
return session_context_->cursor_shape_stub.get();
}
protocol::KeyboardLayoutStub* ChromotingSession::Core::GetKeyboardLayoutStub() {
return this;
}
void ChromotingSession::Core::InjectClipboardEvent(
const protocol::ClipboardEvent& event) {
NOTIMPLEMENTED();
}
void ChromotingSession::Core::SetKeyboardLayout(
const protocol::KeyboardLayout& layout) {
NOTIMPLEMENTED();
}
base::WeakPtr<ChromotingSession::Core> ChromotingSession::Core::GetWeakPtr() {
return weak_ptr_;
}
void ChromotingSession::Core::Invalidate() {
DCHECK(network_task_runner()->BelongsToCurrentThread());
// Prevent all pending and future calls from ChromotingSession.
weak_factory_.InvalidateWeakPtrs();
client_.reset();
token_getter_.reset();
perf_tracker_.reset();
client_context_.reset();
session_context_.reset();
// Dirty hack to make sure session-terminate message is sent before
// |signaling_| gets deleted. W/o the message being sent, the other side will
// believe an error has occurred.
if (signaling_) {
signaling_->Disconnect();
network_task_runner()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
[](std::unique_ptr<SignalStrategy> signaling) {
signaling.reset();
},
std::move(signaling_)),
kDestroySignalingDelay);
}
}
void ChromotingSession::Core::ConnectOnNetworkThread() {
DCHECK(network_task_runner()->BelongsToCurrentThread());
if (session_context_->info.host_ftl_id.empty()) {
// Simulate a CONNECTING state to make sure it doesn't skew telemetry.
OnConnectionState(protocol::ConnectionToHost::State::CONNECTING,
ErrorCode::OK);
OnConnectionState(protocol::ConnectionToHost::State::FAILED,
ErrorCode::INCOMPATIBLE_PROTOCOL);
return;
}
webrtc::ThreadWrapper::EnsureForCurrentMessageLoop();
client_context_ = std::make_unique<ClientContext>(network_task_runner());
client_context_->Start();
perf_tracker_ = std::make_unique<protocol::PerformanceTracker>();
session_context_->video_renderer->Initialize(*client_context_,
perf_tracker_.get());
logger_->SetHostInfo(
session_context_->info.host_version,
ChromotingEvent::ParseOsFromString(session_context_->info.host_os),
session_context_->info.host_os_version);
// TODO(yuweih): Ideally we should make ChromotingClient and all its
// sub-components (e.g. ConnectionToHost) take raw pointer instead of WeakPtr.
client_ = std::make_unique<ChromotingClient>(
client_context_.get(), this, session_context_->video_renderer.get(),
session_context_->audio_player_weak_factory->GetWeakPtr());
signaling_ = std::make_unique<FtlSignalStrategy>(
runtime_->CreateOAuthTokenGetter(), runtime_->url_loader_factory(),
std::make_unique<FtlClientUuidDeviceIdProvider>());
logger_->SetSignalStrategyType(ChromotingEvent::SignalStrategyType::FTL);
token_getter_ = runtime_->CreateOAuthTokenGetter();
scoped_refptr<protocol::TransportContext> transport_context =
new protocol::TransportContext(
std::make_unique<protocol::ChromiumPortAllocatorFactory>(),
webrtc::ThreadWrapper::current()->SocketServer(),
/*ice_config_fetcher=*/nullptr, protocol::TransportRole::CLIENT);
if (session_context_->info.pairing_id.length() &&
session_context_->info.pairing_secret.length()) {
logger_->SetAuthMethod(ChromotingEvent::AuthMethod::PINLESS);
}
protocol::ClientAuthenticationConfig client_auth_config;
client_auth_config.host_id = session_context_->info.host_id;
client_auth_config.pairing_client_id = session_context_->info.pairing_id;
client_auth_config.pairing_secret = session_context_->info.pairing_secret;
client_auth_config.fetch_secret_callback =
base::BindRepeating(&Core::FetchSecret, GetWeakPtr());
client_->Start(signaling_.get(), client_auth_config, transport_context,
session_context_->info.host_ftl_id,
session_context_->info.capabilities);
}
void ChromotingSession::Core::LogPerfStats() {
DCHECK(network_task_runner()->BelongsToCurrentThread());
logger_->LogStatistics(*perf_tracker_);
}
void ChromotingSession::Core::FetchSecret(
bool pairing_supported,
const protocol::SecretFetchedCallback& secret_fetched_callback) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
// TODO(yuweih): Use bindOnce once SecretFetchedCallback becomes OnceCallback.
auto secret_fetched_callback_for_ui_thread = base::BindRepeating(
[](scoped_refptr<AutoThreadTaskRunner> network_task_runner,
base::WeakPtr<ChromotingSession::Core> core,
const protocol::SecretFetchedCallback& callback,
const std::string& secret) {
DCHECK(!network_task_runner->BelongsToCurrentThread());
network_task_runner->PostTask(
FROM_HERE,
base::BindOnce(&ChromotingSession::Core::HandleOnSecretFetched,
core, callback, secret));
},
network_task_runner(), GetWeakPtr(), secret_fetched_callback);
ui_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&ChromotingSession::Delegate::FetchSecret,
session_context_->delegate, pairing_supported,
secret_fetched_callback_for_ui_thread));
}
void ChromotingSession::Core::HandleOnSecretFetched(
const protocol::SecretFetchedCallback& callback,
const std::string secret) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
logger_->SetAuthMethod(ChromotingEvent::AuthMethod::PIN);
callback.Run(secret);
}
// ChromotingSession implementation.
ChromotingSession::ChromotingSession(
base::WeakPtr<ChromotingSession::Delegate> delegate,
std::unique_ptr<protocol::CursorShapeStub> cursor_shape_stub,
std::unique_ptr<protocol::VideoRenderer> video_renderer,
std::unique_ptr<protocol::AudioStub> audio_player,
const ConnectToHostInfo& info)
: runtime_(ChromotingClientRuntime::GetInstance()) {
DCHECK(delegate);
DCHECK(cursor_shape_stub);
DCHECK(video_renderer);
DCHECK(audio_player);
DCHECK(runtime_->ui_task_runner()->BelongsToCurrentThread());
// logger is set when connection is started.
auto session_context = std::make_unique<SessionContext>();
session_context->delegate = delegate;
session_context->audio_player = std::move(audio_player);
session_context->audio_player_weak_factory =
std::make_unique<base::WeakPtrFactory<protocol::AudioStub>>(
session_context->audio_player.get());
session_context->cursor_shape_stub = std::move(cursor_shape_stub);
session_context->video_renderer = std::move(video_renderer);
session_context->info = info;
auto logger = std::make_unique<ClientTelemetryLogger>(
runtime_->log_writer(), ChromotingEvent::Mode::ME2ME,
info.session_entry_point);
core_ = std::make_unique<Core>(runtime_, std::move(logger),
std::move(session_context));
}
ChromotingSession::~ChromotingSession() {
DCHECK(runtime_->ui_task_runner()->BelongsToCurrentThread());
runtime_->network_task_runner()->DeleteSoon(FROM_HERE, core_.release());
}
void ChromotingSession::GetFeedbackData(
GetFeedbackDataCallback callback) const {
DCHECK(runtime_->ui_task_runner()->BelongsToCurrentThread());
// Bind to base::Unretained(core) instead of the WeakPtr so that we can still
// get the feedback data after the session is remotely disconnected.
runtime_->network_task_runner()->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&Core::GetFeedbackData, base::Unretained(core_.get())),
std::move(callback));
}
void ChromotingSession::RequestPairing(const std::string& device_name) {
RunCoreTaskOnNetworkThread(FROM_HERE, &Core::RequestPairing, device_name);
}
void ChromotingSession::SendMouseEvent(int x,
int y,
protocol::MouseEvent_MouseButton button,
bool button_down) {
RunCoreTaskOnNetworkThread(FROM_HERE, &Core::SendMouseEvent, x, y, button,
button_down);
}
void ChromotingSession::SendMouseWheelEvent(int delta_x, int delta_y) {
RunCoreTaskOnNetworkThread(FROM_HERE, &Core::SendMouseWheelEvent, delta_x,
delta_y);
}
bool ChromotingSession::SendKeyEvent(int scan_code,
int key_code,
bool key_down) {
DCHECK(runtime_->ui_task_runner()->BelongsToCurrentThread());
// For software keyboards |scan_code| is set to 0, in which case the
// |key_code| is used instead.
uint32_t usb_key_code =
scan_code ? ui::KeycodeConverter::NativeKeycodeToUsbKeycode(scan_code)
: NativeDeviceKeycodeToUsbKeycode(key_code);
if (!usb_key_code) {
LOG(WARNING) << "Ignoring unknown key code: " << key_code
<< " scan code: " << scan_code;
return false;
}
RunCoreTaskOnNetworkThread(FROM_HERE, &Core::SendKeyEvent, usb_key_code,
key_down);
return true;
}
void ChromotingSession::SendTextEvent(const std::string& text) {
RunCoreTaskOnNetworkThread(FROM_HERE, &Core::SendTextEvent, text);
}
void ChromotingSession::SendTouchEvent(
const protocol::TouchEvent& touch_event) {
RunCoreTaskOnNetworkThread(FROM_HERE, &Core::SendTouchEvent, touch_event);
}
void ChromotingSession::SendClientResolution(int width_pixels,
int height_pixels,
float scale) {
RunCoreTaskOnNetworkThread(FROM_HERE, &Core::SendClientResolution,
width_pixels, height_pixels, scale);
}
void ChromotingSession::EnableVideoChannel(bool enable) {
RunCoreTaskOnNetworkThread(FROM_HERE, &Core::EnableVideoChannel, enable);
}
void ChromotingSession::SendClientMessage(const std::string& type,
const std::string& data) {
RunCoreTaskOnNetworkThread(FROM_HERE, &Core::SendClientMessage, type, data);
}
template <typename Functor, typename... Args>
void ChromotingSession::RunCoreTaskOnNetworkThread(
const base::Location& from_here,
Functor&& core_functor,
Args&&... args) {
DCHECK(runtime_->ui_task_runner()->BelongsToCurrentThread());
runtime_->network_task_runner()->PostTask(
FROM_HERE,
base::BindOnce(std::forward<Functor>(core_functor), core_->GetWeakPtr(),
std::forward<Args>(args)...));
}
} // namespace remoting

@ -1,140 +0,0 @@
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_CHROMOTING_SESSION_H_
#define REMOTING_CLIENT_CHROMOTING_SESSION_H_
#include <memory>
#include <string>
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "remoting/client/chromoting_client.h"
#include "remoting/client/client_context.h"
#include "remoting/client/client_telemetry_logger.h"
#include "remoting/client/client_user_interface.h"
#include "remoting/client/connect_to_host_info.h"
#include "remoting/client/feedback_data.h"
#include "remoting/client/input/client_input_injector.h"
#include "remoting/proto/control.pb.h"
#include "remoting/proto/event.pb.h"
#include "remoting/protocol/clipboard_stub.h"
#include "remoting/protocol/cursor_shape_stub.h"
#include "remoting/signaling/ftl_device_id_provider.h"
#include "remoting/signaling/signal_strategy.h"
namespace remoting {
namespace protocol {
class AudioStub;
class VideoRenderer;
} // namespace protocol
class ChromotingClientRuntime;
// ChromotingSession is scoped to the session.
// Construction, destruction, and all method calls must occur on the UI Thread.
// All callbacks will be posted to the UI Thread.
// A ChromotingSession instance can be used for at most one connection attempt.
// If you need to reconnect an ended session, you will need to create a new
// session instance.
class ChromotingSession : public ClientInputInjector {
public:
// All methods of the delegate are called on the UI thread. Callbacks should
// also be invoked on the UI thread too.
class Delegate {
public:
virtual ~Delegate() {}
// Notifies the delegate of the current connection status. The delegate
// should destroy the ChromotingSession instance when the connection state
// is an end state.
virtual void OnConnectionState(protocol::ConnectionToHost::State state,
protocol::ErrorCode error) = 0;
// Saves new pairing credentials to permanent storage.
virtual void CommitPairingCredentials(const std::string& host,
const std::string& id,
const std::string& secret) = 0;
// Notifies the user interface that the user needs to enter a PIN. The
// current authentication attempt is put on hold until |callback| is
// invoked.
virtual void FetchSecret(
bool pairing_supported,
const protocol::SecretFetchedCallback& secret_fetched_callback) = 0;
// Pass on the set of negotiated capabilities to the client.
virtual void SetCapabilities(const std::string& capabilities) = 0;
// Passes on the deconstructed ExtensionMessage to the client to handle
// appropriately.
virtual void HandleExtensionMessage(const std::string& type,
const std::string& message) = 0;
};
using GetFeedbackDataCallback =
base::OnceCallback<void(std::unique_ptr<FeedbackData>)>;
// Initiates a connection with the specified host. This will start the
// connection immediately.
ChromotingSession(base::WeakPtr<ChromotingSession::Delegate> delegate,
std::unique_ptr<protocol::CursorShapeStub> cursor_stub,
std::unique_ptr<protocol::VideoRenderer> video_renderer,
std::unique_ptr<protocol::AudioStub> audio_player,
const ConnectToHostInfo& info);
ChromotingSession(const ChromotingSession&) = delete;
ChromotingSession& operator=(const ChromotingSession&) = delete;
~ChromotingSession() override;
// Gets the current feedback data and returns it to the callback on the
// UI thread.
void GetFeedbackData(GetFeedbackDataCallback callback) const;
// Requests pairing between the host and client for PIN-less authentication.
void RequestPairing(const std::string& device_name);
// Moves the host's cursor to the specified coordinates, optionally with some
// mouse button depressed. If |button| is BUTTON_UNDEFINED, no click is made.
void SendMouseEvent(int x,
int y,
protocol::MouseEvent_MouseButton button,
bool button_down);
void SendMouseWheelEvent(int delta_x, int delta_y);
// ClientInputInjector implementation.
bool SendKeyEvent(int scan_code, int key_code, bool key_down) override;
void SendTextEvent(const std::string& text) override;
// Sends the provided touch event payload to the host.
void SendTouchEvent(const protocol::TouchEvent& touch_event);
void SendClientResolution(int width_pixels, int height_pixels, float scale);
// Enables or disables the video channel.
void EnableVideoChannel(bool enable);
void SendClientMessage(const std::string& type, const std::string& data);
private:
class Core;
template <typename Functor, typename... Args>
void RunCoreTaskOnNetworkThread(const base::Location& from_here,
Functor&& core_functor,
Args&&... args);
// Used to obtain task runner references.
const raw_ptr<ChromotingClientRuntime> runtime_;
// Created when the session is connected, then used, and destroyed on the
// network thread when the instance is destroyed.
std::unique_ptr<Core> core_;
};
} // namespace remoting
#endif // REMOTING_CLIENT_CHROMOTING_SESSION_H_

@ -1,46 +0,0 @@
// Copyright 2012 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/client/client_context.h"
#include "base/task/single_thread_task_runner.h"
namespace remoting {
ClientContext::ClientContext(
const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner)
: main_task_runner_(main_task_runner),
decode_thread_("ChromotingClientDecodeThread"),
audio_decode_thread_("ChromotingClientAudioDecodeThread") {
}
ClientContext::~ClientContext() = default;
void ClientContext::Start() {
// Start all the threads.
decode_thread_.Start();
audio_decode_thread_.Start();
}
void ClientContext::Stop() {
// Stop all the threads.
decode_thread_.Stop();
audio_decode_thread_.Stop();
}
scoped_refptr<base::SingleThreadTaskRunner> ClientContext::main_task_runner()
const {
return main_task_runner_;
}
scoped_refptr<base::SingleThreadTaskRunner> ClientContext::decode_task_runner()
const {
return decode_thread_.task_runner();
}
scoped_refptr<base::SingleThreadTaskRunner>
ClientContext::audio_decode_task_runner() const {
return audio_decode_thread_.task_runner();
}
} // namespace remoting

@ -1,47 +0,0 @@
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_CLIENT_CONTEXT_H_
#define REMOTING_CLIENT_CLIENT_CONTEXT_H_
#include "base/threading/thread.h"
namespace base {
class SingleThreadTaskRunner;
} // namespace base
namespace remoting {
// A class that manages threads and running context for the chromoting client
// process.
class ClientContext {
public:
ClientContext(
const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner);
ClientContext(const ClientContext&) = delete;
ClientContext& operator=(const ClientContext&) = delete;
virtual ~ClientContext();
void Start();
void Stop();
scoped_refptr<base::SingleThreadTaskRunner> main_task_runner() const;
scoped_refptr<base::SingleThreadTaskRunner> decode_task_runner() const;
scoped_refptr<base::SingleThreadTaskRunner> audio_decode_task_runner() const;
private:
scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
// A thread that handles all video decode operations.
base::Thread decode_thread_;
// A thread that handles all audio decode operations.
base::Thread audio_decode_thread_;
};
} // namespace remoting
#endif // REMOTING_CLIENT_CLIENT_CONTEXT_H_

@ -1,340 +0,0 @@
// Copyright 2016 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/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "remoting/client/client_telemetry_logger.h"
#include <memory>
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "remoting/base/telemetry_log_writer.h"
#if BUILDFLAG(IS_ANDROID)
#include <android/log.h>
#endif // BUILDFLAG(IS_ANDROID)
namespace {
const char kSessionIdAlphabet[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
const int kSessionIdLength = 20;
const int kMaxSessionIdAgeDays = 1;
} // namespace
namespace remoting {
struct ClientTelemetryLogger::HostInfo {
const std::string host_version;
const ChromotingEvent::Os host_os;
const std::string host_os_version;
};
ClientTelemetryLogger::ClientTelemetryLogger(
ChromotingEventLogWriter* log_writer,
ChromotingEvent::Mode mode,
ChromotingEvent::SessionEntryPoint entry_point)
: mode_(mode), entry_point_(entry_point), log_writer_(log_writer) {
thread_checker_.DetachFromThread();
}
ClientTelemetryLogger::~ClientTelemetryLogger() {
DCHECK(thread_checker_.CalledOnValidThread());
}
void ClientTelemetryLogger::SetAuthMethod(
ChromotingEvent::AuthMethod auth_method) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_NE(ChromotingEvent::AuthMethod::NOT_SET, auth_method);
auth_method_ = auth_method;
}
void ClientTelemetryLogger::SetHostInfo(const std::string& host_version,
ChromotingEvent::Os host_os,
const std::string& host_os_version) {
DCHECK(thread_checker_.CalledOnValidThread());
host_info_ = std::make_unique<HostInfo>(
HostInfo{host_version, host_os, host_os_version});
}
void ClientTelemetryLogger::SetTransportRoute(
const protocol::TransportRoute& route) {
DCHECK(thread_checker_.CalledOnValidThread());
transport_route_ = std::make_unique<protocol::TransportRoute>(route);
}
void ClientTelemetryLogger::SetSignalStrategyType(
ChromotingEvent::SignalStrategyType signal_strategy_type) {
DCHECK(thread_checker_.CalledOnValidThread());
signal_strategy_type_ = signal_strategy_type;
}
void ClientTelemetryLogger::LogSessionStateChange(
ChromotingEvent::SessionState state,
ChromotingEvent::ConnectionError error) {
DCHECK(thread_checker_.CalledOnValidThread());
RefreshSessionIdIfOutdated();
if (session_start_time_.is_null()) {
session_start_time_ = base::TimeTicks::Now();
}
ChromotingEvent event =
ClientTelemetryLogger::MakeSessionStateChangeEvent(state, error);
const base::Value* previous_state =
current_session_state_event_.GetValue(ChromotingEvent::kSessionStateKey);
if (previous_state) {
event.SetInteger(ChromotingEvent::kPreviousSessionStateKey,
previous_state->GetInt());
}
log_writer_->Log(event);
current_session_state_event_ = std::move(event);
if (ChromotingEvent::IsEndOfSession(state)) {
session_id_.clear();
session_start_time_ = base::TimeTicks();
}
}
void ClientTelemetryLogger::LogStatistics(
const protocol::PerformanceTracker& perf_tracker) {
DCHECK(thread_checker_.CalledOnValidThread());
RefreshSessionIdIfOutdated();
PrintLogStatistics(perf_tracker);
ChromotingEvent event = MakeStatsEvent(perf_tracker);
log_writer_->Log(event);
}
void ClientTelemetryLogger::PrintLogStatistics(
const protocol::PerformanceTracker& perf_tracker) {
#if BUILDFLAG(IS_ANDROID)
__android_log_print(
ANDROID_LOG_INFO, "stats",
#else
VLOG(0) << base::StringPrintf(
#endif // BUILDFLAG(IS_ANDROID)
"Bandwidth:%.0f FrameRate:%.1f;"
" (Avg, Max) Capture:%.1f, %" PRId64 " Encode:%.1f, %" PRId64
" Decode:%.1f, %" PRId64 " Render:%.1f, %" PRId64 " RTL:%.0f, %" PRId64,
perf_tracker.video_bandwidth(), perf_tracker.video_frame_rate(),
perf_tracker.video_capture_ms().Average(),
perf_tracker.video_capture_ms().Max(),
perf_tracker.video_encode_ms().Average(),
perf_tracker.video_encode_ms().Max(),
perf_tracker.video_decode_ms().Average(),
perf_tracker.video_decode_ms().Max(),
perf_tracker.video_paint_ms().Average(),
perf_tracker.video_paint_ms().Max(),
perf_tracker.round_trip_ms().Average(),
perf_tracker.round_trip_ms().Max());
}
void ClientTelemetryLogger::SetSessionIdGenerationTimeForTest(
base::TimeTicks gen_time) {
session_id_generation_time_ = gen_time;
}
// static
ChromotingEvent::SessionState ClientTelemetryLogger::TranslateState(
protocol::ConnectionToHost::State current_state,
protocol::ConnectionToHost::State previous_state) {
switch (current_state) {
case protocol::ConnectionToHost::State::INITIALIZING:
return ChromotingEvent::SessionState::INITIALIZING;
case protocol::ConnectionToHost::State::CONNECTING:
return ChromotingEvent::SessionState::CONNECTING;
case protocol::ConnectionToHost::State::AUTHENTICATED:
return ChromotingEvent::SessionState::AUTHENTICATED;
case protocol::ConnectionToHost::State::CONNECTED:
return ChromotingEvent::SessionState::CONNECTED;
case protocol::ConnectionToHost::State::FAILED:
return previous_state == protocol::ConnectionToHost::State::CONNECTED
? ChromotingEvent::SessionState::CONNECTION_DROPPED
: ChromotingEvent::SessionState::CONNECTION_FAILED;
case protocol::ConnectionToHost::State::CLOSED:
return ChromotingEvent::SessionState::CLOSED;
default:
NOTREACHED();
}
}
// static
ChromotingEvent::ConnectionError ClientTelemetryLogger::TranslateError(
protocol::ErrorCode error) {
switch (error) {
case ErrorCode::OK:
return ChromotingEvent::ConnectionError::NONE;
case ErrorCode::PEER_IS_OFFLINE:
return ChromotingEvent::ConnectionError::HOST_OFFLINE;
case ErrorCode::SESSION_REJECTED:
return ChromotingEvent::ConnectionError::SESSION_REJECTED;
case ErrorCode::INCOMPATIBLE_PROTOCOL:
return ChromotingEvent::ConnectionError::INCOMPATIBLE_PROTOCOL;
case ErrorCode::AUTHENTICATION_FAILED:
return ChromotingEvent::ConnectionError::AUTHENTICATION_FAILED;
case ErrorCode::INVALID_ACCOUNT:
return ChromotingEvent::ConnectionError::INVALID_ACCOUNT;
case ErrorCode::CHANNEL_CONNECTION_ERROR:
return ChromotingEvent::ConnectionError::P2P_FAILURE;
case ErrorCode::SIGNALING_ERROR:
return ChromotingEvent::ConnectionError::NETWORK_FAILURE;
case ErrorCode::SIGNALING_TIMEOUT:
return ChromotingEvent::ConnectionError::NETWORK_FAILURE;
case ErrorCode::HOST_OVERLOAD:
return ChromotingEvent::ConnectionError::HOST_OVERLOAD;
case ErrorCode::MAX_SESSION_LENGTH:
return ChromotingEvent::ConnectionError::MAX_SESSION_LENGTH;
case ErrorCode::HOST_CONFIGURATION_ERROR:
return ChromotingEvent::ConnectionError::HOST_CONFIGURATION_ERROR;
case ErrorCode::UNKNOWN_ERROR:
return ChromotingEvent::ConnectionError::UNKNOWN_ERROR;
default:
NOTREACHED();
}
}
// static
ChromotingEvent::ConnectionType ClientTelemetryLogger::TranslateConnectionType(
protocol::TransportRoute::RouteType type) {
switch (type) {
case protocol::TransportRoute::DIRECT:
return ChromotingEvent::ConnectionType::DIRECT;
case protocol::TransportRoute::STUN:
return ChromotingEvent::ConnectionType::STUN;
case protocol::TransportRoute::RELAY:
return ChromotingEvent::ConnectionType::RELAY;
default:
NOTREACHED();
}
}
void ClientTelemetryLogger::FillEventContext(ChromotingEvent* event) const {
event->SetEnum(ChromotingEvent::kModeKey, mode_);
event->SetEnum(ChromotingEvent::kRoleKey, ChromotingEvent::Role::CLIENT);
event->SetEnum(ChromotingEvent::kSessionEntryPointKey, entry_point_);
if (auth_method_ != ChromotingEvent::AuthMethod::NOT_SET) {
event->SetEnum(ChromotingEvent::kAuthMethodKey, auth_method_);
}
if (host_info_) {
event->SetString(ChromotingEvent::kHostVersionKey,
host_info_->host_version);
event->SetEnum(ChromotingEvent::kHostOsKey, host_info_->host_os);
event->SetString(ChromotingEvent::kHostOsVersionKey,
host_info_->host_os_version);
}
if (transport_route_) {
ChromotingEvent::ConnectionType connection_type =
TranslateConnectionType(transport_route_->type);
event->SetEnum(ChromotingEvent::kConnectionTypeKey, connection_type);
}
event->AddSystemInfo();
if (!session_id_.empty()) {
event->SetString(ChromotingEvent::kSessionIdKey, session_id_);
}
if (!session_start_time_.is_null()) {
int session_duration =
(base::TimeTicks::Now() - session_start_time_).InSeconds();
event->SetInteger(ChromotingEvent::kSessionDurationKey, session_duration);
}
if (signal_strategy_type_ != ChromotingEvent::SignalStrategyType::NOT_SET) {
event->SetInteger(ChromotingEvent::kSignalStrategyTypeKey,
signal_strategy_type_);
}
}
void ClientTelemetryLogger::GenerateSessionId() {
session_id_.resize(kSessionIdLength);
for (int i = 0; i < kSessionIdLength; i++) {
const int alphabet_size = std::size(kSessionIdAlphabet) - 1;
session_id_[i] = kSessionIdAlphabet[base::RandGenerator(alphabet_size)];
}
session_id_generation_time_ = base::TimeTicks::Now();
}
void ClientTelemetryLogger::RefreshSessionIdIfOutdated() {
if (session_id_.empty()) {
GenerateSessionId();
return;
}
base::TimeDelta max_age = base::Days(kMaxSessionIdAgeDays);
if (base::TimeTicks::Now() - session_id_generation_time_ > max_age) {
// Log the old session ID.
ChromotingEvent event = MakeSessionIdOldEvent();
log_writer_->Log(event);
// Generate a new session ID.
GenerateSessionId();
// Log the new session ID.
ChromotingEvent new_id_event = MakeSessionIdNewEvent();
log_writer_->Log(new_id_event);
}
}
ChromotingEvent ClientTelemetryLogger::MakeStatsEvent(
const protocol::PerformanceTracker& perf_tracker) {
ChromotingEvent event(ChromotingEvent::Type::CONNECTION_STATISTICS);
FillEventContext(&event);
event.SetDouble(ChromotingEvent::kVideoBandwidthKey,
perf_tracker.video_bandwidth());
event.SetDouble(ChromotingEvent::kCaptureLatencyKey,
perf_tracker.video_capture_ms().Average());
event.SetDouble(ChromotingEvent::kEncodeLatencyKey,
perf_tracker.video_encode_ms().Average());
event.SetDouble(ChromotingEvent::kDecodeLatencyKey,
perf_tracker.video_decode_ms().Average());
event.SetDouble(ChromotingEvent::kRenderLatencyKey,
perf_tracker.video_paint_ms().Average());
event.SetDouble(ChromotingEvent::kRoundtripLatencyKey,
perf_tracker.round_trip_ms().Average());
event.SetDouble(ChromotingEvent::kMaxCaptureLatencyKey,
perf_tracker.video_capture_ms().Max());
event.SetDouble(ChromotingEvent::kMaxEncodeLatencyKey,
perf_tracker.video_encode_ms().Max());
event.SetDouble(ChromotingEvent::kMaxDecodeLatencyKey,
perf_tracker.video_decode_ms().Max());
event.SetDouble(ChromotingEvent::kMaxRenderLatencyKey,
perf_tracker.video_paint_ms().Max());
event.SetDouble(ChromotingEvent::kMaxRoundtripLatencyKey,
perf_tracker.round_trip_ms().Max());
return event;
}
ChromotingEvent ClientTelemetryLogger::MakeSessionStateChangeEvent(
ChromotingEvent::SessionState state,
ChromotingEvent::ConnectionError error) {
ChromotingEvent event(ChromotingEvent::Type::SESSION_STATE);
FillEventContext(&event);
event.SetEnum(ChromotingEvent::kSessionStateKey, state);
event.SetEnum(ChromotingEvent::kConnectionErrorKey, error);
return event;
}
ChromotingEvent ClientTelemetryLogger::MakeSessionIdOldEvent() {
ChromotingEvent event(ChromotingEvent::Type::SESSION_ID_OLD);
FillEventContext(&event);
return event;
}
ChromotingEvent ClientTelemetryLogger::MakeSessionIdNewEvent() {
ChromotingEvent event(ChromotingEvent::Type::SESSION_ID_NEW);
FillEventContext(&event);
return event;
}
} // namespace remoting

@ -1,129 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_CLIENT_TELEMETRY_LOGGER_H_
#define REMOTING_CLIENT_CLIENT_TELEMETRY_LOGGER_H_
#include <memory>
#include <string>
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "remoting/base/chromoting_event.h"
#include "remoting/base/chromoting_event_log_writer.h"
#include "remoting/protocol/connection_to_host.h"
#include "remoting/protocol/performance_tracker.h"
#include "remoting/protocol/transport.h"
namespace remoting {
// ClientTelemetryLogger sends client log entries to the telemetry server.
// The logger should be used entirely on one single thread after it is created.
// TODO(yuweih): Implement new features that session_logger.js provides.
class ClientTelemetryLogger {
public:
// |log_writer| must outlive ClientTelemetryLogger.
ClientTelemetryLogger(ChromotingEventLogWriter* log_writer,
ChromotingEvent::Mode mode,
ChromotingEvent::SessionEntryPoint entry_point);
ClientTelemetryLogger(const ClientTelemetryLogger&) = delete;
ClientTelemetryLogger& operator=(const ClientTelemetryLogger&) = delete;
~ClientTelemetryLogger();
void SetAuthMethod(ChromotingEvent::AuthMethod auth_method);
// Sets the host info to be posted along with other log data. By default
// no host info will be logged.
void SetHostInfo(const std::string& host_version,
ChromotingEvent::Os host_os,
const std::string& host_os_version);
void SetSignalStrategyType(ChromotingEvent::SignalStrategyType type);
void SetTransportRoute(const protocol::TransportRoute& route);
void LogSessionStateChange(ChromotingEvent::SessionState state,
ChromotingEvent::ConnectionError error);
void LogStatistics(const protocol::PerformanceTracker& perf_tracker);
const std::string& session_id() const { return session_id_; }
void SetSessionIdGenerationTimeForTest(base::TimeTicks gen_time);
const ChromotingEvent& current_session_state_event() const {
return current_session_state_event_;
}
static ChromotingEvent::SessionState TranslateState(
protocol::ConnectionToHost::State current_state,
protocol::ConnectionToHost::State previous_state);
static ChromotingEvent::ConnectionError TranslateError(
protocol::ErrorCode state);
static ChromotingEvent::ConnectionType TranslateConnectionType(
protocol::TransportRoute::RouteType type);
private:
struct HostInfo;
void FillEventContext(ChromotingEvent* event) const;
// Generates a new random session ID.
void GenerateSessionId();
void PrintLogStatistics(const protocol::PerformanceTracker& perf_tracker);
// If not session ID has been set, simply generates a new one without sending
// any logs, otherwise expire the session ID if the maximum duration has been
// exceeded, and sends SessionIdOld and SessionIdNew events describing the
// change of id.
void RefreshSessionIdIfOutdated();
ChromotingEvent MakeStatsEvent(
const protocol::PerformanceTracker& perf_tracker);
ChromotingEvent MakeSessionStateChangeEvent(
ChromotingEvent::SessionState state,
ChromotingEvent::ConnectionError error);
ChromotingEvent MakeSessionIdOldEvent();
ChromotingEvent MakeSessionIdNewEvent();
// A randomly generated session ID to be attached to log messages. This
// is regenerated at the start of a new session.
std::string session_id_;
base::TimeTicks session_start_time_;
base::TimeTicks session_id_generation_time_;
ChromotingEvent current_session_state_event_;
ChromotingEvent::AuthMethod auth_method_ =
ChromotingEvent::AuthMethod::NOT_SET;
ChromotingEvent::Mode mode_;
ChromotingEvent::SessionEntryPoint entry_point_;
ChromotingEvent::SignalStrategyType signal_strategy_type_ =
ChromotingEvent::SignalStrategyType::NOT_SET;
std::unique_ptr<HostInfo> host_info_;
std::unique_ptr<protocol::TransportRoute> transport_route_;
// The log writer that actually sends log to the server.
raw_ptr<ChromotingEventLogWriter> log_writer_;
base::ThreadChecker thread_checker_;
};
} // namespace remoting
#endif // REMOTING_CLIENT_CLIENT_TELEMETRY_LOGGER_H_

@ -1,159 +0,0 @@
// Copyright 2016 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/client/client_telemetry_logger.h"
#include <memory>
#include <string>
#include "base/containers/circular_deque.h"
#include "base/memory/ptr_util.h"
#include "remoting/protocol/connection_to_host.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
// Returns true if |actual| has all fields that |expected| has and the values
// also match. |actual| can have fields that |expected| doesn't have but not the
// other way around.
static bool Contains(const remoting::ChromotingEvent& actual,
const remoting::ChromotingEvent& expected) {
auto actual_dict = actual.CopyDictionaryValue();
auto expected_dict = expected.CopyDictionaryValue();
for (const auto expected_item : *expected_dict) {
const std::string& key = expected_item.first;
const base::Value* out_value = actual_dict->Find(key);
if (!out_value || expected_item.second != *out_value) {
return false;
}
}
return true;
}
} // namespace
namespace remoting {
// The fake log writer will pass the test IFF:
// 1. The caller logs an entry when the writer expects an entry to be logged.
// 2. The caller logs an entry with all key-value pairs found in the expected
// entry.
// 3. There are no more expected log entries when the writer destructs.
class FakeLogWriter : public ChromotingEventLogWriter {
public:
~FakeLogWriter() override {
EXPECT_TRUE(expected_events_.empty()) << "Sent less logs than expected.";
}
// Add the event to |expected_events_|. Log() will only succeed IFF the actual
// entry has all fields that the expected entry (first entry in
// |expected_events|) has and the values also match. The actual entry can have
// more fields in addition to those in the expected entry.
void AddExpectedEvent(const ChromotingEvent& entry);
// ChromotingEventLogWriter overrides.
void Log(const ChromotingEvent& entry) override;
private:
base::circular_deque<ChromotingEvent> expected_events_;
std::string auth_token_;
};
void FakeLogWriter::AddExpectedEvent(const ChromotingEvent& entry) {
expected_events_.push_back(entry);
}
void FakeLogWriter::Log(const ChromotingEvent& entry) {
ASSERT_FALSE(expected_events_.empty())
<< "Trying to send more logs than expected";
ASSERT_TRUE(Contains(entry, expected_events_.front()))
<< "Unexpected log being sent.";
expected_events_.pop_front();
}
class ClientTelemetryLoggerTest : public testing::Test {
public:
// testing::Test override.
void SetUp() override;
protected:
std::unique_ptr<FakeLogWriter> log_writer_;
std::unique_ptr<ClientTelemetryLogger> logger_;
};
void ClientTelemetryLoggerTest::SetUp() {
log_writer_ = std::make_unique<FakeLogWriter>();
logger_ = std::make_unique<ClientTelemetryLogger>(
log_writer_.get(), ChromotingEvent::Mode::ME2ME,
ChromotingEvent::SessionEntryPoint::CONNECT_BUTTON);
}
TEST_F(ClientTelemetryLoggerTest, LogSessionStateChange) {
ChromotingEvent event(ChromotingEvent::Type::SESSION_STATE);
event.SetEnum("session_state", ChromotingEvent::SessionState::CONNECTED);
event.SetEnum("connection_error", ChromotingEvent::ConnectionError::NONE);
log_writer_->AddExpectedEvent(event);
logger_->LogSessionStateChange(ChromotingEvent::SessionState::CONNECTED,
ChromotingEvent::ConnectionError::NONE);
event.SetEnum("session_state",
ChromotingEvent::SessionState::CONNECTION_FAILED);
event.SetEnum("connection_error",
ChromotingEvent::ConnectionError::HOST_OFFLINE);
log_writer_->AddExpectedEvent(event);
logger_->LogSessionStateChange(
ChromotingEvent::SessionState::CONNECTION_FAILED,
ChromotingEvent::ConnectionError::HOST_OFFLINE);
}
TEST_F(ClientTelemetryLoggerTest, LogStatistics) {
protocol::PerformanceTracker perf_tracker;
log_writer_->AddExpectedEvent(
ChromotingEvent(ChromotingEvent::Type::CONNECTION_STATISTICS));
logger_->LogStatistics(perf_tracker);
}
TEST_F(ClientTelemetryLoggerTest, SessionIdGeneration) {
ChromotingEvent any_event;
log_writer_->AddExpectedEvent(any_event);
log_writer_->AddExpectedEvent(any_event);
log_writer_->AddExpectedEvent(any_event);
logger_->LogSessionStateChange(ChromotingEvent::SessionState::CONNECTED,
ChromotingEvent::ConnectionError::NONE);
std::string last_id = logger_->session_id();
logger_->LogSessionStateChange(ChromotingEvent::SessionState::CLOSED,
ChromotingEvent::ConnectionError::NONE);
EXPECT_TRUE(logger_->session_id().empty());
logger_->LogSessionStateChange(ChromotingEvent::SessionState::CONNECTED,
ChromotingEvent::ConnectionError::NONE);
EXPECT_NE(last_id, logger_->session_id());
}
TEST_F(ClientTelemetryLoggerTest, SessionIdExpiration) {
log_writer_->AddExpectedEvent(
ChromotingEvent(ChromotingEvent::Type::SESSION_STATE));
log_writer_->AddExpectedEvent(
ChromotingEvent(ChromotingEvent::Type::SESSION_ID_OLD));
log_writer_->AddExpectedEvent(
ChromotingEvent(ChromotingEvent::Type::SESSION_ID_NEW));
log_writer_->AddExpectedEvent(
ChromotingEvent(ChromotingEvent::Type::CONNECTION_STATISTICS));
logger_->LogSessionStateChange(ChromotingEvent::SessionState::CONNECTED,
ChromotingEvent::ConnectionError::NONE);
std::string last_id = logger_->session_id();
// kMaxSessionIdAgeDays = 1. Fake the generation time to be 2 days ago and
// force it to expire.
logger_->SetSessionIdGenerationTimeForTest(base::TimeTicks::Now() -
base::Days(2));
protocol::PerformanceTracker perf_tracker;
logger_->LogStatistics(perf_tracker);
EXPECT_NE(last_id, logger_->session_id());
}
} // namespace remoting

@ -1,73 +0,0 @@
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_CLIENT_USER_INTERFACE_H_
#define REMOTING_CLIENT_CLIENT_USER_INTERFACE_H_
#include <memory>
#include <string>
#include "remoting/protocol/connection_to_host.h"
namespace webrtc {
class DesktopSize;
class DesktopVector;
} // namespace webrtc
namespace remoting {
namespace protocol {
class ClipboardStub;
class CursorShapeStub;
class ExtensionMessage;
class KeyboardLayoutStub;
class PairingResponse;
} // namespace protocol
// ClientUserInterface is an interface that must be implemented by
// applications embedding the Chromoting client, to provide client's user
// interface.
//
// TODO(sergeyu): Cleanup this interface, see crbug.com/138108 .
class ClientUserInterface {
public:
virtual ~ClientUserInterface() {}
// Record the update the state of the connection, updating the UI as needed.
virtual void OnConnectionState(protocol::ConnectionToHost::State state,
protocol::ErrorCode error) = 0;
virtual void OnConnectionReady(bool ready) = 0;
virtual void OnRouteChanged(const std::string& channel_name,
const protocol::TransportRoute& route) = 0;
// Passes the final set of capabilities negotiated between the client and host
// to the application.
virtual void SetCapabilities(const std::string& capabilities) = 0;
// Passes a pairing response message to the client.
virtual void SetPairingResponse(
const protocol::PairingResponse& pairing_response) = 0;
// Deliver an extension message from the host to the client.
virtual void DeliverHostMessage(
const protocol::ExtensionMessage& message) = 0;
// Notify the client about screen dimensions. The |size| is in physical
// pixels.
virtual void SetDesktopSize(const webrtc::DesktopSize& size,
const webrtc::DesktopVector& dpi) = 0;
// Get the view's ClipboardStub implementation.
virtual protocol::ClipboardStub* GetClipboardStub() = 0;
// Get the view's CursorShapeStub implementation.
virtual protocol::CursorShapeStub* GetCursorShapeStub() = 0;
// Get the view's KeyboardLayoutStub implementation.
virtual protocol::KeyboardLayoutStub* GetKeyboardLayoutStub() = 0;
};
} // namespace remoting
#endif // REMOTING_CLIENT_CLIENT_USER_INTERFACE_H_

@ -1,17 +0,0 @@
// Copyright 2017 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/client/connect_to_host_info.h"
namespace remoting {
ConnectToHostInfo::ConnectToHostInfo() = default;
ConnectToHostInfo::ConnectToHostInfo(const ConnectToHostInfo& other) = default;
ConnectToHostInfo::ConnectToHostInfo(ConnectToHostInfo&& other) = default;
ConnectToHostInfo::~ConnectToHostInfo() = default;
ConnectToHostInfo& ConnectToHostInfo::operator=(
const ConnectToHostInfo& other) = default;
} // namespace remoting

@ -1,41 +0,0 @@
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_CONNECT_TO_HOST_INFO_H_
#define REMOTING_CLIENT_CONNECT_TO_HOST_INFO_H_
#include <string>
#include "remoting/base/chromoting_event.h"
namespace remoting {
struct ConnectToHostInfo {
ConnectToHostInfo();
ConnectToHostInfo(const ConnectToHostInfo& other);
ConnectToHostInfo(ConnectToHostInfo&& other);
~ConnectToHostInfo();
ConnectToHostInfo& operator=(const ConnectToHostInfo& other);
std::string username;
std::string auth_token;
std::string host_jid;
std::string host_ftl_id;
std::string host_id;
std::string host_pubkey;
std::string pairing_id;
std::string pairing_secret;
std::string capabilities;
std::string flags;
std::string host_version;
std::string host_os;
std::string host_os_version;
ChromotingEvent::SessionEntryPoint session_entry_point =
ChromotingEvent::SessionEntryPoint::CONNECT_BUTTON;
};
} // namespace remoting
#endif // REMOTING_CLIENT_CONNECT_TO_HOST_INFO_H_

@ -1,28 +0,0 @@
// Copyright 2016 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/client/cursor_shape_stub_proxy.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/task/single_thread_task_runner.h"
#include "remoting/proto/control.pb.h"
namespace remoting {
CursorShapeStubProxy::CursorShapeStubProxy(
base::WeakPtr<protocol::CursorShapeStub> stub,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: stub_(stub), task_runner_(task_runner) {}
CursorShapeStubProxy::~CursorShapeStubProxy() = default;
void CursorShapeStubProxy::SetCursorShape(
const protocol::CursorShapeInfo& cursor_shape) {
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&protocol::CursorShapeStub::SetCursorShape,
stub_, cursor_shape));
}
} // namespace remoting

@ -1,38 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_CURSOR_SHAPE_STUB_PROXY_H_
#define REMOTING_CLIENT_CURSOR_SHAPE_STUB_PROXY_H_
#include "base/memory/weak_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "remoting/protocol/cursor_shape_stub.h"
namespace remoting {
// A helper class to forward CursorShapeStub function calls from one thread to
// another.
class CursorShapeStubProxy : public protocol::CursorShapeStub {
public:
// Function calls will be forwarded to |stub| on the thread of |task_runner|.
CursorShapeStubProxy(
base::WeakPtr<protocol::CursorShapeStub> stub,
scoped_refptr<base::SingleThreadTaskRunner> task_runner);
CursorShapeStubProxy(const CursorShapeStubProxy&) = delete;
CursorShapeStubProxy& operator=(const CursorShapeStubProxy&) = delete;
~CursorShapeStubProxy() override;
// CursorShapeStub override.
void SetCursorShape(const protocol::CursorShapeInfo& cursor_shape) override;
private:
base::WeakPtr<protocol::CursorShapeStub> stub_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
};
} // namespace remoting
#endif // REMOTING_CLIENT_CURSOR_SHAPE_STUB_PROXY_H_

@ -1,101 +0,0 @@
# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# TODO(yuweih): This doesn't build on CrOS. crbug.com/869578
source_set("display") {
sources = [
"canvas.h",
"drawable.h",
"gl_canvas.cc",
"gl_canvas.h",
"gl_cursor.cc",
"gl_cursor.h",
"gl_cursor_feedback.cc",
"gl_cursor_feedback.h",
"gl_cursor_feedback_texture.cc",
"gl_cursor_feedback_texture.h",
"gl_desktop.cc",
"gl_desktop.h",
"gl_helpers.cc",
"gl_helpers.h",
"gl_math.cc",
"gl_math.h",
"gl_render_layer.cc",
"gl_render_layer.h",
"gl_renderer.cc",
"gl_renderer.h",
"renderer_proxy.cc",
"renderer_proxy.h",
"sys_opengl.h",
]
public_deps = [ "//remoting/proto" ]
deps = [
"//base",
"//remoting/base",
"//remoting/client/ui:ui_manipulation",
"//third_party/angle:includes",
"//third_party/libyuv",
"//third_party/protobuf:protobuf_lite",
"//third_party/webrtc_overrides:webrtc_component",
]
if (is_android) {
libs = [ "GLESv2" ]
}
if (is_linux || is_chromeos) {
libs = [ "GL" ]
}
if (is_mac) {
frameworks = [ "OpenGL.framework" ]
defines = [ "GL_SILENCE_DEPRECATION" ]
}
if (is_ios) {
frameworks = [
"GLKit.framework",
"OpenGLES.framework",
]
# TODO(crbug.com/40589733) fix for OpenGLES deprecation.
defines = [ "GLES_SILENCE_DEPRECATION" ]
}
if (is_win) {
deps += [ "//third_party/angle:libGLESv2" ]
}
}
if (is_win) {
# Windows clang builder fails to link the test binary with ANGLE GLESv2.
# crbug.com/642027
group("unit_tests") {
deps = []
}
} else {
source_set("unit_tests") {
testonly = true
sources = [
"fake_canvas.cc",
"fake_canvas.h",
"gl_renderer_unittest.cc",
]
configs += [ "//remoting/build/config:version" ]
deps = [
":display",
"//base",
"//base/test:test_support",
"//remoting/proto",
"//testing/gmock",
"//testing/gtest",
"//third_party/webrtc_overrides:webrtc_component",
]
}
}

@ -1,77 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DISPLAY_CANVAS_H_
#define REMOTING_CLIENT_DISPLAY_CANVAS_H_
#include <array>
#include "base/memory/weak_ptr.h"
namespace remoting {
// This class holds zoom and pan configurations of the canvas and is used to
// draw textures on the canvas.
class Canvas {
public:
Canvas() {}
Canvas(const Canvas&) = delete;
Canvas& operator=(const Canvas&) = delete;
virtual ~Canvas() {}
// Clears the frame.
virtual void Clear() = 0;
// Sets the transformation matrix. This matrix defines how the canvas should
// be shown on the view.
// 3 by 3 transformation matrix, [ m0, m1, m2, m3, m4, m5, m6, m7, m8 ].
// The matrix will be multiplied with the positions (with projective space,
// (x, y, 1)) to draw the textures with the right zoom and pan configuration.
//
// | m0, m1, m2, | | x |
// | m3, m4, m5, | * | y |
// | m6, m7, m8 | | 1 |
//
// For a typical transformation matrix such that m1=m3=m6=m7=0 and m8=1, m0
// and m4 defines the scaling factor of the canvas and m2 and m5 defines the
// offset of the upper-left corner in pixel.
virtual void SetTransformationMatrix(const std::array<float, 9>& matrix) = 0;
// Sets the size of the view in pixels such that it fills up the the whole
// viewport.
// Note that this only affects the transformation matrix. It doesn't affect
// how the viewport is rendered on the screen.
virtual void SetViewSize(int width, int height) = 0;
// Draws the texture on the canvas. Nothing will happen if
// SetNormalizedTransformation() has not been called.
// vertex_buffer: reference to the 2x4x2 float vertex buffer.
// [ four (x, y) position of the texture vertices in pixel
// with respect to the canvas,
// four (x, y) position of the vertices in percentage
// defining the visible area of the texture ]
// alpha_multiplier: Will be multiplied with the alpha channel of the texture.
// Passing 1 means no change of the transparency of the
// texture.
virtual void DrawTexture(int texture_id,
int texture_handle,
int vertex_buffer,
float alpha_multiplier) = 0;
// Version if applicable to implementation. Default 0 if unused.
virtual int GetVersion() const = 0;
// Returns the maximum texture resolution limitation. Neither the width nor
// the height of the texture can exceed this limitation.
virtual int GetMaxTextureSize() const = 0;
// Intended to be given to a Drawable to draw onto.
virtual base::WeakPtr<Canvas> GetWeakPtr() = 0;
};
} // namespace remoting
#endif // REMOTING_CLIENT_DISPLAY_CANVAS_H_

@ -1,49 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DISPLAY_DRAWABLE_H_
#define REMOTING_CLIENT_DISPLAY_DRAWABLE_H_
#include "base/memory/weak_ptr.h"
namespace remoting {
class Canvas;
// Interface for drawing on a Canvas from a renderer.
class Drawable {
public:
Drawable() {}
Drawable(const Drawable&) = delete;
Drawable& operator=(const Drawable&) = delete;
virtual ~Drawable() {}
// Sets the canvas on which the object will be drawn.
// If |canvas| is nullptr, nothing will happen when calling Draw().
virtual void SetCanvas(base::WeakPtr<Canvas> canvas) = 0;
// Draws the object on the canvas.
// Returns true if there is a pending next frame.
virtual bool Draw() = 0;
// Used for the renderer to keep a stack of drawables.
virtual base::WeakPtr<Drawable> GetWeakPtr() = 0;
// ZIndex is a recommendation for Z Index of drawable components.
enum ZIndex {
DESKTOP = 100,
CURSOR_FEEDBACK = 200,
CURSOR = 300,
};
// A higher Z Index should be draw ontop of a lower z index. Elements with
// the same Z Index should draw in order inserted into the renderer.
virtual int GetZIndex() = 0;
};
} // namespace remoting
#endif // REMOTING_CLIENT_DISPLAY_DRAWABLE_H_

@ -1,39 +0,0 @@
// Copyright 2016 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/client/display/fake_canvas.h"
namespace remoting {
FakeCanvas::FakeCanvas() {}
FakeCanvas::~FakeCanvas() {
DCHECK(thread_checker_.CalledOnValidThread());
}
void FakeCanvas::Clear() {}
void FakeCanvas::SetTransformationMatrix(const std::array<float, 9>& matrix) {}
void FakeCanvas::SetViewSize(int width, int height) {}
void FakeCanvas::DrawTexture(int texture_id,
int texture_handle,
int vertex_buffer,
float alpha_multiplier) {}
int FakeCanvas::GetVersion() const {
return 0;
}
int FakeCanvas::GetMaxTextureSize() const {
return 0;
}
base::WeakPtr<Canvas> FakeCanvas::GetWeakPtr() {
DCHECK(thread_checker_.CalledOnValidThread());
return weak_factory_.GetWeakPtr();
}
} // namespace remoting

@ -1,44 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DISPLAY_FAKE_CANVAS_H_
#define REMOTING_CLIENT_DISPLAY_FAKE_CANVAS_H_
#include <array>
#include "base/threading/thread_checker.h"
#include "remoting/client/display/gl_canvas.h"
#include "remoting/client/display/sys_opengl.h"
namespace remoting {
class FakeCanvas : public Canvas {
public:
FakeCanvas();
FakeCanvas(const FakeCanvas&) = delete;
FakeCanvas& operator=(const FakeCanvas&) = delete;
~FakeCanvas() override;
// Drawable implementation.
void Clear() override;
void SetTransformationMatrix(const std::array<float, 9>& matrix) override;
void SetViewSize(int width, int height) override;
void DrawTexture(int texture_id,
int texture_handle,
int vertex_buffer,
float alpha_multiplier) override;
int GetVersion() const override;
int GetMaxTextureSize() const override;
base::WeakPtr<Canvas> GetWeakPtr() override;
private:
base::ThreadChecker thread_checker_;
base::WeakPtrFactory<Canvas> weak_factory_{this};
};
} // namespace remoting
#endif // REMOTING_CLIENT_DISPLAY_FAKE_CANVAS_H_

@ -1,164 +0,0 @@
// Copyright 2016 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/client/display/gl_canvas.h"
#include "base/check.h"
#include "remoting/client/display/gl_helpers.h"
#include "remoting/client/display/gl_math.h"
namespace {
const int kVertexSize = 2;
const int kVertexCount = 4;
const char kTexCoordToViewVert[] =
// Region of the texture to be used (normally the whole texture).
"varying vec2 v_texCoord;\n"
"attribute vec2 a_texCoord;\n"
// Position to draw the texture on the canvas.
"attribute vec2 a_position;\n"
// Defines the zoom and pan configurations of the canvas.
"uniform mat3 u_transform;\n"
// Size of the view in pixel.
"uniform vec2 u_viewSize;\n"
// This matrix translates normalized texture coordinates
// ([0, 1] starting at upper-left corner) to the view coordinates
// ([-1, 1] starting at the center of the screen).
// Note that the matrix is defined in column-major order.
"const mat3 tex_to_view = mat3(2, 0, 0,\n"
" 0, -2, 0,\n"
" -1, 1, 0);\n"
"void main() {\n"
" v_texCoord = a_texCoord;\n"
// Transforms coordinates related to the canvas to coordinates
// related to the view.
" vec3 trans_position = u_transform * vec3(a_position, 1.0);\n"
// Normalize the position by the size of the view.
" trans_position.xy /= u_viewSize;\n"
// Transforms texture coordinates to view coordinates and adds
// projection component 1.
" gl_Position = vec4(tex_to_view * trans_position, 1.0);\n"
"}";
const char kDrawTexFrag[] =
"precision mediump float;\n"
// Region on the texture to be used (normally the whole texture).
"varying vec2 v_texCoord;\n"
"uniform sampler2D u_texture;\n"
"uniform float u_alpha_multiplier;\n"
"void main() {\n"
" gl_FragColor = texture2D(u_texture, v_texCoord);\n"
" gl_FragColor.a *= u_alpha_multiplier;\n"
"}";
} // namespace
namespace remoting {
GlCanvas::GlCanvas(int gl_version) : gl_version_(gl_version) {
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size_);
vertex_shader_ = CompileShader(GL_VERTEX_SHADER, kTexCoordToViewVert);
fragment_shader_ = CompileShader(GL_FRAGMENT_SHADER, kDrawTexFrag);
program_ = CreateProgram(vertex_shader_, fragment_shader_);
glUseProgram(program_);
transform_location_ = glGetUniformLocation(program_, "u_transform");
view_size_location_ = glGetUniformLocation(program_, "u_viewSize");
texture_location_ = glGetUniformLocation(program_, "u_texture");
alpha_multiplier_location_ =
glGetUniformLocation(program_, "u_alpha_multiplier");
position_location_ = glGetAttribLocation(program_, "a_position");
tex_cord_location_ = glGetAttribLocation(program_, "a_texCoord");
glEnableVertexAttribArray(position_location_);
glEnableVertexAttribArray(tex_cord_location_);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
GlCanvas::~GlCanvas() {
DCHECK(thread_checker_.CalledOnValidThread());
glDisable(GL_BLEND);
glDisableVertexAttribArray(tex_cord_location_);
glDisableVertexAttribArray(position_location_);
glDeleteProgram(program_);
glDeleteShader(vertex_shader_);
glDeleteShader(fragment_shader_);
}
void GlCanvas::Clear() {
#ifndef NDEBUG
// Set the background clear color to bright green for debugging purposes.
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
#else
// Set the background clear color to black.
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
#endif
glClear(GL_COLOR_BUFFER_BIT);
}
void GlCanvas::SetTransformationMatrix(const std::array<float, 9>& matrix) {
DCHECK(thread_checker_.CalledOnValidThread());
std::array<float, 9> transposed_matrix = matrix;
TransposeTransformationMatrix(&transposed_matrix);
glUniformMatrix3fv(transform_location_, 1, GL_FALSE,
transposed_matrix.data());
transformation_set_ = true;
}
void GlCanvas::SetViewSize(int width, int height) {
DCHECK(width > 0 && height > 0);
float view_size[2]{static_cast<float>(width), static_cast<float>(height)};
glUniform2fv(view_size_location_, 1, view_size);
view_size_set_ = true;
}
void GlCanvas::DrawTexture(int texture_id,
int texture_handle,
int vertex_buffer,
float alpha_multiplier) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!view_size_set_ || !transformation_set_) {
return;
}
glActiveTexture(GL_TEXTURE0 + texture_id);
glBindTexture(GL_TEXTURE_2D, texture_handle);
glUniform1i(texture_location_, texture_id);
glUniform1f(alpha_multiplier_location_, alpha_multiplier);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
glVertexAttribPointer(position_location_, kVertexSize, GL_FLOAT, GL_FALSE, 0,
0);
glVertexAttribPointer(
tex_cord_location_, kVertexSize, GL_FLOAT, GL_FALSE, 0,
reinterpret_cast<void*>(kVertexSize * kVertexCount * sizeof(GLfloat)));
glDrawArrays(GL_TRIANGLE_STRIP, 0, kVertexCount);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
}
int GlCanvas::GetVersion() const {
return gl_version_;
}
int GlCanvas::GetMaxTextureSize() const {
return max_texture_size_;
}
base::WeakPtr<Canvas> GlCanvas::GetWeakPtr() {
DCHECK(thread_checker_.CalledOnValidThread());
return weak_factory_.GetWeakPtr();
}
} // namespace remoting

@ -1,68 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DISPLAY_GL_CANVAS_H_
#define REMOTING_CLIENT_DISPLAY_GL_CANVAS_H_
#include <array>
#include "base/threading/thread_checker.h"
#include "remoting/client/display/canvas.h"
#include "remoting/client/display/sys_opengl.h"
namespace remoting {
// This class holds zoom and pan configurations of the canvas and is used to
// draw textures on the canvas.
// Must be constructed after the OpenGL surface is created and destroyed before
// the surface is destroyed.
class GlCanvas : public Canvas {
public:
// gl_version: version number of the OpenGL ES context. Either 2 or 3.
GlCanvas(int gl_version);
GlCanvas(const GlCanvas&) = delete;
GlCanvas& operator=(const GlCanvas&) = delete;
~GlCanvas() override;
// Canvas implementation.
void Clear() override;
void SetTransformationMatrix(const std::array<float, 9>& matrix) override;
void SetViewSize(int width, int height) override;
void DrawTexture(int texture_id,
int texture_handle,
int vertex_buffer,
float alpha_multiplier) override;
int GetVersion() const override;
int GetMaxTextureSize() const override;
base::WeakPtr<Canvas> GetWeakPtr() override;
private:
int gl_version_;
int max_texture_size_ = 0;
bool transformation_set_ = false;
bool view_size_set_ = false;
// Handles.
GLuint vertex_shader_;
GLuint fragment_shader_;
GLuint program_;
// Locations of the corresponding shader attributes.
GLuint transform_location_;
GLuint view_size_location_;
GLuint texture_location_;
GLuint alpha_multiplier_location_;
GLuint position_location_;
GLuint tex_cord_location_;
base::ThreadChecker thread_checker_;
base::WeakPtrFactory<Canvas> weak_factory_{this};
};
} // namespace remoting
#endif // REMOTING_CLIENT_DISPLAY_GL_CANVAS_H_

@ -1,116 +0,0 @@
// Copyright 2016 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/client/display/gl_cursor.h"
#include <memory>
#include "remoting/base/util.h"
#include "remoting/client/display/gl_canvas.h"
#include "remoting/client/display/gl_math.h"
#include "remoting/client/display/gl_render_layer.h"
#include "remoting/client/display/gl_texture_ids.h"
#include "remoting/proto/control.pb.h"
#include "third_party/libyuv/include/libyuv/convert_argb.h"
namespace remoting {
namespace {
const int kDefaultCursorDataSize = 32 * 32 * GlRenderLayer::kBytesPerPixel;
} // namespace
GlCursor::GlCursor() {}
GlCursor::~GlCursor() {
DCHECK(thread_checker_.CalledOnValidThread());
}
void GlCursor::SetCursorShape(const protocol::CursorShapeInfo& cursor_shape) {
int data_size = cursor_shape.width() * cursor_shape.height() *
GlRenderLayer::kBytesPerPixel;
if (current_cursor_data_size_ < data_size) {
current_cursor_data_size_ =
kDefaultCursorDataSize > data_size ? kDefaultCursorDataSize : data_size;
current_cursor_data_.reset(new uint8_t[current_cursor_data_size_]);
}
int stride = cursor_shape.width() * GlRenderLayer::kBytesPerPixel;
libyuv::ABGRToARGB(
reinterpret_cast<const uint8_t*>(cursor_shape.data().data()), stride,
current_cursor_data_.get(), stride, cursor_shape.width(),
cursor_shape.height());
bool size_changed = current_cursor_width_ != cursor_shape.width() ||
current_cursor_height_ != cursor_shape.height();
current_cursor_width_ = cursor_shape.width();
current_cursor_height_ = cursor_shape.height();
current_cursor_hotspot_x_ = cursor_shape.hotspot_x();
current_cursor_hotspot_y_ = cursor_shape.hotspot_y();
SetCurrentCursorShape(size_changed);
SetCursorPosition(cursor_x_, cursor_y_);
}
void GlCursor::SetCursorPosition(float x, float y) {
cursor_x_ = x;
cursor_y_ = y;
if (!current_cursor_data_) {
return;
}
std::array<float, 8> positions;
FillRectangleVertexPositions(
x - current_cursor_hotspot_x_, y - current_cursor_hotspot_y_,
current_cursor_width_, current_cursor_height_, &positions);
if (layer_) {
layer_->SetVertexPositions(positions);
}
}
void GlCursor::SetCursorVisible(bool visible) {
visible_ = visible;
}
void GlCursor::SetCanvas(base::WeakPtr<Canvas> canvas) {
if (!canvas) {
layer_.reset();
return;
}
layer_ = std::make_unique<GlRenderLayer>(kGlCursorTextureId, canvas);
if (current_cursor_data_) {
SetCurrentCursorShape(true);
}
SetCursorPosition(cursor_x_, cursor_y_);
}
bool GlCursor::Draw() {
DCHECK(thread_checker_.CalledOnValidThread());
if (layer_ && current_cursor_data_ && visible_) {
layer_->Draw(1.f);
}
return false;
}
int GlCursor::GetZIndex() {
return Drawable::ZIndex::CURSOR;
}
void GlCursor::SetCurrentCursorShape(bool size_changed) {
if (layer_) {
if (size_changed) {
layer_->SetTexture(current_cursor_data_.get(), current_cursor_width_,
current_cursor_height_, 0);
} else {
layer_->UpdateTexture(current_cursor_data_.get(), 0, 0,
current_cursor_width_, current_cursor_width_, 0);
}
}
}
base::WeakPtr<Drawable> GlCursor::GetWeakPtr() {
DCHECK(thread_checker_.CalledOnValidThread());
return weak_factory_.GetWeakPtr();
}
} // namespace remoting

@ -1,72 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DISPLAY_GL_CURSOR_H_
#define REMOTING_CLIENT_DISPLAY_GL_CURSOR_H_
#include <array>
#include <cstdint>
#include <memory>
#include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h"
#include "remoting/client/display/drawable.h"
namespace remoting {
namespace protocol {
class CursorShapeInfo;
} // namespace protocol
class Canvas;
class GlRenderLayer;
// This class draws the cursor on the canvas.
class GlCursor : public Drawable {
public:
GlCursor();
GlCursor(const GlCursor&) = delete;
GlCursor& operator=(const GlCursor&) = delete;
~GlCursor() override;
void SetCursorShape(const protocol::CursorShapeInfo& cursor_shape);
// Sets the cursor hotspot positions. Does nothing if the cursor shape or the
// canvas size has not been set.
void SetCursorPosition(float x, float y);
// Draw() will do nothing if cursor is not visible.
void SetCursorVisible(bool visible);
// Drawable implementation.
void SetCanvas(base::WeakPtr<Canvas> canvas) override;
bool Draw() override;
int GetZIndex() override;
base::WeakPtr<Drawable> GetWeakPtr() override;
private:
void SetCurrentCursorShape(bool size_changed);
bool visible_ = true;
std::unique_ptr<GlRenderLayer> layer_;
std::unique_ptr<uint8_t[]> current_cursor_data_;
int current_cursor_data_size_ = 0;
int current_cursor_width_ = 0;
int current_cursor_height_ = 0;
int current_cursor_hotspot_x_ = 0;
int current_cursor_hotspot_y_ = 0;
float cursor_x_ = 0;
float cursor_y_ = 0;
base::ThreadChecker thread_checker_;
base::WeakPtrFactory<Drawable> weak_factory_{this};
};
} // namespace remoting
#endif // REMOTING_CLIENT_DISPLAY_GL_CURSOR_H_

@ -1,105 +0,0 @@
// Copyright 2016 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/client/display/gl_cursor_feedback.h"
#include <math.h>
#include <array>
#include <memory>
#include "base/check.h"
#include "remoting/client/display/canvas.h"
#include "remoting/client/display/gl_cursor_feedback_texture.h"
#include "remoting/client/display/gl_math.h"
#include "remoting/client/display/gl_render_layer.h"
#include "remoting/client/display/gl_texture_ids.h"
namespace {
const float kAnimationDurationMs = 300.f;
// This function is for calculating the size of the feedback animation circle at
// the moment when the animation progress is |progress|.
// |progress|: [0, 1], indicating the progress of the animation.
// Returns a coefficient in [0, 1]. It will be multiplied with the maximum
// diameter of the feedback circle to get the current diameter of the feedback
// circle.
float GetExpansionCoefficient(float progress) {
DCHECK(progress >= 0 && progress <= 1);
// Decelerating expansion. This is conforming to the material design spec.
// More time will be spent showing the larger circle and the animation will
// look more rapid given the same time duration.
auto get_unnormalized_coeff = [](float progress) {
static const float kExpansionBase = 400.f;
return 1.f - pow(kExpansionBase, -progress);
};
static const float kExpansionNormalization = get_unnormalized_coeff(1);
return get_unnormalized_coeff(progress) / kExpansionNormalization;
}
} // namespace
namespace remoting {
GlCursorFeedback::GlCursorFeedback() {}
GlCursorFeedback::~GlCursorFeedback() {
DCHECK(thread_checker_.CalledOnValidThread());
}
void GlCursorFeedback::SetCanvas(base::WeakPtr<Canvas> canvas) {
if (!canvas) {
layer_.reset();
return;
}
layer_ = std::make_unique<GlRenderLayer>(kGlCursorFeedbackTextureId, canvas);
GlCursorFeedbackTexture* texture = GlCursorFeedbackTexture::GetInstance();
layer_->SetTexture(texture->GetTexture().data(),
GlCursorFeedbackTexture::kTextureWidth,
GlCursorFeedbackTexture::kTextureWidth, 0);
}
void GlCursorFeedback::StartAnimation(float x, float y, float diameter) {
cursor_x_ = x;
cursor_y_ = y;
max_diameter_ = diameter;
animation_start_time_ = base::TimeTicks::Now();
}
bool GlCursorFeedback::Draw() {
DCHECK(thread_checker_.CalledOnValidThread());
if (!layer_ || animation_start_time_.is_null()) {
return false;
}
float progress =
(base::TimeTicks::Now() - animation_start_time_).InMilliseconds() /
kAnimationDurationMs;
if (progress > 1) {
animation_start_time_ = base::TimeTicks();
return false;
}
float diameter = GetExpansionCoefficient(progress) * max_diameter_;
std::array<float, 8> positions;
FillRectangleVertexPositions(cursor_x_ - diameter / 2,
cursor_y_ - diameter / 2, diameter, diameter,
&positions);
layer_->SetVertexPositions(positions);
// Linear fade-out.
layer_->Draw(1.f - progress);
return true;
}
int GlCursorFeedback::GetZIndex() {
return Drawable::ZIndex::CURSOR_FEEDBACK;
}
base::WeakPtr<Drawable> GlCursorFeedback::GetWeakPtr() {
DCHECK(thread_checker_.CalledOnValidThread());
return weak_factory_.GetWeakPtr();
}
} // namespace remoting

@ -1,52 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DISPLAY_GL_CURSOR_FEEDBACK_H_
#define REMOTING_CLIENT_DISPLAY_GL_CURSOR_FEEDBACK_H_
#include <cstdint>
#include <memory>
#include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "remoting/client/display/drawable.h"
namespace remoting {
class Canvas;
class GlRenderLayer;
// This class draws the cursor feedback on the canvas.
class GlCursorFeedback : public Drawable {
public:
GlCursorFeedback();
GlCursorFeedback(const GlCursorFeedback&) = delete;
GlCursorFeedback& operator=(const GlCursorFeedback&) = delete;
~GlCursorFeedback() override;
void StartAnimation(float x, float y, float diameter);
// Drawable implementation.
void SetCanvas(base::WeakPtr<Canvas> canvas) override;
bool Draw() override;
int GetZIndex() override;
base::WeakPtr<Drawable> GetWeakPtr() override;
private:
std::unique_ptr<GlRenderLayer> layer_;
float max_diameter_ = 0;
float cursor_x_ = 0;
float cursor_y_ = 0;
base::TimeTicks animation_start_time_;
base::ThreadChecker thread_checker_;
base::WeakPtrFactory<Drawable> weak_factory_{this};
};
} // namespace remoting
#endif // REMOTING_CLIENT_DISPLAY_GL_CURSOR_FEEDBACK_H_

@ -1,116 +0,0 @@
// Copyright 2016 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/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "remoting/client/display/gl_cursor_feedback_texture.h"
#include <cmath>
#include "base/notreached.h"
#include "remoting/client/display/gl_render_layer.h"
namespace remoting {
namespace {
const int kColorRingsCount = 5;
const int kFeedbackTexturePixelDiameter = 512;
const int kFeedbackTexturePixelRadius = kFeedbackTexturePixelDiameter / 2;
// RGBA8888 colors. From inside to outside.
const uint8_t kFeedbackRingColors[kColorRingsCount]
[GlRenderLayer::kBytesPerPixel] = {
{0x0, 0x0, 0x0, 0x7f}, // Black
{0x0, 0x0, 0x0, 0x7f}, // Black
{0xff, 0xff, 0xff, 0x7f}, // White
{0xff, 0xff, 0xff, 0x7f}, // White
{0xff, 0xff, 0xff, 0} // Transparent White
};
const float kFeedbackRadiusStops[kColorRingsCount] = {0.0f, 0.85f, 0.9f, 0.95f,
1.0f};
uint32_t GetColorByRadius(float radius) {
int ring_index = kColorRingsCount - 1;
// Find first radius stop that is not smaller than current radius.
while (radius < kFeedbackRadiusStops[ring_index] && ring_index >= 0) {
ring_index--;
}
if (ring_index < 0) {
NOTREACHED();
}
if (ring_index == kColorRingsCount - 1) {
// Area outside the circle. Just use the outermost color.
return *reinterpret_cast<const uint32_t*>(kFeedbackRingColors[ring_index]);
}
const uint8_t* first_color = kFeedbackRingColors[ring_index];
const uint8_t* second_color = kFeedbackRingColors[ring_index + 1];
float first_radius = kFeedbackRadiusStops[ring_index];
float second_radius = kFeedbackRadiusStops[ring_index + 1];
uint8_t progress =
(radius - first_radius) * 256 / (second_radius - first_radius);
uint32_t color;
uint8_t* color_ptr = reinterpret_cast<uint8_t*>(&color);
for (int i = 0; i < GlRenderLayer::kBytesPerPixel; i++) {
color_ptr[i] =
(first_color[i] * (256 - progress) + second_color[i] * progress) / 256;
}
return color;
}
void FillFeedbackTexture(uint32_t* texture) {
for (int x = 0; x < kFeedbackTexturePixelRadius; x++) {
for (int y = 0; y <= x; y++) {
float radius = sqrt(x * x + y * y) / kFeedbackTexturePixelRadius;
uint32_t color = GetColorByRadius(radius);
int x1 = kFeedbackTexturePixelRadius + x;
int x2 = kFeedbackTexturePixelRadius - 1 - x;
int y1 = kFeedbackTexturePixelRadius + y;
int y2 = kFeedbackTexturePixelRadius - 1 - y;
texture[x1 + y1 * kFeedbackTexturePixelDiameter] = color;
texture[x1 + y2 * kFeedbackTexturePixelDiameter] = color;
texture[x2 + y1 * kFeedbackTexturePixelDiameter] = color;
texture[x2 + y2 * kFeedbackTexturePixelDiameter] = color;
texture[y1 + x1 * kFeedbackTexturePixelDiameter] = color;
texture[y1 + x2 * kFeedbackTexturePixelDiameter] = color;
texture[y2 + x1 * kFeedbackTexturePixelDiameter] = color;
texture[y2 + x2 * kFeedbackTexturePixelDiameter] = color;
}
}
}
} // namespace
// static
const int GlCursorFeedbackTexture::kTextureWidth =
kFeedbackTexturePixelDiameter;
GlCursorFeedbackTexture* GlCursorFeedbackTexture::GetInstance() {
return base::Singleton<GlCursorFeedbackTexture>::get();
}
const std::vector<uint8_t>& GlCursorFeedbackTexture::GetTexture() const {
return texture_;
}
GlCursorFeedbackTexture::GlCursorFeedbackTexture() {
texture_.resize(kTextureWidth * kTextureWidth *
GlRenderLayer::kBytesPerPixel);
FillFeedbackTexture(reinterpret_cast<uint32_t*>(texture_.data()));
}
GlCursorFeedbackTexture::~GlCursorFeedbackTexture() = default;
} // namespace remoting

@ -1,37 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DISPLAY_GL_CURSOR_FEEDBACK_TEXTURE_H_
#define REMOTING_CLIENT_DISPLAY_GL_CURSOR_FEEDBACK_TEXTURE_H_
#include <vector>
#include "base/memory/singleton.h"
namespace remoting {
// This is a singleton for generating and holding the the cursor feedback
// texture.
class GlCursorFeedbackTexture {
public:
static const int kTextureWidth;
static GlCursorFeedbackTexture* GetInstance();
GlCursorFeedbackTexture(const GlCursorFeedbackTexture&) = delete;
GlCursorFeedbackTexture& operator=(const GlCursorFeedbackTexture&) = delete;
const std::vector<uint8_t>& GetTexture() const;
private:
GlCursorFeedbackTexture();
~GlCursorFeedbackTexture();
friend struct base::DefaultSingletonTraits<GlCursorFeedbackTexture>;
std::vector<uint8_t> texture_;
};
} // namespace remoting
#endif // REMOTING_CLIENT_DISPLAY_GL_CURSOR_FEEDBACK_TEXTURE_H_

@ -1,141 +0,0 @@
// Copyright 2016 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/client/display/gl_desktop.h"
#include "base/check.h"
#include "base/memory/ptr_util.h"
#include "remoting/client/display/canvas.h"
#include "remoting/client/display/gl_math.h"
#include "remoting/client/display/gl_render_layer.h"
#include "remoting/client/display/gl_texture_ids.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_region.h"
namespace remoting {
namespace {
void UpdateDesktopRegion(const webrtc::DesktopFrame& frame,
const webrtc::DesktopRegion& region,
const webrtc::DesktopRect& texture_rect,
GlRenderLayer* layer) {
for (webrtc::DesktopRegion::Iterator i(region); !i.IsAtEnd(); i.Advance()) {
const uint8_t* rect_start = frame.GetFrameDataAtPos(i.rect().top_left());
webrtc::DesktopVector update_pos =
i.rect().top_left().subtract(texture_rect.top_left());
layer->UpdateTexture(rect_start, update_pos.x(), update_pos.y(),
i.rect().width(), i.rect().height(), frame.stride());
}
}
void SetFrameForTexture(const webrtc::DesktopFrame& frame,
GlRenderLayer* layer,
const webrtc::DesktopRect& rect) {
if (!layer->is_texture_set()) {
// First frame received.
layer->SetTexture(frame.GetFrameDataAtPos(rect.top_left()), rect.width(),
rect.height(), frame.stride());
return;
}
// Incremental update.
if (frame.size().equals(rect.size())) {
// Single texture. No intersection is needed.
UpdateDesktopRegion(frame, frame.updated_region(), rect, layer);
} else {
webrtc::DesktopRegion intersected_region = frame.updated_region();
intersected_region.IntersectWith(rect);
UpdateDesktopRegion(frame, intersected_region, rect, layer);
}
}
} // namespace
struct GlDesktop::GlDesktopTextureContainer {
std::unique_ptr<GlRenderLayer> layer;
webrtc::DesktopRect rect;
};
GlDesktop::GlDesktop() {}
GlDesktop::~GlDesktop() {
DCHECK(thread_checker_.CalledOnValidThread());
}
void GlDesktop::SetCanvas(base::WeakPtr<Canvas> canvas) {
last_desktop_size_.set(0, 0);
textures_.clear();
canvas_ = canvas;
if (canvas) {
max_texture_size_ = canvas->GetMaxTextureSize();
}
}
void GlDesktop::SetVideoFrame(const webrtc::DesktopFrame& frame) {
if (!canvas_) {
return;
}
if (!frame.size().equals(last_desktop_size_)) {
last_desktop_size_.set(frame.size().width(), frame.size().height());
ReallocateTextures(last_desktop_size_);
}
for (std::unique_ptr<GlDesktopTextureContainer>& texture : textures_) {
SetFrameForTexture(frame, texture->layer.get(), texture->rect);
}
}
bool GlDesktop::Draw() {
DCHECK(thread_checker_.CalledOnValidThread());
if (!textures_.empty() && !last_desktop_size_.is_empty()) {
for (std::unique_ptr<GlDesktopTextureContainer>& texture : textures_) {
texture->layer->Draw(1.0f);
}
}
return false;
}
int GlDesktop::GetZIndex() {
return Drawable::ZIndex::DESKTOP;
}
void GlDesktop::ReallocateTextures(const webrtc::DesktopSize& size) {
DCHECK(max_texture_size_);
DCHECK(canvas_);
textures_.clear();
int textures_per_row =
(size.width() + max_texture_size_ - 1) / max_texture_size_;
int textures_per_column =
(size.height() + max_texture_size_ - 1) / max_texture_size_;
webrtc::DesktopRect desktop_rect = webrtc::DesktopRect::MakeSize(size);
int texture_id = kGlDesktopFirstTextureId;
std::array<float, 8> positions;
for (int x = 0; x < textures_per_row; x++) {
for (int y = 0; y < textures_per_column; y++) {
webrtc::DesktopRect rect = webrtc::DesktopRect::MakeXYWH(
x * max_texture_size_, y * max_texture_size_, max_texture_size_,
max_texture_size_);
rect.IntersectWith(desktop_rect);
std::unique_ptr<GlDesktopTextureContainer> container = base::WrapUnique(
new GlDesktopTextureContainer{std::make_unique<GlRenderLayer>(
texture_id, canvas_->GetWeakPtr()),
rect});
FillRectangleVertexPositions(rect.left(), rect.top(), rect.width(),
rect.height(), &positions);
container->layer->SetVertexPositions(positions);
textures_.push_back(std::move(container));
texture_id++;
}
}
}
base::WeakPtr<Drawable> GlDesktop::GetWeakPtr() {
DCHECK(thread_checker_.CalledOnValidThread());
return weak_factory_.GetWeakPtr();
}
} // namespace remoting

@ -1,59 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DISPLAY_GL_DESKTOP_H_
#define REMOTING_CLIENT_DISPLAY_GL_DESKTOP_H_
#include <memory>
#include <vector>
#include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h"
#include "remoting/client/display/drawable.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
namespace webrtc {
class DesktopFrame;
} // namespace webrtc
namespace remoting {
class Canvas;
// This class draws the desktop on the canvas.
class GlDesktop : public Drawable {
public:
GlDesktop();
GlDesktop(const GlDesktop&) = delete;
GlDesktop& operator=(const GlDesktop&) = delete;
~GlDesktop() override;
// |frame| can be either a full frame or updated regions only frame.
void SetVideoFrame(const webrtc::DesktopFrame& frame);
// Drawable implementation.
void SetCanvas(base::WeakPtr<Canvas> canvas) override;
bool Draw() override;
int GetZIndex() override;
base::WeakPtr<Drawable> GetWeakPtr() override;
private:
struct GlDesktopTextureContainer;
void ReallocateTextures(const webrtc::DesktopSize& size);
std::vector<std::unique_ptr<GlDesktopTextureContainer>> textures_;
webrtc::DesktopSize last_desktop_size_;
int max_texture_size_ = 0;
base::WeakPtr<Canvas> canvas_ = nullptr;
base::ThreadChecker thread_checker_;
base::WeakPtrFactory<Drawable> weak_factory_{this};
};
} // namespace remoting
#endif // REMOTING_CLIENT_DISPLAY_GL_DESKTOP_H_

@ -1,85 +0,0 @@
// Copyright 2016 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/client/display/gl_helpers.h"
#include "base/logging.h"
namespace remoting {
GLuint CompileShader(GLenum shader_type, const char* shader_source) {
GLuint shader = glCreateShader(shader_type);
if (shader != 0) {
int shader_source_length = strlen(shader_source);
// Pass in the shader source.
glShaderSource(shader, 1, &shader_source, &shader_source_length);
// Compile the shader.
glCompileShader(shader);
// Get the compilation status.
GLint compile_status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
// If the compilation failed, delete the shader.
if (compile_status == GL_FALSE) {
LOG(ERROR) << "Error compiling shader: \n" << shader_source;
glDeleteShader(shader);
shader = 0;
}
}
if (shader == 0) {
LOG(FATAL) << "Error creating shader.";
}
return shader;
}
GLuint CreateProgram(GLuint vertex_shader, GLuint fragment_shader) {
GLuint program = glCreateProgram();
if (program != 0) {
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
glLinkProgram(program);
// Get the link status.
GLint link_status;
glGetProgramiv(program, GL_LINK_STATUS, &link_status);
// If the link failed, delete the program.
if (link_status == GL_FALSE) {
LOG(ERROR) << "Error compiling program.";
glDeleteProgram(program);
program = 0;
}
}
if (program == 0) {
LOG(FATAL) << "Error creating program.";
}
return program;
}
GLuint CreateTexture() {
GLuint texture;
glGenTextures(1, &texture);
if (texture == 0) {
LOG(FATAL) << "Error creating texture.";
}
return texture;
}
GLuint CreateBuffer(const void* data, int size) {
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
return buffer;
}
} // namespace remoting

@ -1,28 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DISPLAY_GL_HELPERS_H_
#define REMOTING_CLIENT_DISPLAY_GL_HELPERS_H_
#include "remoting/client/display/sys_opengl.h"
namespace remoting {
// Compiles a shader and returns the reference to the shader if it succeeds.
GLuint CompileShader(GLenum shader_type, const char* shader_source);
// Creates a program with the given reference to the vertex shader and fragment
// shader. returns the reference of the program if it succeeds.
GLuint CreateProgram(GLuint vertex_shader, GLuint fragment_shader);
// Creates and returns the texture names if it succeeds.
GLuint CreateTexture();
// Creates a GL_ARRAY_BUFFER and fills it with |data|. Returns the reference to
// the buffer.
GLuint CreateBuffer(const void* data, int size);
} // namespace remoting
#endif // REMOTING_CLIENT_DISPLAY_GL_HELPERS_H_

@ -1,56 +0,0 @@
// Copyright 2016 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/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "remoting/client/display/gl_math.h"
#include <sstream>
namespace remoting {
void TransposeTransformationMatrix(std::array<float, 9>* matrix) {
// | ??, m1, m2, | | ??, m3, m6 |
// | m3, ??, m5, | -> | m1, ??, m7 |
// | m6, m7, ?? | | m2, m5, ?? |
std::swap((*matrix)[1], (*matrix)[3]);
std::swap((*matrix)[2], (*matrix)[6]);
std::swap((*matrix)[5], (*matrix)[7]);
}
void FillRectangleVertexPositions(float left,
float top,
float width,
float height,
std::array<float, 8>* positions) {
(*positions)[0] = left;
(*positions)[1] = top;
(*positions)[2] = left;
(*positions)[3] = top + height;
(*positions)[4] = left + width;
(*positions)[5] = top;
(*positions)[6] = left + width;
(*positions)[7] = top + height;
}
std::string MatrixToString(const float* mat, int num_rows, int num_cols) {
std::ostringstream outstream;
outstream << "[\n";
for (int i = 0; i < num_rows; i++) {
for (int j = 0; j < num_cols; j++) {
outstream << mat[i * num_cols + j] << ", ";
}
outstream << "\n";
}
outstream << "]";
return outstream.str();
}
} // namespace remoting

@ -1,44 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DISPLAY_GL_MATH_H_
#define REMOTING_CLIENT_DISPLAY_GL_MATH_H_
#include <array>
#include <string>
namespace remoting {
// Transposes matrix [ m0, m1, m2, m3, m4, m5, m6, m7, m8 ]:
//
// | m0, m1, m2, | | x |
// | m3, m4, m5, | * | y |
// | m6, m7, m8 | | 1 |
//
// Into [ m0, m3, m6, m1, m4, m7, m2, m5, m8 ].
void TransposeTransformationMatrix(std::array<float, 9>* matrix);
// Given left, top, width, height of a rectangle, fills |positions| with
// coordinates of four vertices of the rectangle.
// positions: [ x_upperleft, y_upperleft, x_lowerleft, y_lowerleft,
// x_upperright, y_upperright, x_lowerright, y_lowerright ]
void FillRectangleVertexPositions(float left,
float top,
float width,
float height,
std::array<float, 8>* positions);
// Returns the string representation of the matrix for debugging.
//
// For example:
// [
// 1, 0, 0,
// 0, 1, 0,
// 0, 0, 1,
// ]
std::string MatrixToString(const float* mat, int num_rows, int num_cols);
} // namespace remoting
#endif // REMOTING_CLIENT_DISPLAY_GL_MATH_H_

@ -1,179 +0,0 @@
// Copyright 2016 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/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "remoting/client/display/gl_render_layer.h"
#include "base/check.h"
#include "remoting/client/display/gl_canvas.h"
#include "remoting/client/display/gl_helpers.h"
namespace remoting {
namespace {
// Assign texture coordinates to buffers for use in shader program.
const float kVertices[] = {
// Points order: upper-left, bottom-left, upper-right, bottom-right.
// Positions to draw the texture on the normalized canvas coordinate.
0, 0, 0, 0, 0, 0, 0, 0,
// Region of the texture to be used (normally the whole texture).
0, 0, 0, 1, 1, 0, 1, 1};
const int kDefaultUpdateBufferCapacity =
2048 * 2048 * GlRenderLayer::kBytesPerPixel;
void PackDirtyRegion(uint8_t* dest,
const uint8_t* source,
int width,
int height,
int stride) {
for (int i = 0; i < height; i++) {
memcpy(dest, source, width * GlRenderLayer::kBytesPerPixel);
source += stride;
dest += GlRenderLayer::kBytesPerPixel * width;
}
}
} // namespace
GlRenderLayer::GlRenderLayer(int texture_id, base::WeakPtr<Canvas> canvas)
: texture_id_(texture_id), canvas_(canvas) {
texture_handle_ = CreateTexture();
buffer_handle_ = CreateBuffer(kVertices, sizeof(kVertices));
}
GlRenderLayer::~GlRenderLayer() {
DCHECK(thread_checker_.CalledOnValidThread());
glDeleteBuffers(1, &buffer_handle_);
glDeleteTextures(1, &texture_handle_);
}
void GlRenderLayer::SetTexture(const uint8_t* texture,
int width,
int height,
int stride) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(width >= 0 && height >= 0);
texture_set_ = true;
glActiveTexture(GL_TEXTURE0 + texture_id_);
glBindTexture(GL_TEXTURE_2D, texture_handle_);
bool should_reset_row_length;
const void* buffer_to_update = PrepareTextureBuffer(
texture, width, height, stride, &should_reset_row_length);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
GL_UNSIGNED_BYTE, buffer_to_update);
if (should_reset_row_length) {
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
}
void GlRenderLayer::UpdateTexture(const uint8_t* subtexture,
int offset_x,
int offset_y,
int width,
int height,
int stride) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(texture_set_);
DCHECK(width >= 0 && height >= 0);
if (width == 0 || height == 0) {
// There is nothing to update.
return;
}
glActiveTexture(GL_TEXTURE0 + texture_id_);
glBindTexture(GL_TEXTURE_2D, texture_handle_);
bool should_reset_row_length;
const void* buffer_to_update = PrepareTextureBuffer(
subtexture, width, height, stride, &should_reset_row_length);
glTexSubImage2D(GL_TEXTURE_2D, 0, offset_x, offset_y, width, height, GL_RGBA,
GL_UNSIGNED_BYTE, buffer_to_update);
if (should_reset_row_length) {
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
glBindTexture(GL_TEXTURE_2D, 0);
}
void GlRenderLayer::SetVertexPositions(const std::array<float, 8>& positions) {
DCHECK(thread_checker_.CalledOnValidThread());
glBindBuffer(GL_ARRAY_BUFFER, buffer_handle_);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(kVertices) / 2, positions.data());
glBindBuffer(GL_ARRAY_BUFFER, 0);
vertex_position_set_ = true;
}
void GlRenderLayer::SetTextureVisibleArea(
const std::array<float, 8>& positions) {
DCHECK(thread_checker_.CalledOnValidThread());
glBindBuffer(GL_ARRAY_BUFFER, buffer_handle_);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(kVertices) / 2, sizeof(kVertices) / 2,
positions.data());
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void GlRenderLayer::Draw(float alpha_multiplier) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(texture_set_ && vertex_position_set_);
canvas_->DrawTexture(texture_id_, texture_handle_, buffer_handle_,
alpha_multiplier);
}
const uint8_t* GlRenderLayer::PrepareTextureBuffer(
const uint8_t* data,
int width,
int height,
int stride,
bool* should_reset_row_length) {
*should_reset_row_length = false;
bool stride_multiple_of_bytes_per_pixel = stride % kBytesPerPixel == 0;
bool loosely_packed = !stride_multiple_of_bytes_per_pixel ||
(stride > 0 && stride != kBytesPerPixel * width);
if (!loosely_packed) {
return data;
}
if (stride_multiple_of_bytes_per_pixel && canvas_->GetVersion() >= 3) {
glPixelStorei(GL_UNPACK_ROW_LENGTH, stride / kBytesPerPixel);
*should_reset_row_length = true;
return data;
}
// Doesn't support GL_UNPACK_ROW_LENGTH or stride not multiple of
// kBytesPerPixel. Manually pack the data.
int required_size = width * height * kBytesPerPixel;
if (update_buffer_size_ < required_size) {
if (required_size < kDefaultUpdateBufferCapacity) {
update_buffer_size_ = kDefaultUpdateBufferCapacity;
} else {
update_buffer_size_ = required_size;
}
update_buffer_.reset(new uint8_t[update_buffer_size_]);
}
PackDirtyRegion(update_buffer_.get(), data, width, height, stride);
return update_buffer_.get();
}
} // namespace remoting

@ -1,104 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DISPLAY_GL_RENDER_LAYER_H_
#define REMOTING_CLIENT_DISPLAY_GL_RENDER_LAYER_H_
#include <array>
#include <memory>
#include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h"
#include "remoting/client/display/sys_opengl.h"
namespace remoting {
class Canvas;
// This class is for drawing a texture on the canvas. Must be deleted before the
// canvas is deleted.
class GlRenderLayer {
public:
static const int kBytesPerPixel = 4;
// texture_id: An integer in range [0, GL_MAX_TEXTURE_IMAGE_UNITS], defining
// which slot to store the texture.
GlRenderLayer(int texture_id, base::WeakPtr<Canvas> canvas);
GlRenderLayer(const GlRenderLayer&) = delete;
GlRenderLayer& operator=(const GlRenderLayer&) = delete;
~GlRenderLayer();
// Sets the texture (RGBA 8888) to be drawn. Please use UpdateTexture() if the
// texture size isn't changed.
// stride: byte distance between two rows in |subtexture|.
// If |stride| is 0 or |stride| == |width|*kBytesPerPixel, |subtexture| will
// be treated as tightly packed.
void SetTexture(const uint8_t* texture, int width, int height, int stride);
// Updates a subregion (RGBA 8888) of the texture. Can only be called after
// SetTexture() has been called.
// stride: See SetTexture().
void UpdateTexture(const uint8_t* subtexture,
int offset_x,
int offset_y,
int width,
int height,
int stride);
// Sets the positions of four vertices of the texture in pixel with respect to
// the canvas.
// positions: [ x_upperleft, y_upperleft, x_lowerleft, y_lowerleft,
// x_upperright, y_upperright, x_lowerright, y_lowerright ]
void SetVertexPositions(const std::array<float, 8>& positions);
// Sets the visible area of the texture in percentage of the width and height
// of the texture. The default values are (0, 0), (0, 1), (1, 0), (1, 1),
// i.e. showing the whole texture.
// positions: [ x_upperleft, y_upperleft, x_lowerleft, y_lowerleft,
// x_upperright, y_upperright, x_lowerright, y_lowerright ]
void SetTextureVisibleArea(const std::array<float, 8>& positions);
// Draws the texture on the canvas. Texture must be set before calling Draw().
void Draw(float alpha_multiplier);
// true if the texture is already set by calling SetTexture().
bool is_texture_set() { return texture_set_; }
private:
// Returns pointer to the texture buffer that can be used by glTexImage2d or
// glTexSubImage2d. The returned value will be |data| if the texture can be
// used without manual packing, otherwise the data will be manually packed and
// the pointer to |update_buffer_| will be returned.
// should_reset_row_length: Pointer to a bool that will be set by the
// function. If this is true, the user need to call
// glPixelStorei(GL_UNPACK_ROW_LENGTH, 0) after
// updating the texture to reset the stride.
const uint8_t* PrepareTextureBuffer(const uint8_t* data,
int width,
int height,
int stride,
bool* should_reset_row_length);
int texture_id_;
base::WeakPtr<Canvas> canvas_;
GLuint texture_handle_;
GLuint buffer_handle_;
bool texture_set_ = false;
bool vertex_position_set_ = false;
// Used in OpenGL ES 2 context which doesn't support GL_UNPACK_ROW_LENGTH to
// tightly pack dirty regions before sending them to GPU.
std::unique_ptr<uint8_t[]> update_buffer_;
int update_buffer_size_ = 0;
base::ThreadChecker thread_checker_;
};
} // namespace remoting
#endif // REMOTING_CLIENT_DISPLAY_GL_RENDER_LAYER_H_

@ -1,192 +0,0 @@
// Copyright 2016 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/client/display/gl_renderer.h"
#include <algorithm>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/task/single_thread_task_runner.h"
#include "remoting/client/display/drawable.h"
#include "remoting/client/display/gl_canvas.h"
#include "remoting/client/display/gl_math.h"
#include "remoting/client/display/gl_renderer_delegate.h"
#include "remoting/client/display/sys_opengl.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
namespace remoting {
namespace {
bool CompareDrawableZOrder(base::WeakPtr<Drawable> a,
base::WeakPtr<Drawable> b) {
return a->GetZIndex() < b->GetZIndex();
}
} // namespace
GlRenderer::GlRenderer() {
weak_ptr_ = weak_factory_.GetWeakPtr();
thread_checker_.DetachFromThread();
}
GlRenderer::~GlRenderer() {
DCHECK(thread_checker_.CalledOnValidThread());
}
void GlRenderer::SetDelegate(base::WeakPtr<GlRendererDelegate> delegate) {
DCHECK(!delegate_);
delegate_ = delegate;
}
void GlRenderer::RequestCanvasSize() {
DCHECK(thread_checker_.CalledOnValidThread());
if (delegate_) {
delegate_->OnSizeChanged(canvas_width_, canvas_height_);
}
}
void GlRenderer::OnPixelTransformationChanged(
const std::array<float, 9>& matrix) {
DCHECK(thread_checker_.CalledOnValidThread());
transformation_matrix_ = matrix;
if (!canvas_) {
return;
}
canvas_->SetTransformationMatrix(matrix);
RequestRender();
}
void GlRenderer::OnCursorMoved(float x, float y) {
DCHECK(thread_checker_.CalledOnValidThread());
cursor_.SetCursorPosition(x, y);
RequestRender();
}
void GlRenderer::OnCursorInputFeedback(float x, float y, float diameter) {
DCHECK(thread_checker_.CalledOnValidThread());
cursor_feedback_.StartAnimation(x, y, diameter);
RequestRender();
}
void GlRenderer::OnCursorVisibilityChanged(bool visible) {
DCHECK(thread_checker_.CalledOnValidThread());
cursor_.SetCursorVisible(visible);
RequestRender();
}
void GlRenderer::OnFrameReceived(std::unique_ptr<webrtc::DesktopFrame> frame,
base::OnceClosure done) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(frame->size().width() > 0 && frame->size().height() > 0);
if (canvas_width_ != frame->size().width() ||
canvas_height_ != frame->size().height()) {
if (delegate_) {
delegate_->OnSizeChanged(frame->size().width(), frame->size().height());
}
canvas_width_ = frame->size().width();
canvas_height_ = frame->size().height();
}
desktop_.SetVideoFrame(*frame);
pending_done_callbacks_.push(std::move(done));
RequestRender();
}
void GlRenderer::OnCursorShapeChanged(const protocol::CursorShapeInfo& shape) {
DCHECK(thread_checker_.CalledOnValidThread());
cursor_.SetCursorShape(shape);
RequestRender();
}
void GlRenderer::OnSurfaceCreated(std::unique_ptr<Canvas> canvas) {
DCHECK(thread_checker_.CalledOnValidThread());
canvas_ = std::move(canvas);
if (view_width_ > 0 && view_height_ > 0) {
canvas_->SetViewSize(view_width_, view_height_);
}
if (transformation_matrix_) {
canvas_->SetTransformationMatrix(*transformation_matrix_);
}
for (auto& drawable : drawables_) {
drawable->SetCanvas(canvas_->GetWeakPtr());
}
}
void GlRenderer::OnSurfaceChanged(int view_width, int view_height) {
DCHECK(thread_checker_.CalledOnValidThread());
view_width_ = view_width;
view_height_ = view_height;
if (!canvas_) {
return;
}
canvas_->SetViewSize(view_width, view_height);
RequestRender();
}
void GlRenderer::OnSurfaceDestroyed() {
DCHECK(thread_checker_.CalledOnValidThread());
cursor_feedback_.SetCanvas(nullptr);
cursor_.SetCanvas(nullptr);
desktop_.SetCanvas(nullptr);
canvas_.reset();
}
base::WeakPtr<GlRenderer> GlRenderer::GetWeakPtr() {
return weak_ptr_;
}
void GlRenderer::RequestRender() {
DCHECK(thread_checker_.CalledOnValidThread());
if (render_scheduled_) {
return;
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&GlRenderer::OnRender, weak_ptr_));
render_scheduled_ = true;
}
void GlRenderer::AddDrawable(base::WeakPtr<Drawable> drawable) {
drawable->SetCanvas(canvas_ ? canvas_->GetWeakPtr() : nullptr);
drawables_.push_back(drawable);
std::sort(drawables_.begin(), drawables_.end(), CompareDrawableZOrder);
}
void GlRenderer::OnRender() {
DCHECK(thread_checker_.CalledOnValidThread());
render_scheduled_ = false;
if (!delegate_ || !delegate_->CanRenderFrame()) {
return;
}
if (canvas_) {
canvas_->Clear();
// Draw each drawable in order.
for (auto& drawable : drawables_) {
if (drawable->Draw()) {
RequestRender();
}
}
}
delegate_->OnFrameRendered();
while (!pending_done_callbacks_.empty()) {
std::move(pending_done_callbacks_.front()).Run();
pending_done_callbacks_.pop();
}
}
std::unique_ptr<GlRenderer> GlRenderer::CreateGlRendererWithDesktop() {
std::unique_ptr<GlRenderer> renderer(new GlRenderer());
renderer->AddDrawable(renderer->desktop_.GetWeakPtr());
renderer->AddDrawable(renderer->cursor_.GetWeakPtr());
renderer->AddDrawable(renderer->cursor_feedback_.GetWeakPtr());
return renderer;
}
} // namespace remoting

@ -1,157 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DISPLAY_GL_RENDERER_H_
#define REMOTING_CLIENT_DISPLAY_GL_RENDERER_H_
#include <optional>
#include <vector>
#include "base/containers/queue.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h"
#include "remoting/client/display/gl_cursor.h"
#include "remoting/client/display/gl_cursor_feedback.h"
#include "remoting/client/display/gl_desktop.h"
#include "remoting/proto/control.pb.h"
namespace webrtc {
class DesktopFrame;
} // namespace webrtc
namespace remoting {
namespace protocol {
class CursorShapeInfo;
} // namespace protocol
class Canvas;
class GlRendererDelegate;
class GlRendererTest;
// Renders desktop and cursor on the OpenGL surface. Can be created on any
// thread but thereafter must be used and deleted on the same thread (usually
// the display thread. Or any Chromium thread with a task runner attached to
// it) unless otherwise noted.
// The unit of all length arguments is pixel.
class GlRenderer {
public:
explicit GlRenderer();
GlRenderer(const GlRenderer&) = delete;
GlRenderer& operator=(const GlRenderer&) = delete;
~GlRenderer();
// The delegate can be set on any hread no more than once before calling any
// On* functions.
void SetDelegate(base::WeakPtr<GlRendererDelegate> delegate);
// Notifies the delegate with the current canvas size. Canvas size will be
// (0, 0) if no desktop frame is received yet.
// Caller can use this function to get the canvas size when the surface is
// recreated.
void RequestCanvasSize();
// TODO(yuweih): Use ViewMatrix instead of the 3x3 array.
// Sets the pixel based transformation matrix related to the size of the
// canvas.
// 3 by 3 transformation matrix, [ m0, m1, m2, m3, m4, m5, m6, m7, m8 ].
//
// | m0, m1, m2, | | x |
// | m3, m4, m5, | * | y |
// | m6, m7, m8 | | 1 |
//
// The final size of the canvas will be (m0*canvas_width, m4*canvas_height)
// and the top-left corner will be (m2, m5) in pixel coordinates.
void OnPixelTransformationChanged(const std::array<float, 9>& matrix);
void OnCursorMoved(float x, float y);
void OnCursorInputFeedback(float x, float y, float diameter);
void OnCursorVisibilityChanged(bool visible);
// Called when a desktop frame is received.
// The size of the canvas is determined by the dimension of the desktop frame.
// |done| will be queued up and called on the display thread after the actual
// rendering happens.
void OnFrameReceived(std::unique_ptr<webrtc::DesktopFrame> frame,
base::OnceClosure done);
void OnCursorShapeChanged(const protocol::CursorShapeInfo& shape);
// Called after the EGL/EAGL context is established and the surface is created
// (or recreated). Previous desktop frame and canvas transformation will be
// lost after calling this function.
// Caller must call OnSurfaceDestroyed() before calling this function if the
// surface is recreated.
void OnSurfaceCreated(std::unique_ptr<Canvas> canvas);
// Sets the size of the view. Called right after OnSurfaceCreated() or
// whenever the view size is changed.
void OnSurfaceChanged(int view_width, int view_height);
// Called when the surface is destroyed.
void OnSurfaceDestroyed();
void AddDrawable(base::WeakPtr<Drawable> drawable);
// Returns the weak pointer to be used on the display thread.
base::WeakPtr<GlRenderer> GetWeakPtr();
// Convenience method to create a Renderer with standard desktop components.
// This function must be called on the display thread, or whatever thread that
// will be used after the renderer is created.
static std::unique_ptr<GlRenderer> CreateGlRendererWithDesktop();
private:
friend class GlRendererTest;
// Post a rendering task to the task runner of current thread.
// Do nothing if render_callback_ is not set yet or an existing rendering task
// in the queue will cover changes before this function is called.
void RequestRender();
// Draws out everything on current OpenGL buffer and runs closures in
// |pending_done_callbacks_|.
// Nothing will be drawn nor the done callbacks will be run if |delegate_| is
// invalid or !delegate_.CanRenderFrame().
void OnRender();
base::WeakPtr<GlRendererDelegate> delegate_;
// Done callbacks from OnFrameReceived. Will all be called once rendering
// takes place.
base::queue<base::OnceClosure> pending_done_callbacks_;
bool render_scheduled_ = false;
int canvas_width_ = 0;
int canvas_height_ = 0;
// Used to store the view size before the canvas is created.
int view_width_ = 0;
int view_height_ = 0;
std::unique_ptr<Canvas> canvas_;
// Used to recover the transformation matrix when the canvas is recreated.
std::optional<std::array<float, 9>> transformation_matrix_;
GlCursor cursor_;
GlCursorFeedback cursor_feedback_;
GlDesktop desktop_;
std::vector<base::WeakPtr<Drawable>> drawables_;
base::ThreadChecker thread_checker_;
base::WeakPtr<GlRenderer> weak_ptr_;
base::WeakPtrFactory<GlRenderer> weak_factory_{this};
};
} // namespace remoting
#endif // REMOTING_CLIENT_DISPLAY_GL_RENDERER_H_

@ -1,30 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DISPLAY_GL_RENDERER_DELEGATE_H_
#define REMOTING_CLIENT_DISPLAY_GL_RENDERER_DELEGATE_H_
namespace remoting {
// Interface to interact with GlRenderer. All functions will be called on the
// display thread.
class GlRendererDelegate {
public:
// Called when GlRenderer is about to render a frame on current OpenGL
// surface. Return true if GlRenderer can continue the render process.
virtual bool CanRenderFrame() = 0;
// Called after GlRenderer has successfully rendered a frame on current OpenGL
// surface.
virtual void OnFrameRendered() = 0;
// Called when the size of the canvas (= size of desktop frame) is changed.
virtual void OnSizeChanged(int width, int height) = 0;
protected:
virtual ~GlRendererDelegate() {}
};
} // namespace remoting
#endif // REMOTING_CLIENT_DISPLAY_GL_RENDERER_DELEGATE_H_

@ -1,357 +0,0 @@
// Copyright 2016 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/client/display/gl_renderer.h"
#include <memory>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "remoting/client/display/fake_canvas.h"
#include "remoting/client/display/gl_renderer_delegate.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
namespace remoting {
class FakeGlRendererDelegate : public GlRendererDelegate {
public:
FakeGlRendererDelegate() {}
FakeGlRendererDelegate(const FakeGlRendererDelegate&) = delete;
FakeGlRendererDelegate& operator=(const FakeGlRendererDelegate&) = delete;
bool CanRenderFrame() override {
can_render_frame_call_count_++;
return can_render_frame_;
}
void OnFrameRendered() override {
on_frame_rendered_call_count_++;
if (on_frame_rendered_callback_) {
on_frame_rendered_callback_.Run();
}
}
void OnSizeChanged(int width, int height) override {
canvas_width_ = width;
canvas_height_ = height;
on_size_changed_call_count_++;
}
void SetOnFrameRenderedCallback(const base::RepeatingClosure& callback) {
on_frame_rendered_callback_ = callback;
}
int canvas_width() { return canvas_width_; }
int canvas_height() { return canvas_height_; }
base::WeakPtr<FakeGlRendererDelegate> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
int can_render_frame_call_count() { return can_render_frame_call_count_; }
int on_frame_rendered_call_count() { return on_frame_rendered_call_count_; }
int on_size_changed_call_count() { return on_size_changed_call_count_; }
bool can_render_frame_ = false;
private:
int can_render_frame_call_count_ = 0;
int on_frame_rendered_call_count_ = 0;
int on_size_changed_call_count_ = 0;
int canvas_width_ = 0;
int canvas_height_ = 0;
base::RepeatingClosure on_frame_rendered_callback_;
base::WeakPtrFactory<FakeGlRendererDelegate> weak_factory_{this};
};
class FakeDrawable : public Drawable {
public:
FakeDrawable() {}
FakeDrawable(const FakeDrawable&) = delete;
FakeDrawable& operator=(const FakeDrawable&) = delete;
void SetId(int id) { id_ = id; }
int GetId() { return id_; }
base::WeakPtr<Drawable> GetWeakPtr() override {
return weak_factory_.GetWeakPtr();
}
void SetCanvas(base::WeakPtr<Canvas> canvas) override {}
bool Draw() override {
drawn_++;
return false;
}
void SetZIndex(int z_index) { z_index_ = z_index; }
int GetZIndex() override { return z_index_; }
int DrawnCount() { return drawn_; }
private:
int drawn_ = 0;
int id_ = -1;
int z_index_ = -1;
base::WeakPtrFactory<FakeDrawable> weak_factory_{this};
};
class GlRendererTest : public testing::Test {
public:
void SetUp() override;
void SetDesktopFrameWithSize(const webrtc::DesktopSize& size);
void PostSetDesktopFrameTasks(const webrtc::DesktopSize& size, int count);
int GetDrawablesCount();
std::vector<base::WeakPtr<Drawable>> GetDrawables();
protected:
void RequestRender();
void OnDesktopFrameProcessed();
void RunTasksInCurrentQueue();
void RunUntilRendered();
int on_desktop_frame_processed_call_count() {
return on_desktop_frame_processed_call_count_;
}
base::test::SingleThreadTaskEnvironment task_environment_;
std::unique_ptr<GlRenderer> renderer_;
FakeGlRendererDelegate delegate_;
private:
int on_desktop_frame_processed_call_count_ = 0;
};
void GlRendererTest::SetUp() {
renderer_ = std::make_unique<GlRenderer>();
renderer_->SetDelegate(delegate_.GetWeakPtr());
}
void GlRendererTest::RequestRender() {
renderer_->RequestRender();
}
int GlRendererTest::GetDrawablesCount() {
return renderer_->drawables_.size();
}
std::vector<base::WeakPtr<Drawable>> GlRendererTest::GetDrawables() {
return renderer_->drawables_;
}
void GlRendererTest::SetDesktopFrameWithSize(const webrtc::DesktopSize& size) {
renderer_->OnFrameReceived(
std::make_unique<webrtc::BasicDesktopFrame>(size),
base::BindOnce(&GlRendererTest::OnDesktopFrameProcessed,
base::Unretained(this)));
}
void GlRendererTest::PostSetDesktopFrameTasks(const webrtc::DesktopSize& size,
int count) {
for (int i = 0; i < count; i++) {
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&GlRendererTest::SetDesktopFrameWithSize,
base::Unretained(this), size));
}
}
void GlRendererTest::OnDesktopFrameProcessed() {
on_desktop_frame_processed_call_count_++;
}
void GlRendererTest::RunTasksInCurrentQueue() {
base::RunLoop run_loop;
task_environment_.GetMainThreadTaskRunner()->PostTask(FROM_HERE,
run_loop.QuitClosure());
run_loop.Run();
}
void GlRendererTest::RunUntilRendered() {
base::RunLoop run_loop;
delegate_.SetOnFrameRenderedCallback(run_loop.QuitClosure());
run_loop.Run();
}
TEST_F(GlRendererTest, TestDelegateCanRenderFrame) {
delegate_.can_render_frame_ = true;
RequestRender();
RunTasksInCurrentQueue();
EXPECT_EQ(1, delegate_.can_render_frame_call_count());
EXPECT_EQ(1, delegate_.on_frame_rendered_call_count());
delegate_.can_render_frame_ = false;
RequestRender();
RunTasksInCurrentQueue();
EXPECT_EQ(2, delegate_.can_render_frame_call_count());
EXPECT_EQ(1, delegate_.on_frame_rendered_call_count());
}
TEST_F(GlRendererTest, TestRequestRenderOnlyScheduleOnce) {
delegate_.can_render_frame_ = true;
RequestRender();
RequestRender();
RequestRender();
RunTasksInCurrentQueue();
EXPECT_EQ(1, delegate_.can_render_frame_call_count());
EXPECT_EQ(1, delegate_.on_frame_rendered_call_count());
RequestRender();
RunTasksInCurrentQueue();
EXPECT_EQ(2, delegate_.can_render_frame_call_count());
EXPECT_EQ(2, delegate_.on_frame_rendered_call_count());
}
TEST_F(GlRendererTest, TestDelegateOnSizeChanged) {
SetDesktopFrameWithSize(webrtc::DesktopSize(16, 16));
ASSERT_EQ(1, delegate_.on_size_changed_call_count());
ASSERT_EQ(16, delegate_.canvas_width());
ASSERT_EQ(16, delegate_.canvas_height());
SetDesktopFrameWithSize(webrtc::DesktopSize(16, 16));
ASSERT_EQ(1, delegate_.on_size_changed_call_count());
ASSERT_EQ(16, delegate_.canvas_width());
ASSERT_EQ(16, delegate_.canvas_height());
SetDesktopFrameWithSize(webrtc::DesktopSize(32, 32));
ASSERT_EQ(2, delegate_.on_size_changed_call_count());
ASSERT_EQ(32, delegate_.canvas_width());
ASSERT_EQ(32, delegate_.canvas_height());
renderer_->RequestCanvasSize();
ASSERT_EQ(3, delegate_.on_size_changed_call_count());
ASSERT_EQ(32, delegate_.canvas_width());
ASSERT_EQ(32, delegate_.canvas_height());
}
TEST_F(GlRendererTest, TestOnFrameReceivedDoneCallbacks) {
delegate_.can_render_frame_ = true;
// Implicitly calls RequestRender().
PostSetDesktopFrameTasks(webrtc::DesktopSize(16, 16), 1);
RunUntilRendered();
EXPECT_EQ(1, delegate_.on_frame_rendered_call_count());
EXPECT_EQ(1, on_desktop_frame_processed_call_count());
PostSetDesktopFrameTasks(webrtc::DesktopSize(16, 16), 20);
RunUntilRendered();
ASSERT_EQ(2, delegate_.on_frame_rendered_call_count());
ASSERT_EQ(21, on_desktop_frame_processed_call_count());
}
// TODO(yuweih): Add tests to validate the rendered output.
TEST_F(GlRendererTest, TestAddDrawable) {
std::unique_ptr<FakeDrawable> drawable0 = std::make_unique<FakeDrawable>();
drawable0->SetId(0);
renderer_->AddDrawable(drawable0->GetWeakPtr());
ASSERT_EQ(1, GetDrawablesCount());
}
TEST_F(GlRendererTest, TestAddDrawableDefaultOrder) {
std::unique_ptr<FakeDrawable> drawable0 = std::make_unique<FakeDrawable>();
drawable0->SetId(0);
renderer_->AddDrawable(drawable0->GetWeakPtr());
ASSERT_EQ(1, GetDrawablesCount());
std::unique_ptr<FakeDrawable> drawable1 = std::make_unique<FakeDrawable>();
drawable1->SetId(1);
renderer_->AddDrawable(drawable1->GetWeakPtr());
ASSERT_EQ(2, GetDrawablesCount());
std::unique_ptr<FakeDrawable> drawable2 = std::make_unique<FakeDrawable>();
drawable2->SetId(2);
renderer_->AddDrawable(drawable2->GetWeakPtr());
ASSERT_EQ(3, GetDrawablesCount());
int i = 0;
for (auto& drawable : GetDrawables()) {
FakeDrawable* fg = static_cast<FakeDrawable*>(drawable.get());
ASSERT_EQ(i, fg->GetId());
i++;
}
ASSERT_EQ(3, i);
}
TEST_F(GlRendererTest, TestAddDrawableOrder) {
std::unique_ptr<FakeDrawable> drawable2 = std::make_unique<FakeDrawable>();
drawable2->SetId(2);
drawable2->SetZIndex(2);
renderer_->AddDrawable(drawable2->GetWeakPtr());
ASSERT_EQ(1, GetDrawablesCount());
std::unique_ptr<FakeDrawable> drawable0 = std::make_unique<FakeDrawable>();
drawable0->SetId(0);
renderer_->AddDrawable(drawable0->GetWeakPtr());
ASSERT_EQ(2, GetDrawablesCount());
std::unique_ptr<FakeDrawable> drawable1 = std::make_unique<FakeDrawable>();
drawable1->SetId(1);
drawable1->SetZIndex(1);
renderer_->AddDrawable(drawable1->GetWeakPtr());
ASSERT_EQ(3, GetDrawablesCount());
int i = 0;
for (auto& drawable : GetDrawables()) {
FakeDrawable* fg = static_cast<FakeDrawable*>(drawable.get());
ASSERT_EQ(i, fg->GetId());
i++;
}
ASSERT_EQ(3, i);
}
TEST_F(GlRendererTest, TestAddDrawableDrawn) {
std::unique_ptr<Canvas> fakeCanvas = std::make_unique<FakeCanvas>();
renderer_->OnSurfaceCreated(std::move(fakeCanvas));
delegate_.can_render_frame_ = true;
PostSetDesktopFrameTasks(webrtc::DesktopSize(16, 16), 1);
std::unique_ptr<FakeDrawable> drawable0 = std::make_unique<FakeDrawable>();
drawable0->SetId(3);
renderer_->AddDrawable(drawable0->GetWeakPtr());
RequestRender();
RunTasksInCurrentQueue();
std::unique_ptr<FakeDrawable> drawable1 = std::make_unique<FakeDrawable>();
drawable1->SetId(2);
drawable1->SetZIndex(1);
renderer_->AddDrawable(drawable1->GetWeakPtr());
RequestRender();
RunTasksInCurrentQueue();
std::unique_ptr<FakeDrawable> drawable2 = std::make_unique<FakeDrawable>();
drawable2->SetId(1);
drawable2->SetZIndex(2);
renderer_->AddDrawable(drawable2->GetWeakPtr());
ASSERT_EQ(3, GetDrawablesCount());
RequestRender();
RunTasksInCurrentQueue();
for (auto& drawable : GetDrawables()) {
FakeDrawable* fg = static_cast<FakeDrawable*>(drawable.get());
EXPECT_EQ(fg->GetId(), fg->DrawnCount());
}
}
TEST_F(GlRendererTest, TestCreateGlRendererWithDesktop) {
renderer_ = GlRenderer::CreateGlRendererWithDesktop();
renderer_->SetDelegate(delegate_.GetWeakPtr());
ASSERT_EQ(3, GetDrawablesCount());
}
// TODO(nicholss): Add a test where the drawable is destructed and the renderer
// gets a dead weakptr.
} // namespace remoting

@ -1,20 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DISPLAY_GL_TEXTURE_IDS_H_
#define REMOTING_CLIENT_DISPLAY_GL_TEXTURE_IDS_H_
namespace remoting {
const int kGlCursorTextureId = 0;
const int kGlCursorFeedbackTextureId = 1;
// GlDesktop may occupy more than one texture unit. This should be the last
// texture ID so that GlDesktop can use any id >= kGlDesktopFirstTextureId
// without conflict.
const int kGlDesktopFirstTextureId = 2;
} // namespace remoting
#endif // REMOTING_CLIENT_DISPLAY_GL_TEXTURE_IDS_H_

@ -1,70 +0,0 @@
// Copyright 2017 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/client/display/renderer_proxy.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/task/single_thread_task_runner.h"
#include "remoting/base/queued_task_poster.h"
#include "remoting/client/display/gl_renderer.h"
#include "remoting/client/ui/view_matrix.h"
namespace remoting {
RendererProxy::RendererProxy(
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: task_runner_(task_runner),
ui_task_poster_(new remoting::QueuedTaskPoster(task_runner_)) {}
RendererProxy::~RendererProxy() = default;
void RendererProxy::Initialize(base::WeakPtr<GlRenderer> renderer) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
renderer_ = renderer;
}
void RendererProxy::SetTransformation(const ViewMatrix& transformation) {
// Viewport and cursor movements need to be synchronized into the same frame.
RunTaskOnProperThread(
base::BindOnce(&GlRenderer::OnPixelTransformationChanged, renderer_,
transformation.ToMatrixArray()),
true);
}
void RendererProxy::SetCursorPosition(float x, float y) {
RunTaskOnProperThread(
base::BindOnce(&GlRenderer::OnCursorMoved, renderer_, x, y), true);
}
void RendererProxy::SetCursorVisibility(bool visible) {
// Cursor visibility and position should be synchronized.
RunTaskOnProperThread(base::BindOnce(&GlRenderer::OnCursorVisibilityChanged,
renderer_, visible),
true);
}
void RendererProxy::StartInputFeedback(float x, float y, float diameter) {
RunTaskOnProperThread(base::BindOnce(&GlRenderer::OnCursorInputFeedback,
renderer_, x, y, diameter),
false);
}
void RendererProxy::RunTaskOnProperThread(base::OnceClosure task,
bool needs_synchronization) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (task_runner_->BelongsToCurrentThread()) {
std::move(task).Run();
return;
}
if (needs_synchronization) {
ui_task_poster_->AddTask(std::move(task));
return;
}
task_runner_->PostTask(FROM_HERE, std::move(task));
}
} // namespace remoting

@ -1,56 +0,0 @@
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DISPLAY_RENDERER_PROXY_H_
#define REMOTING_CLIENT_DISPLAY_RENDERER_PROXY_H_
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_checker.h"
namespace remoting {
class GlRenderer;
class QueuedTaskPoster;
class ViewMatrix;
// A class to proxy calls to GlRenderer from one thread to another. Must be
// created and used on the same thread.
// TODO(yuweih): This should be removed once we have moved Drawables out of
// GlRenderer.
class RendererProxy {
public:
// task_runner: The task runner that |renderer_| should be run on.
RendererProxy(scoped_refptr<base::SingleThreadTaskRunner> task_runner);
RendererProxy(const RendererProxy&) = delete;
RendererProxy& operator=(const RendererProxy&) = delete;
~RendererProxy();
// Initialize with the renderer to be proxied.
void Initialize(base::WeakPtr<GlRenderer> renderer);
void SetTransformation(const ViewMatrix& transformation);
void SetCursorPosition(float x, float y);
void SetCursorVisibility(bool visible);
void StartInputFeedback(float x, float y, float diameter);
private:
// Runs the |task| on the thread of |task_runner_|. All tasks run with
// |needs_synchronization| set to true inside the same tick will be run on
// |task_runner_| within the same tick.
void RunTaskOnProperThread(base::OnceClosure task,
bool needs_synchronization);
base::WeakPtr<GlRenderer> renderer_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
std::unique_ptr<remoting::QueuedTaskPoster> ui_task_poster_;
THREAD_CHECKER(thread_checker_);
};
} // namespace remoting
#endif // REMOTING_CLIENT_DISPLAY_RENDERER_PROXY_H_

@ -1,24 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DISPLAY_SYS_OPENGL_H_
#define REMOTING_CLIENT_DISPLAY_SYS_OPENGL_H_
#include "build/build_config.h"
#if BUILDFLAG(IS_IOS)
#include <OpenGLES/ES3/gl.h>
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#elif BUILDFLAG(IS_MAC)
#define GL_GLEXT_PROTOTYPES
#include <OpenGL/gl.h>
#include <OpenGL/glext.h>
#else
#include <GLES3/gl3.h>
#endif // BUILDFLAG(IS_IOS)
#endif // REMOTING_CLIENT_DISPLAY_SYS_OPENGL_H_

@ -1,171 +0,0 @@
// Copyright 2016 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/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "remoting/client/dual_buffer_frame_consumer.h"
#include <memory>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/task/single_thread_task_runner.h"
namespace remoting {
namespace {
// The implementation is mostly the same as webrtc::BasicDesktopFrame, except
// that it has an extra padding of one row at the end of the buffer. This is
// to workaround a bug in iOS' implementation of glTexSubimage2D that causes
// occasional SIGSEGV.
//
// glTexSubimage2D is supposed to only read
// kBytesPerPixel * width * (height - 1) bytes from the buffer but it seems to
// be reading more than that, which may end up reading protected memory.
//
// See details in crbug.com/778550
class PaddedDesktopFrame : public webrtc::DesktopFrame {
public:
explicit PaddedDesktopFrame(webrtc::DesktopSize size);
PaddedDesktopFrame(const PaddedDesktopFrame&) = delete;
PaddedDesktopFrame& operator=(const PaddedDesktopFrame&) = delete;
~PaddedDesktopFrame() override;
// Creates a PaddedDesktopFrame that contains copy of |frame|.
static std::unique_ptr<webrtc::DesktopFrame> CopyOf(
const webrtc::DesktopFrame& frame);
};
PaddedDesktopFrame::PaddedDesktopFrame(webrtc::DesktopSize size)
: DesktopFrame(
size,
kBytesPerPixel * size.width(),
new uint8_t[kBytesPerPixel * size.width() * (size.height() + 1)],
nullptr) {}
PaddedDesktopFrame::~PaddedDesktopFrame() {
delete[] data_;
}
// static
std::unique_ptr<webrtc::DesktopFrame> PaddedDesktopFrame::CopyOf(
const webrtc::DesktopFrame& frame) {
std::unique_ptr<PaddedDesktopFrame> result =
std::make_unique<PaddedDesktopFrame>(frame.size());
for (int y = 0; y < frame.size().height(); ++y) {
memcpy(result->data() + y * result->stride(),
frame.data() + y * frame.stride(),
frame.size().width() * kBytesPerPixel);
}
result->CopyFrameInfoFrom(frame);
return result;
}
} // namespace
DualBufferFrameConsumer::DualBufferFrameConsumer(
RenderCallback callback,
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
protocol::FrameConsumer::PixelFormat format)
: callback_(std::move(callback)),
task_runner_(task_runner),
pixel_format_(format) {
weak_ptr_ = weak_factory_.GetWeakPtr();
thread_checker_.DetachFromThread();
}
DualBufferFrameConsumer::~DualBufferFrameConsumer() {
DCHECK(thread_checker_.CalledOnValidThread());
}
void DualBufferFrameConsumer::RequestFullDesktopFrame() {
DCHECK(thread_checker_.CalledOnValidThread());
if (!buffers_[0]) {
return;
}
DCHECK(buffers_[0]->size().equals(buffers_[1]->size()));
// This creates a copy of buffers_[0] and merges area defined in
// |buffer_1_mask_| from buffers_[1] into the copy.
std::unique_ptr<webrtc::DesktopFrame> full_frame =
PaddedDesktopFrame::CopyOf(*buffers_[0]);
webrtc::DesktopRect desktop_rect =
webrtc::DesktopRect::MakeSize(buffers_[0]->size());
for (webrtc::DesktopRegion::Iterator i(buffer_1_mask_); !i.IsAtEnd();
i.Advance()) {
full_frame->CopyPixelsFrom(*buffers_[1], i.rect().top_left(),
i.rect());
}
full_frame->mutable_updated_region()->SetRect(desktop_rect);
RunRenderCallback(std::move(full_frame), base::DoNothing());
}
std::unique_ptr<webrtc::DesktopFrame> DualBufferFrameConsumer::AllocateFrame(
const webrtc::DesktopSize& size) {
DCHECK(thread_checker_.CalledOnValidThread());
// Both buffers are reallocated whenever screen size changes.
if (!buffers_[0] || !buffers_[0]->size().equals(size)) {
buffers_[0] = webrtc::SharedDesktopFrame::Wrap(
std::make_unique<PaddedDesktopFrame>(size));
buffers_[1] = webrtc::SharedDesktopFrame::Wrap(
std::make_unique<PaddedDesktopFrame>(size));
buffer_1_mask_.Clear();
current_buffer_ = 0;
} else {
current_buffer_ = (current_buffer_ + 1) % 2;
}
return buffers_[current_buffer_]->Share();
}
void DualBufferFrameConsumer::DrawFrame(
std::unique_ptr<webrtc::DesktopFrame> frame,
base::OnceClosure done) {
DCHECK(thread_checker_.CalledOnValidThread());
webrtc::SharedDesktopFrame* shared_frame =
reinterpret_cast<webrtc::SharedDesktopFrame*> (frame.get());
if (shared_frame->GetUnderlyingFrame() == buffers_[1]->GetUnderlyingFrame()) {
buffer_1_mask_.AddRegion(frame->updated_region());
} else if (shared_frame->GetUnderlyingFrame() ==
buffers_[0]->GetUnderlyingFrame()) {
buffer_1_mask_.Subtract(frame->updated_region());
}
RunRenderCallback(std::move(frame), std::move(done));
}
protocol::FrameConsumer::PixelFormat
DualBufferFrameConsumer::GetPixelFormat() {
return pixel_format_;
}
base::WeakPtr<DualBufferFrameConsumer> DualBufferFrameConsumer::GetWeakPtr() {
return weak_ptr_;
}
void DualBufferFrameConsumer::RunRenderCallback(
std::unique_ptr<webrtc::DesktopFrame> frame,
base::OnceClosure done) {
if (!task_runner_) {
callback_.Run(std::move(frame), std::move(done));
return;
}
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
callback_, std::move(frame),
base::BindOnce(base::IgnoreResult(&base::TaskRunner::PostTask),
base::SingleThreadTaskRunner::GetCurrentDefault(),
FROM_HERE, std::move(done))));
}
} // namespace remoting

@ -1,80 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_DUAL_BUFFER_FRAME_CONSUMER_H_
#define REMOTING_CLIENT_DUAL_BUFFER_FRAME_CONSUMER_H_
#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_checker.h"
#include "remoting/protocol/frame_consumer.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_region.h"
#include "third_party/webrtc/modules/desktop_capture/shared_desktop_frame.h"
namespace remoting {
// This class continuously uses two BasicDesktopFrame as buffer for decoding
// updated regions until the resolution is changed.
// This class should be used and destroyed on the same thread. If |task_runner|
// is null |callback| will be run directly upon the stack of DrawFrame,
// otherwise a task will be posted to feed the callback on the thread of
// |task_runner|.
// Only areas bound by updated_region() on the buffer are considered valid to
// |callback|. Please use RequestFullDesktopFrame() if you want to get a full
// desktop frame.
class DualBufferFrameConsumer : public protocol::FrameConsumer {
public:
// RenderCallback(decoded_frame, done)
// |done| should be run after it is rendered. Can be called on any thread.
using RenderCallback =
base::RepeatingCallback<void(std::unique_ptr<webrtc::DesktopFrame>,
base::OnceClosure)>;
DualBufferFrameConsumer(
RenderCallback callback,
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
PixelFormat format);
DualBufferFrameConsumer(const DualBufferFrameConsumer&) = delete;
DualBufferFrameConsumer& operator=(const DualBufferFrameConsumer&) = delete;
~DualBufferFrameConsumer() override;
// Feeds the callback on the right thread with a BasicDesktopFrame that merges
// updates from buffer_[0] and buffer_[1]. Do nothing if no updates have
// received yet.
void RequestFullDesktopFrame();
// FrameConsumer interface.
std::unique_ptr<webrtc::DesktopFrame> AllocateFrame(
const webrtc::DesktopSize& size) override;
void DrawFrame(std::unique_ptr<webrtc::DesktopFrame> frame,
base::OnceClosure done) override;
PixelFormat GetPixelFormat() override;
base::WeakPtr<DualBufferFrameConsumer> GetWeakPtr();
private:
void RunRenderCallback(std::unique_ptr<webrtc::DesktopFrame> frame,
base::OnceClosure done);
std::unique_ptr<webrtc::SharedDesktopFrame> buffers_[2];
// Represents dirty regions that are currently in buffers_[1]. Will be used
// when calling RequestFullDesktopFrame() to construct the full desktop frame.
webrtc::DesktopRegion buffer_1_mask_;
int current_buffer_ = 0;
RenderCallback callback_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
PixelFormat pixel_format_;
base::ThreadChecker thread_checker_;
base::WeakPtr<DualBufferFrameConsumer> weak_ptr_;
base::WeakPtrFactory<DualBufferFrameConsumer> weak_factory_{this};
};
} // namespace remoting
#endif // REMOTING_CLIENT_DUAL_BUFFER_FRAME_CONSUMER_H_

@ -1,211 +0,0 @@
// Copyright 2016 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/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "remoting/client/dual_buffer_frame_consumer.h"
#include <memory>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
#include "third_party/webrtc/modules/desktop_capture/shared_desktop_frame.h"
namespace remoting {
namespace {
webrtc::DesktopFrame* GetUnderlyingFrame(
const std::unique_ptr<webrtc::DesktopFrame>& frame) {
return reinterpret_cast<webrtc::SharedDesktopFrame*>(frame.get())->
GetUnderlyingFrame();
}
void FillRGBARect(uint8_t r,
uint8_t g,
uint8_t b,
uint8_t a,
const webrtc::DesktopRect& rect,
webrtc::DesktopFrame* frame) {
for (int x = 0; x < rect.width(); x++) {
for (int y = 0; y < rect.height(); y++) {
uint8_t* data = frame->GetFrameDataAtPos(
rect.top_left().add(webrtc::DesktopVector(x, y)));
data[0] = r;
data[1] = g;
data[2] = b;
data[3] = a;
}
}
frame->mutable_updated_region()->SetRect(rect);
}
void CheckFrameColor(uint8_t r,
uint8_t g,
uint8_t b,
uint8_t a,
const webrtc::DesktopVector& pos,
const webrtc::DesktopFrame& frame) {
uint8_t* data = frame.GetFrameDataAtPos(pos);
EXPECT_EQ(r, data[0]);
EXPECT_EQ(g, data[1]);
EXPECT_EQ(b, data[2]);
EXPECT_EQ(a, data[3]);
}
} // namespace
class DualBufferFrameConsumerTest : public testing::Test {
public:
void SetUp() override;
protected:
std::unique_ptr<DualBufferFrameConsumer> consumer_;
std::unique_ptr<webrtc::DesktopFrame> received_frame_;
base::OnceClosure done_closure_;
private:
void OnFrameReceived(std::unique_ptr<webrtc::DesktopFrame> frame,
base::OnceClosure done);
};
void DualBufferFrameConsumerTest::SetUp() {
consumer_ = std::make_unique<DualBufferFrameConsumer>(
base::BindRepeating(&DualBufferFrameConsumerTest::OnFrameReceived,
base::Unretained(this)),
nullptr, protocol::FrameConsumer::FORMAT_RGBA);
}
void DualBufferFrameConsumerTest::OnFrameReceived(
std::unique_ptr<webrtc::DesktopFrame> frame,
base::OnceClosure done) {
received_frame_ = std::move(frame);
done_closure_ = std::move(done);
}
TEST_F(DualBufferFrameConsumerTest, AllocateOneFrame) {
std::unique_ptr<webrtc::DesktopFrame> frame =
consumer_->AllocateFrame(webrtc::DesktopSize(16, 16));
ASSERT_TRUE(frame->size().equals(webrtc::DesktopSize(16, 16)));
webrtc::DesktopFrame* raw_frame = frame.get();
consumer_->DrawFrame(std::move(frame), base::NullCallback());
EXPECT_EQ(raw_frame, received_frame_.get());
}
TEST_F(DualBufferFrameConsumerTest, BufferRotation) {
webrtc::DesktopSize size16x16(16, 16);
std::unique_ptr<webrtc::DesktopFrame> frame =
consumer_->AllocateFrame(size16x16);
webrtc::DesktopFrame* underlying_frame_1 = GetUnderlyingFrame(frame);
consumer_->DrawFrame(std::move(frame), base::NullCallback());
frame = consumer_->AllocateFrame(size16x16);
webrtc::DesktopFrame* underlying_frame_2 = GetUnderlyingFrame(frame);
EXPECT_NE(underlying_frame_1, underlying_frame_2);
consumer_->DrawFrame(std::move(frame), base::NullCallback());
frame = consumer_->AllocateFrame(size16x16);
webrtc::DesktopFrame* underlying_frame_3 = GetUnderlyingFrame(frame);
EXPECT_EQ(underlying_frame_1, underlying_frame_3);
consumer_->DrawFrame(std::move(frame), base::NullCallback());
frame = consumer_->AllocateFrame(size16x16);
webrtc::DesktopFrame* underlying_frame_4 = GetUnderlyingFrame(frame);
EXPECT_EQ(underlying_frame_2, underlying_frame_4);
consumer_->DrawFrame(std::move(frame), base::NullCallback());
}
TEST_F(DualBufferFrameConsumerTest, DrawAndMergeFrames) {
webrtc::DesktopSize size2x2(2, 2);
// X means uninitialized color.
// Frame 1:
// RR
// RR
std::unique_ptr<webrtc::DesktopFrame> frame =
consumer_->AllocateFrame(size2x2);
FillRGBARect(0xff, 0, 0, 0xff, webrtc::DesktopRect::MakeXYWH(0, 0, 2, 2),
frame.get());
consumer_->DrawFrame(std::move(frame), base::NullCallback());
// Frame 2:
// GG
// XX
frame = consumer_->AllocateFrame(size2x2);
FillRGBARect(0, 0xff, 0, 0xff, webrtc::DesktopRect::MakeXYWH(0, 0, 2, 1),
frame.get());
consumer_->DrawFrame(std::move(frame), base::NullCallback());
// Merged Frame:
// GG
// RR
consumer_->RequestFullDesktopFrame();
ASSERT_TRUE(received_frame_->size().equals(size2x2));
CheckFrameColor(0, 0xff, 0, 0xff, webrtc::DesktopVector(0, 0),
*received_frame_);
CheckFrameColor(0xff, 0, 0, 0xff, webrtc::DesktopVector(0, 1),
*received_frame_);
CheckFrameColor(0, 0xff, 0, 0xff, webrtc::DesktopVector(1, 0),
*received_frame_);
CheckFrameColor(0xff, 0, 0, 0xff, webrtc::DesktopVector(1, 1),
*received_frame_);
// Frame 3:
// BX
// BX
frame = consumer_->AllocateFrame(size2x2);
FillRGBARect(0, 0, 0xff, 0xff, webrtc::DesktopRect::MakeXYWH(0, 0, 1, 2),
frame.get());
consumer_->DrawFrame(std::move(frame), base::NullCallback());
// Merged Frame:
// BG
// BR
consumer_->RequestFullDesktopFrame();
ASSERT_TRUE(received_frame_->size().equals(size2x2));
CheckFrameColor(0, 0, 0xff, 0xff, webrtc::DesktopVector(0, 0),
*received_frame_);
CheckFrameColor(0, 0, 0xff, 0xff, webrtc::DesktopVector(0, 1),
*received_frame_);
CheckFrameColor(0, 0xff, 0, 0xff, webrtc::DesktopVector(1, 0),
*received_frame_);
CheckFrameColor(0xff, 0, 0, 0xff, webrtc::DesktopVector(1, 1),
*received_frame_);
}
TEST_F(DualBufferFrameConsumerTest, ChangeScreenSizeAndReallocateBuffers) {
webrtc::DesktopSize size16x16(16, 16);
std::unique_ptr<webrtc::DesktopFrame> frame =
consumer_->AllocateFrame(size16x16);
webrtc::DesktopFrame* underlying_frame_1 = GetUnderlyingFrame(frame);
consumer_->DrawFrame(std::move(frame), base::NullCallback());
frame = consumer_->AllocateFrame(size16x16);
webrtc::DesktopFrame* underlying_frame_2 = GetUnderlyingFrame(frame);
EXPECT_NE(underlying_frame_1, underlying_frame_2);
consumer_->DrawFrame(std::move(frame), base::NullCallback());
webrtc::DesktopSize size32x32(32, 32);
frame = consumer_->AllocateFrame(size32x32);
webrtc::DesktopFrame* underlying_frame_3 = GetUnderlyingFrame(frame);
EXPECT_NE(underlying_frame_1, underlying_frame_3);
consumer_->DrawFrame(std::move(frame), base::NullCallback());
frame = consumer_->AllocateFrame(size32x32);
webrtc::DesktopFrame* underlying_frame_4 = GetUnderlyingFrame(frame);
EXPECT_NE(underlying_frame_2, underlying_frame_4);
consumer_->DrawFrame(std::move(frame), base::NullCallback());
}
} // namespace remoting

@ -1,74 +0,0 @@
// Copyright 2015 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/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "remoting/client/empty_cursor_filter.h"
#include <stdint.h>
#include <algorithm>
#include "build/build_config.h"
#include "remoting/proto/control.pb.h"
namespace remoting {
protocol::CursorShapeInfo EmptyCursorShape() {
protocol::CursorShapeInfo empty_shape;
empty_shape.set_data(std::string());
empty_shape.set_width(0);
empty_shape.set_height(0);
empty_shape.set_hotspot_x(0);
empty_shape.set_hotspot_y(0);
return empty_shape;
}
bool IsCursorShapeEmpty(const protocol::CursorShapeInfo& cursor_shape) {
return cursor_shape.width() <= 0 || cursor_shape.height() <= 0;
}
EmptyCursorFilter::EmptyCursorFilter(protocol::CursorShapeStub* cursor_stub)
: cursor_stub_(cursor_stub) {
}
EmptyCursorFilter::~EmptyCursorFilter() = default;
namespace {
#if defined(ARCH_CPU_LITTLE_ENDIAN)
const uint32_t kPixelAlphaMask = 0xff000000;
#else // !defined(ARCH_CPU_LITTLE_ENDIAN)
const uint32_t kPixelAlphaMask = 0x000000ff;
#endif // !defined(ARCH_CPU_LITTLE_ENDIAN)
// Returns true if |pixel| is not completely transparent.
bool IsVisiblePixel(uint32_t pixel) {
return (pixel & kPixelAlphaMask) != 0;
}
// Returns true if there is at least one visible pixel in the given range.
bool IsVisibleRow(const uint32_t* begin, const uint32_t* end) {
return std::any_of(begin, end, &IsVisiblePixel);
}
} // namespace
void EmptyCursorFilter::SetCursorShape(
const protocol::CursorShapeInfo& cursor_shape) {
const uint32_t* src_row_data = reinterpret_cast<const uint32_t*>(
cursor_shape.data().data());
const uint32_t* src_row_data_end =
src_row_data + cursor_shape.width() * cursor_shape.height();
if (IsVisibleRow(src_row_data, src_row_data_end)) {
cursor_stub_->SetCursorShape(cursor_shape);
return;
}
cursor_stub_->SetCursorShape(EmptyCursorShape());
}
} // namespace remoting

@ -1,46 +0,0 @@
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_EMPTY_CURSOR_FILTER_H_
#define REMOTING_CLIENT_EMPTY_CURSOR_FILTER_H_
#include "base/memory/raw_ptr.h"
#include "remoting/protocol/cursor_shape_stub.h"
namespace remoting {
// Returns an empty cursor.
protocol::CursorShapeInfo EmptyCursorShape();
// Returns true of the supplied cursor is empty, i.e. width and/or height <= 0.
bool IsCursorShapeEmpty(const protocol::CursorShapeInfo& cursor_shape);
// Helper that checks whether a cursor has any visible pixels, and resizes
// it down to (0x0) if not.
// TODO(wez): Turn this into a general-purpose cropping filter that both
// client and host can use.
class EmptyCursorFilter : public protocol::CursorShapeStub {
public:
explicit EmptyCursorFilter(protocol::CursorShapeStub* cursor_stub);
EmptyCursorFilter(const EmptyCursorFilter&) = delete;
EmptyCursorFilter& operator=(const EmptyCursorFilter&) = delete;
~EmptyCursorFilter() override;
// protocol::CursorShapeStub interface.
void SetCursorShape(const protocol::CursorShapeInfo& cursor_shape) override;
// Replaces the stub to which cursor shapes will be passed-on.
void set_cursor_stub(protocol::CursorShapeStub* cursor_stub) {
cursor_stub_ = cursor_stub;
}
private:
raw_ptr<protocol::CursorShapeStub> cursor_stub_;
};
} // namespace remoting
#endif // REMOTING_CLIENT_EMPTY_CURSOR_FILTER_H_

@ -1,132 +0,0 @@
// Copyright 2015 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/client/empty_cursor_filter.h"
#include <ostream>
#include "remoting/proto/control.pb.h"
#include "remoting/protocol/cursor_shape_stub.h"
#include "remoting/protocol/protocol_mock_objects.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace remoting {
namespace {
// Chromoting cursors are always RGBA.
const int kBytesPerPixel = 4;
const int kTestCursorWidth = 100;
const int kTestCursorHeight = 64;
const int kTestCursorHotspotX = 10;
const int kTestCursorHotspotY = 30;
const int kTestCursorDataSize =
kTestCursorWidth * kTestCursorHeight * kBytesPerPixel;
protocol::CursorShapeInfo CreateTransparentCursorShape() {
protocol::CursorShapeInfo transparent_cursor;
transparent_cursor.set_width(kTestCursorWidth);
transparent_cursor.set_height(kTestCursorHeight);
transparent_cursor.set_hotspot_x(kTestCursorHotspotX);
transparent_cursor.set_hotspot_y(kTestCursorHotspotY);
transparent_cursor.mutable_data()->resize(kTestCursorDataSize, 0);
return transparent_cursor;
}
protocol::CursorShapeInfo CreateOpaqueCursorShape() {
protocol::CursorShapeInfo cursor = CreateTransparentCursorShape();
cursor.mutable_data()->assign(kTestCursorDataSize, 0x01);
return cursor;
}
MATCHER_P(EqualsCursorShape, cursor_shape, "") {
// TODO(wez): Should not assume that all fields were set.
return arg.data() == cursor_shape.data() &&
arg.width() == cursor_shape.width() &&
arg.height() == cursor_shape.height() &&
arg.hotspot_x() == cursor_shape.hotspot_x() &&
arg.hotspot_y() == cursor_shape.hotspot_y();
}
} // namespace
namespace protocol {
// This pretty-printer must be defined under remoting::protocol to be used.
::std::ostream& operator<<(::std::ostream& os, const CursorShapeInfo& cursor) {
return os << "[w:" << cursor.width() << ", h:" << cursor.height()
<< ", h.x:" << cursor.hotspot_x() << ", h.y:" << cursor.hotspot_y()
<< ", data.size:" << cursor.data().size() << "]";
}
} // namespace protocol
// Verify that EmptyCursorShape() generates a normalized empty cursor.
TEST(EmptyCursorFilterTest, EmptyCursorShape) {
const protocol::CursorShapeInfo& empty_cursor = EmptyCursorShape();
// TODO(wez): Replace these individual asserts with IsCursorShapeValid()?
ASSERT_TRUE(empty_cursor.has_data());
ASSERT_TRUE(empty_cursor.has_width());
ASSERT_TRUE(empty_cursor.has_height());
ASSERT_TRUE(empty_cursor.has_hotspot_x());
ASSERT_TRUE(empty_cursor.has_hotspot_y());
EXPECT_EQ(0, empty_cursor.width());
EXPECT_EQ(0, empty_cursor.height());
EXPECT_EQ(0, empty_cursor.hotspot_x());
EXPECT_EQ(0, empty_cursor.hotspot_y());
EXPECT_TRUE(empty_cursor.data().empty());
}
// Verify that IsCursorShapeEmpty returns true only for normalized empty
// cursors, not for opaque or transparent non-empty cursors.
TEST(EmptyCursorFilterTest, IsCursorShapeEmpty) {
const protocol::CursorShapeInfo& kEmptyCursor = EmptyCursorShape();
EXPECT_TRUE(IsCursorShapeEmpty(kEmptyCursor));
const protocol::CursorShapeInfo& kOpaqueCursor = CreateOpaqueCursorShape();
EXPECT_FALSE(IsCursorShapeEmpty(kOpaqueCursor));
const protocol::CursorShapeInfo& kTransparentCursor =
CreateTransparentCursorShape();
EXPECT_FALSE(IsCursorShapeEmpty(kTransparentCursor));
}
// Verify that EmptyCursorFilter behaves correctly for normalized empty cursors.
TEST(EmptyCursorFilterTest, EmptyCursor) {
const protocol::CursorShapeInfo& kEmptyCursor = EmptyCursorShape();
protocol::MockCursorShapeStub cursor_stub;
EmptyCursorFilter cursor_filter(&cursor_stub);
EXPECT_CALL(cursor_stub, SetCursorShape(EqualsCursorShape(kEmptyCursor)));
cursor_filter.SetCursorShape(kEmptyCursor);
}
// Verify that EmptyCursorFilter turns transparent cursors into empty ones.
TEST(EmptyCursorFilterTest, TransparentCursor) {
const protocol::CursorShapeInfo& kEmptyCursor = EmptyCursorShape();
protocol::MockCursorShapeStub cursor_stub;
EmptyCursorFilter cursor_filter(&cursor_stub);
EXPECT_CALL(cursor_stub, SetCursorShape(EqualsCursorShape(kEmptyCursor)));
cursor_filter.SetCursorShape(CreateTransparentCursorShape());
}
// Verify that EmptyCursorFilter leaves non-transparent cursors alone.
TEST(EmptyCursorFilterTest, NonTransparentCursor) {
const protocol::CursorShapeInfo& kOpaqueCursor = CreateOpaqueCursorShape();
protocol::MockCursorShapeStub cursor_stub;
EmptyCursorFilter cursor_filter(&cursor_stub);
EXPECT_CALL(cursor_stub, SetCursorShape(EqualsCursorShape(kOpaqueCursor)));
cursor_filter.SetCursorShape(kOpaqueCursor);
}
} // namespace remoting

@ -1,92 +0,0 @@
// Copyright 2018 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/client/feedback_data.h"
#include "base/values.h"
#include "remoting/base/name_value_map.h"
namespace remoting {
namespace {
const NameMapElement<FeedbackData::Key> kFeedbackDataKeyNames[]{
{FeedbackData::Key::SESSION_PREVIOUS_STATE, "session-previous-state"},
{FeedbackData::Key::SESSION_STATE, "session-state"},
{FeedbackData::Key::SESSION_ERROR, "session-error"},
{FeedbackData::Key::SESSION_MODE, "session-mode"},
{FeedbackData::Key::SESSION_HOST_OS, "session-host-os"},
{FeedbackData::Key::SESSION_HOST_OS_VERSION, "session-host-os-version"},
{FeedbackData::Key::SESSION_HOST_VERSION, "session-host-version"},
{FeedbackData::Key::SESSION_CREDENTIALS_TYPE, "session-credentials-type"},
// TODO(yuweih): Collect session info for these fields.
{FeedbackData::Key::SESSION_PERFORMANCE_STATS, "session-performance-stats"},
{FeedbackData::Key::SESSION_PEER_CONNECTION_STATS,
"session-peer-connection-stats"},
};
template <typename EnumType>
void SetEnumIfNotEmpty(std::map<FeedbackData::Key, std::string>* data,
FeedbackData::Key key,
const ChromotingEvent& event,
const std::string& event_key) {
const base::Value* value = event.GetValue(event_key);
if (!value) {
return;
}
auto enum_value = static_cast<EnumType>(value->GetInt());
const char* string_value = ChromotingEvent::EnumToString(enum_value);
DCHECK(string_value);
(*data)[key] = string_value;
}
void SetStringIfNotEmpty(std::map<FeedbackData::Key, std::string>* data,
FeedbackData::Key key,
const ChromotingEvent& event,
const std::string& event_key) {
const base::Value* value = event.GetValue(event_key);
if (!value) {
return;
}
(*data)[key] = value->GetString();
}
} // namespace
FeedbackData::FeedbackData() {}
FeedbackData::~FeedbackData() {}
void FeedbackData::SetData(Key key, const std::string& data) {
data_[key] = data;
}
void FeedbackData::FillWithChromotingEvent(const ChromotingEvent& event) {
SetEnumIfNotEmpty<ChromotingEvent::SessionState>(
&data_, Key::SESSION_PREVIOUS_STATE, event,
ChromotingEvent::kPreviousSessionStateKey);
SetEnumIfNotEmpty<ChromotingEvent::SessionState>(
&data_, Key::SESSION_STATE, event, ChromotingEvent::kSessionStateKey);
SetEnumIfNotEmpty<ChromotingEvent::ConnectionError>(
&data_, Key::SESSION_ERROR, event, ChromotingEvent::kConnectionErrorKey);
SetEnumIfNotEmpty<ChromotingEvent::Mode>(&data_, Key::SESSION_MODE, event,
ChromotingEvent::kModeKey);
SetEnumIfNotEmpty<ChromotingEvent::Os>(&data_, Key::SESSION_HOST_OS, event,
ChromotingEvent::kHostOsKey);
SetEnumIfNotEmpty<ChromotingEvent::AuthMethod>(
&data_, Key::SESSION_CREDENTIALS_TYPE, event,
ChromotingEvent::kAuthMethodKey);
SetStringIfNotEmpty(&data_, Key::SESSION_HOST_OS_VERSION, event,
ChromotingEvent::kHostOsVersionKey);
SetStringIfNotEmpty(&data_, Key::SESSION_HOST_VERSION, event,
ChromotingEvent::kHostVersionKey);
}
// static
std::string FeedbackData::KeyToString(Key key) {
return ValueToName(kFeedbackDataKeyNames, key);
}
} // namespace remoting

@ -1,52 +0,0 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_FEEDBACK_DATA_H_
#define REMOTING_CLIENT_FEEDBACK_DATA_H_
#include <map>
#include <string>
#include "remoting/base/chromoting_event.h"
namespace remoting {
// Class that stores additional data to be sent with the user feedback.
class FeedbackData {
public:
enum class Key {
SESSION_PREVIOUS_STATE,
SESSION_STATE,
SESSION_ERROR,
SESSION_MODE,
SESSION_CREDENTIALS_TYPE,
SESSION_HOST_OS,
SESSION_HOST_OS_VERSION,
SESSION_HOST_VERSION,
SESSION_PERFORMANCE_STATS,
SESSION_PEER_CONNECTION_STATS,
};
FeedbackData();
FeedbackData(const FeedbackData&) = delete;
FeedbackData& operator=(const FeedbackData&) = delete;
~FeedbackData();
void SetData(Key key, const std::string& data);
void FillWithChromotingEvent(const ChromotingEvent& event);
const std::map<Key, std::string>& data() const { return data_; }
static std::string KeyToString(Key key);
private:
std::map<Key, std::string> data_;
};
} // namespace remoting
#endif // REMOTING_CLIENT_FEEDBACK_DATA_H_

@ -1,291 +0,0 @@
// Copyright 2017 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/client/gesture_interpreter.h"
#include <memory>
#include "base/functional/bind.h"
#include "base/time/time.h"
#include "remoting/client/chromoting_session.h"
#include "remoting/client/display/renderer_proxy.h"
#include "remoting/client/input/direct_touch_input_strategy.h"
#include "remoting/client/input/trackpad_input_strategy.h"
namespace {
const float kOneFingerFlingTimeConstant = 180.f;
const float kScrollFlingTimeConstant = 250.f;
} // namespace
namespace remoting {
GestureInterpreter::GestureInterpreter()
// TODO(yuweih): These animations are better to take GetWeakPtr().
: pan_animation_(
kOneFingerFlingTimeConstant,
base::BindRepeating(&GestureInterpreter::PanWithoutAbortAnimations,
base::Unretained(this))),
scroll_animation_(
kScrollFlingTimeConstant,
base::BindRepeating(&GestureInterpreter::ScrollWithoutAbortAnimations,
base::Unretained(this))) {}
GestureInterpreter::~GestureInterpreter() = default;
void GestureInterpreter::SetContext(RendererProxy* renderer,
ChromotingSession* input_stub) {
renderer_ = renderer;
input_stub_ = input_stub;
auto transformation_callback =
renderer_ ? base::BindRepeating(&RendererProxy::SetTransformation,
base::Unretained(renderer_))
: DesktopViewport::TransformationCallback();
viewport_.RegisterOnTransformationChangedCallback(transformation_callback,
true);
}
void GestureInterpreter::SetInputMode(InputMode mode) {
switch (mode) {
case DIRECT_INPUT_MODE:
input_strategy_ = std::make_unique<DirectTouchInputStrategy>();
break;
case TRACKPAD_INPUT_MODE:
input_strategy_ = std::make_unique<TrackpadInputStrategy>(viewport_);
break;
default:
NOTREACHED();
}
input_mode_ = mode;
if (!renderer_) {
return;
}
renderer_->SetCursorVisibility(input_strategy_->IsCursorVisible());
ViewMatrix::Point cursor_position = input_strategy_->GetCursorPosition();
renderer_->SetCursorPosition(cursor_position.x, cursor_position.y);
}
GestureInterpreter::InputMode GestureInterpreter::GetInputMode() const {
return input_mode_;
}
void GestureInterpreter::Zoom(float pivot_x,
float pivot_y,
float scale,
GestureState state) {
AbortAnimations();
SetGestureInProgress(TouchInputStrategy::ZOOM, state != GESTURE_ENDED);
if (viewport_.IsViewportReady()) {
input_strategy_->HandleZoom({pivot_x, pivot_y}, scale, &viewport_);
}
}
void GestureInterpreter::Pan(float translation_x, float translation_y) {
AbortAnimations();
PanWithoutAbortAnimations(translation_x, translation_y);
}
void GestureInterpreter::Tap(float x, float y) {
AbortAnimations();
InjectMouseClick(x, y, protocol::MouseEvent_MouseButton_BUTTON_LEFT);
}
void GestureInterpreter::TwoFingerTap(float x, float y) {
AbortAnimations();
InjectMouseClick(x, y, protocol::MouseEvent_MouseButton_BUTTON_RIGHT);
}
void GestureInterpreter::ThreeFingerTap(float x, float y) {
AbortAnimations();
InjectMouseClick(x, y, protocol::MouseEvent_MouseButton_BUTTON_MIDDLE);
}
void GestureInterpreter::Drag(float x, float y, GestureState state) {
AbortAnimations();
bool is_dragging_mode = state != GESTURE_ENDED;
SetGestureInProgress(TouchInputStrategy::DRAG, is_dragging_mode);
if (!input_stub_ || !viewport_.IsViewportReady() ||
!input_strategy_->TrackTouchInput({x, y}, viewport_)) {
return;
}
ViewMatrix::Point cursor_position = input_strategy_->GetCursorPosition();
switch (state) {
case GESTURE_BEGAN:
StartInputFeedback(cursor_position.x, cursor_position.y,
TouchInputStrategy::DRAG_FEEDBACK);
input_stub_->SendMouseEvent(cursor_position.x, cursor_position.y,
protocol::MouseEvent_MouseButton_BUTTON_LEFT,
true);
break;
case GESTURE_CHANGED:
InjectCursorPosition(cursor_position.x, cursor_position.y);
break;
case GESTURE_ENDED:
input_stub_->SendMouseEvent(cursor_position.x, cursor_position.y,
protocol::MouseEvent_MouseButton_BUTTON_LEFT,
false);
break;
default:
NOTREACHED();
}
}
void GestureInterpreter::OneFingerFling(float velocity_x, float velocity_y) {
AbortAnimations();
pan_animation_.SetVelocity(velocity_x, velocity_y);
pan_animation_.Tick();
}
void GestureInterpreter::Scroll(float x, float y, float dx, float dy) {
AbortAnimations();
if (!viewport_.IsViewportReady() ||
!input_strategy_->TrackTouchInput({x, y}, viewport_)) {
return;
}
ViewMatrix::Point cursor_position = input_strategy_->GetCursorPosition();
// Inject the cursor position to the host so that scrolling can happen on the
// right place.
InjectCursorPosition(cursor_position.x, cursor_position.y);
ScrollWithoutAbortAnimations(dx, dy);
}
void GestureInterpreter::ScrollWithVelocity(float velocity_x,
float velocity_y) {
AbortAnimations();
scroll_animation_.SetVelocity(velocity_x, velocity_y);
scroll_animation_.Tick();
}
void GestureInterpreter::ProcessAnimations() {
pan_animation_.Tick();
// TODO(yuweih): It's probably not right to handle host side virtual scroll
// momentum in the renderer's callback.
scroll_animation_.Tick();
}
void GestureInterpreter::OnSurfaceSizeChanged(int width, int height) {
viewport_.SetSurfaceSize(width, height);
if (viewport_.IsViewportReady()) {
input_strategy_->FocusViewportOnCursor(&viewport_);
}
}
void GestureInterpreter::OnDesktopSizeChanged(int width, int height) {
viewport_.SetDesktopSize(width, height);
if (viewport_.IsViewportReady()) {
input_strategy_->FocusViewportOnCursor(&viewport_);
}
}
void GestureInterpreter::OnSafeInsetsChanged(int left,
int top,
int right,
int bottom) {
viewport_.SetSafeInsets(left, top, right, bottom);
if (viewport_.IsViewportReady()) {
input_strategy_->FocusViewportOnCursor(&viewport_);
}
}
base::WeakPtr<GestureInterpreter> GestureInterpreter::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void GestureInterpreter::PanWithoutAbortAnimations(float translation_x,
float translation_y) {
if (viewport_.IsViewportReady() &&
input_strategy_->HandlePan({translation_x, translation_y},
gesture_in_progress_, &viewport_)) {
// Cursor position changed.
ViewMatrix::Point cursor_position = input_strategy_->GetCursorPosition();
if (gesture_in_progress_ != TouchInputStrategy::DRAG) {
// Drag() will inject the position so don't need to do that in that case.
InjectCursorPosition(cursor_position.x, cursor_position.y);
}
if (renderer_) {
renderer_->SetCursorPosition(cursor_position.x, cursor_position.y);
}
}
}
void GestureInterpreter::InjectCursorPosition(float x, float y) {
if (!input_stub_) {
return;
}
input_stub_->SendMouseEvent(
x, y, protocol::MouseEvent_MouseButton_BUTTON_UNDEFINED, false);
}
void GestureInterpreter::ScrollWithoutAbortAnimations(float dx, float dy) {
if (!input_stub_ || !viewport_.IsViewportReady()) {
return;
}
ViewMatrix::Point desktopDelta =
input_strategy_->MapScreenVectorToDesktop({dx, dy}, viewport_);
input_stub_->SendMouseWheelEvent(desktopDelta.x, desktopDelta.y);
}
void GestureInterpreter::AbortAnimations() {
pan_animation_.Abort();
scroll_animation_.Abort();
}
void GestureInterpreter::InjectMouseClick(
float touch_x,
float touch_y,
protocol::MouseEvent_MouseButton button) {
if (!input_stub_ || !viewport_.IsViewportReady() ||
!input_strategy_->TrackTouchInput({touch_x, touch_y}, viewport_)) {
return;
}
ViewMatrix::Point cursor_position = input_strategy_->GetCursorPosition();
StartInputFeedback(cursor_position.x, cursor_position.y,
TouchInputStrategy::TAP_FEEDBACK);
input_stub_->SendMouseEvent(cursor_position.x, cursor_position.y, button,
true);
input_stub_->SendMouseEvent(cursor_position.x, cursor_position.y, button,
false);
}
void GestureInterpreter::SetGestureInProgress(
TouchInputStrategy::Gesture gesture,
bool is_in_progress) {
if (!is_in_progress && gesture_in_progress_ == gesture) {
gesture_in_progress_ = TouchInputStrategy::NONE;
return;
}
gesture_in_progress_ = gesture;
}
void GestureInterpreter::StartInputFeedback(
float cursor_x,
float cursor_y,
TouchInputStrategy::TouchFeedbackType feedback_type) {
// This radius is on the view's coordinates. Need to be converted to desktop
// coordinate.
float feedback_radius = input_strategy_->GetFeedbackRadius(feedback_type);
if (feedback_radius > 0) {
// TODO(yuweih): The renderer takes diameter as parameter. Consider moving
// the *2 logic inside the renderer.
float diameter_on_desktop =
2.f * feedback_radius / viewport_.GetTransformation().GetScale();
if (renderer_) {
renderer_->StartInputFeedback(cursor_x, cursor_y, diameter_on_desktop);
}
}
}
} // namespace remoting

@ -1,130 +0,0 @@
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_GESTURE_INTERPRETER_H_
#define REMOTING_CLIENT_GESTURE_INTERPRETER_H_
#include <memory>
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "remoting/client/input/touch_input_strategy.h"
#include "remoting/client/ui/desktop_viewport.h"
#include "remoting/client/ui/fling_animation.h"
#include "remoting/proto/event.pb.h"
namespace remoting {
class ChromotingSession;
class RendererProxy;
// This is a class for interpreting a raw touch input into actions like moving
// the viewport and injecting mouse clicks.
class GestureInterpreter {
public:
enum GestureState { GESTURE_BEGAN, GESTURE_CHANGED, GESTURE_ENDED };
enum InputMode {
UNDEFINED_INPUT_MODE,
DIRECT_INPUT_MODE,
TRACKPAD_INPUT_MODE
};
GestureInterpreter();
GestureInterpreter(const GestureInterpreter&) = delete;
GestureInterpreter& operator=(const GestureInterpreter&) = delete;
~GestureInterpreter();
// Sets the context for the interpreter. Both arguments are nullable. If both
// are nullptr then methods below will have no effect.
void SetContext(RendererProxy* renderer, ChromotingSession* input_stub);
// Must be called right after the renderer is ready.
void SetInputMode(InputMode mode);
// Returns the current input mode.
InputMode GetInputMode() const;
// Coordinates of the OpenGL view surface will be used.
// Called during a two-finger pinching gesture. This can happen in conjunction
// with Pan().
void Zoom(float pivot_x, float pivot_y, float scale, GestureState state);
// Called whenever the user did a pan gesture. It can be one-finger pan, no
// matter dragging in on or not, or two-finger pan in conjunction with zoom.
// Two-finger pan without zoom is consider a scroll gesture.
void Pan(float translation_x, float translation_y);
// Called when the user did a one-finger tap.
void Tap(float x, float y);
void TwoFingerTap(float x, float y);
void ThreeFingerTap(float x, float y);
// Caller is expected to call both Pan() and Drag() when dragging is in
// progress.
void Drag(float x, float y, GestureState state);
// Called when the user has just done a one-finger pan (no dragging or
// zooming) and the pan gesture still has some final velocity.
void OneFingerFling(float velocity_x, float velocity_y);
// Called during a two-finger scroll (panning without zooming) gesture.
void Scroll(float x, float y, float dx, float dy);
// Called when the user has just done a scroll gesture and the scroll gesture
// still has some final velocity.
void ScrollWithVelocity(float velocity_x, float velocity_y);
// Called to process one animation frame.
void ProcessAnimations();
void OnSurfaceSizeChanged(int width, int height);
void OnDesktopSizeChanged(int width, int height);
void OnSafeInsetsChanged(int left, int top, int right, int bottom);
base::WeakPtr<GestureInterpreter> GetWeakPtr();
private:
void PanWithoutAbortAnimations(float translation_x, float translation_y);
void ScrollWithoutAbortAnimations(float dx, float dy);
void AbortAnimations();
// Injects the mouse click event and shows the touch feedback.
void InjectMouseClick(float touch_x,
float touch_y,
protocol::MouseEvent_MouseButton button);
void InjectCursorPosition(float x, float y);
void SetGestureInProgress(TouchInputStrategy::Gesture gesture,
bool is_in_progress);
// Starts the given feedback at (cursor_x, cursor_y) if the feedback radius
// is non-zero.
void StartInputFeedback(float cursor_x,
float cursor_y,
TouchInputStrategy::TouchFeedbackType feedback_type);
InputMode input_mode_ = UNDEFINED_INPUT_MODE;
std::unique_ptr<TouchInputStrategy> input_strategy_;
DesktopViewport viewport_;
raw_ptr<RendererProxy> renderer_ = nullptr;
raw_ptr<ChromotingSession> input_stub_ = nullptr;
TouchInputStrategy::Gesture gesture_in_progress_;
FlingAnimation pan_animation_;
FlingAnimation scroll_animation_;
base::WeakPtrFactory<GestureInterpreter> weak_factory_{this};
};
} // namespace remoting
#endif // REMOTING_CLIENT_GESTURE_INTERPRETER_H_

@ -1,28 +0,0 @@
// Copyright 2017 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/client/host_experiment_sender.h"
#include "remoting/base/constants.h"
namespace remoting {
HostExperimentSender::HostExperimentSender(const std::string& experiment_config)
: experiment_config_(experiment_config) {}
std::unique_ptr<jingle_xmpp::XmlElement> HostExperimentSender::GetNextMessage() {
if (message_sent_ || experiment_config_.empty()) {
return nullptr;
}
message_sent_ = true;
std::unique_ptr<jingle_xmpp::XmlElement> configuration(new jingle_xmpp::XmlElement(
jingle_xmpp::QName(kChromotingXmlNamespace, "host-configuration")));
configuration->SetBodyText(experiment_config_);
return configuration;
}
void HostExperimentSender::OnIncomingMessage(
const jingle_xmpp::XmlElement& attachments) {}
} // namespace remoting

@ -1,35 +0,0 @@
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_HOST_EXPERIMENT_SENDER_H_
#define REMOTING_CLIENT_HOST_EXPERIMENT_SENDER_H_
#include <memory>
#include <string>
#include "remoting/protocol/session_plugin.h"
#include "third_party/libjingle_xmpp/xmllite/xmlelement.h"
namespace remoting {
// A SessionPlugin implementation to send host configuration to the host.
// Currently only WebApp sets the experiment configuration.
// This is a temporary solution until we have more permanent approach
// implemented, which should take host attributes into account.
class HostExperimentSender : public protocol::SessionPlugin {
public:
HostExperimentSender(const std::string& experiment_config);
// protocol::SessionPlugin implementation.
std::unique_ptr<jingle_xmpp::XmlElement> GetNextMessage() override;
void OnIncomingMessage(const jingle_xmpp::XmlElement& attachments) override;
private:
const std::string experiment_config_;
bool message_sent_ = false;
};
} // namespace remoting
#endif // REMOTING_CLIENT_HOST_EXPERIMENT_SENDER_H_

@ -1,68 +0,0 @@
// Copyright 2018 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/client/in_memory_log_handler.h"
#include "base/check.h"
#include "base/containers/ring_buffer.h"
#include "base/logging.h"
#include "base/synchronization/lock.h"
namespace remoting {
namespace {
constexpr size_t kMaxNumberOfLogs = 1000;
struct LogHandlerContext {
base::Lock lock;
base::RingBuffer<std::string, kMaxNumberOfLogs> buffer;
};
// Leaky.
LogHandlerContext* g_log_handler_context = nullptr;
bool HandleLogMessage(int severity,
const char* file,
int line,
size_t message_start,
const std::string& str) {
base::AutoLock auto_lock(g_log_handler_context->lock);
g_log_handler_context->buffer.SaveToBuffer(str);
// Pass log messages through the default logging pipeline.
return false;
}
} // namespace
// static
void InMemoryLogHandler::Register() {
DCHECK(!g_log_handler_context);
DCHECK(!logging::GetLogMessageHandler())
<< "Log message handler has already been set.";
g_log_handler_context = new LogHandlerContext();
base::AutoLock auto_lock(g_log_handler_context->lock);
logging::SetLogMessageHandler(&HandleLogMessage);
}
// static
std::string InMemoryLogHandler::GetInMemoryLogs() {
std::string output;
base::AutoLock auto_lock(g_log_handler_context->lock);
for (auto iter = g_log_handler_context->buffer.Begin(); iter; ++iter) {
if (iter != g_log_handler_context->buffer.Begin()) {
output += '\n';
}
// *iter returns a const std::string*.
output += **iter;
}
return output;
}
} // namespace remoting

@ -1,30 +0,0 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_IN_MEMORY_LOG_HANDLER_H_
#define REMOTING_CLIENT_IN_MEMORY_LOG_HANDLER_H_
#include <string>
namespace remoting {
// Class for capturing logs in memory before printing out.
class InMemoryLogHandler {
public:
InMemoryLogHandler() = delete;
InMemoryLogHandler(const InMemoryLogHandler&) = delete;
InMemoryLogHandler& operator=(const InMemoryLogHandler&) = delete;
// Registers the log handler. This is not thread safe and should be called
// exactly once in the main function.
static void Register();
// Returns most recently captured logs (#lines <= kMaxNumberOfLogs) since the
// app is launched. This must be called after Register() is called.
static std::string GetInMemoryLogs();
};
} // namespace remoting
#endif // REMOTING_CLIENT_IN_MEMORY_LOG_HANDLER_H_

@ -1,114 +0,0 @@
# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
source_set("input") {
sources = [
"client_input_injector.h",
"direct_touch_input_strategy.cc",
"direct_touch_input_strategy.h",
"key_event_mapper.cc",
"key_event_mapper.h",
"keyboard_input_strategy.h",
"keyboard_interpreter.cc",
"keyboard_interpreter.h",
"keycode_map.cc",
"keycode_map.h",
"native_device_keymap.cc",
"native_device_keymap.h",
"text_keyboard_input_strategy.cc",
"text_keyboard_input_strategy.h",
"touch_input_scaler.cc",
"touch_input_scaler.h",
"touch_input_strategy.h",
"trackpad_input_strategy.cc",
"trackpad_input_strategy.h",
]
deps = [
":normalizing_input_filter",
"//remoting/base",
"//remoting/client/ui:ui_manipulation",
"//remoting/protocol",
"//third_party/webrtc_overrides:webrtc_component",
"//ui/events:dom_keycode_converter",
]
if (is_android) {
sources += [ "native_device_keymap_android.cc" ]
}
if (is_ios) {
sources += [ "native_device_keymap_ios.cc" ]
}
if (is_android || is_ios) {
sources -= [ "native_device_keymap.cc" ]
}
}
source_set("normalizing_input_filter") {
# Disabled the source filters because there are _mac files that need to
# be compiled on all platforms.
sources = [
"normalizing_input_filter_cros.cc",
"normalizing_input_filter_cros.h",
"normalizing_input_filter_mac.cc",
"normalizing_input_filter_mac.h",
"normalizing_input_filter_win.cc",
"normalizing_input_filter_win.h",
]
deps = [
"//remoting/base",
"//remoting/protocol",
"//third_party/webrtc_overrides:webrtc_component",
"//ui/events:dom_keycode_converter",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"key_event_mapper_unittest.cc",
"keycode_map_unittest.cc",
"touch_input_scaler_unittest.cc",
]
configs += [ "//remoting/build/config:version" ]
deps = [
":input",
":normalizing_input_filter_unit_tests",
"//base",
"//remoting/proto",
"//remoting/protocol:test_support",
"//testing/gmock",
"//testing/gtest",
"//third_party/webrtc_overrides:webrtc_component",
]
}
source_set("normalizing_input_filter_unit_tests") {
testonly = true
sources = [
"normalizing_input_filter_cros_unittest.cc",
"normalizing_input_filter_mac_unittest.cc",
"normalizing_input_filter_win_unittest.cc",
]
configs += [ "//remoting/build/config:version" ]
deps = [
":input",
"//remoting/client/input:normalizing_input_filter",
"//remoting/proto",
"//remoting/protocol:test_support",
"//testing/gmock",
"//testing/gtest",
"//third_party/webrtc_overrides:webrtc_component",
"//ui/events:dom_keycode_converter",
]
}

@ -1,27 +0,0 @@
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_INPUT_CLIENT_INPUT_INJECTOR_H_
#define REMOTING_CLIENT_INPUT_CLIENT_INPUT_INJECTOR_H_
#include <stdint.h>
#include <string>
namespace remoting {
// This is an interface used by key input strategies to send processed key
// events to the client side input injector.
class ClientInputInjector {
public:
virtual ~ClientInputInjector() {}
// Sends the provided keyboard scan code to the host.
virtual bool SendKeyEvent(int scan_code, int key_code, bool key_down) = 0;
// Send utf8 encoded text to the host.
virtual void SendTextEvent(const std::string& text) = 0;
};
} // namespace remoting
#endif // REMOTING_CLIENT_INPUT_CLIENT_INPUT_INJECTOR_H_

@ -1,88 +0,0 @@
// Copyright 2017 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/client/input/direct_touch_input_strategy.h"
#include "remoting/client/ui/desktop_viewport.h"
namespace remoting {
namespace {
const float kTapFeedbackRadius = 25.f;
const float kDragFeedbackRadius = 55.f;
} // namespace
DirectTouchInputStrategy::DirectTouchInputStrategy() = default;
DirectTouchInputStrategy::~DirectTouchInputStrategy() = default;
void DirectTouchInputStrategy::HandleZoom(const ViewMatrix::Point& pivot,
float scale,
DesktopViewport* viewport) {
viewport->ScaleDesktop(pivot.x, pivot.y, scale);
}
bool DirectTouchInputStrategy::HandlePan(
const ViewMatrix::Vector2D& translation,
Gesture simultaneous_gesture,
DesktopViewport* viewport) {
if (simultaneous_gesture == DRAG) {
// If the user is dragging something, we should synchronize the movement
// with the object that the user is trying to move on the desktop, rather
// than moving the desktop around.
ViewMatrix::Vector2D viewport_movement =
viewport->GetTransformation().Invert().MapVector(translation);
viewport->MoveViewport(viewport_movement.x, viewport_movement.y);
return false;
}
viewport->MoveDesktop(translation.x, translation.y);
return false;
}
bool DirectTouchInputStrategy::TrackTouchInput(
const ViewMatrix::Point& touch_point,
const DesktopViewport& viewport) {
ViewMatrix::Point new_position =
viewport.GetTransformation().Invert().MapPoint(touch_point);
if (!viewport.IsPointWithinDesktopBounds(new_position)) {
return false;
}
cursor_position_ = new_position;
return true;
}
ViewMatrix::Point DirectTouchInputStrategy::GetCursorPosition() const {
return cursor_position_;
}
void DirectTouchInputStrategy::FocusViewportOnCursor(
DesktopViewport* viewport) const {
// No need to focus on the previous touch point.
}
ViewMatrix::Vector2D DirectTouchInputStrategy::MapScreenVectorToDesktop(
const ViewMatrix::Vector2D& delta,
const DesktopViewport& viewport) const {
return viewport.GetTransformation().Invert().MapVector(delta);
}
float DirectTouchInputStrategy::GetFeedbackRadius(
TouchFeedbackType type) const {
switch (type) {
case TouchFeedbackType::TAP_FEEDBACK:
return kTapFeedbackRadius;
case TouchFeedbackType::DRAG_FEEDBACK:
return kDragFeedbackRadius;
}
NOTREACHED();
}
bool DirectTouchInputStrategy::IsCursorVisible() const {
return false;
}
} // namespace remoting

@ -1,54 +0,0 @@
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_INPUT_DIRECT_TOUCH_INPUT_STRATEGY_H_
#define REMOTING_CLIENT_INPUT_DIRECT_TOUCH_INPUT_STRATEGY_H_
#include "remoting/client/input/touch_input_strategy.h"
namespace remoting {
// This strategy directly translates all operations on the OpenGL view into
// corresponding operations on the desktop. It doesn't maintain the cursor
// positions separately -- the positions come from the location of the touch.
class DirectTouchInputStrategy : public TouchInputStrategy {
public:
DirectTouchInputStrategy();
~DirectTouchInputStrategy() override;
// TouchInputStrategy overrides.
void HandleZoom(const ViewMatrix::Point& pivot,
float scale,
DesktopViewport* viewport) override;
bool HandlePan(const ViewMatrix::Vector2D& translation,
Gesture simultaneous_gesture,
DesktopViewport* viewport) override;
bool TrackTouchInput(const ViewMatrix::Point& touch_point,
const DesktopViewport& viewport) override;
ViewMatrix::Point GetCursorPosition() const override;
void FocusViewportOnCursor(DesktopViewport* viewport) const override;
ViewMatrix::Vector2D MapScreenVectorToDesktop(
const ViewMatrix::Vector2D& delta,
const DesktopViewport& viewport) const override;
float GetFeedbackRadius(TouchFeedbackType type) const override;
bool IsCursorVisible() const override;
private:
ViewMatrix::Point cursor_position_{0.f, 0.f};
// TouchInputStrategy is neither copyable nor movable.
DirectTouchInputStrategy(const DirectTouchInputStrategy&) = delete;
DirectTouchInputStrategy& operator=(const DirectTouchInputStrategy&) = delete;
};
} // namespace remoting
#endif // REMOTING_CLIENT_INPUT_DIRECT_TOUCH_INPUT_STRATEGY_H_

@ -1,61 +0,0 @@
// Copyright 2012 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/client/input/key_event_mapper.h"
#include "base/containers/contains.h"
#include "remoting/proto/event.pb.h"
namespace remoting {
KeyEventMapper::KeyEventMapper() = default;
KeyEventMapper::KeyEventMapper(InputStub* stub) : protocol::InputFilter(stub) {}
KeyEventMapper::~KeyEventMapper() = default;
void KeyEventMapper::SetTrapCallback(KeyTrapCallback callback) {
trap_callback = callback;
}
void KeyEventMapper::TrapKey(uint32_t usb_keycode, bool trap_key) {
if (trap_key) {
trapped_keys.insert(usb_keycode);
} else {
trapped_keys.erase(usb_keycode);
}
}
void KeyEventMapper::RemapKey(uint32_t in_usb_keycode,
uint32_t out_usb_keycode) {
if (in_usb_keycode == out_usb_keycode) {
mapped_keys.erase(in_usb_keycode);
} else {
mapped_keys[in_usb_keycode] = out_usb_keycode;
}
}
void KeyEventMapper::InjectKeyEvent(const protocol::KeyEvent& event) {
if (event.has_usb_keycode()) {
// Deliver trapped keys to the callback, not the next stub.
if (!trap_callback.is_null() && event.has_pressed() &&
base::Contains(trapped_keys, event.usb_keycode())) {
trap_callback.Run(event);
return;
}
// Re-map mapped keys to the new value before passing them on.
auto mapped = mapped_keys.find(event.usb_keycode());
if (mapped != mapped_keys.end()) {
protocol::KeyEvent new_event(event);
new_event.set_usb_keycode(mapped->second);
InputFilter::InjectKeyEvent(new_event);
return;
}
}
InputFilter::InjectKeyEvent(event);
}
} // namespace remoting

@ -1,58 +0,0 @@
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_INPUT_KEY_EVENT_MAPPER_H_
#define REMOTING_CLIENT_INPUT_KEY_EVENT_MAPPER_H_
#include <stdint.h>
#include <map>
#include <set>
#include "base/compiler_specific.h"
#include "base/functional/callback.h"
#include "remoting/protocol/input_filter.h"
namespace remoting {
// Filtering InputStub which can be used to re-map the USB keycodes of events
// before they are passed on to the next InputStub in the chain, or to trap
// events with specific USB keycodes for special handling.
class KeyEventMapper : public protocol::InputFilter {
public:
KeyEventMapper();
explicit KeyEventMapper(InputStub* input_stub);
KeyEventMapper(const KeyEventMapper&) = delete;
KeyEventMapper& operator=(const KeyEventMapper&) = delete;
~KeyEventMapper() override;
// Callback type for use with SetTrapCallback(), below.
typedef base::RepeatingCallback<void(const protocol::KeyEvent&)>
KeyTrapCallback;
// Sets the callback to which trapped keys will be delivered.
void SetTrapCallback(KeyTrapCallback callback);
// Causes events matching |usb_keycode| to be delivered to the trap callback.
// Trapped events are not dispatched to the next InputStub in the chain.
void TrapKey(uint32_t usb_keycode, bool trap_key);
// Causes events matching |in_usb_keycode| to be mapped to |out_usb_keycode|.
// Keys are remapped at most once. Traps are processed before remapping.
void RemapKey(uint32_t in_usb_keycode, uint32_t out_usb_keycode);
// InputFilter overrides.
void InjectKeyEvent(const protocol::KeyEvent& event) override;
private:
std::map<uint32_t, uint32_t> mapped_keys;
std::set<uint32_t> trapped_keys;
KeyTrapCallback trap_callback;
};
} // namespace remoting
#endif // REMOTING_CLIENT_INPUT_KEY_EVENT_MAPPER_H_

@ -1,147 +0,0 @@
// Copyright 2012 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/client/input/key_event_mapper.h"
#include <stdint.h>
#include "base/functional/bind.h"
#include "remoting/proto/event.pb.h"
#include "remoting/protocol/protocol_mock_objects.h"
#include "remoting/protocol/test_event_matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::ExpectationSet;
using ::testing::InSequence;
namespace remoting {
using protocol::InputStub;
using protocol::KeyEvent;
using protocol::MockInputStub;
using protocol::test::EqualsKeyEventWithCapsLock;
static KeyEvent NewUsbEvent(uint32_t usb_keycode,
bool pressed,
uint32_t lock_states) {
KeyEvent event;
event.set_usb_keycode(usb_keycode);
event.set_pressed(pressed);
event.set_lock_states(lock_states);
return event;
}
static void PressAndReleaseUsb(InputStub* input_stub, uint32_t usb_keycode) {
input_stub->InjectKeyEvent(
NewUsbEvent(usb_keycode, true, KeyEvent::LOCK_STATES_CAPSLOCK));
input_stub->InjectKeyEvent(
NewUsbEvent(usb_keycode, false, KeyEvent::LOCK_STATES_CAPSLOCK));
}
static void InjectTestSequence(InputStub* input_stub) {
for (int i = 1; i <= 5; ++i)
PressAndReleaseUsb(input_stub, i);
}
// Verify that keys are passed through the KeyEventMapper by default.
TEST(KeyEventMapperTest, NoMappingOrTrapping) {
MockInputStub mock_stub;
KeyEventMapper event_mapper(&mock_stub);
{
InSequence s;
for (int i = 1; i <= 5; ++i) {
EXPECT_CALL(mock_stub,
InjectKeyEvent(EqualsKeyEventWithCapsLock(i, true)));
EXPECT_CALL(mock_stub,
InjectKeyEvent(EqualsKeyEventWithCapsLock(i, false)));
}
EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsKeyEventWithCapsLock(3, true)));
EXPECT_CALL(mock_stub,
InjectKeyEvent(EqualsKeyEventWithCapsLock(3, false)));
}
InjectTestSequence(&event_mapper);
PressAndReleaseUsb(&event_mapper, 3);
}
// Verify that USB keys are remapped at most once.
TEST(KeyEventMapperTest, RemapKeys) {
MockInputStub mock_stub;
KeyEventMapper event_mapper(&mock_stub);
event_mapper.RemapKey(3, 4);
event_mapper.RemapKey(4, 3);
event_mapper.RemapKey(5, 3);
{
InSequence s;
EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsKeyEventWithCapsLock(1, true)));
EXPECT_CALL(mock_stub,
InjectKeyEvent(EqualsKeyEventWithCapsLock(1, false)));
EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsKeyEventWithCapsLock(2, true)));
EXPECT_CALL(mock_stub,
InjectKeyEvent(EqualsKeyEventWithCapsLock(2, false)));
EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsKeyEventWithCapsLock(4, true)));
EXPECT_CALL(mock_stub,
InjectKeyEvent(EqualsKeyEventWithCapsLock(4, false)));
EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsKeyEventWithCapsLock(3, true)));
EXPECT_CALL(mock_stub,
InjectKeyEvent(EqualsKeyEventWithCapsLock(3, false)));
EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsKeyEventWithCapsLock(3, true)));
EXPECT_CALL(mock_stub,
InjectKeyEvent(EqualsKeyEventWithCapsLock(3, false)));
}
InjectTestSequence(&event_mapper);
}
static void HandleTrappedKey(MockInputStub* stub, const KeyEvent& event) {
stub->InjectKeyEvent(event);
}
// Verify that trapped and mapped USB keys are trapped but not remapped.
TEST(KeyEventMapperTest, TrapKeys) {
MockInputStub mock_stub;
MockInputStub trap_stub;
KeyEventMapper event_mapper(&mock_stub);
KeyEventMapper::KeyTrapCallback callback =
base::BindRepeating(&HandleTrappedKey, base::Unretained(&trap_stub));
event_mapper.SetTrapCallback(callback);
event_mapper.TrapKey(4, true);
event_mapper.TrapKey(5, true);
event_mapper.RemapKey(3, 4);
event_mapper.RemapKey(4, 3);
event_mapper.RemapKey(5, 3);
{
InSequence s;
EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsKeyEventWithCapsLock(1, true)));
EXPECT_CALL(mock_stub,
InjectKeyEvent(EqualsKeyEventWithCapsLock(1, false)));
EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsKeyEventWithCapsLock(2, true)));
EXPECT_CALL(mock_stub,
InjectKeyEvent(EqualsKeyEventWithCapsLock(2, false)));
EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsKeyEventWithCapsLock(4, true)));
EXPECT_CALL(mock_stub,
InjectKeyEvent(EqualsKeyEventWithCapsLock(4, false)));
EXPECT_CALL(trap_stub, InjectKeyEvent(EqualsKeyEventWithCapsLock(4, true)));
EXPECT_CALL(trap_stub,
InjectKeyEvent(EqualsKeyEventWithCapsLock(4, false)));
EXPECT_CALL(trap_stub, InjectKeyEvent(EqualsKeyEventWithCapsLock(5, true)));
EXPECT_CALL(trap_stub,
InjectKeyEvent(EqualsKeyEventWithCapsLock(5, false)));
}
InjectTestSequence(&event_mapper);
}
} // namespace remoting

@ -1,33 +0,0 @@
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_INPUT_KEYBOARD_INPUT_STRATEGY_H_
#define REMOTING_CLIENT_INPUT_KEYBOARD_INPUT_STRATEGY_H_
#include <stdint.h>
#include <string>
#include "base/containers/queue.h"
namespace remoting {
struct KeyEvent {
uint32_t keycode;
bool keydown;
};
// This is an interface used by |KeyboardInterpreter| to customize how keyboard
// input is handled.
class KeyboardInputStrategy {
public:
virtual ~KeyboardInputStrategy() {}
// Handle a text event.
virtual void HandleTextEvent(const std::string& text, uint8_t modifiers) = 0;
// Handle keys event as keycodes.
virtual void HandleKeysEvent(base::queue<KeyEvent> keys) = 0;
};
} // namespace remoting
#endif // REMOTING_CLIENT_INPUT_KEYBOARD_INPUT_STRATEGY_H_

@ -1,107 +0,0 @@
// Copyright 2017 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/client/input/keyboard_interpreter.h"
#include "base/check.h"
#include "remoting/client/input/keycode_map.h"
#include "remoting/client/input/text_keyboard_input_strategy.h"
#include "ui/events/keycodes/dom/dom_code.h"
namespace remoting {
KeyboardInterpreter::KeyboardInterpreter() = default;
KeyboardInterpreter::~KeyboardInterpreter() = default;
void KeyboardInterpreter::SetContext(ClientInputInjector* input_injector) {
// TODO(nicholss): This should be configurable.
if (input_injector) {
input_strategy_ =
std::make_unique<TextKeyboardInputStrategy>(input_injector);
} else {
input_strategy_.reset();
}
}
void KeyboardInterpreter::HandleKeypressEvent(const KeypressInfo& keypress) {
if (!input_strategy_) {
return;
}
DCHECK(keypress.dom_code != ui::DomCode::NONE);
base::queue<KeyEvent> keys;
if (keypress.modifiers & KeypressInfo::Modifier::SHIFT) {
keys.push({static_cast<uint32_t>(ui::DomCode::SHIFT_LEFT), true});
}
keys.push({static_cast<uint32_t>(keypress.dom_code), true});
keys.push({static_cast<uint32_t>(keypress.dom_code), false});
if (keypress.modifiers & KeypressInfo::Modifier::SHIFT) {
keys.push({static_cast<uint32_t>(ui::DomCode::SHIFT_LEFT), false});
}
input_strategy_->HandleKeysEvent(keys);
}
void KeyboardInterpreter::HandleTextEvent(const std::string& text,
uint8_t modifiers) {
if (!input_strategy_) {
return;
}
input_strategy_->HandleTextEvent(text, modifiers);
}
void KeyboardInterpreter::HandleDeleteEvent(uint8_t modifiers) {
if (!input_strategy_) {
return;
}
base::queue<KeyEvent> keys;
// TODO(nicholss): Handle modifiers.
// Key press.
keys.push({static_cast<uint32_t>(ui::DomCode::BACKSPACE), true});
// Key release.
keys.push({static_cast<uint32_t>(ui::DomCode::BACKSPACE), false});
input_strategy_->HandleKeysEvent(keys);
}
void KeyboardInterpreter::HandleCtrlAltDeleteEvent() {
if (!input_strategy_) {
return;
}
base::queue<KeyEvent> keys;
// Key press.
keys.push({static_cast<uint32_t>(ui::DomCode::CONTROL_LEFT), true});
keys.push({static_cast<uint32_t>(ui::DomCode::ALT_LEFT), true});
keys.push({static_cast<uint32_t>(ui::DomCode::DEL), true});
// Key release.
keys.push({static_cast<uint32_t>(ui::DomCode::DEL), false});
keys.push({static_cast<uint32_t>(ui::DomCode::ALT_LEFT), false});
keys.push({static_cast<uint32_t>(ui::DomCode::CONTROL_LEFT), false});
input_strategy_->HandleKeysEvent(keys);
}
void KeyboardInterpreter::HandlePrintScreenEvent() {
if (!input_strategy_) {
return;
}
base::queue<KeyEvent> keys;
// Key press.
keys.push({static_cast<uint32_t>(ui::DomCode::PRINT_SCREEN), true});
// Key release.
keys.push({static_cast<uint32_t>(ui::DomCode::PRINT_SCREEN), false});
input_strategy_->HandleKeysEvent(keys);
}
} // namespace remoting

@ -1,51 +0,0 @@
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_INPUT_KEYBOARD_INTERPRETER_H_
#define REMOTING_CLIENT_INPUT_KEYBOARD_INTERPRETER_H_
#include <memory>
#include <string>
namespace remoting {
class KeyboardInputStrategy;
class ClientInputInjector;
struct KeypressInfo;
// This is a class for interpreting raw keyboard input, it will delegate
// handling of text events to the selected keyboard input strategy.
class KeyboardInterpreter {
public:
explicit KeyboardInterpreter();
KeyboardInterpreter(const KeyboardInterpreter&) = delete;
KeyboardInterpreter& operator=(const KeyboardInterpreter&) = delete;
~KeyboardInterpreter();
// If |input_injector| is nullptr, all methods below will have no effect.
void SetContext(ClientInputInjector* input_injector);
// Assembles the key events and then delegates to |KeyboardInputStrategy| to
// send the keys.
void HandleKeypressEvent(const KeypressInfo& keypress);
// Delegates to |KeyboardInputStrategy| to covert and send the input.
void HandleTextEvent(const std::string& text, uint8_t modifiers);
// Delegates to |KeyboardInputStrategy| to covert and send the delete.
void HandleDeleteEvent(uint8_t modifiers);
// Assembles CTRL+ALT+DEL key event and then delegates to
// |KeyboardInputStrategy| send the keys.
void HandleCtrlAltDeleteEvent();
// Assembles PRINT_SCREEN key event and then delegates to
// |KeyboardInputStrategy| send the keys.
void HandlePrintScreenEvent();
private:
std::unique_ptr<KeyboardInputStrategy> input_strategy_;
};
} // namespace remoting
#endif // REMOTING_CLIENT_INPUT_KEYBOARD_INTERPRETER_H_

@ -1,131 +0,0 @@
// Copyright 2018 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/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "remoting/client/input/keycode_map.h"
#include <array>
#include <limits>
#include <ostream>
#include "base/check.h"
namespace remoting {
namespace {
// TODO(yuweih): Using char to store the characters may not work if we decide
// to support AZERTY or other keyboard.
const size_t kMaxAsciiConvertibleLength =
std::numeric_limits<unsigned char>::max();
// An array with character code as its index and KeycodeInfo as its value.
using KeycodeMap = std::array<KeypressInfo, kMaxAsciiConvertibleLength>;
struct KeycodeMapEntry {
ui::DomCode dom_code;
unsigned char normal_character;
// 0 if there is no shift character.
unsigned char shift_character;
};
constexpr KeycodeMapEntry kKeycodeMapEntriesQwerty[] = {
{ui::DomCode::US_A, 'a', 'A'},
{ui::DomCode::US_B, 'b', 'B'},
{ui::DomCode::US_C, 'c', 'C'},
{ui::DomCode::US_D, 'd', 'D'},
{ui::DomCode::US_E, 'e', 'E'},
{ui::DomCode::US_F, 'f', 'F'},
{ui::DomCode::US_G, 'g', 'G'},
{ui::DomCode::US_H, 'h', 'H'},
{ui::DomCode::US_I, 'i', 'I'},
{ui::DomCode::US_J, 'j', 'J'},
{ui::DomCode::US_K, 'k', 'K'},
{ui::DomCode::US_L, 'l', 'L'},
{ui::DomCode::US_M, 'm', 'M'},
{ui::DomCode::US_N, 'n', 'N'},
{ui::DomCode::US_O, 'o', 'O'},
{ui::DomCode::US_P, 'p', 'P'},
{ui::DomCode::US_Q, 'q', 'Q'},
{ui::DomCode::US_R, 'r', 'R'},
{ui::DomCode::US_S, 's', 'S'},
{ui::DomCode::US_T, 't', 'T'},
{ui::DomCode::US_U, 'u', 'U'},
{ui::DomCode::US_V, 'v', 'V'},
{ui::DomCode::US_W, 'w', 'W'},
{ui::DomCode::US_X, 'x', 'X'},
{ui::DomCode::US_Y, 'y', 'Y'},
{ui::DomCode::US_Z, 'z', 'Z'},
{ui::DomCode::DIGIT1, '1', '!'},
{ui::DomCode::DIGIT2, '2', '@'},
{ui::DomCode::DIGIT3, '3', '#'},
{ui::DomCode::DIGIT4, '4', '$'},
{ui::DomCode::DIGIT5, '5', '%'},
{ui::DomCode::DIGIT6, '6', '^'},
{ui::DomCode::DIGIT7, '7', '&'},
{ui::DomCode::DIGIT8, '8', '*'},
{ui::DomCode::DIGIT9, '9', '('},
{ui::DomCode::DIGIT0, '0', ')'},
{ui::DomCode::SPACE, ' ', 0},
{ui::DomCode::ENTER, '\n', 0},
{ui::DomCode::MINUS, '-', '_'},
{ui::DomCode::EQUAL, '=', '+'},
{ui::DomCode::BRACKET_LEFT, '[', '{'},
{ui::DomCode::BRACKET_RIGHT, ']', '}'},
{ui::DomCode::BACKSLASH, '\\', '|'},
{ui::DomCode::SEMICOLON, ';', ':'},
{ui::DomCode::QUOTE, '\'', '"'},
{ui::DomCode::BACKQUOTE, '`', '~'},
{ui::DomCode::COMMA, ',', '<'},
{ui::DomCode::PERIOD, '.', '>'},
{ui::DomCode::SLASH, '/', '?'},
};
template <size_t N>
KeycodeMap CreateKeycodeMapFromMapEntries(const KeycodeMapEntry (&entries)[N]) {
KeycodeMap map;
// Initialize keycodes with NONE.
for (size_t i = 0; i < map.size(); i++) {
map[i] = {ui::DomCode::NONE, KeypressInfo::Modifier::NONE};
}
for (size_t i = 0; i < N; i++) {
const KeycodeMapEntry& entry = entries[i];
DCHECK(entry.dom_code != ui::DomCode::NONE);
DCHECK(entry.normal_character != 0);
DCHECK(map[entry.normal_character].dom_code == ui::DomCode::NONE)
<< "Character " << entry.normal_character << " is already in the map.";
map[entry.normal_character].dom_code = entry.dom_code;
if (entry.shift_character != 0) {
DCHECK(map[entry.shift_character].dom_code == ui::DomCode::NONE)
<< "Character " << entry.shift_character << " is already in the map.";
map[entry.shift_character].dom_code = entry.dom_code;
map[entry.shift_character].modifiers = KeypressInfo::Modifier::SHIFT;
}
}
return map;
}
const KeycodeMap& GetKeycodeMapQwerty() {
static const KeycodeMap map(
CreateKeycodeMapFromMapEntries(kKeycodeMapEntriesQwerty));
return map;
}
} // namespace
KeypressInfo KeypressFromUnicode(unsigned int unicode) {
if (unicode >= kMaxAsciiConvertibleLength) {
return {ui::DomCode::NONE, KeypressInfo::Modifier::NONE};
} else {
return GetKeycodeMapQwerty()[unicode];
}
}
} // namespace remoting

@ -1,31 +0,0 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_INPUT_KEYCODE_MAP_H_
#define REMOTING_CLIENT_INPUT_KEYCODE_MAP_H_
#include "ui/events/keycodes/dom/dom_code.h"
namespace remoting {
// A class representing a combination of keys (i.e. main key+modifiers) that
// should be pressed at the same time.
struct KeypressInfo {
enum Modifier {
NONE = 0,
SHIFT = 1 << 0,
// TODO(yuweih): Add more modifiers when needed.
};
ui::DomCode dom_code;
Modifier modifiers;
};
// Gets a keypress that can produce the given unicode on the given keyboard
// layout. If no such a keypress can be found, a keypress with dom_code = NONE
// will be returned.
KeypressInfo KeypressFromUnicode(unsigned int unicode);
} // namespace remoting
#endif // REMOTING_CLIENT_INPUT_KEYCODE_MAP_H_

@ -1,64 +0,0 @@
// Copyright 2012 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/client/input/keycode_map.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace remoting {
TEST(KeycodeMapTest, KeypressFromUnicodeWithAsciiCharsOnQwertyLayout) {
KeypressInfo keypress = KeypressFromUnicode('a');
EXPECT_EQ(ui::DomCode::US_A, keypress.dom_code);
EXPECT_EQ(KeypressInfo::Modifier::NONE, keypress.modifiers);
keypress = KeypressFromUnicode('Q');
EXPECT_EQ(ui::DomCode::US_Q, keypress.dom_code);
EXPECT_EQ(KeypressInfo::Modifier::SHIFT, keypress.modifiers);
keypress = KeypressFromUnicode(' ');
EXPECT_EQ(ui::DomCode::SPACE, keypress.dom_code);
EXPECT_EQ(KeypressInfo::Modifier::NONE, keypress.modifiers);
keypress = KeypressFromUnicode('\n');
EXPECT_EQ(ui::DomCode::ENTER, keypress.dom_code);
EXPECT_EQ(KeypressInfo::Modifier::NONE, keypress.modifiers);
keypress = KeypressFromUnicode('!');
EXPECT_EQ(ui::DomCode::DIGIT1, keypress.dom_code);
EXPECT_EQ(KeypressInfo::Modifier::SHIFT, keypress.modifiers);
keypress = KeypressFromUnicode('`');
EXPECT_EQ(ui::DomCode::BACKQUOTE, keypress.dom_code);
EXPECT_EQ(KeypressInfo::Modifier::NONE, keypress.modifiers);
}
TEST(KeycodeMapTest, KeypressFromUnicodeWithUnmappableCharsOnQwertyLayout) {
// NULL
KeypressInfo keypress = KeypressFromUnicode(0);
EXPECT_EQ(ui::DomCode::NONE, keypress.dom_code);
EXPECT_EQ(KeypressInfo::Modifier::NONE, keypress.modifiers);
// BELL
keypress = KeypressFromUnicode(7);
EXPECT_EQ(ui::DomCode::NONE, keypress.dom_code);
EXPECT_EQ(KeypressInfo::Modifier::NONE, keypress.modifiers);
// ESC
keypress = KeypressFromUnicode(27);
EXPECT_EQ(ui::DomCode::NONE, keypress.dom_code);
EXPECT_EQ(KeypressInfo::Modifier::NONE, keypress.modifiers);
// Non-ASCII character
keypress = KeypressFromUnicode(128);
EXPECT_EQ(ui::DomCode::NONE, keypress.dom_code);
EXPECT_EQ(KeypressInfo::Modifier::NONE, keypress.modifiers);
// Non-ASCII character
keypress = KeypressFromUnicode(2000);
EXPECT_EQ(ui::DomCode::NONE, keypress.dom_code);
EXPECT_EQ(KeypressInfo::Modifier::NONE, keypress.modifiers);
}
} // namespace remoting

@ -1,17 +0,0 @@
// Copyright 2017 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/client/input/native_device_keymap.h"
#include "base/notreached.h"
namespace remoting {
// Default implementation.
uint32_t NativeDeviceKeycodeToUsbKeycode(size_t device_keycode) {
NOTIMPLEMENTED();
return device_keycode;
}
} // namespace remoting

@ -1,17 +0,0 @@
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_INPUT_NATIVE_DEVICE_KEYMAP_H_
#define REMOTING_CLIENT_INPUT_NATIVE_DEVICE_KEYMAP_H_
#include <stddef.h>
#include <stdint.h>
namespace remoting {
uint32_t NativeDeviceKeycodeToUsbKeycode(size_t device_keycode);
} // namespace remoting
#endif // REMOTING_CLIENT_INPUT_NATIVE_DEVICE_KEYMAP_H_

@ -1,215 +0,0 @@
// Copyright 2017 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/client/input/native_device_keymap.h"
#include "base/logging.h"
namespace {
// These must be defined in the same order as the Android keycodes in
// <android/keycodes.h> and
// "ui/events/keycodes/keyboard_code_conversion_android.cc" . Some of these
// mappings assume a US keyboard layout for now.
const uint32_t usb_keycodes[] = {
0, // UNKNOWN
0, // SOFT_LEFT
0, // SOFT_RIGHT
0, // HOME
0, // BACK
0, // CALL
0, // ENDCALL
0x070027, // 0
0x07001e, // 1
0x07001f, // 2
0x070020, // 3
0x070021, // 4
0x070022, // 5
0x070023, // 6
0x070024, // 7
0x070025, // 8
0x070026, // 9
0, // STAR
0, // POUND
0x070052, // DPAD_UP
0x070051, // DPAD_DOWN
0x070050, // DPAD_LEFT
0x07004f, // DPAD_RIGHT
0, // DPAD_CENTER
0, // VOLUME_UP
0, // VOLUME_DOWN
0, // POWER
0, // CAMERA
0, // CLEAR
0x070004, // A
0x070005, // B
0x070006, // C
0x070007, // D
0x070008, // E
0x070009, // F
0x07000a, // G
0x07000b, // H
0x07000c, // I
0x07000d, // J
0x07000e, // K
0x07000f, // L
0x070010, // M
0x070011, // N
0x070012, // O
0x070013, // P
0x070014, // Q
0x070015, // R
0x070016, // S
0x070017, // T
0x070018, // U
0x070019, // V
0x07001a, // W
0x07001b, // X
0x07001c, // Y
0x07001d, // Z
0x070036, // COMMA
0x070037, // PERIOD
0x0700e2, // ALT_LEFT
0x0700e6, // ALT_RIGHT
0x0700e1, // SHIFT_LEFT
0x0700e5, // SHIFT_RIGHT
0x07002b, // TAB
0x07002c, // SPACE
0, // SYM
0, // EXPLORER
0, // ENVELOPE
0x070028, // ENTER
0x07002a, // DEL (backspace)
0x070035, // GRAVE (backtick)
0x07002d, // MINUS
0x07002e, // EQUALS
0x07002f, // LEFT_BRACKET
0x070030, // RIGHT_BRACKET
0x070031, // BACKSLASH
0x070033, // SEMICOLON
0x070034, // APOSTROPHE
0x070038, // SLASH
0, // AT
0, // NUM
0, // HEADSETHOOK
0, // FOCUS
0, // PLUS
0, // MENU
0, // NOTIFICATION
0, // SEARCH
0, // MEDIA_PLAY_PAUSE
0, // MEDIA_STOP
0, // MEDIA_NEXT
0, // MEDIA_PREVIOUS
0, // MEDIA_REWIND
0, // MEDIA_FAST_FORWARD
0, // MUTE
0x07004b, // PAGE_UP
0x07004e, // PAGE_DOWN
0, // PICTSYMBOLS
0, // SWITCH_CHARSET
0, // BUTTON_A
0, // BUTTON_B
0, // BUTTON_C
0, // BUTTON_X
0, // BUTTON_Y
0, // BUTTON_Z
0, // BUTTON_L1
0, // BUTTON_R1
0, // BUTTON_L2
0, // BUTTON_R2
0, // BUTTON_THUMBL
0, // BUTTON_THUMBR
0, // BUTTON_START
0, // BUTTON_SELECT
0, // BUTTON_MODE
0x070029, // ESCAPE
0x07004c, // FORWARD_DEL
0x0700e0, // CTRL_LEFT
0x0700e4, // CTRL_RIGHT
0, // CAPS_LOCK
0, // SCROLL_LOCK
0x0700e3, // META_LEFT
0x0700e7, // META_RIGHT
0, // FUNCTION
0x070046, // SYSRQ (printscreen)
0x070048, // BREAK (pause)
0x07004a, // MOVE_HOME (home)
0x07004d, // MOVE_END (end)
0x070049, // INSERT
0, // FORWARD
0, // MEDIA_PLAY
0, // MEDIA_PAUSE
0, // MEDIA_CLOSE
0, // MEDIA_EJECT
0, // MEDIA_RECORD
0x07003a, // F1
0x07003b, // F2
0x07003c, // F3
0x07003d, // F4
0x07003e, // F5
0x07003f, // F6
0x070040, // F7
0x070041, // F8
0x070042, // F9
0x070043, // F10
0x070044, // F11
0x070045, // F12
0, // NUM_LOCK
0x070062, // NUMPAD_0
0x070059, // NUMPAD_1
0x07005a, // NUMPAD_2
0x07005b, // NUMPAD_3
0x07005c, // NUMPAD_4
0x07005d, // NUMPAD_5
0x07005e, // NUMPAD_6
0x07005f, // NUMPAD_7
0x070060, // NUMPAD_8
0x070061, // NUMPAD_9
0x070054, // NUMPAD_DIVIDE
0x070055, // NUMPAD_MULTIPLY
0x070056, // NUMPAD_SUBTRACT
0x070057, // NUMPAD_ADD
0x070063, // NUMPAD_DOT
0x070085, // NUMPAD_COMMA
0x070058, // NUMPAD_ENTER
0x070067, // NUMPAD_EQUALS
0x0700b6, // NUMPAD_LEFT_PAREN
0x0700b7, // NUMPAD_RIGHT_PAREN
};
} // namespace
namespace remoting {
uint32_t NativeDeviceKeycodeToUsbKeycode(size_t device_keycode) {
if (device_keycode >= sizeof(usb_keycodes) / sizeof(uint32_t)) {
LOG(WARNING) << "Attempted to decode out-of-range Android keycode";
return 0;
}
return usb_keycodes[device_keycode];
}
} // namespace remoting

Some files were not shown because too many files have changed in this diff Show More