| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "remoting/host/desktop_session_proxy.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/process/process_handle.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "build/build_config.h" |
| #include "ipc/ipc_channel_proxy.h" |
| #include "ipc/ipc_message_macros.h" |
| #include "remoting/base/capabilities.h" |
| #include "remoting/host/chromoting_messages.h" |
| #include "remoting/host/client_session.h" |
| #include "remoting/host/client_session_control.h" |
| #include "remoting/host/crash_process.h" |
| #include "remoting/host/desktop_session_connector.h" |
| #include "remoting/host/ipc_action_executor.h" |
| #include "remoting/host/ipc_audio_capturer.h" |
| #include "remoting/host/ipc_input_injector.h" |
| #include "remoting/host/ipc_keyboard_layout_monitor.h" |
| #include "remoting/host/ipc_mouse_cursor_monitor.h" |
| #include "remoting/host/ipc_screen_controls.h" |
| #include "remoting/host/ipc_url_forwarder_configurator.h" |
| #include "remoting/host/ipc_video_frame_capturer.h" |
| #include "remoting/host/remote_open_url/remote_open_url_util.h" |
| #include "remoting/proto/audio.pb.h" |
| #include "remoting/proto/control.pb.h" |
| #include "remoting/proto/event.pb.h" |
| #include "remoting/protocol/capability_names.h" |
| #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" |
| #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h" |
| #include "third_party/webrtc/modules/desktop_capture/mouse_cursor.h" |
| #include "third_party/webrtc/modules/desktop_capture/shared_memory.h" |
| |
| #if defined(OS_WIN) |
| #include "base/win/scoped_handle.h" |
| #endif // defined(OS_WIN) |
| |
| namespace remoting { |
| |
| using SetUpUrlForwarderResponse = |
| protocol::UrlForwarderControl::SetUpUrlForwarderResponse; |
| |
| class DesktopSessionProxy::IpcSharedBufferCore |
| : public base::RefCountedThreadSafe<IpcSharedBufferCore> { |
| public: |
| IpcSharedBufferCore(int id, base::ReadOnlySharedMemoryRegion region) |
| : id_(id) { |
| mapping_ = region.Map(); |
| if (!mapping_.IsValid()) { |
| LOG(ERROR) << "Failed to map a shared buffer: id=" << id |
| << ", size=" << region.GetSize(); |
| } |
| // After being mapped, |region| is no longer needed and can be discarded. |
| } |
| |
| IpcSharedBufferCore(const IpcSharedBufferCore&) = delete; |
| IpcSharedBufferCore& operator=(const IpcSharedBufferCore&) = delete; |
| |
| int id() const { return id_; } |
| size_t size() const { return mapping_.size(); } |
| const void* memory() const { return mapping_.memory(); } |
| |
| private: |
| virtual ~IpcSharedBufferCore() = default; |
| friend class base::RefCountedThreadSafe<IpcSharedBufferCore>; |
| |
| int id_; |
| base::ReadOnlySharedMemoryMapping mapping_; |
| }; |
| |
| class DesktopSessionProxy::IpcSharedBuffer : public webrtc::SharedMemory { |
| public: |
| // Note that the webrtc::SharedMemory class is used for both read-only and |
| // writable shared memory, necessitating the ugly const_cast here. |
| IpcSharedBuffer(scoped_refptr<IpcSharedBufferCore> core) |
| : SharedMemory(const_cast<void*>(core->memory()), |
| core->size(), |
| 0, |
| core->id()), |
| core_(core) {} |
| |
| IpcSharedBuffer(const IpcSharedBuffer&) = delete; |
| IpcSharedBuffer& operator=(const IpcSharedBuffer&) = delete; |
| |
| private: |
| scoped_refptr<IpcSharedBufferCore> core_; |
| }; |
| |
| DesktopSessionProxy::DesktopSessionProxy( |
| scoped_refptr<base::SingleThreadTaskRunner> audio_capture_task_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, |
| base::WeakPtr<ClientSessionControl> client_session_control, |
| base::WeakPtr<ClientSessionEvents> client_session_events, |
| base::WeakPtr<DesktopSessionConnector> desktop_session_connector, |
| const DesktopEnvironmentOptions& options) |
| : audio_capture_task_runner_(audio_capture_task_runner), |
| caller_task_runner_(caller_task_runner), |
| io_task_runner_(io_task_runner), |
| client_session_control_(client_session_control), |
| client_session_events_(client_session_events), |
| desktop_session_connector_(desktop_session_connector), |
| ipc_file_operations_factory_(this), |
| pending_capture_frame_requests_(0), |
| is_desktop_session_connected_(false), |
| options_(options) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| } |
| |
| std::unique_ptr<ActionExecutor> DesktopSessionProxy::CreateActionExecutor() { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| return std::make_unique<IpcActionExecutor>(this); |
| } |
| |
| std::unique_ptr<AudioCapturer> DesktopSessionProxy::CreateAudioCapturer() { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| return std::make_unique<IpcAudioCapturer>(this); |
| } |
| |
| std::unique_ptr<InputInjector> DesktopSessionProxy::CreateInputInjector() { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| return std::make_unique<IpcInputInjector>(this); |
| } |
| |
| std::unique_ptr<ScreenControls> DesktopSessionProxy::CreateScreenControls() { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| return std::make_unique<IpcScreenControls>(this); |
| } |
| |
| std::unique_ptr<webrtc::DesktopCapturer> |
| DesktopSessionProxy::CreateVideoCapturer() { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| return std::make_unique<IpcVideoFrameCapturer>(this); |
| } |
| |
| std::unique_ptr<webrtc::MouseCursorMonitor> |
| DesktopSessionProxy::CreateMouseCursorMonitor() { |
| return std::make_unique<IpcMouseCursorMonitor>(this); |
| } |
| |
| std::unique_ptr<KeyboardLayoutMonitor> |
| DesktopSessionProxy::CreateKeyboardLayoutMonitor( |
| base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| return std::make_unique<IpcKeyboardLayoutMonitor>(std::move(callback), this); |
| } |
| |
| std::unique_ptr<FileOperations> DesktopSessionProxy::CreateFileOperations() { |
| return ipc_file_operations_factory_.CreateFileOperations(); |
| } |
| |
| std::unique_ptr<UrlForwarderConfigurator> |
| DesktopSessionProxy::CreateUrlForwarderConfigurator() { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| return std::make_unique<IpcUrlForwarderConfigurator>(this); |
| } |
| |
| std::string DesktopSessionProxy::GetCapabilities() const { |
| std::string result = protocol::kRateLimitResizeRequests; |
| // Ask the client to send its resolution unconditionally. |
| if (options_.enable_curtaining()) { |
| result += " "; |
| result += protocol::kSendInitialResolution; |
| } |
| |
| if (InputInjector::SupportsTouchEvents()) { |
| result += " "; |
| result += protocol::kTouchEventsCapability; |
| } |
| |
| if (options_.enable_file_transfer()) { |
| result += " "; |
| result += protocol::kFileTransferCapability; |
| } |
| |
| if (options_.enable_remote_open_url() && IsRemoteOpenUrlSupported()) { |
| result += " "; |
| result += protocol::kRemoteOpenUrlCapability; |
| } |
| |
| if (options_.enable_remote_webauthn()) { |
| result += " "; |
| result += protocol::kRemoteWebAuthnCapability; |
| } |
| |
| return result; |
| } |
| |
| void DesktopSessionProxy::SetCapabilities(const std::string& capabilities) { |
| // Delay creation of the desktop session until the client screen resolution is |
| // received if the desktop session requires the initial screen resolution |
| // (when enable_curtaining() is true) and the client is expected to |
| // sent its screen resolution (the 'sendInitialResolution' capability is |
| // supported). |
| if (options_.enable_curtaining() && |
| HasCapability(capabilities, protocol::kSendInitialResolution)) { |
| VLOG(1) << "Waiting for the client screen resolution."; |
| return; |
| } |
| |
| // Connect to the desktop session. |
| if (!is_desktop_session_connected_) { |
| is_desktop_session_connected_ = true; |
| if (desktop_session_connector_.get()) { |
| desktop_session_connector_->ConnectTerminal(this, screen_resolution_, |
| options_.enable_curtaining()); |
| } |
| } |
| } |
| |
| bool DesktopSessionProxy::OnMessageReceived(const IPC::Message& message) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(DesktopSessionProxy, message) |
| IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_AudioPacket, |
| OnAudioPacket) |
| IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_CaptureResult, |
| OnCaptureResult) |
| IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_DisplayChanged, |
| OnDesktopDisplayChanged) |
| IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_MouseCursor, |
| OnMouseCursor) |
| IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_CreateSharedBuffer, |
| OnCreateSharedBuffer) |
| IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_ReleaseSharedBuffer, |
| OnReleaseSharedBuffer) |
| IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_KeyboardChanged, |
| OnKeyboardChanged) |
| IPC_MESSAGE_HANDLER(ChromotingDesktopNetworkMsg_DisconnectSession, |
| DisconnectSession) |
| IPC_MESSAGE_FORWARD(ChromotingDesktopNetworkMsg_FileResult, |
| &ipc_file_operations_factory_, |
| IpcFileOperations::ResultHandler::OnResult) |
| IPC_MESSAGE_FORWARD(ChromotingDesktopNetworkMsg_FileInfoResult, |
| &ipc_file_operations_factory_, |
| IpcFileOperations::ResultHandler::OnInfoResult) |
| IPC_MESSAGE_FORWARD(ChromotingDesktopNetworkMsg_FileDataResult, |
| &ipc_file_operations_factory_, |
| IpcFileOperations::ResultHandler::OnDataResult) |
| IPC_END_MESSAGE_MAP() |
| |
| CHECK(handled) << "Received unexpected IPC type: " << message.type(); |
| return handled; |
| } |
| |
| void DesktopSessionProxy::OnChannelConnected(int32_t peer_pid) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| VLOG(1) << "IPC: network <- desktop (" << peer_pid << ")"; |
| } |
| |
| void DesktopSessionProxy::OnChannelError() { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| DetachFromDesktop(); |
| } |
| |
| void DesktopSessionProxy::OnAssociatedInterfaceRequest( |
| const std::string& interface_name, |
| mojo::ScopedInterfaceEndpointHandle handle) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| if (interface_name == mojom::DesktopSessionEventHandler::Name_) { |
| if (desktop_session_event_handler_.is_bound()) { |
| LOG(ERROR) << "Receiver already bound for associated interface: " |
| << mojom::DesktopSessionEventHandler::Name_; |
| CrashProcess(base::Location::Current()); |
| } |
| |
| mojo::PendingAssociatedReceiver<mojom::DesktopSessionEventHandler> |
| pending_receiver(std::move(handle)); |
| desktop_session_event_handler_.Bind(std::move(pending_receiver)); |
| } else { |
| LOG(ERROR) << "Unknown associated interface requested: " << interface_name |
| << ", crashing this process"; |
| CrashProcess(base::Location::Current()); |
| } |
| } |
| |
| bool DesktopSessionProxy::AttachToDesktop( |
| const IPC::ChannelHandle& desktop_pipe, |
| int session_id) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| DCHECK(!desktop_channel_); |
| |
| if (client_session_events_) { |
| client_session_events_->OnDesktopAttached(session_id); |
| } |
| |
| // Ignore the attach notification if the client session has been disconnected |
| // already. |
| if (!client_session_control_.get()) |
| return false; |
| |
| // Connect to the desktop process. |
| desktop_channel_ = IPC::ChannelProxy::Create( |
| desktop_pipe, IPC::Channel::MODE_CLIENT, this, io_task_runner_.get(), |
| base::ThreadTaskRunnerHandle::Get()); |
| |
| // Pass ID of the client (which is authenticated at this point) to the desktop |
| // session agent and start the agent. |
| SendToDesktop(new ChromotingNetworkDesktopMsg_StartSessionAgent( |
| client_session_control_->client_jid(), screen_resolution_, options_)); |
| |
| desktop_channel_->GetRemoteAssociatedInterface(&desktop_session_control_); |
| |
| desktop_session_id_ = session_id; |
| |
| return true; |
| } |
| |
| void DesktopSessionProxy::DetachFromDesktop() { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| desktop_channel_.reset(); |
| desktop_session_control_.reset(); |
| desktop_session_event_handler_.reset(); |
| desktop_session_id_ = UINT32_MAX; |
| |
| current_url_forwarder_state_ = mojom::UrlForwarderState::kUnknown; |
| // We don't reset |is_url_forwarder_set_up_callback_| here since the request |
| // can come in before the DetachFromDesktop-AttachToDesktop sequence. |
| |
| shared_buffers_.clear(); |
| |
| // Generate fake responses to keep the video capturer in sync. |
| while (pending_capture_frame_requests_) { |
| --pending_capture_frame_requests_; |
| video_capturer_->OnCaptureResult( |
| webrtc::DesktopCapturer::Result::ERROR_TEMPORARY, nullptr); |
| } |
| |
| if (client_session_events_) { |
| client_session_events_->OnDesktopDetached(); |
| } |
| } |
| |
| void DesktopSessionProxy::SetAudioCapturer( |
| const base::WeakPtr<IpcAudioCapturer>& audio_capturer) { |
| DCHECK(audio_capture_task_runner_->BelongsToCurrentThread()); |
| |
| audio_capturer_ = audio_capturer; |
| } |
| |
| void DesktopSessionProxy::CaptureFrame() { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| if (desktop_channel_) { |
| ++pending_capture_frame_requests_; |
| SendToDesktop(new ChromotingNetworkDesktopMsg_CaptureFrame()); |
| } else { |
| video_capturer_->OnCaptureResult( |
| webrtc::DesktopCapturer::Result::ERROR_TEMPORARY, nullptr); |
| } |
| } |
| |
| bool DesktopSessionProxy::SelectSource(webrtc::DesktopCapturer::SourceId id) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| SendToDesktop(new ChromotingNetworkDesktopMsg_SelectSource(id)); |
| return true; |
| } |
| |
| void DesktopSessionProxy::SetVideoCapturer( |
| const base::WeakPtr<IpcVideoFrameCapturer> video_capturer) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| video_capturer_ = video_capturer; |
| } |
| |
| void DesktopSessionProxy::SetMouseCursorMonitor( |
| const base::WeakPtr<IpcMouseCursorMonitor>& mouse_cursor_monitor) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| mouse_cursor_monitor_ = mouse_cursor_monitor; |
| } |
| |
| void DesktopSessionProxy::SetKeyboardLayoutMonitor( |
| const base::WeakPtr<IpcKeyboardLayoutMonitor>& keyboard_layout_monitor) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| keyboard_layout_monitor_ = std::move(keyboard_layout_monitor); |
| } |
| |
| const absl::optional<protocol::KeyboardLayout>& |
| DesktopSessionProxy::GetKeyboardCurrentLayout() const { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| return keyboard_layout_; |
| } |
| |
| void DesktopSessionProxy::DisconnectSession(protocol::ErrorCode error) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| // Disconnect the client session if it hasn't been disconnected yet. |
| if (client_session_control_.get()) |
| client_session_control_->DisconnectSession(error); |
| } |
| |
| void DesktopSessionProxy::InjectClipboardEvent( |
| const protocol::ClipboardEvent& event) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| if (desktop_session_control_) { |
| desktop_session_control_->InjectClipboardEvent(event); |
| } |
| } |
| |
| void DesktopSessionProxy::InjectKeyEvent(const protocol::KeyEvent& event) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| if (desktop_session_control_) { |
| desktop_session_control_->InjectKeyEvent(event); |
| } |
| } |
| |
| void DesktopSessionProxy::InjectTextEvent(const protocol::TextEvent& event) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| if (desktop_session_control_) { |
| desktop_session_control_->InjectTextEvent(event); |
| } |
| } |
| |
| void DesktopSessionProxy::InjectMouseEvent(const protocol::MouseEvent& event) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| if (desktop_session_control_) { |
| desktop_session_control_->InjectMouseEvent(event); |
| } |
| } |
| |
| void DesktopSessionProxy::InjectTouchEvent(const protocol::TouchEvent& event) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| if (desktop_session_control_) { |
| desktop_session_control_->InjectTouchEvent(event); |
| } |
| } |
| |
| void DesktopSessionProxy::StartInputInjector( |
| std::unique_ptr<protocol::ClipboardStub> client_clipboard) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| client_clipboard_ = std::move(client_clipboard); |
| } |
| |
| void DesktopSessionProxy::SetScreenResolution( |
| const ScreenResolution& resolution) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| screen_resolution_ = resolution; |
| |
| // Connect to the desktop session if it is not done yet. |
| if (!is_desktop_session_connected_) { |
| is_desktop_session_connected_ = true; |
| if (desktop_session_connector_.get()) { |
| desktop_session_connector_->ConnectTerminal(this, screen_resolution_, |
| options_.enable_curtaining()); |
| } |
| return; |
| } |
| |
| // Pass the client's resolution to both daemon and desktop session agent. |
| // Depending on the session kind the screen resolution can be set by either |
| // the daemon (for example RDP sessions on Windows) or by the desktop session |
| // agent (when sharing the physical console). |
| // Desktop-size-restore functionality (via an empty resolution param) does not |
| // exist for the Daemon process. Passing an empty resolution object is |
| // treated as a critical error so we want to prevent that here. |
| if (desktop_session_connector_.get() && !screen_resolution_.IsEmpty()) |
| desktop_session_connector_->SetScreenResolution(this, screen_resolution_); |
| |
| // Passing an empty |screen_resolution_| value to the desktop process |
| // indicates that the original resolution, if one exists, should be restored. |
| SendToDesktop( |
| new ChromotingNetworkDesktopMsg_SetScreenResolution(screen_resolution_)); |
| } |
| |
| void DesktopSessionProxy::ExecuteAction( |
| const protocol::ActionRequest& request) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| SendToDesktop(new ChromotingNetworkDesktopMsg_ExecuteActionRequest(request)); |
| } |
| |
| void DesktopSessionProxy::ReadFile(std::uint64_t file_id) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| SendToDesktop(new ChromotingNetworkDesktopMsg_ReadFile(file_id)); |
| } |
| |
| void DesktopSessionProxy::ReadChunk(std::uint64_t file_id, std::uint64_t size) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| SendToDesktop(new ChromotingNetworkDesktopMsg_ReadFileChunk(file_id, size)); |
| } |
| |
| void DesktopSessionProxy::WriteFile(uint64_t file_id, |
| const base::FilePath& filename) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| SendToDesktop(new ChromotingNetworkDesktopMsg_WriteFile(file_id, filename)); |
| } |
| |
| void DesktopSessionProxy::WriteChunk(uint64_t file_id, |
| std::vector<std::uint8_t> data) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| SendToDesktop(new ChromotingNetworkDesktopMsg_WriteFileChunk(file_id, data)); |
| } |
| |
| void DesktopSessionProxy::Close(uint64_t file_id) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| SendToDesktop(new ChromotingNetworkDesktopMsg_CloseFile(file_id)); |
| } |
| |
| void DesktopSessionProxy::Cancel(uint64_t file_id) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| SendToDesktop(new ChromotingNetworkDesktopMsg_CancelFile(file_id)); |
| } |
| |
| void DesktopSessionProxy::IsUrlForwarderSetUp( |
| UrlForwarderConfigurator::IsUrlForwarderSetUpCallback callback) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| switch (current_url_forwarder_state_) { |
| case mojom::UrlForwarderState::kUnknown: |
| // State is not known yet. Wait for OnUrlForwarderStateChange() to be |
| // called. |
| DCHECK(!is_url_forwarder_set_up_callback_); |
| is_url_forwarder_set_up_callback_ = std::move(callback); |
| break; |
| case mojom::UrlForwarderState::kSetUp: |
| std::move(callback).Run(true); |
| break; |
| default: |
| std::move(callback).Run(false); |
| } |
| } |
| |
| void DesktopSessionProxy::SetUpUrlForwarder( |
| const UrlForwarderConfigurator::SetUpUrlForwarderCallback& callback) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| DCHECK(!set_up_url_forwarder_callback_); |
| |
| if (!desktop_session_control_.is_connected()) { |
| LOG(ERROR) << "The UrlForwarderConfigurator remote is not connected. Setup " |
| << "request ignored."; |
| callback.Run(SetUpUrlForwarderResponse::FAILED); |
| return; |
| } |
| set_up_url_forwarder_callback_ = callback; |
| desktop_session_control_->SetUpUrlForwarder(); |
| } |
| |
| void DesktopSessionProxy::OnUrlForwarderStateChange( |
| mojom::UrlForwarderState state) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| current_url_forwarder_state_ = state; |
| |
| if (is_url_forwarder_set_up_callback_) { |
| std::move(is_url_forwarder_set_up_callback_) |
| .Run(state == mojom::UrlForwarderState::kSetUp); |
| } |
| |
| if (set_up_url_forwarder_callback_) { |
| switch (state) { |
| case mojom::UrlForwarderState::kSetUp: |
| set_up_url_forwarder_callback_.Run(SetUpUrlForwarderResponse::COMPLETE); |
| // Cleanup callback due to terminating state. |
| set_up_url_forwarder_callback_.Reset(); |
| break; |
| case mojom::UrlForwarderState::kNotSetUp: |
| // The desktop session agent during the setup process will only report |
| // SET_UP or FAILED. NOT_SET_UP must come from a freshly started agent. |
| LOG(WARNING) << "Setup process failed because the previous desktop " |
| << "session agent has exited"; |
| [[fallthrough]]; |
| case mojom::UrlForwarderState::kFailed: |
| set_up_url_forwarder_callback_.Run(SetUpUrlForwarderResponse::FAILED); |
| // Cleanup callback due to terminating state. |
| set_up_url_forwarder_callback_.Reset(); |
| break; |
| case mojom::UrlForwarderState::kSetupPendingUserIntervention: |
| set_up_url_forwarder_callback_.Run( |
| SetUpUrlForwarderResponse::USER_INTERVENTION_REQUIRED); |
| break; |
| default: |
| LOG(ERROR) << "Received unexpected state: " << state; |
| } |
| } |
| } |
| |
| DesktopSessionProxy::~DesktopSessionProxy() { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| if (desktop_session_connector_.get() && is_desktop_session_connected_) |
| desktop_session_connector_->DisconnectTerminal(this); |
| } |
| |
| scoped_refptr<DesktopSessionProxy::IpcSharedBufferCore> |
| DesktopSessionProxy::GetSharedBufferCore(int id) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| SharedBuffers::const_iterator i = shared_buffers_.find(id); |
| if (i != shared_buffers_.end()) { |
| return i->second; |
| } else { |
| LOG(ERROR) << "Failed to find the shared buffer " << id; |
| return nullptr; |
| } |
| } |
| |
| void DesktopSessionProxy::OnAudioPacket(const std::string& serialized_packet) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| // Parse a serialized audio packet. No further validation is done since |
| // the message was sent by more privileged process. |
| std::unique_ptr<AudioPacket> packet(new AudioPacket()); |
| if (!packet->ParseFromString(serialized_packet)) { |
| LOG(ERROR) << "Failed to parse AudioPacket."; |
| return; |
| } |
| |
| // Pass a captured audio packet to |audio_capturer_|. |
| audio_capture_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&IpcAudioCapturer::OnAudioPacket, |
| audio_capturer_, std::move(packet))); |
| } |
| |
| void DesktopSessionProxy::OnCreateSharedBuffer( |
| int id, |
| base::ReadOnlySharedMemoryRegion region, |
| uint32_t size) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| scoped_refptr<IpcSharedBufferCore> shared_buffer = |
| new IpcSharedBufferCore(id, std::move(region)); |
| |
| if (shared_buffer->memory() != nullptr && |
| !shared_buffers_.insert(std::make_pair(id, shared_buffer)).second) { |
| LOG(ERROR) << "Duplicate shared buffer id " << id << " encountered"; |
| } |
| } |
| |
| void DesktopSessionProxy::OnReleaseSharedBuffer(int id) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| // Drop the cached reference to the buffer. |
| shared_buffers_.erase(id); |
| } |
| |
| void DesktopSessionProxy::OnDesktopDisplayChanged( |
| const protocol::VideoLayout& displays) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| LOG(INFO) << "DSP::OnDesktopDisplayChanged"; |
| for (int display_id = 0; display_id < displays.video_track_size(); |
| display_id++) { |
| protocol::VideoTrackLayout track = displays.video_track(display_id); |
| LOG(INFO) << " #" << display_id << " : " |
| << " [" << track.x_dpi() << "," << track.y_dpi() << "]"; |
| } |
| |
| if (client_session_control_) { |
| auto layout = std::make_unique<protocol::VideoLayout>(); |
| layout->CopyFrom(displays); |
| client_session_control_->OnDesktopDisplayChanged(std::move(layout)); |
| } |
| } |
| |
| void DesktopSessionProxy::OnCaptureResult( |
| webrtc::DesktopCapturer::Result result, |
| const SerializedDesktopFrame& serialized_frame) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| --pending_capture_frame_requests_; |
| |
| if (!video_capturer_) { |
| return; |
| } |
| |
| if (result != webrtc::DesktopCapturer::Result::SUCCESS) { |
| video_capturer_->OnCaptureResult(result, nullptr); |
| return; |
| } |
| |
| // Assume that |serialized_frame| is well-formed because it was received from |
| // a more privileged process. |
| scoped_refptr<IpcSharedBufferCore> shared_buffer_core = |
| GetSharedBufferCore(serialized_frame.shared_buffer_id); |
| CHECK(shared_buffer_core.get()); |
| |
| std::unique_ptr<webrtc::DesktopFrame> frame( |
| new webrtc::SharedMemoryDesktopFrame( |
| serialized_frame.dimensions, serialized_frame.bytes_per_row, |
| new IpcSharedBuffer(shared_buffer_core))); |
| frame->set_capture_time_ms(serialized_frame.capture_time_ms); |
| frame->set_dpi(serialized_frame.dpi); |
| frame->set_capturer_id(serialized_frame.capturer_id); |
| |
| for (const auto& rect : serialized_frame.dirty_region) { |
| frame->mutable_updated_region()->AddRect(rect); |
| } |
| |
| video_capturer_->OnCaptureResult(webrtc::DesktopCapturer::Result::SUCCESS, |
| std::move(frame)); |
| } |
| |
| void DesktopSessionProxy::OnMouseCursor( |
| const webrtc::MouseCursor& mouse_cursor) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| if (mouse_cursor_monitor_) { |
| mouse_cursor_monitor_->OnMouseCursor( |
| base::WrapUnique(webrtc::MouseCursor::CopyOf(mouse_cursor))); |
| } |
| } |
| |
| void DesktopSessionProxy::OnKeyboardChanged( |
| const protocol::KeyboardLayout& layout) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| keyboard_layout_ = layout; |
| if (keyboard_layout_monitor_) { |
| keyboard_layout_monitor_->OnKeyboardChanged(layout); |
| } |
| } |
| |
| void DesktopSessionProxy::OnClipboardEvent( |
| const protocol::ClipboardEvent& event) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| if (client_clipboard_) { |
| client_clipboard_->InjectClipboardEvent(event); |
| } |
| } |
| |
| void DesktopSessionProxy::SendToDesktop(IPC::Message* message) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| if (desktop_channel_) { |
| desktop_channel_->Send(message); |
| } else { |
| delete message; |
| } |
| } |
| |
| // static |
| void DesktopSessionProxyTraits::Destruct( |
| const DesktopSessionProxy* desktop_session_proxy) { |
| desktop_session_proxy->caller_task_runner_->DeleteSoon(FROM_HERE, |
| desktop_session_proxy); |
| } |
| |
| } // namespace remoting |