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

#include <memory>
#include <string>
#include <utility>

#include "base/check.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "remoting/host/action_executor.h"
#include "remoting/host/audio_injector.h"
#include "remoting/host/base/desktop_environment_options.h"
#include "remoting/host/base/screen_controls.h"
#include "remoting/host/basic_desktop_environment.h"
#include "remoting/host/client_session_control.h"
#include "remoting/host/curtain_mode.h"
#include "remoting/host/desktop_environment.h"
#include "remoting/host/desktop_interaction_strategy.h"
#include "remoting/host/host_window.h"
#include "remoting/host/host_window_proxy.h"
#include "remoting/host/input_monitor/local_input_monitor.h"
#include "remoting/host/resizing_host_observer.h"
#include "remoting/protocol/capability_names.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"

#if BUILDFLAG(IS_POSIX)
#include <sys/types.h>
#include <unistd.h>
#endif  // BUILDFLAG(IS_POSIX)

#if BUILDFLAG(IS_WIN)
#include "base/win/windows_version.h"
#endif  // BUILDFLAG(IS_WIN)

#if defined(REMOTING_USE_X11)
#include "remoting/host/linux/desktop_resizer_x11.h"
#include "remoting/host/linux/x11_util.h"
#include "ui/gfx/x/connection.h"
#endif  // defined(REMOTING_USE_X11)

namespace remoting {

namespace {

#if defined(REMOTING_USE_X11)

// Helper function that caches the result of IsUsingVideoDummyDriver().
bool UsingVideoDummyDriver() {
  static bool is_using_dummy_driver =
      IsUsingVideoDummyDriver(x11::Connection::Get());
  return is_using_dummy_driver;
}

bool RunningUnderWayland() {
  static bool is_running_under_wayland =
      webrtc::DesktopCapturer::IsRunningUnderWayland();
  return is_running_under_wayland;
}

#endif  // defined(REMOTING_USE_X11)

}  // namespace

Me2MeDesktopEnvironment::~Me2MeDesktopEnvironment() {
  DCHECK(caller_task_runner()->BelongsToCurrentThread());
}

std::unique_ptr<ActionExecutor>
Me2MeDesktopEnvironment::CreateActionExecutor() {
  DCHECK(caller_task_runner()->BelongsToCurrentThread());

  return interaction_strategy().CreateActionExecutor();
}

std::unique_ptr<ScreenControls>
Me2MeDesktopEnvironment::CreateScreenControls() {
  DCHECK(caller_task_runner()->BelongsToCurrentThread());

  // We only want to restore the host resolution on disconnect if we are not
  // curtained so we don't mess up the user's window layout unnecessarily if
  // they disconnect and reconnect. Both OS X and Windows will restore the
  // resolution automatically when the user logs back in on the console, and on
  // Linux the curtain-mode uses a separate session.
  auto resizer = std::make_unique<ResizingHostObserver>(
      interaction_strategy().CreateDesktopResizer(), curtain_ == nullptr);
  resizer->RegisterForDisplayChanges(*GetDisplayInfoMonitor());
  return resizer;
}

std::string Me2MeDesktopEnvironment::GetCapabilities() const {
  std::string capabilities = BasicDesktopEnvironment::GetCapabilities();
  if (!capabilities.empty()) {
    capabilities += " ";
  }
  capabilities += protocol::kRateLimitResizeRequests;

#if BUILDFLAG(IS_WIN)
  capabilities += " ";
  capabilities += protocol::kSendAttentionSequenceAction;

  if (base::win::OSInfo::GetInstance()->version_type() !=
      base::win::VersionType::SUITE_HOME) {
    capabilities += " ";
    capabilities += protocol::kLockWorkstationAction;
  }
#endif  // BUILDFLAG(IS_WIN)

  if (desktop_environment_options().enable_remote_webauthn()) {
    capabilities += " ";
    capabilities += protocol::kRemoteWebAuthnCapability;
  }

  if (AudioInjector::IsSupported()) {
    capabilities += " ";
    capabilities += protocol::kMicrophoneRemotingCapability;
  }

#if BUILDFLAG(IS_LINUX) && defined(REMOTING_USE_X11)
  capabilities += " ";
  capabilities += protocol::kMultiStreamCapability;
  capabilities += " ";
  capabilities += protocol::kDefaultResizeCapability;

  if (RunningUnderWayland()) {
    capabilities += " ";
    capabilities += protocol::kClientControlledLayoutCapability;
    capabilities += " ";
    capabilities += protocol::kHighDpiCapability;
  } else if (UsingVideoDummyDriver()) {
    capabilities += " ";
    capabilities += protocol::kClientControlledLayoutCapability;

    if (DesktopResizerX11::supportsHighDpiResize()) {
      capabilities += " ";
      capabilities += protocol::kHighDpiCapability;
    }
  }
#elif BUILDFLAG(IS_MAC)
  capabilities += " ";
  capabilities += protocol::kMultiStreamCapability;
#endif  // BUILDFLAG(IS_LINUX) && defined(REMOTING_USE_X11)

  return capabilities;
}

Me2MeDesktopEnvironment::Me2MeDesktopEnvironment(
    scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
    scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
    std::unique_ptr<DesktopInteractionStrategy> interaction_strategy,
    base::WeakPtr<ClientSessionControl> client_session_control,
    const DesktopEnvironmentOptions& options)
    : BasicDesktopEnvironment(caller_task_runner,
                              ui_task_runner,
                              std::move(interaction_strategy),
                              client_session_control,
                              options) {
  DCHECK(caller_task_runner->BelongsToCurrentThread());

  // TODO(zijiehe): This logic should belong to RemotingMe2MeHost, instead of
  // Me2MeDesktopEnvironment, which does not take response to create a new
  // session.
  // X DAMAGE is not enabled by default, since it is broken on many systems -
  // see http://crbug.com/73423. It's safe to enable it here because it works
  // properly under Xvfb.
  mutable_desktop_capture_options()->set_use_update_notifications(true);

#if BUILDFLAG(IS_LINUX)
  // Setting this option to false means that the capture differ wrapper will not
  // be used when the X11 capturer is selected. This reduces the X11 capture
  // time by a few milliseconds per frame and is safe because we can rely on
  // XDAMAGE to identify the changed regions rather than checking each pixel
  // ourselves.
  mutable_desktop_capture_options()->set_detect_updated_region(false);
#endif
}

bool Me2MeDesktopEnvironment::InitializeSecurity(
    base::WeakPtr<ClientSessionControl> client_session_control) {
  DCHECK(caller_task_runner()->BelongsToCurrentThread());

  // Detach the session from the local console if the caller requested.
  if (desktop_environment_options().enable_curtaining()) {
    curtain_ = interaction_strategy().CreateCurtainMode(client_session_control);
    if (!curtain_->Activate()) {
      LOG(ERROR) << "Failed to activate the curtain mode.";
      curtain_ = nullptr;
      return false;
    }
    return true;
  }

  // Otherwise, if the session is shared with the local user start monitoring
  // the local input and create the in-session UI.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
  bool want_user_interface = false;
#elif BUILDFLAG(IS_APPLE)
  // Don't try to display any UI on top of the system's login screen as this
  // is rejected by the Window Server on OS X 10.7.4, and prevents the
  // capturer from working (http://crbug.com/140984).

  // TODO(lambroslambrou): Use a better technique of detecting whether we're
  // running in the LoginWindow context, and refactor this into a separate
  // function to be used here and in CurtainMode::ActivateCurtain().
  bool want_user_interface = getuid() != 0;
#else
  // TODO: crbug.com/499225384 - Re-enable this and extract the value from
  // desktop_environment_options().enable_user_interface() after the network
  // process has been split into low- and high-trust processes.
  bool want_user_interface = true;
#endif

  if (want_user_interface) {
    // Create the local input monitor.
    local_input_monitor_ = interaction_strategy().CreateLocalInputMonitor();
    local_input_monitor_->StartMonitoringForClientSession(
        client_session_control);

    // Create the disconnect window.
#if BUILDFLAG(IS_WIN)
    disconnect_window_ = HostWindow::CreateAutoHidingDisconnectWindow(
        interaction_strategy().CreateLocalInputMonitor());
#else
    disconnect_window_ = HostWindow::CreateDisconnectWindow();
#endif
    disconnect_window_ = std::make_unique<HostWindowProxy>(
        caller_task_runner(), ui_task_runner(), std::move(disconnect_window_));
    disconnect_window_->Start(client_session_control);
  }

  return true;
}

Me2MeDesktopEnvironmentFactory::Me2MeDesktopEnvironmentFactory(
    scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
    scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
    std::unique_ptr<DesktopInteractionStrategyFactory>
        interaction_strategy_factory)
    : BasicDesktopEnvironmentFactory(std::move(caller_task_runner),
                                     std::move(ui_task_runner),
                                     std::move(interaction_strategy_factory)) {}

Me2MeDesktopEnvironmentFactory::~Me2MeDesktopEnvironmentFactory() = default;

void Me2MeDesktopEnvironmentFactory::Create(
    base::WeakPtr<ClientSessionControl> client_session_control,
    base::WeakPtr<ClientSessionEvents> client_session_events,
    const DesktopEnvironmentOptions& options,
    CreateCallback callback) {
  DCHECK(caller_task_runner()->BelongsToCurrentThread());

  auto create_with_interaction_strategy =
      [](scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
         scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
         base::WeakPtr<ClientSessionControl> client_session_control,
         const DesktopEnvironmentOptions& options,
         std::unique_ptr<DesktopInteractionStrategy> interaction_strategy)
      -> std::unique_ptr<DesktopEnvironment> {
    if (!interaction_strategy) {
      return nullptr;
    }
    auto desktop_environment = base::WrapUnique(new Me2MeDesktopEnvironment(
        std::move(caller_task_runner), std::move(ui_task_runner),
        std::move(interaction_strategy), client_session_control, options));
    if (!desktop_environment->InitializeSecurity(client_session_control)) {
      return nullptr;
    }

    return desktop_environment;
  };

  CreateInteractionStrategy(
      options, base::BindOnce(create_with_interaction_strategy,
                              caller_task_runner(), ui_task_runner(),
                              std::move(client_session_control), options)
                   .Then(std::move(callback)));
}

}  // namespace remoting
