WIP - Apply optimal monitor configs, if needed

This commit is contained in:
wheaney 2024-06-17 22:46:18 -07:00
parent 88e3692cca
commit 23b36b93e3
2 changed files with 157 additions and 17 deletions

View File

@ -82,8 +82,12 @@ export default class BreezyDesktopExtension extends Extension {
const is_driver_running = this._check_driver_running();
if (is_driver_running && target_monitor) {
Globals.logger.log('Driver is running, supported monitor connected. Enabling XR effect.');
this._effect_enable();
// don't enable the effect yet if an async optimal mode check is needed,
// _setup will be triggered after a mode change so we can remove this timeout_add
if (target_monitor.is_dummy || !this._monitor_manager.checkOptimalMode(target_monitor.connector)) {
Globals.logger.log('Driver is running, supported monitor connected. Enabling XR effect.');
this._effect_enable();
}
return GLib.SOURCE_REMOVE;
} else {
return GLib.SOURCE_CONTINUE;
@ -99,7 +103,8 @@ export default class BreezyDesktopExtension extends Extension {
if (target_monitor !== undefined) {
return {
monitor: this._monitor_manager.getMonitors()[target_monitor.index],
refreshRate: target_monitor.refreshRate,
connector: target_monitor.connector,
refreshRate: target_monitor.refreshRate
};
}
@ -107,7 +112,9 @@ export default class BreezyDesktopExtension extends Extension {
// allow testing XR devices with just USB, no video needed
return {
monitor: this._monitor_manager.getMonitors()[0],
connector: 'dummy',
refreshRate: 60,
is_dummy: true
};
}
@ -132,8 +139,12 @@ export default class BreezyDesktopExtension extends Extension {
this._refresh_rate = target_monitor.refreshRate;
if (this._check_driver_running()) {
Globals.logger.log('Ready, enabling XR effect');
this._effect_enable();
// don't enable the effect yet if an async optimal mode check is needed,
// _setup will be triggered again after a mode change
if (target_monitor.is_dummy || !this._monitor_manager.checkOptimalMode(target_monitor.connector)) {
Globals.logger.log('Ready, enabling XR effect');
this._effect_enable();
}
} else {
this._poll_for_ready();
}

View File

@ -52,10 +52,10 @@ export function newDisplayConfig(extPath, callback) {
);
}
export function getMonitorConfig(displayConfigProxy, callback) {
displayConfigProxy.GetResourcesRemote((result) => {
if (result.length <= 2) {
callback(null, 'Cannot get DisplayConfig: No outputs in GetResources()');
function getMonitorConfig(displayConfigProxy, callback) {
displayConfigProxy.GetResourcesRemote((result, error) => {
if (error) {
callback(null, `GetResourcesRemote failed: ${error}`);
} else {
const monitors = [];
for (let i = 0; i < result[2].length; i++) {
@ -84,6 +84,106 @@ export function getMonitorConfig(displayConfigProxy, callback) {
});
}
// triggers callback with true result if an an async monitor config change was triggered, false if no config change needed
function performOptimalModeCheck(displayConfigProxy, connectorName, callback) {
Globals.logger.log_debug(`monitormanager.js performOptimalModeCheck for ${connectorName}`);
displayConfigProxy.GetCurrentStateRemote((result, error) => {
if (error) {
callback(null, `GetCurrentState failed: ${error}`);
} else {
Globals.logger.log_debug(`monitormanager.js performOptimalModeCheck GetCurrentState result: ${JSON.stringify(result)}`);
const [serial, monitors, logicalMonitors, properties] = result;
// iterate over all monitors at least once, collecting the best fit mode for our monitor, and mode information
// for each monitor
let ourMonitor = undefined;
let monitorToModeIdMap = {};
let bestFitMode = undefined;
for (let monitor of monitors) {
const [details, modes, monProperties] = monitor;
const [connector, vendor, product, monitorSerial] = details;
const isOurMonitor = connector == connectorName;
if (isOurMonitor) ourMonitor = monitor;
for (let mode of modes) {
const [modeId, width, height, refreshRate, preferredScale, supportedScales, modeProperites] = mode;
const isCurrent = !!modeProperites['is-current'];
if (isCurrent) monitorToModeIdMap[connector] = modeId;
if (isOurMonitor && (!bestFitMode || (
width >= bestFitMode.width &&
height >= bestFitMode.height &&
refreshRate >= bestFitMode.refreshRate))) {
// find the scale that is closest to 1.0
const bestScale = supportedScales.reduce((prev, curr) => {
return Math.abs(curr - 1.0) < Math.abs(prev - 1.0) ? curr : prev;
});
bestFitMode = {
modeId,
width,
height,
refreshRate,
bestScale,
isCurrent
};
}
}
}
if (!!ourMonitor) {
let anyMonitorsChanged = false;
if (!!bestFitMode) {
// map from original logical monitors schema to a(iiduba(ssa{sv})) for ApplyMonitorsConfig call
const updatedLogicalMonitors = logicalMonitors.map((logicalMonitor) => {
const [x, y, scale, transform, primary, monitors, logMonProperties] = logicalMonitor;
const hasOurMonitor = !!monitors.some((monitor) => monitor[0] === connectorName);
anyMonitorsChanged |= hasOurMonitor && bestFitMode.bestScale !== scale;
return [
x,
y,
hasOurMonitor ? bestFitMode.bestScale : scale,
transform,
primary, // TODO - user setting should dictate if we want to set ours primary
monitors.map((monitor) => {
const monitorConnector = monitor[0];
const isOurMonitor = monitorConnector === connectorName;
anyMonitorsChanged |= isOurMonitor && !bestFitMode.isCurrent;
return [
monitorConnector,
isOurMonitor ? bestFitMode.modeId : monitorToModeIdMap[monitorConnector],
{} // properties
];
})
];
});
// if our monitor is already properly configured, we can skip the ApplyMonitorsConfig call
if (anyMonitorsChanged) {
Globals.logger.log_debug(`monitormanager.js performOptimalModeCheck updatedLogicalMonitors: ${JSON.stringify(updatedLogicalMonitors)}`);
displayConfigProxy.ApplyMonitorsConfigRemote(
serial,
1, // "temporary" config -- "permanent" might be pointless since we always do this check
updatedLogicalMonitors,
{}, // properties
(_result, error) => {
if (error) {
callback(null, `ApplyMonitorsConfig failed: ${error}`);
} else {
callback(true, null);
}
}
);
}
}
if (!anyMonitorsChanged) callback(false, null);
} else {
callback(null, `Monitor ${connectorName} not found in GetCurrentState result`);
}
}
});
}
// Monitor change handling
export default class MonitorManager {
constructor(extPath) {
@ -94,22 +194,25 @@ export default class MonitorManager {
this._backendManager = null;
this._monitorProperties = null;
this._changeHookFn = null;
this._needsConfigCheck = true;
}
enable() {
Globals.logger.log_debug('MonitorManager enable');
this._backendManager = global.backend.get_monitor_manager();
newDisplayConfig(this._extPath, (proxy, error) => {
newDisplayConfig(this._extPath, ((proxy, error) => {
if (error) {
return;
}
this._displayConfigProxy = proxy;
this._on_monitors_change();
});
}).bind(this));
this._monitorsChangedConnection = Main.layoutManager.connect('monitors-changed', this._on_monitors_change.bind(this));
}
disable() {
Globals.logger.log_debug('MonitorManager disable');
Main.layoutManager.disconnect(this._monitorsChangedConnection);
this._monitorsChangedConnection = null;
@ -123,10 +226,6 @@ export default class MonitorManager {
this._changeHookFn = fn;
}
setPostCallback(callback) {
this._postCallback = callback;
}
getMonitors() {
return Main.layoutManager.monitors;
}
@ -135,11 +234,41 @@ export default class MonitorManager {
return this._monitorProperties;
}
// returns true if a check is needed, caller should wait for the next change hook call
checkOptimalMode(monitorConnector) {
Globals.logger.log_debug(`MonitorManager checkOptimalMode: ${monitorConnector}`);
if (this._displayConfigProxy == null) {
Globals.logger.log('MonitorManager checkOptimalMode: _displayConfigProxy not set!');
return false;
}
if (this._needsConfigCheck) {
performOptimalModeCheck(this._displayConfigProxy, monitorConnector, ((configChanged, error) => {
this._needsConfigCheck = false;
if (error) {
Globals.logger.log(`Failed to switch to optimal mode for monitor ${monitorConnector}: ${error}`);
} else {
if (configChanged) {
Globals.logger.log(`Switched to optimal mode for monitor ${monitorConnector}`);
} else if (this._changeHookFn !== null) {
// no config change means this won't be triggered automatically, so trigger it manually
this._changeHookFn();
}
}
}).bind(this));
} else {
Globals.logger.log_debug('MonitorManager checkOptimalMode: skipping config check');
}
return this._needsConfigCheck;
}
_on_monitors_change() {
Globals.logger.log_debug('MonitorManager _on_monitors_change');
if (this._displayConfigProxy == null) {
return;
}
getMonitorConfig(this._displayConfigProxy, (result, error) => {
this._needsConfigCheck = true;
getMonitorConfig(this._displayConfigProxy, ((result, error) => {
if (error) {
return;
}
@ -164,6 +293,6 @@ export default class MonitorManager {
if (this._changeHookFn !== null) {
this._changeHookFn();
}
});
}).bind(this));
}
}