| // 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_ |