diff options
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/WlrOutputService.qml')
| -rw-r--r-- | raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/WlrOutputService.qml | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/WlrOutputService.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/WlrOutputService.qml new file mode 100644 index 0000000..655a485 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/WlrOutputService.qml @@ -0,0 +1,346 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell + +Singleton { + id: root + + 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; + console.info("WlrOutputService: 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) { + console.warn("WlrOutputService: Received empty outputs list"); + } else { + console.log("WlrOutputService: Updated with", outputs.length, "outputs, serial:", serial); + outputs.forEach((output, index) => { + console.log("WlrOutputService: 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 getEnabledOutputs() { + return outputs.filter(output => output.enabled); + } + + function applyConfiguration(heads, callback) { + if (!DMSService.isConnected || !wlrOutputAvailable) { + if (callback) { + callback(false, "Not connected"); + } + return; + } + + console.log("WlrOutputService: Applying configuration for", heads.length, "outputs"); + heads.forEach((head, index) => { + console.log("WlrOutputService: 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; + const message = response.error || response.result?.message || ""; + + if (response.error) { + console.warn("WlrOutputService: applyConfiguration error:", response.error); + } else { + console.log("WlrOutputService: Configuration applied successfully"); + } + + configurationApplied(success, message); + if (callback) { + callback(success, message); + } + }); + } + + function testConfiguration(heads, callback) { + if (!DMSService.isConnected || !wlrOutputAvailable) { + if (callback) { + callback(false, "Not connected"); + } + return; + } + + console.log("WlrOutputService: Testing configuration for", heads.length, "outputs"); + + DMSService.sendRequest("wlroutput.testConfiguration", { + "heads": heads + }, response => { + const success = !response.error; + const message = response.error || response.result?.message || ""; + + if (response.error) { + console.warn("WlrOutputService: testConfiguration error:", response.error); + } else { + console.log("WlrOutputService: Configuration test passed"); + } + + if (callback) { + callback(success, message); + } + }); + } + + function setOutputEnabled(outputName, enabled, callback) { + const output = getOutput(outputName); + if (!output) { + console.warn("WlrOutputService: Output not found:", outputName); + if (callback) { + callback(false, "Output not found"); + } + return; + } + + const heads = [ + { + "name": outputName, + "enabled": enabled + } + ]; + + if (enabled && output.currentMode) { + heads[0].modeId = output.currentMode.id; + } + + applyConfiguration(heads, callback); + } + + function setOutputMode(outputName, modeId, callback) { + const heads = [ + { + "name": outputName, + "enabled": true, + "modeId": modeId + } + ]; + + applyConfiguration(heads, callback); + } + + function setOutputCustomMode(outputName, width, height, refresh, callback) { + const heads = [ + { + "name": outputName, + "enabled": true, + "customMode": { + "width": width, + "height": height, + "refresh": refresh + } + } + ]; + + applyConfiguration(heads, callback); + } + + function setOutputPosition(outputName, x, y, callback) { + const heads = [ + { + "name": outputName, + "enabled": true, + "position": { + "x": x, + "y": y + } + } + ]; + + applyConfiguration(heads, callback); + } + + function setOutputScale(outputName, scale, callback) { + const heads = [ + { + "name": outputName, + "enabled": true, + "scale": scale + } + ]; + + applyConfiguration(heads, callback); + } + + function setOutputTransform(outputName, transform, callback) { + const heads = [ + { + "name": outputName, + "enabled": true, + "transform": transform + } + ]; + + applyConfiguration(heads, callback); + } + + function setOutputAdaptiveSync(outputName, state, callback) { + const heads = [ + { + "name": outputName, + "enabled": true, + "adaptiveSync": state + } + ]; + + applyConfiguration(heads, callback); + } + + function configureOutput(config, callback) { + const heads = [config]; + applyConfiguration(heads, callback); + } + + function configureMultipleOutputs(configs, callback) { + applyConfiguration(configs, callback); + } + + // High-level apply matching the generateOutputsConfig() pattern used by + // NiriService, HyprlandService and DwlService. Instead of writing a + // config file, the changes are applied directly via the + // wlr-output-management protocol. + function applyOutputsConfig(outputsData, connectedOutputs) { + if (!wlrOutputAvailable) + return; + 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 = transformFromName(output.logical.transform); + } + } + heads.push(head); + } + + if (heads.length > 0) + applyConfiguration(heads); + } + + function transformFromName(name) { + switch (name) { + case "Normal": + return 0; + case "90": + return 1; + case "180": + return 2; + case "270": + return 3; + case "Flipped": + return 4; + case "Flipped90": + return 5; + case "Flipped180": + return 6; + case "Flipped270": + return 7; + default: + return 0; + } + } +} |