summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml1981
1 files changed, 1981 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml
new file mode 100644
index 0000000..3721e35
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml
@@ -0,0 +1,1981 @@
+import QtQuick
+import QtQuick.Effects
+import Quickshell
+import Quickshell.Widgets
+import Quickshell.Hyprland
+import Quickshell.I3
+import qs.Common
+import qs.Services
+import qs.Widgets
+
+Item {
+ id: root
+
+ property bool isVertical: axis?.isVertical ?? false
+ property var axis: null
+ property string screenName: ""
+ property real widgetHeight: 30
+ property real barThickness: 48
+ property var barConfig: null
+ property var blurBarWindow: null
+ property var hyprlandOverviewLoader: null
+ property var parentScreen: null
+
+ readonly property real _leftMargin: {
+ if (isVertical)
+ return 0;
+ root.x;
+ if (!root.parent)
+ return 0;
+ const gap = root.mapToItem(null, 0, 0).x;
+ return (gap > 0 && gap < 30) ? gap + 5 : 0;
+ }
+ readonly property real _rightMargin: {
+ if (isVertical)
+ return 0;
+ root.x;
+ root.width;
+ if (!root.parent || !blurBarWindow)
+ return 0;
+ const gap = blurBarWindow.width - root.mapToItem(null, root.width, 0).x;
+ return (gap > 0 && gap < 30) ? gap + 5 : 0;
+ }
+ readonly property real _topMargin: {
+ if (!isVertical)
+ return 0;
+ root.y;
+ if (!root.parent)
+ return 0;
+ const gap = root.mapToItem(null, 0, 0).y;
+ return (gap > 0 && gap < 30) ? gap + 5 : 0;
+ }
+ readonly property real _bottomMargin: {
+ if (!isVertical)
+ return 0;
+ root.y;
+ root.height;
+ if (!root.parent || !blurBarWindow)
+ return 0;
+ const gap = blurBarWindow.height - root.mapToItem(null, 0, root.height).y;
+ return (gap > 0 && gap < 30) ? gap + 5 : 0;
+ }
+
+ property int _desktopEntriesUpdateTrigger: 0
+ readonly property var sortedToplevels: {
+ return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
+ }
+
+ readonly property string effectiveScreenName: {
+ if (!SettingsData.workspaceFollowFocus)
+ return root.screenName;
+
+ switch (CompositorService.compositor) {
+ case "niri":
+ return NiriService.currentOutput || root.screenName;
+ case "hyprland":
+ return Hyprland.focusedWorkspace?.monitor?.name || root.screenName;
+ case "dwl":
+ return DwlService.activeOutput || root.screenName;
+ case "sway":
+ case "scroll":
+ case "miracle":
+ const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
+ return focusedWs?.monitor?.name || root.screenName;
+ default:
+ return root.screenName;
+ }
+ }
+
+ readonly property bool useExtWorkspace: DMSService.forceExtWorkspace || (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isDwl && !CompositorService.isSway && !CompositorService.isScroll && !CompositorService.isMiracle && ExtWorkspaceService.extWorkspaceAvailable)
+
+ Connections {
+ target: DesktopEntries
+ function onApplicationsChanged() {
+ _desktopEntriesUpdateTrigger++;
+ }
+ }
+
+ property var currentWorkspace: {
+ if (useExtWorkspace)
+ return getExtWorkspaceActiveWorkspace();
+
+ switch (CompositorService.compositor) {
+ case "niri":
+ return getNiriActiveWorkspace();
+ case "hyprland":
+ return getHyprlandActiveWorkspace();
+ case "dwl":
+ const activeTags = getDwlActiveTags();
+ return activeTags.length > 0 ? activeTags[0] : -1;
+ case "sway":
+ case "scroll":
+ case "miracle":
+ return getSwayActiveWorkspace();
+ default:
+ return 1;
+ }
+ }
+ property var dwlActiveTags: {
+ if (CompositorService.isDwl) {
+ return getDwlActiveTags();
+ }
+ return [];
+ }
+ property var workspaceList: {
+ if (useExtWorkspace) {
+ const baseList = getExtWorkspaceWorkspaces();
+ return SettingsData.showWorkspacePadding ? padWorkspaces(baseList) : baseList;
+ }
+
+ let baseList;
+ switch (CompositorService.compositor) {
+ case "niri":
+ baseList = getNiriWorkspaces();
+ break;
+ case "hyprland":
+ baseList = getHyprlandWorkspaces();
+ break;
+ case "dwl":
+ baseList = getDwlTags();
+ break;
+ case "sway":
+ case "scroll":
+ case "miracle":
+ baseList = getSwayWorkspaces();
+ break;
+ default:
+ return [1];
+ }
+ return SettingsData.showWorkspacePadding ? padWorkspaces(baseList) : baseList;
+ }
+
+ function getSwayWorkspaces() {
+ const workspaces = I3.workspaces?.values || [];
+ if (workspaces.length === 0)
+ return [
+ {
+ "num": 1
+ }
+ ];
+
+ function mapWorkspace(ws) {
+ return {
+ "num": ws.number,
+ "name": ws.name,
+ "focused": ws.focused,
+ "active": ws.active,
+ "urgent": ws.urgent,
+ "monitor": ws.monitor
+ };
+ }
+
+ if (!root.screenName || SettingsData.workspaceFollowFocus) {
+ return workspaces.slice().sort((a, b) => a.num - b.num).map(mapWorkspace);
+ }
+
+ const monitorWorkspaces = workspaces.filter(ws => ws.monitor?.name === root.screenName);
+ return monitorWorkspaces.length > 0 ? monitorWorkspaces.sort((a, b) => a.num - b.num).map(mapWorkspace) : [
+ {
+ "num": 1
+ }
+ ];
+ }
+
+ function getSwayActiveWorkspace() {
+ if (!root.screenName || SettingsData.workspaceFollowFocus) {
+ const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
+ return focusedWs ? focusedWs.num : 1;
+ }
+
+ const focusedWs = I3.workspaces?.values?.find(ws => ws.monitor?.name === root.screenName && ws.focused === true);
+ return focusedWs ? focusedWs.num : 1;
+ }
+
+ function getHyprlandWorkspaces() {
+ const workspaces = Hyprland.workspaces?.values || [];
+ if (workspaces.length === 0) {
+ return [
+ {
+ id: 1,
+ name: "1"
+ }
+ ];
+ }
+
+ let filtered = workspaces.filter(ws => ws.id > -1);
+ if (filtered.length === 0) {
+ return [
+ {
+ id: 1,
+ name: "1"
+ }
+ ];
+ }
+
+ if (!root.screenName || SettingsData.workspaceFollowFocus) {
+ filtered = filtered.slice().sort((a, b) => a.id - b.id);
+ } else {
+ const monitorWorkspaces = filtered.filter(ws => ws.monitor?.name === root.screenName);
+ filtered = monitorWorkspaces.length > 0 ? monitorWorkspaces.sort((a, b) => a.id - b.id) : [
+ {
+ id: 1,
+ name: "1"
+ }
+ ];
+ }
+
+ if (!SettingsData.showOccupiedWorkspacesOnly) {
+ return filtered;
+ }
+
+ const hyprlandToplevels = Array.from(Hyprland.toplevels?.values || []);
+ const activeWsId = root.currentWorkspace;
+ return filtered.filter(ws => {
+ if (ws.id === activeWsId)
+ return true;
+ return hyprlandToplevels.some(tl => tl.workspace?.id === ws.id);
+ });
+ }
+
+ function getHyprlandActiveWorkspace() {
+ if (!root.screenName || SettingsData.workspaceFollowFocus) {
+ return Hyprland.focusedWorkspace?.id || 1;
+ }
+
+ const monitor = Hyprland.monitors?.values?.find(m => m.name === root.screenName);
+ return monitor?.activeWorkspace?.id || 1;
+ }
+
+ function getWorkspaceIcons(ws) {
+ _desktopEntriesUpdateTrigger;
+ if (!SettingsData.showWorkspaceApps || !ws) {
+ return [];
+ }
+
+ let targetWorkspaceId;
+ if (CompositorService.isNiri) {
+ if (!ws || typeof ws !== "object") {
+ const wsNumber = typeof ws === "number" ? ws : -1;
+ if (wsNumber <= 0) {
+ return [];
+ }
+ const workspace = NiriService.allWorkspaces.find(w => w.idx + 1 === wsNumber && w.output === root.effectiveScreenName);
+ if (!workspace) {
+ return [];
+ }
+ targetWorkspaceId = workspace.id;
+ } else {
+ if (ws.id === undefined || ws.id === -1 || ws.idx === -1) {
+ return [];
+ }
+ targetWorkspaceId = ws.id;
+ }
+ } else if (CompositorService.isHyprland) {
+ targetWorkspaceId = ws.id !== undefined ? ws.id : ws;
+ } else if (CompositorService.isDwl) {
+ if (typeof ws !== "object" || ws.tag === undefined) {
+ return [];
+ }
+ targetWorkspaceId = ws.tag;
+ } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
+ targetWorkspaceId = ws.num !== undefined ? ws.num : ws;
+ } else {
+ return [];
+ }
+
+ const wins = CompositorService.isNiri ? (NiriService.windows || []) : CompositorService.sortedToplevels;
+
+ const byApp = {};
+ let isActiveWs = false;
+ if (CompositorService.isNiri) {
+ isActiveWs = NiriService.allWorkspaces.some(ws => ws.id === targetWorkspaceId && ws.is_active);
+ } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
+ const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
+ isActiveWs = focusedWs ? (focusedWs.num === targetWorkspaceId) : false;
+ } else if (CompositorService.isDwl) {
+ const output = DwlService.getOutputState(root.effectiveScreenName);
+ if (output && output.tags) {
+ const tag = output.tags.find(t => t.tag === targetWorkspaceId);
+ isActiveWs = tag ? (tag.state === 1) : false;
+ }
+ } else {
+ isActiveWs = targetWorkspaceId === root.currentWorkspace;
+ }
+
+ wins.forEach((w, i) => {
+ if (!w) {
+ return;
+ }
+
+ let winWs = null;
+ if (CompositorService.isNiri) {
+ winWs = w.workspace_id;
+ } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
+ winWs = w.workspace?.num;
+ } else {
+ const hyprlandToplevels = Array.from(Hyprland.toplevels?.values || []);
+ const hyprToplevel = hyprlandToplevels.find(ht => ht.wayland === w);
+ winWs = hyprToplevel?.workspace?.id;
+ }
+
+ if (winWs === undefined || winWs === null || winWs !== targetWorkspaceId) {
+ return;
+ }
+
+ const keyBase = (w.app_id || w.appId || w.class || w.windowClass || "unknown");
+ const moddedId = Paths.moddedAppId(keyBase);
+ const key = isActiveWs || !SettingsData.groupWorkspaceApps ? `${moddedId}_${i}` : moddedId;
+
+ if (!byApp[key]) {
+ const isQuickshell = keyBase === "org.quickshell" || keyBase === "com.danklinux.dms";
+ const isSteamApp = Paths.isSteamApp(moddedId);
+ const desktopEntry = DesktopEntries.heuristicLookup(moddedId);
+ const icon = Paths.getAppIcon(moddedId, desktopEntry);
+ const appName = Paths.getAppName(moddedId, desktopEntry);
+ byApp[key] = {
+ "type": "icon",
+ "icon": icon,
+ "isQuickshell": isQuickshell,
+ "isSteamApp": isSteamApp,
+ "active": !!((w.activated || w.is_focused) || (CompositorService.isNiri && w.is_focused)),
+ "count": 1,
+ "windowId": w.address || w.id,
+ "fallbackText": appName || ""
+ };
+ } else {
+ byApp[key].count++;
+ if ((w.activated || w.is_focused) || (CompositorService.isNiri && w.is_focused)) {
+ byApp[key].active = true;
+ }
+ }
+ });
+
+ return Object.values(byApp);
+ }
+
+ function padWorkspaces(list) {
+ const padded = list.slice();
+ let placeholder;
+ if (useExtWorkspace) {
+ placeholder = {
+ "id": "",
+ "name": "",
+ "active": false,
+ "hidden": true
+ };
+ } else if (CompositorService.isNiri) {
+ placeholder = {
+ "id": -1,
+ "idx": -1,
+ "name": ""
+ };
+ } else if (CompositorService.isHyprland) {
+ placeholder = {
+ "id": -1,
+ "name": ""
+ };
+ } else if (CompositorService.isDwl) {
+ placeholder = {
+ "tag": -1
+ };
+ } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
+ placeholder = {
+ "num": -1
+ };
+ } else {
+ placeholder = -1;
+ }
+ while (padded.length < 3) {
+ padded.push(placeholder);
+ }
+ return padded;
+ }
+
+ function getNiriWorkspaces() {
+ if (NiriService.allWorkspaces.length === 0) {
+ return [
+ {
+ "id": 1,
+ "idx": 0,
+ "name": ""
+ },
+ {
+ "id": 2,
+ "idx": 1,
+ "name": ""
+ }
+ ];
+ }
+
+ const fallbackWorkspaces = [
+ {
+ "id": 1,
+ "idx": 0,
+ "name": ""
+ },
+ {
+ "id": 2,
+ "idx": 1,
+ "name": ""
+ }
+ ];
+
+ let workspaces;
+ if (!root.screenName || SettingsData.workspaceFollowFocus) {
+ const currentWorkspaces = NiriService.getCurrentOutputWorkspaces();
+ workspaces = currentWorkspaces.length > 0 ? currentWorkspaces : fallbackWorkspaces;
+ } else {
+ const displayWorkspaces = NiriService.allWorkspaces.filter(ws => ws.output === root.screenName);
+ workspaces = displayWorkspaces.length > 0 ? displayWorkspaces : fallbackWorkspaces;
+ }
+
+ workspaces = workspaces.slice().sort((a, b) => a.idx - b.idx);
+
+ if (!SettingsData.showOccupiedWorkspacesOnly) {
+ return workspaces;
+ }
+
+ return workspaces.filter(ws => {
+ if (ws.is_active)
+ return true;
+ return NiriService.windows?.some(win => win.workspace_id === ws.id) ?? false;
+ });
+ }
+
+ function getNiriActiveWorkspace() {
+ if (NiriService.allWorkspaces.length === 0) {
+ return 1;
+ }
+
+ if (!root.screenName || SettingsData.workspaceFollowFocus) {
+ return NiriService.getCurrentWorkspaceNumber();
+ }
+
+ const activeWs = NiriService.allWorkspaces.find(ws => ws.output === root.screenName && ws.is_active);
+ return activeWs ? activeWs.idx : 1;
+ }
+
+ function getDwlTags() {
+ if (!DwlService.dwlAvailable)
+ return [];
+
+ const targetScreen = root.effectiveScreenName;
+ const output = DwlService.getOutputState(targetScreen);
+ if (!output || !output.tags || output.tags.length === 0)
+ return [];
+
+ if (SettingsData.dwlShowAllTags) {
+ return output.tags.map(tag => ({
+ "tag": tag.tag,
+ "state": tag.state,
+ "clients": tag.clients,
+ "focused": tag.focused
+ }));
+ }
+
+ const visibleTagIndices = DwlService.getVisibleTags(targetScreen);
+ return visibleTagIndices.map(tagIndex => {
+ const tagData = output.tags.find(t => t.tag === tagIndex);
+ return {
+ "tag": tagIndex,
+ "state": tagData?.state ?? 0,
+ "clients": tagData?.clients ?? 0,
+ "focused": tagData?.focused ?? false
+ };
+ });
+ }
+
+ function getDwlActiveTags() {
+ if (!DwlService.dwlAvailable)
+ return [];
+
+ return DwlService.getActiveTags(root.effectiveScreenName);
+ }
+
+ function getExtWorkspaceWorkspaces() {
+ const groups = ExtWorkspaceService.groups;
+ if (!ExtWorkspaceService.extWorkspaceAvailable || groups.length === 0) {
+ return [
+ {
+ "id": "1",
+ "name": "1",
+ "active": false
+ }
+ ];
+ }
+
+ const group = groups.find(g => g.outputs && g.outputs.includes(root.screenName));
+ if (!group || !group.workspaces) {
+ return [
+ {
+ "id": "1",
+ "name": "1",
+ "active": false
+ }
+ ];
+ }
+
+ let visible = group.workspaces.filter(ws => !ws.hidden);
+
+ const hasValidCoordinates = visible.some(ws => ws.coordinates && ws.coordinates.length > 0);
+ if (hasValidCoordinates) {
+ visible = visible.sort((a, b) => {
+ const coordsA = a.coordinates || [0, 0];
+ const coordsB = b.coordinates || [0, 0];
+ if (coordsA[0] !== coordsB[0])
+ return coordsA[0] - coordsB[0];
+ return coordsA[1] - coordsB[1];
+ });
+ }
+
+ visible = visible.map(ws => ({
+ id: ws.id,
+ name: ws.name,
+ coordinates: ws.coordinates,
+ state: ws.state,
+ active: ws.active,
+ urgent: ws.urgent,
+ hidden: ws.hidden,
+ groupID: group.id
+ }));
+
+ return visible.length > 0 ? visible : [
+ {
+ "id": "1",
+ "name": "1",
+ "active": false
+ }
+ ];
+ }
+
+ function getExtWorkspaceActiveWorkspace() {
+ if (!ExtWorkspaceService.extWorkspaceAvailable) {
+ return 1;
+ }
+
+ const activeWs = ExtWorkspaceService.getActiveWorkspaceForOutput(root.screenName);
+ return activeWs ? (activeWs.id || activeWs.name || "1") : "1";
+ }
+
+ readonly property real dpr: parentScreen ? CompositorService.getScreenScale(parentScreen) : 1
+ readonly property real padding: (root.barConfig?.removeWidgetPadding ?? false) ? 0 : Theme.snap((root.barConfig?.widgetPadding ?? 12) * (widgetHeight / 30), dpr)
+ readonly property real visualWidth: isVertical ? widgetHeight : (workspaceRow.implicitWidth + padding * 2)
+ readonly property real visualHeight: isVertical ? (workspaceRow.implicitHeight + padding * 2) : widgetHeight
+ readonly property real appIconSize: Theme.barIconSize(barThickness, -6 + SettingsData.workspaceAppIconSizeOffset, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
+
+ function getRealWorkspaces() {
+ return root.workspaceList.filter(ws => {
+ if (useExtWorkspace)
+ return ws && (ws.id !== "" || ws.name !== "") && !ws.hidden;
+ if (CompositorService.isNiri)
+ return ws && ws.idx !== -1;
+ if (CompositorService.isHyprland)
+ return ws && ws.id !== -1;
+ if (CompositorService.isDwl)
+ return ws && ws.tag !== -1;
+ if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
+ return ws && ws.num !== -1;
+ return ws !== -1;
+ });
+ }
+
+ function switchToWorkspaceByModelData(data) {
+ if (!data)
+ return;
+
+ if (root.useExtWorkspace && (data.id || data.name)) {
+ ExtWorkspaceService.activateWorkspace(data.id || data.name, data.groupID || "");
+ return;
+ }
+
+ switch (CompositorService.compositor) {
+ case "niri":
+ if (data.idx !== undefined)
+ NiriService.switchToWorkspace(data.idx);
+ break;
+ case "hyprland":
+ if (data.id)
+ Hyprland.dispatch(`workspace ${data.id}`);
+ break;
+ case "dwl":
+ if (data.tag !== undefined)
+ DwlService.switchToTag(root.screenName, data.tag);
+ break;
+ case "sway":
+ case "scroll":
+ case "miracle":
+ if (data.num)
+ try {
+ I3.dispatch(`workspace number ${data.num}`);
+ } catch (_) {}
+ break;
+ }
+ }
+
+ function findClosestWorkspaceIndex(localX, localY) {
+ if (workspaceRepeater.count === 0)
+ return -1;
+
+ let closestIdx = -1;
+ let closestDist = Infinity;
+
+ for (let i = 0; i < workspaceRepeater.count; i++) {
+ const item = workspaceRepeater.itemAt(i);
+ if (!item)
+ continue;
+ const center = item.mapToItem(root, item.width / 2, item.height / 2);
+ const dist = isVertical ? Math.abs(localY - center.y) : Math.abs(localX - center.x);
+ if (dist < closestDist) {
+ closestDist = dist;
+ closestIdx = i;
+ }
+ }
+ return closestIdx;
+ }
+
+ function switchWorkspace(direction) {
+ if (useExtWorkspace) {
+ const realWorkspaces = getRealWorkspaces();
+ if (realWorkspaces.length < 2) {
+ return;
+ }
+
+ const currentIndex = realWorkspaces.findIndex(ws => (ws.id || ws.name) === root.currentWorkspace);
+ const validIndex = currentIndex === -1 ? 0 : currentIndex;
+ const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);
+
+ if (nextIndex === validIndex) {
+ return;
+ }
+
+ const nextWorkspace = realWorkspaces[nextIndex];
+ ExtWorkspaceService.activateWorkspace(nextWorkspace.id || nextWorkspace.name, nextWorkspace.groupID || "");
+ } else if (CompositorService.isNiri) {
+ const realWorkspaces = getRealWorkspaces();
+ if (realWorkspaces.length < 2) {
+ return;
+ }
+
+ const currentIndex = realWorkspaces.findIndex(ws => ws && ws.idx === root.currentWorkspace);
+ const validIndex = currentIndex === -1 ? 0 : currentIndex;
+ const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);
+
+ if (nextIndex === validIndex) {
+ return;
+ }
+
+ const nextWorkspace = realWorkspaces[nextIndex];
+ if (!nextWorkspace || nextWorkspace.idx === undefined) {
+ return;
+ }
+ NiriService.switchToWorkspace(nextWorkspace.idx);
+ } else if (CompositorService.isHyprland) {
+ const realWorkspaces = getRealWorkspaces();
+ if (realWorkspaces.length < 2) {
+ return;
+ }
+
+ const currentIndex = realWorkspaces.findIndex(ws => ws.id === root.currentWorkspace);
+ const validIndex = currentIndex === -1 ? 0 : currentIndex;
+ const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);
+
+ if (nextIndex === validIndex) {
+ return;
+ }
+
+ Hyprland.dispatch(`workspace ${realWorkspaces[nextIndex].id}`);
+ } else if (CompositorService.isDwl) {
+ const realWorkspaces = getRealWorkspaces();
+ if (realWorkspaces.length < 2) {
+ return;
+ }
+
+ const currentIndex = realWorkspaces.findIndex(ws => ws.tag === root.currentWorkspace);
+ const validIndex = currentIndex === -1 ? 0 : currentIndex;
+ const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);
+
+ if (nextIndex === validIndex) {
+ return;
+ }
+
+ DwlService.switchToTag(root.screenName, realWorkspaces[nextIndex].tag);
+ } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
+ const realWorkspaces = getRealWorkspaces();
+ if (realWorkspaces.length < 2) {
+ return;
+ }
+
+ const currentIndex = realWorkspaces.findIndex(ws => ws.num === root.currentWorkspace);
+ const validIndex = currentIndex === -1 ? 0 : currentIndex;
+ const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);
+
+ if (nextIndex === validIndex) {
+ return;
+ }
+
+ try {
+ I3.dispatch(`workspace number ${realWorkspaces[nextIndex].num}`);
+ } catch (_) {}
+ }
+ }
+
+ function getWorkspaceIndexFallback(modelData, index) {
+ if (root.useExtWorkspace)
+ return index + 1;
+ if (CompositorService.isNiri)
+ return (modelData?.idx !== undefined && modelData?.idx !== -1) ? modelData.idx : "";
+ if (CompositorService.isHyprland)
+ return modelData?.id || "";
+ if (CompositorService.isDwl)
+ return (modelData?.tag !== undefined) ? (modelData.tag + 1) : "";
+ if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
+ return modelData?.num || "";
+ return modelData - 1;
+ }
+
+ function getWorkspaceIndex(modelData, index) {
+ let isPlaceholder;
+ if (root.useExtWorkspace) {
+ isPlaceholder = modelData?.hidden === true;
+ } else if (CompositorService.isNiri) {
+ isPlaceholder = modelData?.idx === -1;
+ } else if (CompositorService.isHyprland) {
+ isPlaceholder = modelData?.id === -1;
+ } else if (CompositorService.isDwl) {
+ isPlaceholder = modelData?.tag === -1;
+ } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
+ isPlaceholder = modelData?.num === -1;
+ } else {
+ isPlaceholder = modelData === -1;
+ }
+
+ if (isPlaceholder)
+ return index + 1;
+
+ let workspaceName = "";
+ if (SettingsData.showWorkspaceName) {
+ workspaceName = modelData?.name ?? "";
+
+ if (workspaceName && workspaceName !== "") {
+ if (root.isVertical) {
+ workspaceName = workspaceName.charAt(0);
+ }
+ } else {
+ workspaceName = "";
+ }
+ }
+
+ if (workspaceName) {
+ if (SettingsData.showWorkspaceIndex) {
+ const indexLabel = getWorkspaceIndexFallback(modelData, index);
+ return indexLabel ? `${indexLabel}: ${workspaceName}` : workspaceName;
+ }
+ return workspaceName;
+ }
+
+ return getWorkspaceIndexFallback(modelData, index);
+ }
+
+ readonly property bool hasNativeWorkspaceSupport: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
+ readonly property bool hasWorkspaces: getRealWorkspaces().length > 0
+ readonly property bool shouldShow: hasNativeWorkspaceSupport || (useExtWorkspace && hasWorkspaces)
+
+ width: shouldShow ? (isVertical ? barThickness : visualWidth) : 0
+ height: shouldShow ? (isVertical ? visualHeight : barThickness) : 0
+ visible: shouldShow
+
+ Item {
+ id: visualBackground
+ width: root.visualWidth
+ height: root.visualHeight
+ anchors.centerIn: parent
+
+ Rectangle {
+ id: outline
+ anchors.centerIn: parent
+ width: {
+ const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
+ return parent.width + borderWidth * 2;
+ }
+ height: {
+ const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
+ return parent.height + borderWidth * 2;
+ }
+ radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
+ color: "transparent"
+ border.width: {
+ if (barConfig?.widgetOutlineEnabled ?? false) {
+ return barConfig?.widgetOutlineThickness ?? 1;
+ }
+ return 0;
+ }
+ border.color: {
+ if (!(barConfig?.widgetOutlineEnabled ?? false)) {
+ return "transparent";
+ }
+ const colorOption = barConfig?.widgetOutlineColor || "primary";
+ const opacity = barConfig?.widgetOutlineOpacity ?? 1.0;
+ switch (colorOption) {
+ case "surfaceText":
+ return Theme.withAlpha(Theme.surfaceText, opacity);
+ case "secondary":
+ return Theme.withAlpha(Theme.secondary, opacity);
+ case "primary":
+ return Theme.withAlpha(Theme.primary, opacity);
+ default:
+ return Theme.withAlpha(Theme.primary, opacity);
+ }
+ }
+ }
+
+ Rectangle {
+ id: background
+ anchors.fill: parent
+ radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
+ color: {
+ if ((barConfig?.noBackground ?? false))
+ return "transparent";
+ const baseColor = Theme.widgetBaseBackgroundColor;
+ const transparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0;
+ if (Theme.widgetBackgroundHasAlpha) {
+ return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * transparency);
+ }
+ return Theme.withAlpha(baseColor, transparency);
+ }
+ }
+ }
+
+ MouseArea {
+ id: edgeMouseArea
+ z: -1
+ x: -root._leftMargin
+ y: -root._topMargin
+ width: root.width + root._leftMargin + root._rightMargin
+ height: root.height + root._topMargin + root._bottomMargin
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+
+ property real touchpadAccumulator: 0
+ property real mouseAccumulator: 0
+ property bool scrollInProgress: false
+
+ Timer {
+ id: scrollCooldown
+ interval: 100
+ onTriggered: parent.scrollInProgress = false
+ }
+
+ onClicked: mouse => {
+ const rootPos = edgeMouseArea.mapToItem(root, mouse.x, mouse.y);
+ switch (mouse.button) {
+ case Qt.RightButton:
+ if (CompositorService.isNiri) {
+ NiriService.toggleOverview();
+ } else if (CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) {
+ root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen;
+ }
+ break;
+ case Qt.LeftButton:
+ const idx = root.findClosestWorkspaceIndex(rootPos.x, rootPos.y);
+ if (idx >= 0)
+ root.switchToWorkspaceByModelData(root.workspaceList[idx]);
+ break;
+ }
+ }
+
+ onWheel: wheel => {
+ if (Math.abs(wheel.angleDelta.x) > Math.abs(wheel.angleDelta.y)) {
+ wheel.accepted = false;
+ return;
+ }
+
+ if (scrollInProgress)
+ return;
+
+ const delta = wheel.angleDelta.y;
+ const isTouchpad = wheel.pixelDelta && wheel.pixelDelta.y !== 0;
+ const reverse = SettingsData.reverseScrolling ? -1 : 1;
+
+ if (isTouchpad) {
+ touchpadAccumulator += delta;
+ if (Math.abs(touchpadAccumulator) < 500)
+ return;
+ const direction = touchpadAccumulator * reverse < 0 ? 1 : -1;
+ root.switchWorkspace(direction);
+ scrollInProgress = true;
+ scrollCooldown.restart();
+ touchpadAccumulator = 0;
+ return;
+ }
+
+ mouseAccumulator += delta;
+ if (Math.abs(mouseAccumulator) < 120)
+ return;
+ const direction = mouseAccumulator * reverse < 0 ? 1 : -1;
+ root.switchWorkspace(direction);
+ scrollInProgress = true;
+ scrollCooldown.restart();
+ mouseAccumulator = 0;
+ }
+ }
+
+ property int dragSourceIndex: -1
+ property int dragTargetIndex: -1
+ property bool suppressShiftAnimation: false
+
+ onWorkspaceListChanged: {
+ if (dragSourceIndex >= 0) {
+ dragSourceIndex = -1;
+ dragTargetIndex = -1;
+ suppressShiftAnimation = false;
+ }
+ }
+
+ Flow {
+ id: workspaceRow
+
+ x: isVertical ? visualBackground.x : (parent.width - implicitWidth) / 2
+ y: isVertical ? (parent.height - implicitHeight) / 2 : visualBackground.y
+ spacing: Theme.spacingS
+ flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight
+
+ Repeater {
+ id: workspaceRepeater
+ model: ScriptModel {
+ values: root.workspaceList
+ }
+
+ Item {
+ id: delegateRoot
+
+ property bool isDropTarget: root.dragTargetIndex === index
+
+ z: dragHandler.dragging ? 1000 : 1
+
+ property real shiftOffset: {
+ if (root.dragSourceIndex < 0 || index === root.dragSourceIndex)
+ return 0;
+ const dragIdx = root.dragSourceIndex;
+ const dropIdx = root.dragTargetIndex;
+ if (dropIdx < 0)
+ return 0;
+ const shiftAmount = delegateRoot.width + Theme.spacingS;
+ if (dragIdx < dropIdx && index > dragIdx && index <= dropIdx)
+ return -shiftAmount;
+ if (dragIdx > dropIdx && index >= dropIdx && index < dragIdx)
+ return shiftAmount;
+ return 0;
+ }
+
+ transform: Translate {
+ x: root.isVertical ? 0 : delegateRoot.shiftOffset
+ y: root.isVertical ? delegateRoot.shiftOffset : 0
+ Behavior on x {
+ enabled: !root.suppressShiftAnimation
+ NumberAnimation {
+ duration: 150
+ easing.type: Easing.OutCubic
+ }
+ }
+ Behavior on y {
+ enabled: !root.suppressShiftAnimation
+ NumberAnimation {
+ duration: 150
+ easing.type: Easing.OutCubic
+ }
+ }
+ }
+
+ property bool isActive: {
+ if (root.useExtWorkspace)
+ return (modelData?.id || modelData?.name) === root.currentWorkspace;
+ if (CompositorService.isNiri)
+ return !!(modelData && modelData.idx === root.currentWorkspace);
+ if (CompositorService.isHyprland)
+ return !!(modelData && modelData.id === root.currentWorkspace);
+ if (CompositorService.isDwl)
+ return !!(modelData && root.dwlActiveTags.includes(modelData.tag));
+ if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
+ return !!(modelData && modelData.num === root.currentWorkspace);
+ return modelData === root.currentWorkspace;
+ }
+ property bool isOccupied: {
+ if (CompositorService.isHyprland)
+ return Array.from(Hyprland.toplevels?.values || []).some(tl => tl.workspace?.id === modelData?.id);
+ if (CompositorService.isDwl)
+ return modelData.clients > 0;
+ if (CompositorService.isNiri) {
+ const workspace = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.effectiveScreenName);
+ return workspace ? (NiriService.windows?.some(win => win.workspace_id === workspace.id) ?? false) : false;
+ }
+ return false;
+ }
+ property bool isPlaceholder: {
+ if (root.useExtWorkspace)
+ return !!(modelData && modelData.hidden);
+ if (CompositorService.isNiri)
+ return !!(modelData && modelData.idx === -1);
+ if (CompositorService.isHyprland)
+ return !!(modelData && modelData.id === -1);
+ if (CompositorService.isDwl)
+ return !!(modelData && modelData.tag === -1);
+ if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
+ return !!(modelData && modelData.num === -1);
+ return modelData === -1;
+ }
+ property bool isHovered: mouseArea.containsMouse
+
+ property var loadedWorkspaceData: null
+ property bool loadedIsUrgent: false
+ property bool isUrgent: {
+ if (root.useExtWorkspace)
+ return modelData?.urgent ?? false;
+ if (CompositorService.isHyprland)
+ return modelData?.urgent ?? false;
+ if (CompositorService.isNiri)
+ return loadedIsUrgent;
+ if (CompositorService.isDwl)
+ return modelData?.state === 2;
+ if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
+ return loadedIsUrgent;
+ return false;
+ }
+ readonly property var loadedIconData: {
+ if (isPlaceholder)
+ return null;
+ const name = modelData?.name;
+ if (!name)
+ return null;
+ return SettingsData.getWorkspaceNameIcon(name);
+ }
+ readonly property bool loadedHasIcon: loadedIconData !== null
+ property var loadedIcons: []
+
+ readonly property int stableIconCount: {
+ if (!SettingsData.showWorkspaceApps || isPlaceholder)
+ return 0;
+
+ let targetWorkspaceId;
+ if (root.useExtWorkspace) {
+ targetWorkspaceId = modelData?.id || modelData?.name;
+ } else if (CompositorService.isNiri) {
+ targetWorkspaceId = modelData?.id;
+ } else if (CompositorService.isHyprland) {
+ targetWorkspaceId = modelData?.id;
+ } else if (CompositorService.isDwl) {
+ targetWorkspaceId = modelData?.tag;
+ } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
+ targetWorkspaceId = modelData?.num;
+ }
+ if (targetWorkspaceId === undefined || targetWorkspaceId === null)
+ return 0;
+
+ const wins = CompositorService.isNiri ? (NiriService.windows || []) : CompositorService.sortedToplevels;
+ const seen = {};
+ let groupedCount = 0;
+ let totalCount = 0;
+
+ for (let i = 0; i < wins.length; i++) {
+ const w = wins[i];
+ if (!w)
+ continue;
+
+ let winWs = null;
+ if (CompositorService.isNiri) {
+ winWs = w.workspace_id;
+ } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
+ winWs = w.workspace?.num;
+ } else if (CompositorService.isHyprland) {
+ const hyprlandToplevels = Array.from(Hyprland.toplevels?.values || []);
+ const hyprToplevel = hyprlandToplevels.find(ht => ht.wayland === w);
+ winWs = hyprToplevel?.workspace?.id;
+ }
+
+ if (winWs !== targetWorkspaceId)
+ continue;
+ totalCount++;
+
+ const appKey = w.app_id || w.appId || w.class || w.windowClass || "unknown";
+ if (!seen[appKey]) {
+ seen[appKey] = true;
+ groupedCount++;
+ }
+ }
+
+ return (SettingsData.groupWorkspaceApps && !isActive) ? groupedCount : totalCount;
+ }
+
+ readonly property real baseWidth: root.isVertical ? (SettingsData.showWorkspaceApps ? Math.max(widgetHeight * 0.7, root.appIconSize + Theme.spacingXS * 2) : widgetHeight * 0.5) : (isActive ? Math.max(root.widgetHeight * 1.05, root.appIconSize * 1.6) : Math.max(root.widgetHeight * 0.7, root.appIconSize * 1.2))
+ readonly property real baseHeight: root.isVertical ? (isActive ? Math.max(root.widgetHeight * 1.05, root.appIconSize * 1.6) : Math.max(root.widgetHeight * 0.7, root.appIconSize * 1.2)) : (SettingsData.showWorkspaceApps ? Math.max(widgetHeight * 0.7, root.appIconSize + Theme.spacingXS * 2) : widgetHeight * 0.5)
+ readonly property bool hasWorkspaceName: SettingsData.showWorkspaceName && modelData?.name && modelData.name !== ""
+ readonly property bool workspaceNamesEnabled: SettingsData.showWorkspaceName && (CompositorService.isNiri || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
+ readonly property real contentImplicitWidth: appIconsLoader.item?.contentWidth ?? 0
+ readonly property real contentImplicitHeight: appIconsLoader.item?.contentHeight ?? 0
+
+ readonly property real iconsExtraWidth: {
+ if (!root.isVertical && SettingsData.showWorkspaceApps && stableIconCount > 0) {
+ const numIcons = Math.min(stableIconCount, SettingsData.maxWorkspaceIcons);
+ return numIcons * root.appIconSize + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0) + (isActive ? Theme.spacingXS : 0);
+ }
+ return 0;
+ }
+ readonly property real iconsExtraHeight: {
+ if (root.isVertical && SettingsData.showWorkspaceApps && stableIconCount > 0) {
+ const numIcons = Math.min(stableIconCount, SettingsData.maxWorkspaceIcons);
+ return numIcons * root.appIconSize + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0) + (isActive ? Theme.spacingXS : 0);
+ }
+ return 0;
+ }
+
+ readonly property real visualWidth: {
+ if (contentImplicitWidth <= 0)
+ return baseWidth + iconsExtraWidth;
+ const padding = root.isVertical ? Theme.spacingXS : Theme.spacingS;
+ return Math.max(baseWidth + iconsExtraWidth, contentImplicitWidth + padding);
+ }
+ readonly property real visualHeight: {
+ if (contentImplicitHeight <= 0)
+ return baseHeight + iconsExtraHeight;
+ const padding = root.isVertical ? Theme.spacingS : Theme.spacingXS;
+ return Math.max(baseHeight + iconsExtraHeight, contentImplicitHeight + padding);
+ }
+
+ readonly property color unfocusedColor: {
+ switch (SettingsData.workspaceUnfocusedColorMode) {
+ case "s":
+ return Theme.surface;
+ case "sc":
+ return Theme.surfaceContainer;
+ case "sch":
+ return Theme.surfaceContainerHigh;
+ default:
+ return Theme.surfaceTextAlpha;
+ }
+ }
+
+ readonly property color activeColor: {
+ switch (SettingsData.workspaceColorMode) {
+ case "s":
+ return Theme.surface;
+ case "sc":
+ return Theme.surfaceContainer;
+ case "sch":
+ return Theme.surfaceContainerHigh;
+ case "none":
+ return unfocusedColor;
+ default:
+ return Theme.primary;
+ }
+ }
+
+ readonly property color occupiedColor: {
+ switch (SettingsData.workspaceOccupiedColorMode) {
+ case "sec":
+ return Theme.secondary;
+ case "s":
+ return Theme.surface;
+ case "sc":
+ return Theme.surfaceContainer;
+ case "sch":
+ return Theme.surfaceContainerHigh;
+ case "schh":
+ return Theme.surfaceContainerHighest;
+ default:
+ return unfocusedColor;
+ }
+ }
+
+ readonly property color urgentColor: {
+ switch (SettingsData.workspaceUrgentColorMode) {
+ case "primary":
+ return Theme.primary;
+ case "secondary":
+ return Theme.secondary;
+ case "s":
+ return Theme.surface;
+ case "sc":
+ return Theme.surfaceContainer;
+ default:
+ return Theme.error;
+ }
+ }
+
+ readonly property color focusedBorderColor: {
+ switch (SettingsData.workspaceFocusedBorderColor) {
+ case "surfaceText":
+ return Theme.surfaceText;
+ case "secondary":
+ return Theme.secondary;
+ default:
+ return Theme.primary;
+ }
+ }
+
+ function getContrastingIconColor(bgColor) {
+ const luminance = 0.299 * bgColor.r + 0.587 * bgColor.g + 0.114 * bgColor.b;
+ return luminance > 0.4 ? Qt.rgba(0.15, 0.15, 0.15, 1) : Qt.rgba(0.8, 0.8, 0.8, 1);
+ }
+
+ readonly property color quickshellIconActiveColor: getContrastingIconColor(activeColor)
+ readonly property color quickshellIconInactiveColor: getContrastingIconColor(unfocusedColor)
+
+ Item {
+ id: dragHandler
+ anchors.fill: parent
+ property bool dragging: false
+ property point dragStartPos: Qt.point(0, 0)
+ property real dragAxisOffset: 0
+
+ Connections {
+ target: root
+ function onWorkspaceListChanged() {
+ if (dragHandler.dragging) {
+ dragHandler.dragging = false;
+ dragHandler.dragAxisOffset = 0;
+ mouseArea.mousePressed = false;
+ }
+ }
+ }
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ hoverEnabled: !isPlaceholder
+ cursorShape: isPlaceholder ? Qt.ArrowCursor : (dragHandler.dragging ? Qt.ClosedHandCursor : Qt.PointingHandCursor)
+ enabled: !isPlaceholder
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+
+ property bool mousePressed: false
+
+ onPressed: mouse => {
+ if (mouse.button === Qt.LeftButton && CompositorService.isNiri && SettingsData.workspaceDragReorder && !isPlaceholder) {
+ mousePressed = true;
+ dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y);
+ }
+ }
+
+ onPositionChanged: mouse => {
+ if (!mousePressed || !CompositorService.isNiri || !SettingsData.workspaceDragReorder || isPlaceholder)
+ return;
+
+ if (!dragHandler.dragging) {
+ const distance = root.isVertical ? Math.abs(mouse.y - dragHandler.dragStartPos.y) : Math.abs(mouse.x - dragHandler.dragStartPos.x);
+ if (distance > 5) {
+ dragHandler.dragging = true;
+ root.dragSourceIndex = index;
+ root.dragTargetIndex = index;
+ }
+ }
+
+ if (!dragHandler.dragging)
+ return;
+
+ const rawAxisOffset = root.isVertical ? (mouse.y - dragHandler.dragStartPos.y) : (mouse.x - dragHandler.dragStartPos.x);
+
+ const itemSize = (root.isVertical ? delegateRoot.height : delegateRoot.width) + Theme.spacingS;
+ const maxOffsetPositive = (root.workspaceList.length - 1 - index) * itemSize;
+ const maxOffsetNegative = -index * itemSize;
+ const axisOffset = Math.max(maxOffsetNegative, Math.min(maxOffsetPositive, rawAxisOffset));
+ dragHandler.dragAxisOffset = axisOffset;
+
+ const slotOffset = Math.round(axisOffset / itemSize);
+ const newTargetIndex = Math.max(0, Math.min(root.workspaceList.length - 1, index + slotOffset));
+
+ if (newTargetIndex !== root.dragTargetIndex) {
+ root.dragTargetIndex = newTargetIndex;
+ }
+ }
+
+ onReleased: mouse => {
+ const wasDragging = dragHandler.dragging;
+ const didReorder = wasDragging && root.dragTargetIndex >= 0 && root.dragTargetIndex !== root.dragSourceIndex;
+
+ if (didReorder) {
+ const sourceWs = root.workspaceList[root.dragSourceIndex];
+ const targetWs = root.workspaceList[root.dragTargetIndex];
+
+ if (sourceWs && targetWs && sourceWs.idx !== undefined && targetWs.idx !== undefined) {
+ root.suppressShiftAnimation = true;
+ NiriService.moveWorkspaceToIndex(sourceWs.idx, targetWs.idx);
+ Qt.callLater(() => root.suppressShiftAnimation = false);
+ }
+ }
+
+ mousePressed = false;
+ dragHandler.dragging = false;
+ dragHandler.dragAxisOffset = 0;
+ root.dragSourceIndex = -1;
+ root.dragTargetIndex = -1;
+
+ if (wasDragging || isPlaceholder)
+ return;
+
+ if (mouse.button === Qt.LeftButton) {
+ if (root.useExtWorkspace && (modelData?.id || modelData?.name)) {
+ ExtWorkspaceService.activateWorkspace(modelData.id || modelData.name, modelData.groupID || "");
+ } else if (CompositorService.isNiri) {
+ if (modelData && modelData.idx !== undefined) {
+ NiriService.switchToWorkspace(modelData.idx);
+ }
+ } else if (CompositorService.isHyprland && modelData?.id) {
+ Hyprland.dispatch(`workspace ${modelData.id}`);
+ } else if (CompositorService.isDwl && modelData?.tag !== undefined) {
+ DwlService.switchToTag(root.screenName, modelData.tag);
+ } else if ((CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) && modelData?.num) {
+ try {
+ I3.dispatch(`workspace number ${modelData.num}`);
+ } catch (_) {}
+ }
+ } else if (mouse.button === Qt.RightButton) {
+ if (CompositorService.isNiri) {
+ NiriService.toggleOverview();
+ } else if (CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) {
+ root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen;
+ } else if (CompositorService.isDwl && modelData?.tag !== undefined) {
+ DwlService.toggleTag(root.screenName, modelData.tag);
+ }
+ }
+ }
+ }
+
+ Timer {
+ id: dataUpdateTimer
+ interval: 50
+ onTriggered: {
+ if (isPlaceholder) {
+ delegateRoot.loadedWorkspaceData = null;
+ delegateRoot.loadedIcons = [];
+ delegateRoot.loadedIsUrgent = false;
+ return;
+ }
+
+ var wsData = null;
+ if (root.useExtWorkspace) {
+ wsData = modelData;
+ } else if (CompositorService.isNiri) {
+ wsData = modelData || null;
+ } else if (CompositorService.isHyprland) {
+ wsData = modelData;
+ } else if (CompositorService.isDwl) {
+ wsData = modelData;
+ } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
+ wsData = modelData;
+ }
+ delegateRoot.loadedWorkspaceData = wsData;
+ if (CompositorService.isNiri) {
+ const workspaceId = wsData?.id;
+ delegateRoot.loadedIsUrgent = workspaceId ? NiriService.windows.some(w => w.workspace_id === workspaceId && w.is_urgent) : false;
+ } else {
+ delegateRoot.loadedIsUrgent = wsData?.urgent ?? false;
+ }
+
+ if (SettingsData.showWorkspaceApps) {
+ if (CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
+ delegateRoot.loadedIcons = root.getWorkspaceIcons(modelData);
+ } else if (CompositorService.isNiri) {
+ delegateRoot.loadedIcons = root.getWorkspaceIcons(isPlaceholder ? null : modelData);
+ } else {
+ delegateRoot.loadedIcons = root.getWorkspaceIcons(CompositorService.isHyprland ? modelData : (modelData === -1 ? null : modelData));
+ }
+ } else {
+ delegateRoot.loadedIcons = [];
+ }
+ }
+ }
+
+ function updateAllData() {
+ dataUpdateTimer.restart();
+ }
+
+ width: root.isVertical ? root.widgetHeight : visualWidth
+ height: root.isVertical ? visualHeight : root.widgetHeight
+
+ Behavior on width {
+ NumberAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.emphasizedEasing
+ }
+ }
+
+ Behavior on height {
+ NumberAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.emphasizedEasing
+ }
+ }
+
+ Rectangle {
+ id: focusedBorderRing
+ x: root.isVertical ? (root.widgetHeight - width) / 2 : (parent.width - width) / 2
+ y: root.isVertical ? (parent.height - height) / 2 : (root.widgetHeight - height) / 2
+ width: {
+ const borderWidth = (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? SettingsData.workspaceFocusedBorderThickness : 0;
+ return delegateRoot.visualWidth + borderWidth * 2;
+ }
+ height: {
+ const borderWidth = (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? SettingsData.workspaceFocusedBorderThickness : 0;
+ return delegateRoot.visualHeight + borderWidth * 2;
+ }
+ radius: Theme.cornerRadius
+ color: "transparent"
+ border.width: (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? SettingsData.workspaceFocusedBorderThickness : 0
+ border.color: (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? focusedBorderColor : "transparent"
+
+ Behavior on width {
+ NumberAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.emphasizedEasing
+ }
+ }
+
+ Behavior on height {
+ NumberAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.emphasizedEasing
+ }
+ }
+
+ Behavior on border.width {
+ NumberAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.emphasizedEasing
+ }
+ }
+
+ Behavior on border.color {
+ ColorAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.emphasizedEasing
+ }
+ }
+ }
+
+ Rectangle {
+ id: visualContent
+ width: delegateRoot.visualWidth
+ height: delegateRoot.visualHeight
+ x: root.isVertical ? (root.widgetHeight - width) / 2 : (parent.width - width) / 2
+ y: root.isVertical ? (parent.height - height) / 2 : (root.widgetHeight - height) / 2
+ radius: Theme.cornerRadius
+ color: isActive ? activeColor : isUrgent ? urgentColor : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.withAlpha(unfocusedColor, 0.7) : isOccupied ? occupiedColor : unfocusedColor
+ opacity: dragHandler.dragging ? 0.8 : 1.0
+
+ border.width: dragHandler.dragging ? 2 : (isUrgent ? 2 : (isDropTarget ? 2 : 0))
+ border.color: dragHandler.dragging ? Theme.primary : (isUrgent ? urgentColor : (isDropTarget ? Theme.primary : "transparent"))
+
+ transform: Translate {
+ x: root.isVertical ? 0 : (dragHandler.dragging ? dragHandler.dragAxisOffset : 0)
+ y: root.isVertical ? (dragHandler.dragging ? dragHandler.dragAxisOffset : 0) : 0
+ }
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: Theme.shortDuration
+ easing.type: Theme.emphasizedEasing
+ }
+ }
+
+ Behavior on width {
+ NumberAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.emphasizedEasing
+ }
+ }
+
+ Behavior on height {
+ NumberAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.emphasizedEasing
+ }
+ }
+
+ Behavior on color {
+ ColorAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.emphasizedEasing
+ }
+ }
+
+ Behavior on border.width {
+ NumberAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.emphasizedEasing
+ }
+ }
+
+ Behavior on border.color {
+ ColorAnimation {
+ duration: Theme.mediumDuration
+ easing.type: Theme.emphasizedEasing
+ }
+ }
+
+ Loader {
+ id: appIconsLoader
+ anchors.fill: parent
+ active: SettingsData.showWorkspaceApps || SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName || loadedHasIcon
+ sourceComponent: Item {
+ id: contentRoot
+ readonly property real contentWidth: contentRow.item?.implicitWidth ?? 0
+ readonly property real contentHeight: contentRow.item?.implicitHeight ?? 0
+
+ Loader {
+ id: contentRow
+ anchors.centerIn: parent
+ sourceComponent: root.isVertical ? columnLayout : rowLayout
+ }
+
+ Component {
+ id: rowLayout
+ Row {
+ spacing: 4
+ visible: loadedIcons.length > 0 || SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName || loadedHasIcon
+
+ Item {
+ visible: loadedHasIcon && loadedIconData?.type === "icon"
+ width: wsIcon.width
+ height: root.appIconSize
+
+ DankIcon {
+ id: wsIcon
+ anchors.verticalCenter: parent.verticalCenter
+ name: loadedIconData?.value ?? ""
+ size: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
+ color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
+ weight: (isActive && !isPlaceholder) ? 500 : 400
+ }
+ }
+
+ Item {
+ visible: loadedHasIcon && loadedIconData?.type === "text"
+ width: wsText.implicitWidth
+ height: root.appIconSize
+
+ StyledText {
+ id: wsText
+ anchors.verticalCenter: parent.verticalCenter
+ text: loadedIconData?.value ?? ""
+ color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
+ font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
+ font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
+ }
+ }
+
+ Item {
+ visible: ((SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon) || (loadedHasIcon && SettingsData.showWorkspaceName && hasWorkspaceName)
+ width: wsIndexText.implicitWidth
+ height: root.appIconSize
+
+ StyledText {
+ id: wsIndexText
+ anchors.verticalCenter: parent.verticalCenter
+ text: loadedHasIcon ? (modelData?.name ?? "") : root.getWorkspaceIndex(modelData, index)
+ color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
+ font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
+ font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
+ }
+ }
+
+ Repeater {
+ model: ScriptModel {
+ values: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)
+ }
+ delegate: Item {
+ width: root.appIconSize
+ height: root.appIconSize
+ readonly property bool appHighlightActive: SettingsData.workspaceActiveAppHighlightEnabled && modelData.active
+ readonly property color appBorderColor: appHighlightActive ? focusedBorderColor : Theme.primarySelected
+ readonly property color appGlyphColor: appHighlightActive ? focusedBorderColor : Theme.primary
+ readonly property real appOpacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
+
+ IconImage {
+ id: rowAppIcon
+ anchors.fill: parent
+ source: modelData.icon || ""
+ opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
+ visible: !modelData.isQuickshell && !modelData.isSteamApp && status === Image.Ready
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ visible: !modelData.isQuickshell && !modelData.isSteamApp && rowAppIcon.status !== Image.Ready
+ color: Theme.surfaceContainer
+ radius: Theme.cornerRadius * (root.appIconSize / 40)
+ border.width: 1
+ border.color: appBorderColor
+ opacity: appOpacity
+
+ StyledText {
+ anchors.centerIn: parent
+ text: (modelData.fallbackText || "?").charAt(0).toUpperCase()
+ font.pixelSize: parent.width * 0.45
+ color: appGlyphColor
+ font.weight: Font.Bold
+ }
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ visible: !modelData.isQuickshell && modelData.isSteamApp && rowSteamIcon.status !== Image.Ready
+ color: Theme.surfaceContainer
+ radius: Theme.cornerRadius * (root.appIconSize / 40)
+ border.width: 1
+ border.color: appBorderColor
+ opacity: appOpacity
+
+ DankIcon {
+ anchors.centerIn: parent
+ size: parent.width * 0.7
+ name: "sports_esports"
+ color: appGlyphColor
+ }
+ }
+
+ IconImage {
+ anchors.fill: parent
+ source: modelData.icon
+ opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
+ visible: modelData.isQuickshell
+ layer.enabled: true
+ layer.effect: MultiEffect {
+ saturation: 0
+ colorization: 1
+ colorizationColor: appHighlightActive ? focusedBorderColor : (isActive ? quickshellIconActiveColor : quickshellIconInactiveColor)
+ }
+ }
+
+ IconImage {
+ id: rowSteamIcon
+ anchors.fill: parent
+ source: modelData.icon
+ opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
+ visible: modelData.isSteamApp && modelData.icon
+ }
+
+ DankIcon {
+ anchors.centerIn: parent
+ size: root.appIconSize
+ name: "sports_esports"
+ color: appHighlightActive ? focusedBorderColor : Theme.widgetTextColor
+ opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
+ visible: modelData.isSteamApp && !modelData.icon
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ visible: (rowAppIcon.visible || rowSteamIcon.visible || modelData.isQuickshell) && appHighlightActive
+ color: "transparent"
+ radius: Theme.cornerRadius * (root.appIconSize / 40)
+ border.width: 1
+ border.color: focusedBorderColor
+ z: 1
+ }
+
+ MouseArea {
+ id: rowAppMouseArea
+ anchors.fill: parent
+ enabled: isActive
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ const winId = modelData.windowId;
+ if (!winId)
+ return;
+ if (CompositorService.isHyprland) {
+ Hyprland.dispatch(`focuswindow address:${winId}`);
+ } else if (CompositorService.isNiri) {
+ NiriService.focusWindow(winId);
+ }
+ }
+ }
+
+ Rectangle {
+ visible: modelData.count > 1 && !isActive
+ width: root.appIconSize * 0.67
+ height: root.appIconSize * 0.67
+ radius: root.appIconSize * 0.33
+ color: "black"
+ border.color: "white"
+ border.width: 1
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ z: 2
+
+ Text {
+ anchors.centerIn: parent
+ text: modelData.count
+ font.pixelSize: root.appIconSize * 0.44
+ color: "white"
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Component {
+ id: columnLayout
+ Column {
+ spacing: 4
+ visible: loadedIcons.length > 0 || SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName || loadedHasIcon
+
+ DankIcon {
+ visible: loadedHasIcon && loadedIconData?.type === "icon"
+ anchors.horizontalCenter: parent.horizontalCenter
+ name: loadedIconData?.value ?? ""
+ size: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
+ color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
+ weight: (isActive && !isPlaceholder) ? 500 : 400
+ }
+
+ StyledText {
+ visible: loadedHasIcon && loadedIconData?.type === "text"
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: loadedIconData?.value ?? ""
+ color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
+ font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
+ font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
+ }
+
+ StyledText {
+ visible: ((SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon) || (loadedHasIcon && SettingsData.showWorkspaceName && hasWorkspaceName)
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: loadedHasIcon ? (root.isVertical ? (modelData?.name ?? "").charAt(0) : (modelData?.name ?? "")) : root.getWorkspaceIndex(modelData, index)
+ color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
+ font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
+ font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
+ }
+
+ Repeater {
+ model: ScriptModel {
+ values: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)
+ }
+ delegate: Item {
+ width: root.appIconSize
+ height: root.appIconSize
+ readonly property bool appHighlightActive: SettingsData.workspaceActiveAppHighlightEnabled && modelData.active
+ readonly property color appBorderColor: appHighlightActive ? focusedBorderColor : Theme.primarySelected
+ readonly property color appGlyphColor: appHighlightActive ? focusedBorderColor : Theme.primary
+ readonly property real appOpacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
+
+ IconImage {
+ id: colAppIcon
+ anchors.fill: parent
+ source: modelData.icon || ""
+ opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
+ visible: !modelData.isQuickshell && !modelData.isSteamApp && status === Image.Ready
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ visible: !modelData.isQuickshell && !modelData.isSteamApp && colAppIcon.status !== Image.Ready
+ color: Theme.surfaceContainer
+ radius: Theme.cornerRadius * (root.appIconSize / 40)
+ border.width: 1
+ border.color: appBorderColor
+ opacity: appOpacity
+
+ StyledText {
+ anchors.centerIn: parent
+ text: (modelData.fallbackText || "?").charAt(0).toUpperCase()
+ font.pixelSize: parent.width * 0.45
+ color: appGlyphColor
+ font.weight: Font.Bold
+ }
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ visible: !modelData.isQuickshell && modelData.isSteamApp && colSteamIcon.status !== Image.Ready
+ color: Theme.surfaceContainer
+ radius: Theme.cornerRadius * (root.appIconSize / 40)
+ border.width: 1
+ border.color: appBorderColor
+ opacity: appOpacity
+
+ DankIcon {
+ anchors.centerIn: parent
+ size: parent.width * 0.7
+ name: "sports_esports"
+ color: appGlyphColor
+ }
+ }
+
+ IconImage {
+ anchors.fill: parent
+ source: modelData.icon
+ opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
+ visible: modelData.isQuickshell
+ layer.enabled: true
+ layer.effect: MultiEffect {
+ saturation: 0
+ colorization: 1
+ colorizationColor: appHighlightActive ? focusedBorderColor : (isActive ? quickshellIconActiveColor : quickshellIconInactiveColor)
+ }
+ }
+
+ IconImage {
+ id: colSteamIcon
+ anchors.fill: parent
+ source: modelData.icon
+ opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
+ visible: modelData.isSteamApp && modelData.icon
+ }
+
+ DankIcon {
+ anchors.centerIn: parent
+ size: root.appIconSize
+ name: "sports_esports"
+ color: appHighlightActive ? focusedBorderColor : Theme.widgetTextColor
+ opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
+ visible: modelData.isSteamApp && !modelData.icon
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ visible: (colAppIcon.visible || colSteamIcon.visible || modelData.isQuickshell) && appHighlightActive
+ color: "transparent"
+ radius: Theme.cornerRadius * (root.appIconSize / 40)
+ border.width: 1
+ border.color: focusedBorderColor
+ z: 1
+ }
+
+ MouseArea {
+ id: colAppMouseArea
+ anchors.fill: parent
+ enabled: isActive
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ const winId = modelData.windowId;
+ if (!winId)
+ return;
+ if (CompositorService.isHyprland) {
+ Hyprland.dispatch(`focuswindow address:${winId}`);
+ } else if (CompositorService.isNiri) {
+ NiriService.focusWindow(winId);
+ }
+ }
+ }
+
+ Rectangle {
+ visible: modelData.count > 1 && !isActive
+ width: root.appIconSize * 0.67
+ height: root.appIconSize * 0.67
+ radius: root.appIconSize * 0.33
+ color: "black"
+ border.color: "white"
+ border.width: 1
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ z: 2
+
+ Text {
+ anchors.centerIn: parent
+ text: modelData.count
+ font.pixelSize: root.appIconSize * 0.44
+ color: "white"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Component.onCompleted: updateAllData()
+
+ Connections {
+ target: CompositorService
+ function onSortedToplevelsChanged() {
+ delegateRoot.updateAllData();
+ }
+ }
+ Connections {
+ target: NiriService
+ enabled: CompositorService.isNiri
+ function onAllWorkspacesChanged() {
+ delegateRoot.updateAllData();
+ }
+ function onWindowUrgentChanged() {
+ delegateRoot.updateAllData();
+ }
+ function onWindowsChanged() {
+ delegateRoot.updateAllData();
+ }
+ }
+ Connections {
+ target: SettingsData
+ function onShowWorkspaceAppsChanged() {
+ delegateRoot.updateAllData();
+ }
+ function onWorkspaceNameIconsChanged() {
+ delegateRoot.updateAllData();
+ }
+ function onAppIdSubstitutionsChanged() {
+ delegateRoot.updateAllData();
+ }
+ }
+ Connections {
+ target: DwlService
+ enabled: CompositorService.isDwl
+ function onStateChanged() {
+ delegateRoot.updateAllData();
+ }
+ }
+ Connections {
+ target: Hyprland.workspaces
+ enabled: CompositorService.isHyprland
+ function onValuesChanged() {
+ delegateRoot.updateAllData();
+ }
+ }
+ Connections {
+ target: I3.workspaces
+ enabled: (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
+ function onValuesChanged() {
+ delegateRoot.updateAllData();
+ }
+ }
+ Connections {
+ target: ExtWorkspaceService
+ enabled: root.useExtWorkspace
+ function onStateChanged() {
+ delegateRoot.updateAllData();
+ }
+ }
+ }
+ }
+ }
+
+ Component.onCompleted: {
+ if (useExtWorkspace && !DMSService.activeSubscriptions.includes("extworkspace")) {
+ DMSService.addSubscription("extworkspace");
+ }
+ _updateBlurRegistration();
+ }
+
+ property bool _blurRegistered: false
+ readonly property bool _shouldBlur: BlurService.enabled && blurBarWindow && blurBarWindow.registerBlurWidget && !(barConfig?.noBackground ?? false) && root.visible && root.width > 0
+
+ on_ShouldBlurChanged: _updateBlurRegistration()
+
+ function _updateBlurRegistration() {
+ if (_shouldBlur && !_blurRegistered) {
+ blurBarWindow.registerBlurWidget(visualBackground);
+ _blurRegistered = true;
+ } else if (!_shouldBlur && _blurRegistered) {
+ if (blurBarWindow && blurBarWindow.unregisterBlurWidget)
+ blurBarWindow.unregisterBlurWidget(visualBackground);
+ _blurRegistered = false;
+ }
+ }
+
+ Component.onDestruction: {
+ if (_blurRegistered && blurBarWindow && blurBarWindow.unregisterBlurWidget)
+ blurBarWindow.unregisterBlurWidget(visualBackground);
+ }
+}