blob: e1b8c233979edf5a194153d0cbeaee96dd89a03b [file] [log] [blame]
// Copyright 2014 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 "ash/display/display_util.h"
#include <algorithm>
#include "ash/common/display/display_info.h"
#include "ash/common/system/system_notifier.h"
#include "ash/display/display_manager.h"
#include "ash/host/ash_window_tree_host.h"
#include "ash/new_window_delegate.h"
#include "ash/shell.h"
#include "base/strings/string_number_conversions.h"
#include "grit/ash_resources.h"
#include "ui/aura/env.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/display/display.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/notification.h"
#include "ui/message_center/notification_delegate.h"
#include "ui/message_center/notification_list.h"
#include "ui/wm/core/coordinate_conversion.h"
#if defined(OS_CHROMEOS)
#include "base/sys_info.h"
#endif
namespace ash {
namespace {
const char kDisplayErrorNotificationId[] = "chrome://settings/display/error";
// A notification delegate that will start the feedback app when the notication
// is clicked.
class DisplayErrorNotificationDelegate
: public message_center::NotificationDelegate {
public:
DisplayErrorNotificationDelegate() = default;
// message_center::NotificationDelegate:
bool HasClickedListener() override { return true; }
void Click() override {
ash::Shell::GetInstance()->new_window_delegate()->OpenFeedbackPage();
}
private:
// Private destructor since NotificationDelegate is ref-counted.
~DisplayErrorNotificationDelegate() override = default;
DISALLOW_COPY_AND_ASSIGN(DisplayErrorNotificationDelegate);
};
// List of value UI Scale values. Scales for 2x are equivalent to 640,
// 800, 1024, 1280, 1440, 1600 and 1920 pixel width respectively on
// 2560 pixel width 2x density display. Please see crbug.com/233375
// for the full list of resolutions.
const float kUIScalesFor2x[] = {0.5f, 0.625f, 0.8f, 1.0f,
1.125f, 1.25f, 1.5f, 2.0f};
const float kUIScalesFor1_25x[] = {0.5f, 0.625f, 0.8f, 1.0f, 1.25f};
const float kUIScalesFor1280[] = {0.5f, 0.625f, 0.8f, 1.0f, 1.125f};
const float kUIScalesFor1366[] = {0.5f, 0.6f, 0.75f, 1.0f, 1.125f};
std::vector<float> GetScalesForDisplay(const DisplayMode& native_mode) {
#define ASSIGN_ARRAY(v, a) v.assign(a, a + arraysize(a))
std::vector<float> ret;
if (native_mode.device_scale_factor == 2.0f) {
ASSIGN_ARRAY(ret, kUIScalesFor2x);
return ret;
} else if (native_mode.device_scale_factor == 1.25f) {
ASSIGN_ARRAY(ret, kUIScalesFor1_25x);
return ret;
}
switch (native_mode.size.width()) {
case 1280:
ASSIGN_ARRAY(ret, kUIScalesFor1280);
break;
case 1366:
ASSIGN_ARRAY(ret, kUIScalesFor1366);
break;
default:
ASSIGN_ARRAY(ret, kUIScalesFor1280);
#if defined(OS_CHROMEOS)
if (base::SysInfo::IsRunningOnChromeOS())
NOTREACHED() << "Unknown resolution:" << native_mode.size.ToString();
#endif
}
return ret;
}
struct ScaleComparator {
explicit ScaleComparator(float s) : scale(s) {}
bool operator()(const DisplayMode& mode) const {
const float kEpsilon = 0.0001f;
return std::abs(scale - mode.ui_scale) < kEpsilon;
}
float scale;
};
void ConvertPointFromScreenToNative(aura::WindowTreeHost* host,
gfx::Point* point) {
::wm::ConvertPointFromScreen(host->window(), point);
host->ConvertPointToNativeScreen(point);
}
bool GetDisplayModeForUIScale(const DisplayInfo& info,
float ui_scale,
DisplayMode* out) {
const std::vector<DisplayMode>& modes = info.display_modes();
auto iter = std::find_if(modes.begin(), modes.end(),
[ui_scale](const DisplayMode& mode) {
return mode.ui_scale == ui_scale;
});
if (iter == modes.end())
return false;
*out = *iter;
return true;
}
void FindNextMode(std::vector<DisplayMode>::const_iterator& iter,
const std::vector<DisplayMode>& modes,
bool up,
DisplayMode* out) {
DCHECK(iter != modes.end());
if (up && (iter + 1) != modes.end())
*out = *(iter + 1);
else if (!up && iter != modes.begin())
*out = *(iter - 1);
else
*out = *iter;
}
} // namespace
std::vector<DisplayMode> CreateInternalDisplayModeList(
const DisplayMode& native_mode) {
std::vector<DisplayMode> display_mode_list;
float native_ui_scale = (native_mode.device_scale_factor == 1.25f)
? 1.0f
: native_mode.device_scale_factor;
for (float ui_scale : GetScalesForDisplay(native_mode)) {
DisplayMode mode = native_mode;
mode.ui_scale = ui_scale;
mode.native = (ui_scale == native_ui_scale);
display_mode_list.push_back(mode);
}
return display_mode_list;
}
std::vector<DisplayMode> CreateUnifiedDisplayModeList(
const DisplayMode& native_mode,
const std::set<std::pair<float, float>>& dsf_scale_list) {
std::vector<DisplayMode> display_mode_list;
for (auto& pair : dsf_scale_list) {
DisplayMode mode = native_mode;
mode.device_scale_factor = pair.first;
gfx::SizeF scaled_size(native_mode.size);
scaled_size.Scale(pair.second);
mode.size = gfx::ToFlooredSize(scaled_size);
mode.native = false;
display_mode_list.push_back(mode);
}
// Sort the mode by the size in DIP.
std::sort(display_mode_list.begin(), display_mode_list.end(),
[](const DisplayMode& a, const DisplayMode& b) {
return a.GetSizeInDIP(false).GetArea() <
b.GetSizeInDIP(false).GetArea();
});
return display_mode_list;
}
bool GetDisplayModeForResolution(const DisplayInfo& info,
const gfx::Size& resolution,
DisplayMode* out) {
if (display::Display::IsInternalDisplayId(info.id()))
return false;
const std::vector<DisplayMode>& modes = info.display_modes();
DCHECK_NE(0u, modes.size());
DisplayMode target_mode;
target_mode.size = resolution;
std::vector<DisplayMode>::const_iterator iter = std::find_if(
modes.begin(), modes.end(), [resolution](const DisplayMode& mode) {
return mode.size == resolution;
});
if (iter == modes.end()) {
LOG(WARNING) << "Unsupported resolution was requested:"
<< resolution.ToString();
return false;
}
*out = *iter;
return true;
}
bool GetDisplayModeForNextUIScale(const DisplayInfo& info,
bool up,
DisplayMode* out) {
DisplayManager* display_manager = Shell::GetInstance()->display_manager();
if (!display_manager->IsActiveDisplayId(info.id()) ||
!display::Display::IsInternalDisplayId(info.id())) {
return false;
}
const std::vector<DisplayMode>& modes = info.display_modes();
ScaleComparator comparator(info.configured_ui_scale());
auto iter = std::find_if(modes.begin(), modes.end(), comparator);
FindNextMode(iter, modes, up, out);
return true;
}
bool GetDisplayModeForNextResolution(const DisplayInfo& info,
bool up,
DisplayMode* out) {
if (display::Display::IsInternalDisplayId(info.id()))
return false;
const std::vector<DisplayMode>& modes = info.display_modes();
DisplayMode tmp(info.size_in_pixel(), 0.0f, false, false);
tmp.device_scale_factor = info.device_scale_factor();
gfx::Size resolution = tmp.GetSizeInDIP(false);
auto iter = std::find_if(modes.begin(), modes.end(),
[resolution](const DisplayMode& mode) {
return mode.GetSizeInDIP(false) == resolution;
});
FindNextMode(iter, modes, up, out);
return true;
}
bool SetDisplayUIScale(int64_t id, float ui_scale) {
DisplayManager* display_manager = Shell::GetInstance()->display_manager();
if (!display_manager->IsActiveDisplayId(id) ||
!display::Display::IsInternalDisplayId(id)) {
return false;
}
const DisplayInfo& info = display_manager->GetDisplayInfo(id);
DisplayMode mode;
if (!GetDisplayModeForUIScale(info, ui_scale, &mode))
return false;
return display_manager->SetDisplayMode(id, mode);
}
bool HasDisplayModeForUIScale(const DisplayInfo& info, float ui_scale) {
ScaleComparator comparator(ui_scale);
const std::vector<DisplayMode>& modes = info.display_modes();
return std::find_if(modes.begin(), modes.end(), comparator) != modes.end();
}
bool ComputeBoundary(const display::Display& a_display,
const display::Display& b_display,
gfx::Rect* a_edge_in_screen,
gfx::Rect* b_edge_in_screen) {
const gfx::Rect& a_bounds = a_display.bounds();
const gfx::Rect& b_bounds = b_display.bounds();
// Find touching side.
int rx = std::max(a_bounds.x(), b_bounds.x());
int ry = std::max(a_bounds.y(), b_bounds.y());
int rr = std::min(a_bounds.right(), b_bounds.right());
int rb = std::min(a_bounds.bottom(), b_bounds.bottom());
display::DisplayPlacement::Position position;
if ((rb - ry) == 0) {
// top bottom
if (a_bounds.bottom() == b_bounds.y()) {
position = display::DisplayPlacement::BOTTOM;
} else if (a_bounds.y() == b_bounds.bottom()) {
position = display::DisplayPlacement::TOP;
} else {
return false;
}
} else {
// left right
if (a_bounds.right() == b_bounds.x()) {
position = display::DisplayPlacement::RIGHT;
} else if (a_bounds.x() == b_bounds.right()) {
position = display::DisplayPlacement::LEFT;
} else {
DCHECK_NE(rr, rx);
return false;
}
}
switch (position) {
case display::DisplayPlacement::TOP:
case display::DisplayPlacement::BOTTOM: {
int left = std::max(a_bounds.x(), b_bounds.x());
int right = std::min(a_bounds.right(), b_bounds.right());
if (position == display::DisplayPlacement::TOP) {
a_edge_in_screen->SetRect(left, a_bounds.y(), right - left, 1);
b_edge_in_screen->SetRect(left, b_bounds.bottom() - 1, right - left, 1);
} else {
a_edge_in_screen->SetRect(left, a_bounds.bottom() - 1, right - left, 1);
b_edge_in_screen->SetRect(left, b_bounds.y(), right - left, 1);
}
break;
}
case display::DisplayPlacement::LEFT:
case display::DisplayPlacement::RIGHT: {
int top = std::max(a_bounds.y(), b_bounds.y());
int bottom = std::min(a_bounds.bottom(), b_bounds.bottom());
if (position == display::DisplayPlacement::LEFT) {
a_edge_in_screen->SetRect(a_bounds.x(), top, 1, bottom - top);
b_edge_in_screen->SetRect(b_bounds.right() - 1, top, 1, bottom - top);
} else {
a_edge_in_screen->SetRect(a_bounds.right() - 1, top, 1, bottom - top);
b_edge_in_screen->SetRect(b_bounds.x(), top, 1, bottom - top);
}
break;
}
}
return true;
}
gfx::Rect GetNativeEdgeBounds(AshWindowTreeHost* ash_host,
const gfx::Rect& bounds_in_screen) {
aura::WindowTreeHost* host = ash_host->AsWindowTreeHost();
gfx::Rect native_bounds = host->GetBounds();
native_bounds.Inset(ash_host->GetHostInsets());
gfx::Point start_in_native = bounds_in_screen.origin();
gfx::Point end_in_native = bounds_in_screen.bottom_right();
ConvertPointFromScreenToNative(host, &start_in_native);
ConvertPointFromScreenToNative(host, &end_in_native);
if (std::abs(start_in_native.x() - end_in_native.x()) <
std::abs(start_in_native.y() - end_in_native.y())) {
// vertical in native
int x = std::abs(native_bounds.x() - start_in_native.x()) <
std::abs(native_bounds.right() - start_in_native.x())
? native_bounds.x()
: native_bounds.right() - 1;
return gfx::Rect(x, std::min(start_in_native.y(), end_in_native.y()), 1,
std::abs(end_in_native.y() - start_in_native.y()));
} else {
// horizontal in native
int y = std::abs(native_bounds.y() - start_in_native.y()) <
std::abs(native_bounds.bottom() - start_in_native.y())
? native_bounds.y()
: native_bounds.bottom() - 1;
return gfx::Rect(std::min(start_in_native.x(), end_in_native.x()), y,
std::abs(end_in_native.x() - start_in_native.x()), 1);
}
}
// Moves the cursor to the point inside the root that is closest to
// the point_in_screen, which is outside of the root window.
void MoveCursorTo(AshWindowTreeHost* ash_host,
const gfx::Point& point_in_screen,
bool update_last_location_now) {
aura::WindowTreeHost* host = ash_host->AsWindowTreeHost();
gfx::Point point_in_native = point_in_screen;
::wm::ConvertPointFromScreen(host->window(), &point_in_native);
host->ConvertPointToNativeScreen(&point_in_native);
// now fit the point inside the native bounds.
gfx::Rect native_bounds = host->GetBounds();
gfx::Point native_origin = native_bounds.origin();
native_bounds.Inset(ash_host->GetHostInsets());
// Shrink further so that the mouse doesn't warp on the
// edge. The right/bottom needs to be shrink by 2 to subtract
// the 1 px from width/height value.
native_bounds.Inset(1, 1, 2, 2);
// Ensure that |point_in_native| is inside the |native_bounds|.
point_in_native.SetToMax(native_bounds.origin());
point_in_native.SetToMin(native_bounds.bottom_right());
gfx::Point point_in_host = point_in_native;
point_in_host.Offset(-native_origin.x(), -native_origin.y());
host->MoveCursorToHostLocation(point_in_host);
if (update_last_location_now) {
gfx::Point new_point_in_screen;
if (Shell::GetInstance()->display_manager()->IsInUnifiedMode()) {
new_point_in_screen = point_in_host;
// First convert to the unified host.
host->ConvertPointFromHost(&new_point_in_screen);
// Then convert to the unified screen.
Shell::GetPrimaryRootWindow()->GetHost()->ConvertPointFromHost(
&new_point_in_screen);
} else {
new_point_in_screen = point_in_native;
host->ConvertPointFromNativeScreen(&new_point_in_screen);
::wm::ConvertPointToScreen(host->window(), &new_point_in_screen);
}
aura::Env::GetInstance()->set_last_mouse_location(new_point_in_screen);
}
}
int FindDisplayIndexContainingPoint(
const std::vector<display::Display>& displays,
const gfx::Point& point_in_screen) {
auto iter = std::find_if(displays.begin(), displays.end(),
[point_in_screen](const display::Display& display) {
return display.bounds().Contains(point_in_screen);
});
return iter == displays.end() ? -1 : (iter - displays.begin());
}
display::DisplayIdList CreateDisplayIdList(const display::DisplayList& list) {
return GenerateDisplayIdList(
list.begin(), list.end(),
[](const display::Display& display) { return display.id(); });
}
void SortDisplayIdList(display::DisplayIdList* ids) {
std::sort(ids->begin(), ids->end(),
[](int64_t a, int64_t b) { return CompareDisplayIds(a, b); });
}
std::string DisplayIdListToString(const display::DisplayIdList& list) {
std::stringstream s;
const char* sep = "";
for (int64_t id : list) {
s << sep << id;
sep = ",";
}
return s.str();
}
bool CompareDisplayIds(int64_t id1, int64_t id2) {
DCHECK_NE(id1, id2);
// Output index is stored in the first 8 bits. See GetDisplayIdFromEDID
// in edid_parser.cc.
int index_1 = id1 & 0xFF;
int index_2 = id2 & 0xFF;
DCHECK_NE(index_1, index_2) << id1 << " and " << id2;
return display::Display::IsInternalDisplayId(id1) ||
(index_1 < index_2 && !display::Display::IsInternalDisplayId(id2));
}
void ShowDisplayErrorNotification(int message_id) {
// Always remove the notification to make sure the notification appears
// as a popup in any situation.
message_center::MessageCenter::Get()->RemoveNotification(
kDisplayErrorNotificationId, false /* by_user */);
ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
std::unique_ptr<message_center::Notification> notification(
new message_center::Notification(
message_center::NOTIFICATION_TYPE_SIMPLE, kDisplayErrorNotificationId,
base::string16(), // title
l10n_util::GetStringUTF16(message_id),
bundle.GetImageNamed(IDR_AURA_NOTIFICATION_DISPLAY),
base::string16(), // display_source
GURL(), message_center::NotifierId(
message_center::NotifierId::SYSTEM_COMPONENT,
system_notifier::kNotifierDisplayError),
message_center::RichNotificationData(),
new DisplayErrorNotificationDelegate));
message_center::MessageCenter::Get()->AddNotification(
std::move(notification));
}
base::string16 GetDisplayErrorNotificationMessageForTest() {
message_center::NotificationList::Notifications notifications =
message_center::MessageCenter::Get()->GetVisibleNotifications();
for (auto* const notification : notifications) {
if (notification->id() == kDisplayErrorNotificationId)
return notification->message();
}
return base::string16();
}
} // namespace ash