From c2ee41c8446961187dd7b8e2592ae298db386445 Mon Sep 17 00:00:00 2001 From: bbedward Date: Thu, 5 Feb 2026 14:37:05 -0500 Subject: [PATCH] running apps: make settings bar-specific --- quickshell/Common/SettingsData.qml | 3 +- quickshell/Common/settings/SettingsSpec.js | 3 +- .../Modules/DankBar/Widgets/RunningApps.qml | 29 +- quickshell/Modules/Settings/AudioTab.qml | 24 +- .../Modules/Settings/RunningAppsTab.qml | 15 - quickshell/Modules/Settings/WidgetsTab.qml | 14 +- .../Modules/Settings/WidgetsTabSection.qml | 324 ++++++++++++++++-- quickshell/Services/CompositorService.qml | 27 ++ quickshell/Services/NiriService.qml | 81 +++++ 9 files changed, 448 insertions(+), 72 deletions(-) diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index 288d1872..bc0dfcd8 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -279,8 +279,9 @@ Singleton { property int appsDockEnlargePercentage: 125 property int appsDockIconSizePercentage: 100 property bool keyboardLayoutNameCompactMode: false - property bool runningAppsCurrentWorkspace: false + property bool runningAppsCurrentWorkspace: true property bool runningAppsGroupByApp: false + property bool runningAppsCurrentMonitor: false property var appIdSubstitutions: [] property string centeringMode: "index" property string clockDateFormat: "" diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index 6440241c..1e712261 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -136,8 +136,9 @@ var SPEC = { appsDockEnlargePercentage: { def: 125 }, appsDockIconSizePercentage: { def: 100 }, keyboardLayoutNameCompactMode: { def: false }, - runningAppsCurrentWorkspace: { def: false }, + runningAppsCurrentWorkspace: { def: true }, runningAppsGroupByApp: { def: false }, + runningAppsCurrentMonitor: { def: false }, appIdSubstitutions: { def: [ { pattern: "Spotify", replacement: "spotify", type: "exact" }, diff --git a/quickshell/Modules/DankBar/Widgets/RunningApps.qml b/quickshell/Modules/DankBar/Widgets/RunningApps.qml index 3903f6d5..de320cd9 100644 --- a/quickshell/Modules/DankBar/Widgets/RunningApps.qml +++ b/quickshell/Modules/DankBar/Widgets/RunningApps.qml @@ -71,15 +71,20 @@ Item { property int _toplevelsUpdateTrigger: 0 property int _appIdSubstitutionsTrigger: 0 + readonly property bool _currentWorkspace: widgetData?.runningAppsCurrentWorkspace !== undefined ? widgetData.runningAppsCurrentWorkspace : SettingsData.runningAppsCurrentWorkspace + readonly property bool _currentMonitor: widgetData?.runningAppsCurrentMonitor !== undefined ? widgetData.runningAppsCurrentMonitor : SettingsData.runningAppsCurrentMonitor + readonly property bool _groupByApp: widgetData?.runningAppsGroupByApp !== undefined ? widgetData.runningAppsGroupByApp : SettingsData.runningAppsGroupByApp + readonly property var sortedToplevels: { _toplevelsUpdateTrigger; - const toplevels = CompositorService.sortedToplevels; + let toplevels = CompositorService.sortedToplevels; if (!toplevels || toplevels.length === 0) return []; - if (SettingsData.runningAppsCurrentWorkspace) { - return CompositorService.filterCurrentWorkspace(toplevels, parentScreen?.name) || []; - } + if (_currentWorkspace) + toplevels = CompositorService.filterCurrentWorkspace(toplevels, parentScreen?.name) || []; + if (_currentMonitor) + toplevels = CompositorService.filterCurrentDisplay(toplevels, parentScreen?.name) || []; return toplevels; } @@ -104,7 +109,7 @@ Item { } } readonly property var groupedWindows: { - if (!SettingsData.runningAppsGroupByApp) { + if (!_groupByApp) { return []; } try { @@ -133,7 +138,7 @@ Item { return []; } } - readonly property int windowCount: SettingsData.runningAppsGroupByApp ? (groupedWindows?.length || 0) : (sortedToplevels?.length || 0) + readonly property int windowCount: _groupByApp ? (groupedWindows?.length || 0) : (sortedToplevels?.length || 0) readonly property int calculatedSize: { if (windowCount === 0) { return 0; @@ -318,14 +323,14 @@ Item { Repeater { id: windowRepeater model: ScriptModel { - values: SettingsData.runningAppsGroupByApp ? groupedWindows : sortedToplevels - objectProp: SettingsData.runningAppsGroupByApp ? "appId" : "address" + values: _groupByApp ? groupedWindows : sortedToplevels + objectProp: _groupByApp ? "appId" : "address" } delegate: Item { id: delegateItem - property bool isGrouped: SettingsData.runningAppsGroupByApp + property bool isGrouped: root._groupByApp property var groupData: isGrouped ? modelData : null property var toplevelData: isGrouped ? (modelData.windows.length > 0 ? modelData.windows[0].toplevel : null) : modelData property bool isFocused: toplevelData ? toplevelData.activated : false @@ -564,14 +569,14 @@ Item { Repeater { id: windowRepeater model: ScriptModel { - values: SettingsData.runningAppsGroupByApp ? groupedWindows : sortedToplevels - objectProp: SettingsData.runningAppsGroupByApp ? "appId" : "address" + values: _groupByApp ? groupedWindows : sortedToplevels + objectProp: _groupByApp ? "appId" : "address" } delegate: Item { id: delegateItem - property bool isGrouped: SettingsData.runningAppsGroupByApp + property bool isGrouped: root._groupByApp property var groupData: isGrouped ? modelData : null property var toplevelData: isGrouped ? (modelData.windows.length > 0 ? modelData.windows[0].toplevel : null) : modelData property bool isFocused: toplevelData ? toplevelData.activated : false diff --git a/quickshell/Modules/Settings/AudioTab.qml b/quickshell/Modules/Settings/AudioTab.qml index ff80f157..cf2a0be7 100644 --- a/quickshell/Modules/Settings/AudioTab.qml +++ b/quickshell/Modules/Settings/AudioTab.qml @@ -105,7 +105,7 @@ Item { SettingsCard { tab: "audio" tags: ["audio", "device", "output", "speaker"] - title: I18n.tr("Output Devices") + title: I18n.tr("Output Devices", "Audio settings: speaker/headphone devices") settingKey: "audioOutputDevices" iconName: "volume_up" @@ -115,7 +115,7 @@ Item { StyledText { width: parent.width - text: I18n.tr("Set custom names for your audio output devices") + text: I18n.tr("Set custom names for your audio output devices", "Audio settings description") font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceVariantText wrapMode: Text.WordWrap @@ -152,7 +152,7 @@ Item { StyledText { width: parent.width - text: I18n.tr("No output devices found") + text: I18n.tr("No output devices found", "Audio settings empty state") font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceVariantText horizontalAlignment: Text.AlignHCenter @@ -175,7 +175,7 @@ Item { StyledText { width: parent.width - text: I18n.tr("Set custom names for your audio input devices") + text: I18n.tr("Set custom names for your audio input devices", "Audio settings description") font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceVariantText wrapMode: Text.WordWrap @@ -212,7 +212,7 @@ Item { StyledText { width: parent.width - text: I18n.tr("No input devices found") + text: I18n.tr("No input devices found", "Audio settings empty state") font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceVariantText horizontalAlignment: Text.AlignHCenter @@ -265,7 +265,7 @@ Item { anchors.horizontalCenter: parent.horizontalCenter StyledText { - text: I18n.tr("Restarting audio system...") + text: I18n.tr("Restarting audio system...", "Loading overlay while WirePlumber restarts") font.pixelSize: Theme.fontSizeLarge font.weight: Font.Medium color: Theme.surfaceText @@ -273,7 +273,7 @@ Item { } StyledText { - text: I18n.tr("This may take a few seconds") + text: I18n.tr("This may take a few seconds", "Loading overlay subtitle") font.pixelSize: Theme.fontSizeMedium color: Theme.surfaceVariantText anchors.horizontalCenter: parent.horizontalCenter @@ -340,7 +340,7 @@ Item { spacing: 4 StyledText { - text: I18n.tr("Set Custom Device Name") + text: I18n.tr("Set Custom Device Name", "Audio device rename dialog title") font.pixelSize: Theme.fontSizeLarge font.weight: Font.Bold color: Theme.surfaceText @@ -358,7 +358,7 @@ Item { StyledText { visible: AudioService.hasDeviceAlias(root.editingDevice?.name ?? "") - text: I18n.tr("Original: %1").arg(AudioService.originalName(root.editingDevice)) + text: I18n.tr("Original: %1", "Shows the original device name before renaming").arg(AudioService.originalName(root.editingDevice)) font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceVariantText width: parent.width @@ -373,7 +373,7 @@ Item { spacing: Theme.spacingM StyledText { - text: I18n.tr("Custom Name") + text: I18n.tr("Custom Name", "Audio device rename dialog field label") font.pixelSize: Theme.fontSizeMedium font.weight: Font.Medium color: Theme.surfaceText @@ -382,7 +382,7 @@ Item { DankTextField { id: nameInput width: parent.width - placeholderText: I18n.tr("Enter device name...") + placeholderText: I18n.tr("Enter device name...", "Audio device rename dialog placeholder") text: root.newDeviceName normalBorderColor: Theme.outlineMedium focusedBorderColor: Theme.primary @@ -412,7 +412,7 @@ Item { StyledText { width: parent.width - text: I18n.tr("Press Enter and the audio system will restart to apply the change") + text: I18n.tr("Press Enter and the audio system will restart to apply the change", "Audio device rename dialog hint") font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceVariantText wrapMode: Text.WordWrap diff --git a/quickshell/Modules/Settings/RunningAppsTab.qml b/quickshell/Modules/Settings/RunningAppsTab.qml index 636f8740..022e131d 100644 --- a/quickshell/Modules/Settings/RunningAppsTab.qml +++ b/quickshell/Modules/Settings/RunningAppsTab.qml @@ -19,20 +19,6 @@ Item { anchors.horizontalCenter: parent.horizontalCenter spacing: Theme.spacingXL - SettingsCard { - width: parent.width - iconName: "apps" - title: I18n.tr("Running Apps Settings") - settingKey: "runningApps" - - SettingsToggleRow { - text: I18n.tr("Running Apps Only In Current Workspace") - description: I18n.tr("Show only apps running in current workspace") - checked: SettingsData.runningAppsCurrentWorkspace - onToggled: checked => SettingsData.set("runningAppsCurrentWorkspace", checked) - } - } - SettingsCard { width: parent.width iconName: "find_replace" @@ -182,7 +168,6 @@ Item { } } } - } } } diff --git a/quickshell/Modules/Settings/WidgetsTab.qml b/quickshell/Modules/Settings/WidgetsTab.qml index 201d1bfd..964171d0 100644 --- a/quickshell/Modules/Settings/WidgetsTab.qml +++ b/quickshell/Modules/Settings/WidgetsTab.qml @@ -392,6 +392,12 @@ Item { widgetObj.showPrinterIcon = SettingsData.controlCenterShowPrinterIcon; widgetObj.showScreenSharingIcon = SettingsData.controlCenterShowScreenSharingIcon; } + if (widgetId === "runningApps") { + widgetObj.runningAppsCompactMode = SettingsData.runningAppsCompactMode; + widgetObj.runningAppsGroupByApp = SettingsData.runningAppsGroupByApp; + widgetObj.runningAppsCurrentWorkspace = SettingsData.runningAppsCurrentWorkspace; + widgetObj.runningAppsCurrentMonitor = false; + } if (widgetId === "diskUsage") widgetObj.mountPath = "/"; if (widgetId === "cpuUsage" || widgetId === "memUsage" || widgetId === "cpuTemp" || widgetId === "gpuTemp") @@ -419,7 +425,7 @@ Item { "id": widget.id, "enabled": widget.enabled }; - var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "minimumWidth", "showSwap", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"]; + var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "minimumWidth", "showSwap", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"]; for (var i = 0; i < keys.length; i++) { if (widget[keys[i]] !== undefined) result[keys[i]] = widget[keys[i]]; @@ -631,6 +637,12 @@ Item { item.focusedWindowCompactMode = widget.focusedWindowCompactMode; if (widget.runningAppsCompactMode !== undefined) item.runningAppsCompactMode = widget.runningAppsCompactMode; + if (widget.runningAppsGroupByApp !== undefined) + item.runningAppsGroupByApp = widget.runningAppsGroupByApp; + if (widget.runningAppsCurrentWorkspace !== undefined) + item.runningAppsCurrentWorkspace = widget.runningAppsCurrentWorkspace; + if (widget.runningAppsCurrentMonitor !== undefined) + item.runningAppsCurrentMonitor = widget.runningAppsCurrentMonitor; if (widget.keyboardLayoutNameCompactMode !== undefined) item.keyboardLayoutNameCompactMode = widget.keyboardLayoutNameCompactMode; if (widget.barMaxVisibleApps !== undefined) diff --git a/quickshell/Modules/Settings/WidgetsTabSection.qml b/quickshell/Modules/Settings/WidgetsTabSection.qml index 70bc8e41..5e23e1f6 100644 --- a/quickshell/Modules/Settings/WidgetsTabSection.qml +++ b/quickshell/Modules/Settings/WidgetsTabSection.qml @@ -37,7 +37,7 @@ Column { "id": widget.id, "enabled": widget.enabled }; - var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "minimumWidth", "showSwap", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"]; + var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "minimumWidth", "showSwap", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"]; for (var i = 0; i < keys.length; i++) { if (widget[keys[i]] !== undefined) result[keys[i]] = widget[keys[i]]; @@ -407,14 +407,47 @@ Column { } } + DankActionButton { + id: runningAppsMenuButton + visible: modelData.id === "runningApps" + buttonSize: 32 + iconName: "more_vert" + iconSize: 18 + iconColor: Theme.outline + onClicked: { + runningAppsContextMenu.widgetData = modelData; + runningAppsContextMenu.sectionId = root.sectionId; + runningAppsContextMenu.widgetIndex = index; + + var buttonPos = runningAppsMenuButton.mapToItem(root, 0, 0); + var popupWidth = runningAppsContextMenu.width; + var popupHeight = runningAppsContextMenu.height; + + var xPos = buttonPos.x - popupWidth - Theme.spacingS; + if (xPos < 0) + xPos = buttonPos.x + runningAppsMenuButton.width + Theme.spacingS; + + var yPos = buttonPos.y - popupHeight / 2 + runningAppsMenuButton.height / 2; + if (yPos < 0) { + yPos = Theme.spacingS; + } else if (yPos + popupHeight > root.height) { + yPos = root.height - popupHeight - Theme.spacingS; + } + + runningAppsContextMenu.x = xPos; + runningAppsContextMenu.y = yPos; + runningAppsContextMenu.open(); + } + } + Row { spacing: Theme.spacingXS - visible: modelData.id === "clock" || modelData.id === "focusedWindow" || modelData.id === "runningApps" || modelData.id === "keyboard_layout_name" || modelData.id === "appsDock" + visible: modelData.id === "clock" || modelData.id === "focusedWindow" || modelData.id === "keyboard_layout_name" || modelData.id === "appsDock" DankActionButton { id: compactModeButton buttonSize: 28 - visible: modelData.id === "clock" || modelData.id === "focusedWindow" || modelData.id === "runningApps" || modelData.id === "keyboard_layout_name" + visible: modelData.id === "clock" || modelData.id === "focusedWindow" || modelData.id === "keyboard_layout_name" iconName: { const isCompact = (() => { switch (modelData.id) { @@ -422,8 +455,6 @@ Column { return modelData.clockCompactMode !== undefined ? modelData.clockCompactMode : SettingsData.clockCompactMode; case "focusedWindow": return modelData.focusedWindowCompactMode !== undefined ? modelData.focusedWindowCompactMode : SettingsData.focusedWindowCompactMode; - case "runningApps": - return modelData.runningAppsCompactMode !== undefined ? modelData.runningAppsCompactMode : SettingsData.runningAppsCompactMode; case "keyboard_layout_name": return modelData.keyboardLayoutNameCompactMode !== undefined ? modelData.keyboardLayoutNameCompactMode : SettingsData.keyboardLayoutNameCompactMode; default: @@ -440,8 +471,6 @@ Column { return modelData.clockCompactMode !== undefined ? modelData.clockCompactMode : SettingsData.clockCompactMode; case "focusedWindow": return modelData.focusedWindowCompactMode !== undefined ? modelData.focusedWindowCompactMode : SettingsData.focusedWindowCompactMode; - case "runningApps": - return modelData.runningAppsCompactMode !== undefined ? modelData.runningAppsCompactMode : SettingsData.runningAppsCompactMode; case "keyboard_layout_name": return modelData.keyboardLayoutNameCompactMode !== undefined ? modelData.keyboardLayoutNameCompactMode : SettingsData.keyboardLayoutNameCompactMode; default: @@ -457,8 +486,6 @@ Column { return modelData.clockCompactMode !== undefined ? modelData.clockCompactMode : SettingsData.clockCompactMode; case "focusedWindow": return modelData.focusedWindowCompactMode !== undefined ? modelData.focusedWindowCompactMode : SettingsData.focusedWindowCompactMode; - case "runningApps": - return modelData.runningAppsCompactMode !== undefined ? modelData.runningAppsCompactMode : SettingsData.runningAppsCompactMode; case "keyboard_layout_name": return modelData.keyboardLayoutNameCompactMode !== undefined ? modelData.keyboardLayoutNameCompactMode : SettingsData.keyboardLayoutNameCompactMode; default: @@ -474,8 +501,6 @@ Column { return modelData.clockCompactMode !== undefined ? modelData.clockCompactMode : SettingsData.clockCompactMode; case "focusedWindow": return modelData.focusedWindowCompactMode !== undefined ? modelData.focusedWindowCompactMode : SettingsData.focusedWindowCompactMode; - case "runningApps": - return modelData.runningAppsCompactMode !== undefined ? modelData.runningAppsCompactMode : SettingsData.runningAppsCompactMode; case "keyboard_layout_name": return modelData.keyboardLayoutNameCompactMode !== undefined ? modelData.keyboardLayoutNameCompactMode : SettingsData.keyboardLayoutNameCompactMode; default: @@ -490,25 +515,6 @@ Column { } } - DankActionButton { - id: groupByAppButton - buttonSize: 28 - visible: modelData.id === "runningApps" - iconName: "apps" - iconSize: 16 - iconColor: SettingsData.runningAppsGroupByApp ? Theme.primary : Theme.outline - onClicked: { - SettingsData.set("runningAppsGroupByApp", !SettingsData.runningAppsGroupByApp); - } - onEntered: { - const tooltipText = SettingsData.runningAppsGroupByApp ? "Ungroup" : "Group by App"; - sharedTooltip.show(tooltipText, groupByAppButton, 0, 0, "bottom"); - } - onExited: { - sharedTooltip.hide(); - } - } - DankActionButton { id: appsDockMenuButton buttonSize: 32 @@ -1419,6 +1425,264 @@ Column { } } + Popup { + id: runningAppsContextMenu + + property var widgetData: null + property string sectionId: "" + property int widgetIndex: -1 + + readonly property var currentWidgetData: (widgetIndex >= 0 && widgetIndex < root.items.length) ? root.items[widgetIndex] : widgetData + + width: 240 + height: runningAppsMenuColumn.implicitHeight + Theme.spacingS * 2 + padding: 0 + modal: true + focus: true + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + background: Rectangle { + color: Theme.surfaceContainer + radius: Theme.cornerRadius + border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) + border.width: 0 + } + + contentItem: Item { + Column { + id: runningAppsMenuColumn + anchors.fill: parent + anchors.margins: Theme.spacingS + spacing: 2 + + Rectangle { + width: parent.width + height: 32 + radius: Theme.cornerRadius + color: "transparent" + + StyledText { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + text: I18n.tr("Running Apps Settings") + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Medium + color: Theme.surfaceText + } + } + + Rectangle { + width: parent.width + height: 32 + radius: Theme.cornerRadius + color: raCompactArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" + + Row { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankIcon { + name: "zoom_in" + size: 16 + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: I18n.tr("Compact Mode") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } + + DankToggle { + id: raCompactToggle + anchors.right: parent.right + anchors.rightMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + width: 40 + height: 20 + checked: runningAppsContextMenu.currentWidgetData?.runningAppsCompactMode ?? SettingsData.runningAppsCompactMode + onToggled: { + root.overflowSettingChanged(runningAppsContextMenu.sectionId, runningAppsContextMenu.widgetIndex, "runningAppsCompactMode", toggled); + } + } + + MouseArea { + id: raCompactArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onPressed: { + raCompactToggle.checked = !raCompactToggle.checked; + root.overflowSettingChanged(runningAppsContextMenu.sectionId, runningAppsContextMenu.widgetIndex, "runningAppsCompactMode", raCompactToggle.checked); + } + } + } + + Rectangle { + width: parent.width + height: 32 + radius: Theme.cornerRadius + color: raGroupArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" + + Row { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankIcon { + name: "apps" + size: 16 + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: I18n.tr("Group by App") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } + + DankToggle { + id: raGroupToggle + anchors.right: parent.right + anchors.rightMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + width: 40 + height: 20 + checked: runningAppsContextMenu.currentWidgetData?.runningAppsGroupByApp ?? SettingsData.runningAppsGroupByApp + onToggled: { + root.overflowSettingChanged(runningAppsContextMenu.sectionId, runningAppsContextMenu.widgetIndex, "runningAppsGroupByApp", toggled); + } + } + + MouseArea { + id: raGroupArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onPressed: { + raGroupToggle.checked = !raGroupToggle.checked; + root.overflowSettingChanged(runningAppsContextMenu.sectionId, runningAppsContextMenu.widgetIndex, "runningAppsGroupByApp", raGroupToggle.checked); + } + } + } + + Rectangle { + width: parent.width + height: 32 + radius: Theme.cornerRadius + color: raWorkspaceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" + + Row { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankIcon { + name: "workspaces" + size: 16 + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: I18n.tr("Current Workspace", "Running apps filter: only show apps from the active workspace") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } + + DankToggle { + id: raWorkspaceToggle + anchors.right: parent.right + anchors.rightMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + width: 40 + height: 20 + checked: runningAppsContextMenu.currentWidgetData?.runningAppsCurrentWorkspace ?? SettingsData.runningAppsCurrentWorkspace + onToggled: { + root.overflowSettingChanged(runningAppsContextMenu.sectionId, runningAppsContextMenu.widgetIndex, "runningAppsCurrentWorkspace", toggled); + } + } + + MouseArea { + id: raWorkspaceArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onPressed: { + raWorkspaceToggle.checked = !raWorkspaceToggle.checked; + root.overflowSettingChanged(runningAppsContextMenu.sectionId, runningAppsContextMenu.widgetIndex, "runningAppsCurrentWorkspace", raWorkspaceToggle.checked); + } + } + } + + Rectangle { + width: parent.width + height: 32 + radius: Theme.cornerRadius + color: raDisplayArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" + + Row { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankIcon { + name: "monitor" + size: 16 + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: I18n.tr("Current Monitor", "Running apps filter: only show apps from the same monitor") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } + + DankToggle { + id: raDisplayToggle + anchors.right: parent.right + anchors.rightMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + width: 40 + height: 20 + checked: runningAppsContextMenu.currentWidgetData?.runningAppsCurrentMonitor ?? SettingsData.runningAppsCurrentMonitor + onToggled: { + root.overflowSettingChanged(runningAppsContextMenu.sectionId, runningAppsContextMenu.widgetIndex, "runningAppsCurrentMonitor", toggled); + } + } + + MouseArea { + id: raDisplayArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onPressed: { + raDisplayToggle.checked = !raDisplayToggle.checked; + root.overflowSettingChanged(runningAppsContextMenu.sectionId, runningAppsContextMenu.widgetIndex, "runningAppsCurrentMonitor", raDisplayToggle.checked); + } + } + } + } + } + } + Popup { id: appsDockContextMenu diff --git a/quickshell/Services/CompositorService.qml b/quickshell/Services/CompositorService.qml index d591dcf0..ec7363fa 100644 --- a/quickshell/Services/CompositorService.qml +++ b/quickshell/Services/CompositorService.qml @@ -335,6 +335,33 @@ Singleton { return toplevels; } + function filterCurrentDisplay(toplevels, screenName) { + if (!toplevels || toplevels.length === 0 || !screenName) + return toplevels; + if (useNiriSorting) + return NiriService.filterCurrentDisplay(toplevels, screenName); + if (isHyprland) + return filterHyprlandCurrentDisplaySafe(toplevels, screenName); + return toplevels; + } + + function filterHyprlandCurrentDisplaySafe(toplevels, screenName) { + if (!toplevels || toplevels.length === 0 || !Hyprland.toplevels) + return toplevels; + + let monitorWindows = new Set(); + try { + const hy = Array.from(Hyprland.toplevels.values); + for (const t of hy) { + const mon = _get(t, ["monitor", "name"], ""); + if (mon === screenName && t.wayland) + monitorWindows.add(t.wayland); + } + } catch (e) {} + + return toplevels.filter(w => monitorWindows.has(w)); + } + function filterHyprlandCurrentWorkspaceSafe(toplevels, screenName) { if (!toplevels || toplevels.length === 0 || !Hyprland.toplevels) return toplevels; diff --git a/quickshell/Services/NiriService.qml b/quickshell/Services/NiriService.qml index 53ebad8e..bcf77f80 100644 --- a/quickshell/Services/NiriService.qml +++ b/quickshell/Services/NiriService.qml @@ -1084,6 +1084,87 @@ Singleton { return result; } + function filterCurrentDisplay(toplevels, screenName) { + if (!toplevels || toplevels.length === 0 || !screenName) + return toplevels; + + const outputWorkspaceIds = new Set(); + for (var i = 0; i < allWorkspaces.length; i++) { + const ws = allWorkspaces[i]; + if (ws.output === screenName) + outputWorkspaceIds.add(ws.id); + } + + if (outputWorkspaceIds.size === 0) + return toplevels; + + const displayWindows = windows.filter(niriWindow => outputWorkspaceIds.has(niriWindow.workspace_id)); + const usedToplevels = new Set(); + const result = []; + + for (const niriWindow of displayWindows) { + let bestMatch = null; + let bestScore = -1; + + for (const toplevel of toplevels) { + if (usedToplevels.has(toplevel)) + continue; + if (toplevel.appId === niriWindow.app_id) { + let score = 1; + + if (niriWindow.title && toplevel.title) { + if (toplevel.title === niriWindow.title) { + score = 3; + } else if (toplevel.title.includes(niriWindow.title) || niriWindow.title.includes(toplevel.title)) { + score = 2; + } + } + + if (score > bestScore) { + bestScore = score; + bestMatch = toplevel; + if (score === 3) + break; + } + } + } + + if (!bestMatch) + continue; + usedToplevels.add(bestMatch); + + const workspace = workspaces[niriWindow.workspace_id]; + const isFocused = niriWindow.is_focused ?? (workspace && workspace.active_window_id === niriWindow.id) ?? false; + + const enrichedToplevel = { + "appId": bestMatch.appId, + "title": bestMatch.title, + "activated": isFocused, + "niriWindowId": niriWindow.id, + "niriWorkspaceId": niriWindow.workspace_id, + "activate": function () { + return NiriService.focusWindow(niriWindow.id); + }, + "close": function () { + if (bestMatch.close) { + return bestMatch.close(); + } + return false; + } + }; + + for (let prop in bestMatch) { + if (!(prop in enrichedToplevel)) { + enrichedToplevel[prop] = bestMatch[prop]; + } + } + + result.push(enrichedToplevel); + } + + return result; + } + function generateNiriLayoutConfig() { if (!CompositorService.isNiri || configGenerationPending) return;