mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-28 05:57:47 -04:00
503 lines
18 KiB
QML
503 lines
18 KiB
QML
pragma Singleton
|
|
pragma ComponentBehavior: Bound
|
|
|
|
import Quickshell
|
|
import QtQuick
|
|
import qs.Common
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
property var currentPopoutsByScreen: ({})
|
|
property var currentPopoutTriggers: ({})
|
|
|
|
// Set by the screenshot IPC handshake (dms screenshot region select); cleared by end() or any popout/modal open.
|
|
property bool screenshotActive: false
|
|
|
|
signal popoutOpening
|
|
signal popoutChanged
|
|
signal popoutToggledClosed(string screenName)
|
|
|
|
property real hoverCursorGlobalX: 0
|
|
property real hoverCursorGlobalY: 0
|
|
|
|
function updateHoverCursor(gx, gy) {
|
|
hoverCursorGlobalX = gx;
|
|
hoverCursorGlobalY = gy;
|
|
}
|
|
|
|
function cursorOverBar(gx, gy, padding, excludedWindow) {
|
|
const pad = padding !== undefined ? padding : 16;
|
|
const bars = KeyboardFocus.barWindows || [];
|
|
for (let i = 0; i < bars.length; i++) {
|
|
const w = bars[i];
|
|
if (!w?.visible || w === excludedWindow)
|
|
continue;
|
|
if (typeof w.containsGlobalPoint === "function") {
|
|
if (w.containsGlobalPoint(gx, gy, pad))
|
|
return true;
|
|
continue;
|
|
}
|
|
const item = w.contentItem;
|
|
if (!item || typeof item.mapToItem !== "function")
|
|
continue;
|
|
const topLeft = item.mapToItem(null, 0, 0);
|
|
if (!topLeft)
|
|
continue;
|
|
if (gx >= topLeft.x - pad && gx < topLeft.x + item.width + pad && gy >= topLeft.y - pad && gy < topLeft.y + item.height + pad)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function _openPopout(popout) {
|
|
if (popout.dashVisible !== undefined) {
|
|
let flagStayedTrue = popout.dashVisible === true;
|
|
if (popout.dashVisible && !popout.shouldBeVisible && !popout.isClosing) {
|
|
popout.dashVisible = false;
|
|
flagStayedTrue = false;
|
|
}
|
|
popout.dashVisible = true;
|
|
// Flag already true (e.g. retargeting to another monitor) won't re-fire
|
|
// the change handler, so drive the surface open explicitly.
|
|
if (flagStayedTrue)
|
|
_ensureSurfaceOpen(popout);
|
|
return;
|
|
}
|
|
if (popout.notificationHistoryVisible !== undefined) {
|
|
const flagStayedTrue = popout.notificationHistoryVisible === true;
|
|
popout.notificationHistoryVisible = true;
|
|
if (flagStayedTrue)
|
|
_ensureSurfaceOpen(popout);
|
|
return;
|
|
}
|
|
_ensureSurfaceOpen(popout);
|
|
}
|
|
|
|
function _ensureSurfaceOpen(popout) {
|
|
if (typeof popout.present === "function") {
|
|
popout.present();
|
|
return;
|
|
}
|
|
if (typeof popout.open === "function")
|
|
popout.open();
|
|
}
|
|
|
|
function _closePopout(popout) {
|
|
try {
|
|
if (popout.hoverDismissEnabled !== undefined)
|
|
popout.hoverDismissEnabled = false;
|
|
switch (true) {
|
|
case popout.dashVisible !== undefined:
|
|
popout.dashVisible = false;
|
|
return;
|
|
case popout.notificationHistoryVisible !== undefined:
|
|
popout.notificationHistoryVisible = false;
|
|
return;
|
|
default:
|
|
if (typeof popout.close !== "function")
|
|
return;
|
|
popout.close();
|
|
}
|
|
} catch (e) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
function _dismissPopoutFromHover(popout) {
|
|
try {
|
|
if (!popout || popout.hoverDismissEnabled !== true)
|
|
return;
|
|
if (typeof popout.closeFromHoverDismiss === "function") {
|
|
popout.closeFromHoverDismiss();
|
|
return;
|
|
}
|
|
_closePopout(popout);
|
|
} catch (e) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
function _isStale(popout) {
|
|
try {
|
|
if (!popout || !("shouldBeVisible" in popout))
|
|
return true;
|
|
if (!popout.screen)
|
|
return true;
|
|
return false;
|
|
} catch (e) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function showPopout(popout) {
|
|
if (!popout || !popout.screen)
|
|
return;
|
|
screenshotActive = false;
|
|
popoutOpening();
|
|
|
|
const screenName = popout.screen.name;
|
|
|
|
for (const otherScreenName in currentPopoutsByScreen) {
|
|
const otherPopout = currentPopoutsByScreen[otherScreenName];
|
|
if (!otherPopout || otherPopout === popout)
|
|
continue;
|
|
if (_isStale(otherPopout)) {
|
|
currentPopoutsByScreen[otherScreenName] = null;
|
|
continue;
|
|
}
|
|
_closePopout(otherPopout);
|
|
}
|
|
|
|
currentPopoutsByScreen[screenName] = popout;
|
|
popoutChanged();
|
|
ModalManager.closeAllModalsExcept(null);
|
|
}
|
|
|
|
function hidePopout(popout) {
|
|
if (!popout || !popout.screen)
|
|
return;
|
|
const screenName = popout.screen.name;
|
|
if (currentPopoutsByScreen[screenName] === popout) {
|
|
currentPopoutsByScreen[screenName] = null;
|
|
currentPopoutTriggers[screenName] = null;
|
|
popoutChanged();
|
|
}
|
|
}
|
|
|
|
function closeAllPopouts() {
|
|
for (const screenName in currentPopoutsByScreen) {
|
|
const popout = currentPopoutsByScreen[screenName];
|
|
if (!popout || _isStale(popout))
|
|
continue;
|
|
_closePopout(popout);
|
|
}
|
|
// Keep map entries until each popout's close animation finishes (hidePopout).
|
|
}
|
|
|
|
function dismissAllForScreen(screenName) {
|
|
if (!screenName)
|
|
return;
|
|
if (currentPopoutsByScreen[screenName])
|
|
closeAllPopouts();
|
|
if (ModalManager.currentModalsByScreen[screenName])
|
|
ModalManager.closeAllModalsExcept(null);
|
|
TrayMenuManager.closeAllMenus();
|
|
}
|
|
|
|
function closePopoutForScreen(screen) {
|
|
if (!screen)
|
|
return;
|
|
const screenName = screen.name;
|
|
const popout = currentPopoutsByScreen[screenName];
|
|
if (!popout || _isStale(popout)) {
|
|
currentPopoutsByScreen[screenName] = null;
|
|
currentPopoutTriggers[screenName] = null;
|
|
return;
|
|
}
|
|
_closePopout(popout);
|
|
}
|
|
|
|
function dismissHoverPopoutForScreen(screen) {
|
|
if (!screen)
|
|
return;
|
|
const screenName = screen.name;
|
|
const popout = currentPopoutsByScreen[screenName];
|
|
if (!popout || _isStale(popout)) {
|
|
currentPopoutsByScreen[screenName] = null;
|
|
currentPopoutTriggers[screenName] = null;
|
|
return;
|
|
}
|
|
_dismissPopoutFromHover(popout);
|
|
}
|
|
|
|
function cancelHoverDismiss(screen) {
|
|
const popout = getActivePopout(screen);
|
|
if (popout?.cancelHoverDismiss)
|
|
popout.cancelHoverDismiss();
|
|
}
|
|
|
|
function getActivePopout(screen) {
|
|
if (!screen)
|
|
return null;
|
|
return currentPopoutsByScreen[screen.name] || null;
|
|
}
|
|
|
|
// Checks if the active popout is pinned for auto-dismissal
|
|
function isActivePopoutPinned(screen) {
|
|
const p = getActivePopout(screen);
|
|
if (!p || !p.shouldBeVisible)
|
|
return false;
|
|
const dismissSuspended = p.effectiveHoverDismissSuspended ?? p.hoverDismissSuspended;
|
|
return p.hoverDismissEnabled === false || dismissSuspended === true;
|
|
}
|
|
|
|
function isCurrentPopout(popout, screenName) {
|
|
const name = screenName || popout?.screen?.name || "";
|
|
return !!name && currentPopoutsByScreen[name] === popout;
|
|
}
|
|
|
|
function _requestPopout(popout, tabIndex, triggerSource, hoverRequest) {
|
|
if (!popout || !popout.screen)
|
|
return;
|
|
|
|
// Clicks pin a popout, while clicking its active trigger toggles it closed below.
|
|
if (!hoverRequest && popout.hoverDismissEnabled !== undefined)
|
|
popout.hoverDismissEnabled = false;
|
|
|
|
screenshotActive = false;
|
|
const screenName = popout.screen.name;
|
|
const currentPopout = currentPopoutsByScreen[screenName];
|
|
const triggerId = triggerSource !== undefined ? triggerSource : tabIndex;
|
|
const alreadyPresented = currentPopout === popout && popout.shouldBeVisible;
|
|
|
|
const willOpen = !(alreadyPresented && triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId);
|
|
if (willOpen)
|
|
popoutOpening();
|
|
|
|
let movedFromOtherScreen = false;
|
|
for (const otherScreenName in currentPopoutsByScreen) {
|
|
if (otherScreenName === screenName)
|
|
continue;
|
|
const otherPopout = currentPopoutsByScreen[otherScreenName];
|
|
if (!otherPopout)
|
|
continue;
|
|
|
|
if (_isStale(otherPopout)) {
|
|
currentPopoutsByScreen[otherScreenName] = null;
|
|
currentPopoutTriggers[otherScreenName] = null;
|
|
continue;
|
|
}
|
|
|
|
if (otherPopout === popout) {
|
|
movedFromOtherScreen = true;
|
|
currentPopoutsByScreen[otherScreenName] = null;
|
|
currentPopoutTriggers[otherScreenName] = null;
|
|
continue;
|
|
}
|
|
|
|
_closePopout(otherPopout);
|
|
}
|
|
|
|
if (currentPopout && currentPopout !== popout) {
|
|
if (_isStale(currentPopout)) {
|
|
currentPopoutsByScreen[screenName] = null;
|
|
currentPopoutTriggers[screenName] = null;
|
|
} else {
|
|
if (hoverRequest && typeof currentPopout.beginSupersededClose === "function")
|
|
currentPopout.beginSupersededClose();
|
|
_closePopout(currentPopout);
|
|
}
|
|
}
|
|
|
|
if (alreadyPresented && !movedFromOtherScreen) {
|
|
const sameDefinedTrigger = triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId;
|
|
if (hoverRequest && sameDefinedTrigger)
|
|
return;
|
|
|
|
if (!hoverRequest && (triggerId === undefined || sameDefinedTrigger)) {
|
|
_closePopout(popout);
|
|
popoutToggledClosed(screenName);
|
|
return;
|
|
}
|
|
|
|
if (tabIndex !== undefined && popout.currentTabIndex !== undefined) {
|
|
popout.currentTabIndex = tabIndex;
|
|
}
|
|
if (popout.updateSurfacePosition)
|
|
popout.updateSurfacePosition();
|
|
currentPopoutTriggers[screenName] = triggerId;
|
|
if (hoverRequest && popout.hoverDismissEnabled !== undefined)
|
|
popout.hoverDismissEnabled = true;
|
|
return;
|
|
}
|
|
|
|
currentPopoutTriggers[screenName] = triggerId;
|
|
currentPopoutsByScreen[screenName] = popout;
|
|
popoutChanged();
|
|
|
|
if (tabIndex !== undefined && popout.currentTabIndex !== undefined) {
|
|
popout.currentTabIndex = tabIndex;
|
|
}
|
|
|
|
if (currentPopout !== popout) {
|
|
ModalManager.closeAllModalsExcept(null);
|
|
}
|
|
|
|
if (hoverRequest && popout.hoverDismissEnabled !== undefined)
|
|
popout.hoverDismissEnabled = true;
|
|
|
|
_openPopout(popout);
|
|
}
|
|
|
|
function requestPopout(popout, tabIndex, triggerSource) {
|
|
_requestPopout(popout, tabIndex, triggerSource, false);
|
|
}
|
|
|
|
function requestHoverPopout(popout, tabIndex, triggerSource) {
|
|
_requestPopout(popout, tabIndex, triggerSource, true);
|
|
}
|
|
|
|
function _islandBarBand(host, screen) {
|
|
const vertical = host.edge === "left" || host.edge === "right";
|
|
if (host.embedded) {
|
|
const start = vertical ? host.hostOriginX : host.hostOriginY;
|
|
return {
|
|
"start": start,
|
|
"end": start + host.bandThickness
|
|
};
|
|
}
|
|
if (host.freeMode)
|
|
return null;
|
|
|
|
const extent = vertical ? screen.width : screen.height;
|
|
const instances = ShellLayout.forScreen(screen)?.instances ?? [];
|
|
let start = Infinity;
|
|
let end = -Infinity;
|
|
for (const instance of instances) {
|
|
if (instance.edge !== host.edge || instance.barId === host.barId || (instance.kind !== "bar" && instance.kind !== "frame") || instance.rowThickness <= 0)
|
|
continue;
|
|
const config = SettingsData.getBarConfig(instance.barId);
|
|
if (!config || config.enabled === false || config.visible === false)
|
|
continue;
|
|
const near = instance.rowOffset ?? 0;
|
|
const far = near + instance.rowThickness;
|
|
const bandStart = host.edge === "top" || host.edge === "left" ? near : extent - far;
|
|
const bandEnd = host.edge === "top" || host.edge === "left" ? far : extent - near;
|
|
start = Math.min(start, bandStart);
|
|
end = Math.max(end, bandEnd);
|
|
}
|
|
return end > start ? {
|
|
"start": start,
|
|
"end": end
|
|
} : null;
|
|
}
|
|
|
|
function getActiveSurfaces() {
|
|
const results = [];
|
|
|
|
// The host registry covers embedded, standalone, and free Islands with their live visual bounds.
|
|
for (const key of Object.keys(IslandHostRegistry.hosts || {})) {
|
|
const host = IslandHostRegistry.hosts[key];
|
|
const screen = host?.screen;
|
|
const controller = host?.islandController;
|
|
const snapVisible = controller?.expanded || (!host?.embedded && host?.motionRunning);
|
|
if (!screen?.name || !snapVisible || host.surfaceOpacity <= 0)
|
|
continue;
|
|
const rect = host.surfaceBodyRect?.();
|
|
if (!rect || rect.width <= 0 || rect.height <= 0)
|
|
continue;
|
|
let x = rect.x;
|
|
let y = rect.y;
|
|
let width = rect.width;
|
|
let height = rect.height;
|
|
const barBand = _islandBarBand(host, screen);
|
|
if (barBand) {
|
|
switch (host.edge) {
|
|
case "top":
|
|
if (y < barBand.end && y + height > barBand.start) {
|
|
const inset = Math.max(0, Math.min(height, barBand.end - y));
|
|
y += inset;
|
|
height -= inset;
|
|
}
|
|
break;
|
|
case "bottom":
|
|
if (y + height > barBand.start && y < barBand.end)
|
|
height = Math.max(0, barBand.start - y);
|
|
break;
|
|
case "left":
|
|
if (x < barBand.end && x + width > barBand.start) {
|
|
const inset = Math.max(0, Math.min(width, barBand.end - x));
|
|
x += inset;
|
|
width -= inset;
|
|
}
|
|
break;
|
|
case "right":
|
|
if (x + width > barBand.start && x < barBand.end)
|
|
width = Math.max(0, barBand.start - x);
|
|
break;
|
|
}
|
|
}
|
|
const left = Math.max(0, x);
|
|
const top = Math.max(0, y);
|
|
const right = Math.min(screen.width, x + width);
|
|
const bottom = Math.min(screen.height, y + height);
|
|
if (right <= left || bottom <= top)
|
|
continue;
|
|
results.push({
|
|
"name": "dms:dankisland-surface",
|
|
"type": "island",
|
|
"x": Math.round((screen.x ?? 0) + left),
|
|
"y": Math.round((screen.y ?? 0) + top),
|
|
"width": Math.round(right - left),
|
|
"height": Math.round(bottom - top)
|
|
});
|
|
}
|
|
|
|
for (const screenName in currentPopoutsByScreen) {
|
|
const popout = currentPopoutsByScreen[screenName];
|
|
if (!popout || !popout.shouldBeVisible)
|
|
continue;
|
|
|
|
const host = popout.impl?.item ?? popout;
|
|
if (!host || !host.shouldBeVisible || host.isClosing)
|
|
continue;
|
|
|
|
const screen = host.surfaceScreen ?? popout.screen ?? null;
|
|
const sx = screen?.x ?? 0;
|
|
const sy = screen?.y ?? 0;
|
|
const px = host.renderedAlignedX;
|
|
const py = host.renderedAlignedY;
|
|
const pw = host.renderedAlignedWidth ?? popout.popupWidth ?? 0;
|
|
const ph = host.renderedAlignedHeight ?? popout.popupHeight ?? 0;
|
|
|
|
// Skip if origin is not yet laid out (undefined/null means not positioned).
|
|
if (px == null || py == null || pw <= 0 || ph <= 0)
|
|
continue;
|
|
|
|
results.push({
|
|
"name": popout.layerNamespace || "popout",
|
|
"type": "popout",
|
|
"x": Math.round(sx + px),
|
|
"y": Math.round(sy + py),
|
|
"width": Math.round(pw),
|
|
"height": Math.round(ph)
|
|
});
|
|
}
|
|
|
|
const modals = ModalManager.currentModalsByScreen || {};
|
|
for (const screenName in modals) {
|
|
const modal = modals[screenName];
|
|
if (!modal || !modal.shouldBeVisible)
|
|
continue;
|
|
|
|
const host = modal.impl?.item ?? modal;
|
|
if (!host || !host.shouldBeVisible || host.isClosing)
|
|
continue;
|
|
|
|
const screen = host.effectiveScreen ?? modal.targetScreen ?? null;
|
|
const sx = screen?.x ?? 0;
|
|
const sy = screen?.y ?? 0;
|
|
const mx = host.alignedX;
|
|
const my = host.alignedY;
|
|
const mw = host.alignedWidth ?? modal.modalWidth ?? 0;
|
|
const mh = host.alignedHeight ?? modal.modalHeight ?? 0;
|
|
|
|
// Skip if origin is not yet laid out.
|
|
if (mx == null || my == null || mw <= 0 || mh <= 0)
|
|
continue;
|
|
|
|
results.push({
|
|
"name": modal.layerNamespace || "modal",
|
|
"type": "modal",
|
|
"x": Math.round(sx + mx),
|
|
"y": Math.round(sy + my),
|
|
"width": Math.round(mw),
|
|
"height": Math.round(mh)
|
|
});
|
|
}
|
|
|
|
return results;
|
|
}
|
|
}
|