mango: discover layouts and fix monitor targeting and visibility

Closes #2924
This commit is contained in:
purian23 committed 2026-09-20 15:18:54 -04:00
1 parent 03a3ef28d2
commit c65cda2273
3 files changed
+57 -13

No files matched your search

+2
View File
@@ -164,6 +164,8 @@ function mangoVisibleWindows(windows, output, screenName) {
return windows.filter(win => {
if (!win || win.monitor !== screenName || win.is_minimized)
return false;
if (typeof win.is_visible === "boolean")
return win.is_visible;
return active.size === 0 || (win.tags || []).some(t => active.has(t));
});
}
+42 -13
View File
@@ -107,8 +107,10 @@ Singleton {
connected: root.available
onConnectionStateChanged: {
if (connected)
if (linkUp) {
send("watch all-monitors");
root.refreshLayouts();
}
}
parser: SplitParser {
@@ -122,7 +124,7 @@ Singleton {
connected: root.available
onConnectionStateChanged: {
if (connected)
if (linkUp)
send("watch all-clients");
}
@@ -161,6 +163,10 @@ Singleton {
property bool _dispatchConnectionUsed: false
function dispatch(command, callback) {
request("dispatch " + command, callback);
}
function request(command, callback) {
if (!root.available)
return;
_dispatchQueue.push({
@@ -175,7 +181,7 @@ Singleton {
return;
_dispatchConnectionUsed = true;
_dispatchInFlight = _dispatchQueue.shift();
dispatchSocket.send("dispatch " + _dispatchInFlight.command);
dispatchSocket.send(_dispatchInFlight.command);
}
function _onDispatchReply(line) {
@@ -472,6 +478,7 @@ Singleton {
}
if (shouldShowToast)
ToastService.showInfo(I18n.tr("mango: config reloaded"), "", "", "mango-config");
root.refreshLayouts();
});
}
@@ -479,26 +486,48 @@ Singleton {
dispatch("quit");
}
// mango tag dispatches act on the focused monitor; tagIndex is 0-based
// (dwl model), mango `view`/`toggleview` take a 1-based tag number.
function dispatchOnOutput(outputName, command) {
if (!outputName || !getOutputState(outputName))
return;
// Always queue the focus: IPC snapshots can lag behind earlier commands.
dispatch("focusmon," + outputName);
dispatch(command);
}
// The dwl model is 0-based; Mango view/toggleview take a 1-based tag number.
function switchToTag(outputName, tagIndex) {
dispatch("view," + (tagIndex + 1));
dispatchOnOutput(outputName, "view," + (tagIndex + 1));
}
function toggleTag(outputName, tagIndex) {
dispatch("toggleview," + (tagIndex + 1));
dispatchOnOutput(outputName, "toggleview," + (tagIndex + 1));
}
// mango's tiling layouts are a fixed compiled-in set the IPC doesn't expose,
// so mirror it here in mango's layouts[] order (layout_index aligns). The
// parallel name list exists because `setlayout` dispatches by name, not index.
readonly property var layouts: ["T", "S", "G", "M", "K", "CT", "RT", "VS", "VT", "VG", "VK", "DW", "F", "VF"]
readonly property var _layoutNames: ["tile", "scroller", "grid", "monocle", "deck", "center_tile", "right_tile", "vertical_scroller", "vertical_tile", "vertical_grid", "vertical_deck", "dwindle", "fair", "vertical_fair"]
// Mango before 0.16.1 has no `get layouts`; retain its compiled-in order.
property var _availableLayouts: null
readonly property var layouts: _availableLayouts ? _availableLayouts.map(layout => layout.symbol) : ["T", "S", "G", "M", "K", "CT", "RT", "VS", "VT", "VG", "VK", "DW", "F", "VF"]
readonly property var _layoutNames: _availableLayouts ? _availableLayouts.map(layout => layout.name) : ["tile", "scroller", "grid", "monocle", "deck", "center_tile", "right_tile", "vertical_scroller", "vertical_tile", "vertical_grid", "vertical_deck", "dwindle", "fair", "vertical_fair"]
function refreshLayouts() {
request("get layouts", line => {
let data;
try {
data = JSON.parse(line);
} catch (e) {
return;
}
if (!Array.isArray(data?.layouts) || data.layouts.length === 0)
return;
if (!data.layouts.every(layout => typeof layout?.name === "string" && layout.name.length > 0 && typeof layout.symbol === "string"))
return;
root._availableLayouts = data.layouts;
});
}
function setLayout(outputName, index) {
const name = _layoutNames[index];
if (name)
dispatch("setlayout," + name);
dispatchOnOutput(outputName, "setlayout," + name);
}
function cycleKeyboardLayout() {
+13
View File
@@ -174,6 +174,19 @@ test("hyprland: workspace membership against the focused workspace, pid from the
assert.equal(specialWorkspaceName(hypr, null), "");
});
test("mango: compositor visibility covers global windows and overview without counting hidden clients", () => {
const windows = [
{ id: 1, monitor: "DP-1", tags: [2], is_visible: true },
{ id: 2, monitor: "DP-1", tags: [1], is_visible: false },
{ id: 3, monitor: "DP-2", tags: [1], is_visible: true },
{ id: 4, monitor: "DP-1", tags: [1], is_visible: true, is_minimized: true },
{ id: 5, monitor: "DP-1", tags: [1] }
];
const visible = activeTags => Array.from(model.mangoVisibleWindows(windows, { activeTags }, "DP-1"), win => win.id);
assert.deepEqual(visible([1]), [1, 5]);
assert.deepEqual(visible([0]), [1]);
});
test("mango: pid by window id, overlap and maximized only count visible windows on the output's active tags", () => {
const m = fixture("mango");
assert.equal(mangoPid(m.twoOutputs.windows, { mangoWindowId: 1 }), 201);