Files
noctalia-shell/Services/Compositor/ExtWorkspaceService.qml
T

301 lines
7.7 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.WindowManager
import qs.Commons
// Generic ext-workspace-v1 + toplevel handling
Item {
id: root
property ListModel workspaces: ListModel {}
property var windows: []
property int focusedWindowIndex: -1
property var trackedToplevels: new Set()
property bool globalWorkspaces: false
property var nativeWorkspaceMap: ({})
property var connectedWorkspaces: ({})
signal workspaceChanged
signal activeWindowChanged
signal windowListChanged
signal displayScalesChanged
function initialize() {
updateWindows();
connectWorkspaceSignals();
syncWorkspaces();
Logger.i("ExtWorkspaceService", "Service started (generic ext-workspace-v1)");
}
Connections {
target: WindowManager
function onWindowsetsChanged() {
root.connectWorkspaceSignals();
Qt.callLater(root.syncWorkspaces);
}
function onWindowsetProjectionsChanged() {
Qt.callLater(root.syncWorkspaces);
}
}
Timer {
interval: 500
running: true
repeat: false
onTriggered: {
if (WindowManager.windowsets.length > 0) {
root.connectWorkspaceSignals();
root.syncWorkspaces();
}
}
}
function connectWorkspaceSignals() {
const nativeWs = WindowManager.windowsets;
const newConnected = {};
for (const ws of nativeWs) {
const key = ws.id || ws.toString();
newConnected[key] = true;
if (connectedWorkspaces[key])
continue;
ws.activeChanged.connect(() => {
Qt.callLater(root.syncWorkspaces);
});
ws.urgentChanged.connect(() => {
Qt.callLater(root.syncWorkspaces);
});
ws.shouldDisplayChanged.connect(() => {
Qt.callLater(root.syncWorkspaces);
});
ws.nameChanged.connect(() => {
Qt.callLater(root.syncWorkspaces);
});
}
connectedWorkspaces = newConnected;
}
function syncWorkspaces() {
const nativeWs = WindowManager.windowsets;
workspaces.clear();
nativeWorkspaceMap = {};
/* Per-output (projection) index: compositors expose one workspace group per
* monitor with names "1"…"9". A single global idx produced 1018 on the
* second head because all windowsets were merged into one list. */
const perOutputNextIdx = {};
for (const ws of nativeWs) {
if (!ws.shouldDisplay) {
continue;
}
let outputName = "";
if (ws.projection) {
const projScreens = ws.projection.screens;
if (projScreens && projScreens.length > 0) {
outputName = projScreens[0].name || "";
}
}
const groupKey = outputName || "_";
let idx;
const numericName = ws.name && /^\d+$/.test(String(ws.name)) ? parseInt(ws.name, 10) : NaN;
if (!isNaN(numericName) && numericName >= 1) {
idx = numericName;
} else {
if (perOutputNextIdx[groupKey] === undefined) {
perOutputNextIdx[groupKey] = 1;
}
idx = perOutputNextIdx[groupKey]++;
}
const wsEntry = {
"id": ws.id || idx.toString(),
"idx": idx,
"name": ws.name || ("Workspace " + idx),
"output": outputName,
"isFocused": ws.active,
"isActive": true,
"isUrgent": ws.urgent,
"isOccupied": false,
"oid": ws.id || idx.toString()
};
workspaces.append(wsEntry);
nativeWorkspaceMap[wsEntry.id] = ws;
}
updateWindowWorkspaces();
workspaceChanged();
}
function updateWindowWorkspaces() {
let activeId = "";
for (let i = 0; i < workspaces.count; i++) {
const ws = workspaces.get(i);
if (ws.isFocused) {
activeId = ws.id;
break;
}
}
for (let i = 0; i < windows.length; i++) {
if (activeId) {
windows[i].workspaceId = activeId;
}
}
windowListChanged();
}
Connections {
target: ToplevelManager.toplevels
function onValuesChanged() {
updateWindows();
}
}
function connectToToplevel(toplevel) {
if (!toplevel)
return;
toplevel.activatedChanged.connect(() => {
Qt.callLater(onToplevelActivationChanged);
});
toplevel.titleChanged.connect(() => {
Qt.callLater(updateWindows);
});
}
function onToplevelActivationChanged() {
updateWindows();
activeWindowChanged();
}
function updateWindows() {
const newWindows = [];
const toplevels = ToplevelManager.toplevels?.values || [];
let focusedIdx = -1;
let idx = 0;
let activeId = "";
for (let i = 0; i < workspaces.count; i++) {
const ws = workspaces.get(i);
if (ws.isFocused) {
activeId = ws.id;
break;
}
}
for (const toplevel of toplevels) {
if (!toplevel)
continue;
if (!trackedToplevels.has(toplevel)) {
connectToToplevel(toplevel);
trackedToplevels.add(toplevel);
}
const output = (toplevel.screens && toplevel.screens.length > 0) ? (toplevel.screens[0].name || "") : "";
const windowId = (toplevel.appId || "") + ":" + idx;
newWindows.push({
"id": windowId,
"appId": toplevel.appId || "",
"title": toplevel.title || "",
"output": output,
"workspaceId": activeId || "1",
"isFocused": toplevel.activated || false,
"toplevel": toplevel
});
if (toplevel.activated) {
focusedIdx = idx;
}
idx++;
}
windows = newWindows;
focusedWindowIndex = focusedIdx;
windowListChanged();
}
function focusWindow(window) {
if (window.toplevel && typeof window.toplevel.activate === "function") {
window.toplevel.activate();
}
}
function closeWindow(window) {
if (window.toplevel && typeof window.toplevel.close === "function") {
window.toplevel.close();
}
}
function switchToWorkspace(workspace) {
const nativeWs = nativeWorkspaceMap[workspace.id] || nativeWorkspaceMap[workspace.oid];
if (nativeWs && nativeWs.canActivate) {
nativeWs.activate();
} else {
Logger.w("ExtWorkspaceService", "Cannot activate workspace: " + (workspace.name || workspace.id));
}
}
function turnOffMonitors() {
try {
Quickshell.execDetached(["wlr-randr", "--off"]);
} catch (e) {
Logger.e("ExtWorkspaceService", "Failed to turn off monitors:", e);
}
}
function turnOnMonitors() {
try {
Quickshell.execDetached(["wlr-randr", "--on"]);
} catch (e) {
Logger.e("ExtWorkspaceService", "Failed to turn on monitors:", e);
}
}
function logout() {
const sid = Quickshell.env("XDG_SESSION_ID");
try {
if (sid && sid.length > 0) {
Quickshell.execDetached(["loginctl", "terminate-session", sid]);
} else {
Logger.w("ExtWorkspaceService", "logout: XDG_SESSION_ID unset; use session menu custom command or compositor-specific backend");
}
} catch (e) {
Logger.e("ExtWorkspaceService", "Failed to logout:", e);
}
}
function cycleKeyboardLayout() {
Logger.w("ExtWorkspaceService", "Keyboard layout cycling not supported");
}
function queryDisplayScales() {
Logger.w("ExtWorkspaceService", "Display scale queries not supported via ToplevelManager");
}
function getFocusedScreen() {
return null;
}
}