Files
DankMaterialShell/quickshell/Services/WlrOutputService.qml
T
a8bb04a2b2 displays: send the vrr setting on the wlr-output path (#3701)
* displays: send the vrr setting on the wlr-output path

The toggle shown on compositors without their own output writer (sway,
labwc, ...) was dropped when the heads were built, so it never applied.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* remove test

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: bbedward <[email protected]>
2026-10-12 11:13:45 -04:00

233 lines
7.1 KiB
QML

pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.Common
import qs.Services
import "../Common/OutputModel.js" as OutputModel
Singleton {
id: root
readonly property var log: Log.scoped("WlrOutputService")
property bool wlrOutputAvailable: false
property var outputs: []
property int serial: 0
signal stateChanged
signal configurationApplied(bool success, string message)
Connections {
target: DMSService
function onCapabilitiesReceived() {
checkCapabilities();
}
function onConnectionStateChanged() {
if (DMSService.isConnected) {
checkCapabilities();
return;
}
wlrOutputAvailable = false;
}
function onWlrOutputStateUpdate(data) {
if (!wlrOutputAvailable) {
return;
}
handleStateUpdate(data);
}
}
Component.onCompleted: {
if (!DMSService.dmsAvailable) {
return;
}
checkCapabilities();
}
function checkCapabilities() {
if (!DMSService.capabilities || !Array.isArray(DMSService.capabilities)) {
wlrOutputAvailable = false;
return;
}
const hasWlrOutput = DMSService.capabilities.includes("wlroutput");
if (hasWlrOutput && !wlrOutputAvailable) {
wlrOutputAvailable = true;
log.info("wlr-output-management capability detected");
requestState();
return;
}
if (!hasWlrOutput) {
wlrOutputAvailable = false;
}
}
function requestState() {
if (!DMSService.isConnected || !wlrOutputAvailable) {
return;
}
DMSService.sendRequest("wlroutput.getState", null, response => {
if (!response.result) {
return;
}
handleStateUpdate(response.result);
});
}
function handleStateUpdate(state) {
outputs = state.outputs || [];
serial = state.serial || 0;
if (outputs.length === 0) {
log.warn("Received empty outputs list");
} else {
log.debug("Updated with", outputs.length, "outputs, serial:", serial);
outputs.forEach((output, index) => {
log.debug("Output", index, "-", output.name, "enabled:", output.enabled, "mode:", output.currentMode ? output.currentMode.width + "x" + output.currentMode.height + "@" + (output.currentMode.refresh / 1000) + "Hz" : "none");
});
}
stateChanged();
}
function getOutput(name) {
for (const output of outputs) {
if (output.name === name) {
return output;
}
}
return null;
}
function applyConfiguration(heads, callback) {
if (!DMSService.isConnected || !wlrOutputAvailable) {
if (callback) {
callback(false, I18n.tr("Not connected"));
}
return;
}
log.debug("Applying configuration for", heads.length, "outputs");
heads.forEach((head, index) => {
log.debug("Head", index, "- name:", head.name, "enabled:", head.enabled, "modeId:", head.modeId, "customMode:", JSON.stringify(head.customMode), "position:", JSON.stringify(head.position), "scale:", head.scale, "transform:", head.transform, "adaptiveSync:", head.adaptiveSync);
});
DMSService.sendRequest("wlroutput.applyConfiguration", {
"heads": heads
}, response => {
const success = !response.error && response.result?.success === true;
const message = response.error || response.result?.message || "";
if (!success) {
log.warn("applyConfiguration error:", message);
} else {
log.debug("Configuration applied successfully");
}
configurationApplied(success, message);
if (callback) {
callback(success, message);
}
}, 5000);
}
function testConfiguration(heads, callback) {
if (!DMSService.isConnected || !wlrOutputAvailable) {
if (callback) {
callback(false, I18n.tr("Not connected"));
}
return;
}
log.debug("Testing configuration for", heads.length, "outputs");
DMSService.sendRequest("wlroutput.testConfiguration", {
"heads": heads
}, response => {
const success = !response.error && response.result?.success === true;
const message = response.error || response.result?.message || "";
if (!success) {
log.warn("testConfiguration error:", message);
} else {
log.debug("Configuration test passed");
}
if (callback) {
callback(success, message);
}
}, 5000);
}
function applyOutputsConfig(outputsData, connectedOutputs, callback) {
if (!wlrOutputAvailable) {
if (callback)
callback(false, I18n.tr("Not connected"));
return;
}
const heads = outputsConfigHeads(outputsData, connectedOutputs);
if (heads.length === 0) {
if (callback)
callback(false, I18n.tr("No monitors"));
return;
}
applyConfiguration(heads, callback);
}
function outputsConfigHeads(outputsData, connectedOutputs) {
const heads = [];
for (const name in outputsData) {
if (!connectedOutputs[name])
continue;
const output = outputsData[name];
const mode = (output.modes && output.current_mode >= 0) ? output.modes[output.current_mode] : null;
const enabled = !!mode;
const head = {
"name": name,
"enabled": enabled
};
if (enabled) {
if (mode.id !== undefined)
head.modeId = mode.id;
else
head.customMode = {
"width": mode.width,
"height": mode.height,
"refresh": mode.refresh_rate
};
if (output.logical) {
head.position = {
"x": output.logical.x ?? 0,
"y": output.logical.y ?? 0
};
head.scale = output.logical.scale ?? 1.0;
head.transform = OutputModel.transformIndex(output.logical.transform);
}
// Only heads that reported adaptive sync (wlr-output-management v4) accept set_adaptive_sync.
if (output.vrr_supported)
head.adaptiveSync = output.vrr_enabled ? 1 : 0;
}
heads.push(head);
}
return heads;
}
Connections {
target: SessionService
function onSessionResumed() {
log.info("Session resumed, re-requesting output state, current outputs:", outputs.length);
requestState();
}
}
}