mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-14 23:34:01 -04:00
630 lines
21 KiB
JavaScript
630 lines
21 KiB
JavaScript
.pragma library
|
|
|
|
function placeholder() {
|
|
return {
|
|
id: null,
|
|
idx: null,
|
|
name: "",
|
|
output: "",
|
|
active: false,
|
|
placeholder: true
|
|
};
|
|
}
|
|
|
|
function neighbor(workspaces, index, direction) {
|
|
const next = direction > 0 ? Math.min(index + 1, workspaces.length - 1) : Math.max(index - 1, 0);
|
|
return next === index ? null : workspaces[next];
|
|
}
|
|
|
|
function reuse(cache, id, record) {
|
|
const previous = cache[id];
|
|
if (previous && Object.keys(record).every(key => previous[key] === record[key]))
|
|
return previous;
|
|
cache[id] = record;
|
|
return record;
|
|
}
|
|
|
|
function dropStale(cache, ids) {
|
|
for (const key of Object.keys(cache)) {
|
|
if (!ids.has(key))
|
|
delete cache[key];
|
|
}
|
|
}
|
|
|
|
function niriFallback() {
|
|
return [
|
|
{
|
|
id: 1,
|
|
idx: 0,
|
|
name: ""
|
|
},
|
|
{
|
|
id: 2,
|
|
idx: 1,
|
|
name: ""
|
|
}
|
|
];
|
|
}
|
|
|
|
function niriRecord(ws) {
|
|
return {
|
|
id: ws.id,
|
|
idx: ws.idx,
|
|
name: ws.name ?? "",
|
|
output: ws.output ?? "",
|
|
active: ws.is_active === true,
|
|
placeholder: false
|
|
};
|
|
}
|
|
|
|
function niriCurrentIdx(raw, screenName, followFocus) {
|
|
if (raw.allWorkspaces.length === 0)
|
|
return 1;
|
|
if (!screenName || followFocus)
|
|
return raw.focusedIdx;
|
|
const active = raw.allWorkspaces.find(ws => ws.output === screenName && ws.is_active);
|
|
return active ? active.idx : 1;
|
|
}
|
|
|
|
function niriWorkspacesForScreen(raw, screenName, followFocus, occupiedOnly, cache) {
|
|
const all = raw.allWorkspaces;
|
|
if (all.length === 0)
|
|
return niriFallback().map(niriRecord);
|
|
dropStale(cache, new Set(all.map(ws => String(ws.id))));
|
|
const onScreen = !screenName || followFocus ? raw.currentOutputWorkspaces : all.filter(ws => ws.output === screenName);
|
|
const sorted = (onScreen.length > 0 ? onScreen : niriFallback()).slice().sort((a, b) => a.idx - b.idx);
|
|
const workspaces = occupiedOnly ? sorted.filter(ws => ws.is_active || (raw.windows?.some(win => win.workspace_id === ws.id) ?? false)) : sorted;
|
|
return workspaces.map(ws => reuse(cache, ws.id, niriRecord(ws)));
|
|
}
|
|
|
|
function niriActiveWorkspace(raw, screenName) {
|
|
const ws = raw.allWorkspaces.find(w => (!screenName || w.output === screenName) && w.is_active);
|
|
return ws ? niriRecord(ws) : null;
|
|
}
|
|
|
|
function niriWorkspaceActive(allWorkspaces, workspace) {
|
|
return allWorkspaces.some(ws => ws.id === workspace.id && ws.is_active);
|
|
}
|
|
|
|
function niriWindowsOnWorkspace(windows, workspace) {
|
|
return windows.filter(win => win && win.workspace_id === workspace.id);
|
|
}
|
|
|
|
function niriWorkspaceOccupied(windows, workspace) {
|
|
return windows?.some(win => win.workspace_id === workspace.id) ?? false;
|
|
}
|
|
|
|
function niriWorkspaceUrgent(windows, workspace) {
|
|
if (!workspace?.id)
|
|
return false;
|
|
return windows.some(win => win.workspace_id === workspace.id && win.is_urgent);
|
|
}
|
|
|
|
function hyprlandSpecialName(name) {
|
|
return name === "special" || name.startsWith("special:");
|
|
}
|
|
|
|
function hyprlandSpecial(ws) {
|
|
return !(ws.id > 0) && hyprlandSpecialName(ws.name ?? "");
|
|
}
|
|
|
|
function hyprlandOrder(a, b) {
|
|
const keyA = a.id < 0 ? Number.MAX_SAFE_INTEGER : a.id;
|
|
const keyB = b.id < 0 ? Number.MAX_SAFE_INTEGER : b.id;
|
|
if (keyA !== keyB)
|
|
return keyA - keyB;
|
|
return (a.name ?? "").localeCompare(b.name ?? "");
|
|
}
|
|
|
|
function hyprlandRecord(ws) {
|
|
return {
|
|
id: ws.id,
|
|
idx: ws.id > 0 ? ws.id : null,
|
|
name: ws.name ?? "",
|
|
output: ws.monitor?.name ?? "",
|
|
active: ws.active === true,
|
|
placeholder: false,
|
|
urgent: ws.urgent === true
|
|
};
|
|
}
|
|
|
|
function hyprlandCurrentId(raw, screenName, followFocus) {
|
|
if (!screenName || followFocus)
|
|
return raw.focusedWorkspace?.id || 1;
|
|
return raw.monitors.find(m => m.name === screenName)?.activeWorkspace?.id || 1;
|
|
}
|
|
|
|
function hyprlandMonitorWorkspaces(raw, workspaces, screenName) {
|
|
const monitorWorkspaces = workspaces.filter(ws => ws.monitor?.name === screenName);
|
|
if (monitorWorkspaces.length > 0)
|
|
return monitorWorkspaces.sort(hyprlandOrder);
|
|
const active = raw.monitors.find(m => m.name === screenName)?.activeWorkspace;
|
|
return active ? [active] : [];
|
|
}
|
|
|
|
function hyprlandListedWorkspaces(raw, screenName, followFocus, occupiedOnly) {
|
|
const regular = raw.workspaces.filter(ws => !hyprlandSpecial(ws));
|
|
if (regular.length === 0)
|
|
return [hyprlandRecord({
|
|
id: 1,
|
|
name: "1"
|
|
})];
|
|
const workspaces = !screenName || followFocus ? regular.slice().sort(hyprlandOrder) : hyprlandMonitorWorkspaces(raw, regular, screenName);
|
|
if (!occupiedOnly)
|
|
return workspaces.map(hyprlandRecord);
|
|
const currentId = hyprlandCurrentId(raw, screenName, followFocus);
|
|
const toplevels = raw.toplevels;
|
|
return workspaces.filter(ws => ws.id === currentId || toplevels.some(tl => tl.workspace?.id === ws.id)).map(hyprlandRecord);
|
|
}
|
|
|
|
function hyprlandRuleIds(workspaceString) {
|
|
const tokens = String(workspaceString ?? "").trim().split(/\s+/);
|
|
if (tokens.includes("s[true]"))
|
|
return [];
|
|
const ids = [];
|
|
for (const token of tokens) {
|
|
if (/^\d+$/.test(token)) {
|
|
ids.push(Number(token));
|
|
continue;
|
|
}
|
|
const range = /^r\[(\d+)-(\d+)\]$/.exec(token);
|
|
if (!range)
|
|
continue;
|
|
for (let id = Number(range[1]); id <= Number(range[2]); id++)
|
|
ids.push(id);
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
// `desc:` rules match a description prefix, like Hyprland's getMonitorFromDesc; a rule for an unplugged monitor binds nothing
|
|
function hyprlandRuleMonitor(raw, rule) {
|
|
const target = String(rule.monitor ?? "");
|
|
if (!target)
|
|
return "";
|
|
if (!target.startsWith("desc:"))
|
|
return raw.monitors.some(m => m.name === target) ? target : "";
|
|
const desc = target.slice(5).trim();
|
|
return raw.monitors.find(m => (m.description ?? m.lastIpcObject?.description ?? "").startsWith(desc))?.name ?? "";
|
|
}
|
|
|
|
function hyprlandBoundMonitor(raw, id) {
|
|
for (const rule of raw.workspaceRules ?? []) {
|
|
if (!hyprlandRuleIds(rule.workspaceString).includes(id))
|
|
continue;
|
|
const monitor = hyprlandRuleMonitor(raw, rule);
|
|
if (monitor)
|
|
return monitor;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// Hyprland creates a numbered workspace on demand, so ids up to minCount are real switch targets even before they exist
|
|
function hyprlandPersistentWorkspaces(raw, records, screenName, followFocus, minCount) {
|
|
if (!(minCount > 0))
|
|
return records;
|
|
const perMonitor = !!screenName && !followFocus;
|
|
const taken = new Set(records.map(ws => ws.id));
|
|
// a just-created workspace has no monitor until quickshell's j/workspaces refresh lands; claiming it for another monitor drops its slot for a frame
|
|
if (perMonitor)
|
|
raw.workspaces.forEach(ws => ws.id > 0 && ws.monitor && ws.monitor.name !== screenName && taken.add(ws.id));
|
|
const filled = records.slice();
|
|
for (let id = 1; id <= minCount; id++) {
|
|
if (taken.has(id))
|
|
continue;
|
|
if (perMonitor) {
|
|
const bound = hyprlandBoundMonitor(raw, id);
|
|
if (bound && bound !== screenName)
|
|
continue;
|
|
}
|
|
filled.push(hyprlandRecord({
|
|
id,
|
|
name: String(id),
|
|
monitor: {
|
|
name: perMonitor ? screenName : ""
|
|
}
|
|
}));
|
|
}
|
|
return filled.sort(hyprlandOrder);
|
|
}
|
|
|
|
function hyprlandSpecialDisplayName(name) {
|
|
return name.startsWith("special:") ? name.slice(8) : name;
|
|
}
|
|
|
|
// Hyprland >= 0.56 reports every special workspace with a null id, so specials only match by name
|
|
function hyprlandWorkspaceMatches(ws, record) {
|
|
if (record.special !== true)
|
|
return ws?.id === record.id;
|
|
const name = ws?.name ?? "";
|
|
return hyprlandSpecialName(name) && hyprlandSpecialDisplayName(name) === record.name;
|
|
}
|
|
|
|
// Hyprland overlays a special workspace on the monitor's regular one, so active comes from the tracked overlay, not ws.active
|
|
function hyprlandSpecialWorkspaces(raw, screenName, followFocus, occupiedOnly) {
|
|
const perMonitor = !!screenName && !followFocus;
|
|
const visible = raw.visibleSpecials ?? {};
|
|
const toplevels = raw.toplevels;
|
|
const isVisible = normalized => perMonitor ? visible[screenName] === normalized : Object.values(visible).includes(normalized);
|
|
const specials = raw.workspaces.filter(ws => hyprlandSpecial(ws) && (!perMonitor || ws.monitor?.name === screenName)).map(ws => {
|
|
const name = ws.name ?? "";
|
|
return {
|
|
id: ws.id,
|
|
idx: null,
|
|
name: hyprlandSpecialDisplayName(name),
|
|
output: ws.monitor?.name ?? "",
|
|
active: isVisible(name === "special" ? "special:special" : name),
|
|
placeholder: false,
|
|
urgent: ws.urgent === true,
|
|
special: true
|
|
};
|
|
}).filter(ws => !occupiedOnly || ws.active || toplevels.some(tl => hyprlandWorkspaceMatches(tl.workspace, ws))).sort((a, b) => a.name.localeCompare(b.name));
|
|
// the default scratchpad is always offered so it can be opened before Hyprland has created it
|
|
if (!specials.some(ws => ws.name === "special"))
|
|
specials.push({
|
|
id: null,
|
|
idx: null,
|
|
name: "special",
|
|
output: screenName ?? "",
|
|
active: isVisible("special:special"),
|
|
placeholder: false,
|
|
urgent: false,
|
|
special: true
|
|
});
|
|
return specials;
|
|
}
|
|
|
|
function hyprlandWorkspacesForScreen(raw, screenName, followFocus, occupiedOnly, minCount, showSpecial) {
|
|
const regular = hyprlandPersistentWorkspaces(raw, hyprlandListedWorkspaces(raw, screenName, followFocus, occupiedOnly), screenName, followFocus, minCount);
|
|
if (!showSpecial)
|
|
return regular;
|
|
return regular.concat(hyprlandSpecialWorkspaces(raw, screenName, followFocus, occupiedOnly));
|
|
}
|
|
|
|
function hyprlandScrollWorkspaces(raw, screenName, followFocus) {
|
|
const onScreen = !screenName || followFocus ? raw.workspaces : raw.workspaces.filter(ws => ws.lastIpcObject?.monitor === screenName);
|
|
const numbered = onScreen.filter(ws => ws.id > -1).sort((a, b) => a.id - b.id);
|
|
return (numbered.length > 0 ? numbered : [
|
|
{
|
|
id: 1,
|
|
name: "1"
|
|
}
|
|
]).map(hyprlandRecord);
|
|
}
|
|
|
|
function hyprlandScrollCurrentId(raw, screenName) {
|
|
return raw.monitors.find(m => m.name === screenName)?.activeWorkspace?.id ?? 1;
|
|
}
|
|
|
|
function hyprlandVisibleSpecial(raw, monitorName) {
|
|
const tracked = raw.visibleSpecials?.[monitorName];
|
|
if (!tracked)
|
|
return null;
|
|
// the overlay event can land before the workspace list refresh, so the tracked name is the OSD's key
|
|
const ws = raw.workspaces.find(ws => hyprlandSpecial(ws) && (ws.name === "special" ? "special:special" : ws.name) === tracked);
|
|
return {
|
|
id: ws?.id || tracked,
|
|
idx: null,
|
|
name: hyprlandSpecialDisplayName(tracked),
|
|
output: monitorName,
|
|
active: true,
|
|
placeholder: false,
|
|
special: true
|
|
};
|
|
}
|
|
|
|
function hyprlandActiveWorkspace(raw, screenName) {
|
|
const monitor = raw.monitors.find(m => !screenName || m.name === screenName);
|
|
const ws = monitor?.activeWorkspace;
|
|
if (!ws)
|
|
return null;
|
|
const special = hyprlandVisibleSpecial(raw, monitor.name ?? screenName);
|
|
if (special)
|
|
return special;
|
|
const name = ws.name ?? "";
|
|
if (hyprlandSpecialName(name))
|
|
return null;
|
|
return {
|
|
id: ws.id,
|
|
idx: ws.id > 0 ? ws.id : null,
|
|
name: name !== "" && name !== String(ws.id) ? name : "",
|
|
output: monitor?.name ?? screenName,
|
|
active: true,
|
|
placeholder: false
|
|
};
|
|
}
|
|
|
|
function hyprlandWindowsOnWorkspace(windows, workspace, toplevels) {
|
|
const workspaces = new Map();
|
|
for (const toplevel of toplevels) {
|
|
if (!workspaces.has(toplevel.wayland))
|
|
workspaces.set(toplevel.wayland, toplevel.workspace);
|
|
}
|
|
return windows.filter(win => win && hyprlandWorkspaceMatches(workspaces.get(win), workspace));
|
|
}
|
|
|
|
function hyprlandWorkspaceOccupied(toplevels, workspace) {
|
|
return toplevels.some(tl => hyprlandWorkspaceMatches(tl.workspace, workspace));
|
|
}
|
|
|
|
function mangoRecord(index, tag, output) {
|
|
const state = tag?.state ?? 0;
|
|
return {
|
|
id: index,
|
|
idx: index + 1,
|
|
name: "",
|
|
output,
|
|
active: state === 1,
|
|
placeholder: false,
|
|
urgent: state === 2,
|
|
occupied: (tag?.clients ?? 0) > 0
|
|
};
|
|
}
|
|
|
|
function mangoWorkspacesForScreen(raw, screenName, showAllTags, minCount) {
|
|
if (!raw.available)
|
|
return [];
|
|
const tags = raw.output?.tags;
|
|
if (!tags || tags.length === 0)
|
|
return [];
|
|
if (showAllTags)
|
|
return tags.map(tag => mangoRecord(tag.tag, tag, screenName));
|
|
if (!(minCount > 0))
|
|
return raw.visibleTags.map(index => mangoRecord(index, tags.find(tag => tag.tag === index), screenName));
|
|
// tags always exist per output, so the first minCount are real switch targets
|
|
const indices = raw.visibleTags.slice();
|
|
for (let index = 0; index < Math.min(minCount, tags.length); index++) {
|
|
if (!indices.includes(index))
|
|
indices.push(index);
|
|
}
|
|
return indices.sort((a, b) => a - b).map(index => mangoRecord(index, tags.find(tag => tag.tag === index), screenName));
|
|
}
|
|
|
|
function mangoCurrentTag(raw) {
|
|
if (!raw.available)
|
|
return -1;
|
|
const activeTags = raw.activeTags;
|
|
return activeTags.length > 0 ? activeTags[0] : -1;
|
|
}
|
|
|
|
function mangoScrollWorkspaces(raw, screenName, showAllTags) {
|
|
const current = mangoScrollCurrentTag(raw);
|
|
const tags = !raw.available ? [0] : showAllTags ? Array.from({
|
|
length: raw.tagCount
|
|
}, (_, i) => i) : raw.visibleTags;
|
|
return tags.map(index => ({
|
|
id: index,
|
|
idx: index + 1,
|
|
name: "",
|
|
output: screenName,
|
|
active: index === current,
|
|
placeholder: false
|
|
}));
|
|
}
|
|
|
|
function mangoScrollCurrentTag(raw) {
|
|
if (!raw.available || !raw.output?.tags)
|
|
return 0;
|
|
const activeTags = raw.activeTags;
|
|
return activeTags.length > 0 ? activeTags[0] : 0;
|
|
}
|
|
|
|
function mangoActiveWorkspace(raw, screenName) {
|
|
const activeTags = raw.activeTags;
|
|
if (activeTags.length === 0)
|
|
return null;
|
|
return {
|
|
id: activeTags[0],
|
|
idx: activeTags[0] + 1,
|
|
name: "",
|
|
output: screenName,
|
|
active: true,
|
|
placeholder: false
|
|
};
|
|
}
|
|
|
|
function mangoWindowsOnWorkspace(windows, workspace) {
|
|
// mangoTags are 1-based; workspace.id is 0-based.
|
|
return windows.filter(win => win && (win.mangoTags || []).includes(workspace.id + 1));
|
|
}
|
|
|
|
function i3Key(ws) {
|
|
return ws.num !== -1 ? ws.num : ws.name;
|
|
}
|
|
|
|
function i3Number(workspace) {
|
|
return typeof workspace.id === "number" ? workspace.id : -1;
|
|
}
|
|
|
|
function i3StripNumber(num, name) {
|
|
if (num === undefined || num === -1 || typeof name !== "string")
|
|
return name ?? "";
|
|
const prefix = num + ":";
|
|
return name.startsWith(prefix) ? name.slice(prefix.length) : name;
|
|
}
|
|
|
|
function i3Order(a, b) {
|
|
const keyA = a.num === -1 ? Number.MAX_SAFE_INTEGER : a.num;
|
|
const keyB = b.num === -1 ? Number.MAX_SAFE_INTEGER : b.num;
|
|
if (keyA !== keyB)
|
|
return keyA - keyB;
|
|
return (a.name ?? "").localeCompare(b.name ?? "");
|
|
}
|
|
|
|
function i3Record(ws) {
|
|
return {
|
|
id: i3Key(ws),
|
|
idx: ws.num !== -1 ? ws.num : null,
|
|
name: i3StripNumber(ws.num, ws.name),
|
|
output: ws.monitor?.name ?? "",
|
|
active: ws.active === true,
|
|
placeholder: false,
|
|
urgent: ws.urgent === true,
|
|
focused: ws.focused === true
|
|
};
|
|
}
|
|
|
|
function i3CurrentKey(raw, screenName, followFocus) {
|
|
const focused = !screenName || followFocus ? raw.workspaces.find(ws => ws.focused === true) : raw.workspaces.find(ws => ws.monitor?.name === screenName && ws.focused === true);
|
|
return focused ? i3Key(focused) : 1;
|
|
}
|
|
|
|
function i3ListedWorkspaces(raw, screenName, followFocus) {
|
|
const workspaces = raw.workspaces;
|
|
if (workspaces.length === 0)
|
|
return [
|
|
{
|
|
num: 1
|
|
}
|
|
];
|
|
if (!screenName || followFocus)
|
|
return workspaces.slice();
|
|
const onScreen = workspaces.filter(ws => ws.monitor?.name === screenName);
|
|
return onScreen.length > 0 ? onScreen : [
|
|
{
|
|
num: 1
|
|
}
|
|
];
|
|
}
|
|
|
|
// `workspace number N` creates the workspace on demand, so numbers up to minCount are real switch targets
|
|
function i3PersistentWorkspaces(raw, listed, screenName, followFocus, minCount) {
|
|
if (!(minCount > 0))
|
|
return listed;
|
|
const perMonitor = !!screenName && !followFocus;
|
|
const taken = new Set(listed.map(ws => ws.num));
|
|
if (perMonitor)
|
|
raw.workspaces.forEach(ws => ws.num !== -1 && ws.monitor?.name !== screenName && taken.add(ws.num));
|
|
const filled = listed.slice();
|
|
for (let num = 1; num <= minCount; num++) {
|
|
if (taken.has(num))
|
|
continue;
|
|
filled.push({
|
|
num,
|
|
name: String(num),
|
|
monitor: {
|
|
name: perMonitor ? screenName : ""
|
|
}
|
|
});
|
|
}
|
|
return filled;
|
|
}
|
|
|
|
function i3WorkspacesForScreen(raw, screenName, followFocus, minCount) {
|
|
return i3PersistentWorkspaces(raw, i3ListedWorkspaces(raw, screenName, followFocus), screenName, followFocus, minCount).sort(i3Order).map(i3Record);
|
|
}
|
|
|
|
function i3ScrollWorkspaces(raw, screenName, followFocus) {
|
|
const workspaces = raw.workspaces;
|
|
if (workspaces.length === 0)
|
|
return [i3Record({
|
|
num: 1
|
|
})];
|
|
const onScreen = !screenName || followFocus ? workspaces.slice() : workspaces.filter(ws => ws.monitor?.name === screenName);
|
|
return onScreen.length > 0 ? onScreen.sort((a, b) => a.num - b.num).map(i3Record) : [i3Record({
|
|
num: 1
|
|
})];
|
|
}
|
|
|
|
function i3ActiveWorkspace(raw, screenName) {
|
|
const onScreen = raw.workspaces.filter(w => !screenName || w.monitor?.name === screenName);
|
|
const ws = onScreen.find(w => w.focused) || onScreen.find(w => w.active) || onScreen[0];
|
|
if (!ws)
|
|
return null;
|
|
const num = ws.number;
|
|
const name = i3StripNumber(num, ws.name);
|
|
return {
|
|
id: num !== undefined && num !== -1 ? num : name,
|
|
idx: num !== undefined && num > 0 ? num : null,
|
|
name,
|
|
output: ws.monitor?.name ?? screenName,
|
|
active: true,
|
|
placeholder: false
|
|
};
|
|
}
|
|
|
|
function i3WorkspaceFocused(raw, workspace) {
|
|
const focused = raw.workspaces.find(ws => ws.focused === true);
|
|
return focused ? focused.num === i3Number(workspace) : false;
|
|
}
|
|
|
|
function i3WindowsOnWorkspace(windows, workspace) {
|
|
const number = i3Number(workspace);
|
|
return windows.filter(win => win && win.workspace?.num === number);
|
|
}
|
|
|
|
function aqueousRecord(ws, screenName) {
|
|
return {
|
|
id: ws.id,
|
|
idx: ws.number ?? null,
|
|
name: ws.name ?? "",
|
|
output: screenName,
|
|
active: ws.active === true,
|
|
placeholder: false,
|
|
urgent: ws.urgent === true,
|
|
session: ws.aqueousSession
|
|
};
|
|
}
|
|
|
|
function aqueousWorkspacesForScreen(raw, screenName, occupiedOnly, cache) {
|
|
const workspaces = raw.workspaces;
|
|
dropStale(cache, new Set(workspaces.map(ws => String(ws.id))));
|
|
const visible = occupiedOnly ? workspaces.filter(ws => ws.active || raw.toplevels.some(win => win.aqueousWorkspaceId === ws.id)) : workspaces;
|
|
return visible.map(ws => reuse(cache, ws.id, aqueousRecord(ws, screenName)));
|
|
}
|
|
|
|
function aqueousCurrentId(raw) {
|
|
return raw.workspaces.find(ws => ws.active)?.id || "";
|
|
}
|
|
|
|
function aqueousActiveWorkspace(raw, screenName) {
|
|
const ws = raw.workspaces.find(w => w.active);
|
|
return ws ? aqueousRecord(ws, screenName) : null;
|
|
}
|
|
|
|
function aqueousWindowsOnWorkspace(windows, workspace) {
|
|
return windows.filter(win => win && win.aqueousWorkspaceId === workspace.id);
|
|
}
|
|
|
|
function aqueousWorkspaceOccupied(toplevels, workspace) {
|
|
return toplevels.some(win => win.aqueousWorkspaceId === workspace.id);
|
|
}
|
|
|
|
function umbrielRecord(ws) {
|
|
return {
|
|
id: ws.id,
|
|
idx: ws.index,
|
|
name: ws.named === true ? ws.name : "",
|
|
output: ws.output ?? "",
|
|
active: ws.active === true,
|
|
placeholder: false,
|
|
occupied: ws.occupied === true
|
|
};
|
|
}
|
|
|
|
function umbrielCurrentId(workspaces, screenName) {
|
|
return workspaces.find(ws => ws.output === screenName && ws.active)?.id ?? "";
|
|
}
|
|
|
|
function umbrielWorkspacesForScreen(workspaces, screenName, occupiedOnly, cache) {
|
|
dropStale(cache, new Set(workspaces.map(ws => ws.id)));
|
|
const listed = workspaces.filter(ws => ws.output === screenName && (!occupiedOnly || ws.active || ws.occupied));
|
|
return listed.sort((a, b) => a.index - b.index).map(ws => reuse(cache, ws.id, umbrielRecord(ws)));
|
|
}
|
|
|
|
function umbrielActiveWorkspace(workspaces, screenName) {
|
|
const ws = workspaces.find(w => w.output === screenName && w.active);
|
|
return ws ? umbrielRecord(ws) : null;
|
|
}
|
|
|
|
function umbrielWindowsOnWorkspace(windows, workspace) {
|
|
return windows.filter(win => win.workspace === workspace.id).sort((a, b) => a.x - b.x || a.y - b.y || a.id.localeCompare(b.id)).map(win => ({
|
|
id: win.id,
|
|
app_id: win.app_id,
|
|
activated: win.active === true
|
|
}));
|
|
}
|
|
|
|
function umbrielWorkspaceUrgent(windows, workspace) {
|
|
return windows.some(win => win.workspace === workspace.id && win.urgent === true);
|
|
}
|