summaryrefslogtreecommitdiff
path: root/chromium/ui/aura/native_window_occlusion_tracker_win.h
blob: 7e47a6630d8a681d78bf7d37ef14199536debf62 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
// Copyright 2018 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.

#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 <vector>

#include "base/callback_forward.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.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;

    // 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.
    absl::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_