chromium/ui/views/win/fullscreen_handler.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/win/fullscreen_handler.h"

#include <memory>

#include "base/win/win_util.h"
#include "ui/display/types/display_constants.h"
#include "ui/display/win/screen_win.h"
#include "ui/display/win/screen_win_display.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/win/scoped_fullscreen_visibility.h"

namespace views {

////////////////////////////////////////////////////////////////////////////////
// FullscreenHandler, public:

FullscreenHandler::FullscreenHandler() = default;

FullscreenHandler::~FullscreenHandler() = default;

void FullscreenHandler::SetFullscreen(bool fullscreen,
                                      int64_t target_display_id) {
  if (fullscreen_ == fullscreen &&
      target_display_id == display::kInvalidDisplayId) {
    return;
  }

  ProcessFullscreen(fullscreen, target_display_id);
}

void FullscreenHandler::MarkFullscreen(bool fullscreen) {
  if (!task_bar_list_) {
    HRESULT hr =
        ::CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER,
                           IID_PPV_ARGS(&task_bar_list_));
    if (SUCCEEDED(hr) && FAILED(task_bar_list_->HrInit()))
      task_bar_list_ = nullptr;
  }

  // As per MSDN marking the window as fullscreen should ensure that the
  // taskbar is moved to the bottom of the Z-order when the fullscreen window
  // is activated. If the window is not fullscreen, the Shell falls back to
  // heuristics to determine how the window should be treated, which means
  // that it could still consider the window as fullscreen. :(
  if (task_bar_list_)
    task_bar_list_->MarkFullscreenWindow(hwnd_, !!fullscreen);
}

gfx::Rect FullscreenHandler::GetRestoreBounds() const {
  return gfx::Rect(saved_window_info_.rect);
}

////////////////////////////////////////////////////////////////////////////////
// FullscreenHandler, private:

void FullscreenHandler::ProcessFullscreen(bool fullscreen,
                                          int64_t target_display_id) {
  std::unique_ptr<ScopedFullscreenVisibility> visibility;

  // Save current window state if not already fullscreen.
  if (!fullscreen_) {
    saved_window_info_.style = GetWindowLong(hwnd_, GWL_STYLE);
    saved_window_info_.ex_style = GetWindowLong(hwnd_, GWL_EXSTYLE);
    // Store the original window rect, DPI, and monitor info to detect changes
    // and more accurately restore window placements when exiting fullscreen.
    ::GetWindowRect(hwnd_, &saved_window_info_.rect);
    saved_window_info_.dpi = display::win::ScreenWin::GetDPIForHWND(hwnd_);
    saved_window_info_.monitor =
        MonitorFromWindow(hwnd_, MONITOR_DEFAULTTONEAREST);
    saved_window_info_.monitor_info.cbSize =
        sizeof(saved_window_info_.monitor_info);
    GetMonitorInfo(saved_window_info_.monitor,
                   &saved_window_info_.monitor_info);
  }

  fullscreen_ = fullscreen;

  auto ref = weak_ptr_factory_.GetWeakPtr();
  if (fullscreen_) {
    // Set new window style and size.
    SetWindowLong(hwnd_, GWL_STYLE,
                  saved_window_info_.style & ~(WS_CAPTION | WS_THICKFRAME));
    SetWindowLong(
        hwnd_, GWL_EXSTYLE,
        saved_window_info_.ex_style & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE |
                                        WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));

    // Set the window rect to the rcMonitor of the targeted or current display.
    const display::win::ScreenWinDisplay screen_win_display =
        display::win::ScreenWin::GetScreenWinDisplayWithDisplayId(
            target_display_id);
    gfx::Rect window_rect = screen_win_display.screen_rect();
    if (target_display_id == display::kInvalidDisplayId ||
        screen_win_display.display().id() != target_display_id) {
      HMONITOR monitor = MonitorFromWindow(hwnd_, MONITOR_DEFAULTTONEAREST);
      MONITORINFO monitor_info;
      monitor_info.cbSize = sizeof(monitor_info);
      GetMonitorInfo(monitor, &monitor_info);
      window_rect = gfx::Rect(monitor_info.rcMonitor);
    }
    SetWindowPos(hwnd_, nullptr, window_rect.x(), window_rect.y(),
                 window_rect.width(), window_rect.height(),
                 SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
  } else {
    // Restore the window style and bounds saved prior to entering fullscreen.
    // Use WS_VISIBLE for windows shown after SetFullscreen: crbug.com/1062251.
    // Making multiple window adjustments here is ugly, but if SetWindowPos()
    // doesn't redraw, the taskbar won't be repainted.
    SetWindowLong(hwnd_, GWL_STYLE, saved_window_info_.style | WS_VISIBLE);
    SetWindowLong(hwnd_, GWL_EXSTYLE, saved_window_info_.ex_style);

    gfx::Rect window_rect(saved_window_info_.rect);
    HMONITOR monitor =
        MonitorFromRect(&saved_window_info_.rect, MONITOR_DEFAULTTONEAREST);
    MONITORINFO monitor_info;
    monitor_info.cbSize = sizeof(monitor_info);
    GetMonitorInfo(monitor, &monitor_info);
    // Adjust the window bounds to restore, if displays were disconnected,
    // virtually rearranged, or otherwise changed metrics during fullscreen.
    if (monitor != saved_window_info_.monitor ||
        gfx::Rect(saved_window_info_.monitor_info.rcWork) !=
            gfx::Rect(monitor_info.rcWork)) {
      window_rect.AdjustToFit(gfx::Rect(monitor_info.rcWork));
    }
    const int fullscreen_dpi = display::win::ScreenWin::GetDPIForHWND(hwnd_);

    SetWindowPos(hwnd_, nullptr, window_rect.x(), window_rect.y(),
                 window_rect.width(), window_rect.height(),
                 SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);

    const int final_dpi = display::win::ScreenWin::GetDPIForHWND(hwnd_);
    if (final_dpi != saved_window_info_.dpi || final_dpi != fullscreen_dpi) {
      // Reissue SetWindowPos if the DPI changed from saved or fullscreen DPIs.
      // The first call may misinterpret bounds spanning displays, if the
      // fullscreen display's DPI does not match the target display's DPI.
      //
      // Scale and clamp the bounds if the final DPI changed from the saved DPI.
      // This more accurately matches the original placement, while avoiding
      // unexpected offscreen placement in a recongifured multi-screen space.
      if (final_dpi != saved_window_info_.dpi) {
        gfx::SizeF size(window_rect.size());
        size.Scale(final_dpi / static_cast<float>(saved_window_info_.dpi));
        window_rect.set_size(gfx::ToCeiledSize(size));
        window_rect.AdjustToFit(gfx::Rect(monitor_info.rcWork));
      }
      SetWindowPos(hwnd_, nullptr, window_rect.x(), window_rect.y(),
                   window_rect.width(), window_rect.height(),
                   SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
    }
  }
  if (!ref)
    return;

  MarkFullscreen(fullscreen);
}

}  // namespace views