// 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_agent.h" #include "base/file_util.h" #include "base/logging.h" #include "base/memory/shared_memory.h" #include "ipc/ipc_channel_proxy.h" #include "ipc/ipc_message.h" #include "ipc/ipc_message_macros.h" #include "remoting/base/auto_thread_task_runner.h" #include "remoting/base/constants.h" #include "remoting/host/audio_capturer.h" #include "remoting/host/chromoting_messages.h" #include "remoting/host/desktop_environment.h" #include "remoting/host/input_injector.h" #include "remoting/host/ipc_util.h" #include "remoting/host/remote_input_filter.h" #include "remoting/host/screen_controls.h" #include "remoting/host/screen_resolution.h" #include "remoting/proto/audio.pb.h" #include "remoting/proto/control.pb.h" #include "remoting/proto/event.pb.h" #include "remoting/protocol/clipboard_stub.h" #include "remoting/protocol/input_event_tracker.h" #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h" #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" #include "third_party/webrtc/modules/desktop_capture/shared_memory.h" namespace remoting { namespace { // Routes local clipboard events though the IPC channel to the network process. class DesktopSesssionClipboardStub : public protocol::ClipboardStub { public: explicit DesktopSesssionClipboardStub( scoped_refptr desktop_session_agent); virtual ~DesktopSesssionClipboardStub(); // protocol::ClipboardStub implementation. virtual void InjectClipboardEvent( const protocol::ClipboardEvent& event) OVERRIDE; private: scoped_refptr desktop_session_agent_; DISALLOW_COPY_AND_ASSIGN(DesktopSesssionClipboardStub); }; DesktopSesssionClipboardStub::DesktopSesssionClipboardStub( scoped_refptr desktop_session_agent) : desktop_session_agent_(desktop_session_agent) { } DesktopSesssionClipboardStub::~DesktopSesssionClipboardStub() { } void DesktopSesssionClipboardStub::InjectClipboardEvent( const protocol::ClipboardEvent& event) { desktop_session_agent_->InjectClipboardEvent(event); } } // namespace // webrtc::SharedMemory implementation that notifies creating // DesktopSessionAgent when it's deleted. class DesktopSessionAgent::SharedBuffer : public webrtc::SharedMemory { public: static scoped_ptr Create(DesktopSessionAgent* agent, size_t size, int id) { scoped_ptr memory(new base::SharedMemory()); if (!memory->CreateAndMapAnonymous(size)) return scoped_ptr(); return scoped_ptr( new SharedBuffer(agent, memory.Pass(), size, id)); } virtual ~SharedBuffer() { agent_->OnSharedBufferDeleted(id()); } private: SharedBuffer(DesktopSessionAgent* agent, scoped_ptr memory, size_t size, int id) : SharedMemory(memory->memory(), size, #if defined(OS_WIN) memory->handle(), #else memory->handle().fd, #endif id), agent_(agent), shared_memory_(memory.Pass()) { } DesktopSessionAgent* agent_; scoped_ptr shared_memory_; DISALLOW_COPY_AND_ASSIGN(SharedBuffer); }; DesktopSessionAgent::Delegate::~Delegate() { } DesktopSessionAgent::DesktopSessionAgent( scoped_refptr audio_capture_task_runner, scoped_refptr caller_task_runner, scoped_refptr input_task_runner, scoped_refptr io_task_runner, scoped_refptr video_capture_task_runner) : audio_capture_task_runner_(audio_capture_task_runner), caller_task_runner_(caller_task_runner), input_task_runner_(input_task_runner), io_task_runner_(io_task_runner), video_capture_task_runner_(video_capture_task_runner), control_factory_(this), desktop_pipe_(IPC::InvalidPlatformFileForTransit()), next_shared_buffer_id_(1), shared_buffers_(0), started_(false) { DCHECK(caller_task_runner_->BelongsToCurrentThread()); } bool DesktopSessionAgent::OnMessageReceived(const IPC::Message& message) { DCHECK(caller_task_runner_->BelongsToCurrentThread()); bool handled = true; if (started_) { IPC_BEGIN_MESSAGE_MAP(DesktopSessionAgent, message) IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_CaptureFrame, OnCaptureFrame) IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_InjectClipboardEvent, OnInjectClipboardEvent) IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_InjectKeyEvent, OnInjectKeyEvent) IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_InjectTextEvent, OnInjectTextEvent) IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_InjectMouseEvent, OnInjectMouseEvent) IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_SetScreenResolution, SetScreenResolution) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() } else { IPC_BEGIN_MESSAGE_MAP(DesktopSessionAgent, message) IPC_MESSAGE_HANDLER(ChromotingNetworkDesktopMsg_StartSessionAgent, OnStartSessionAgent) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() } CHECK(handled) << "Received unexpected IPC type: " << message.type(); return handled; } void DesktopSessionAgent::OnChannelConnected(int32 peer_pid) { DCHECK(caller_task_runner_->BelongsToCurrentThread()); VLOG(1) << "IPC: desktop <- network (" << peer_pid << ")"; CloseDesktopPipeHandle(); } void DesktopSessionAgent::OnChannelError() { DCHECK(caller_task_runner_->BelongsToCurrentThread()); // Make sure the channel is closed. network_channel_.reset(); CloseDesktopPipeHandle(); // Notify the caller that the channel has been disconnected. if (delegate_.get()) delegate_->OnNetworkProcessDisconnected(); } webrtc::SharedMemory* DesktopSessionAgent::CreateSharedMemory(size_t size) { DCHECK(video_capture_task_runner_->BelongsToCurrentThread()); scoped_ptr buffer = SharedBuffer::Create(this, size, next_shared_buffer_id_); if (buffer) { shared_buffers_++; // |next_shared_buffer_id_| starts from 1 and incrementing it by 2 makes // sure it is always odd and therefore zero is never used as a valid buffer // ID. // // It is very unlikely (though theoretically possible) to allocate the same // ID for two different buffers due to integer overflow. It should take // about a year of allocating 100 new buffers every second. Practically // speaking it never happens. next_shared_buffer_id_ += 2; IPC::PlatformFileForTransit handle; #if defined(OS_WIN) handle = buffer->handle(); #else handle = base::FileDescriptor(buffer->handle(), false); #endif SendToNetwork(new ChromotingDesktopNetworkMsg_CreateSharedBuffer( buffer->id(), handle, buffer->size())); } return buffer.release(); } DesktopSessionAgent::~DesktopSessionAgent() { DCHECK(!audio_capturer_); DCHECK(!desktop_environment_); DCHECK(!network_channel_); DCHECK(!screen_controls_); DCHECK(!video_capturer_); CloseDesktopPipeHandle(); } const std::string& DesktopSessionAgent::client_jid() const { return client_jid_; } void DesktopSessionAgent::DisconnectSession() { SendToNetwork(new ChromotingDesktopNetworkMsg_DisconnectSession()); } void DesktopSessionAgent::OnLocalMouseMoved( const webrtc::DesktopVector& new_pos) { DCHECK(caller_task_runner_->BelongsToCurrentThread()); remote_input_filter_->LocalMouseMoved(new_pos); } void DesktopSessionAgent::SetDisableInputs(bool disable_inputs) { DCHECK(caller_task_runner_->BelongsToCurrentThread()); // Do not expect this method to be called because it is only used by It2Me. NOTREACHED(); } void DesktopSessionAgent::OnStartSessionAgent( const std::string& authenticated_jid, const ScreenResolution& resolution, bool virtual_terminal) { DCHECK(caller_task_runner_->BelongsToCurrentThread()); DCHECK(!started_); DCHECK(!audio_capturer_); DCHECK(!desktop_environment_); DCHECK(!input_injector_); DCHECK(!screen_controls_); DCHECK(!video_capturer_); started_ = true; client_jid_ = authenticated_jid; // Enable the curtain mode. delegate_->desktop_environment_factory().SetEnableCurtaining( virtual_terminal); // Create a desktop environment for the new session. desktop_environment_ = delegate_->desktop_environment_factory().Create( control_factory_.GetWeakPtr()); // Create the session controller and set the initial screen resolution. screen_controls_ = desktop_environment_->CreateScreenControls(); SetScreenResolution(resolution); // Create the input injector. input_injector_ = desktop_environment_->CreateInputInjector(); // Hook up the input filter. input_tracker_.reset(new protocol::InputEventTracker(input_injector_.get())); remote_input_filter_.reset(new RemoteInputFilter(input_tracker_.get())); #if defined(OS_WIN) // LocalInputMonitorWin filters out an echo of the injected input before it // reaches |remote_input_filter_|. remote_input_filter_->SetExpectLocalEcho(false); #endif // defined(OS_WIN) // Start the input injector. scoped_ptr clipboard_stub( new DesktopSesssionClipboardStub(this)); input_injector_->Start(clipboard_stub.Pass()); // Start the audio capturer. if (delegate_->desktop_environment_factory().SupportsAudioCapture()) { audio_capturer_ = desktop_environment_->CreateAudioCapturer(); audio_capture_task_runner_->PostTask( FROM_HERE, base::Bind(&DesktopSessionAgent::StartAudioCapturer, this)); } // Start the video capturer. video_capturer_ = desktop_environment_->CreateVideoCapturer(); video_capture_task_runner_->PostTask( FROM_HERE, base::Bind(&DesktopSessionAgent::StartVideoCapturer, this)); } void DesktopSessionAgent::OnCaptureCompleted(webrtc::DesktopFrame* frame) { DCHECK(video_capture_task_runner_->BelongsToCurrentThread()); last_frame_.reset(frame); current_size_ = frame->size(); // Serialize webrtc::DesktopFrame. SerializedDesktopFrame serialized_frame; serialized_frame.shared_buffer_id = frame->shared_memory()->id(); serialized_frame.bytes_per_row = frame->stride(); serialized_frame.dimensions = frame->size(); serialized_frame.capture_time_ms = frame->capture_time_ms(); serialized_frame.dpi = frame->dpi(); for (webrtc::DesktopRegion::Iterator i(frame->updated_region()); !i.IsAtEnd(); i.Advance()) { serialized_frame.dirty_region.push_back(i.rect()); } SendToNetwork( new ChromotingDesktopNetworkMsg_CaptureCompleted(serialized_frame)); } void DesktopSessionAgent::OnCursorShapeChanged( webrtc::MouseCursorShape* cursor_shape) { DCHECK(video_capture_task_runner_->BelongsToCurrentThread()); scoped_ptr owned_cursor(cursor_shape); SendToNetwork(new ChromotingDesktopNetworkMsg_CursorShapeChanged( *cursor_shape)); } void DesktopSessionAgent::InjectClipboardEvent( const protocol::ClipboardEvent& event) { DCHECK(caller_task_runner_->BelongsToCurrentThread()); std::string serialized_event; if (!event.SerializeToString(&serialized_event)) { LOG(ERROR) << "Failed to serialize protocol::ClipboardEvent."; return; } SendToNetwork( new ChromotingDesktopNetworkMsg_InjectClipboardEvent(serialized_event)); } void DesktopSessionAgent::ProcessAudioPacket(scoped_ptr packet) { DCHECK(audio_capture_task_runner_->BelongsToCurrentThread()); std::string serialized_packet; if (!packet->SerializeToString(&serialized_packet)) { LOG(ERROR) << "Failed to serialize AudioPacket."; return; } SendToNetwork(new ChromotingDesktopNetworkMsg_AudioPacket(serialized_packet)); } bool DesktopSessionAgent::Start(const base::WeakPtr& delegate, IPC::PlatformFileForTransit* desktop_pipe_out) { DCHECK(caller_task_runner_->BelongsToCurrentThread()); DCHECK(delegate_.get() == NULL); delegate_ = delegate; // Create an IPC channel to communicate with the network process. bool result = CreateConnectedIpcChannel(io_task_runner_, this, &desktop_pipe_, &network_channel_); *desktop_pipe_out = desktop_pipe_; return result; } void DesktopSessionAgent::Stop() { DCHECK(caller_task_runner_->BelongsToCurrentThread()); delegate_.reset(); // Make sure the channel is closed. network_channel_.reset(); if (started_) { started_ = false; // Ignore any further callbacks. control_factory_.InvalidateWeakPtrs(); client_jid_.clear(); remote_input_filter_.reset(); // Ensure that any pressed keys or buttons are released. input_tracker_->ReleaseAll(); input_tracker_.reset(); desktop_environment_.reset(); input_injector_.reset(); screen_controls_.reset(); // Stop the audio capturer. audio_capture_task_runner_->PostTask( FROM_HERE, base::Bind(&DesktopSessionAgent::StopAudioCapturer, this)); // Stop the video capturer. video_capture_task_runner_->PostTask( FROM_HERE, base::Bind(&DesktopSessionAgent::StopVideoCapturer, this)); } } void DesktopSessionAgent::OnCaptureFrame() { if (!video_capture_task_runner_->BelongsToCurrentThread()) { video_capture_task_runner_->PostTask( FROM_HERE, base::Bind(&DesktopSessionAgent::OnCaptureFrame, this)); return; } // webrtc::ScreenCapturer supports a very few (currently 2) outstanding // capture requests. The requests are serialized on // |video_capture_task_runner()| task runner. If the client issues more // requests, pixel data in captured frames will likely be corrupted but // stability of webrtc::ScreenCapturer will not be affected. video_capturer_->Capture(webrtc::DesktopRegion()); } void DesktopSessionAgent::OnInjectClipboardEvent( const std::string& serialized_event) { DCHECK(caller_task_runner_->BelongsToCurrentThread()); protocol::ClipboardEvent event; if (!event.ParseFromString(serialized_event)) { LOG(ERROR) << "Failed to parse protocol::ClipboardEvent."; return; } // InputStub implementations must verify events themselves, so we don't need // verification here. This matches HostEventDispatcher. input_injector_->InjectClipboardEvent(event); } void DesktopSessionAgent::OnInjectKeyEvent( const std::string& serialized_event) { DCHECK(caller_task_runner_->BelongsToCurrentThread()); protocol::KeyEvent event; if (!event.ParseFromString(serialized_event)) { LOG(ERROR) << "Failed to parse protocol::KeyEvent."; return; } // InputStub implementations must verify events themselves, so we need only // basic verification here. This matches HostEventDispatcher. if (!event.has_usb_keycode() || !event.has_pressed()) { LOG(ERROR) << "Received invalid key event."; return; } remote_input_filter_->InjectKeyEvent(event); } void DesktopSessionAgent::OnInjectTextEvent( const std::string& serialized_event) { DCHECK(caller_task_runner_->BelongsToCurrentThread()); protocol::TextEvent event; if (!event.ParseFromString(serialized_event)) { LOG(ERROR) << "Failed to parse protocol::TextEvent."; return; } // InputStub implementations must verify events themselves, so we need only // basic verification here. This matches HostEventDispatcher. if (!event.has_text()) { LOG(ERROR) << "Received invalid TextEvent."; return; } remote_input_filter_->InjectTextEvent(event); } void DesktopSessionAgent::OnInjectMouseEvent( const std::string& serialized_event) { DCHECK(caller_task_runner_->BelongsToCurrentThread()); protocol::MouseEvent event; if (!event.ParseFromString(serialized_event)) { LOG(ERROR) << "Failed to parse protocol::MouseEvent."; return; } // InputStub implementations must verify events themselves, so we don't need // verification here. This matches HostEventDispatcher. remote_input_filter_->InjectMouseEvent(event); } void DesktopSessionAgent::SetScreenResolution( const ScreenResolution& resolution) { DCHECK(caller_task_runner_->BelongsToCurrentThread()); if (screen_controls_ && resolution.IsEmpty()) screen_controls_->SetScreenResolution(resolution); } void DesktopSessionAgent::SendToNetwork(IPC::Message* message) { if (!caller_task_runner_->BelongsToCurrentThread()) { caller_task_runner_->PostTask( FROM_HERE, base::Bind(&DesktopSessionAgent::SendToNetwork, this, message)); return; } if (network_channel_) { network_channel_->Send(message); } else { delete message; } } void DesktopSessionAgent::StartAudioCapturer() { DCHECK(audio_capture_task_runner_->BelongsToCurrentThread()); if (audio_capturer_) { audio_capturer_->Start(base::Bind(&DesktopSessionAgent::ProcessAudioPacket, this)); } } void DesktopSessionAgent::StopAudioCapturer() { DCHECK(audio_capture_task_runner_->BelongsToCurrentThread()); audio_capturer_.reset(); } void DesktopSessionAgent::StartVideoCapturer() { DCHECK(video_capture_task_runner_->BelongsToCurrentThread()); if (video_capturer_) { video_capturer_->SetMouseShapeObserver(this); video_capturer_->Start(this); } } void DesktopSessionAgent::StopVideoCapturer() { DCHECK(video_capture_task_runner_->BelongsToCurrentThread()); video_capturer_.reset(); last_frame_.reset(); // Video capturer must delete all buffers. DCHECK_EQ(shared_buffers_, 0); } void DesktopSessionAgent::OnSharedBufferDeleted(int id) { DCHECK(video_capture_task_runner_->BelongsToCurrentThread()); DCHECK(id != 0); shared_buffers_--; DCHECK_GE(shared_buffers_, 0); SendToNetwork(new ChromotingDesktopNetworkMsg_ReleaseSharedBuffer(id)); } void DesktopSessionAgent::CloseDesktopPipeHandle() { if (!(desktop_pipe_ == IPC::InvalidPlatformFileForTransit())) { #if defined(OS_WIN) base::ClosePlatformFile(desktop_pipe_); #elif defined(OS_POSIX) base::ClosePlatformFile(desktop_pipe_.fd); #else // !defined(OS_POSIX) #error Unsupported platform. #endif // !defined(OS_POSIX) desktop_pipe_ = IPC::InvalidPlatformFileForTransit(); } } } // namespace remoting