| // 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_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<DesktopSessionAgent> desktop_session_agent); |
| virtual ~DesktopSesssionClipboardStub(); |
| |
| // protocol::ClipboardStub implementation. |
| virtual void InjectClipboardEvent( |
| const protocol::ClipboardEvent& event) OVERRIDE; |
| |
| private: |
| scoped_refptr<DesktopSessionAgent> desktop_session_agent_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DesktopSesssionClipboardStub); |
| }; |
| |
| DesktopSesssionClipboardStub::DesktopSesssionClipboardStub( |
| scoped_refptr<DesktopSessionAgent> 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<SharedBuffer> Create(DesktopSessionAgent* agent, |
| size_t size, |
| int id) { |
| scoped_ptr<base::SharedMemory> memory(new base::SharedMemory()); |
| if (!memory->CreateAndMapAnonymous(size)) |
| return scoped_ptr<SharedBuffer>(); |
| return scoped_ptr<SharedBuffer>( |
| new SharedBuffer(agent, memory.Pass(), size, id)); |
| } |
| |
| virtual ~SharedBuffer() { |
| agent_->OnSharedBufferDeleted(id()); |
| } |
| |
| private: |
| SharedBuffer(DesktopSessionAgent* agent, |
| scoped_ptr<base::SharedMemory> 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<base::SharedMemory> shared_memory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SharedBuffer); |
| }; |
| |
| DesktopSessionAgent::Delegate::~Delegate() { |
| } |
| |
| DesktopSessionAgent::DesktopSessionAgent( |
| scoped_refptr<AutoThreadTaskRunner> audio_capture_task_runner, |
| scoped_refptr<AutoThreadTaskRunner> caller_task_runner, |
| scoped_refptr<AutoThreadTaskRunner> input_task_runner, |
| scoped_refptr<AutoThreadTaskRunner> io_task_runner, |
| scoped_refptr<AutoThreadTaskRunner> 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_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<SharedBuffer> 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 SkIPoint& 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<protocol::ClipboardStub> 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<webrtc::MouseCursorShape> 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<AudioPacket> 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>& 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::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 |