From dd3b0f6ae822b41d3926af7730b1d67405f2d774 Mon Sep 17 00:00:00 2001 From: Josevi Date: Wed, 8 Jul 2026 20:47:59 +0200 Subject: [PATCH] plasmoides KDE Store/GitHub (gnome-pager, kMenu, kvitals, windowtitle, claude, launchpad) --- .../contents/config/config.qml | 23 + .../contents/config/main.xml | 139 ++ .../ui/DynamicWorkspacesController.qml | 117 ++ .../contents/ui/IndicatorMetrics.qml | 57 + .../contents/ui/RenameDialog.qml | 80 ++ .../contents/ui/ScreenCurrentDesktop.qml | 60 + .../contents/ui/WindowAggregator.qml | 209 +++ .../contents/ui/WorkspaceDot.qml | 199 +++ .../contents/ui/WorkspaceIndicator.qml | 281 ++++ .../contents/ui/config/ConfigAppearance.qml | 251 ++++ .../contents/ui/config/ConfigGeneral.qml | 145 ++ .../contents/ui/config/ConfigPageBase.qml | 58 + .../contents/ui/config/ConfigSlider.qml | 61 + .../contents/ui/coordinator.js | 62 + .../contents/ui/logic.js | 556 ++++++++ .../contents/ui/main.qml | 300 +++++ .../metadata.json | 35 + .../org.51n7.kMenu/contents/config/config.qml | 22 + .../org.51n7.kMenu/contents/config/main.xml | 98 ++ .../contents/ui/ItemListDelegate.qml | 251 ++++ .../contents/ui/ItemListDialog.qml | 99 ++ .../contents/ui/ItemListView.qml | 391 ++++++ .../contents/ui/JsonMenuModel.qml | 324 +++++ .../contents/ui/MenuRepresentation.qml | 132 ++ .../contents/ui/config/ConfigEditJson.qml | 48 + .../contents/ui/config/ConfigGeneral.qml | 166 +++ .../contents/ui/config/ConfigMenuJson.qml | 43 + .../contents/ui/config/MenuBuilder.qml | 954 ++++++++++++++ .../contents/ui/config/TreeItemDelegate.qml | 273 ++++ .../org.51n7.kMenu/contents/ui/main.qml | 108 ++ .../plasmoids/org.51n7.kMenu/metadata.json | 22 + .../plasmoids/org.51n7.kMenu/translate/fr.po | 130 ++ .../org.51n7.kMenu/translate/template.pot | 112 ++ .../LICENSE | 674 ++++++++++ .../README.md | 212 +++ .../claude-usage-widget.plasmoid | Bin 0 -> 42178 bytes .../contents/config/config.qml | 15 + .../contents/config/main.xml | 54 + .../contents/icons/claude-usage-widget.svg | 26 + .../contents/icons/claude.svg | 3 + .../contents/icons/widget.svg | 22 + .../contents/screenshot.png | Bin 0 -> 17899 bytes .../contents/ui/Translations.qml | 790 +++++++++++ .../contents/ui/configGeneral.qml | 191 +++ .../contents/ui/main.qml | 1163 +++++++++++++++++ .../executable_install.sh | 16 + .../logo.png | Bin 0 -> 51520 bytes .../logo.svg | 36 + .../metadata.json | 21 + .../screenshots/panel.png | Bin 0 -> 4947 bytes .../screenshots/popup.png | Bin 0 -> 61972 bytes .../org.kde.plasma.kvitals/CHANGELOG.md | 250 ++++ .../org.kde.plasma.kvitals/CODE_OF_CONDUCT.md | 128 ++ .../org.kde.plasma.kvitals/CONTRIBUTING.md | 75 ++ .../plasmoids/org.kde.plasma.kvitals/LICENSE | 674 ++++++++++ .../org.kde.plasma.kvitals/README.md | 105 ++ .../org.kde.plasma.kvitals/ROADMAP.md | 78 ++ .../org.kde.plasma.kvitals/SECURITY.md | 81 ++ .../contents/config/config.qml | 26 + .../contents/config/main.xml | 209 +++ .../contents/ui/CompactView.qml | 211 +++ .../contents/ui/FullView.qml | 48 + .../contents/ui/configColors.qml | 425 ++++++ .../contents/ui/configGeneral.qml | 154 +++ .../contents/ui/configIcons.qml | 131 ++ .../contents/ui/configMetrics.qml | 669 ++++++++++ .../contents/ui/main.qml | 558 ++++++++ .../contents/ui/sensors/BatterySensors.qml | 193 +++ .../contents/ui/sensors/CpuSensors.qml | 47 + .../contents/ui/sensors/DiskSensors.qml | 113 ++ .../contents/ui/sensors/FanSensors.qml | 117 ++ .../contents/ui/sensors/GpuSensors.qml | 270 ++++ .../contents/ui/sensors/MemorySensors.qml | 38 + .../contents/ui/sensors/NetworkSensors.qml | 43 + .../contents/ui/sensors/TempSensors.qml | 31 + .../contents/ui/sensors/Utils.qml | 99 ++ .../contents/ui/sensors/qmldir | 10 + .../docs/architecture.md | 133 ++ .../org.kde.plasma.kvitals/docs/changelog.md | 250 ++++ .../docs/configuration.md | 156 +++ .../docs/contributing.md | 86 ++ .../org.kde.plasma.kvitals/docs/dataflow.svg | 119 ++ .../org.kde.plasma.kvitals/docs/index.md | 30 + .../docs/installation.md | 58 + .../org.kde.plasma.kvitals/docs/robots.txt | 39 + .../docs/troubleshooting.md | 141 ++ .../executable_install-remote.sh | 51 + .../executable_install.sh | 37 + .../org.kde.plasma.kvitals/metadata.json | 21 + .../org.kde.plasma.kvitals/mkdocs.yaml | 82 ++ .../org.kde.plasma.kvitals/package.sh | 24 + .../screenshots/demo.gif | Bin 0 -> 997294 bytes .../screenshots/general_settings.png | Bin 0 -> 46586 bytes .../screenshots/icons_only.png | Bin 0 -> 166660 bytes .../screenshots/icons_settings.png | Bin 0 -> 51189 bytes .../screenshots/icons_text.png | Bin 0 -> 145228 bytes .../screenshots/metrics_settings.png | Bin 0 -> 51910 bytes .../screenshots/text_only.png | Bin 0 -> 127689 bytes .../org.kde.plasma.windowtitlereborn/LICENSE | 674 ++++++++++ .../README.md | 110 ++ .../contents/config/config.qml | 21 + .../contents/config/main.xml | 47 + .../contents/tools/Tools.js | 51 + .../contents/ui/ActionsMouseArea.qml | 51 + .../contents/ui/CItem.qml | 11 + .../contents/ui/PlasmaTasksModel.qml | 219 ++++ .../contents/ui/Title.qml | 100 ++ .../contents/ui/config/ConfigAppearance.qml | 260 ++++ .../contents/ui/config/ConfigBehavior.qml | 52 + .../ui/config/ConfigSubstitutions.qml | 49 + .../contents/ui/config/IconDialog.qml | 54 + .../contents/ui/config/MockWidget.qml | 113 ++ .../contents/ui/config/SubsBox.qml | 33 + .../contents/ui/main.qml | 183 +++ .../metadata.json | 23 + .../screenshots/config-appearance.png | Bin 0 -> 135481 bytes .../screenshots/config-behavior.png | Bin 0 -> 64558 bytes .../screenshots/config-substitutions.png | Bin 0 -> 63855 bytes .../screenshots/preview.png | Bin 0 -> 65439 bytes .../contents/config/config.qml | 17 + .../contents/config/main.xml | 95 ++ .../contents/ui/ActionMenu.qml | 122 ++ .../contents/ui/CompactRepresentation.qml | 119 ++ .../contents/ui/ConfigGeneral.qml | 239 ++++ .../contents/ui/DashboardRepresentation.qml | 523 ++++++++ .../contents/ui/ItemGridDelegate.qml | 120 ++ .../contents/ui/ItemGridView.qml | 472 +++++++ .../contents/ui/ItemListDelegate.qml | 137 ++ .../contents/ui/ItemListDialog.qml | 99 ++ .../contents/ui/ItemListView.qml | 267 ++++ .../contents/ui/ItemMultiGridView.qml | 235 ++++ .../contents/ui/SideBarItem.qml | 142 ++ .../contents/ui/SideBarSection.qml | 83 ++ .../contents/ui/code/tools.js | 181 +++ .../tt.launchpadPlasma/contents/ui/main.qml | 274 ++++ .../tt.launchpadPlasma/metadata.json | 24 + 136 files changed, 20239 insertions(+) create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/config/config.qml create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/config/main.xml create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/DynamicWorkspacesController.qml create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/IndicatorMetrics.qml create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/RenameDialog.qml create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/ScreenCurrentDesktop.qml create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/WindowAggregator.qml create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/WorkspaceDot.qml create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/WorkspaceIndicator.qml create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/config/ConfigAppearance.qml create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/config/ConfigGeneral.qml create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/config/ConfigPageBase.qml create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/config/ConfigSlider.qml create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/coordinator.js create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/logic.js create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/main.qml create mode 100644 dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/metadata.json create mode 100644 dot_local/share/plasma/plasmoids/org.51n7.kMenu/contents/config/config.qml create mode 100644 dot_local/share/plasma/plasmoids/org.51n7.kMenu/contents/config/main.xml create mode 100644 dot_local/share/plasma/plasmoids/org.51n7.kMenu/contents/ui/ItemListDelegate.qml create mode 100644 dot_local/share/plasma/plasmoids/org.51n7.kMenu/contents/ui/ItemListDialog.qml create mode 100644 dot_local/share/plasma/plasmoids/org.51n7.kMenu/contents/ui/ItemListView.qml create mode 100644 dot_local/share/plasma/plasmoids/org.51n7.kMenu/contents/ui/JsonMenuModel.qml create mode 100644 dot_local/share/plasma/plasmoids/org.51n7.kMenu/contents/ui/MenuRepresentation.qml create mode 100644 dot_local/share/plasma/plasmoids/org.51n7.kMenu/contents/ui/config/ConfigEditJson.qml create mode 100644 dot_local/share/plasma/plasmoids/org.51n7.kMenu/contents/ui/config/ConfigGeneral.qml create mode 100644 dot_local/share/plasma/plasmoids/org.51n7.kMenu/contents/ui/config/ConfigMenuJson.qml create mode 100644 dot_local/share/plasma/plasmoids/org.51n7.kMenu/contents/ui/config/MenuBuilder.qml create mode 100644 dot_local/share/plasma/plasmoids/org.51n7.kMenu/contents/ui/config/TreeItemDelegate.qml create mode 100644 dot_local/share/plasma/plasmoids/org.51n7.kMenu/contents/ui/main.qml create mode 100644 dot_local/share/plasma/plasmoids/org.51n7.kMenu/metadata.json create mode 100644 dot_local/share/plasma/plasmoids/org.51n7.kMenu/translate/fr.po create mode 100644 dot_local/share/plasma/plasmoids/org.51n7.kMenu/translate/template.pot create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/LICENSE create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/README.md create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/claude-usage-widget.plasmoid create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/contents/config/config.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/contents/config/main.xml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/contents/icons/claude-usage-widget.svg create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/contents/icons/claude.svg create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/contents/icons/widget.svg create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/contents/screenshot.png create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/contents/ui/Translations.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/contents/ui/configGeneral.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/contents/ui/main.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/executable_install.sh create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/logo.png create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/logo.svg create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/metadata.json create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/screenshots/panel.png create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.claudeusageswitcher/screenshots/popup.png create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/CHANGELOG.md create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/CODE_OF_CONDUCT.md create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/CONTRIBUTING.md create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/LICENSE create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/README.md create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/ROADMAP.md create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/SECURITY.md create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/config/config.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/config/main.xml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/CompactView.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/FullView.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/configColors.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/configGeneral.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/configIcons.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/configMetrics.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/main.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/sensors/BatterySensors.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/sensors/CpuSensors.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/sensors/DiskSensors.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/sensors/FanSensors.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/sensors/GpuSensors.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/sensors/MemorySensors.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/sensors/NetworkSensors.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/sensors/TempSensors.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/sensors/Utils.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/contents/ui/sensors/qmldir create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/docs/architecture.md create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/docs/changelog.md create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/docs/configuration.md create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/docs/contributing.md create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/docs/dataflow.svg create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/docs/index.md create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/docs/installation.md create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/docs/robots.txt create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/docs/troubleshooting.md create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/executable_install-remote.sh create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/executable_install.sh create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/metadata.json create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/mkdocs.yaml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/package.sh create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/screenshots/demo.gif create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/screenshots/general_settings.png create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/screenshots/icons_only.png create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/screenshots/icons_settings.png create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/screenshots/icons_text.png create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/screenshots/metrics_settings.png create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.kvitals/screenshots/text_only.png create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/LICENSE create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/README.md create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/contents/config/config.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/contents/config/main.xml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/contents/tools/Tools.js create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/contents/ui/ActionsMouseArea.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/contents/ui/CItem.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/contents/ui/PlasmaTasksModel.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/contents/ui/Title.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/contents/ui/config/ConfigAppearance.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/contents/ui/config/ConfigBehavior.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/contents/ui/config/ConfigSubstitutions.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/contents/ui/config/IconDialog.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/contents/ui/config/MockWidget.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/contents/ui/config/SubsBox.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/contents/ui/main.qml create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/metadata.json create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/screenshots/config-appearance.png create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/screenshots/config-behavior.png create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/screenshots/config-substitutions.png create mode 100644 dot_local/share/plasma/plasmoids/org.kde.plasma.windowtitlereborn/screenshots/preview.png create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/contents/config/config.qml create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/contents/config/main.xml create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/contents/ui/ActionMenu.qml create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/contents/ui/CompactRepresentation.qml create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/contents/ui/ConfigGeneral.qml create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/contents/ui/DashboardRepresentation.qml create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/contents/ui/ItemGridDelegate.qml create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/contents/ui/ItemGridView.qml create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/contents/ui/ItemListDelegate.qml create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/contents/ui/ItemListDialog.qml create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/contents/ui/ItemListView.qml create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/contents/ui/ItemMultiGridView.qml create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/contents/ui/SideBarItem.qml create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/contents/ui/SideBarSection.qml create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/contents/ui/code/tools.js create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/contents/ui/main.qml create mode 100644 dot_local/share/plasma/plasmoids/tt.launchpadPlasma/metadata.json diff --git a/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/config/config.qml b/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/config/config.qml new file mode 100644 index 0000000..3e69178 --- /dev/null +++ b/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/config/config.qml @@ -0,0 +1,23 @@ +/* + * Plasma Gnome Pager — config.qml + * + * SPDX-FileCopyrightText: 2026 Kenan Salar + * SPDX-License-Identifier: GPL-3.0-or-later + * + * The settings dialog's category list. ConfigCategory.source resolves relative to contents/ui/, so the + * pages live in contents/ui/config/ while this file + main.xml live in contents/config/ (plasmoid.md). + */ +import org.kde.plasma.configuration + +ConfigModel { + ConfigCategory { + name: i18n("Behavior") + icon: "preferences-system-windows-actions" + source: "config/ConfigGeneral.qml" + } + ConfigCategory { + name: i18n("Appearance") + icon: "preferences-desktop-color" + source: "config/ConfigAppearance.qml" + } +} diff --git a/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/config/main.xml b/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/config/main.xml new file mode 100644 index 0000000..a1b263a --- /dev/null +++ b/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/config/main.xml @@ -0,0 +1,139 @@ + + + + + + + + + true + + + + false + + + + false + + + + 0 + + + + true + + + + true + + + + true + + + + true + + + + false + + + + + + + + 0 + + + + + + + 0 + + + + false + + + + false + + + + 0 + + + + 0 + + + + 0.5 + + + + 3.5 + + + + 0.45 + + + + 0.8 + + + + false + + + + 0.7 + + + + 0 + + + + true + + + + #3daee9 + + + + #eff0f1 + + + + #3daee9 + + + diff --git a/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/DynamicWorkspacesController.qml b/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/DynamicWorkspacesController.qml new file mode 100644 index 0000000..19562fe --- /dev/null +++ b/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/DynamicWorkspacesController.qml @@ -0,0 +1,117 @@ +/* + * Plasma Gnome Pager — DynamicWorkspacesController.qml + * + * SPDX-FileCopyrightText: 2026 Kenan Salar + * SPDX-License-Identifier: GPL-3.0-or-later + * + * GNOME-style dynamic-workspaces controller — a non-visual Item (QtQuick + pure .js only, headless-tested). + * When enabled, keep one empty trailing desktop via ONE KWin add/remove per cycle. The desktop SET is + * global, so coordinator.js does setting SYNC + single-WRITER election (else panels double-add → a flash). + */ +pragma ComponentBehavior: Bound + +import QtQuick + +import "logic.js" as Logic +import "coordinator.js" as Coordinator // single-writer election + prefix sync across instances + +Item { + id: controller + + // Inputs (injected by main.qml). dynamicEnabled (not `enabled` — QQuickItem already defines that). + property bool dynamicEnabled: false + property string namePrefix: "" // base name for auto-created desktops ("" = defaultPrefix) + property string defaultPrefix: "Desktop" // i18n default, passed IN so this stays i18n-free; never empty (KWin drops empty-name createDesktop) + property var virtualDesktopInfo: null // the read source (null-safe throughout — transiently absent) + // Per-desktop occupancy bool[], index-aligned with desktopIds. The length guard in dynamicWorkspacePlan + // makes a transient frame (occupancy still lagging a just-changed desktop set) a no-op until it catches up. + property var desktopOccupancy: [] + + // Outputs (the two things only main.qml's e2e boundary can do). + signal dispatchRequested(var spec) // a built KWin add/remove spec → root.dispatch(spec) + signal syncConfigRequested(bool nextEnabled, string nextPrefix) // mirror the global setting into persisted config + + // Internal state. + property bool dynBusy: false + property int dynToken: 0 + // Liveness fallback only — the real lock-clear is vdi.desktopIdsChanged below. + readonly property int busyFallbackMs: 750 + + Timer { + id: dynBusyTimer + interval: controller.busyFallbackMs + onTriggered: controller.dynBusy = false + } + + // Join the coordinator (syncConfigRequested is the push channel). Adopt the global if a sibling seeded + // it, else seed it from our value, then evaluate once. Leave on teardown (stop counting in the election). + Component.onCompleted: { + controller.dynToken = Coordinator.join((en, pf) => controller.syncConfigRequested(en, pf)); + if (Coordinator.haveGlobal()) + controller.syncConfigRequested(Coordinator.globalEnabled(), Coordinator.globalPrefix()); + else + Coordinator.publish(controller.dynamicEnabled, controller.namePrefix); + controller.scheduleDynamic(); + } + Component.onDestruction: Coordinator.leave(controller.dynToken) + + // Our setting changed: if it differs from the global WE changed it → publish to every panel; else it's + // a sync echo → just re-evaluate. Guard the pre-join window (dynToken 0): config onChanged fires before + // Component.onCompleted, and publishing then registers a phantom value that stalls the real writer. + function publishDynamicConfig() { + if (controller.dynToken === 0) + return; + if (!Coordinator.haveGlobal() + || controller.dynamicEnabled !== Coordinator.globalEnabled() + || controller.namePrefix !== Coordinator.globalPrefix()) + Coordinator.publish(controller.dynamicEnabled, controller.namePrefix); + controller.scheduleDynamic(); + } + + // Coalesce a burst of occupancy / desktop-set changes into ONE evaluation next tick. + function scheduleDynamic() { + if (!controller.dynamicEnabled) + return; + Qt.callLater(controller.evaluateDynamic); + } + + // Compute and dispatch the single action for the freshest state, or nothing. Only the elected writer acts (panels never double-add). + function evaluateDynamic() { + if (!controller.dynamicEnabled || controller.dynBusy) + return; + if (!Coordinator.isWriter(controller.dynToken)) + return; // another instance is the single global writer + const ids = controller.virtualDesktopInfo?.desktopIds ?? []; + const plan = Logic.dynamicWorkspacePlan(controller.desktopOccupancy, ids); + if (!plan) + return; + let spec = null; + if (plan.kind === "add") { + // Name " N" (prefix synced across panels); position == current count (append), so the number is pos + 1. + const pos = controller.virtualDesktopInfo?.numberOfDesktops ?? ids.length; + spec = Logic.addSpec(pos, Logic.formatDynamicDesktopName(controller.namePrefix, pos + 1, controller.defaultPrefix)); + } else if (plan.kind === "remove") { + const count = controller.virtualDesktopInfo?.numberOfDesktops ?? ids.length; + spec = Logic.removeSpec(plan.uuid, count); + } + if (!spec) + return; + controller.dynBusy = true; + controller.dispatchRequested(spec); + dynBusyTimer.restart(); + } + + // Triggers: occupancy flips, and our own setting changing (routed through publishDynamicConfig so the global syncs before we act). + onDesktopOccupancyChanged: controller.scheduleDynamic() + onDynamicEnabledChanged: controller.publishDynamicConfig() + onNamePrefixChanged: controller.publishDynamicConfig() + + // The desktop SET changing is also the signal that OUR add/remove landed → clear the lock and re-evaluate (multi-step trim converges over cycles). + Connections { + target: controller.virtualDesktopInfo + function onDesktopIdsChanged() { + controller.dynBusy = false; + controller.scheduleDynamic(); + } + } +} diff --git a/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/IndicatorMetrics.qml b/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/IndicatorMetrics.qml new file mode 100644 index 0000000..2e03f70 --- /dev/null +++ b/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/IndicatorMetrics.qml @@ -0,0 +1,57 @@ +/* + * Plasma Gnome Pager — IndicatorMetrics.qml + * + * SPDX-FileCopyrightText: 2026 Kenan Salar + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Non-visual dot-strip sizing engine (unit-tested by tst_indicatormetrics). EFFECTIVE sizes shrink to fit + * a crowded panel (floored at minDotSize); NATURAL/floor extents (the Layout hints) depend only on + * requests/grid, never on geometry — keep that split or you get a binding loop. + */ +pragma ComponentBehavior: Bound + +import QtQuick +import org.kde.kirigami as Kirigami + +import "logic.js" as Logic + +QtObject { + id: metrics + + // Inputs (bound by WorkspaceIndicator). + property int dotSizeRequest: Logic.DEFAULTS.dotSize // px; 0 = auto (HiDPI themed) + property int pillSizeRequest: Logic.DEFAULTS.pillSize // px pill thickness; 0 = auto (match dots) + property real spacingFactor: Logic.DEFAULTS.spacingFactor + property real pillWidthFactor: Logic.DEFAULTS.pillWidthFactor // pill length / pill thickness + property real availableMajor: 0 // live panel allocation along the line axis (0 before layout) + property real availableCross: 0 // live panel allocation across the stacked lines + property int perLine: 0 // desktops per line (KWin columns) + property int lineCount: 0 // stacked lines (KWin rows) + + // Natural / floor — geometry-INDEPENDENT, drive the Layout hints (no loop). + readonly property real naturalDotSize: dotSizeRequest > 0 ? dotSizeRequest : Kirigami.Units.iconSizes.small / 2 + readonly property real naturalPillSize: pillSizeRequest > 0 ? pillSizeRequest : naturalDotSize + readonly property real pillThicknessRatio: naturalPillSize / naturalDotSize // pill thickness in dot units; carries the dot⇄pill decoupling + readonly property real minDotSize: Math.min(naturalDotSize, Kirigami.Units.iconSizes.small / 4) // legibility floor, clamped ≤ natural + readonly property real naturalStripLength: Logic.lineExtent(perLine, naturalDotSize, naturalDotSize * spacingFactor, naturalPillSize * pillWidthFactor) + readonly property real floorStripLength: Logic.lineExtent(perLine, minDotSize, minDotSize * spacingFactor, minDotSize * pillThicknessRatio * pillWidthFactor) + readonly property real naturalCrossThickness: Logic.lineExtent(lineCount, naturalDotSize, naturalDotSize * spacingFactor, Math.max(naturalDotSize, naturalPillSize)) + readonly property real floorCrossThickness: Logic.lineExtent(lineCount, minDotSize, minDotSize * spacingFactor, minDotSize * Math.max(1, pillThicknessRatio)) + + // Effective — geometry-DEPENDENT rendered sizes (read by each WorkspaceDot). fitDotSize is the + // inverse of lineExtent; +Infinity on an unconstrained axis, so min() picks the binding axis. + readonly property real majorFitDotSize: Logic.fitDotSize(availableMajor, perLine, pillThicknessRatio * pillWidthFactor, spacingFactor) + readonly property real crossFitDotSize: Logic.fitDotSize(availableCross, lineCount, Math.max(1, pillThicknessRatio), spacingFactor) + readonly property real fitDotSize: Math.min(majorFitDotSize, crossFitDotSize) + readonly property real dotSize: Math.max(minDotSize, Math.min(naturalDotSize, fitDotSize)) // shrink-to-fit, capped natural, floored minDotSize + readonly property real pillSize: dotSize * pillThicknessRatio // scales in lockstep with the dot + readonly property real pillWidth: pillSize * pillWidthFactor // active capsule LENGTH (major axis) + readonly property real dotSpacing: dotSize * spacingFactor // uniform gap between every element + + // Conserved (capsule-bearing) line extents — the length/thickness of a line that HOLDS the capsule, which + // is the MAX over all lines and is independent of the morph progress. The indicator pins the strip to these + // so a cross-row morph can't resize+recenter it (else the dots "breathe"/drift). Effective (post-scale-to-fit). + // Assumes pillWidth >= dotSize, the same assumption naturalStripLength already makes. + readonly property real stripLength: Logic.lineExtent(perLine, dotSize, dotSpacing, pillWidth) + readonly property real crossThickness: Logic.lineExtent(lineCount, dotSize, dotSpacing, Math.max(dotSize, pillSize)) +} diff --git a/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/RenameDialog.qml b/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/RenameDialog.qml new file mode 100644 index 0000000..dc72c57 --- /dev/null +++ b/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/RenameDialog.qml @@ -0,0 +1,80 @@ +/* + * Plasma Gnome Pager — RenameDialog.qml + * + * SPDX-FileCopyrightText: 2026 Kenan Salar + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Rename prompt — a panel-native PlasmaCore.Dialog (top-level Window), declared directly. NOT + * Kirigami.PromptDialog, whose base parents to applicationWindow().overlay (undefined in a plasmoid → it + * would clip to the thin panel). View only: the parent owns visualParent/location and the DBus write. + */ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts + +import org.kde.plasma.core as PlasmaCore +import org.kde.plasma.components as PlasmaComponents3 +import org.kde.kirigami as Kirigami + +import "logic.js" as Logic + +PlasmaCore.Dialog { + id: renameDialog + + property string targetUuid: "" + signal accepted(string uuid, string name) + + visible: false + hideOnWindowDeactivate: true // click-away cancels + + // Prefill with the current name, select-all and focus for immediate typing. + function openFor(uuid, currentName) { + renameDialog.targetUuid = uuid; + renameField.text = currentName; + renameDialog.visible = true; + renameField.selectAll(); + renameField.forceActiveFocus(); + } + + // Sanitize then emit. An empty/whitespace name (sanitize → "") keeps the prompt open; the parent re-sanitizes, so the write stays guarded. + function commit() { + const clean = Logic.sanitizeDesktopName(renameField.text); + if (clean === "") { + return; + } + renameDialog.accepted(renameDialog.targetUuid, clean); + renameDialog.visible = false; + } + + mainItem: ColumnLayout { + spacing: Kirigami.Units.smallSpacing + + PlasmaComponents3.Label { + text: i18n("Rename desktop:") + } + PlasmaComponents3.TextField { + id: renameField + Layout.fillWidth: true + Layout.minimumWidth: Kirigami.Units.gridUnit * 12 + onAccepted: renameDialog.commit() + Keys.onEscapePressed: renameDialog.visible = false + } + RowLayout { + Layout.alignment: Qt.AlignRight + spacing: Kirigami.Units.smallSpacing + + PlasmaComponents3.Button { + text: i18n("Cancel") + icon.name: "dialog-cancel" + onClicked: renameDialog.visible = false + } + PlasmaComponents3.Button { + text: i18n("Rename") + icon.name: "edit-rename" + enabled: renameField.text.trim().length > 0 + onClicked: renameDialog.commit() + } + } + } +} diff --git a/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/ScreenCurrentDesktop.qml b/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/ScreenCurrentDesktop.qml new file mode 100644 index 0000000..763f01e --- /dev/null +++ b/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/ScreenCurrentDesktop.qml @@ -0,0 +1,60 @@ +/* + * Plasma Gnome Pager — ScreenCurrentDesktop.qml + * + * SPDX-FileCopyrightText: 2026 Kenan Salar + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Resolves the current desktop FOR ONE SCREEN (Plasma 6.7 per-output; unit-tested by tst_screencurrentdesktop). + * currentDesktopByScreenName is a METHOD + SIGNAL, not a notifying property, so currentDesktop is recomputed + * imperatively (a plain binding evaluates once); the prefer-per-screen decision is Logic.resolveCurrentDesktop. + */ +pragma ComponentBehavior: Bound + +import QtQuick + +import "logic.js" as Logic + +Item { + id: resolver + + // Inputs (injected by WorkspaceIndicator). + property var virtualDesktopInfo: null + property string screenName: "" + + // Output: the current-desktop UUID for screenName (the global current when there is no per-screen info). + property string currentDesktop: "" + + function updateCurrentDesktop() { + const vdi = resolver.virtualDesktopInfo; + const globalCurrent = vdi?.currentDesktop ?? ""; + let perScreen; // stays undefined unless we have a screen AND the 6.7 API + if (vdi && resolver.screenName && typeof vdi.currentDesktopByScreenName === "function") + perScreen = vdi.currentDesktopByScreenName(resolver.screenName); + resolver.currentDesktop = Logic.resolveCurrentDesktop(perScreen, globalCurrent); + } + + // Recompute on every external change ("bind, don't cache"). The always-present signals keep their own + // block (no ignoreUnknownSignals) so a typo here still warns. + Connections { + target: resolver.virtualDesktopInfo + function onCurrentDesktopChanged() { + resolver.updateCurrentDesktop(); + } + function onDesktopIdsChanged() { + resolver.updateCurrentDesktop(); + } + } + // The per-screen current signal is Plasma 6.7+ only; on 6.5/6.6 it is absent, so ignoreUnknownSignals + // keeps the Connections quiet — the resolver already degrades to the global current (typeof guard above). + Connections { + target: resolver.virtualDesktopInfo + ignoreUnknownSignals: true + function onCurrentDesktopForScreenChanged(screenName) { + if (screenName === resolver.screenName) + resolver.updateCurrentDesktop(); + } + } + onScreenNameChanged: resolver.updateCurrentDesktop() + onVirtualDesktopInfoChanged: resolver.updateCurrentDesktop() + Component.onCompleted: resolver.updateCurrentDesktop() +} diff --git a/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/WindowAggregator.qml b/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/WindowAggregator.qml new file mode 100644 index 0000000..40dd78f --- /dev/null +++ b/dot_local/share/plasma/plasmoids/com.github.kenansalar.plasma-gnome-pager/contents/ui/WindowAggregator.qml @@ -0,0 +1,209 @@ +/* + * Plasma Gnome Pager — WindowAggregator.qml + * + * SPDX-FileCopyrightText: 2026 Kenan Salar + * SPDX-License-Identifier: GPL-3.0-or-later + * + * The window aggregator — a non-visual Item. ONE unfiltered public TasksModel feeds the tooltip window + * list, GLOBAL dynamic-workspace occupancy, and the PER-SCREEN occupied-dot indicator from a single + * snapshot via pure JS. The desktop set is INJECTED as `virtualDesktopInfo` and this pager's output rect + * as `screenRect`; i18n + HTML formatting stays here (logic.js is i18n-free), so e2e-only. + */ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQml // Instantiator (materialise TasksModel rows) +import org.kde.taskmanager as TaskManager // TasksModel/ActivityInfo (read) + +import "logic.js" as Logic + +Item { + id: aggregator + + // The read source (a VirtualDesktopInfo), injected by main.qml. Null-safe throughout (transiently absent). + property var virtualDesktopInfo: null + + // The per-dot window-list tooltip wanted? Injected as (showTooltips && showWindowList). When false the + // aggregator is live only for occupancy, skipping tooltip work (drops title/minimise roles; empty tooltips). + property bool windowListActive: true + + // Per-screen occupied-dot indicator wanted? Injected as showOccupancy. When false the per-screen occupancy + // reduction (and the ScreenGeometry role that feeds it) is skipped entirely (screenOccupancy stays []). + property bool occupancyActive: false + + // Dynamic workspaces wanted? Injected as dynamicWorkspaces. When false the GLOBAL occupancy reduction (whose + // sole consumer is the controller) is skipped entirely (desktopOccupancy stays []). + property bool dynamicActive: false + + // This pager's output rect (global compositor space), injected by main.qml from the placed representation's + // Screen attached property. A zero rect → per-screen occupancy degrades to global (see computeDesktopOccupancyForScreen). + property rect screenRect: Qt.rect(0, 0, 0, 0) + + property var desktopTooltips: [] + property var desktopOccupancy: [] // GLOBAL (all monitors) — consumed by the dynamic-workspaces controller + property var screenOccupancy: [] // PER-SCREEN (this monitor only) — consumed by the occupied-dot indicator + + // One materialised TasksModel row, a NAMED inline component so objectAt(i) can be `as`-cast for typed role access (capitalised roles read off `model`). + component WindowRow: QtObject { + required property var model + required property string display // window title (Qt::DisplayRole) + readonly property var windowDesktops: model.VirtualDesktops + readonly property bool onAllDesktops: model.IsOnAllVirtualDesktops + readonly property bool minimized: model.IsMinimized + readonly property bool isWindow: model.IsWindow // false for launchers / startup tasks + readonly property bool skipPager: model.SkipPager // hidden from pagers — never occupies a desktop + readonly property rect windowScreen: model.ScreenGeometry // rect of the OUTPUT this window is on (per-screen occupancy) + } + + TaskManager.ActivityInfo { + id: activityInfo + } + TaskManager.TasksModel { + id: tasksModel + groupMode: TaskManager.TasksModel.GroupDisabled + filterByVirtualDesktop: false + filterByActivity: true + activity: activityInfo.currentActivity + } + + // The role ints rebuild() reads (PUBLIC enum); other roles are skipped (notably the IsActive focus churn, so + // window-focus changes never wake a rebuild). Built per ACTIVE feature, so an off feature's role can't trigger + // work nothing consumes: VirtualDesktops/IsOnAllVirtualDesktops/IsWindow gate desktop membership (every + // consumer); title + IsMinimized are tooltip-only; SkipPager is occupancy-only (excluded windows); and + // ScreenGeometry is PER-SCREEN-occupancy-only (so monitor-moves don't rebuild when that indicator is off). + readonly property var relevantRoles: { + var roles = [ + TaskManager.AbstractTasksModel.VirtualDesktops, + TaskManager.AbstractTasksModel.IsOnAllVirtualDesktops, + TaskManager.AbstractTasksModel.IsWindow + ]; + if (aggregator.windowListActive) + roles.push(Qt.DisplayRole, TaskManager.AbstractTasksModel.IsMinimized); + if (aggregator.occupancyActive || aggregator.dynamicActive) + roles.push(TaskManager.AbstractTasksModel.SkipPager); // occupancy excludes skip-pager windows + if (aggregator.occupancyActive) + roles.push(TaskManager.AbstractTasksModel.ScreenGeometry); // per-screen occupancy needs the window's output + return roles; + } + + // Materialise the rows so objectAt(i) can read role values by name (a C++ model has no model.get(i)). + // Row add/remove triggers a rebuild; role-value changes arrive via the model's dataChanged below. + Instantiator { + id: winInstantiator + model: tasksModel + delegate: WindowRow {} + onObjectAdded: aggregator.scheduleRebuild() + onObjectRemoved: aggregator.scheduleRebuild() + } + + // Rebuild on a relevant role change, a full reset, or a desktop-SET change. All funnel through the debounced scheduleRebuild. + Connections { + target: tasksModel + function onDataChanged(topLeft, bottomRight, roles) { + if (Logic.dataChangeAffectsRoles(roles, aggregator.relevantRoles)) + aggregator.scheduleRebuild(); + } + function onModelReset() { + aggregator.scheduleRebuild(); + } + } + Connections { + target: aggregator.virtualDesktopInfo + function onDesktopIdsChanged() { + aggregator.scheduleRebuild(); + } + } + + // Coalesce a burst of change signals into ONE rebuild per frame. No loop: rows read the model, rebuild() writes, the dots only read. + function scheduleRebuild() { + Qt.callLater(aggregator.rebuild); + } + + // Snapshot the materialised rows into a plain JS array, group per desktop (pure logic.js), then format each + // summary. `as WindowRow` gives typed access; normalise VirtualDesktops to plain strings (variant wrappers). + function rebuild() { + let windows = []; + for (let i = 0; i < winInstantiator.count; ++i) { + const o = winInstantiator.objectAt(i) as WindowRow; + if (!o) + continue; + windows.push({ + title: o.display || "", + minimized: o.minimized, + onAll: o.onAllDesktops, + isWindow: o.isWindow, + skipPager: o.skipPager, + desktops: (o.windowDesktops || []).map(x => String(x)), + // Plain {x,y,width,height} (a QRect role isn't a plain JS value); null when absent → counts on every screen. + screen: o.windowScreen ? { x: o.windowScreen.x, y: o.windowScreen.y, width: o.windowScreen.width, height: o.windowScreen.height } : null + }); + } + const ids = aggregator.virtualDesktopInfo?.desktopIds ?? []; + // Three reductions of the SAME snapshot. Compare-before-assign on EACH (arraysShallowEqual) avoids waking downstream on an unchanged array. + const tooltips = aggregator.windowListActive + ? Logic.groupWindowsByDesktop(windows, ids).map(aggregator.formatSubText) + : []; + if (!Logic.arraysShallowEqual(tooltips, aggregator.desktopTooltips)) + aggregator.desktopTooltips = tooltips; + + // GLOBAL occupancy (all monitors) for the dynamic-workspaces controller — the desktop SET is global. + // Skipped (→ []) unless dynamic workspaces is on (its sole consumer); compare-before-assign as below. + const occupancy = aggregator.dynamicActive ? Logic.computeDesktopOccupancy(windows, ids) : []; + if (!Logic.arraysShallowEqual(occupancy, aggregator.desktopOccupancy)) + aggregator.desktopOccupancy = occupancy; + + // PER-SCREEN occupancy (this monitor only) for the occupied-dot indicator. Skipped (→ []) unless that + // indicator is on. A zero screenRect degrades to global, so on a single monitor it equals the global array + // — reuse that array directly when dynamic already computed it, instead of recomputing the same thing. + let screenOcc = []; + if (aggregator.occupancyActive) { + screenOcc = (aggregator.dynamicActive && !Logic.isValidScreenRect(aggregator.screenRect)) + ? occupancy + : Logic.computeDesktopOccupancyForScreen(windows, ids, aggregator.screenRect); + } + if (!Logic.arraysShallowEqual(screenOcc, aggregator.screenOccupancy)) + aggregator.screenOccupancy = screenOcc; + } + + // One window title as escaped rich text, falling back to a localized "Untitled Window" (shared fallback). + function titleHtml(title) { + return Logic.sanitizeHtml(title.length ? title : i18nc("@item:intext window with no title", "Untitled Window")); + } + + // One desktop's window list as a rich-text