[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:

committed by
Chromium LUCI CQ

parent
a010d009b6
commit
3b9d445ece
BUILD.gn
infra/config
remoting
BUILD.gn
client
BUILD.gnDEPS
audio
BUILD.gnasync_audio_data_supplier.ccasync_audio_data_supplier.haudio_jitter_buffer.ccaudio_jitter_buffer.haudio_jitter_buffer_unittest.ccaudio_playback_sink.haudio_playback_stream.ccaudio_playback_stream.haudio_player.ccaudio_player.haudio_player_android.ccaudio_player_android.haudio_player_unittest.ccaudio_stream_format.ccaudio_stream_format.hfake_async_audio_data_supplier.ccfake_async_audio_data_supplier.h
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.hdisplay
BUILD.gncanvas.hdrawable.hfake_canvas.ccfake_canvas.hgl_canvas.ccgl_canvas.hgl_cursor.ccgl_cursor.hgl_cursor_feedback.ccgl_cursor_feedback.hgl_cursor_feedback_texture.ccgl_cursor_feedback_texture.hgl_desktop.ccgl_desktop.hgl_helpers.ccgl_helpers.hgl_math.ccgl_math.hgl_render_layer.ccgl_render_layer.hgl_renderer.ccgl_renderer.hgl_renderer_delegate.hgl_renderer_unittest.ccgl_texture_ids.hrenderer_proxy.ccrenderer_proxy.hsys_opengl.h
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.hinput
BUILD.gnclient_input_injector.hdirect_touch_input_strategy.ccdirect_touch_input_strategy.hkey_event_mapper.cckey_event_mapper.hkey_event_mapper_unittest.cckeyboard_input_strategy.hkeyboard_interpreter.cckeyboard_interpreter.hkeycode_map.cckeycode_map.hkeycode_map_unittest.ccnative_device_keymap.ccnative_device_keymap.hnative_device_keymap_android.ccnative_device_keymap_ios.ccnormalizing_input_filter_cros.ccnormalizing_input_filter_cros.hnormalizing_input_filter_cros_unittest.ccnormalizing_input_filter_mac.ccnormalizing_input_filter_mac.hnormalizing_input_filter_mac_unittest.ccnormalizing_input_filter_win.ccnormalizing_input_filter_win.hnormalizing_input_filter_win_unittest.cctext_keyboard_input_strategy.cctext_keyboard_input_strategy.htouch_input_scaler.cctouch_input_scaler.htouch_input_scaler_unittest.cctouch_input_strategy.htrackpad_input_strategy.cctrackpad_input_strategy.h
notification
BUILD.gngstatic_json_fetcher.ccgstatic_json_fetcher.hgstatic_json_fetcher_unittest.ccjson_fetcher.hnotification_client.ccnotification_client.hnotification_client_unittest.ccnotification_message.ccnotification_message.hversion_range.ccversion_range.hversion_range_unittest.cc
software_video_renderer.ccsoftware_video_renderer.hsoftware_video_renderer_unittest.ccui
test
testing/buildbot
tools/traffic_annotation/summary
1
BUILD.gn
1
BUILD.gn
@ -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
Reference in New Issue
Block a user