blob: 22747ebcf31c096b5ec46f3e59e6baf98a9ffacb [file] [log] [blame]
// 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/display/screen.h"
#include <utility>
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "ui/display/display.h"
#include "ui/display/display_util.h"
#include "ui/display/tablet_state.h"
#include "ui/display/types/display_constants.h"
#include "ui/gfx/geometry/rect.h"
namespace display {
namespace {
Screen* g_screen;
} // namespace
Screen::Screen() : display_id_for_new_windows_(kInvalidDisplayId) {}
Screen::~Screen() = default;
// static
Screen* Screen::GetScreen() {
#if BUILDFLAG(IS_IOS)
if (!g_screen)
g_screen = CreateNativeScreen();
#endif
return g_screen;
}
// static
Screen* Screen::SetScreenInstance(Screen* instance,
const base::Location& location) {
// Do not allow screen instance override. The screen object has a lot of
// states, such as current display settings as well as observers, and safely
// transferring these to new screen implementation is very difficult and not
// safe. If you hit the DCHECK in a test, please look for other examples that
// that set a test screen instance in the setup process.
DCHECK(!g_screen || !instance || (instance && instance->shutdown_))
<< "fail=" << location.ToString();
return std::exchange(g_screen, instance);
}
// static
bool Screen::HasScreen() {
return !!g_screen;
}
void Screen::SetCursorScreenPointForTesting(const gfx::Point& point) {
NOTIMPLEMENTED_LOG_ONCE();
}
Display Screen::GetDisplayNearestView(gfx::NativeView view) const {
return GetDisplayNearestWindow(GetWindowForView(view));
}
Display Screen::GetDisplayForNewWindows() const {
Display display;
// Scoped value can override if it is set.
if (scoped_display_id_for_new_windows_ != kInvalidDisplayId &&
GetDisplayWithDisplayId(scoped_display_id_for_new_windows_, &display)) {
return display;
}
if (GetDisplayWithDisplayId(display_id_for_new_windows_, &display))
return display;
// Fallback to primary display.
return GetPrimaryDisplay();
}
void Screen::SetDisplayForNewWindows(int64_t display_id) {
// GetDisplayForNewWindows() handles invalid display ids.
display_id_for_new_windows_ = display_id;
}
#if BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_LINUX)
Screen::ScreenSaverSuspender::~ScreenSaverSuspender() = default;
std::unique_ptr<Screen::ScreenSaverSuspender> Screen::SuspendScreenSaver() {
NOTIMPLEMENTED_LOG_ONCE();
return nullptr;
}
#endif // BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_LINUX)
bool Screen::IsScreenSaverActive() const {
NOTIMPLEMENTED_LOG_ONCE();
return false;
}
base::TimeDelta Screen::CalculateIdleTime() const {
NOTIMPLEMENTED_LOG_ONCE();
return base::Seconds(0);
}
gfx::Rect Screen::ScreenToDIPRectInWindow(gfx::NativeWindow window,
const gfx::Rect& screen_rect) const {
float scale = GetDisplayNearestWindow(window).device_scale_factor();
return ScaleToEnclosingRect(screen_rect, 1.0f / scale);
}
gfx::Rect Screen::DIPToScreenRectInWindow(gfx::NativeWindow window,
const gfx::Rect& dip_rect) const {
float scale = GetDisplayNearestWindow(window).device_scale_factor();
return ScaleToEnclosingRect(dip_rect, scale);
}
bool Screen::GetDisplayWithDisplayId(int64_t display_id,
Display* display) const {
for (const Display& display_in_list : GetAllDisplays()) {
if (display_in_list.id() == display_id) {
*display = display_in_list;
return true;
}
}
return false;
}
void Screen::SetPanelRotationForTesting(int64_t display_id,
Display::Rotation rotation) {
// Not implemented.
DCHECK(false);
}
std::string Screen::GetCurrentWorkspace() {
NOTIMPLEMENTED_LOG_ONCE();
return {};
}
base::Value::List Screen::GetGpuExtraInfo(
const gfx::GpuExtraInfo& gpu_extra_info) {
return base::Value::List();
}
#if BUILDFLAG(IS_CHROMEOS)
TabletState Screen::GetTabletState() const {
return TabletState::kInClamshellMode;
}
bool Screen::InTabletMode() const {
TabletState state = GetTabletState();
return state == TabletState::kInTabletMode ||
state == TabletState::kEnteringTabletMode;
}
#endif
void Screen::SetScopedDisplayForNewWindows(int64_t display_id) {
if (display_id == scoped_display_id_for_new_windows_)
return;
// Only allow set and clear, not switch.
DCHECK(display_id == kInvalidDisplayId ^
scoped_display_id_for_new_windows_ == kInvalidDisplayId)
<< "display_id=" << display_id << ", scoped_display_id_for_new_windows_="
<< scoped_display_id_for_new_windows_;
scoped_display_id_for_new_windows_ = display_id;
}
ScreenInfos Screen::GetScreenInfosNearestDisplay(int64_t nearest_id) const {
ScreenInfos result;
// Determine the current and primary display ids.
std::vector<Display> displays = GetAllDisplays();
Display primary = GetPrimaryDisplay();
// Note: displays being empty can happen in Fuchsia unit tests.
if (displays.empty()) {
if (primary.id() == kInvalidDisplayId) {
// If we are in a situation where we have no displays and so the primary
// display is invalid, then it's a logic error (elsewhere) to pass in a
// valid id, because where would it come from?
DCHECK_EQ(nearest_id, kInvalidDisplayId);
primary.set_id(kDefaultDisplayId);
}
displays = {primary};
}
// Use the primary and nearest displays as fallbacks for each other, if the
// counterpart exists in `displays`. Otherwise, use `display[0]` for both.
int64_t primary_id = primary.id();
int64_t current_id = nearest_id;
const bool has_primary = base::Contains(displays, primary_id, &Display::id);
const bool has_nearest = base::Contains(displays, nearest_id, &Display::id);
if (!has_primary)
primary_id = has_nearest ? nearest_id : displays[0].id();
if (!has_nearest)
current_id = primary_id;
// Build ScreenInfos from discovered ids and set of all displays.
bool current_display_exists = false;
bool primary_display_exists = false;
for (const auto& display : displays) {
ScreenInfo screen_info;
DisplayUtil::DisplayToScreenInfo(&screen_info, display);
if (display.id() == current_id) {
result.current_display_id = display.id();
current_display_exists = true;
}
// TODO(enne): move DisplayToScreenInfo to be a private function here,
// so that we don't need to overwrite this.
screen_info.is_primary = display.id() == primary_id;
if (display.id() == primary_id)
primary_display_exists = true;
result.screen_infos.push_back(screen_info);
}
// This is a bit overkill, but verify that the logic above is correct
// because it will cause crashes elsewhere to not have a current display.
CHECK(current_display_exists);
CHECK(primary_display_exists);
return result;
}
#if BUILDFLAG(IS_APPLE)
ScopedNativeScreen::ScopedNativeScreen(const base::Location& location) {
if (!Screen::HasScreen()) {
#if BUILDFLAG(IS_IOS)
Screen::GetScreen();
#else
screen_ = base::WrapUnique(CreateNativeScreen());
Screen::SetScreenInstance(screen_.get(), location);
#endif
}
}
ScopedNativeScreen::~ScopedNativeScreen() {
if (screen_) {
DCHECK_EQ(screen_.get(), Screen::GetScreen());
Screen::SetScreenInstance(nullptr);
screen_.reset();
}
}
#endif
} // namespace display