mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-30 15:07:49 -04:00
fix(ipc): open bar widget popouts at the right bar, hidden bars included
- References #3786
This commit is contained in:
1 parent
ad964512a9
commit
87ce92703c
5 files changed
+140
-30
No files matched your search
+30
-20
@@ -1,4 +1,5 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Services.SystemTray
|
||||
@@ -33,29 +34,38 @@ Item {
|
||||
for (const screenName in BarWidgetService.frameHostedBars)
|
||||
bars.push(...BarWidgetService.frameBarsForScreen(screenName));
|
||||
|
||||
let currentBar = null;
|
||||
let islandBar = null;
|
||||
for (const bar of bars) {
|
||||
if (!bar)
|
||||
continue;
|
||||
|
||||
const onFocusedScreen = focusedScreenName && bar.modelData?.name === focusedScreenName;
|
||||
const hasRef = !refPropertyName || !!bar[refPropertyName];
|
||||
if (!hasRef)
|
||||
continue;
|
||||
|
||||
if (bar.isIsland) {
|
||||
if (!islandBar || onFocusedScreen)
|
||||
islandBar = bar;
|
||||
continue;
|
||||
const configOrder = SettingsData.barConfigs.map(config => config.id);
|
||||
const screenOrder = Quickshell.screens.map(screen => screen.name);
|
||||
const lastBarId = focusedScreenName ? SettingsData.lastUsedBarByScreen[focusedScreenName] : "";
|
||||
// Same order as BarWidgetService.resolveIpcWidget, so every IPC path picks the same bar.
|
||||
const rank = bar => {
|
||||
const screenName = bar.modelData?.name;
|
||||
const onFocused = !!focusedScreenName && screenName === focusedScreenName;
|
||||
const registration = refPropertyName ? BarWidgetService.registrationForItem(bar[refPropertyName]) : null;
|
||||
const tier = !refPropertyName || BarWidgetService.registrationShown(registration) ? 0 : BarWidgetService.registrationLive(registration) ? 1 : 2;
|
||||
const position = bar.barConfig?.position;
|
||||
return [bar.isIsland ? 1 : 0, onFocused ? 0 : 1, tier, onFocused && bar.barConfig?.id === lastBarId ? 0 : 1, screenOrder.indexOf(screenName), position === SettingsData.Position.Left || position === SettingsData.Position.Right ? 1 : 0, configOrder.indexOf(bar.barConfig?.id)];
|
||||
};
|
||||
const compare = (a, b) => {
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i])
|
||||
return a[i] - b[i];
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
currentBar = bar;
|
||||
if (onFocusedScreen)
|
||||
break;
|
||||
let best = null;
|
||||
let bestRank = null;
|
||||
for (const bar of bars) {
|
||||
if (!bar || (refPropertyName && !bar[refPropertyName]))
|
||||
continue;
|
||||
const barRank = rank(bar);
|
||||
if (!best || compare(barRank, bestRank) < 0) {
|
||||
best = bar;
|
||||
bestRank = barRank;
|
||||
}
|
||||
}
|
||||
|
||||
return currentBar ?? islandBar;
|
||||
return best;
|
||||
}
|
||||
|
||||
readonly property var defaultAppMimeTypes: ({
|
||||
|
||||
@@ -140,9 +140,17 @@ Item {
|
||||
if (!loader.item)
|
||||
return;
|
||||
loader.item.screen = screen;
|
||||
positionUnresolvedPopout(loader.item, systemUpdateButtonRef);
|
||||
PopoutManager.requestPopout(loader.item, undefined, "systemUpdate");
|
||||
}
|
||||
|
||||
function positionUnresolvedPopout(popout, item) {
|
||||
const section = item?.section || "right";
|
||||
const anchor = item ?? dashSectionItem(section);
|
||||
if (anchor)
|
||||
topBarContent.surfaceContext.positionPopout(popout, anchor, section);
|
||||
}
|
||||
|
||||
function triggerControlCenter() {
|
||||
if (BarWidgetService.triggerWidgetPopout("controlCenterButton", {
|
||||
screenName: screen?.name,
|
||||
@@ -157,6 +165,7 @@ Item {
|
||||
if (!loader.item)
|
||||
return;
|
||||
loader.item.triggerScreen = screen;
|
||||
positionUnresolvedPopout(loader.item, controlCenterButtonRef);
|
||||
loader.item.toggle();
|
||||
if (loader.item.shouldBeVisible && NetworkService.wifiEnabled)
|
||||
NetworkService.scanWifi();
|
||||
|
||||
@@ -84,7 +84,7 @@ Item {
|
||||
}
|
||||
|
||||
function _finishWidgetPopoutOpen(spec, popout) {
|
||||
if (spec.registration && !BarWidgetService.registrationActive(spec.registration))
|
||||
if (spec.registration && !BarWidgetService.registrationActive(spec.registration, true))
|
||||
return false;
|
||||
const effectiveBarConfig = barConfig;
|
||||
const barPosition = getBarPosition();
|
||||
|
||||
@@ -121,15 +121,15 @@ Singleton {
|
||||
return null;
|
||||
}
|
||||
|
||||
function registrationActive(entry) {
|
||||
return registrationLive(entry) && entry.item.effectiveVisible !== false;
|
||||
function registrationActive(entry, includeHidden = false) {
|
||||
return registrationLive(entry, includeHidden) && entry.item.effectiveVisible !== false;
|
||||
}
|
||||
|
||||
function registrationShown(entry) {
|
||||
return registrationActive(entry) && entry.context?.owner?.widgetEnabled !== false;
|
||||
}
|
||||
|
||||
function registrationLive(entry) {
|
||||
function registrationLive(entry, includeHidden = false) {
|
||||
if (!entry?.item || widgetRegistry[entry.key] !== entry)
|
||||
return false;
|
||||
const context = entry.context;
|
||||
@@ -137,18 +137,47 @@ Singleton {
|
||||
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))
|
||||
if (!includeHidden && 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) {
|
||||
// IPC passes includeHidden: a hidden bar's widget still opens, anchored where the bar sits.
|
||||
function resolveWidget(widgetId, target, includeHidden = false) {
|
||||
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);
|
||||
return select(registrationShown) ?? select(registrationLive) ?? (includeHidden ? select(entry => registrationLive(entry, true)) : null);
|
||||
}
|
||||
|
||||
// Matches getPreferredBar in DMSShellIPC: focused screen, then visible before hidden, then the last used bar.
|
||||
function resolveIpcWidget(widgetId) {
|
||||
const configs = SettingsData.barConfigs.concat(SettingsData.dockConfigs ?? []).map(config => config.id);
|
||||
const screens = Quickshell.screens.map(screen => screen.name);
|
||||
const screenName = getFocusedScreenName();
|
||||
const lastBarId = screenName ? SettingsData.lastUsedBarByScreen[screenName] : "";
|
||||
const tiers = [registrationShown, registrationLive, entry => registrationLive(entry, true)];
|
||||
const focusedTargets = screenName ? [
|
||||
{
|
||||
screenName,
|
||||
barId: lastBarId
|
||||
},
|
||||
{
|
||||
screenName
|
||||
}
|
||||
] : [];
|
||||
for (const targets of [focusedTargets, [{}]]) {
|
||||
for (const eligible of tiers) {
|
||||
for (const target of targets) {
|
||||
const hit = Registry.select(widgetRegistry, widgetId, target, configs, screens, eligible);
|
||||
if (hit)
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getWidget(widgetId, screenName, instanceId) {
|
||||
@@ -198,7 +227,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function getWidgetOnFocusedScreen(widgetId) {
|
||||
return getWidget(widgetId, getFocusedScreenName()) || getWidget(widgetId);
|
||||
return resolveIpcWidget(widgetId)?.item ?? null;
|
||||
}
|
||||
|
||||
readonly property bool focusedScreenDetectionSupported: (CompositorService.isAqueous && AqueousService.available) || CompositorService.isHyprland || CompositorService.isNiri || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle || CompositorService.isUmbriel
|
||||
@@ -214,7 +243,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function hasWidget(widgetId) {
|
||||
return getWidget(widgetId) !== null;
|
||||
return resolveIpcWidget(widgetId) !== null;
|
||||
}
|
||||
|
||||
function ensureWidget(widgetId) {
|
||||
@@ -224,7 +253,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function triggerWidgetPopout(widgetId, target) {
|
||||
const widget = target ? resolveWidget(widgetId, target)?.item : getWidgetOnFocusedScreen(widgetId);
|
||||
const widget = target ? resolveWidget(widgetId, target, true)?.item : getWidgetOnFocusedScreen(widgetId);
|
||||
if (!widget)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -76,3 +76,65 @@ test("toggleReveal only works on auto hide bars and unhides first", () => {
|
||||
assert.deepEqual(reveals.slice(1), [["toggle", "hidden"], ["toggle", "hidden"]]);
|
||||
bars[2].visible = false;
|
||||
});
|
||||
|
||||
const ipcBarBody = source.slice(source.indexOf(" function getPreferredBar("), source.indexOf(" readonly property var defaultAppMimeTypes"));
|
||||
const serviceSource = readFileSync(new URL("../Services/BarWidgetService.qml", import.meta.url), "utf8");
|
||||
const serviceFunctions = ["registrationActive", "registrationShown", "registrationLive", "resolveWidget", "resolveIpcWidget"].map(name => serviceSource.match(new RegExp(`^ {4}function ${name}\\([\\s\\S]*?^ {4}\\}$`, "m"))[0]);
|
||||
const registrySource = readFileSync(new URL("../Common/InstanceRegistry.js", import.meta.url), "utf8").replace(/^\.pragma.*$/m, "");
|
||||
|
||||
// Both IPC paths, getPreferredBar for control center and updates, resolveIpcWidget for `widget`, over one registry.
|
||||
function ipcBarChoice(bars, { focused = "", lastUsed = {}, screens = ["A", "B"] } = {}) {
|
||||
const registry = vm.createContext({});
|
||||
vm.runInContext(registrySource, registry);
|
||||
const widgetRegistry = {};
|
||||
const windows = bars.map(spec => {
|
||||
const instanceId = `bar:${spec.id}:right:cc`;
|
||||
const config = { id: spec.id, enabled: true, visible: !spec.hidden, position: spec.position ?? 0 };
|
||||
const entry = {
|
||||
key: registry.key(spec.screen, instanceId),
|
||||
widgetId: "controlCenterButton",
|
||||
screenName: spec.screen,
|
||||
instanceId,
|
||||
item: { effectiveVisible: true },
|
||||
context: { barId: spec.id, kind: "bar", section: "right", occurrenceOrder: 0, owner: { active: true, widgetEnabled: !spec.disabled }, surface: { screen: { name: spec.screen }, config, host: { barRevealed: false } } }
|
||||
};
|
||||
widgetRegistry[entry.key] = entry;
|
||||
return { id: spec.id, modelData: { name: spec.screen }, barConfig: config, isIsland: false, controlCenterButtonRef: entry.item };
|
||||
});
|
||||
const context = vm.createContext({
|
||||
Registry: registry,
|
||||
widgetRegistry,
|
||||
Quickshell: { screens: screens.map(name => ({ name })) },
|
||||
SettingsData: { barConfigs: windows.map(bar => bar.barConfig), dockConfigs: [], lastUsedBarByScreen: lastUsed, Position: { Top: 0, Bottom: 1, Left: 2, Right: 3 } },
|
||||
ShellLayout: { forScreen: name => ({ instances: windows.filter(bar => bar.modelData.name === name).map(bar => ({ barId: bar.id })) }) },
|
||||
root: { dankBarRepeater: { count: windows.length, itemAt: i => ({ item: { barVariants: { instances: [windows[i]] } } }) } },
|
||||
getFocusedScreenName: () => focused
|
||||
});
|
||||
for (const fn of serviceFunctions)
|
||||
vm.runInContext(fn.replace(/^ {4}/gm, ""), context);
|
||||
context.BarWidgetService = {
|
||||
getFocusedScreenName: () => focused,
|
||||
frameHostedBars: {},
|
||||
frameBarsForScreen: () => [],
|
||||
registrationForItem: item => Object.values(widgetRegistry).find(entry => entry.item === item) ?? null,
|
||||
registrationShown: context.registrationShown,
|
||||
registrationLive: context.registrationLive
|
||||
};
|
||||
vm.runInContext(ipcBarBody.replace(/^ {4}/gm, ""), context);
|
||||
return [context.getPreferredBar("controlCenterButtonRef")?.id ?? null, context.resolveIpcWidget("controlCenterButton")?.context.barId ?? null];
|
||||
}
|
||||
|
||||
test("every IPC path picks the same bar, visible first", () => {
|
||||
const cases = [
|
||||
["visible bar beats a hidden one on the same screen", [{ id: "main", screen: "A", hidden: true }, { id: "second", screen: "A" }], { focused: "A" }, "second"],
|
||||
["last used bar wins among visible bars", [{ id: "main", screen: "A" }, { id: "second", screen: "A" }], { focused: "A", lastUsed: { A: "second" } }, "second"],
|
||||
["a visible bar beats a hidden last used one", [{ id: "main", screen: "A", hidden: true }, { id: "second", screen: "A" }], { focused: "A", lastUsed: { A: "main" } }, "second"],
|
||||
["a shown bar beats a disabled last used one", [{ id: "main", screen: "A", disabled: true }, { id: "second", screen: "A" }], { focused: "A", lastUsed: { A: "main" } }, "second"],
|
||||
["a hidden bar still answers when it is the only one", [{ id: "main", screen: "A", hidden: true }], { focused: "A" }, "main"],
|
||||
["focused screen beats visibility elsewhere", [{ id: "main", screen: "A" }, { id: "other", screen: "B", hidden: true }], { focused: "B" }, "other"],
|
||||
["unknown focus falls back to screen order", [{ id: "main", screen: "A" }, { id: "other", screen: "B" }], { screens: ["B", "A"] }, "other"],
|
||||
["horizontal bar beats vertical in a tie", [{ id: "side", screen: "A", position: 2 }, { id: "top", screen: "A" }], { focused: "A" }, "top"]
|
||||
];
|
||||
for (const [name, bars, opts, expected] of cases)
|
||||
assert.deepEqual(ipcBarChoice(bars, opts), [expected, expected], name);
|
||||
});
|
||||
Reference in new issue
Block a user