// 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/host/desktop_session_agent.h"

#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/process/process_handle.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "ipc/ipc_channel_proxy.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
#include "mojo/public/cpp/system/message_pipe.h"
#include "remoting/base/auto_thread_task_runner.h"
#include "remoting/base/errors.h"
#include "remoting/host/action_executor.h"
#include "remoting/host/audio_capturer.h"
#include "remoting/host/base/desktop_environment_options.h"
#include "remoting/host/base/screen_controls.h"
#include "remoting/host/base/screen_resolution.h"
#include "remoting/host/crash_process.h"
#include "remoting/host/desktop_display_info_monitor.h"
#include "remoting/host/desktop_environment.h"
#include "remoting/host/input_injector.h"
#include "remoting/host/keyboard_layout_monitor.h"
#include "remoting/host/mojom/desktop_session.mojom.h"
#include "remoting/host/mouse_shape_pump.h"
#include "remoting/host/remote_input_filter.h"
#include "remoting/host/remote_open_url/url_forwarder_configurator.h"
#include "remoting/host/webauthn/remote_webauthn_state_change_notifier.h"
#include "remoting/proto/action.pb.h"
#include "remoting/proto/audio.pb.h"
#include "remoting/proto/control.pb.h"
#include "remoting/proto/event.pb.h"
#include "remoting/proto/url_forwarder_control.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/mouse_cursor.h"
#include "ui/events/types/event_type.h"

#if BUILDFLAG(IS_POSIX)
#include "remoting/host/security_key/security_key_auth_handler_posix.h"
#endif

namespace remoting {

namespace {

using SetUpUrlForwarderResponse =
    protocol::UrlForwarderControl::SetUpUrlForwarderResponse;

// Routes local clipboard events though the IPC channel to the network process.
class DesktopSessionClipboardStub : public protocol::ClipboardStub {
 public:
  explicit DesktopSessionClipboardStub(
      scoped_refptr<DesktopSessionAgent> desktop_session_agent);

  DesktopSessionClipboardStub(const DesktopSessionClipboardStub&) = delete;
  DesktopSessionClipboardStub& operator=(const DesktopSessionClipboardStub&) =
      delete;

  ~DesktopSessionClipboardStub() override;

  // protocol::ClipboardStub implementation.
  void InjectClipboardEvent(const protocol::ClipboardEvent& event) override;

 private:
  scoped_refptr<DesktopSessionAgent> desktop_session_agent_;
};

DesktopSessionClipboardStub::DesktopSessionClipboardStub(
    scoped_refptr<DesktopSessionAgent> desktop_session_agent)
    : desktop_session_agent_(desktop_session_agent) {}

DesktopSessionClipboardStub::~DesktopSessionClipboardStub() = default;

void DesktopSessionClipboardStub::InjectClipboardEvent(
    const protocol::ClipboardEvent& event) {
  desktop_session_agent_->OnClipboardEvent(event);
}

}  // namespace

DesktopSessionAgent::Delegate::~Delegate() = default;

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)
    : 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) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
}

void DesktopSessionAgent::OnChannelConnected(std::int32_t peer_pid) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  VLOG(1) << "IPC: desktop <- network (" << peer_pid << ")";
}

void DesktopSessionAgent::OnChannelError() {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  // Make sure the channel is closed.
  network_channel_.reset();

  // Notify the caller that the channel has been disconnected.
  if (delegate_.get()) {
    delegate_->OnNetworkProcessDisconnected();
  }
}

void DesktopSessionAgent::OnAssociatedInterfaceRequest(
    const std::string& interface_name,
    mojo::ScopedInterfaceEndpointHandle handle) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  if (interface_name == mojom::DesktopSessionAgent::Name_) {
    if (desktop_session_agent_.is_bound()) {
      LOG(ERROR) << "Receiver already bound for associated interface: "
                 << mojom::DesktopSessionAgent::Name_;
      delegate_->CrashNetworkProcess(base::Location::Current());
    }

    mojo::PendingAssociatedReceiver<mojom::DesktopSessionAgent>
        pending_receiver(std::move(handle));
    desktop_session_agent_.Bind(std::move(pending_receiver));
  } else {
    LOG(ERROR) << "Unknown associated interface requested: " << interface_name
               << ", crashing the network process";
    delegate_->CrashNetworkProcess(base::Location::Current());
  }
}

DesktopSessionAgent::~DesktopSessionAgent() {
  DCHECK(!audio_capturer_);
  DCHECK(!desktop_environment_);
  DCHECK(!network_channel_);
  DCHECK(!screen_controls_);
  DCHECK(video_capturers_.IsEmpty());
  DCHECK(!session_file_operations_handler_);
}

const std::string& DesktopSessionAgent::client_jid() const {
  return client_jid_;
}

void DesktopSessionAgent::DisconnectSession(
    ErrorCode error,
    std::string_view error_details,
    const SourceLocation& error_location) {
  if (desktop_session_state_handler_) {
    desktop_session_state_handler_->DisconnectSession(
        error, std::string(error_details), error_location);
  }
}

void DesktopSessionAgent::OnLocalKeyPressed(std::uint32_t usb_keycode) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  remote_input_filter_->LocalKeyPressed(usb_keycode);

  if (desktop_session_event_handler_) {
    desktop_session_event_handler_->OnLocalKeyboardInputDetected(usb_keycode);
  }
}

void DesktopSessionAgent::OnLocalPointerMoved(
    const webrtc::DesktopVector& new_pos,
    ui::EventType type) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  remote_input_filter_->LocalPointerMoved(new_pos, type);

  if (desktop_session_event_handler_) {
    // |type| is always kMouseMoved, if this changes, we need to convey this
    // information to the network process.
    DCHECK_EQ(type, ui::EventType::kMouseMoved);
    desktop_session_event_handler_->OnLocalMouseMoveDetected(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::OnDesktopDisplayChanged(
    std::unique_ptr<protocol::VideoLayout> layout) {
  LOG(INFO) << "DSA::OnDesktopDisplayChanged";
  for (int display_id = 0; display_id < layout->video_track_size();
       display_id++) {
    protocol::VideoTrackLayout track = layout->video_track(display_id);
    LOG(INFO) << "   #" << display_id << " : "
              << " [" << track.x_dpi() << "," << track.y_dpi() << "]";
  }
  if (desktop_session_event_handler_) {
    desktop_session_event_handler_->OnDesktopDisplayChanged(*layout);
  }
}

void DesktopSessionAgent::OnMicrophoneControl(
    const protocol::MicrophoneControl& control) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  if (desktop_session_event_handler_) {
    desktop_session_event_handler_->OnMicrophoneControl(control);
  }
}

void DesktopSessionAgent::OnAudioInjectorConsumersChanged(bool has_consumers) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  protocol::MicrophoneControl control;
  control.set_enable(has_consumers);
  OnMicrophoneControl(control);
}

void DesktopSessionAgent::Start(
    const std::string& authenticated_jid,
    const ScreenResolution& resolution,
    const remoting::DesktopEnvironmentOptions& options,
    StartCallback callback) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  DCHECK(!audio_capturer_);
  DCHECK(!desktop_environment_);
  DCHECK(!input_injector_);
  DCHECK(!screen_controls_);
  DCHECK(video_capturers_.IsEmpty());
  DCHECK(!session_file_operations_handler_);

  if (started_) {
    LOG(ERROR) << __func__ << " called more than once for the current process.";
    delegate_->CrashNetworkProcess(base::Location::Current());
    // No need to run the callback since it just calls into the process we are
    // asking the daemon process to crash.
    callback.Reset();
    return;
  }

  started_ = true;
  client_jid_ = authenticated_jid;

  // Hook up the associated interfaces.
  network_channel_->GetRemoteAssociatedInterface(
      &desktop_session_event_handler_);
  network_channel_->GetRemoteAssociatedInterface(
      &desktop_session_state_handler_);

#if BUILDFLAG(IS_POSIX)
  if (options.enable_security_key()) {
    const base::FilePath& socket_name =
        SecurityKeyAuthHandlerPosix::GetSecurityKeySocketName();
    if (!socket_name.empty()) {
      // Since all socket operations in SecurityKeyAuthHandlerPosix are done on
      // the caller's sequence, we wrap it with a SequenceBound to run it on the
      // IO thread.
      security_key_auth_handler_ =
          base::SequenceBound<SecurityKeyAuthHandlerPosix>(io_task_runner_);

      // We pass `base::Unretained(this)` as the `client_id` token. This is
      // safe because the handler only uses it as an opaque key for
      // comparison and never dereferences it.
      security_key_auth_handler_
          .AsyncCall(&SecurityKeyAuthHandler::SetSendMessageCallback)
          .WithArgs(base::BindPostTaskToCurrentDefault(base::BindRepeating(
                        &DesktopSessionAgent::OnSecurityKeyMessage,
                        weak_factory_.GetWeakPtr())),
                    base::Unretained(this));

      security_key_auth_handler_.AsyncCall(
          &SecurityKeyAuthHandler::CreateSecurityKeyConnection);
    } else {
      LOG(WARNING) << "Security key forwarding is enabled, but the socket name "
                   << "is empty (e.g. XDG_RUNTIME_DIR is not set). "
                   << "Forwarding will be disabled.";
    }
  }
#endif

  // Create a desktop environment for the new session.
  delegate_->desktop_environment_factory().Create(
      weak_factory_.GetWeakPtr(), /* client_session_events= */ nullptr, options,
      base::BindOnce(&DesktopSessionAgent::OnDesktopEnvironmentCreated,
                     weak_factory_.GetWeakPtr(), resolution,
                     std::move(callback)));
}

void DesktopSessionAgent::OnMouseCursor(
    std::unique_ptr<webrtc::MouseCursor> cursor) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  if (desktop_session_event_handler_) {
    desktop_session_event_handler_->OnMouseCursorChanged(*cursor);
  }

  video_capturers_.SetMouseCursor(*cursor);
}

void DesktopSessionAgent::OnMouseCursorPosition(
    const webrtc::DesktopVector& position) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  if (!host_cursor_rendered_by_client_) {
    video_capturers_.SetMouseCursorPosition(position);
  }
}

void DesktopSessionAgent::OnMouseCursorFractionalPosition(
    const protocol::FractionalCoordinate& position) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  if (desktop_session_event_handler_) {
    desktop_session_event_handler_->OnMouseCursorFractionalPositionChanged(
        position);
  }
}

void DesktopSessionAgent::OnClipboardEvent(
    const protocol::ClipboardEvent& event) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  CHECK(started_) << "OnClipboardEvent called before agent was started.";

  if (desktop_session_event_handler_) {
    desktop_session_event_handler_->OnClipboardEvent(event);
  }
}

void DesktopSessionAgent::ProcessAudioPacket(
    std::unique_ptr<AudioPacket> packet) {
  // AudioPackets are received on the audio_capture task runner but must be sent
  // over IPC on the same task_runner the mojo remote was bound on.
  if (!caller_task_runner_->BelongsToCurrentThread()) {
    DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());
    caller_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&DesktopSessionAgent::ProcessAudioPacket,
                                  this, std::move(packet)));
    return;
  }

  if (desktop_session_event_handler_) {
    desktop_session_event_handler_->OnAudioPacket(std::move(packet));
  }
}

mojo::ScopedMessagePipeHandle DesktopSessionAgent::Initialize(
    const base::WeakPtr<Delegate>& delegate) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  DCHECK(delegate);
  DCHECK(!delegate_);

  delegate_ = delegate;

  mojo::MessagePipe pipe;
  network_channel_ = IPC::ChannelProxy::Create(
      std::move(pipe.handle0), IPC::Channel::MODE_SERVER, this, io_task_runner_,
      base::SingleThreadTaskRunner::GetCurrentDefault());
  return std::move(pipe.handle1);
}

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.
    weak_factory_.InvalidateWeakPtrs();
    client_jid_.clear();

    desktop_session_event_handler_.reset();
    desktop_session_state_handler_.reset();
    desktop_session_control_.reset();
    desktop_session_agent_.reset();

    url_forwarder_configurator_.reset();
    webauthn_state_change_notifier_.reset();

    remote_input_filter_.reset();

    session_file_operations_handler_.reset();

    // Ensure that any pressed keys or buttons are released.
    input_tracker_->ReleaseAll();
    input_tracker_.reset();

    desktop_environment_.reset();
    action_executor_.reset();
    input_injector_.reset();
    screen_controls_.reset();
    keyboard_layout_monitor_.reset();
    audio_injector_.reset();
    security_key_auth_handler_ = {};
    security_key_remotes_.clear();

    // Stop the audio capturer.
    audio_capture_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(&DesktopSessionAgent::StopAudioCapturer, this));

    // Stop the video capturers.
    video_capturers_.Clear();
    mouse_shape_pump_.reset();
  }
}

void DesktopSessionAgent::CreateVideoCapturer(
    std::int64_t desktop_display_id,
    CreateVideoCapturerCallback callback) {
  std::move(callback).Run(video_capturers_.CreateVideoCapturer(
      desktop_display_id, desktop_environment_.get(), caller_task_runner_));
}

void DesktopSessionAgent::InjectClipboardEvent(
    const protocol::ClipboardEvent& event) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  CHECK(started_);

  // InputStub implementations must verify events themselves, so we don't need
  // verification here. This matches HostEventDispatcher.
  input_injector_->InjectClipboardEvent(event);
}

void DesktopSessionAgent::InjectKeyEvent(const protocol::KeyEvent& event) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  CHECK(started_);

  // 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::InjectTextEvent(const protocol::TextEvent& event) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  CHECK(started_);

  // 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::InjectMouseEvent(const protocol::MouseEvent& event) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  CHECK(started_);

  if (!host_cursor_rendered_by_client_) {
    video_capturers_.SetComposeEnabled(event.has_delta_x() ||
                                       event.has_delta_y());
  }

  // InputStub implementations must verify events themselves, so we don't need
  // verification here. This matches HostEventDispatcher.
  remote_input_filter_->InjectMouseEvent(event);
}

void DesktopSessionAgent::InjectTouchEvent(const protocol::TouchEvent& event) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  CHECK(started_);

  remote_input_filter_->InjectTouchEvent(event);
}

void DesktopSessionAgent::InjectSendAttentionSequence() {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  CHECK(started_);

  protocol::ActionRequest request;
  request.set_action(protocol::ActionRequest::SEND_ATTENTION_SEQUENCE);
  action_executor_->ExecuteAction(request);
}

void DesktopSessionAgent::LockWorkstation() {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  CHECK(started_);

  protocol::ActionRequest request;
  request.set_action(protocol::ActionRequest::LOCK_WORKSTATION);
  action_executor_->ExecuteAction(request);
}

void DesktopSessionAgent::OnKeyboardLayoutChange(
    const protocol::KeyboardLayout& layout) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  if (desktop_session_event_handler_) {
    desktop_session_event_handler_->OnKeyboardLayoutChanged(layout);
  }
}

void DesktopSessionAgent::SetScreenResolution(
    const ScreenResolution& resolution,
    std::optional<std::int64_t> screen_id) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  CHECK(started_);

  if (screen_controls_) {
    screen_controls_->SetScreenResolution(resolution, screen_id);
  }
}

void DesktopSessionAgent::SetVideoLayout(const protocol::VideoLayout& layout) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  CHECK(started_);

  if (screen_controls_) {
    screen_controls_->SetVideoLayout(layout);
  }
}

void DesktopSessionAgent::StartAudioCapturer() {
  DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());

  if (audio_capturer_) {
    audio_capturer_->Start(
        base::BindRepeating(&DesktopSessionAgent::ProcessAudioPacket, this));
  }
}

void DesktopSessionAgent::StopAudioCapturer() {
  DCHECK(audio_capture_task_runner_->BelongsToCurrentThread());

  audio_capturer_.reset();
}

void DesktopSessionAgent::SetUpUrlForwarder() {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  CHECK(started_);

  url_forwarder_configurator_->SetUpUrlForwarder(base::BindRepeating(
      &DesktopSessionAgent::OnUrlForwarderSetUpStateChanged, this));
}

void DesktopSessionAgent::SignalWebAuthnExtension() {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  CHECK(started_);

  webauthn_state_change_notifier_->NotifyStateChange();
}

void DesktopSessionAgent::BeginFileRead(BeginFileReadCallback callback) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  CHECK(started_);

  session_file_operations_handler_->BeginFileRead(std::move(callback));
}

void DesktopSessionAgent::BeginFileWrite(const base::FilePath& file_path,
                                         BeginFileWriteCallback callback) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  CHECK(started_);

  session_file_operations_handler_->BeginFileWrite(file_path,
                                                   std::move(callback));
}

void DesktopSessionAgent::SetHostCursorRenderedByClient() {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  if (host_cursor_rendered_by_client_) {
    return;
  }

  host_cursor_rendered_by_client_ = true;
  // Hide the host cursor from the desktop frames.
  video_capturers_.SetComposeEnabled(false);
  if (mouse_shape_pump_) {
    mouse_shape_pump_->SetSendCursorPositionToClient(true);
  }
}

void DesktopSessionAgent::StartAudioInjector(
    std::unique_ptr<IpcFifoBufferReader> audio_reader) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  if (audio_injector_) {
    return;
  }

  audio_injector_ =
      desktop_environment_->CreateAudioInjector(std::move(audio_reader));
  if (!audio_injector_) {
    LOG(ERROR) << "Cannot start audio injector because it is not supported.";
    return;
  }
  if (pending_audio_sample_info_) {
    base::OnceCallback<void(bool)> done =
        pending_audio_sample_info_callback_
            ? std::move(pending_audio_sample_info_callback_)
            : base::DoNothing();
    audio_injector_->SetSampleInfo(*pending_audio_sample_info_,
                                   std::move(done));
    pending_audio_sample_info_.reset();
  }
  audio_injector_->Start(weak_factory_.GetWeakPtr());
}

void DesktopSessionAgent::SetAudioInjectorSampleInfo(
    const protocol::AudioSampleInfo& info,
    SetAudioInjectorSampleInfoCallback callback) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  if (audio_injector_) {
    audio_injector_->SetSampleInfo(info, std::move(callback));
  } else {
    if (pending_audio_sample_info_callback_) {
      std::move(pending_audio_sample_info_callback_).Run(false);
    }
    pending_audio_sample_info_ = info;
    pending_audio_sample_info_callback_ = std::move(callback);
  }
}

void DesktopSessionAgent::OnDesktopEnvironmentCreated(
    const ScreenResolution& resolution,
    StartCallback callback,
    std::unique_ptr<DesktopEnvironment> desktop_environment) {
  // TODO(rkjnsn): Handle null desktop_environment once multiprocess is
  // is supported on platforms where remote desktop session creation can fail.

  desktop_environment_ = std::move(desktop_environment);

  // Create the session controller and set the initial screen resolution.
  screen_controls_ = desktop_environment_->CreateScreenControls();
  SetScreenResolution(resolution, std::nullopt);

  // Create the input injector.
  input_injector_ = desktop_environment_->CreateInputInjector();

  action_executor_ = desktop_environment_->CreateActionExecutor();

  // Hook up the input filter.
  input_tracker_ =
      std::make_unique<protocol::InputEventTracker>(input_injector_.get());
  // TODO: crbug.com/456252029 - Verify that `remote_input_filter_` is a no-op
  // then remove it.
  remote_input_filter_ = std::make_unique<RemoteInputFilter>(
      input_tracker_.get(),
      // Unretained() is safe because `remote_input_filter_` will be destroyed
      // before `input_tracker_`, after which the callback will no longer be
      // called.
      base::BindRepeating(&protocol::InputEventTracker::ReleaseAll,
                          base::Unretained(input_tracker_.get())));

#if BUILDFLAG(IS_WIN)
  // LocalInputMonitorWin filters out an echo of the injected input before it
  // reaches |remote_input_filter_|.
  remote_input_filter_->SetExpectLocalEcho(false);
#endif  // BUILDFLAG(IS_WIN)

  // Start the input injector.
  std::unique_ptr<protocol::ClipboardStub> clipboard_stub(
      new DesktopSessionClipboardStub(this));
  input_injector_->Start(std::move(clipboard_stub));

  // Start the audio capturer.
  if (delegate_->desktop_environment_factory().SupportsAudioCapture()) {
    audio_capturer_ = desktop_environment_->CreateAudioCapturer();
    audio_capture_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(&DesktopSessionAgent::StartAudioCapturer, this));
  }

  // Start the mouse cursor monitor.
  mouse_shape_pump_ = std::make_unique<MouseShapePump>(
      desktop_environment_->CreateMouseCursorMonitor(),
      /*CursorShapeStub*/ nullptr);
  mouse_shape_pump_->SetMouseCursorMonitorCallback(this);
  if (host_cursor_rendered_by_client_) {
    // Just always send cursor positions to the "client", i.e. the network
    // process. The MouseShapePump in the network process will decide whether
    // they should actually be sent to the client.
    mouse_shape_pump_->SetSendCursorPositionToClient(true);
  }

  // Unretained is sound because callback will never be invoked after
  // |keyboard_layout_monitor_| is destroyed.
  keyboard_layout_monitor_ = desktop_environment_->CreateKeyboardLayoutMonitor(
      base::BindRepeating(&DesktopSessionAgent::OnKeyboardLayoutChange,
                          base::Unretained(this)));
  keyboard_layout_monitor_->Start();

  // Begin observing the desktop display(s) for changes. Note that some
  // desktop environments may not provide a display info monitor.
  auto* display_info_monitor = desktop_environment_->GetDisplayInfoMonitor();
  if (display_info_monitor) {
    display_info_monitor->Start();
  }

  // Set up the message handler for file transfers.
  session_file_operations_handler_.emplace(
      desktop_environment_->CreateFileOperations());

  url_forwarder_configurator_ =
      desktop_environment_->CreateUrlForwarderConfigurator();

  // Check and report the initial URL forwarder setup state.
  url_forwarder_configurator_->IsUrlForwarderSetUp(base::BindOnce(
      &DesktopSessionAgent::OnCheckUrlForwarderSetUpResult, this));

  webauthn_state_change_notifier_ =
      desktop_environment_->CreateRemoteWebAuthnStateChangeNotifier();

  std::move(callback).Run(
      desktop_session_control_.BindNewEndpointAndPassRemote());
}

void DesktopSessionAgent::OnCheckUrlForwarderSetUpResult(bool is_set_up) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  if (!desktop_session_event_handler_) {
    // Callback passed to UrlForwarderConfiguratorWin::IsUrlForwarderSetUp()
    // may be called after the configurator is deleted and the agent is
    // stopped, so we need to null-check |desktop_session_event_handler_|.
    //
    // TODO(yuweih): Scope callback of IsUrlForwarderSetUp() to the lifetime of
    //     the UrlForwarderConfiguratorWin instance.
    return;
  }
  desktop_session_event_handler_->OnUrlForwarderStateChange(
      is_set_up ? mojom::UrlForwarderState::kSetUp
                : mojom::UrlForwarderState::kNotSetUp);
}

void DesktopSessionAgent::OnUrlForwarderSetUpStateChanged(
    SetUpUrlForwarderResponse::State state) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  mojom::UrlForwarderState mojo_state;
  switch (state) {
    case SetUpUrlForwarderResponse::COMPLETE:
      mojo_state = mojom::UrlForwarderState::kSetUp;
      break;
    case SetUpUrlForwarderResponse::FAILED:
      mojo_state = mojom::UrlForwarderState::kFailed;
      break;
    case SetUpUrlForwarderResponse::USER_INTERVENTION_REQUIRED:
      mojo_state = mojom::UrlForwarderState::kSetupPendingUserIntervention;
      break;
    default:
      NOTREACHED() << "Unknown state: " << state;
  }
  desktop_session_event_handler_->OnUrlForwarderStateChange(mojo_state);
}

void DesktopSessionAgent::OnSecurityKeyMessage(int connection_id,
                                               const std::string& data) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  auto& remote = security_key_remotes_[connection_id];
  if (!remote.is_bound()) {
    desktop_session_event_handler_->OnSecurityKeyConnection(
        remote.BindNewPipeAndPassReceiver());
    remote.set_disconnect_handler(
        base::BindOnce(&DesktopSessionAgent::OnSecurityKeyRemoteDisconnected,
                       weak_factory_.GetWeakPtr(), connection_id));
  }

  remote->OnSecurityKeyRequest(
      data, base::BindOnce(&DesktopSessionAgent::OnSecurityKeyResponse,
                           weak_factory_.GetWeakPtr(), connection_id));
}

void DesktopSessionAgent::OnSecurityKeyResponse(int connection_id,
                                                const std::string& data) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  security_key_auth_handler_
      .AsyncCall(&SecurityKeyAuthHandler::SendClientResponse)
      .WithArgs(connection_id, data);
}

void DesktopSessionAgent::OnSecurityKeyRemoteDisconnected(int connection_id) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  security_key_remotes_.erase(connection_id);
}
}  // namespace remoting
