// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview
* 'settings-display' is the settings subpage for display settings.
*/
import 'chrome://resources/ash/common/cr_elements/cr_checkbox/cr_checkbox.js';
import 'chrome://resources/ash/common/cr_elements/cr_link_row/cr_link_row.js';
import 'chrome://resources/ash/common/cr_elements/cr_slider/cr_slider.js';
import 'chrome://resources/ash/common/cr_elements/cr_tabs/cr_tabs.js';
import 'chrome://resources/ash/common/cr_elements/cr_toggle/cr_toggle.js';
import 'chrome://resources/ash/common/cr_elements/policy/cr_policy_pref_indicator.js';
import 'chrome://resources/ash/common/cr_elements/md_select.css.js';
import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
import './display_layout.js';
import './display_overscan_dialog.js';
import './display_night_light.js';
import '../controls/settings_slider.js';
import '../settings_shared.css.js';
import '../settings_vars.css.js';
import 'chrome://resources/ash/common/cr_elements/cr_slider/cr_slider.js';
import 'chrome://resources/ash/common/cr_elements/cr_shared_style.css.js';
import {PrefsMixin} from '/shared/settings/prefs/prefs_mixin.js';
import {CrCheckboxElement} from 'chrome://resources/ash/common/cr_elements/cr_checkbox/cr_checkbox.js';
import {CrSliderElement, SliderTick} from 'chrome://resources/ash/common/cr_elements/cr_slider/cr_slider.js';
import {CrToggleElement} from 'chrome://resources/ash/common/cr_elements/cr_toggle/cr_toggle.js';
import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js';
import {assert} from 'chrome://resources/js/assert.js';
import {focusWithoutInk} from 'chrome://resources/js/focus_without_ink.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {flush, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {assertExists, cast, castExists} from '../assert_extras.js';
import {DeepLinkingMixin} from '../common/deep_linking_mixin.js';
import {isDisplayBrightnessControlInSettingsEnabled, isRevampWayfindingEnabled} from '../common/load_time_booleans.js';
import {RouteObserverMixin} from '../common/route_observer_mixin.js';
import {DropdownMenuOptionList} from '../controls/settings_dropdown_menu.js';
import {SettingsSliderElement} from '../controls/settings_slider.js';
import {AmbientLightSensorObserverReceiver, DisplayBrightnessSettingsObserverReceiver, DisplayConfigurationObserverReceiver, DisplaySettingsOrientationOption, DisplaySettingsProviderInterface, DisplaySettingsType, DisplaySettingsValue, TabletModeObserverReceiver} from '../mojom-webui/display_settings_provider.mojom-webui.js';
import {Setting} from '../mojom-webui/setting.mojom-webui.js';
import {Route, routes} from '../router.js';
import {DevicePageBrowserProxy, DevicePageBrowserProxyImpl, getDisplayApi} from './device_page_browser_proxy.js';
import {getTemplate} from './display.html.js';
import {SettingsDisplayOverscanDialogElement} from './display_overscan_dialog.js';
import {getDisplaySettingsProvider} from './display_settings_mojo_interface_provider.js';
import DisplayLayout = chrome.system.display.DisplayLayout;
import DisplayMode = chrome.system.display.DisplayMode;
import DisplayProperties = chrome.system.display.DisplayProperties;
import DisplayUnitInfo = chrome.system.display.DisplayUnitInfo;
import GetInfoFlags = chrome.system.display.GetInfoFlags;
import MirrorMode = chrome.system.display.MirrorMode;
import MirrorModeInfo = chrome.system.display.MirrorModeInfo;
interface DisplayResolutionPrefObject {
value: {
recommended?: boolean,
external_width?: number,
external_height?: number,
external_use_native?: boolean,
external_scale_percentage?: number,
internal_scale_percentage?: number,
}|null;
}
function createDisplayValue(overrides: Partial<DisplaySettingsValue>):
DisplaySettingsValue {
const empty = {
isInternalDisplay: null,
displayId: null,
orientation: null,
nightLightStatus: null,
nightLightSchedule: null,
mirrorModeStatus: null,
unifiedModeStatus: null,
};
return Object.assign(empty, overrides);
}
export interface SettingsDisplayElement {
$: {
displayOverscan: SettingsDisplayOverscanDialogElement,
displaySizeSlider: SettingsSliderElement,
};
}
const SettingsDisplayElementBase =
DeepLinkingMixin(PrefsMixin(RouteObserverMixin(I18nMixin(PolymerElement))));
// Set the MIN_VISIBLE_PERCENT to 10%. The lowest brightness that the slider can
// go is 5%, so the slider appears the same at 0% and 5%. Therefore, the minimum
// visible percent should be greater than 5%.
const MIN_VISIBLE_PERCENT = 10;
export class SettingsDisplayElement extends SettingsDisplayElementBase {
static get is() {
return 'settings-display';
}
static get template() {
return getTemplate();
}
static get properties() {
return {
isRevampWayfindingEnabled_: {
type: Boolean,
value: () => {
return isRevampWayfindingEnabled();
},
readOnly: true,
},
selectedModePref_: {
type: Object,
value() {
return {
key: 'fakeDisplaySliderPref',
type: chrome.settingsPrivate.PrefType.NUMBER,
value: 0,
};
},
},
selectedZoomPref_: {
type: Object,
value() {
return {
key: 'fakeDisplaySliderZoomPref',
type: chrome.settingsPrivate.PrefType.NUMBER,
value: 0,
};
},
},
displays: Array,
layouts: Array,
/**
* String listing the ids in displays. Used to observe changes to the
* display configuration (i.e. when a display is added or removed).
*/
displayIds: {type: String, observer: 'onDisplayIdsChanged_'},
/** Primary display id */
primaryDisplayId: String,
selectedDisplay: Object,
/** Id passed to the overscan dialog. */
overscanDisplayId: {
type: String,
notify: true,
},
/** Ids for mirroring destination displays. */
mirroringDestinationIds: Array,
/** Mode index values for slider. */
modeValues_: Array,
/**
* Display zoom slider tick values.
*/
zoomValues_: Array,
displayModeList_: {
type: Array,
value: [],
},
refreshRateList_: {
type: Array,
value: [],
},
unifiedDesktopAvailable_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('unifiedDesktopAvailable');
},
},
isDisplayPerformanceSupported_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('isDisplayPerformanceSupported');
},
},
ambientColorAvailable_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('deviceSupportsAmbientColor');
},
},
listAllDisplayModes_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('listAllDisplayModes');
},
},
unifiedDesktopMode_: {
type: Boolean,
value: false,
},
isTabletMode_: {
type: Boolean,
value: false,
},
currentInternalScreenBrightness_: {type: Number, value: 0},
isAmbientLightSensorEnabled_: {
type: Boolean,
value: true,
},
hasAmbientLightSensor_: {
type: Boolean,
value: false,
},
brightnessSliderMin_: {
type: Number,
value: 5,
},
brightnessSliderMax_: {
type: Number,
value: 100,
},
isDisplayPerformanceEnabled_: {
type: Boolean,
value: false,
},
selectedParentModePref_: {
type: Object,
value: function() {
return {
key: 'fakeDisplayParentModePref',
type: chrome.settingsPrivate.PrefType.NUMBER,
value: 0,
};
},
},
logicalResolutionText_: String,
displayTabNames_: Array,
selectedTab_: Number,
/**
* Contains the settingId of any deep link that wasn't able to be shown,
* null otherwise.
*/
pendingSettingId_: {
type: Number,
value: null,
},
/**
* Used by DeepLinkingMixin to focus this page's deep links.
*/
supportedSettingIds: {
type: Object,
value: () => new Set<Setting>([
Setting.kDisplaySize,
Setting.kDisplayOrientation,
Setting.kDisplayArrangement,
Setting.kDisplayResolution,
Setting.kDisplayRefreshRate,
Setting.kDisplayMirroring,
Setting.kAllowWindowsToSpanDisplays,
Setting.kAmbientColors,
Setting.kTouchscreenCalibration,
Setting.kDisplayOverscan,
]),
},
};
}
static get observers() {
return [
'onSelectedModeChange_(selectedModePref_.value)',
'onSelectedParentModeChange_(selectedParentModePref_.value)',
'onSelectedZoomChange_(selectedZoomPref_.value)',
'onDisplaysChanged_(displays.*)',
];
}
displayIds: string;
displays: DisplayUnitInfo[];
layouts: DisplayLayout[];
mirroringDestinationIds: string[];
overscanDisplayId: string;
primaryDisplayId: string;
selectedDisplay?: DisplayUnitInfo;
private browserProxy_: DevicePageBrowserProxy;
private brightnessSliderMax_: number;
private brightnessSliderMin_: number;
private currentInternalScreenBrightness_: number;
private currentRoute_: Route|null;
private currentSelectedModeIndex_: number;
private currentSelectedParentModeIndex_: number;
private displayChangedListener_: (() => void)|null;
private displayModeList_: DropdownMenuOptionList;
private displaySettingsProvider: DisplaySettingsProviderInterface;
private displayTabNames_: string[];
private hasAmbientLightSensor_: boolean;
private invalidDisplayId_: string;
private isAmbientLightSensorEnabled_: boolean;
private isDisplayPerformanceEnabled_: boolean;
private readonly isRevampWayfindingEnabled_: boolean;
private isTabletMode_: boolean;
private listAllDisplayModes_: boolean;
private logicalResolutionText_: string;
private modeToParentModeMap_: Map<number, number>;
private modeValues_: number[];
private parentModeToRefreshRateMap_: Map<number, DropdownMenuOptionList>;
private pendingSettingId_: Setting|null;
private refreshRateList_: DropdownMenuOptionList;
private selectedModePref_: chrome.settingsPrivate.PrefObject;
private selectedParentModePref_: chrome.settingsPrivate.PrefObject;
private selectedTab_: number;
private selectedZoomPref_: chrome.settingsPrivate.PrefObject;
private unifiedDesktopMode_: boolean;
private zoomValues_: SliderTick[];
constructor() {
super();
/**
* This represents the index of the mode with the highest refresh rate at
* the current resolution.
*/
this.currentSelectedParentModeIndex_ = -1;
/**
* This is the index of the currently selected mode.
* Selected mode index received from chrome.
*/
this.currentSelectedModeIndex_ = -1;
/**
* Listener for chrome.system.display.onDisplayChanged events.
*/
this.displayChangedListener_ = null;
this.invalidDisplayId_ = loadTimeData.getString('invalidDisplayId');
this.currentRoute_ = null;
this.browserProxy_ = DevicePageBrowserProxyImpl.getInstance();
/**
* Maps a parentModeIndex to the list of possible refresh rates.
* All modes have a modeIndex corresponding to the index in the selected
* display's mode list. Parent mode indexes represent the mode with the
* highest refresh rate at a given resolution. There is 1 and only 1
* parentModeIndex for each possible resolution .
*/
this.parentModeToRefreshRateMap_ = new Map();
/**
* Map containing an entry for each display mode mapping its modeIndex to
* the corresponding parentModeIndex value.
* Mode index values for slider.
*/
this.modeToParentModeMap_ = new Map();
// Provider of display settings mojo API.
this.displaySettingsProvider = getDisplaySettingsProvider();
}
override async connectedCallback(): Promise<void> {
super.connectedCallback();
this.displayChangedListener_ =
this.displayChangedListener_ || this.getDisplayInfo_.bind(this);
getDisplayApi().onDisplayChanged.addListener(this.displayChangedListener_);
this.getDisplayInfo_();
this.$.displaySizeSlider.updateValueInstantly = false;
const {isTabletMode} = await this.displaySettingsProvider.observeTabletMode(
new TabletModeObserverReceiver(this).$.bindNewPipeAndPassRemote());
this.isTabletMode_ = isTabletMode;
const {brightnessPercent} =
await this.displaySettingsProvider.observeDisplayBrightnessSettings(
new DisplayBrightnessSettingsObserverReceiver(this)
.$.bindNewPipeAndPassRemote());
this.currentInternalScreenBrightness_ = brightnessPercent;
const {isAmbientLightSensorEnabled} =
await this.displaySettingsProvider.observeAmbientLightSensor(
new AmbientLightSensorObserverReceiver(this)
.$.bindNewPipeAndPassRemote());
this.isAmbientLightSensorEnabled_ = isAmbientLightSensorEnabled;
const {hasAmbientLightSensor} =
await this.displaySettingsProvider.hasAmbientLightSensor();
this.hasAmbientLightSensor_ = hasAmbientLightSensor;
this.displaySettingsProvider.observeDisplayConfiguration(
new DisplayConfigurationObserverReceiver(this)
.$.bindNewPipeAndPassRemote());
// Record metrics that user has opened the display settings page.
this.displaySettingsProvider.recordChangingDisplaySettings(
DisplaySettingsType.kDisplayPage, /*value=*/ createDisplayValue({}));
}
override disconnectedCallback(): void {
super.disconnectedCallback();
getDisplayApi().onDisplayChanged.removeListener(
castExists(this.displayChangedListener_));
this.currentSelectedModeIndex_ = -1;
this.currentSelectedParentModeIndex_ = -1;
}
/**
* Implements TabletModeObserver.OnTabletModeChanged.
*/
onTabletModeChanged(isTabletMode: boolean): void {
this.isTabletMode_ = isTabletMode;
}
/**
* Implements DisplayConfigurationObserver.OnDisplayConfigurationChanged.
*/
onDisplayConfigurationChanged(): void {
// Sync active display settings to avoid UI inconsistency.
this.getDisplayInfo_();
}
/**
* Implements DisplayBrightnessSettingsObserver.OnDisplayBrightnessChanged.
*/
onDisplayBrightnessChanged(
brightnessPercent: number, triggeredByAls: boolean): void {
if (triggeredByAls && brightnessPercent > 0 &&
brightnessPercent < MIN_VISIBLE_PERCENT) {
// When auto-brightness is enabled, it's likely that the automated
// brightness percentage will fall between 0% and 10%. To avoid confusion
// where the user cannot distinguish between the screen being off (0%)
// and low brightness levels, set the slider to a minimum visible
// percentage (10%).
this.currentInternalScreenBrightness_ = MIN_VISIBLE_PERCENT;
return;
}
this.currentInternalScreenBrightness_ = brightnessPercent;
}
/**
* Implements AmbientLightSensorObserver.OnAmbientLightSensorEnabledChanged.
*/
onAmbientLightSensorEnabledChanged(isAmbientLightSensorEnabled: boolean):
void {
this.isAmbientLightSensorEnabled_ = isAmbientLightSensorEnabled;
}
override beforeDeepLinkAttempt(_settingId: Setting): boolean {
if (!this.displays) {
// On initial page load, displays will not be loaded and deep link
// attempt will fail. Suppress warnings by exiting early and try again
// in updateDisplayInfo_.
return false;
}
// Continue with deep link attempt.
return true;
}
override currentRouteChanged(newRoute: Route, oldRoute?: Route): void {
this.currentRoute_ = newRoute;
// When navigating away from the page, deselect any selected display.
if (newRoute !== routes.DISPLAY && oldRoute === routes.DISPLAY) {
this.browserProxy_.highlightDisplay(this.invalidDisplayId_);
return;
}
// Does not apply to this page.
if (newRoute !== routes.DISPLAY) {
this.pendingSettingId_ = null;
return;
}
this.attemptDeepLink().then(result => {
if (!result.deepLinkShown && result.pendingSettingId) {
// Store any deep link settingId that wasn't shown so we can try again
// in updateDisplayInfo_.
this.pendingSettingId_ = result.pendingSettingId;
}
});
}
/**
* Shows or hides the overscan dialog.
*/
private showOverscanDialog_(showOverscan: boolean): void {
if (showOverscan) {
this.$.displayOverscan.open();
this.$.displayOverscan.focus();
} else {
this.$.displayOverscan.close();
}
}
private onDisplayIdsChanged_(): void {
// Close any overscan dialog (which will cancel any overscan operation)
// if displayIds changes.
this.showOverscanDialog_(false);
}
private getDisplayInfo_(): void {
const flags: GetInfoFlags = {
singleUnified: true,
};
getDisplayApi().getInfo(flags).then(
(displays: DisplayUnitInfo[]) => this.displayInfoFetched_(displays));
}
private displayInfoFetched_(displays: DisplayUnitInfo[]): void {
if (!displays.length) {
return;
}
getDisplayApi().getDisplayLayout().then(
(layouts: DisplayLayout[]) =>
this.displayLayoutFetched_(displays, layouts));
if (this.isMirrored(displays)) {
this.mirroringDestinationIds = displays[0].mirroringDestinationIds;
} else {
this.mirroringDestinationIds = [];
}
}
private displayLayoutFetched_(
displays: DisplayUnitInfo[], layouts: DisplayLayout[]): void {
this.layouts = layouts;
this.displays = displays;
this.displayTabNames_ = displays.map(({name}) => name);
this.updateDisplayInfo_();
}
/**
* @return The index of the currently selected mode of the
* |selectedDisplay|. If the display has no modes, returns 0.
*/
private getSelectedModeIndex_(selectedDisplay: DisplayUnitInfo): number {
for (let i = 0; i < selectedDisplay.modes.length; ++i) {
if (selectedDisplay.modes[i].isSelected) {
return i;
}
}
return 0;
}
private isDevicePolicyEnabled_(policyPref: DisplayResolutionPrefObject):
boolean {
return policyPref !== undefined && policyPref.value !== null;
}
private isDisplayResolutionManagedByPolicy_(
resolutionPref: DisplayResolutionPrefObject): boolean {
return this.isDevicePolicyEnabled_(resolutionPref) &&
(resolutionPref.value!.external_use_native !== undefined ||
(resolutionPref.value!.external_width !== undefined &&
resolutionPref.value!.external_height !== undefined));
}
/**
* Checks if display resolution is managed by policy and the policy
* is mandatory.
*/
private isDisplayResolutionMandatory_(
resolutionPref: DisplayResolutionPrefObject): boolean {
return this.isDisplayResolutionManagedByPolicy_(resolutionPref) &&
!resolutionPref.value!.recommended;
}
/**
* Checks if display scale factor is managed by device policy.
*/
private isDisplayScaleManagedByPolicy_(
selectedDisplay: DisplayUnitInfo,
resolutionPref: DisplayResolutionPrefObject): boolean {
if (!this.isDevicePolicyEnabled_(resolutionPref) || !selectedDisplay) {
return false;
}
if (selectedDisplay.isInternal) {
return resolutionPref.value!.internal_scale_percentage !== undefined;
}
return resolutionPref.value!.external_scale_percentage !== undefined;
}
/**
* Checks if display scale factor is managed by policy and the policy
* is mandatory.
*/
private isDisplayScaleMandatory_(
selectedDisplay: DisplayUnitInfo,
resolutionPref: DisplayResolutionPrefObject): boolean {
return this.isDisplayScaleManagedByPolicy_(
selectedDisplay, resolutionPref) &&
!resolutionPref.value!.recommended;
}
/**
* Parses the display modes for |selectedDisplay|. |displayModeList_| will
* contain entries representing a combined resolution + refresh rate.
* Only one parse*DisplayModes_ method must be called, depending on the
* state of |listAllDisplayModes_|.
*/
private parseCompoundDisplayModes_(selectedDisplay: DisplayUnitInfo): void {
assert(!this.listAllDisplayModes_);
const optionList: DropdownMenuOptionList = [];
for (let i = 0; i < selectedDisplay.modes.length; ++i) {
const mode = selectedDisplay.modes[i];
const id = 'displayResolutionMenuItem';
const refreshRate = Math.round(mode.refreshRate * 100) / 100;
const resolution = this.i18n(
id, mode.width.toString(), mode.height.toString(),
refreshRate.toString());
optionList.push({
name: resolution,
value: i,
});
}
this.displayModeList_ = optionList;
}
/**
* Uses the modes of |selectedDisplay| to build a nested map of width =>
* height => refreshRate => modeIndex. modeIndex is the index of the
* resolution + refreshRate combination in |selectedDisplay|'s mode list.
* This is used to traverse all possible display modes in ascending order.
*/
private createModeMap_(selectedDisplay: DisplayUnitInfo):
Map<number, Map<number, Map<number, number>>> {
const modes = new Map();
for (let i = 0; i < selectedDisplay.modes.length; ++i) {
const mode = selectedDisplay.modes[i];
if (!modes.has(mode.width)) {
modes.set(mode.width, new Map());
}
if (!modes.get(mode.width).has(mode.height)) {
modes.get(mode.width).set(mode.height, new Map());
}
// Prefer the first native mode we find, for consistency.
if (modes.get(mode.width).get(mode.height).has(mode.refreshRate)) {
const existingModeIndex =
modes.get(mode.width).get(mode.height).get(mode.refreshRate);
const existingMode = selectedDisplay.modes[existingModeIndex];
if (existingMode.isNative || !mode.isNative) {
continue;
}
}
modes.get(mode.width).get(mode.height).set(mode.refreshRate, i);
}
return modes;
}
/**
* Parses the display modes for |selectedDisplay|. |displayModeList_| will
* contain entries representing only resolution options.
* The 'parentMode' for a resolution is the highest refresh rate. This
* method goes through the mode list for a given display creating data
* structures so that given a resolution, the default refresh rate is
* selected, and other possible refresh rates at that resolution are shown
* in a dropdown. Only one parse*DisplayModes_ method must be called,
* depending on the state of |listAllDisplayModes_|.
*/
private parseSplitDisplayModes_(selectedDisplay: DisplayUnitInfo): void {
assert(this.listAllDisplayModes_);
// Clear the mappings before recalculating.
this.modeToParentModeMap_ = new Map();
this.parentModeToRefreshRateMap_ = new Map();
this.displayModeList_ = [];
// Build the modes into a nested map of width => height => refresh rate.
const modes = this.createModeMap_(selectedDisplay);
// Traverse the modes ordered by width (asc), height (asc),
// refresh rate (desc).
const widthsArr = Array.from(modes.keys()).sort();
for (let i = 0; i < widthsArr.length; i++) {
const width = widthsArr[i];
const heightsMap = modes.get(width)!;
const heightArr = Array.from(heightsMap.keys());
for (let j = 0; j < heightArr.length; j++) {
// The highest/first refresh rate for each width/height pair
// (resolution) is the default and therefore the "parent" mode.
const height = heightArr[j];
const refreshRates = heightsMap.get(height)!;
const parentModeIndex = this.getParentModeIndex_(refreshRates);
this.addResolution_(parentModeIndex, width, height);
// For each of the refresh rates at a given resolution, add an entry
// to |parentModeToRefreshRateMap_|. This allows us to retrieve a
// list of all the possible refresh rates given a resolution's
// parentModeIndex.
const refreshRatesArr = Array.from(refreshRates.keys());
for (let k = 0; k < refreshRatesArr.length; k++) {
const rate = refreshRatesArr[k];
const modeIndex = refreshRates.get(rate)!;
const isInterlaced = selectedDisplay.modes[modeIndex].isInterlaced;
this.addRefreshRate_(parentModeIndex, modeIndex, rate, isInterlaced);
}
}
}
// Construct mode->parentMode map so we can get parent modes later.
for (let i = 0; i < selectedDisplay.modes.length; i++) {
const mode = selectedDisplay.modes[i];
const parentModeIndex =
this.getParentModeIndex_(modes.get(mode.width)!.get(mode.height)!);
this.modeToParentModeMap_.set(i, parentModeIndex);
}
assert(this.modeToParentModeMap_.size === selectedDisplay.modes.length);
// Use the new sort order.
this.sortResolutionList_();
}
/**
* Picks the appropriate parent mode from a refresh rate -> mode index map.
* Currently this chooses the mode with the highest refresh rate.
* @param refreshRates each possible refresh rate
* mapped to the corresponding mode index.
*/
private getParentModeIndex_(refreshRates: Map<number, number>): number {
const maxRefreshRate = Math.max(...refreshRates.keys());
// maxRefreshRate always exists as a key
return refreshRates.get(maxRefreshRate)!;
}
/**
* Adds a an entry in |displayModeList_| for the resolution represented by
* |width| and |height| and possible |refreshRates|.
*/
private addResolution_(
parentModeIndex: number, width: number, height: number): void {
assert(this.listAllDisplayModes_);
// Add an entry in the outer map for |parentModeIndex|. The inner
// array (the value at |parentModeIndex|) will be populated with all
// possible refresh rates for the given resolution.
this.parentModeToRefreshRateMap_.set(parentModeIndex, []);
const resolutionOption =
this.i18n('displayResolutionOnlyMenuItem', width, height);
// Only store one entry in the |resolutionList| per resolution,
// mapping it to the parentModeIndex for that resolution.
this.push('displayModeList_', {
name: resolutionOption,
value: parentModeIndex,
});
}
/**
* Adds a an entry in |parentModeToRefreshRateMap_| for the refresh rate
* represented by |rate|.
*/
private addRefreshRate_(
parentModeIndex: number, modeIndex: number, rate: number,
isInterlaced?: boolean): void {
assert(this.listAllDisplayModes_);
// Truncate at two decimal places for display. If the refresh rate
// is a whole number, remove the mantissa.
let refreshRate = Number(rate).toFixed(2);
if (refreshRate.endsWith('.00')) {
refreshRate = refreshRate.substring(0, refreshRate.length - 3);
}
const id = isInterlaced ? 'displayRefreshRateInterlacedMenuItem' :
'displayRefreshRateMenuItem';
const refreshRateOption = this.i18n(id, refreshRate.toString());
this.parentModeToRefreshRateMap_.get(parentModeIndex)!.push({
name: refreshRateOption,
value: modeIndex,
});
}
/**
* Sorts |displayModeList_| in descending order. First order sort is width,
* second order sort is height.
*/
private sortResolutionList_(): void {
const getWidthFromResolutionString = (str: string): number => {
return Number(str.substr(0, str.indexOf(' ')));
};
this.displayModeList_ =
this.displayModeList_
.sort((first, second) => {
return getWidthFromResolutionString(first.name) -
getWidthFromResolutionString(second.name);
})
.reverse();
}
/**
* Parses display modes for |selectedDisplay|. A 'mode' is a resolution +
* refresh rate combo. If |listAllDisplayModes_| is on, resolution and
* refresh rate are parsed into separate dropdowns and
* |parentModeToRefreshRateMap_| + |modeToParentModeMap_| are populated.
*/
private updateDisplayModeStructures_(selectedDisplay: DisplayUnitInfo): void {
if (this.listAllDisplayModes_) {
this.parseSplitDisplayModes_(selectedDisplay);
} else {
this.parseCompoundDisplayModes_(selectedDisplay);
}
}
/**
* Returns a value from |zoomValues_| that is closest to the display zoom
* percentage currently selected for the |selectedDisplay|.
*/
private getSelectedDisplayZoom_(selectedDisplay: DisplayUnitInfo): number {
const selectedZoom = selectedDisplay.displayZoomFactor;
let closestMatch = this.zoomValues_[0].value;
let minimumDiff = Math.abs(closestMatch - selectedZoom);
for (let i = 0; i < this.zoomValues_.length; i++) {
const currentDiff = Math.abs(this.zoomValues_[i].value - selectedZoom);
if (currentDiff < minimumDiff) {
closestMatch = this.zoomValues_[i].value;
minimumDiff = currentDiff;
}
}
return closestMatch;
}
/**
* Given the display with the current display mode, this function lists all
* the display zoom values and their labels to be used by the slider.
*/
private getZoomValues_(selectedDisplay: DisplayUnitInfo): SliderTick[] {
return selectedDisplay.availableDisplayZoomFactors.map(value => {
const ariaValue = Math.round(value * 100);
return {
value,
ariaValue,
label: this.i18n('displayZoomValue', ariaValue.toString()),
};
});
}
/**
* We need to call this explicitly rather than relying on change events
* so that we can control the update order.
*/
private setSelectedDisplay_(selectedDisplay: DisplayUnitInfo): void {
// |modeValues_| controls the resolution slider's tick values. Changing it
// might trigger a change in the |selectedModePref_.value| if the number
// of modes differs and the current mode index is out of range of the new
// modes indices. Thus, we need to set |currentSelectedModeIndex_| to -1
// to indicate that the |selectedDisplay| and |selectedModePref_.value|
// are out of sync, and therefore getResolutionText_() and
// onSelectedModeChange_() will be no-ops.
this.currentSelectedModeIndex_ = -1;
this.currentSelectedParentModeIndex_ = -1;
const numModes = selectedDisplay.modes.length;
this.modeValues_ = numModes === 0 ? [] : Array.from(Array(numModes).keys());
// Note that the display zoom values has the same number of ticks for all
// displays, so the above problem doesn't apply here.
this.zoomValues_ = this.getZoomValues_(selectedDisplay);
this.set(
'selectedZoomPref_.value',
this.getSelectedDisplayZoom_(selectedDisplay));
this.updateDisplayModeStructures_(selectedDisplay);
// Set |selectedDisplay| first since only the resolution slider depends
// on |selectedModePref_|.
this.selectedDisplay = selectedDisplay;
this.selectedTab_ = this.displays.indexOf(this.selectedDisplay);
const currentModeIndex = this.getSelectedModeIndex_(selectedDisplay);
this.currentSelectedModeIndex_ = currentModeIndex;
// This will also cause the parent mode to be updated.
this.set('selectedModePref_.value', this.currentSelectedModeIndex_);
if (this.listAllDisplayModes_) {
// Now that everything is in sync, set the selected mode to its correct
// value right before updating the pref.
this.currentSelectedParentModeIndex_ =
this.modeToParentModeMap_.get(currentModeIndex)!;
this.refreshRateList_ = this.parentModeToRefreshRateMap_.get(
this.currentSelectedParentModeIndex_)!;
} else {
this.currentSelectedParentModeIndex_ = currentModeIndex;
}
this.set(
'selectedParentModePref_.value', this.currentSelectedParentModeIndex_);
this.updateLogicalResolutionText_(this.selectedZoomPref_.value);
}
/**
* Returns true if the resolution setting needs to be displayed.
*/
private showDropDownResolutionSetting_(display: DisplayUnitInfo): boolean {
return !display.isInternal;
}
/**
* Returns true if the refresh rate setting needs to be displayed.
*/
private showRefreshRateSetting_(display: DisplayUnitInfo): boolean {
return this.listAllDisplayModes_ &&
this.showDropDownResolutionSetting_(display);
}
/**
* Returns true if external touch devices are connected and the current
* display is not an internal display. If the feature is not enabled via the
* switch, this will return false.
* @param display Display being checked for touch support.
*/
private showTouchCalibrationSetting_(display: DisplayUnitInfo): boolean {
return !display.isInternal &&
loadTimeData.getBoolean('enableTouchCalibrationSetting');
}
/**
* Returns true if external touch devices are connected a
*/
private showTouchRemappingExperience_(): boolean {
return loadTimeData.getBoolean('enableTouchscreenMappingExperience');
}
/**
* Returns true if the overscan setting should be shown for |display|.
*/
private showOverscanSetting_(display: DisplayUnitInfo): boolean {
return !display.isInternal;
}
/**
* Returns true if display brightness controls should be shown for |display|.
*/
private showBrightnessControls_(display: DisplayUnitInfo): boolean {
return isDisplayBrightnessControlInSettingsEnabled() && display.isInternal;
}
/**
* Returns true if the auto-brightness toggle should be shown.
*/
private showAutoBrightnessToggle_(): boolean {
return isDisplayBrightnessControlInSettingsEnabled() &&
this.hasAmbientLightSensor_;
}
/**
* Returns true if the ambient color setting should be shown for |display|.
*/
showAmbientColorSetting(
ambientColorAvailable: boolean, display: DisplayUnitInfo): boolean {
return ambientColorAvailable && display && display.isInternal;
}
private hasMultipleDisplays_(): boolean {
return this.displays.length > 1;
}
/**
* Returns false if the display select menu has to be hidden.
*/
private showDisplaySelectMenu_(
displays: DisplayUnitInfo[], selectedDisplay: DisplayUnitInfo): boolean {
if (selectedDisplay) {
return displays.length > 1 && !selectedDisplay.isPrimary;
}
return false;
}
/**
* Returns the select menu index indicating whether the display currently is
* primary or extended.
* @return Returns 0 if the display is primary else returns 1.
*/
private getDisplaySelectMenuIndex_(
selectedDisplay: DisplayUnitInfo, primaryDisplayId: string): number {
if (selectedDisplay && selectedDisplay.id === primaryDisplayId) {
return 0;
}
return 1;
}
/**
* Returns the i18n string for the text to be used for mirroring settings.
* @return i18n string for mirroring settings text.
*/
private getDisplayMirrorText_(displays: DisplayUnitInfo[]): string {
return this.i18n('displayMirror', displays[0].name);
}
showUnifiedDesktop(
unifiedDesktopAvailable: boolean, unifiedDesktopMode: boolean,
displays: DisplayUnitInfo[], isTabletMode: boolean): boolean {
if (displays === undefined) {
return false;
}
// Unified desktop is not supported in tablet mode.
return unifiedDesktopMode ||
(unifiedDesktopAvailable && displays.length > 1 &&
!this.isMirrored(displays) && !isTabletMode);
}
private getUnifiedDesktopText_(unifiedDesktopMode: boolean): string {
return this.i18n(
unifiedDesktopMode ? 'displayUnifiedDesktopOn' :
'displayUnifiedDesktopOff');
}
showMirror(unifiedDesktopMode: boolean, displays: DisplayUnitInfo[]):
boolean {
if (displays === undefined) {
return false;
}
return this.isMirrored(displays) ||
(!unifiedDesktopMode && displays.length > 1);
}
isMirrored(displays: DisplayUnitInfo[]): boolean {
return displays !== undefined && displays.length > 0 &&
!!displays[0].mirroringSourceId;
}
private isSelected_(
display: DisplayUnitInfo, selectedDisplay: DisplayUnitInfo): boolean {
return display.id === selectedDisplay.id;
}
private enableSetResolution_(selectedDisplay: DisplayUnitInfo): boolean {
return selectedDisplay.modes.length > 1;
}
private enableDisplayZoomSlider_(selectedDisplay: DisplayUnitInfo): boolean {
return selectedDisplay.availableDisplayZoomFactors.length > 1;
}
/**
* Returns true if the given mode is the best mode for the
* |selectedDisplay|.
*/
private isBestMode_(selectedDisplay: DisplayUnitInfo, mode: DisplayMode):
boolean {
if (!selectedDisplay.isInternal) {
return mode.isNative;
}
// Things work differently for full HD devices(1080p). The best mode is
// the one with 1.25 device scale factor and 0.8 ui scale.
if (mode.heightInNativePixels === 1080) {
return Math.abs(mode.uiScale! - 0.8) < 0.001 &&
Math.abs(mode.deviceScaleFactor - 1.25) < 0.001;
}
return mode.uiScale === 1.0;
}
private getResolutionText_(): string {
assertExists(this.selectedDisplay);
if (this.selectedDisplay.modes.length === 0 ||
this.currentSelectedModeIndex_ === -1) {
// If currentSelectedModeIndex_ is -1, selectedDisplay and
// |selectedModePref_.value| are not in sync.
return this.i18n(
'displayResolutionText', this.selectedDisplay.bounds.width.toString(),
this.selectedDisplay.bounds.height.toString());
}
const mode =
castExists(this.selectedDisplay.modes[this.selectedModePref_.value]);
const widthStr = mode.width.toString();
const heightStr = mode.height.toString();
if (this.isBestMode_(this.selectedDisplay, mode)) {
return this.i18n('displayResolutionTextBest', widthStr, heightStr);
} else if (mode.isNative) {
return this.i18n('displayResolutionTextNative', widthStr, heightStr);
}
return this.i18n('displayResolutionText', widthStr, heightStr);
}
/**
* Updates the logical resolution text to be used for the display size
* section
* @param zoomFactor Current zoom factor applied on the selected display.
*/
private updateLogicalResolutionText_(zoomFactor: number): void {
assertExists(this.selectedDisplay);
if (!this.selectedDisplay.isInternal) {
this.logicalResolutionText_ = '';
return;
}
const mode = this.selectedDisplay.modes[this.currentSelectedModeIndex_];
const deviceScaleFactor = mode.deviceScaleFactor;
const inverseZoomFactor = 1.0 / zoomFactor;
let logicalResolutionStrId = 'displayZoomLogicalResolutionText';
if (Math.abs(deviceScaleFactor - inverseZoomFactor) < 0.001) {
logicalResolutionStrId = 'displayZoomNativeLogicalResolutionNativeText';
} else if (Math.abs(inverseZoomFactor - 1.0) < 0.001) {
logicalResolutionStrId = 'displayZoomLogicalResolutionDefaultText';
}
let widthStr =
Math.round(mode.widthInNativePixels / (deviceScaleFactor * zoomFactor))
.toString();
let heightStr =
Math.round(mode.heightInNativePixels / (deviceScaleFactor * zoomFactor))
.toString();
if (this.shouldSwapLogicalResolutionText_()) {
const temp = widthStr;
widthStr = heightStr;
heightStr = temp;
}
this.logicalResolutionText_ =
this.i18n(logicalResolutionStrId, widthStr, heightStr);
}
/**
* Determines whether width and height should be swapped in the
* Logical Resolution Text. Returns true if the longer edge of the
* display's native pixels is different than the longer edge of the
* display's current bounds.
*/
private shouldSwapLogicalResolutionText_(): boolean {
assertExists(this.selectedDisplay);
const mode = this.selectedDisplay.modes[this.currentSelectedModeIndex_];
const bounds = this.selectedDisplay.bounds;
return bounds.width > bounds.height !==
mode.widthInNativePixels > mode.heightInNativePixels;
}
/**
* Handles the event where the display size slider is being dragged, i.e.
* the mouse or tap has not been released.
*/
private onDisplaySizeSliderDrag_(): void {
if (!this.selectedDisplay) {
return;
}
const slider = castExists(
this.$.displaySizeSlider.shadowRoot!.querySelector<CrSliderElement>(
'#slider'));
const zoomFactor =
(this.$.displaySizeSlider.ticks as SliderTick[])[slider.value].value;
this.updateLogicalResolutionText_(zoomFactor);
}
/**
* @param e |e.detail| is the id of the selected display.
*/
private onSelectDisplay_(e: CustomEvent<string>): void {
const id = e.detail;
for (let i = 0; i < this.displays.length; ++i) {
const display = this.displays[i];
if (id === display.id) {
if (this.selectedDisplay !== display) {
this.setSelectedDisplay_(display);
}
return;
}
}
}
private onSelectDisplayTab_(): void {
const {selected} = castExists(this.shadowRoot!.querySelector('cr-tabs'));
if (this.selectedTab_ !== selected) {
this.setSelectedDisplay_(this.displays[selected]);
}
}
/**
* Handles event when a touch calibration option is selected.
*/
private onTouchCalibrationClick_(): void {
getDisplayApi().showNativeTouchCalibration(this.selectedDisplay!.id);
}
private onTouchMappingClick_(): void {
this.displaySettingsProvider.startNativeTouchscreenMappingExperience();
}
/**
* Handles the event when an option from display select menu is selected.
*/
private updatePrimaryDisplay_(e: Event): void {
if (!this.selectedDisplay) {
return;
}
if (this.selectedDisplay.id === this.primaryDisplayId) {
return;
}
if (!(e.target as HTMLSelectElement).value) {
return;
}
const properties: DisplayProperties = {
isPrimary: true,
};
getDisplayApi()
.setDisplayProperties(this.selectedDisplay.id, properties)
.then(() => this.setPropertiesCallback_());
this.displaySettingsProvider.recordChangingDisplaySettings(
DisplaySettingsType.kPrimaryDisplay, createDisplayValue({}));
}
/**
* Handles the event when the display brightness slider changes value.
*/
private onDisplayBrightnessSliderChanged_(): void {
if (!isDisplayBrightnessControlInSettingsEnabled()) {
return;
}
const brightnessSliderValue =
strictQuery('#brightnessSlider', this.shadowRoot, CrSliderElement)
.value;
// Clamp the brightness value between 5 and 100 inclusive.
const newBrightness = Math.max(
this.brightnessSliderMin_,
Math.min(brightnessSliderValue, this.brightnessSliderMax_));
this.displaySettingsProvider.setInternalDisplayScreenBrightness(
newBrightness);
}
/**
* Handles the event when the auto-brightness toggle changes value.
*/
private onAutoBrightnessToggleChange_(): void {
if (!isDisplayBrightnessControlInSettingsEnabled()) {
return;
}
const isAutoBrightnessToggleChecked: boolean =
strictQuery('#autoBrightnessToggle', this.shadowRoot, CrToggleElement)
.checked;
this.displaySettingsProvider.setInternalDisplayAmbientLightSensorEnabled(
isAutoBrightnessToggleChecked);
}
private onAutoBrightnessToggleRowClicked_(): void {
const autoBrightnessToggle =
strictQuery('#autoBrightnessToggle', this.shadowRoot, CrToggleElement);
autoBrightnessToggle.checked = !autoBrightnessToggle.checked;
this.onAutoBrightnessToggleChange_();
}
/**
* Handles a change in the |selectedParentModePref| value triggered via the
* observer.
*/
private onSelectedParentModeChange_(newModeIndex: number): void {
if (this.currentSelectedParentModeIndex_ === newModeIndex) {
return;
}
if (!this.hasNewParentModeBeenSet()) {
// Don't change the selected display mode until we have received an
// update from Chrome and the mode differs from the current mode.
return;
}
// Reset |selectedModePref| to the parentMode.
this.set('selectedModePref_.value', this.selectedParentModePref_.value);
}
/**
* Returns True if a new parentMode has been set and we have received an
* update from Chrome.
*/
private hasNewParentModeBeenSet(): boolean {
if (this.currentSelectedParentModeIndex_ === -1) {
return false;
}
return this.currentSelectedParentModeIndex_ !==
this.selectedParentModePref_.value;
}
/**
* Returns True if a new mode has been set and we have received an update
* from Chrome.
*/
private hasNewModeBeenSet(): boolean {
if (this.currentSelectedModeIndex_ === -1) {
return false;
}
if (this.currentSelectedParentModeIndex_ !==
this.selectedParentModePref_.value) {
return true;
}
return this.currentSelectedModeIndex_ !== this.selectedModePref_.value;
}
/**
* Handles a change in |selectedModePref| triggered via the observer.
*/
private onSelectedModeChange_(newModeIndex: number): void {
// We want to ignore all value changes to the pref due to the slider being
// dragged. See http://crbug/845712 for more info.
if (this.currentSelectedModeIndex_ === newModeIndex) {
return;
}
if (!this.hasNewModeBeenSet()) {
// Don't change the selected display mode until we have received an
// update from Chrome and the mode differs from the current mode.
return;
}
assertExists(this.selectedDisplay);
const properties: DisplayProperties = {
displayMode: this.selectedDisplay.modes[this.selectedModePref_.value],
};
this.refreshRateList_ = castExists(this.parentModeToRefreshRateMap_.get(
this.selectedParentModePref_.value));
getDisplayApi()
.setDisplayProperties(this.selectedDisplay.id, properties)
.then(() => this.setPropertiesCallback_());
// Compare new mode and current mode to find out if user has changed the
// resolution or just the refresh rate.
const currentMode =
this.selectedDisplay.modes[this.currentSelectedModeIndex_];
const newMode = this.selectedDisplay.modes[this.selectedModePref_.value];
const displaySettingsType = (currentMode.height === newMode.height &&
currentMode.width === newMode.width) ?
DisplaySettingsType.kRefreshRate :
DisplaySettingsType.kResolution;
this.displaySettingsProvider.recordChangingDisplaySettings(
displaySettingsType, createDisplayValue({
isInternalDisplay: this.selectedDisplay.isInternal,
displayId: BigInt(this.selectedDisplay.id),
}));
}
/**
* Triggerend when the display size slider changes its value. This only
* occurs when the value is committed (i.e. not while the slider is being
* dragged).
*/
private onSelectedZoomChange_(): void {
if (this.currentSelectedModeIndex_ === -1 || !this.selectedDisplay) {
return;
}
const properties: DisplayProperties = {
displayZoomFactor: this.selectedZoomPref_.value,
};
getDisplayApi()
.setDisplayProperties(this.selectedDisplay.id, properties)
.then(() => this.setPropertiesCallback_());
this.displaySettingsProvider.recordChangingDisplaySettings(
DisplaySettingsType.kScaling, createDisplayValue({
isInternalDisplay: this.selectedDisplay.isInternal,
displayId: BigInt(this.selectedDisplay.id),
}));
}
/**
* Returns whether the option "Auto-rotate" is one of the shown options in
* the rotation drop-down menu.
*/
private showAutoRotateOption_(selectedDisplay: DisplayUnitInfo): boolean
|undefined {
return selectedDisplay.isAutoRotationAllowed;
}
private onOrientationChange_(event: Event): void {
const select = cast(event.target, HTMLSelectElement);
const value = parseInt(select.value, 10);
assertExists(this.selectedDisplay);
assert(value !== -1 || this.selectedDisplay.isAutoRotationAllowed);
const properties: DisplayProperties = {
rotation: value,
};
getDisplayApi()
.setDisplayProperties(this.selectedDisplay.id, properties)
.then(() => this.setPropertiesCallback_());
let orientation = DisplaySettingsOrientationOption.k0Degree;
if (value === -1) {
orientation = DisplaySettingsOrientationOption.kAuto;
} else if (value === 90) {
orientation = DisplaySettingsOrientationOption.k90Degree;
} else if (value === 180) {
orientation = DisplaySettingsOrientationOption.k180Degree;
} else if (value === 270) {
orientation = DisplaySettingsOrientationOption.k270Degree;
}
this.displaySettingsProvider.recordChangingDisplaySettings(
DisplaySettingsType.kOrientation,
createDisplayValue(
{isInternalDisplay: this.selectedDisplay.isInternal, orientation}));
}
private onMirroredClick_(event: Event): void {
// Blur the control so that when the transition animation completes and
// the UI is focused, the control does not receive focus. crbug.com/785070
(event.currentTarget as CrCheckboxElement).blur();
const mirrorModeInfo: MirrorModeInfo = {
mode: this.isMirrored(this.displays) ? MirrorMode.OFF : MirrorMode.NORMAL,
};
getDisplayApi().setMirrorMode(mirrorModeInfo).then(() => {
const error = chrome.runtime.lastError;
if (error) {
console.error('setMirrorMode Error: ' + error.message);
}
});
this.displaySettingsProvider.recordChangingDisplaySettings(
DisplaySettingsType.kMirrorMode, /*value=*/ createDisplayValue({
mirrorModeStatus: mirrorModeInfo.mode === MirrorMode.NORMAL,
}));
}
private onUnifiedDesktopClick_(): void {
const properties: DisplayProperties = {
isUnified: !this.unifiedDesktopMode_,
};
getDisplayApi()
.setDisplayProperties(this.primaryDisplayId, properties)
.then(() => this.setPropertiesCallback_());
const unified =
properties.isUnified === undefined ? null : properties.isUnified;
this.displaySettingsProvider.recordChangingDisplaySettings(
DisplaySettingsType.kUnifiedMode,
createDisplayValue({unifiedModeStatus: unified}));
}
private onOverscanClick_(e: Event): void {
e.preventDefault();
assert(this.selectedDisplay);
this.overscanDisplayId = this.selectedDisplay.id;
this.showOverscanDialog_(true);
this.displaySettingsProvider.recordChangingDisplaySettings(
DisplaySettingsType.kOverscan,
createDisplayValue(
{isInternalDisplay: this.selectedDisplay.isInternal}));
}
private onCloseOverscanDialog_(): void {
focusWithoutInk(castExists(this.shadowRoot!.getElementById('overscan')));
}
private updateDisplayInfo_(): void {
let displayIds = '';
let primaryDisplay: DisplayUnitInfo|undefined = undefined;
let selectedDisplay: DisplayUnitInfo|undefined = undefined;
for (let i = 0; i < this.displays.length; ++i) {
const display = this.displays[i];
if (displayIds) {
displayIds += ',';
}
displayIds += display.id;
if (display.isPrimary && !primaryDisplay) {
primaryDisplay = display;
}
if (this.selectedDisplay && display.id === this.selectedDisplay.id) {
selectedDisplay = display;
}
}
this.displayIds = displayIds;
this.primaryDisplayId = (primaryDisplay && primaryDisplay.id) || '';
selectedDisplay = selectedDisplay || primaryDisplay ||
(this.displays && this.displays[0]);
this.setSelectedDisplay_(selectedDisplay);
this.unifiedDesktopMode_ = !!primaryDisplay && primaryDisplay.isUnified;
// Check if we have yet to focus a deep-linked element.
if (!this.pendingSettingId_) {
return;
}
this.showDeepLink(this.pendingSettingId_).then(result => {
if (result.deepLinkShown) {
this.pendingSettingId_ = null;
}
});
}
private setPropertiesCallback_(): void {
if (chrome.runtime.lastError) {
console.error(
'setDisplayProperties Error: ' + chrome.runtime.lastError.message);
}
}
shouldShowArrangementSection(): boolean {
if (!this.displays) {
return false;
}
return this.hasMultipleDisplays_() || this.isMirrored(this.displays);
}
private onDisplaysChanged_(): void {
flush();
const displayLayout = this.shadowRoot!.querySelector('display-layout');
if (displayLayout) {
displayLayout.updateDisplays(
this.displays, this.layouts, this.mirroringDestinationIds);
}
}
private toggleDisplayPerformanceEnabled_(): void {
this.isDisplayPerformanceEnabled_ = !this.isDisplayPerformanceEnabled_;
this.displaySettingsProvider.setShinyPerformance(
this.isDisplayPerformanceEnabled_);
}
getInvalidDisplayId(): string {
return this.invalidDisplayId_;
}
getRefreshRateList(): DropdownMenuOptionList {
return this.refreshRateList_;
}
getModeToParentModeMap(): Map<number, number> {
return this.modeToParentModeMap_;
}
getParentModeToRefreshRateMap(): Map<number, DropdownMenuOptionList> {
return this.parentModeToRefreshRateMap_;
}
getSelectedZoomPref(): chrome.settingsPrivate.PrefObject {
return this.selectedZoomPref_;
}
}
declare global {
interface HTMLElementTagNameMap {
'settings-display': SettingsDisplayElement;
}
}
customElements.define(SettingsDisplayElement.is, SettingsDisplayElement);