summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/dms/Services/WlrOutputService.qml
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-hyprland-theme/theme-data/dms/Services/WlrOutputService.qml')
-rw-r--r--raveos-hyprland-theme/theme-data/dms/Services/WlrOutputService.qml346
1 files changed, 346 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/dms/Services/WlrOutputService.qml b/raveos-hyprland-theme/theme-data/dms/Services/WlrOutputService.qml
new file mode 100644
index 0000000..655a485
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/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;
+ }
+ }
+}