chromium/ui/aura/native_window_occlusion_tracker_win.h

// Copyright 2018 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_AURA_NATIVE_WINDOW_OCCLUSION_TRACKER_WIN_H_
#define UI_AURA_NATIVE_WINDOW_OCCLUSION_TRACKER_WIN_H_

#include <shobjidl.h>
#include <windows.h>

#include <winuser.h>
#include <wrl/client.h>

#include <memory>
#include <optional>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/functional/callback_forward.h"
#include "base/gtest_prod_util.h"
#include "base/memory/weak_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/timer/timer.h"
#include "third_party/skia/include/core/SkRegion.h"
#include "ui/aura/aura_export.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/base/win/power_setting_change_listener.h"
#include "ui/base/win/session_change_observer.h"

namespace base {
class WaitableEvent;
}

namespace gfx {
class Rect;
}

namespace aura {

// This class keeps track of whether any HWNDs are occluding any app windows.
// It notifies the host of any app window whose occlusion state changes. Most
// code should not need to use this; it's an implementation detail.
class AURA_EXPORT NativeWindowOcclusionTrackerWin
    : public WindowObserver,
      public ui::PowerSettingChangeListener {
 public:
  static NativeWindowOcclusionTrackerWin* GetOrCreateInstance();

  static void DeleteInstanceForTesting();

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

  // Enables notifying the host of |window| via SetNativeWindowOcclusionState()
  // when the occlusion state has been computed.
  void Enable(Window* window);

  // Disables notifying the host of |window| via
  // OnNativeWindowOcclusionStateChanged() when the occlusion state has been
  // computed. It's not neccesary to call this when |window| is deleted because
  // OnWindowDestroying calls Disable.
  void Disable(Window* window);

  // aura::WindowObserver:
  void OnWindowVisibilityChanged(Window* window, bool visible) override;
  void OnWindowDestroying(Window* window) override;

 private:
  friend class NativeWindowOcclusionTrackerTest;
  FRIEND_TEST_ALL_PREFIXES(NativeWindowOcclusionTrackerTest,
                           DisplayOnOffHandling);

  // Tracks the occlusion state of HWNDs registered via Enable().
  struct RootOcclusionState {
    Window::OcclusionState occlusion_state = Window::OcclusionState::UNKNOWN;
    std::optional<bool> on_current_workspace;
    // If `occlusion_state` is VISIBLE, this gives the occluded region. It may
    // be empty (which indicates the the window is entirely visible). This is
    // relative to the origin of the HWND. In other words, it's in window
    // coordinates (not client coordinates).
    SkRegion occluded_region_pixels;
  };

  using HwndToRootOcclusionStateMap = base::flat_map<HWND, RootOcclusionState>;

  // This class computes the occlusion state of the tracked windows.
  // It runs on a separate thread, and notifies the main thread of
  // the occlusion state of the tracked windows.
  class WindowOcclusionCalculator {
   public:
    using UpdateOcclusionStateCallback =
        base::RepeatingCallback<void(const HwndToRootOcclusionStateMap&,
                                     bool show_all_windows)>;

    // Creates WindowOcclusionCalculator instance. Must be called on UI thread.
    static void CreateInstance(
        scoped_refptr<base::SequencedTaskRunner> task_runner,
        scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner,
        UpdateOcclusionStateCallback update_occlusion_state_callback);

    // Returns existing WindowOcclusionCalculator instance.
    static WindowOcclusionCalculator* GetInstance() { return instance_; }

    // Deletes |instance_| and signals |done_event|. Must be called on COMSTA
    // thread.
    static void DeleteInstanceForTesting(base::WaitableEvent* done_event);

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

    void EnableOcclusionTrackingForWindow(HWND hwnd);
    void DisableOcclusionTrackingForWindow(HWND hwnd);

    // Forces a recalculation of occlusion
    void ForceRecalculation();

    // If a window becomes visible, makes sure event hooks are registered.
    void HandleVisibilityChanged(bool visible);

    // Special handling for when the device is going to sleep or waking up.
    void HandleResumeSuspend();

   private:
    WindowOcclusionCalculator(
        scoped_refptr<base::SequencedTaskRunner> task_runner,
        scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner,
        UpdateOcclusionStateCallback update_occlusion_state_callback);
    ~WindowOcclusionCalculator();

    // Registers event hooks, if not registered.
    void MaybeRegisterEventHooks();

    // This is the callback registered to get notified of various Windows
    // events, like window moving/resizing.
    static void CALLBACK EventHookCallback(HWINEVENTHOOK hWinEventHook,
                                           DWORD event,
                                           HWND hwnd,
                                           LONG id_object,
                                           LONG id_child,
                                           DWORD dwEventThread,
                                           DWORD dwmsEventTime);

    // EnumWindows callback used to iterate over all hwnds to determine
    // occlusion status of all tracked root windows.  Also builds up
    // |current_pids_with_visible_windows_| and registers event hooks for newly
    // discovered processes with visible hwnds.
    static BOOL CALLBACK
    ComputeNativeWindowOcclusionStatusCallback(HWND hwnd, LPARAM lParam);

    // EnumWindows callback used to update the list of process ids with
    // visible hwnds, |pids_for_location_change_hook_|.
    static BOOL CALLBACK UpdateVisibleWindowProcessIdsCallback(HWND hwnd,
                                                               LPARAM lParam);

    // Determines which processes owning visible application windows to set the
    // EVENT_OBJECT_LOCATIONCHANGE event hook for and stores the pids in
    // |pids_for_location_change_hook_|.
    void UpdateVisibleWindowProcessIds();

    // Computes the native window occlusion status for all tracked root aura
    // windows in |root_window_hwnds_occlusion_state_| and notifies them if
    // their occlusion status has changed.
    void ComputeNativeWindowOcclusionStatus();

    // Schedules an occlusion calculation |update_occlusion_delay_| time in the
    // future, if one isn't already scheduled.
    void ScheduleOcclusionCalculationIfNeeded();

    // Registers a global event hook (not per process) for the events in the
    // range from |event_min| to |event_max|, inclusive.
    void RegisterGlobalEventHook(UINT event_min, UINT event_max);

    // Registers the EVENT_OBJECT_LOCATIONCHANGE event hook for the process with
    // passed id. The process has one or more visible, opaque windows.
    void RegisterEventHookForProcess(DWORD pid);

    // Registers/Unregisters the event hooks necessary for occlusion tracking
    // via calls to RegisterEventHook. These event hooks are disabled when all
    // tracked windows are minimized.
    void RegisterEventHooks();
    void UnregisterEventHooks();

    // EnumWindows callback for occlusion calculation. Returns true to
    // continue enumeration, false otherwise. Currently, always returns
    // true because this function also updates
    // |current_pids_with_visible_windows|, and needs to see all HWNDs.
    bool ProcessComputeNativeWindowOcclusionStatusCallback(
        HWND hwnd,
        base::flat_set<DWORD>* current_pids_with_visible_windows);

    // Processes events sent to OcclusionEventHookCallback.
    // It generally triggers scheduling of the occlusion calculation, but
    // ignores certain events in order to not calculate occlusion more than
    // necessary.
    void ProcessEventHookCallback(DWORD event,
                                  HWND hwnd,
                                  LONG idObject,
                                  LONG idChild);

    // EnumWindows callback for determining which processes to set the
    // EVENT_OBJECT_LOCATIONCHANGE event hook for. We set that event hook for
    // processes hosting fully visible, opaque windows.
    void ProcessUpdateVisibleWindowProcessIdsCallback(HWND hwnd);

    // Returns true if the window is visible, fully opaque, and on the current
    // virtual desktop, false otherwise.
    bool WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(
        HWND hwnd,
        gfx::Rect* window_rect);

    // Returns true if |hwnd| is definitely on the current virtual desktop,
    // false if it's definitely not on the current virtual desktop, and nullopt
    // if we we can't tell for sure.
    std::optional<bool> IsWindowOnCurrentVirtualDesktop(HWND hwnd);

    static WindowOcclusionCalculator* instance_;

    // Task runner for our thread.
    scoped_refptr<base::SequencedTaskRunner> task_runner_;

    // Task runner for the thread that created |this|.  UpdateOcclusionState
    // task is posted to this task runner.
    const scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner_;

    // True if the occluded region should be tracked. This caches the value of
    // the feature `kApplyNativeOccludedRegionToWindowTracker`.
    const bool calculate_occluded_region_;

    // Callback used to update occlusion state on UI thread.
    UpdateOcclusionStateCallback update_occlusion_state_callback_;

    // Map of root app window hwnds and their occlusion state. This contains
    // both visible and hidden windows.
    HwndToRootOcclusionStateMap root_window_hwnds_occlusion_state_;

    // Values returned by SetWinEventHook are stored so that hooks can be
    // unregistered when necessary.
    std::vector<HWINEVENTHOOK> global_event_hooks_;

    // Map from process id to EVENT_OBJECT_LOCATIONCHANGE event hook.
    base::flat_map<DWORD, HWINEVENTHOOK> process_event_hooks_;

    // Pids of processes for which the EVENT_OBJECT_LOCATIONCHANGE event hook is
    // set. These are the processes hosting windows in
    // |visible_and_fully_opaque_windows_|.
    base::flat_set<DWORD> pids_for_location_change_hook_;

    // Timer to delay occlusion update.
    base::OneShotTimer occlusion_update_timer_;

    // Used to keep track of whether we're in the middle of getting window move
    // events, in order to wait until the window move is complete before
    // calculating window occlusion.
    bool window_is_moving_ = false;

    // Used to determine if a root window is occluded. As we iterate through the
    // hwnds in z-order, we subtract each opaque window's rect from
    // |unoccluded_desktop_region_|. When we get to a root window, we subtract
    // it from |unoccluded_desktop_region_|, and if |unoccluded_desktop_region_|
    // doesn't change, the root window was already occluded.
    SkRegion unoccluded_desktop_region_;

    // Keeps track of how many root windows we need to compute the occlusion
    // state of in a call to ComputeNativeWindowOcclusionStatus. Once we've
    // determined the state of all root windows, we can stop subtracting
    // windows from |unoccluded_desktop_region_|.
    int num_root_windows_with_unknown_occlusion_state_;

    // This is true if the task bar thumbnails or the alt tab thumbnails are
    // showing.
    bool showing_thumbnails_ = false;

    // Used to keep track of the window that's currently moving. That window
    // is ignored for calculation occlusion so that tab dragging won't
    // ignore windows occluded by the dragged window.
    HWND moving_window_ = 0;

    // Only used on Win10+.
    Microsoft::WRL::ComPtr<IVirtualDesktopManager> virtual_desktop_manager_;

    SEQUENCE_CHECKER(sequence_checker_);

    base::WeakPtrFactory<WindowOcclusionCalculator> weak_factory_{this};
  };

  NativeWindowOcclusionTrackerWin();
  ~NativeWindowOcclusionTrackerWin() override;

  // Returns true if we are interested in |hwnd| for purposes of occlusion
  // calculation. We are interested in |hwnd| if it is a window that is
  // visible, opaque, bounded, and not a popup or floating window. If we are
  // interested in |hwnd|, stores the window rectangle in |window_rect|.
  static bool IsWindowVisibleAndFullyOpaque(HWND hwnd, gfx::Rect* window_rect);

  // Updates root windows occclusion state. If |show_all_windows| is true,
  // all non-hidden windows will be marked visible.  This is used to force
  // rendering of thumbnails.
  void UpdateOcclusionState(
      const HwndToRootOcclusionStateMap& root_window_hwnds_occlusion_state,
      bool show_all_windows);

  // This is called with session changed notifications. If the screen is locked
  // by the current session, it marks app windows as occluded.
  void OnSessionChange(WPARAM status_code, const bool* is_current_session);

  // This is called when the display is put to sleep. If the display is sleeping
  // it marks app windows as occluded.
  void OnDisplayStateChanged(bool display_on) override;

  // Called when the device resumes from sleep.
  void OnResume() override;

  // Called before the device goes to sleep.
  void OnSuspend() override;

  // Marks all root windows as either occluded, or if hwnd IsIconic, hidden.
  void MarkNonIconicWindowsOccluded();

  // Task runner to call ComputeNativeWindowOcclusionStatus, and to handle
  // Windows event notifications, off of the UI thread.
  const scoped_refptr<base::SequencedTaskRunner> update_occlusion_task_runner_;

  // Map of HWND to root app windows. Maintained on the UI thread, and used
  // to send occlusion state notifications to Windows from
  // |root_window_hwnds_occlusion_state_|.
  base::flat_map<HWND, Window*> hwnd_root_window_map_;

  // This is set by UpdateOcclusionState. It is currently only used by tests.
  int num_visible_root_windows_ = 0;

  // Manages observation of Windows Session Change messages.
  ui::SessionChangeObserver session_change_observer_;

  // Listens for Power Setting Change messages.
  ui::ScopedPowerSettingChangeListener power_setting_change_listener_;

  // If the screen is locked, windows are considered occluded.
  bool screen_locked_ = false;

  // If the display is off, windows are considered occluded.
  bool display_on_ = true;

  base::WeakPtrFactory<NativeWindowOcclusionTrackerWin> weak_factory_{this};
};

}  // namespace aura

#endif  // UI_AURA_NATIVE_WINDOW_OCCLUSION_TRACKER_WIN_H_