Files
2026-10-21 11:24:35 -04:00

2260 lines
82 KiB
QML

pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.I3
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.Common
import qs.DCommon.Common as DCommon
import qs.Services
import "../Common/WorkspaceModel.js" as WorkspaceModel
import "../Common/WindowModel.js" as WindowModel
Singleton {
id: root
readonly property var log: Log.scoped("CompositorService")
property bool isHyprland: false
property bool isNiri: false
property bool isMango: false
property bool isSway: false
property bool isScroll: false
property bool isMiracle: false
property bool isLabwc: false
property bool isAqueous: false
property bool isUmbriel: false
property bool isKwin: false
property string compositor: "unknown"
property bool compositorDetected: false
property bool outputPowerAvailable: false
readonly property bool genericPowerBackend: compositorDetected && !isNiri && !isHyprland && !isMango && !isSway && !isScroll && !isMiracle && !isLabwc && !isUmbriel
onGenericPowerBackendChanged: probeOutputPower()
readonly property bool inOverview: (isHyprland && HyprlandService.inOverview) || (isNiri && NiriService.inOverview) || (isMango && MangoService.inOverview)
function probeOutputPower() {
outputPowerAvailable = false;
if (!genericPowerBackend)
return;
const backend = compositor;
Proc.runCommand("output-power-probe", [Proc.dmsBin, "dpms", "list"], (output, code) => {
if (root.compositor === backend && root.genericPowerBackend)
root.outputPowerAvailable = code === 0;
}, 0, 5000);
}
function setOutputPower(on) {
Proc.runCommand("output-power-action", [Proc.dmsBin, "dpms", on ? "on" : "off"], (output, code) => {
if (code !== 0)
ToastService.showError(I18n.tr("Error"), I18n.tr("Failed to change display power", "Error shown when changing monitor power fails"));
}, 0, 12000);
}
Connections {
target: DMSService
function onConnectionStateChanged() {
if (DMSService.isConnected)
root.probeOutputPower();
else
root.outputPowerAvailable = false;
}
}
Connections {
target: Quickshell
function onScreensChanged() {
root.probeOutputPower();
root.refreshHyprlandMonitorLayout();
}
}
readonly property bool frameCompositorLayoutReady: (!isNiri || NiriService.frameLayoutReady) && (!isHyprland || HyprlandService.frameLayoutReady)
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 miracleSocket: Quickshell.env("MIRACLESOCK")
readonly property string labwcPid: Quickshell.env("LABWC_PID")
readonly property string mangoSignature: Quickshell.env("MANGO_INSTANCE_SIGNATURE")
property bool useNiriSorting: isNiri && NiriService
property bool useMangoSorting: isMango && MangoService
property var randrScales: ({})
property bool randrReady: false
signal randrDataReady
property var sortedToplevels: []
property var hyprlandVisibleSpecialWorkspaces: ({})
property bool _sortScheduled: false
signal toplevelsChanged
signal workspaceStateChanged
property int windowStateRevision: 0
onToplevelsChanged: windowStateRevision++
onWorkspaceStateChanged: windowStateRevision++
readonly property var _edgePositions: ({
top: SettingsData.Position.Top,
bottom: SettingsData.Position.Bottom,
left: SettingsData.Position.Left,
right: SettingsData.Position.Right
})
readonly property bool supportsMinimize: isAqueous && AqueousService.available ? AqueousService.capabilities.commands && !AqueousService.locked : DCommon.Compositor.supportsMinimize
readonly property bool hasWorkspaceIpc: {
switch (compositor) {
case "niri":
case "hyprland":
case "mango":
case "sway":
case "scroll":
case "miracle":
case "umbriel":
return true;
default:
return false;
}
}
readonly property bool ephemeralWorkspaces: isHyprland
readonly property bool windowOverlapSupported: {
switch (compositor) {
case "niri":
case "hyprland":
case "mango":
return true;
case "aqueous":
return AqueousService.available;
default:
return false;
}
}
readonly property bool workspaceReorderSupported: isNiri
readonly property bool isKnownCompositor: configKey !== ""
readonly property bool supportsWindowRules: isNiri || isHyprland || isMango
readonly property string dmsFloatingRuleId: "dms-floating-windows"
property bool dmsWindowFloatingActive: false
readonly property string dmsOpaqueRuleId: "dms-window-opaque"
readonly property bool supportsLayoutConfig: isNiri || isHyprland || isMango
readonly property bool supportsCursorConfig: isNiri || isHyprland || isMango
readonly property bool supportsDisplayConfig: isNiri || isHyprland || isMango || isAqueous
readonly property bool supportsBarAutoHideReveal: isNiri || isHyprland || isMango
readonly property bool supportsWorkspaces: isNiri || isHyprland || isMango || isAqueous || isUmbriel
// compositors where a workspace that does not exist yet is still a valid switch target
readonly property bool supportsPersistentWorkspaces: isHyprland || isMango || isSway || isScroll || isMiracle
readonly property bool supportsWorkspaceUrgency: isKnownCompositor && !isLabwc
readonly property bool supportsWorkspaceFollowFocus: isKnownCompositor && !isLabwc
// labwc root menu and desktop scroll, sway/scroll --whole-window binds, plasmashell desktop menu
readonly property bool reservesDesktopInput: isLabwc || isSway || isScroll || isKwin
readonly property bool desktopContextMenuEnabled: {
switch (SettingsData.desktopContextMenu) {
case "on":
return true;
case "off":
return false;
default:
return !reservesDesktopInput;
}
}
readonly property bool supportsSmartDock: isNiri || isHyprland || isMango || isAqueous
readonly property bool supportsNativeOverview: isNiri || isAqueous
readonly property bool supportsPointerConfig: isNiri || isHyprland || isMango
readonly property bool supportsInputConfig: isNiri || isHyprland || isMango
readonly property string displayName: {
switch (compositor) {
case "niri":
return "niri";
case "hyprland":
return "Hyprland";
case "mango":
return "MangoWC";
case "sway":
return "Sway";
case "scroll":
return "Scroll";
case "miracle":
return "Miracle WM";
case "labwc":
return "Labwc";
case "aqueous":
return "Aqueous";
case "umbriel":
return "Umbriel";
case "kwin":
return "KWin";
default:
return "";
}
}
readonly property string configKey: {
switch (compositor) {
case "niri":
case "hyprland":
case "mango":
case "sway":
case "scroll":
case "miracle":
case "labwc":
case "aqueous":
case "umbriel":
return compositor;
default:
return "";
}
}
property var _workspaceRecords: ({})
Connections {
target: AqueousService
enabled: root.isAqueous
function onStateChanged() {
root.scheduleSort();
if (AqueousService.available)
root.workspaceStateChanged();
}
}
Connections {
target: NiriService
enabled: root.isNiri
function onAllWorkspacesChanged() {
root.workspaceStateChanged();
}
function onWindowUrgentChanged() {
root.workspaceStateChanged();
}
function onWindowsChanged() {
if (NiriService.titleOnlyWindowsUpdate)
return;
root.workspaceStateChanged();
}
function onCurrentOutputChanged() {
root.workspaceStateChanged();
}
}
Connections {
target: root.isHyprland ? Hyprland.workspaces : null
function onValuesChanged() {
root.workspaceStateChanged();
}
}
Connections {
target: root.isSway || root.isScroll || root.isMiracle ? I3.workspaces : null
function onValuesChanged() {
root.workspaceStateChanged();
}
}
Binding {
target: UmbrielService
property: "active"
value: root.isUmbriel
}
Connections {
target: UmbrielService
enabled: root.isUmbriel
function onWorkspacesChanged() {
root.workspaceStateChanged();
}
function onWindowsChanged() {
root.workspaceStateChanged();
}
}
function canMinimize(toplevel) {
if (isAqueous && toplevel?.aqueousWindowId)
return AqueousService.available && !AqueousService.locked && AqueousService.capabilities.commands && toplevel.canMinimize;
return supportsMinimize && toplevel && toplevel.minimized !== undefined;
}
function closeNiriOverviewOnWindowFocus() {
if (SettingsData.closeNiriOverviewOnWindowFocus && isNiri && NiriService.inOverview)
NiriService.closeOverview();
}
function activateToplevel(toplevel) {
if (!toplevel)
return;
if (isAqueous && toplevel.aqueousWindowId) {
toplevel.activate();
return;
}
closeNiriOverviewOnWindowFocus();
if (canMinimize(toplevel) && toplevel.minimized)
toplevel.minimized = false;
toplevel.activate();
}
function toggleToplevel(toplevel) {
if (!toplevel)
return;
if (!canMinimize(toplevel)) {
activateToplevel(toplevel);
return;
}
if (toplevel.minimized) {
activateToplevel(toplevel);
return;
}
if (toplevel.activated) {
toplevel.minimized = true;
return;
}
activateToplevel(toplevel);
}
function fetchRandrData() {
Proc.runCommand("randr", [Proc.dmsBin, "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) {
log.warn("failed to parse randr data:", e);
}
}
randrReady = true;
randrDataReady();
}, 0, 3000);
}
// wlr-output reports wl_fixed (1/256 steps), surfaces render at fractional-scale-v1 N/120
function fractionalScale(fixedScale) {
return Math.round(fixedScale * 120) / 120;
}
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 fractionalScale(randrScale);
if (WlrOutputService.wlrOutputAvailable && screen) {
const wlrOutput = WlrOutputService.getOutput(screen.name);
if (wlrOutput?.enabled && wlrOutput.scale !== undefined && wlrOutput.scale > 0) {
return fractionalScale(wlrOutput.scale);
}
}
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 (isMango && screen) {
const mangoScale = MangoService.getOutputScale(screen.name);
if (mangoScale !== undefined && mangoScale > 0)
return mangoScale;
}
return screen?.devicePixelRatio || 1;
}
property var _windowKeys: []
property int _windowSerial: 0
function windowKey(toplevel) {
if (!toplevel)
return "";
if (toplevel.aqueousKey)
return toplevel.aqueousKey;
if (toplevel.niriWindowId !== undefined)
return "niri:" + toplevel.niriWindowId;
if (toplevel.mangoWindowId !== undefined)
return "mango:" + toplevel.mangoWindowId;
const known = _windowKeys.find(entry => entry.toplevel === toplevel);
if (known)
return known.key;
const live = ToplevelManager.toplevels?.values;
if (live) {
for (let i = _windowKeys.length - 1; i >= 0; i--) {
if (!live.includes(_windowKeys[i].toplevel))
_windowKeys.splice(i, 1);
}
}
const key = "window:" + (++_windowSerial);
_windowKeys.push({
toplevel,
key
});
return key;
}
function getFocusedScreenName() {
if (isAqueous && AqueousService.available)
return AqueousService.focusedOutput;
if (isHyprland && Hyprland.focusedMonitor)
return Hyprland.focusedMonitor.name;
if (isNiri && NiriService.currentOutput)
return NiriService.currentOutput;
if (isSway || isScroll || isMiracle) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
return focusedWs?.monitor?.name || "";
}
if (isMango && MangoService.activeOutput)
return MangoService.activeOutput;
if (isUmbriel)
return UmbrielService.focusedOutput;
return "";
}
function getFocusedScreen() {
const screenName = getFocusedScreenName();
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;
const next = computeSortedToplevels();
// Avoid reassigning (and invalidating bindings) when contents are equivalent.
if (!_toplevelListEquivalent(next, sortedToplevels))
sortedToplevels = next;
toplevelsChanged();
}
}
function _toplevelListEquivalent(a, b) {
if (!a || !b || a.length !== b.length)
return false;
for (let i = 0; i < a.length; i++) {
const x = a[i];
const y = b[i];
if (x === y)
continue;
if (!x || !y)
return false;
// Only niri/mango enriched snapshots support value comparison
const xKey = x.niriWindowId !== undefined ? x.niriWindowId : x.mangoWindowId;
const yKey = y.niriWindowId !== undefined ? y.niriWindowId : y.mangoWindowId;
if (xKey === undefined || yKey === undefined)
return false;
if (xKey !== yKey || x.niriWorkspaceId !== y.niriWorkspaceId || x.mangoMonitor !== y.mangoMonitor || x.appId !== y.appId || x.title !== y.title || !!x.activated !== !!y.activated || !!x.fullscreen !== !!y.fullscreen || !!x.maximized !== !!y.maximized || !!x.minimized !== !!y.minimized)
return false;
}
return true;
}
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 === "monitoraddedv2" || event.name === "monitorremoved" || event.name === "configreloaded") {
if (event.name === "configreloaded")
HyprlandService.refreshFollowMouse();
root.refreshHyprlandMonitorLayout();
return;
}
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" || event.name === "activespecial") {
try {
Hyprland.refreshToplevels();
if (event.name === "workspace" || event.name === "workspacev2" || event.name === "focusedmon" || event.name === "focusedmonv2" || event.name === "activespecial")
Hyprland.refreshMonitors();
} catch (e) {}
if (event.name === "activespecial")
root.updateHyprlandVisibleSpecialWorkspaces(event);
root.scheduleSort();
if (event.name === "activewindow" || event.name === "activewindowv2")
root.workspaceStateChanged();
}
}
}
Connections {
target: NiriService
enabled: root.isNiri
function onWindowsChanged() {
root.scheduleSort();
}
}
Component.onCompleted: {
fetchRandrData();
detectCompositor();
updateHyprlandVisibleSpecialWorkspaces(null);
scheduleSort();
}
Connections {
target: MangoService
enabled: root.isMango
function onStateChanged() {
root.scheduleSort();
root.workspaceStateChanged();
}
function onWindowsChanged() {
root.scheduleSort();
}
}
function computeSortedToplevels() {
if (isAqueous && AqueousService.available)
return AqueousService.toplevels;
if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values)
return [];
if (useNiriSorting)
return NiriService.sortToplevels(ToplevelManager.toplevels.values);
if (useMangoSorting)
return MangoService.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 _normalizeSpecialWorkspaceName(name) {
const raw = String(name ?? "").trim();
if (raw.length === 0)
return "";
if (raw === "special")
return "special:special";
return raw.startsWith("special:") ? raw : `special:${raw}`;
}
function _hyprlandRawEventParts(event, argumentCount) {
if (!event)
return [];
try {
const parsed = event.parse(argumentCount);
if (parsed && parsed.length !== undefined)
return parsed;
} catch (e) {}
const data = String(event.data ?? "");
return data.length > 0 ? data.split(",") : [];
}
function _specialWorkspaceNameFromMonitor(monitor) {
if (!monitor)
return "";
const candidates = [monitor.activeSpecialWorkspace?.name, monitor.specialWorkspace?.name, monitor.lastIpcObject?.specialWorkspace?.name, monitor.lastIpcObject?.specialWorkspace, monitor.lastIpcObject?.activeSpecialWorkspace?.name];
for (let i = 0; i < candidates.length; i++) {
const normalized = _normalizeSpecialWorkspaceName(candidates[i]);
if (normalized)
return normalized;
}
return "";
}
function updateHyprlandVisibleSpecialWorkspaces(event) {
if (!isHyprland) {
hyprlandVisibleSpecialWorkspaces = ({});
return;
}
const next = {};
try {
const monitors = Hyprland.monitors?.values || [];
for (const monitor of monitors) {
const monitorName = monitor?.name ?? monitor?.lastIpcObject?.name ?? "";
if (!monitorName)
continue;
const specialName = _specialWorkspaceNameFromMonitor(monitor);
if (specialName)
next[monitorName] = specialName;
}
} catch (e) {
log.warn("updateHyprlandVisibleSpecialWorkspaces monitor snapshot failed:", e);
}
if (event?.name === "activespecial") {
const parts = _hyprlandRawEventParts(event, 2);
const specialName = _normalizeSpecialWorkspaceName(parts[0]);
const monitorName = String(parts[1] ?? Hyprland.focusedMonitor?.name ?? Hyprland.focusedWorkspace?.monitor?.name ?? "");
if (monitorName) {
if (specialName)
next[monitorName] = specialName;
else
delete next[monitorName];
}
}
hyprlandVisibleSpecialWorkspaces = next;
}
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 => {
if (!x.wayland)
return null;
x.wayland.address = x.address;
return x.wayland;
}).filter(w => w !== null && w !== undefined);
}
function filterCurrentWorkspace(toplevels, screen) {
if (isAqueous && AqueousService.available) {
const active = AqueousService.workspacesForOutput(_screenName(screen)).filter(w => w.active).map(w => w.id);
return toplevels.filter(t => active.includes(t.aqueousWorkspaceId));
}
if (useNiriSorting)
return NiriService.filterCurrentWorkspace(toplevels, screen);
if (useMangoSorting)
return MangoService.filterCurrentWorkspace(toplevels, screen);
if (isHyprland)
return filterHyprlandCurrentWorkspaceSafe(toplevels, screen);
return toplevels;
}
readonly property string toplevelKey: {
switch (compositor) {
case "aqueous":
return AqueousService.available ? "aqueousKey" : "address";
case "niri":
return "niriWindowId";
case "mango":
return "mangoWindowId";
default:
return "address";
}
}
function toplevelPlacement(toplevel) {
if (!toplevel)
return {
"workspace": "",
"output": ""
};
if (isAqueous && AqueousService.available) {
const workspace = AqueousService.workspaces.find(w => w.id === toplevel.aqueousWorkspaceId);
return {
"workspace": String(workspace?.name || workspace?.number || ""),
"output": AqueousService.outputs.find(o => o.id === toplevel.aqueousOutputId)?.name ?? ""
};
}
if (useNiriSorting) {
const workspace = NiriService.allWorkspaces.find(w => w.id === toplevel.niriWorkspaceId);
return {
"workspace": String(workspace?.name || workspace?.idx || ""),
"output": workspace?.output ?? ""
};
}
if (useMangoSorting)
return {
"workspace": (toplevel.mangoTags || []).join(", "),
"output": toplevel.mangoMonitor ?? ""
};
if (isHyprland) {
const workspace = WindowModel.hyprlandToplevelFor(Array.from(Hyprland.toplevels?.values || []), toplevel)?.workspace;
return {
"workspace": workspace?.name ?? "",
"output": workspace?.monitor?.name ?? ""
};
}
return {
"workspace": "",
"output": toplevel.screens?.[0]?.name ?? ""
};
}
function fullscreenToplevelOnScreen(screenOrName) {
const screenName = _screenName(screenOrName);
if (isAqueous && AqueousService.available)
return AqueousService.windows.some(w => w.output === AqueousService.outputId(screenName) && w.visible && w.fullscreen);
if (!screenName || !ToplevelManager.toplevels?.values)
return false;
const toplevels = ToplevelManager.toplevels.values;
for (let i = 0; i < toplevels.length; i++) {
const toplevel = toplevels[i];
if (toplevel?.fullscreen && toplevel.activated && _toplevelOnScreen(toplevel, screenName))
return true;
}
return false;
}
function filterCurrentDisplay(toplevels, screenName) {
if (!toplevels || toplevels.length === 0 || !screenName)
return toplevels;
if (useNiriSorting) {
const active = ToplevelManager.activeToplevel;
if (active && toplevels.length === 1 && toplevels[0] === active) {
if (NiriService.currentOutput !== screenName)
return [];
const focusedWin = NiriService.windows.find(nw => nw.is_focused);
if (!focusedWin)
return [];
const screenWsIds = new Set(NiriService.allWorkspaces.filter(ws => ws.output === screenName).map(ws => ws.id));
return screenWsIds.has(focusedWin.workspace_id) ? toplevels : [];
}
return NiriService.filterCurrentDisplay(toplevels, screenName);
}
if (useMangoSorting)
return MangoService.filterCurrentDisplay(toplevels, screenName);
if (isHyprland)
return filterHyprlandCurrentDisplaySafe(toplevels, screenName);
return toplevels.filter(t => _toplevelOnScreen(t, screenName));
}
function _screenName(screenOrName) {
if (typeof screenOrName === "string")
return screenOrName;
return screenOrName?.name ?? "";
}
function _toplevelOnScreen(toplevel, screenName) {
if (!toplevel || !screenName)
return false;
const screens = toplevel.screens;
if (!screens)
return false;
for (let i = 0; i < screens.length; i++) {
if (screens[i]?.name === screenName)
return true;
}
return false;
}
function _hyprlandToplevelMapped(hyprToplevel) {
if (!hyprToplevel)
return false;
if (hyprToplevel.mapped === false)
return false;
const ipcMapped = hyprToplevel.lastIpcObject?.mapped;
if (ipcMapped === false)
return false;
if (hyprToplevel.hidden === true)
return false;
const ipcHidden = hyprToplevel.lastIpcObject?.hidden;
if (ipcHidden === true)
return false;
return true;
}
readonly property var specialWorkspaceNames: {
if (!isHyprland)
return [];
const names = ["special"];
for (const ws of Hyprland.workspaces?.values || []) {
if (!WorkspaceModel.hyprlandSpecial(ws))
continue;
const name = WorkspaceModel.hyprlandSpecialDisplayName(ws.name ?? "");
if (!names.includes(name))
names.push(name);
}
return names;
}
function hyprlandAddressFor(window) {
return isHyprland ? (_hyprlandToplevelFor(window)?.address ?? "") : "";
}
// IPC callers pass Hyprland's own names, prefixed or not
function _scratchpadName(name) {
const bare = WorkspaceModel.hyprlandSpecialDisplayName(String(name ?? ""));
return !bare || bare === "special" ? "special" : bare;
}
function toggleSpecialWorkspace(name) {
if (!isHyprland)
return;
const bare = _scratchpadName(name);
HyprlandService.toggleSpecial(bare === "special" ? "" : bare);
}
function _hyprlandToplevelFor(window) {
if (!window)
return null;
const byAddress = typeof window === "string";
for (const t of Hyprland.toplevels?.values || []) {
if (byAddress ? t.address === window : t.wayland === window)
return t;
}
return null;
}
function windowScratchpadName(window) {
if (!isHyprland)
return "";
const t = _hyprlandToplevelFor(window);
const ws = String(t?.lastIpcObject?.workspace?.name || t?.workspace?.name || "");
return WorkspaceModel.hyprlandSpecialName(ws) ? WorkspaceModel.hyprlandSpecialDisplayName(ws) : "";
}
// "+0" is Hyprland's own way out but resolves against the focused monitor, so prefer the window's monitor when known
function moveWindowOutOfSpecial(window) {
const t = _hyprlandToplevelFor(window);
if (!t?.address)
return;
const target = t.monitor?.activeWorkspace;
HyprlandService.moveToWorkspace(target?.name ? WorkspaceModel.hyprlandSelector(target) : "+0", t.address, true);
}
function moveWindowToSpecial(window, name) {
const address = hyprlandAddressFor(window);
if (!address)
return;
const bare = _scratchpadName(name);
HyprlandService.moveToWorkspace(bare === "special" ? "special" : "special:" + bare, address, false);
}
function hyprlandVisibleSpecialWorkspaceOnScreen(screenOrName) {
const screenName = _screenName(screenOrName);
if (!isHyprland || !screenName)
return "";
hyprlandVisibleSpecialWorkspaces;
const trackedName = hyprlandVisibleSpecialWorkspaces[screenName] ?? "";
if (trackedName)
return trackedName;
try {
const monitor = Hyprland.monitors?.values?.find(m => m.name === screenName);
return _specialWorkspaceNameFromMonitor(monitor);
} catch (e) {
return "";
}
}
// Workspace moves can land before Hyprland announces the monitor and before the Wayland
// output reaches us, so re-read both models once the screen set settles (#3133)
function refreshHyprlandMonitorLayout() {
if (!isHyprland)
return;
Hyprland.refreshMonitors();
Hyprland.refreshWorkspaces();
HyprlandService.refreshWorkspaceRules();
}
function _screenForName(screenOrName) {
if (screenOrName && typeof screenOrName !== "string")
return screenOrName;
const screenName = _screenName(screenOrName);
if (!screenName)
return null;
const screens = Quickshell.screens || [];
for (let i = 0; i < screens.length; i++) {
if (screens[i]?.name === screenName)
return screens[i];
}
return null;
}
function frameConfiguredForScreen(screenOrName) {
if (!FrameTransitionState.effectiveFrameEnabled)
return false;
const screen = _screenForName(screenOrName);
if (!screen || !SettingsData.isScreenInPreferences(screen, SettingsData.frameScreenPreferences))
return false;
return true;
}
function frameWindowVisibleForScreen(screenOrName) {
return frameConfiguredForScreen(screenOrName);
}
function overviewActiveOnScreen(screenOrName) {
if (isAqueous && AqueousService.available)
return !!AqueousService.sessionState.overview_output && AqueousService.sessionState.overview_output === AqueousService.outputId(_screenName(screenOrName));
return isNiri && NiriService.inOverview;
}
function usesConnectedFrameChromeForScreen(screenOrName) {
return FrameTransitionState.effectiveConnectedFrameModeActive && frameWindowVisibleForScreen(screenOrName);
}
function canShareConnectedFrameChromeForScreen(screenOrName) {
if (!usesConnectedFrameChromeForScreen(screenOrName))
return false;
if (!isHyprland)
return true;
const screenName = _screenName(screenOrName);
const monitor = Hyprland.monitors.values.find(m => m.name === screenName);
const specialWorkspace = monitor?.lastIpcObject?.specialWorkspace?.name;
const workspace = specialWorkspace ? Hyprland.workspaces.values.find(w => w.name === specialWorkspace) : monitor?.activeWorkspace;
if (!workspace)
return true;
return !workspace.toplevels.values.some(t => t.lastIpcObject?.fullscreen === 2);
}
// Connected mode renders the bar inside the frame surface.
function frameHostsSurfacesForScreen(screenOrName) {
return FrameTransitionState.effectiveConnectedFrameModeActive && frameConfiguredForScreen(screenOrName);
}
// A surface set to the overlay layer cannot live inside the frame's single top-layer window,
// so it stays a standalone window (which resolves itself onto the overlay layer) even in connected mode.
function frameHostsBarForConfig(screenOrName, barConfig) {
return frameHostsSurfacesForScreen(screenOrName) && !(barConfig?.useOverlayLayer ?? false);
}
function frameHostsDockForConfig(screenOrName, dockConfig) {
return !!dockConfig && frameHostsSurfacesForScreen(screenOrName) && !dockConfig.useOverlayLayer;
}
function frameHostsDockForScreen(screenOrName) {
return SettingsData.dockConfigsForScreen(screenOrName).some(config => frameHostsDockForConfig(screenOrName, config));
}
function framePeerSurfacesUseOverlayForScreen(screenOrName) {
return frameWindowVisibleForScreen(screenOrName);
}
function hyprlandToplevelOverlapsDockEdge(hyprToplevel, screenName, dockPosition, dockThickness, screenWidth, screenHeight) {
if (!hyprToplevel?.lastIpcObject || !screenName)
return false;
const monName = hyprToplevel.monitor?.name ?? hyprToplevel.lastIpcObject?.monitor ?? "";
if (monName && monName !== screenName)
return false;
const ipc = hyprToplevel.lastIpcObject;
const at = ipc.at;
const size = ipc.size;
if (!at || !size)
return false;
const monX = hyprToplevel.monitor?.x ?? 0;
const monY = hyprToplevel.monitor?.y ?? 0;
const winX = at[0] - monX;
const winY = at[1] - monY;
const winW = size[0];
const winH = size[1];
switch (dockPosition) {
case SettingsData.Position.Top:
return winY < dockThickness;
case SettingsData.Position.Bottom:
return winY + winH > screenHeight - dockThickness;
case SettingsData.Position.Left:
return winX < dockThickness;
case SettingsData.Position.Right:
return winX + winW > screenWidth - dockThickness;
default:
return false;
}
}
function _hyprlandDockOverlap(screenName, dockPosition, dockThickness, screenWidth, screenHeight) {
if (!isHyprland || !screenName || !Hyprland.toplevels?.values)
return false;
const filtered = filterCurrentWorkspace(sortedToplevels, screenName);
for (let i = 0; i < filtered.length; i++) {
const toplevel = filtered[i];
let hyprToplevel = null;
for (const t of Hyprland.toplevels.values) {
if (t.wayland === toplevel) {
hyprToplevel = t;
break;
}
}
if (hyprlandToplevelOverlapsDockEdge(hyprToplevel, screenName, dockPosition, dockThickness, screenWidth, screenHeight))
return true;
}
const visibleSpecialWorkspace = hyprlandVisibleSpecialWorkspaceOnScreen(screenName);
if (!visibleSpecialWorkspace)
return false;
for (const hyprToplevel of Hyprland.toplevels.values) {
const wsName = _normalizeSpecialWorkspaceName(hyprToplevel.workspace?.name ?? hyprToplevel.lastIpcObject?.workspace?.name ?? "");
if (wsName !== visibleSpecialWorkspace)
continue;
if (!_hyprlandToplevelMapped(hyprToplevel))
continue;
if (hyprlandToplevelOverlapsDockEdge(hyprToplevel, screenName, dockPosition, dockThickness, screenWidth, screenHeight))
return true;
}
return false;
}
function _windowAlive(toplevel) {
if (!toplevel)
return false;
const alive = ToplevelManager.toplevels?.values;
return !!alive && Array.from(alive).some(t => t === toplevel);
}
function _fallbackActiveWindow(screenName, includeLastFocused) {
switch (compositor) {
case "niri":
return WindowModel.niriActiveWindow(NiriService.windows, NiriService.allWorkspaces, sortedToplevels || [], Array.from(ToplevelManager.toplevels?.values || []), screenName ?? "", NiriService.lastFocusedWindowId, includeLastFocused);
default:
return null;
}
}
function _retainedWindow(screenName, previous) {
if (!_windowAlive(previous))
return null;
switch (compositor) {
case "niri":
return WindowModel.niriActiveWorkspaceOccupied(NiriService.windows, NiriService.allWorkspaces, screenName ?? "") ? previous : null;
default:
return previous;
}
}
function activeWindowForScreen(screenName, previous, includeLastFocused) {
switch (compositor) {
case "aqueous":
if (AqueousService.available)
return WindowModel.aqueousActiveWindow(AqueousService.focusedWindow, screenName);
break;
}
const active = ToplevelManager.activeToplevel || _fallbackActiveWindow(screenName, includeLastFocused);
if (!active)
return _retainedWindow(screenName, previous);
if (!screenName || filterCurrentDisplay([active], screenName)?.length > 0)
return active;
return _windowAlive(previous) ? previous : null;
}
function windowPid(toplevel) {
if (!toplevel)
return 0;
switch (compositor) {
case "niri":
return WindowModel.niriWindowPid(NiriService.windows, WindowModel.sortedWindowFor(sortedToplevels || [], toplevel));
case "hyprland":
return WindowModel.hyprlandWindowPid(Array.from(Hyprland.toplevels?.values || []), toplevel);
case "mango":
return WindowModel.mangoWindowPid(MangoService.windows, WindowModel.sortedWindowFor(sortedToplevels || [], toplevel));
default:
return toplevel.pid || 0;
}
}
function windowOnActiveWorkspace(screenName, toplevel, includeLastFocused) {
switch (compositor) {
case "niri":
return WindowModel.niriWindowOnActiveWorkspace(NiriService.windows, NiriService.allWorkspaces, screenName, NiriService.currentOutput, WindowModel.niriFocusedWindow(NiriService.windows, NiriService.lastFocusedWindowId, includeLastFocused));
case "hyprland":
if (!Hyprland.toplevels)
return false;
try {
return WindowModel.hyprlandWindowOnActiveWorkspace(Array.from(Hyprland.toplevels.values), Hyprland.focusedWorkspace, toplevel);
} catch (e) {
return false;
}
default:
return true;
}
}
function windowsHideBar(screenName, position, thickness, screenWidth, screenHeight) {
switch (compositor) {
case "niri":
return WindowModel.niriBarHideForWindows(NiriService.windows, NiriService.allWorkspaces, screenName, position, thickness, screenWidth, screenHeight, _edgePositions);
case "hyprland":
case "mango":
case "aqueous":
return filterCurrentWorkspace(sortedToplevels, screenName).length > 0;
default:
return false;
}
}
function windowsOverlapDock(screenName, position, thickness, screenWidth, screenHeight) {
switch (compositor) {
case "niri":
return WindowModel.niriDockOverlap(NiriService.windows, NiriService.allWorkspaces, screenName, position, thickness, screenWidth, screenHeight, _edgePositions);
case "hyprland":
return _hyprlandDockOverlap(screenName, position, thickness, screenWidth, screenHeight);
case "mango":
if (!screenName || !MangoService.windows)
return false;
return WindowModel.mangoEdgeOverlap(MangoService.windows, MangoService.outputs[screenName], screenName, position, thickness, screenWidth, screenHeight, _edgePositions);
case "aqueous":
return AqueousService.available && AqueousService.overlapsDock(screenName, position, thickness, screenWidth, screenHeight);
default:
return false;
}
}
function maximizedWindowOnScreen(screenName) {
switch (compositor) {
case "mango":
return WindowModel.mangoMaximizedOnScreen(MangoService.windows || [], MangoService.outputs[screenName], screenName);
case "hyprland":
case "niri":
case "aqueous":
return filterCurrentWorkspace(sortedToplevels, screenName).some(t => t?.maximized);
default:
return false;
}
}
function overviewFocusedToplevel(toplevels) {
switch (compositor) {
case "niri":
if (!NiriService.inOverview || NiriService.lastFocusedWindowId === null)
return null;
return toplevels.find(t => t.niriWindowId === NiriService.lastFocusedWindowId) ?? null;
default:
return null;
}
}
function specialWorkspaceName(toplevel) {
switch (compositor) {
case "hyprland":
if (!toplevel || !Hyprland.toplevels)
return "";
return WindowModel.hyprlandSpecialWorkspaceName(Array.from(Hyprland.toplevels.values), toplevel);
default:
return "";
}
}
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 _workspaceKeyOf(ws) {
return ws ? (WorkspaceModel.hyprlandKey(ws) || null) : null;
}
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 = _workspaceKeyOf(_get(monitor, ["activeWorkspace"], null));
}
if (currentWorkspaceId === null) {
const hy = Array.from(Hyprland.toplevels.values);
for (const t of hy) {
const mon = _get(t, ["monitor", "name"], "");
const wsId = _workspaceKeyOf(_get(t, ["workspace"], 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 = _workspaceKeyOf(Hyprland.focusedWorkspace);
for (const ws of wss) {
const monName = _get(ws, ["monitor", "name"], "");
const wsId = _workspaceKeyOf(ws);
if (monName === screenName && wsId !== null) {
if (focusedId !== null && wsId === focusedId) {
currentWorkspaceId = wsId;
break;
}
if (currentWorkspaceId === null)
currentWorkspaceId = wsId;
}
}
}
} catch (e) {
log.warn("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 = _workspaceKeyOf(_get(t, ["workspace"], null));
if (t && t.wayland && wsId !== null)
map.set(t.wayland, wsId);
}
} catch (e) {}
return toplevels.filter(w => map.get(w) === currentWorkspaceId);
}
// Primary detection asks the kernel which process owns the $WAYLAND_DISPLAY
// socket — the compositor quickshell is actually connected to. Env vars like
// HYPRLAND_INSTANCE_SIGNATURE / MANGO_INSTANCE_SIGNATURE can leak into the
// systemd user environment from previous sessions and lie. Unset
// WAYLAND_DISPLAY falls back to "wayland-0", mirroring wl_display_connect.
// /proc/net/unix: field 6 is state (01 = listening), 7 inode, 8 bound path.
// The BSDs have no /proc/net; sockstat(1) -l -u lists listening unix
// sockets as USER COMMAND PID FD PROTO LOCAL-ADDRESS.
function detectCompositor() {
const procScript = 'sock="${WAYLAND_DISPLAY:-wayland-0}"; case "$sock" in /*) ;; *) sock="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/$sock" ;; esac; inode=$(awk -v p="$sock" \'$8 == p && $6 == 1 {print $7; exit}\' /proc/net/unix); [ -n "$inode" ] || exit 1; fd=$(find /proc/[0-9]*/fd/ -mindepth 1 -maxdepth 1 -lname "socket:\\[$inode\\]" 2>/dev/null | head -n1); [ -n "$fd" ] || exit 1; pid="${fd#/proc/}"; cat "/proc/${pid%%/*}/comm"';
const sockstatScript = 'sock="${WAYLAND_DISPLAY:-wayland-0}"; case "$sock" in /*) ;; *) sock="${XDG_RUNTIME_DIR:-/var/run/user/$(id -u)}/$sock" ;; esac; sockstat -l -u | awk -v p="$sock" \'$6 == p {print $2; exit}\'';
const script = Qt.platform.os === "unix" ? sockstatScript : procScript;
Proc.runCommand("waylandSocketOwner", ["sh", "-c", script], (output, exitCode) => {
const comm = (exitCode === 0 && output) ? output.trim().toLowerCase() : "";
const name = _compositorNameFromComm(comm);
if (name) {
_applyCompositor(name);
log.info("Detected", name, "from Wayland socket owner:", comm);
return;
}
if (comm)
log.info("Unrecognized Wayland socket owner:", comm, "- falling back to env detection");
_detectFromEnv(0);
}, 0, 3000);
}
function _compositorNameFromComm(comm) {
switch (comm) {
case "niri":
return "niri";
case "hyprland":
return "hyprland";
case "sway":
return "sway";
case "scroll":
return "scroll";
case "mango":
return "mango";
case "miracle-wm":
return "miracle";
case "labwc":
return "labwc";
case "aqueous":
return "aqueous";
case "umbriel":
return "umbriel";
case "kwin_wayland":
return "kwin";
default:
return "";
}
}
function _applyCompositor(name) {
isHyprland = name === "hyprland";
isNiri = name === "niri";
isMango = name === "mango";
isSway = name === "sway";
isScroll = name === "scroll";
isMiracle = name === "miracle";
isLabwc = name === "labwc";
isAqueous = name === "aqueous";
isUmbriel = name === "umbriel";
isKwin = name === "kwin";
compositor = name;
compositorDetected = true;
if (isNiri)
NiriService.generateNiriBlurrule();
Qt.callLater(seedDmsWindowFloatingRule);
}
function seedDmsWindowFloatingRule() {
if (!supportsWindowRules)
return;
if (SettingsData.dmsWindowsFloatingSeeded.includes(compositor)) {
refreshDmsWindowFloatingRule(true);
return;
}
const seeded = compositor;
setDmsWindowFloatingRule(true, () => {
SettingsData.set("dmsWindowsFloatingSeeded", SettingsData.dmsWindowsFloatingSeeded.concat([seeded]));
refreshDmsWindowFloatingRule(true);
});
}
function refreshDmsWindowFloatingRule(ensureOpaque) {
if (!supportsWindowRules)
return;
const procId = ensureOpaque ? "dms-windowrule-startup-list" : "dms-windowrule-float-list";
Proc.runCommand(procId, [Proc.dmsBin, "config", "windowrules", "list", compositor], (output, exitCode) => {
if (exitCode !== 0) {
if (ensureOpaque)
log.warn("failed to list window rules:", exitCode, output);
return;
}
let rules;
try {
rules = JSON.parse(output.trim()).rules || [];
} catch (e) {
log.warn("failed to parse window rules", e);
return;
}
// Each rule write rewrites the whole rules file, so writes must not overlap.
syncDmsWindowFloatingRule(rules, () => {
if (ensureOpaque)
ensureDmsOpaqueRule(rules);
});
});
}
function dmsRuleMatchesCurrentAppId(rule) {
return (rule.matchCriteria?.appId || "").includes("duxlabs");
}
function syncDmsWindowFloatingRule(rules, onDone) {
const rule = rules.find(rule => rule.id === dmsFloatingRuleId && rule.enabled !== false && rule.actions?.openFloating === true);
dmsWindowFloatingActive = rule !== undefined;
if (!rule || dmsRuleMatchesCurrentAppId(rule)) {
onDone?.();
return;
}
setDmsWindowFloatingRule(true, onDone);
}
function reloadAfterWindowRuleWrite() {
if (isNiri)
NiriService.validate();
else if (isMango)
MangoService.reloadConfig();
else if (isHyprland)
HyprlandService.reloadConfig();
}
function setDmsRule(id, rule, onDone) {
if (!supportsWindowRules)
return;
const args = rule ? ["add", compositor, JSON.stringify(Object.assign({
"id": id,
"enabled": true
}, rule))] : ["remove", compositor, id];
Proc.runCommand("dms-windowrule-" + id, [Proc.dmsBin, "config", "windowrules", ...args], (output, exitCode) => {
if (exitCode !== 0) {
log.warn("failed to update DMS window rule", id, exitCode, output);
return;
}
reloadAfterWindowRuleWrite();
onDone?.();
});
}
function setDmsWindowFloatingRule(enabled, onDone) {
setDmsRule(dmsFloatingRuleId, enabled ? {
"name": "DMS Floating Windows",
"matchCriteria": {
"appId": "^(dev\\.duxlabs\\.shell|com\\.danklinux\\.dms)$"
},
"actions": {
"openFloating": true
}
} : null, () => {
dmsWindowFloatingActive = enabled;
onDone?.();
});
}
function ensureDmsOpaqueRule(rules) {
if (rules.some(rule => rule.id === dmsOpaqueRuleId && dmsRuleMatchesCurrentAppId(rule)))
return;
// DMS windows are translucent via client alpha; Hyprland's opaque keeps that alpha, force_rgbx would drop it.
let actions;
if (isHyprland)
actions = {
"opaque": true
};
else if (isNiri)
actions = {
"opacity": 1.0
};
else
return;
setDmsRule(dmsOpaqueRuleId, {
"name": "DMS Window Opaque",
"matchCriteria": {
"appId": "^(dev\\.duxlabs\\.shell|com\\.danklinux\\.dms)$"
},
"actions": actions
});
}
// Fallback when the socket owner can't be resolved (no ss, unrecognized
// comm). Same priority order as before, but every candidate must prove
// liveness; a dead socket/PID falls through to the next candidate instead
// of winning on a stale env var.
function _envDetectionCandidates() {
const runtimeDir = Quickshell.env("XDG_RUNTIME_DIR") || "";
const aqueousSocket = Quickshell.env("AQUEOUS_SOCKET") || "";
const umbrielSocket = Quickshell.env("UMBRIEL_SOCKET") || "";
return [
{
name: "aqueous",
present: !!aqueousSocket,
test: ["test", "-S", aqueousSocket],
detail: "AQUEOUS_SOCKET " + aqueousSocket
},
{
name: "mango",
present: !!mangoSignature,
test: ["test", "-S", mangoSignature],
detail: "MANGO_INSTANCE_SIGNATURE " + mangoSignature
},
{
name: "umbriel",
present: !!umbrielSocket,
test: ["test", "-S", umbrielSocket],
detail: "UMBRIEL_SOCKET " + umbrielSocket
},
{
name: "niri",
present: !!niriSocket,
test: ["test", "-S", niriSocket],
detail: "NIRI_SOCKET " + niriSocket
},
{
name: "miracle",
present: !!miracleSocket,
test: ["test", "-S", miracleSocket],
detail: "MIRACLESOCK " + miracleSocket
},
{
name: "sway",
present: !!swaySocket,
test: ["test", "-S", swaySocket],
resolve: () => {
const desktop = String(Quickshell.env("XDG_CURRENT_DESKTOP") || "").toLowerCase();
return desktop.includes("sway") ? "sway" : "scroll";
},
detail: "SWAYSOCK " + swaySocket
},
{
name: "labwc",
present: !!labwcPid,
test: ["sh", "-c", "[ \"$(ps -p \"$LABWC_PID\" -o comm= 2>/dev/null)\" = labwc ]"],
detail: "LABWC_PID " + labwcPid
},
{
name: "hyprland",
present: !!hyprlandSignature,
test: ["test", "-S", runtimeDir + "/hypr/" + hyprlandSignature + "/.socket.sock"],
detail: "HYPRLAND_INSTANCE_SIGNATURE " + hyprlandSignature
},
{
name: "kwin",
present: !!Quickshell.env("KDE_FULL_SESSION"),
test: ["pgrep", "-x", "kwin_wayland"],
detail: "KDE_FULL_SESSION"
}
];
}
function _detectFromEnv(index) {
const candidates = _envDetectionCandidates();
for (let i = index; i < candidates.length; i++) {
const c = candidates[i];
if (!c.present)
continue;
const next = i + 1;
Proc.runCommand(c.name + "SocketCheck", c.test, (output, exitCode) => {
if (exitCode !== 0) {
log.warn(c.detail, "is set but not alive, skipping");
_detectFromEnv(next);
return;
}
const name = c.resolve ? c.resolve() : c.name;
_applyCompositor(name);
log.info("Detected", name, "via", c.detail);
}, 0);
return;
}
_applyCompositor("unknown");
log.warn("No compositor detected");
}
function powerOffMonitors() {
if (isNiri)
return NiriService.powerOffMonitors();
if (isHyprland)
return HyprlandService.dpmsOff();
if (isMango)
return MangoService.powerOffMonitors();
if (isSway || isScroll || isMiracle) {
try {
I3.dispatch("output * dpms off");
} catch (_) {}
return;
}
if (isLabwc) {
Quickshell.execDetached(["dms", "dpms", "off"]);
return;
}
if (isUmbriel)
return UmbrielService.action("dpms-off");
if (outputPowerAvailable) {
setOutputPower(false);
return;
}
log.warn("Cannot power off monitors, unknown compositor");
}
function powerOnMonitors() {
if (isNiri)
return NiriService.powerOnMonitors();
if (isHyprland)
return HyprlandService.dpmsOn();
if (isMango)
return MangoService.powerOnMonitors();
if (isSway || isScroll || isMiracle) {
try {
I3.dispatch("output * dpms on");
} catch (_) {}
return;
}
if (isLabwc) {
Quickshell.execDetached(["dms", "dpms", "on"]);
return;
}
if (isUmbriel)
return UmbrielService.action("dpms-on");
if (outputPowerAvailable) {
setOutputPower(true);
return;
}
log.warn("Cannot power on monitors, unknown compositor");
}
function escapeSwayWorkspaceName(name) {
return String(name ?? "").replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
}
function dispatchSwayWorkspace(ws) {
if (!ws)
return;
try {
if (ws.num !== undefined && ws.num !== -1) {
I3.dispatch(`workspace number ${ws.num}`);
} else if (ws.name) {
I3.dispatch(`workspace "${escapeSwayWorkspaceName(ws.name)}"`);
}
} catch (_) {}
}
function _niriWorkspaceState() {
return {
get allWorkspaces() {
return NiriService.allWorkspaces;
},
get currentOutputWorkspaces() {
return NiriService.getCurrentOutputWorkspaces();
},
get focusedIdx() {
return NiriService.getCurrentWorkspaceNumber();
},
get windows() {
return NiriService.windows;
}
};
}
function _hyprlandWorkspaceState() {
return {
get workspaces() {
return Hyprland.workspaces?.values || [];
},
get monitors() {
return Hyprland.monitors?.values || [];
},
get focusedWorkspace() {
return Hyprland.focusedWorkspace;
},
get toplevels() {
return Array.from(Hyprland.toplevels?.values || []);
},
get workspaceRules() {
return HyprlandService.workspaceRules;
},
get visibleSpecials() {
return root.hyprlandVisibleSpecialWorkspaces;
}
};
}
function _mangoWorkspaceState(screenName) {
return {
get available() {
return MangoService.available;
},
get tagCount() {
return MangoService.tagCount;
},
get output() {
return MangoService.getOutputState(screenName);
},
get visibleTags() {
return MangoService.getVisibleTags(screenName);
},
get activeTags() {
return MangoService.getActiveTags(screenName);
}
};
}
function _i3WorkspaceState() {
return {
get workspaces() {
return I3.workspaces?.values || [];
}
};
}
function _aqueousWorkspaceState(screenName) {
return {
get workspaces() {
return AqueousService.workspacesForOutput(screenName);
},
get toplevels() {
return AqueousService.toplevels;
}
};
}
function _followedScreen(screenName, followFocus) {
return followFocus ? (getFocusedScreenName() || screenName) : screenName;
}
function _workspaceKey(record) {
switch (compositor) {
case "niri":
return record.idx;
case "hyprland":
return WorkspaceModel.hyprlandKey(record);
default:
return record.id;
}
}
function workspacesForScreen(screenName, followFocus, options) {
switch (compositor) {
case "niri":
return WorkspaceModel.niriWorkspacesForScreen(_niriWorkspaceState(), screenName, followFocus, options.occupiedOnly, _workspaceRecords);
case "hyprland":
return WorkspaceModel.hyprlandWorkspacesForScreen(_hyprlandWorkspaceState(), screenName, followFocus, options.occupiedOnly, options.minCount, options.showSpecial);
case "mango":
{
const name = _followedScreen(screenName, followFocus);
return WorkspaceModel.mangoWorkspacesForScreen(_mangoWorkspaceState(name), name, options.showAllTags, options.minCount);
}
case "sway":
case "scroll":
case "miracle":
return WorkspaceModel.i3WorkspacesForScreen(_i3WorkspaceState(), screenName, followFocus, options.minCount);
case "aqueous":
{
const name = _followedScreen(screenName, followFocus);
return WorkspaceModel.aqueousWorkspacesForScreen(_aqueousWorkspaceState(name), name, options.occupiedOnly, _workspaceRecords);
}
case "umbriel":
return WorkspaceModel.umbrielWorkspacesForScreen(UmbrielService.workspaces, _followedScreen(screenName, followFocus), options.occupiedOnly, _workspaceRecords);
default:
return [];
}
}
function currentWorkspaceKey(screenName, followFocus) {
switch (compositor) {
case "niri":
return WorkspaceModel.niriCurrentIdx(_niriWorkspaceState(), screenName, followFocus);
case "hyprland":
return WorkspaceModel.hyprlandCurrentId(_hyprlandWorkspaceState(), screenName, followFocus);
case "mango":
return WorkspaceModel.mangoCurrentTag(_mangoWorkspaceState(_followedScreen(screenName, followFocus)));
case "sway":
case "scroll":
case "miracle":
return WorkspaceModel.i3CurrentKey(_i3WorkspaceState(), screenName, followFocus);
case "aqueous":
return WorkspaceModel.aqueousCurrentId(_aqueousWorkspaceState(_followedScreen(screenName, followFocus)));
case "umbriel":
return WorkspaceModel.umbrielCurrentId(UmbrielService.workspaces, _followedScreen(screenName, followFocus));
default:
return 1;
}
}
function isCurrentWorkspace(record, currentKey) {
if (!record)
return false;
switch (compositor) {
case "mango":
case "aqueous":
return record.active === true;
case "hyprland":
return record.special === true ? record.active === true : WorkspaceModel.hyprlandKey(record) === currentKey;
default:
return _workspaceKey(record) === currentKey;
}
}
function activeWorkspaceForScreen(screenName) {
switch (compositor) {
case "niri":
return WorkspaceModel.niriActiveWorkspace(_niriWorkspaceState(), screenName);
case "hyprland":
return WorkspaceModel.hyprlandActiveWorkspace(_hyprlandWorkspaceState(), screenName);
case "mango":
return WorkspaceModel.mangoActiveWorkspace(_mangoWorkspaceState(screenName), screenName);
case "sway":
case "scroll":
case "miracle":
return WorkspaceModel.i3ActiveWorkspace(_i3WorkspaceState(), screenName);
case "aqueous":
return WorkspaceModel.aqueousActiveWorkspace(_aqueousWorkspaceState(screenName), screenName);
case "umbriel":
return WorkspaceModel.umbrielActiveWorkspace(UmbrielService.workspaces, screenName);
default:
return null;
}
}
function windowsOnWorkspace(record) {
if (!record || record.placeholder)
return [];
switch (compositor) {
case "niri":
return WorkspaceModel.niriWindowsOnWorkspace(NiriService.windows || [], record);
case "hyprland":
return WorkspaceModel.hyprlandWindowsOnWorkspace(sortedToplevels, record, Array.from(Hyprland.toplevels?.values || []));
case "mango":
return WorkspaceModel.mangoWindowsOnWorkspace(sortedToplevels, record);
case "sway":
case "scroll":
case "miracle":
return WorkspaceModel.i3WindowsOnWorkspace(sortedToplevels, record);
case "aqueous":
return WorkspaceModel.aqueousWindowsOnWorkspace(sortedToplevels, record);
case "umbriel":
return WorkspaceModel.umbrielWindowsOnWorkspace(UmbrielService.windows, record);
default:
return [];
}
}
function workspaceOccupied(record) {
if (!record || record.placeholder)
return false;
switch (compositor) {
case "niri":
return WorkspaceModel.niriWorkspaceOccupied(NiriService.windows, record);
case "hyprland":
return WorkspaceModel.hyprlandWorkspaceOccupied(Array.from(Hyprland.toplevels?.values || []), record);
case "mango":
case "umbriel":
return record.occupied === true;
case "aqueous":
return AqueousService.available && WorkspaceModel.aqueousWorkspaceOccupied(AqueousService.toplevels, record);
default:
return false;
}
}
function workspaceAppsActive(record, currentKey) {
switch (compositor) {
case "niri":
return WorkspaceModel.niriWorkspaceActive(NiriService.allWorkspaces, record);
case "mango":
return record.active === true;
case "sway":
case "scroll":
case "miracle":
return WorkspaceModel.i3WorkspaceFocused(_i3WorkspaceState(), record);
case "hyprland":
return isCurrentWorkspace(record, currentKey);
default:
return record.id === currentKey;
}
}
function workspaceUrgent(record, loadedUrgent) {
switch (compositor) {
case "niri":
case "sway":
case "scroll":
case "miracle":
case "umbriel":
return loadedUrgent;
default:
return record?.urgent === true;
}
}
function loadWorkspaceUrgent(record) {
switch (compositor) {
case "niri":
return WorkspaceModel.niriWorkspaceUrgent(NiriService.windows, record);
case "umbriel":
return WorkspaceModel.umbrielWorkspaceUrgent(UmbrielService.windows, record);
default:
return record?.urgent ?? false;
}
}
function switchToWorkspace(record, screenName) {
if (!record || record.placeholder)
return;
switch (compositor) {
case "niri":
NiriService.switchToWorkspace(record.id);
return;
case "hyprland":
if (record.special === true) {
// togglespecialworkspace acts on the focused monitor, so a pill on another monitor's bar focuses that monitor first
if (screenName && screenName !== Hyprland.focusedMonitor?.name)
HyprlandService.focusMonitor(screenName);
HyprlandService.toggleSpecial(record.name === "special" ? "" : record.name);
return;
}
HyprlandService.focusWorkspace(WorkspaceModel.hyprlandSelector(record));
return;
case "mango":
MangoService.switchToTag(record.output, record.id);
return;
case "sway":
case "scroll":
case "miracle":
dispatchSwayWorkspace(typeof record.id === "number" ? {
"num": record.id
} : {
"name": record.id
});
return;
case "aqueous":
AqueousService.activateWorkspace({
"id": record.id,
"aqueousSession": record.session
});
return;
case "umbriel":
UmbrielService.action(`workspace-switch:${record.idx}/${record.output}`);
return;
}
}
function stepWorkspace(workspaces, currentKey, direction) {
if (workspaces.length < 2)
return;
const index = workspaces.findIndex(ws => ws && _workspaceKey(ws) === currentKey);
switchToWorkspace(WorkspaceModel.neighbor(workspaces, Math.max(index, 0), direction));
}
function scrollWorkspace(screenName, followFocus, showAllTags, direction) {
switch (compositor) {
case "niri":
{
const state = _niriWorkspaceState();
stepWorkspace(WorkspaceModel.niriWorkspacesForScreen(state, screenName, followFocus, false, _workspaceRecords), WorkspaceModel.niriCurrentIdx(state, screenName, followFocus), direction);
return;
}
case "hyprland":
{
const state = _hyprlandWorkspaceState();
stepWorkspace(WorkspaceModel.hyprlandScrollWorkspaces(state, screenName, followFocus), WorkspaceModel.hyprlandScrollCurrentId(state, screenName), direction);
return;
}
case "mango":
{
const state = _mangoWorkspaceState(screenName);
stepWorkspace(WorkspaceModel.mangoScrollWorkspaces(state, screenName, showAllTags), WorkspaceModel.mangoScrollCurrentTag(state), direction);
return;
}
case "sway":
case "scroll":
case "miracle":
{
const state = _i3WorkspaceState();
stepWorkspace(WorkspaceModel.i3ScrollWorkspaces(state, screenName, followFocus), WorkspaceModel.i3CurrentKey(state, screenName, followFocus), direction);
return;
}
case "aqueous":
_scrollAqueousWorkspace(followFocus ? AqueousService.focusedOutput : screenName, direction);
return;
case "umbriel":
{
const name = _followedScreen(screenName, followFocus);
stepWorkspace(WorkspaceModel.umbrielWorkspacesForScreen(UmbrielService.workspaces, name, false, _workspaceRecords), WorkspaceModel.umbrielCurrentId(UmbrielService.workspaces, name), direction);
return;
}
}
}
function _scrollAqueousWorkspace(screenName, direction) {
if (!AqueousService.available)
return;
const state = _aqueousWorkspaceState(screenName);
const workspaces = WorkspaceModel.aqueousWorkspacesForScreen(state, screenName, false, _workspaceRecords);
if (workspaces.length < 2)
return;
const currentId = WorkspaceModel.aqueousCurrentId(state);
const index = workspaces.findIndex(ws => ws.id === currentId);
if (index < 0)
return;
switchToWorkspace(WorkspaceModel.neighbor(workspaces, index, direction));
}
function overviewActiveForScreen(screenName) {
switch (compositor) {
case "niri":
return NiriService.inOverview;
case "mango":
return MangoService.isOutputInOverview(screenName);
case "aqueous":
return overviewActiveOnScreen(screenName);
default:
return false;
}
}
function workspacesHiddenByOverview(screenName) {
switch (compositor) {
case "mango":
return MangoService.isOutputInOverview(screenName);
default:
return false;
}
}
function toggleOverview(screenName) {
switch (compositor) {
case "aqueous":
AqueousService.toggleOverview(screenName);
return;
case "niri":
NiriService.toggleOverview();
return;
case "mango":
if (MangoService.getOutputState(screenName))
MangoService.dispatchOnOutput(screenName, "toggleoverview");
else
MangoService.dispatch("toggleoverview");
return;
}
}
function workspaceSecondaryAction(record, screenName) {
switch (compositor) {
case "aqueous":
case "niri":
toggleOverview(screenName);
return;
case "mango":
if (record)
MangoService.toggleTag(screenName, record.id);
return;
}
}
readonly property bool canStepWindowFocus: compositor === "niri" || compositor === "hyprland" || compositor === "mango"
function stepWindowFocus(screenName, direction) {
switch (compositor) {
case "niri":
if (direction > 0)
NiriService.focusColumnRight(screenName);
else
NiriService.focusColumnLeft(screenName);
return true;
case "hyprland":
if (screenName && screenName !== Hyprland.focusedMonitor?.name)
HyprlandService.focusMonitor(screenName);
HyprlandService.cycleWindow(direction > 0);
return true;
case "mango":
{
const command = direction > 0 ? "focusstack,next" : "focusstack,prev";
if (screenName)
MangoService.dispatchOnOutput(screenName, command);
else
MangoService.dispatch(command);
return true;
}
default:
return false;
}
}
function focusWindow(windowId) {
switch (compositor) {
case "aqueous":
AqueousService.command("window.activate", windowId);
return true;
case "hyprland":
HyprlandService.focusWindow(windowId);
return true;
case "niri":
NiriService.focusWindow(windowId);
return true;
case "umbriel":
UmbrielService.action("window-focus:" + windowId);
return true;
default:
return false;
}
}
function moveWorkspace(record, target) {
switch (compositor) {
case "niri":
NiriService.moveWorkspaceToIndex(record.id, target.idx);
return;
}
}
Binding {
target: SettingsData
property: "activeCompositor"
value: root.compositor
}
Connections {
target: SettingsData
function onCompositorLayoutRefreshNeeded(frame) {
if (root.isNiri && typeof NiriService !== "undefined")
NiriService.generateNiriLayoutConfig(frame);
if (root.isHyprland && typeof HyprlandService !== "undefined")
HyprlandService.generateLayoutConfig(frame);
if (!frame && root.isMango && typeof MangoService !== "undefined")
MangoService.generateLayoutConfig();
}
function onBlurStrengthRefreshNeeded() {
if (root.isAqueous && typeof AqueousConfigService !== "undefined") {
AqueousConfigService.applyBlurStrength();
return;
}
onCompositorLayoutRefreshNeeded(false);
}
function onCompositorInputRefreshNeeded() {
if (root.isNiri && typeof NiriService !== "undefined")
NiriService.generateNiriInputConfig();
if (root.isHyprland && typeof HyprlandService !== "undefined")
HyprlandService.generateInputConfig();
if (root.isMango && typeof MangoService !== "undefined")
MangoService.generateInputConfig();
}
function onCompositorCursorRefreshNeeded() {
if (root.isNiri && typeof NiriService !== "undefined") {
NiriService.generateNiriCursorConfig();
return;
}
if (root.isHyprland && typeof HyprlandService !== "undefined") {
HyprlandService.generateCursorConfig();
return;
}
if (root.isMango && typeof MangoService !== "undefined") {
MangoService.generateCursorConfig();
return;
}
}
}
}