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
|
// Copyright 2017 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.
#include "chrome/browser/ui/views/chrome_views_delegate.h"
#include <dwmapi.h>
#include <shellapi.h>
#include "base/task/post_task.h"
#include "base/win/windows_version.h"
#include "chrome/browser/ui/views/native_widget_factory.h"
#include "chrome/browser/win/app_icon.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#include "ui/base/win/shell.h"
namespace {
bool MonitorHasAutohideTaskbarForEdge(UINT edge, HMONITOR monitor) {
APPBARDATA taskbar_data = {sizeof(APPBARDATA), NULL, 0, edge};
taskbar_data.hWnd = ::GetForegroundWindow();
// MSDN documents an ABM_GETAUTOHIDEBAREX, which supposedly takes a monitor
// rect and returns autohide bars on that monitor. This sounds like a good
// idea for multi-monitor systems. Unfortunately, it appears to not work at
// least some of the time (erroneously returning NULL) and there's almost no
// online documentation or other sample code using it that suggests ways to
// address this problem. We do the following:-
// 1. Use the ABM_GETAUTOHIDEBAR message. If it works, i.e. returns a valid
// window we are done.
// 2. If the ABM_GETAUTOHIDEBAR message does not work we query the auto hide
// state of the taskbar and then retrieve its position. That call returns
// the edge on which the taskbar is present. If it matches the edge we
// are looking for, we are done.
// NOTE: This call spins a nested run loop.
HWND taskbar = reinterpret_cast<HWND>(
SHAppBarMessage(ABM_GETAUTOHIDEBAR, &taskbar_data));
if (!::IsWindow(taskbar)) {
APPBARDATA taskbar_data = {sizeof(APPBARDATA), 0, 0, 0};
unsigned int taskbar_state = SHAppBarMessage(ABM_GETSTATE, &taskbar_data);
if (!(taskbar_state & ABS_AUTOHIDE))
return false;
taskbar_data.hWnd = ::FindWindow(L"Shell_TrayWnd", NULL);
if (!::IsWindow(taskbar_data.hWnd))
return false;
SHAppBarMessage(ABM_GETTASKBARPOS, &taskbar_data);
if (taskbar_data.uEdge == edge)
taskbar = taskbar_data.hWnd;
}
// There is a potential race condition here:
// 1. A maximized chrome window is fullscreened.
// 2. It is switched back to maximized.
// 3. In the process the window gets a WM_NCCACLSIZE message which calls us to
// get the autohide state.
// 4. The worker thread is invoked. It calls the API to get the autohide
// state. On Windows versions earlier than Windows 7, taskbars could
// easily be always on top or not.
// This meant that we only want to look for taskbars which have the topmost
// bit set. However this causes problems in cases where the window on the
// main thread is still in the process of switching away from fullscreen.
// In this case the taskbar might not yet have the topmost bit set.
// 5. The main thread resumes and does not leave space for the taskbar and
// hence it does not pop when hovered.
//
// To address point 4 above, it is best to not check for the WS_EX_TOPMOST
// window style on the taskbar, as starting from Windows 7, the topmost
// style is always set. We don't support XP and Vista anymore.
if (::IsWindow(taskbar)) {
if (MonitorFromWindow(taskbar, MONITOR_DEFAULTTONEAREST) == monitor)
return true;
// In some cases like when the autohide taskbar is on the left of the
// secondary monitor, the MonitorFromWindow call above fails to return the
// correct monitor the taskbar is on. We fallback to MonitorFromPoint for
// the cursor position in that case, which seems to work well.
POINT cursor_pos = {0};
GetCursorPos(&cursor_pos);
if (MonitorFromPoint(cursor_pos, MONITOR_DEFAULTTONEAREST) == monitor)
return true;
}
return false;
}
int GetAppbarAutohideEdgesOnWorkerThread(HMONITOR monitor) {
DCHECK(monitor);
int edges = 0;
if (MonitorHasAutohideTaskbarForEdge(ABE_LEFT, monitor))
edges |= views::ViewsDelegate::EDGE_LEFT;
if (MonitorHasAutohideTaskbarForEdge(ABE_TOP, monitor))
edges |= views::ViewsDelegate::EDGE_TOP;
if (MonitorHasAutohideTaskbarForEdge(ABE_RIGHT, monitor))
edges |= views::ViewsDelegate::EDGE_RIGHT;
if (MonitorHasAutohideTaskbarForEdge(ABE_BOTTOM, monitor))
edges |= views::ViewsDelegate::EDGE_BOTTOM;
return edges;
}
} // namespace
HICON ChromeViewsDelegate::GetDefaultWindowIcon() const {
return GetAppIcon();
}
HICON ChromeViewsDelegate::GetSmallWindowIcon() const {
return GetSmallAppIcon();
}
views::NativeWidget* ChromeViewsDelegate::CreateNativeWidget(
views::Widget::InitParams* params,
views::internal::NativeWidgetDelegate* delegate) {
// Check the force_software_compositing flag only on Windows. If this flag is
// on, it means that the widget being created wants to use the software
// compositor which requires a top level window. We cannot have a mixture of
// compositors active in one view hierarchy.
NativeWidgetType native_widget_type =
(params->parent && params->child && !params->force_software_compositing &&
params->type != views::Widget::InitParams::TYPE_TOOLTIP)
? NativeWidgetType::NATIVE_WIDGET_AURA
: NativeWidgetType::DESKTOP_NATIVE_WIDGET_AURA;
if (params->shadow_type == views::Widget::InitParams::SHADOW_TYPE_DROP &&
params->shadow_elevation.has_value()) {
// If the window defines an elevation based shadow in the Widget
// initialization parameters, force the use of a non toplevel window,
// as the native window manager has no concept of elevation based shadows.
// TODO: This may no longer be needed if we get proper elevation-based
// shadows on toplevel windows. See https://crbug.com/838667.
native_widget_type = NativeWidgetType::NATIVE_WIDGET_AURA;
} else if (!ui::win::IsAeroGlassEnabled()) {
// If we don't have composition (either because Glass is not enabled or
// because it was disabled at the command line), anything that requires
// transparency will be broken with a toplevel window, so force the use of
// a non toplevel window.
if (params->opacity == views::Widget::InitParams::TRANSLUCENT_WINDOW &&
!params->force_software_compositing)
native_widget_type = NativeWidgetType::NATIVE_WIDGET_AURA;
} else {
// If we're on Vista+ with composition enabled, then we can use toplevel
// windows for most things (they get blended via WS_EX_COMPOSITED, which
// allows for animation effects, but also exceeding the bounds of the parent
// window).
if (params->parent &&
params->type != views::Widget::InitParams::TYPE_CONTROL &&
params->type != views::Widget::InitParams::TYPE_WINDOW) {
native_widget_type = NativeWidgetType::DESKTOP_NATIVE_WIDGET_AURA;
}
}
return ::CreateNativeWidget(native_widget_type, params, delegate);
}
int ChromeViewsDelegate::GetAppbarAutohideEdges(HMONITOR monitor,
const base::Closure& callback) {
// Initialize the map with EDGE_BOTTOM. This is important, as if we return an
// initial value of 0 (no auto-hide edges) then we'll go fullscreen and
// windows will automatically remove WS_EX_TOPMOST from the appbar resulting
// in us thinking there is no auto-hide edges. By returning at least one edge
// we don't initially go fullscreen until we figure out the real auto-hide
// edges.
if (!appbar_autohide_edge_map_.count(monitor))
appbar_autohide_edge_map_[monitor] = EDGE_BOTTOM;
// We use the SHAppBarMessage API to get the taskbar autohide state. This API
// spins a modal loop which could cause callers to be reentered. To avoid
// that we retrieve the taskbar state in a worker thread.
if (monitor && !in_autohide_edges_callback_) {
// TODO(robliao): Annotate this task with .WithCOM() once supported.
// https://crbug.com/662122
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
base::Bind(&GetAppbarAutohideEdgesOnWorkerThread, monitor),
base::Bind(&ChromeViewsDelegate::OnGotAppbarAutohideEdges,
weak_factory_.GetWeakPtr(), callback, monitor,
appbar_autohide_edge_map_[monitor]));
}
return appbar_autohide_edge_map_[monitor];
}
void ChromeViewsDelegate::OnGotAppbarAutohideEdges(
const base::Closure& callback,
HMONITOR monitor,
int returned_edges,
int edges) {
appbar_autohide_edge_map_[monitor] = edges;
if (returned_edges == edges)
return;
base::AutoReset<bool> in_callback_setter(&in_autohide_edges_callback_, true);
callback.Run();
}
|