summaryrefslogtreecommitdiff
path: root/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/CompositorService.qml
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/CompositorService.qml')
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/CompositorService.qml677
1 files changed, 677 insertions, 0 deletions
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/CompositorService.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/CompositorService.qml
new file mode 100644
index 0000000..d327ea2
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/CompositorService.qml
@@ -0,0 +1,677 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import Quickshell
+import Quickshell.I3
+import Quickshell.Wayland
+import Quickshell.Hyprland
+import qs.Common
+
+Singleton {
+ id: root
+
+ property bool isHyprland: false
+ property bool isNiri: false
+ property bool isDwl: false
+ property bool isSway: false
+ property bool isScroll: false
+ property bool isMiracle: false
+ property bool isLabwc: false
+ property string compositor: "unknown"
+ readonly property bool useHyprlandFocusGrab: isHyprland && Quickshell.env("DMS_HYPRLAND_EXCLUSIVE_FOCUS") !== "1"
+
+ readonly property string hyprlandSignature: Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE")
+ readonly property string niriSocket: Quickshell.env("NIRI_SOCKET")
+ readonly property string swaySocket: Quickshell.env("SWAYSOCK")
+ readonly property string scrollSocket: Quickshell.env("SWAYSOCK")
+ readonly property string miracleSocket: Quickshell.env("MIRACLESOCK")
+ readonly property string labwcPid: Quickshell.env("LABWC_PID")
+ property bool useNiriSorting: isNiri && NiriService
+
+ property var randrScales: ({})
+ property bool randrReady: false
+ signal randrDataReady
+
+ property var sortedToplevels: []
+ property bool _sortScheduled: false
+
+ signal toplevelsChanged
+
+ function fetchRandrData() {
+ Proc.runCommand("randr", ["dms", "randr", "--json"], (output, exitCode) => {
+ if (exitCode === 0 && output) {
+ try {
+ const data = JSON.parse(output.trim());
+ if (data.outputs && Array.isArray(data.outputs)) {
+ const scales = {};
+ for (const out of data.outputs) {
+ if (out.name && out.scale > 0)
+ scales[out.name] = out.scale;
+ }
+ randrScales = scales;
+ }
+ } catch (e) {
+ console.warn("CompositorService: failed to parse randr data:", e);
+ }
+ }
+ randrReady = true;
+ randrDataReady();
+ }, 0, 3000);
+ }
+
+ function getScreenScale(screen) {
+ if (!screen)
+ return 1;
+
+ if (Quickshell.env("QT_WAYLAND_FORCE_DPI") || Quickshell.env("QT_SCALE_FACTOR")) {
+ return screen.devicePixelRatio || 1;
+ }
+
+ const randrScale = randrScales[screen.name];
+ if (randrScale !== undefined && randrScale > 0)
+ return Math.round(randrScale * 20) / 20;
+
+ if (WlrOutputService.wlrOutputAvailable && screen) {
+ const wlrOutput = WlrOutputService.getOutput(screen.name);
+ if (wlrOutput?.enabled && wlrOutput.scale !== undefined && wlrOutput.scale > 0) {
+ return Math.round(wlrOutput.scale * 20) / 20;
+ }
+ }
+
+ if (isNiri && screen) {
+ const niriScale = NiriService.displayScales[screen.name];
+ if (niriScale !== undefined)
+ return niriScale;
+ }
+
+ if (isHyprland && screen) {
+ const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === screen.name);
+ if (hyprlandMonitor?.scale !== undefined)
+ return hyprlandMonitor.scale;
+ }
+
+ if (isDwl && screen) {
+ const dwlScale = DwlService.getOutputScale(screen.name);
+ if (dwlScale !== undefined && dwlScale > 0)
+ return dwlScale;
+ }
+
+ return screen?.devicePixelRatio || 1;
+ }
+
+ function getFocusedScreen() {
+ let screenName = "";
+ if (isHyprland && Hyprland.focusedWorkspace?.monitor)
+ screenName = Hyprland.focusedWorkspace.monitor.name;
+ else if (isNiri && NiriService.currentOutput)
+ screenName = NiriService.currentOutput;
+ else if (isSway || isScroll || isMiracle) {
+ const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
+ screenName = focusedWs?.monitor?.name || "";
+ } else if (isDwl && DwlService.activeOutput)
+ screenName = DwlService.activeOutput;
+
+ if (!screenName)
+ return Quickshell.screens.length > 0 ? Quickshell.screens[0] : null;
+
+ for (let i = 0; i < Quickshell.screens.length; i++) {
+ if (Quickshell.screens[i].name === screenName)
+ return Quickshell.screens[i];
+ }
+ return Quickshell.screens.length > 0 ? Quickshell.screens[0] : null;
+ }
+
+ Timer {
+ id: sortDebounceTimer
+ interval: 100
+ repeat: false
+ onTriggered: {
+ _sortScheduled = false;
+ sortedToplevels = computeSortedToplevels();
+ toplevelsChanged();
+ }
+ }
+
+ function scheduleSort() {
+ if (_sortScheduled)
+ return;
+ _sortScheduled = true;
+ sortDebounceTimer.restart();
+ }
+
+ Connections {
+ target: ToplevelManager.toplevels
+ function onValuesChanged() {
+ root.scheduleSort();
+ }
+ }
+ Connections {
+ target: isHyprland ? Hyprland : null
+ enabled: isHyprland
+
+ function onRawEvent(event) {
+ if (event.name === "openwindow" || event.name === "closewindow" || event.name === "movewindow" || event.name === "movewindowv2" || event.name === "workspace" || event.name === "workspacev2" || event.name === "focusedmon" || event.name === "focusedmonv2" || event.name === "activewindow" || event.name === "activewindowv2" || event.name === "changefloatingmode" || event.name === "fullscreen" || event.name === "moveintogroup" || event.name === "moveoutofgroup") {
+ try {
+ Hyprland.refreshToplevels();
+ } catch (e) {}
+ root.scheduleSort();
+ }
+ }
+ }
+ Connections {
+ target: NiriService
+ function onWindowsChanged() {
+ root.scheduleSort();
+ }
+ }
+
+ Component.onCompleted: {
+ fetchRandrData();
+ detectCompositor();
+ scheduleSort();
+ Qt.callLater(() => {
+ NiriService.generateNiriLayoutConfig();
+ HyprlandService.generateLayoutConfig();
+ DwlService.generateLayoutConfig();
+ });
+ }
+
+ Connections {
+ target: DwlService
+ function onStateChanged() {
+ if (isDwl && !isHyprland && !isNiri) {
+ scheduleSort();
+ }
+ }
+ }
+
+ function computeSortedToplevels() {
+ if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values)
+ return [];
+
+ if (useNiriSorting)
+ return NiriService.sortToplevels(ToplevelManager.toplevels.values);
+
+ if (isHyprland)
+ return sortHyprlandToplevelsSafe();
+
+ return Array.from(ToplevelManager.toplevels.values);
+ }
+
+ function _get(o, path, fallback) {
+ try {
+ let v = o;
+ for (let i = 0; i < path.length; i++) {
+ if (v === null || v === undefined)
+ return fallback;
+ v = v[path[i]];
+ }
+ return (v === undefined || v === null) ? fallback : v;
+ } catch (e) {
+ return fallback;
+ }
+ }
+
+ function sortHyprlandToplevelsSafe() {
+ if (!Hyprland.toplevels || !Hyprland.toplevels.values)
+ return [];
+
+ const items = Array.from(Hyprland.toplevels.values);
+
+ function _get(o, path, fb) {
+ try {
+ let v = o;
+ for (let k of path) {
+ if (v == null)
+ return fb;
+ v = v[k];
+ }
+ return (v == null) ? fb : v;
+ } catch (e) {
+ return fb;
+ }
+ }
+
+ let snap = [];
+ for (let i = 0; i < items.length; i++) {
+ const t = items[i];
+ if (!t)
+ continue;
+ const addr = t.address || "";
+ if (!addr)
+ continue;
+ const li = t.lastIpcObject || null;
+
+ const monName = _get(li, ["monitor"], null) ?? _get(t, ["monitor", "name"], "");
+ const monX = _get(t, ["monitor", "x"], Number.MAX_SAFE_INTEGER);
+ const monY = _get(t, ["monitor", "y"], Number.MAX_SAFE_INTEGER);
+
+ const wsId = _get(li, ["workspace", "id"], null) ?? _get(t, ["workspace", "id"], Number.MAX_SAFE_INTEGER);
+
+ const at = _get(li, ["at"], null);
+ let atX = (at !== null && at !== undefined && typeof at[0] === "number") ? at[0] : 1e9;
+ let atY = (at !== null && at !== undefined && typeof at[1] === "number") ? at[1] : 1e9;
+
+ const relX = Number.isFinite(monX) ? (atX - monX) : atX;
+ const relY = Number.isFinite(monY) ? (atY - monY) : atY;
+
+ snap.push({
+ monKey: String(monName),
+ monOrderX: Number.isFinite(monX) ? monX : Number.MAX_SAFE_INTEGER,
+ monOrderY: Number.isFinite(monY) ? monY : Number.MAX_SAFE_INTEGER,
+ wsId: (typeof wsId === "number") ? wsId : Number.MAX_SAFE_INTEGER,
+ x: relX,
+ y: relY,
+ title: t.title || "",
+ address: addr,
+ wayland: t.wayland
+ });
+ }
+
+ const groups = new Map();
+ for (const it of snap) {
+ const key = it.monKey + "::" + it.wsId;
+ if (!groups.has(key))
+ groups.set(key, []);
+ groups.get(key).push(it);
+ }
+
+ let groupList = [];
+ for (const [key, arr] of groups) {
+ const repr = arr[0];
+ groupList.push({
+ key,
+ monKey: repr.monKey,
+ monOrderX: repr.monOrderX,
+ monOrderY: repr.monOrderY,
+ wsId: repr.wsId,
+ items: arr
+ });
+ }
+
+ groupList.sort((a, b) => {
+ if (a.monOrderX !== b.monOrderX)
+ return a.monOrderX - b.monOrderX;
+ if (a.monOrderY !== b.monOrderY)
+ return a.monOrderY - b.monOrderY;
+ if (a.monKey !== b.monKey)
+ return a.monKey.localeCompare(b.monKey);
+ if (a.wsId !== b.wsId)
+ return a.wsId - b.wsId;
+ return 0;
+ });
+
+ const COLUMN_THRESHOLD = 48;
+ const JITTER_Y = 6;
+
+ let ordered = [];
+ for (const g of groupList) {
+ const arr = g.items;
+
+ const xs = arr.map(it => it.x).filter(x => Number.isFinite(x)).sort((a, b) => a - b);
+ let colCenters = [];
+ if (xs.length > 0) {
+ for (const x of xs) {
+ if (colCenters.length === 0) {
+ colCenters.push(x);
+ } else {
+ const last = colCenters[colCenters.length - 1];
+ if (x - last >= COLUMN_THRESHOLD) {
+ colCenters.push(x);
+ }
+ }
+ }
+ } else {
+ colCenters = [0];
+ }
+
+ for (const it of arr) {
+ let bestCol = 0;
+ let bestDist = Number.POSITIVE_INFINITY;
+ for (let ci = 0; ci < colCenters.length; ci++) {
+ const d = Math.abs(it.x - colCenters[ci]);
+ if (d < bestDist) {
+ bestDist = d;
+ bestCol = ci;
+ }
+ }
+ it._col = bestCol;
+ }
+
+ arr.sort((a, b) => {
+ if (a._col !== b._col)
+ return a._col - b._col;
+
+ const dy = a.y - b.y;
+ if (Math.abs(dy) > JITTER_Y)
+ return dy;
+
+ if (a.title !== b.title)
+ return a.title.localeCompare(b.title);
+ if (a.address !== b.address)
+ return a.address.localeCompare(b.address);
+ return 0;
+ });
+
+ ordered.push.apply(ordered, arr);
+ }
+
+ return ordered.map(x => x.wayland).filter(w => w !== null && w !== undefined);
+ }
+
+ function filterCurrentWorkspace(toplevels, screen) {
+ if (useNiriSorting)
+ return NiriService.filterCurrentWorkspace(toplevels, screen);
+ if (isHyprland)
+ return filterHyprlandCurrentWorkspaceSafe(toplevels, screen);
+ return toplevels;
+ }
+
+ function filterCurrentDisplay(toplevels, screenName) {
+ if (!toplevels || toplevels.length === 0 || !screenName)
+ return toplevels;
+ if (useNiriSorting)
+ return NiriService.filterCurrentDisplay(toplevels, screenName);
+ if (isHyprland)
+ return filterHyprlandCurrentDisplaySafe(toplevels, screenName);
+ return toplevels;
+ }
+
+ function filterHyprlandCurrentDisplaySafe(toplevels, screenName) {
+ if (!toplevels || toplevels.length === 0 || !Hyprland.toplevels)
+ return toplevels;
+
+ let monitorWindows = new Set();
+ try {
+ const hy = Array.from(Hyprland.toplevels.values);
+ for (const t of hy) {
+ const mon = _get(t, ["monitor", "name"], "");
+ if (mon === screenName && t.wayland)
+ monitorWindows.add(t.wayland);
+ }
+ } catch (e) {}
+
+ return toplevels.filter(w => monitorWindows.has(w));
+ }
+
+ function filterHyprlandCurrentWorkspaceSafe(toplevels, screenName) {
+ if (!toplevels || toplevels.length === 0 || !Hyprland.toplevels)
+ return toplevels;
+
+ let currentWorkspaceId = null;
+ try {
+ if (Hyprland.monitors) {
+ const monitor = Hyprland.monitors.values.find(m => m.name === screenName);
+ if (monitor)
+ currentWorkspaceId = _get(monitor, ["activeWorkspace", "id"], null);
+ }
+
+ if (currentWorkspaceId === null) {
+ const hy = Array.from(Hyprland.toplevels.values);
+ for (const t of hy) {
+ const mon = _get(t, ["monitor", "name"], "");
+ const wsId = _get(t, ["workspace", "id"], null);
+ const active = !!_get(t, ["activated"], false);
+ if (mon === screenName && wsId !== null) {
+ if (active) {
+ currentWorkspaceId = wsId;
+ break;
+ }
+ if (currentWorkspaceId === null)
+ currentWorkspaceId = wsId;
+ }
+ }
+ }
+
+ if (currentWorkspaceId === null && Hyprland.workspaces) {
+ const wss = Array.from(Hyprland.workspaces.values);
+ const focusedId = _get(Hyprland, ["focusedWorkspace", "id"], null);
+ for (const ws of wss) {
+ const monName = _get(ws, ["monitor", "name"], "");
+ const wsId = _get(ws, ["id"], null);
+ if (monName === screenName && wsId !== null) {
+ if (focusedId !== null && wsId === focusedId) {
+ currentWorkspaceId = wsId;
+ break;
+ }
+ if (currentWorkspaceId === null)
+ currentWorkspaceId = wsId;
+ }
+ }
+ }
+ } catch (e) {
+ console.warn("CompositorService: workspace snapshot failed:", e);
+ }
+
+ if (currentWorkspaceId === null)
+ return toplevels;
+
+ let map = new Map();
+ try {
+ const hy = Array.from(Hyprland.toplevels.values);
+ for (const t of hy) {
+ const wsId = _get(t, ["workspace", "id"], null);
+ if (t && t.wayland && wsId !== null)
+ map.set(t.wayland, wsId);
+ }
+ } catch (e) {}
+
+ return toplevels.filter(w => map.get(w) === currentWorkspaceId);
+ }
+
+ Timer {
+ id: compositorInitTimer
+ interval: 100
+ running: true
+ repeat: false
+ onTriggered: {
+ detectCompositor();
+ Qt.callLater(() => {
+ NiriService.generateNiriLayoutConfig();
+ HyprlandService.generateLayoutConfig();
+ DwlService.generateLayoutConfig();
+ });
+ }
+ }
+
+ function detectCompositor() {
+ if (hyprlandSignature && hyprlandSignature.length > 0 && !niriSocket && !swaySocket && !scrollSocket && !miracleSocket && !labwcPid) {
+ isHyprland = true;
+ isNiri = false;
+ isDwl = false;
+ isSway = false;
+ isScroll = false;
+ isMiracle = false;
+ isLabwc = false;
+ compositor = "hyprland";
+ console.info("CompositorService: Detected Hyprland");
+ return;
+ }
+
+ if (niriSocket && niriSocket.length > 0) {
+ Proc.runCommand("niriSocketCheck", ["test", "-S", niriSocket], (output, exitCode) => {
+ if (exitCode === 0) {
+ isNiri = true;
+ isHyprland = false;
+ isDwl = false;
+ isSway = false;
+ isScroll = false;
+ isMiracle = false;
+ isLabwc = false;
+ compositor = "niri";
+ console.info("CompositorService: Detected Niri with socket:", niriSocket);
+ NiriService.generateNiriBlurrule();
+ }
+ }, 0);
+ return;
+ }
+
+ if (swaySocket && swaySocket.length > 0 && !scrollSocket && scrollSocket.length == 0 && !miracleSocket) {
+ Proc.runCommand("swaySocketCheck", ["test", "-S", swaySocket], (output, exitCode) => {
+ if (exitCode === 0) {
+ isNiri = false;
+ isHyprland = false;
+ isDwl = false;
+ isSway = true;
+ isScroll = false;
+ isMiracle = false;
+ isLabwc = false;
+ compositor = "sway";
+ console.info("CompositorService: Detected Sway with socket:", swaySocket);
+ }
+ }, 0);
+ return;
+ }
+
+ if (miracleSocket && miracleSocket.length > 0) {
+ Proc.runCommand("miracleSocketCheck", ["test", "-S", miracleSocket], (output, exitCode) => {
+ if (exitCode === 0) {
+ isNiri = false;
+ isHyprland = false;
+ isDwl = false;
+ isSway = false;
+ isScroll = false;
+ isMiracle = true;
+ isLabwc = false;
+ compositor = "miracle";
+ console.info("CompositorService: Detected Miracle WM with socket:", miracleSocket);
+ }
+ }, 0);
+ return;
+ }
+
+ if (scrollSocket && scrollSocket.length > 0 && !miracleSocket) {
+ Proc.runCommand("scrollSocketCheck", ["test", "-S", scrollSocket], (output, exitCode) => {
+ if (exitCode === 0) {
+ isNiri = false;
+ isHyprland = false;
+ isDwl = false;
+ isSway = false;
+ isScroll = true;
+ isMiracle = false;
+ isLabwc = false;
+ compositor = "scroll";
+ console.info("CompositorService: Detected Scroll with socket:", scrollSocket);
+ }
+ }, 0);
+ return;
+ }
+
+ if (labwcPid && labwcPid.length > 0) {
+ isHyprland = false;
+ isNiri = false;
+ isDwl = false;
+ isSway = false;
+ isScroll = false;
+ isMiracle = false;
+ isLabwc = true;
+ compositor = "labwc";
+ console.info("CompositorService: Detected LabWC with PID:", labwcPid);
+ return;
+ }
+
+ if (DMSService.dmsAvailable) {
+ Qt.callLater(checkForDwl);
+ } else {
+ isHyprland = false;
+ isNiri = false;
+ isDwl = false;
+ isSway = false;
+ isScroll = false;
+ isMiracle = false;
+ isLabwc = false;
+ compositor = "unknown";
+ console.warn("CompositorService: No compositor detected");
+ }
+ }
+
+ Connections {
+ target: DMSService
+ function onCapabilitiesReceived() {
+ if (!isHyprland && !isNiri && !isDwl && !isLabwc) {
+ checkForDwl();
+ }
+ }
+ }
+
+ function checkForDwl() {
+ if (DMSService.apiVersion >= 12 && DMSService.capabilities.includes("dwl")) {
+ isHyprland = false;
+ isNiri = false;
+ isDwl = true;
+ isSway = false;
+ isScroll = false;
+ isMiracle = false;
+ isLabwc = false;
+ compositor = "dwl";
+ console.info("CompositorService: Detected DWL via DMS capability");
+ }
+ }
+
+ function powerOffMonitors() {
+ if (isNiri)
+ return NiriService.powerOffMonitors();
+ if (isHyprland)
+ return Hyprland.dispatch("dpms off");
+ if (isDwl)
+ return _dwlPowerOffMonitors();
+ if (isSway || isScroll || isMiracle) {
+ try {
+ I3.dispatch("output * dpms off");
+ } catch (_) {}
+ return;
+ }
+ if (isLabwc) {
+ Quickshell.execDetached(["dms", "dpms", "off"]);
+ }
+ console.warn("CompositorService: Cannot power off monitors, unknown compositor");
+ }
+
+ function powerOnMonitors() {
+ if (isNiri)
+ return NiriService.powerOnMonitors();
+ if (isHyprland)
+ return Hyprland.dispatch("dpms on");
+ if (isDwl)
+ return _dwlPowerOnMonitors();
+ if (isSway || isScroll || isMiracle) {
+ try {
+ I3.dispatch("output * dpms on");
+ } catch (_) {}
+ return;
+ }
+ if (isLabwc) {
+ Quickshell.execDetached(["dms", "dpms", "on"]);
+ }
+ console.warn("CompositorService: Cannot power on monitors, unknown compositor");
+ }
+
+ function _dwlPowerOffMonitors() {
+ if (!Quickshell.screens || Quickshell.screens.length === 0) {
+ console.warn("CompositorService: No screens available for DWL power off");
+ return;
+ }
+
+ for (let i = 0; i < Quickshell.screens.length; i++) {
+ const screen = Quickshell.screens[i];
+ if (screen && screen.name) {
+ Quickshell.execDetached(["mmsg", "-d", "disable_monitor," + screen.name]);
+ }
+ }
+ }
+
+ function _dwlPowerOnMonitors() {
+ if (!Quickshell.screens || Quickshell.screens.length === 0) {
+ console.warn("CompositorService: No screens available for DWL power on");
+ return;
+ }
+
+ for (let i = 0; i < Quickshell.screens.length; i++) {
+ const screen = Quickshell.screens[i];
+ if (screen && screen.name) {
+ Quickshell.execDetached(["mmsg", "-d", "enable_monitor," + screen.name]);
+ }
+ }
+ }
+}