Files
DankMaterialShell/quickshell/Services/BarWidgetService.qml
T

300 lines
12 KiB
QML

pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.Common
import "../Common/InstanceRegistry.js" as Registry
Singleton {
id: root
property var widgetRegistry: ({})
property var onDemandWidgetIds: []
property var dankBarRepeater: null
property var frameHostedBars: ({})
signal dockEditRequested(string dockId)
property var dockContextMenu: null
property var dockTrashContextMenu: null
function registerFrameBar(screenName, barId, body) {
if (!screenName || !barId || !body)
return;
const next = Object.assign({}, frameHostedBars);
const bars = Object.assign({}, next[screenName] ?? {});
bars[barId] = body;
next[screenName] = bars;
frameHostedBars = next;
}
function unregisterFrameBar(screenName, barId, body) {
if (!screenName || !barId || frameHostedBars[screenName]?.[barId] !== body)
return;
const next = Object.assign({}, frameHostedBars);
const bars = Object.assign({}, next[screenName]);
delete bars[barId];
if (Object.keys(bars).length === 0)
delete next[screenName];
else
next[screenName] = bars;
frameHostedBars = next;
}
function frameBarsForScreen(screenName) {
const bars = frameHostedBars[screenName] ?? {};
const order = SettingsData.barConfigs.map(cfg => cfg.id);
const vertical = barId => {
const position = SettingsData.getBarConfig(barId)?.position ?? SettingsData.Position.Top;
return (position === SettingsData.Position.Left || position === SettingsData.Position.Right) ? 1 : 0;
};
return Object.keys(bars).sort((a, b) => (vertical(a) - vertical(b)) || (order.indexOf(a) - order.indexOf(b))).map(barId => bars[barId]);
}
property var dankBarItems: ({})
function registerDankBarItem(barId, item) {
if (!barId || !item)
return;
const next = Object.assign({}, dankBarItems);
next[barId] = item;
dankBarItems = next;
}
function unregisterDankBarItem(barId, item) {
if (!barId || dankBarItems[barId] !== item)
return;
const next = Object.assign({}, dankBarItems);
delete next[barId];
dankBarItems = next;
}
signal widgetRegistered(string widgetId, string screenName)
signal widgetUnregistered(string widgetId, string screenName)
property int _legacySerial: 0
function registerWidget(widgetId, screenName, widgetRef, instanceId, context) {
if (!widgetId || !screenName || !widgetRef)
return null;
const previous = Object.values(widgetRegistry).find(entry => entry.item === widgetRef && entry.widgetId === widgetId && entry.screenName === screenName);
const identity = instanceId || previous?.instanceId || "legacy:" + (++_legacySerial);
const registration = {
key: Registry.key(screenName, identity),
widgetId,
screenName,
instanceId: identity,
item: widgetRef,
context: context ?? null
};
widgetRegistry = Object.assign({}, widgetRegistry, {
[registration.key]: registration
});
widgetRegistered(widgetId, screenName);
return registration;
}
function releaseWidget(registration) {
const next = Registry.release(widgetRegistry, registration);
if (next === widgetRegistry)
return;
widgetRegistry = next;
widgetUnregistered(registration.widgetId, registration.screenName);
}
function unregisterWidget(widgetId, screenName, widgetRef, instanceId) {
if (!widgetRef)
return;
const registration = Object.values(widgetRegistry).find(entry => entry.widgetId === widgetId && entry.screenName === screenName && entry.item === widgetRef && (!instanceId || entry.instanceId === instanceId));
releaseWidget(registration);
}
function registrationForItem(sourceItem) {
const entries = Object.values(widgetRegistry);
for (let item = sourceItem; item; item = item.parent) {
const entry = entries.find(entry => entry.item === item || entry.context?.owner === item);
if (entry)
return entry;
}
return null;
}
function registrationActive(entry) {
return registrationLive(entry) && entry.item.effectiveVisible !== false;
}
function registrationShown(entry) {
return registrationActive(entry) && entry.context?.owner?.widgetEnabled !== false;
}
function registrationLive(entry) {
if (!entry?.item || widgetRegistry[entry.key] !== entry)
return false;
const context = entry.context;
if (!context?.surface?.screen)
return true;
if (!context.owner?.active || context.surface.config.enabled === false)
return false;
if (context.surface.config.visible === false && !(context.surface.host?.barRevealed ?? context.surface.revealed))
return false;
if (context.kind === "dock")
return SettingsData.dockConfigsForScreen(context.surface.screen).some(config => config.id === context.barId);
return ShellLayout.forScreen(entry.screenName)?.instances.some(instance => instance.barId === context.barId) === true;
}
function resolveWidget(widgetId, target) {
const configs = SettingsData.barConfigs.concat(SettingsData.dockConfigs ?? []).map(config => config.id);
const screens = Quickshell.screens.map(screen => screen.name);
const select = eligible => Registry.select(widgetRegistry, widgetId, target, configs, screens, eligible);
return select(registrationShown) ?? select(registrationLive);
}
function getWidget(widgetId, screenName, instanceId) {
return resolveWidget(widgetId, {
screenName,
instanceId
})?.item ?? null;
}
function getWidgetInstance(widgetId, target) {
if (!target?.screenName || !target.barId || !target.section || !target.occurrenceId)
return null;
return resolveWidget(widgetId, target)?.item ?? null;
}
function naturalPopoutAnchor(screen, sourceItem, section) {
const source = registrationForItem(sourceItem);
if (source?.context?.kind !== "dock" && source?.context?.surface?.popupAnchor)
return source.context.surface.popupAnchor(source.item, section);
const widget = source ? resolveWidget(source.widgetId, {
screenName: screen?.name,
kind: "bar"
})?.item : null;
const context = registrationForItem(widget)?.context?.surface;
if (context?.popupAnchor)
return context.popupAnchor(widget, widget.section || section);
const bar = getBarWindowForScreen(screen?.name);
const config = widget?.barConfig ?? bar?.barConfig ?? null;
const position = config?.position ?? SettingsData.Position.Top;
const thickness = widget?.barThickness ?? bar?.effectiveBarThickness ?? Theme.barHeight;
const spacing = widget?.barSpacing ?? config?.spacing ?? Theme.spacingXS;
const targetSection = widget?.section ?? section ?? "center";
const vertical = position === SettingsData.Position.Left || position === SettingsData.Position.Right;
const fraction = targetSection === "left" ? 0 : targetSection === "right" ? 1 : 0.5;
const visual = widget?.visualContent ?? widget;
const point = visual ? visual.mapToItem(null, 0, 0) : Qt.point(vertical ? (position === SettingsData.Position.Left ? 0 : screen.width - thickness) : screen.width * fraction, vertical ? screen.height * fraction : (position === SettingsData.Position.Top ? 0 : screen.height - thickness));
const width = widget?.visualWidth ?? widget?.width ?? 0;
const trigger = SettingsData.getPopupTriggerPosition(point, screen, thickness, width, spacing, position, config);
return {
trigger,
section: targetSection,
position,
thickness,
spacing,
config
};
}
function getWidgetOnFocusedScreen(widgetId) {
return getWidget(widgetId, getFocusedScreenName()) || getWidget(widgetId);
}
readonly property bool focusedScreenDetectionSupported: (CompositorService.isAqueous && AqueousService.available) || CompositorService.isHyprland || CompositorService.isNiri || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle || CompositorService.isUmbriel
function getFocusedScreenName() {
return CompositorService.getFocusedScreenName();
}
function getRegisteredWidgetIds() {
if (typeof widgetRegistry !== "object" || widgetRegistry === null)
return [];
return [...new Set(Object.values(widgetRegistry).map(entry => entry.widgetId))];
}
function hasWidget(widgetId) {
return getWidget(widgetId) !== null;
}
function ensureWidget(widgetId) {
if (!hasWidget(widgetId) && !onDemandWidgetIds.includes(widgetId))
onDemandWidgetIds = [...onDemandWidgetIds, widgetId];
return hasWidget(widgetId);
}
function triggerWidgetPopout(widgetId, target) {
const widget = target ? resolveWidget(widgetId, target)?.item : getWidgetOnFocusedScreen(widgetId);
if (!widget)
return false;
const registration = registrationForItem(widget);
registration?.context?.surface?.ensureVisible(widget);
if (typeof widget.triggerPopout === "function") {
widget.triggerPopout();
return true;
}
const signalMap = {
"battery": "toggleBatteryPopup",
"vpn": "toggleVpnPopup",
"layout": "toggleLayoutPopup",
"clock": "clockClicked",
"cpuUsage": "cpuClicked",
"memUsage": "ramClicked",
"cpuTemp": "cpuTempClicked",
"gpuTemp": "gpuTempClicked"
};
const signalName = signalMap[widgetId];
if (signalName && typeof widget[signalName] === "function") {
widget[signalName]();
return true;
}
if (typeof widget.clicked === "function") {
widget.clicked();
return true;
}
if (widget.popoutTarget?.toggle) {
widget.popoutTarget.toggle();
return true;
}
return false;
}
function barsForScreen(screenName, barId) {
const bars = [];
for (const config of SettingsData.barConfigs) {
if (barId && config.id !== barId)
continue;
const instance = ShellLayout.forScreen(screenName)?.instances.find(instance => instance.barId === config.id);
if (!instance || instance.kind === "island")
continue;
const item = instance.kind === "frame" ? frameHostedBars[screenName]?.[config.id] : dankBarItems[config.id]?.barVariants?.instances.find(bar => bar.modelData?.name === screenName);
if (item)
bars.push(item);
}
return bars.sort((a, b) => Number(a.isVertical) - Number(b.isVertical));
}
function getBarWindowForScreen(screenName, barId) {
return barsForScreen(screenName, barId)[0] ?? null;
}
function getBarWindowOnFocusedScreen() {
return getBarWindowForScreen(getFocusedScreenName()) || getFirstBarWindow();
}
function getFirstBarWindow() {
for (const screen of Quickshell.screens) {
const bar = getBarWindowForScreen(screen.name);
if (bar)
return bar;
}
return null;
}
}