Initial release: Virtual Desktop Switcher for KDE Plasma
Features: - Real-time desktop name display in panel - Hover to open popup with desktop grid - Real window geometry using native PagerModel - Window icons in previews - Proper screen aspect ratio - Desktop management (add, rename, delete) - Scroll wheel navigation - High performance native implementation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
commit
d867b3591f
|
|
@ -0,0 +1,4 @@
|
||||||
|
*.qmlc
|
||||||
|
*.jsc
|
||||||
|
.directory
|
||||||
|
*~
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2025 Andres Garcia
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
# Virtual Desktop Switcher
|
||||||
|
|
||||||
|
A KDE Plasma 5 widget that displays the current virtual desktop name and provides a visual overview of all desktops with real window positions.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Real-time desktop name** in panel - Shows active virtual desktop name
|
||||||
|
- **Hover to open** - Popup appears automatically when hovering (80ms delay)
|
||||||
|
- **Real window geometry** - Windows displayed in actual positions using native KDE pager API
|
||||||
|
- **Window icons** - Application icons shown inside window previews
|
||||||
|
- **Proper aspect ratio** - Desktop previews match your screen proportions
|
||||||
|
- **Grid layout** - Automatically arranges desktops in optimal columns/rows
|
||||||
|
- **Desktop management**:
|
||||||
|
- Click to switch desktop
|
||||||
|
- Right-click to rename or delete
|
||||||
|
- Add new desktops with "Add" button
|
||||||
|
- Scroll wheel to cycle through desktops
|
||||||
|
- **High performance** - Uses native `PagerModel` instead of shell commands
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
The widget shows a grid of desktop previews with:
|
||||||
|
- Window outlines in real positions
|
||||||
|
- Application icons inside windows
|
||||||
|
- Desktop number and name badge
|
||||||
|
- Active desktop highlighted
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Manual Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone https://github.com/andresgarcia0313/plasma-virtual-desktop-switcher.git
|
||||||
|
|
||||||
|
# Copy to Plasma plasmoids directory
|
||||||
|
cp -r plasma-virtual-desktop-switcher ~/.local/share/plasma/plasmoids/org.kde.virtualdesktopswitcher
|
||||||
|
|
||||||
|
# Restart Plasma
|
||||||
|
kquitapp5 plasmashell && kstart5 plasmashell
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add to Panel
|
||||||
|
|
||||||
|
1. Right-click on your panel → **Add Widgets**
|
||||||
|
2. Search for **"Virtual Desktop Switcher"**
|
||||||
|
3. Drag to your panel
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Right-click on the widget → **Configure Virtual Desktop Switcher**
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| Show window previews | Display window outlines inside desktop previews |
|
||||||
|
| Show window icons | Display application icons on window rectangles |
|
||||||
|
| Preview size | Adjust the size of preview area (40-150px) |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
| Action | Result |
|
||||||
|
|--------|--------|
|
||||||
|
| **Hover** | Opens desktop overview popup |
|
||||||
|
| **Click** on desktop | Switches to that desktop |
|
||||||
|
| **Right-click** on desktop | Context menu (Rename, Delete) |
|
||||||
|
| **Scroll wheel** | Cycles through desktops |
|
||||||
|
| **Add button** | Creates new virtual desktop |
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- KDE Plasma 5.27 or later
|
||||||
|
- KWin window manager
|
||||||
|
- Qt 5.15+
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
|
||||||
|
The widget uses the native KDE pager plugin (`org.kde.plasma.private.pager`) which provides:
|
||||||
|
|
||||||
|
- `PagerModel` - Virtual desktop model with real-time updates
|
||||||
|
- `TasksModel` - Window information including geometry
|
||||||
|
- `pagerItemSize` - Screen dimensions for aspect ratio
|
||||||
|
|
||||||
|
### Key Files
|
||||||
|
|
||||||
|
```
|
||||||
|
org.kde.virtualdesktopswitcher/
|
||||||
|
├── metadata.desktop # Plugin metadata
|
||||||
|
├── contents/
|
||||||
|
│ ├── config/
|
||||||
|
│ │ ├── main.xml # Configuration schema
|
||||||
|
│ │ └── config.qml # Config page registration
|
||||||
|
│ └── ui/
|
||||||
|
│ ├── main.qml # Main widget code
|
||||||
|
│ └── configGeneral.qml # Settings UI
|
||||||
|
```
|
||||||
|
|
||||||
|
### QML Imports Used
|
||||||
|
|
||||||
|
```qml
|
||||||
|
import org.kde.plasma.plasmoid 2.0
|
||||||
|
import org.kde.plasma.core 2.0 as PlasmaCore
|
||||||
|
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||||
|
import org.kde.plasma.private.pager 2.0 // Native pager API
|
||||||
|
```
|
||||||
|
|
||||||
|
### Window Geometry Scaling
|
||||||
|
|
||||||
|
Windows are scaled from screen coordinates to preview size:
|
||||||
|
|
||||||
|
```qml
|
||||||
|
readonly property real scaleX: deskW / pagerModel.pagerItemSize.width
|
||||||
|
readonly property real scaleY: deskH / pagerModel.pagerItemSize.height
|
||||||
|
|
||||||
|
x: Math.round(model.Geometry.x * scaleX)
|
||||||
|
y: Math.round(model.Geometry.y * scaleY)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Testing Changes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Edit files in
|
||||||
|
~/.local/share/plasma/plasmoids/org.kde.virtualdesktopswitcher/
|
||||||
|
|
||||||
|
# Restart Plasma to see changes
|
||||||
|
kquitapp5 plasmashell && kstart5 plasmashell
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debugging
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View Plasma logs
|
||||||
|
journalctl -f | grep -i plasma
|
||||||
|
|
||||||
|
# Check QML errors
|
||||||
|
QT_LOGGING_RULES="qml.debug=true" plasmashell
|
||||||
|
```
|
||||||
|
|
||||||
|
### KWin D-Bus API
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List virtual desktops
|
||||||
|
gdbus call --session --dest org.kde.KWin \
|
||||||
|
--object-path /VirtualDesktopManager \
|
||||||
|
--method org.freedesktop.DBus.Properties.Get \
|
||||||
|
org.kde.KWin.VirtualDesktopManager desktops
|
||||||
|
|
||||||
|
# Get current desktop
|
||||||
|
qdbus org.kde.KWin /VirtualDesktopManager current
|
||||||
|
|
||||||
|
# Rename desktop
|
||||||
|
qdbus org.kde.KWin /VirtualDesktopManager setDesktopName '<id>' 'New Name'
|
||||||
|
|
||||||
|
# Create desktop
|
||||||
|
qdbus org.kde.KWin /VirtualDesktopManager createDesktop <position> 'Name'
|
||||||
|
|
||||||
|
# Remove desktop
|
||||||
|
qdbus org.kde.KWin /VirtualDesktopManager removeDesktop '<id>'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please:
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch
|
||||||
|
3. Make your changes
|
||||||
|
4. Test thoroughly
|
||||||
|
5. Submit a pull request
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the GPL-3.0 License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- Uses KDE Plasma's native pager plugin for window geometry
|
||||||
|
- Inspired by the official KDE Pager widget
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
**Andres Garcia** - [GitHub](https://github.com/andresgarcia0313)
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import org.kde.plasma.configuration 2.0
|
||||||
|
|
||||||
|
ConfigModel {
|
||||||
|
ConfigCategory {
|
||||||
|
name: i18n("General")
|
||||||
|
icon: "preferences-desktop-virtual"
|
||||||
|
source: "configGeneral.qml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
|
||||||
|
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
|
||||||
|
<kcfgfile name=""/>
|
||||||
|
<group name="General">
|
||||||
|
<entry name="showWindowPreviews" type="Bool">
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="showWindowIcons" type="Bool">
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="previewSize" type="Int">
|
||||||
|
<default>80</default>
|
||||||
|
<min>40</min>
|
||||||
|
<max>150</max>
|
||||||
|
</entry>
|
||||||
|
</group>
|
||||||
|
</kcfg>
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import org.kde.kirigami 2.19 as Kirigami
|
||||||
|
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
id: page
|
||||||
|
|
||||||
|
property alias cfg_showWindowPreviews: showWindowPreviews.checked
|
||||||
|
property alias cfg_showWindowIcons: showWindowIcons.checked
|
||||||
|
property alias cfg_previewSize: previewSize.value
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: showWindowPreviews
|
||||||
|
Kirigami.FormData.label: i18n("Window Previews:")
|
||||||
|
text: i18n("Show window outlines in popup")
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: showWindowIcons
|
||||||
|
Kirigami.FormData.label: i18n("Window Icons:")
|
||||||
|
text: i18n("Show application icons on windows")
|
||||||
|
enabled: showWindowPreviews.checked
|
||||||
|
}
|
||||||
|
|
||||||
|
SpinBox {
|
||||||
|
id: previewSize
|
||||||
|
Kirigami.FormData.label: i18n("Preview Size:")
|
||||||
|
from: 40
|
||||||
|
to: 150
|
||||||
|
stepSize: 10
|
||||||
|
enabled: showWindowPreviews.checked
|
||||||
|
|
||||||
|
textFromValue: function(value) {
|
||||||
|
return value + " px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,310 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import org.kde.plasma.plasmoid 2.0
|
||||||
|
import org.kde.plasma.core 2.0 as PlasmaCore
|
||||||
|
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||||
|
import org.kde.plasma.private.pager 2.0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
Plasmoid.preferredRepresentation: Plasmoid.compactRepresentation
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// CONFIGURATION
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
readonly property bool showPreviews: plasmoid.configuration.showWindowPreviews
|
||||||
|
readonly property bool showIcons: plasmoid.configuration.showWindowIcons
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// PAGER MODEL (native KDE plugin with real window geometry)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
PagerModel {
|
||||||
|
id: pagerModel
|
||||||
|
enabled: root.visible
|
||||||
|
showDesktop: false
|
||||||
|
pagerType: PagerModel.VirtualDesktops
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// HELPER FUNCTIONS
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
function currentDesktopName() {
|
||||||
|
if (pagerModel.count > 0 && pagerModel.currentPage >= 0) {
|
||||||
|
var idx = pagerModel.index(pagerModel.currentPage, 0)
|
||||||
|
var name = pagerModel.data(idx, Qt.DisplayRole)
|
||||||
|
return name || ("Desktop " + (pagerModel.currentPage + 1))
|
||||||
|
}
|
||||||
|
return "Desktop"
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDesktop() { pagerModel.addDesktop() }
|
||||||
|
function removeDesktop() { pagerModel.removeDesktop() }
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// HOVER TIMERS
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
property bool hoverCompact: false
|
||||||
|
property bool hoverPopup: false
|
||||||
|
Timer { id: openTimer; interval: 80; onTriggered: plasmoid.expanded = true }
|
||||||
|
Timer { id: closeTimer; interval: 300; onTriggered: if (!hoverCompact && !hoverPopup) plasmoid.expanded = false }
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// COMPACT REPRESENTATION
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
Plasmoid.compactRepresentation: MouseArea {
|
||||||
|
id: compactArea
|
||||||
|
Layout.minimumWidth: compactLabel.implicitWidth + 16
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: parent.containsMouse ? PlasmaCore.Theme.highlightColor : "transparent"
|
||||||
|
opacity: 0.2; radius: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
id: compactLabel
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: currentDesktopName()
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
onEntered: { hoverCompact = true; closeTimer.stop(); openTimer.start() }
|
||||||
|
onExited: { hoverCompact = false; openTimer.stop(); if (!hoverPopup) closeTimer.start() }
|
||||||
|
onClicked: { openTimer.stop(); closeTimer.stop(); plasmoid.expanded = !plasmoid.expanded }
|
||||||
|
onWheel: {
|
||||||
|
var next = wheel.angleDelta.y > 0
|
||||||
|
? (pagerModel.currentPage - 1 + pagerModel.count) % pagerModel.count
|
||||||
|
: (pagerModel.currentPage + 1) % pagerModel.count
|
||||||
|
pagerModel.changePage(next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// FULL REPRESENTATION
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
Plasmoid.fullRepresentation: Item {
|
||||||
|
id: popup
|
||||||
|
|
||||||
|
readonly property int cols: Math.max(1, Math.ceil(Math.sqrt(pagerModel.count)))
|
||||||
|
readonly property int rows: Math.max(1, Math.ceil(pagerModel.count / cols))
|
||||||
|
readonly property real deskW: 130
|
||||||
|
readonly property real deskH: pagerModel.pagerItemSize.height > 0
|
||||||
|
? deskW * pagerModel.pagerItemSize.height / pagerModel.pagerItemSize.width
|
||||||
|
: deskW * 9 / 16
|
||||||
|
readonly property real scaleX: deskW / Math.max(1, pagerModel.pagerItemSize.width)
|
||||||
|
readonly property real scaleY: deskH / Math.max(1, pagerModel.pagerItemSize.height)
|
||||||
|
|
||||||
|
Layout.preferredWidth: cols * (deskW + 8) + 32
|
||||||
|
Layout.preferredHeight: rows * (deskH + 8) + 56
|
||||||
|
Layout.minimumWidth: 200
|
||||||
|
Layout.minimumHeight: 120
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
onEntered: { hoverPopup = true; closeTimer.stop() }
|
||||||
|
onExited: { hoverPopup = false; if (!hoverCompact) closeTimer.start() }
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 10
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
// DESKTOP GRID
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
Grid {
|
||||||
|
id: desktopGrid
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
columns: popup.cols
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: repeater
|
||||||
|
model: pagerModel
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: desktop
|
||||||
|
width: popup.deskW
|
||||||
|
height: popup.deskH
|
||||||
|
|
||||||
|
readonly property string desktopName: model.display || ("Desktop " + (index + 1))
|
||||||
|
readonly property bool isActive: index === pagerModel.currentPage
|
||||||
|
|
||||||
|
color: isActive ? Qt.darker(PlasmaCore.Theme.highlightColor, 1.3) : PlasmaCore.Theme.backgroundColor
|
||||||
|
border.width: isActive ? 2 : 1
|
||||||
|
border.color: isActive ? PlasmaCore.Theme.highlightColor : PlasmaCore.Theme.disabledTextColor
|
||||||
|
radius: 4
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
onEntered: { hoverPopup = true; closeTimer.stop(); parent.opacity = 1 }
|
||||||
|
onExited: parent.opacity = 0.92
|
||||||
|
onClicked: mouse.button === Qt.RightButton ? ctxMenu.popup() : (pagerModel.changePage(index), plasmoid.expanded = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
opacity: 0.92
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────
|
||||||
|
// WINDOWS with real geometry
|
||||||
|
// ─────────────────────────────────────────────────
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 2
|
||||||
|
clip: true
|
||||||
|
visible: showPreviews
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: TasksModel
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
readonly property rect geo: model.Geometry
|
||||||
|
readonly property bool minimized: model.IsMinimized === true
|
||||||
|
|
||||||
|
x: Math.round(geo.x * popup.scaleX)
|
||||||
|
y: Math.round(geo.y * popup.scaleY)
|
||||||
|
width: Math.max(8, Math.round(geo.width * popup.scaleX))
|
||||||
|
height: Math.max(6, Math.round(geo.height * popup.scaleY))
|
||||||
|
visible: !minimized
|
||||||
|
|
||||||
|
color: model.IsActive ? Qt.rgba(1,1,1,0.4) : Qt.rgba(1,1,1,0.2)
|
||||||
|
border.width: 1
|
||||||
|
border.color: model.IsActive ? PlasmaCore.Theme.highlightColor : PlasmaCore.Theme.textColor
|
||||||
|
radius: 2
|
||||||
|
|
||||||
|
PlasmaCore.IconItem {
|
||||||
|
visible: showIcons && parent.width > 16 && parent.height > 12
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: Math.min(parent.width - 4, parent.height - 4, 20)
|
||||||
|
height: width
|
||||||
|
source: model.decoration || "application-x-executable"
|
||||||
|
usesPlasmaTheme: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────
|
||||||
|
// DESKTOP LABEL (centered)
|
||||||
|
// ─────────────────────────────────────────────────
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: !showPreviews
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: index + 1
|
||||||
|
font.bold: true; font.pixelSize: 18
|
||||||
|
color: desktop.isActive ? PlasmaCore.Theme.highlightedTextColor : PlasmaCore.Theme.textColor
|
||||||
|
}
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: desktop.desktopName
|
||||||
|
font.pixelSize: 10
|
||||||
|
color: desktop.isActive ? PlasmaCore.Theme.highlightedTextColor : PlasmaCore.Theme.textColor
|
||||||
|
width: popup.deskW - 10
|
||||||
|
elide: Text.ElideRight
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Badge when previews enabled
|
||||||
|
Rectangle {
|
||||||
|
visible: showPreviews
|
||||||
|
anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter; bottomMargin: 3 }
|
||||||
|
width: badgeLbl.implicitWidth + 12; height: badgeLbl.implicitHeight + 4
|
||||||
|
color: Qt.rgba(0,0,0,0.7); radius: 3
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
id: badgeLbl; anchors.centerIn: parent
|
||||||
|
text: (index + 1) + " " + desktop.desktopName
|
||||||
|
font.pixelSize: 11; font.bold: true; color: "white"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context menu
|
||||||
|
Menu {
|
||||||
|
id: ctxMenu
|
||||||
|
MenuItem {
|
||||||
|
text: "Rename..."
|
||||||
|
icon.name: "edit-rename"
|
||||||
|
onTriggered: { renameDlg.idx = index; renameDlg.open(); renameField.text = desktop.desktopName; renameField.selectAll() }
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Delete"
|
||||||
|
icon.name: "edit-delete"
|
||||||
|
enabled: pagerModel.count > 1
|
||||||
|
onTriggered: pagerModel.removeDesktop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
// BOTTOM BAR
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
MouseArea {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 36
|
||||||
|
hoverEnabled: true
|
||||||
|
onEntered: { hoverPopup = true; closeTimer.stop() }
|
||||||
|
onExited: { hoverPopup = false; if (!hoverCompact) closeTimer.start() }
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 8
|
||||||
|
PlasmaComponents.Button {
|
||||||
|
icon.name: "list-add"; text: "Add"
|
||||||
|
onClicked: pagerModel.addDesktop()
|
||||||
|
}
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
text: pagerModel.count + " desktops"
|
||||||
|
opacity: 0.6; font.pixelSize: 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// RENAME DIALOG
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
Dialog {
|
||||||
|
id: renameDlg
|
||||||
|
property int idx: 0
|
||||||
|
title: "Rename Desktop"
|
||||||
|
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||||
|
anchors.centerIn: parent
|
||||||
|
contentItem: PlasmaComponents.TextField {
|
||||||
|
id: renameField
|
||||||
|
Layout.preferredWidth: 180
|
||||||
|
onAccepted: renameDlg.accept()
|
||||||
|
}
|
||||||
|
onAccepted: {
|
||||||
|
if (renameField.text.trim()) {
|
||||||
|
// Use KWin DBus to rename
|
||||||
|
var desktopItem = repeater.itemAt(idx)
|
||||||
|
if (desktopItem) {
|
||||||
|
execSource.connectSource("qdbus org.kde.KWin /VirtualDesktopManager setDesktopName '" +
|
||||||
|
pagerModel.data(pagerModel.index(idx, 0), 0x0100 + 1) + "' '" +
|
||||||
|
renameField.text.trim().replace(/'/g, "'\\''") + "'")
|
||||||
|
}
|
||||||
|
pagerModel.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaCore.DataSource {
|
||||||
|
id: execSource
|
||||||
|
engine: "executable"
|
||||||
|
onNewData: disconnectSource(sourceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Virtual Desktop Switcher
|
||||||
|
Comment=Shows current virtual desktop and allows switching with window previews
|
||||||
|
Icon=preferences-desktop-virtual
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=Plasma/Applet
|
||||||
|
X-Plasma-API=declarativeappletscript
|
||||||
|
X-Plasma-MainScript=ui/main.qml
|
||||||
|
X-Plasma-ConfigPlugins=org.kde.virtualdesktopswitcher
|
||||||
|
X-KDE-PluginInfo-Name=org.kde.virtualdesktopswitcher
|
||||||
|
X-KDE-PluginInfo-Version=1.1
|
||||||
|
X-KDE-PluginInfo-Author=Andres Garcia
|
||||||
|
X-KDE-PluginInfo-License=GPL-3.0
|
||||||
|
X-KDE-PluginInfo-Category=Windows and Tasks
|
||||||
|
X-KDE-FormFactors=desktop,panel
|
||||||
Loading…
Reference in New Issue