Skip to content

Commit

Permalink
fix: leave space for autohide taskbar for frameless maximized window (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
trop[bot] authored and zcbenz committed May 21, 2019
1 parent 4468b86 commit da76c4d
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 13 deletions.
15 changes: 2 additions & 13 deletions atom/browser/native_window_views.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
#include "atom/browser/native_window_views.h"

#if defined(OS_WIN)
#include <objbase.h>
#include <wrl/client.h>
#endif

Expand Down Expand Up @@ -62,7 +61,6 @@
#include "atom/browser/ui/win/atom_desktop_window_tree_host_win.h"
#include "skia/ext/skia_utils_win.h"
#include "ui/base/win/shell.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/display/win/screen_win.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
Expand Down Expand Up @@ -473,24 +471,15 @@ void NativeWindowViews::SetEnabledInternal(bool enable) {
#endif
}

#if defined(USE_X11)
void NativeWindowViews::Maximize() {
#if defined(OS_WIN)
// For window without WS_THICKFRAME style, we can not call Maximize().
if (!(::GetWindowLong(GetAcceleratedWidget(), GWL_STYLE) & WS_THICKFRAME)) {
restore_bounds_ = GetBounds();
auto display =
display::Screen::GetScreen()->GetDisplayNearestPoint(GetPosition());
SetBounds(display.work_area(), false);
return;
}
#endif

if (IsVisible())
widget()->Maximize();
else
widget()->native_widget_private()->Show(ui::SHOW_STATE_MAXIMIZED,
gfx::Rect());
}
#endif

void NativeWindowViews::Unmaximize() {
#if defined(OS_WIN)
Expand Down
149 changes: 149 additions & 0 deletions atom/browser/native_window_views_win.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

#include <dwmapi.h>
#include <shellapi.h>

#include "atom/browser/browser.h"
#include "atom/browser/native_window_views.h"
#include "atom/browser/ui/views/root_view.h"
#include "atom/common/atom_constants.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "ui/base/win/accessibility_misc_utils.h"
#include "ui/display/display.h"
#include "ui/display/win/screen_win.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/widget/native_widget_private.h"

// Must be included after other Windows headers.
#include <UIAutomationCoreApi.h>
Expand Down Expand Up @@ -138,11 +143,155 @@ bool IsScreenReaderActive() {
return screenReader && UiaClientsAreListening();
}

// We use "enum" instead of "enum class" because we need to do bitwise compare.
enum AppbarAutohideEdge {
TOP = 1 << 0,
LEFT = 1 << 1,
BOTTOM = 1 << 2,
RIGHT = 1 << 3,
};

// The thickness of an auto-hide taskbar in pixel.
constexpr int kAutoHideTaskbarThicknessPx = 2;

// Code is copied from chrome_views_delegate_win.cc.
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 GetAppbarAutohideEdges(HWND hwnd) {
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
if (!monitor)
return 0;

int edges = 0;
if (MonitorHasAutohideTaskbarForEdge(ABE_LEFT, monitor))
edges |= AppbarAutohideEdge::LEFT;
if (MonitorHasAutohideTaskbarForEdge(ABE_TOP, monitor))
edges |= AppbarAutohideEdge::TOP;
if (MonitorHasAutohideTaskbarForEdge(ABE_RIGHT, monitor))
edges |= AppbarAutohideEdge::RIGHT;
if (MonitorHasAutohideTaskbarForEdge(ABE_BOTTOM, monitor))
edges |= AppbarAutohideEdge::BOTTOM;
return edges;
}

} // namespace

std::set<NativeWindowViews*> NativeWindowViews::forwarding_windows_;
HHOOK NativeWindowViews::mouse_hook_ = NULL;

void NativeWindowViews::Maximize() {
int autohide_edges = 0;
if (!has_frame())
autohide_edges = GetAppbarAutohideEdges(GetAcceleratedWidget());

// Only use Maximize() when:
// 1. window has WS_THICKFRAME style;
// 2. and window is not frameless when there is autohide taskbar.
if ((::GetWindowLong(GetAcceleratedWidget(), GWL_STYLE) & WS_THICKFRAME) &&
(has_frame() || autohide_edges == 0)) {
if (IsVisible())
widget()->Maximize();
else
widget()->native_widget_private()->Show(ui::SHOW_STATE_MAXIMIZED,
gfx::Rect());
return;
}

gfx::Insets insets;
if (!has_frame()) {
// When taskbar is autohide, we need to leave some space so the window
// isn't treated as a "fullscreen app", which would cause the taskbars
// to disappear.
//
// This trick comes from hwnd_message_handler.cc. While Chromium already
// does this for normal window, somehow it is not applying the trick when
// using frameless window, and we have to do it ourselves.
float scale_factor =
display::win::ScreenWin::GetScaleFactorForHWND(GetAcceleratedWidget());
int thickness = std::ceil(kAutoHideTaskbarThicknessPx / scale_factor);
if (autohide_edges & AppbarAutohideEdge::LEFT)
insets.set_left(-thickness);
if (autohide_edges & AppbarAutohideEdge::TOP)
insets.set_top(-thickness);
if (autohide_edges & AppbarAutohideEdge::RIGHT)
insets.set_right(thickness);
if (autohide_edges & AppbarAutohideEdge::BOTTOM)
insets.set_bottom(thickness);
}

restore_bounds_ = GetBounds();
auto display =
display::Screen::GetScreen()->GetDisplayNearestPoint(GetPosition());
gfx::Rect bounds = display.work_area();
bounds.Inset(insets);
SetBounds(bounds, false);
}

bool NativeWindowViews::ExecuteWindowsCommand(int command_id) {
std::string command = AppCommandToString(command_id);
NotifyWindowExecuteAppCommand(command);
Expand Down

0 comments on commit da76c4d

Please sign in to comment.