chromium/ui/display/manager/display_configurator.h

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_DISPLAY_MANAGER_DISPLAY_CONFIGURATOR_H_
#define UI_DISPLAY_MANAGER_DISPLAY_CONFIGURATOR_H_

#include <cstdint>
#include <memory>
#include <optional>
#include <unordered_map>
#include <vector>

#include "base/containers/flat_set.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/timer/timer.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "ui/display/manager/display_manager_export.h"
#include "ui/display/types/display_constants.h"
#include "ui/display/types/native_display_observer.h"
#include "ui/events/platform_event.h"
#include "ui/gfx/geometry/size.h"

namespace gfx {
class Size;
}  // namespace gfx

namespace display {

class ContentProtectionManager;
class DisplayLayoutManager;
class DisplayMode;
class DisplaySnapshot;
class ManagedDisplayMode;
class NativeDisplayDelegate;
class UpdateDisplayConfigurationTask;

struct ColorTemperatureAdjustment;
struct ColorCalibration;

namespace test {
class DisplayManagerTestApi;
}  // namespace test

// This class interacts directly with the system display configurator.
class DISPLAY_MANAGER_EXPORT DisplayConfigurator
    : public NativeDisplayObserver {
 public:
  using ConfigurationCallback = base::OnceCallback<void(bool /* success */)>;
  using DisplayControlCallback = base::OnceCallback<void(bool success)>;
  using GetSeamlessRefreshRatesCallback =
      base::OnceCallback<void(const std::optional<std::vector<float>>&)>;

  using DisplayStateList =
      std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>;
  // Map of display id to a refresh rate override.
  using RefreshRateOverrideMap = std::unordered_map<int64_t, float>;

  class Observer {
   public:
    virtual ~Observer() = default;

    // Called after the display configuration has been changed. |display|
    // contains the just-applied configuration. Note that the X server is no
    // longer grabbed when this method is called, so the actual configuration
    // could've changed already.
    virtual void OnDisplayConfigurationChanged(
        const DisplayStateList& displays) {}

    // Called after a display configuration change attempt failed. |displays|
    // contains displays that are detected when failed. |failed_new_state| is
    // the new state which the system failed to enter.
    virtual void OnDisplayConfigurationChangeFailed(
        const DisplayStateList& displays,
        MultipleDisplayState failed_new_state) {}

    // Called after the power state has been changed. |power_state| contains
    // the just-applied power state.
    virtual void OnPowerStateChanged(chromeos::DisplayPowerState power_state) {}

    // Called when the |cached_displays_| is cleared.
    virtual void OnDisplaySnapshotsInvalidated() {}
  };

  // Interface for classes that make decisions about which display state
  // should be used.
  class StateController {
   public:
    virtual ~StateController() {}

    // Called when displays are detected.
    virtual MultipleDisplayState GetStateForDisplayIds(
        const DisplayConfigurator::DisplayStateList& outputs) = 0;

    virtual bool GetSelectedModeForDisplayId(
        int64_t display_id,
        ManagedDisplayMode* out_mode) const = 0;
  };

  // Interface for classes that implement software based mirroring.
  class SoftwareMirroringController {
   public:
    virtual ~SoftwareMirroringController() {}

    // Called when the hardware mirroring failed.
    virtual void SetSoftwareMirroring(bool enabled) = 0;

    // Returns true when software mirroring mode is requested, but it does
    // not guarantee that the mode is active.
    virtual bool SoftwareMirroringEnabled() const = 0;

    // Returns true if hardware mirroring should not be used. (e.g. In mixed
    // mirror mode, the API caller specifies the mirroring source and
    // destination displays which do not exist in hardware mirroring.)
    virtual bool IsSoftwareMirroringEnforced() const = 0;
  };

  // Helper class used by tests.
  class TestApi {
   public:
    explicit TestApi(DisplayConfigurator* configurator)
        : configurator_(configurator) {}

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

    ~TestApi() {}

    // If |configure_timer_| is started, stops the timer, runs
    // ConfigureDisplays(), and returns true; returns false otherwise.
    [[nodiscard]] bool TriggerConfigureTimeout();

    // Gets the current delay of the |configure_timer_| if it's running, or zero
    // time delta otherwise.
    base::TimeDelta GetConfigureDelay() const;

    DisplayLayoutManager* GetDisplayLayoutManager() const;

   private:
    raw_ptr<DisplayConfigurator, DanglingUntriaged> configurator_;  // not owned
  };

  // Flags that can be passed to SetDisplayPower().
  static const int kSetDisplayPowerNoFlags;
  // Configure displays even if the passed-in state matches |power_state_|.
  static const int kSetDisplayPowerForceProbe;
  // Do not change the state if multiple displays are connected or if the
  // only connected display is external.
  static const int kSetDisplayPowerOnlyIfSingleInternalDisplay;

  // Gap between screens so cursor at bottom of active display doesn't
  // partially appear on top of inactive display. Higher numbers guard
  // against larger cursors, but also waste more memory.
  // For simplicity, this is hard-coded to avoid the complexity of always
  // determining the DPI of the screen and rationalizing which screen we
  // need to use for the DPI calculation.
  // See crbug.com/130188 for initial discussion.
  static const int kVerticalGap = 60;

  // The delay to perform configuration after RRNotify. See the comment for
  // |configure_timer_|.
  static const int kConfigureDelayMs = 1000;

  // The delay to perform configuration after waking up from suspend when in
  // multi display mode. Should be bigger than |kConfigureDelayMs|. Generally
  // big enough for external displays to be detected and added.
  // crbug.com/614624.
  static const int kResumeConfigureMultiDisplayDelayMs = 2000;

  // Returns the mode within |display| that matches the given size with highest
  // refresh rate. Returns None if no matching display was found.
  static const DisplayMode* FindDisplayModeMatchingSize(
      const DisplaySnapshot& display,
      const gfx::Size& size);

  DisplayConfigurator();

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

  ~DisplayConfigurator() override;

  MultipleDisplayState display_state() const { return current_display_state_; }
  const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>&
  cached_displays() const {
    return cached_displays_;
  }
  void set_state_controller(StateController* controller) {
    state_controller_ = controller;
  }
  void set_mirroring_controller(SoftwareMirroringController* controller) {
    mirroring_controller_ = controller;
  }
  void SetConfigureDisplays(bool configure_displays);
  bool has_unassociated_display() const { return has_unassociated_display_; }
  chromeos::DisplayPowerState current_power_state() const {
    return current_power_state_;
  }
  ContentProtectionManager* content_protection_manager() const {
    return content_protection_manager_.get();
  }

  // Called when an external process no longer needs to control the display
  // and Chrome can take control.
  void TakeControl(DisplayControlCallback callback);

  // Called when an external process needs to control the display and thus
  // Chrome should relinquish it.
  void RelinquishControl(DisplayControlCallback callback);

  // Replaces |native_display_delegate_| with the delegate passed in and sets
  // |configure_display_| to true. Should be called before Init().
  void SetDelegateForTesting(
      std::unique_ptr<NativeDisplayDelegate> display_delegate);

  // Called asynchronously with the initial |power_state| loaded from prefs.
  // This may be called after ForceInitialConfigure triggers a call to
  // OnConfigured(), in which case UpdatePowerState() will be called with the
  // correct initial value. Does nothing if |requested_power_state_| is set,
  // e.g. via SetDisplayPower().
  void SetInitialDisplayPower(chromeos::DisplayPowerState power_state);

  // Initialize the display power state to DISPLAY_POWER_ALL_ON
  void InitializeDisplayPowerState();

  // Initialization, must be called right after constructor.
  // |is_panel_fitting_enabled| indicates hardware panel fitting support.
  void Init(std::unique_ptr<NativeDisplayDelegate> delegate,
            bool is_panel_fitting_enabled);

  // Does initial configuration of displays during startup.
  void ForceInitialConfigure();

  // Stop handling display configuration events/requests.
  void PrepareForExit();

  // Called when powerd notifies us that some set of displays should be turned
  // on or off.  This requires enabling or disabling the CRTC associated with
  // the display(s) in question so that the low power state is engaged.
  // |flags| contains bitwise-or-ed kSetDisplayPower* values. After the
  // configuration finishes |callback| is called with the status of the
  // operation.
  void SetDisplayPower(chromeos::DisplayPowerState power_state,
                       int flags,
                       ConfigurationCallback callback);

  // Force switching the display state to |new_state|. Returns false if
  // switching failed (possibly because |new_state| is invalid for the
  // current set of connected displays).
  void SetMultipleDisplayState(MultipleDisplayState new_state);

  // Request a description of the refresh rates to which the display can support
  // a configuration without a full modeset.
  // The supported refresh rates depend on the current configuration of the
  // display driver and hardware.
  //
  // It's possible that there could be some configuration change such that a
  // seamless modeset to a refresh rate returned from here succeeds at one time,
  // and fails at another due to some configuration change in the display
  // driver. The caller should re-query the supported refresh rates whenever
  // there is a full modeset, or when a seamless refresh rate change fails, to
  // ensure that the caller has an up-to-date picture of which refresh rates are
  // supported.
  //
  // A result of nullopt indicates that the request failed for some reason such
  // as an invalid display_id. An empty vector indicates that there
  // are no modes to which the display can be configured seamlessly. This could
  // happen if the display is currently turned off.
  void GetSeamlessRefreshRates(int64_t display_id,
                               GetSeamlessRefreshRatesCallback callback);

  // NativeDisplayObserver:
  void OnConfigurationChanged() override;
  void OnDisplaySnapshotsInvalidated() override;

  void AddObserver(Observer* observer);
  void RemoveObserver(Observer* observer);
  bool HasObserverForTesting(Observer* observer) const;

  // Sets all the displays into pre-suspend mode; usually this means
  // configure them for their resume state. This allows faster resume on
  // machines where display configuration is slow. On completion of the display
  // configuration |callback| is executed synchronously or asynchronously.
  void SuspendDisplays(ConfigurationCallback callback);

  // Reprobes displays to handle changes made while the system was
  // suspended.
  void ResumeDisplays();

  // Returns true if there is at least one display on.
  bool IsDisplayOn() const;

  // Sets the color temperature adjustment for the specified display.
  void SetColorTemperatureAdjustment(int64_t display_id,
                                     const ColorTemperatureAdjustment& cta);

  // Sets the color calibration for the specified display;
  void SetColorCalibration(int64_t display_id,
                           const ColorCalibration& calibration);

  // Enable/disable the privacy screen on display with |display_id|.
  // For this to succeed, privacy screen must be supported by the display.
  // After privacy screen is set, |callback| is called with the outcome
  // (success/failure) of the operation.
  void SetPrivacyScreen(int64_t display_id,
                        bool enabled,
                        ConfigurationCallback callback);

  // Returns the requested power state if set or the default power state.
  chromeos::DisplayPowerState GetRequestedPowerState() const;

  void reset_requested_power_state_for_test() {
    requested_power_state_ = std::nullopt;
  }

  std::optional<chromeos::DisplayPowerState> GetRequestedPowerStateForTest()
      const {
    return requested_power_state_;
  }

  // Requests to enable variable refresh rates on the specified displays and to
  // disable variable refresh rates on all other displays, and schedules a
  // seamless configuration change as needed.
  void SetVrrEnabled(const base::flat_set<int64_t>& display_ids);

  // Requests to override the refresh rate of the specified displays and
  // schedule a seamless configuration change if needed. If a display is not in
  // |overrides| then then the display may be configured back to its native
  // refresh rate, if the configuration can happen without a modeset. If the
  // affected displays are already configured according to |overrides|, then no
  // configuration will occur.
  void SetRefreshRateOverrides(const RefreshRateOverrideMap& overrides);

 private:
  friend class test::DisplayManagerTestApi;

  class DisplayLayoutManagerImpl;

  bool configurator_disabled() const {
    return !configure_displays_ || display_externally_controlled_;
  }

  // Updates |pending_*| members and applies the passed-in state. |callback| is
  // invoked (perhaps synchronously) on completion.
  void SetDisplayPowerInternal(chromeos::DisplayPowerState power_state,
                               int flags,
                               ConfigurationCallback callback);

  // Configures displays. Invoked by |configure_timer_|.
  void ConfigureDisplays();

  // Notifies observers about an attempted state change.
  void NotifyDisplayStateObservers(bool success,
                                   MultipleDisplayState attempted_state);

  // Notifies observers about a power state change.
  void NotifyPowerStateObservers();

  // Returns the display state that should be used with |cached_displays_| while
  // in |power_state|.
  MultipleDisplayState ChooseDisplayState(
      chromeos::DisplayPowerState power_state) const;

  // If |configuration_task_| isn't initialized, initializes it and starts the
  // configuration task.
  void RunPendingConfiguration();

  // Callback for |configuration_task_|. When the configuration process finishes
  // this is called with the result (|success|) and the updated display state.
  void OnConfigured(
      bool success,
      const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>& displays,
      const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>&
          unassociated_displays,
      MultipleDisplayState new_display_state,
      chromeos::DisplayPowerState new_power_state);

  // Updates the current and pending power state and notifies observers.
  void UpdatePowerState(chromeos::DisplayPowerState new_power_state);

  // Updates the cached internal display. nullptr if one does not exists.
  void UpdateInternalDisplayCache();

  // Helps in identifying if a configuration task needs to be scheduled.
  // Return true if any of the |requested_*| parameters have been updated. False
  // otherwise.
  bool ShouldRunConfigurationTask() const;

  // Returns true if there are pending configuration changes that should be done
  // seamlessly.
  bool HasPendingSeamlessConfiguration() const;

  // Returns true if there are pending configuration changes that require a full
  // modeset.
  bool HasPendingFullConfiguration() const;

  // Helper functions which will call the callbacks in
  // |in_progress_configuration_callbacks_| and
  // |queued_configuration_callbacks_| and clear the lists after. |success| is
  // the configuration status used when calling the callbacks.
  void CallAndClearInProgressCallbacks(bool success);
  void CallAndClearQueuedCallbacks(bool success);

  // Callbacks used to signal when the native platform has released/taken
  // display control.
  void OnDisplayControlTaken(DisplayControlCallback callback, bool success);
  void OnDisplayControlRelinquished(DisplayControlCallback callback,
                                    bool success);

  // Helper function that sends the actual command.
  // |callback| is called upon completion of the relinquish command.
  // |success| is the result from calling SetDisplayPowerInternal() in
  // RelinquishDisplay().
  void SendRelinquishDisplayControl(DisplayControlCallback callback,
                                    bool success);

  // Returns the requested VRR state listing the display ids which should have
  // VRR enabled, defaulting to the current state as needed.
  const base::flat_set<int64_t> GetRequestedVrrState() const;

  // Returns whether a configuration should occur on account of a pending VRR
  // request.
  bool ShouldConfigureVrr() const;

  // Returns the per-display refresh rate overrides which should be used for
  // a configuration attempt. If there is a full configuration pending,
  // there will be no overrides set. If no new overrides have been requested,
  // this will return the current state.
  RefreshRateOverrideMap GetRequestedRefreshRateOverrides() const;

  // Returns the current state of refresh rate overrides. This is determined
  // by comparing the refresh rates of the currently configured mode and the
  // display's native mode.
  RefreshRateOverrideMap GetCurrentRefreshRateOverrideState() const;

  // Dangling in DemoIntegrationTest.NewTab on chromeos-amd64-generic-rel-gtest.
  raw_ptr<StateController, DanglingUntriaged> state_controller_;
  // Dangling in DemoIntegrationTest.NewTab on chromeos-amd64-generic-rel-gtest.
  raw_ptr<SoftwareMirroringController, DanglingUntriaged> mirroring_controller_;
  std::unique_ptr<NativeDisplayDelegate> native_display_delegate_;

  // Used to enable modes which rely on panel fitting.
  bool is_panel_fitting_enabled_;

  // This is detected by the constructor to determine whether or not we should
  // be enabled. If this flag is set to false, any attempts to change the
  // display configuration will immediately fail without changing the state.
  bool configure_displays_;

  // Current configuration state.
  MultipleDisplayState current_display_state_;
  chromeos::DisplayPowerState current_power_state_;

  // Pending requests. These values are used when triggering the next display
  // configuration.
  //
  // Stores the user requested state or INVALID if nothing was requested.
  MultipleDisplayState requested_display_state_;

  // Stores the requested power state.
  std::optional<chromeos::DisplayPowerState> requested_power_state_;

  // The power state used by RunPendingConfiguration(). May be
  // |requested_power_state_| or DISPLAY_POWER_ALL_OFF for suspend.
  chromeos::DisplayPowerState pending_power_state_;

  // True if |pending_power_state_| has been changed.
  bool has_pending_power_state_;

  // Bitwise-or value of the |kSetDisplayPower*| flags defined above.
  int pending_power_flags_;

  // Per-display pending refresh rate override requests. Displays not included
  // in this map will have their refresh rates set to their native refresh
  // rates.
  std::optional<RefreshRateOverrideMap> pending_refresh_rate_overrides_;

  // List of callbacks from callers waiting for the display configuration to
  // start/finish. Note these callbacks belong to the pending request, not a
  // request currently active.
  std::vector<ConfigurationCallback> queued_configuration_callbacks_;

  // List of callbacks belonging to the currently running display configuration
  // task.
  std::vector<ConfigurationCallback> in_progress_configuration_callbacks_;

  // True if the caller wants to force the display configuration process.
  bool force_configure_;

  // Most-recently-used display configuration. Note that the actual
  // configuration changes asynchronously.
  DisplayStateList cached_displays_;

  base::ObserverList<Observer>::Unchecked observers_;

  // The timer to delay configuring displays. This is used to aggregate multiple
  // display configuration events when they are reported in short time spans.
  base::OneShotTimer configure_timer_;

  // Display controlled by an external entity.
  bool display_externally_controlled_;

  // True if a TakeControl or RelinquishControl has been called but the response
  // hasn't arrived yet.
  bool display_control_changing_;

  // Whether the displays are currently suspended.
  bool displays_suspended_;

  std::unique_ptr<DisplayLayoutManagerImpl> layout_manager_;
  std::unique_ptr<ContentProtectionManager> content_protection_manager_;

  std::unique_ptr<UpdateDisplayConfigurationTask> configuration_task_;

  // Indicates whether there is any connected display having no associated crtc.
  // This can be caused by crtc shortage. When it is true, the corresponding
  // notification will be created to inform user.
  bool has_unassociated_display_;

  // Stores the requested variable refresh rate state as a set of display ids
  // for which VRR should be enabled. All omitted displays should have VRR
  // disabled. Absent if there is no pending state.
  std::optional<base::flat_set<int64_t>> pending_vrr_state_ = std::nullopt;

  // This must be the last variable.
  base::WeakPtrFactory<DisplayConfigurator> weak_ptr_factory_{this};
};

}  // namespace display

#endif  // UI_DISPLAY_MANAGER_DISPLAY_CONFIGURATOR_H_