diff --git a/applets/taskmanager/package/contents/ui/ToolTipInstance.qml b/applets/taskmanager/package/contents/ui/ToolTipInstance.qml index 31e4faa10..c06d69a0a 100644 --- a/applets/taskmanager/package/contents/ui/ToolTipInstance.qml +++ b/applets/taskmanager/package/contents/ui/ToolTipInstance.qml @@ -1,477 +1,487 @@ /* * Copyright 2013 by Sebastian Kügler * Copyright 2014 by Martin Gräßlin * Copyright 2016 by Kai Uwe Broulik * Copyright 2017 by Roman Gilg * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. */ import QtQuick 2.6 import QtQuick.Layouts 1.1 import QtGraphicalEffects 1.0 import QtQml.Models 2.2 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons import org.kde.taskmanager 0.1 as TaskManager Column { property var submodelIndex: tasksModel.makeModelIndex(parentIndex, isGroup ? index : -1) property int flatIndex: isGroup && index != undefined ? index : 0 spacing: units.smallSpacing property string mprisSourceName: mpris2Source.sourceNameForLauncherUrl(toolTipDelegate.launcherUrl, isGroup ? AppPid : pidParent) property var playerData: mprisSourceName != "" ? mpris2Source.data[mprisSourceName] : 0 property bool hasPlayer: !!mprisSourceName && !!playerData property bool playing: hasPlayer && playerData.PlaybackStatus === "Playing" property bool canControl: hasPlayer && playerData.CanControl + property bool canPlay: hasPlayer && playerData.CanPlay + property bool canPause: hasPlayer && playerData.CanPause property bool canGoBack: hasPlayer && playerData.CanGoPrevious property bool canGoNext: hasPlayer && playerData.CanGoNext property bool canRaise: hasPlayer && playerData.CanRaise property var currentMetadata: hasPlayer ? playerData.Metadata : ({}) readonly property string track: { var xesamTitle = currentMetadata["xesam:title"] if (xesamTitle) { return xesamTitle; } // if no track title is given, print out the file name var xesamUrl = currentMetadata["xesam:url"] ? currentMetadata["xesam:url"].toString() : "" if (!xesamUrl) { return ""; } var lastSlashPos = xesamUrl.lastIndexOf('/') if (lastSlashPos < 0) { return ""; } var lastUrlPart = xesamUrl.substring(lastSlashPos + 1) return decodeURIComponent(lastUrlPart); } readonly property string artist: currentMetadata["xesam:artist"] || "" readonly property string albumArt: currentMetadata["mpris:artUrl"] || "" // // launcher icon + text labels + close button RowLayout { id: header Layout.minimumWidth: childrenRect.width Layout.maximumWidth: Layout.minimumWidth Layout.minimumHeight: childrenRect.height Layout.maximumHeight: Layout.minimumHeight anchors.horizontalCenter: parent.horizontalCenter // launcher icon PlasmaCore.IconItem { Layout.preferredWidth: units.iconSizes.medium Layout.preferredHeight: units.iconSizes.medium source: icon animated: false usesPlasmaTheme: false visible: !isWin } // all textlabels Column { PlasmaExtras.Heading { level: 3 width: isWin ? textWidth : undefined height: undefined maximumLineCount: 1 elide: Text.ElideRight text: appName opacity: flatIndex == 0 textFormat: Text.PlainText visible: text !== "" } // window title PlasmaExtras.Heading { level: 5 width: isWin ? textWidth : undefined height: undefined maximumLineCount: 1 elide: Text.ElideRight text: generateTitle() textFormat: Text.PlainText opacity: 0.75 } // subtext PlasmaExtras.Heading { level: 6 width: isWin ? textWidth : undefined height: undefined maximumLineCount: 1 elide: Text.ElideRight text: isWin ? generateSubText() : "" textFormat: Text.PlainText opacity: 0.6 visible: text !== "" } } // Count badge. Badge { Layout.alignment: Qt.AlignRight | Qt.AlignTop height: units.iconSizes.smallMedium visible: flatIndex === 0 && smartLauncherCountVisible number: smartLauncherCount } // close button MouseArea { Layout.alignment: Qt.AlignRight | Qt.AlignTop height: units.iconSizes.smallMedium width: height visible: isWin acceptedButtons: Qt.LeftButton hoverEnabled: true onClicked: { backend.cancelHighlightWindows(); tasksModel.requestClose(submodelIndex); } PlasmaCore.IconItem { anchors.fill: parent active: parent.containsMouse source: "window-close" animated: false } } } // thumbnail container Item { id: thumbnail width: header.width // similar to 0.5625 = 1 / (16:9) as most screens are // round necessary, otherwise shadow mask for players has gap! height: Math.round(0.5 * width) anchors.horizontalCenter: parent.horizontalCenter visible: isWin Item { id: thumbnailSourceItem anchors.fill: parent readonly property bool isMinimized: isGroup ? IsMinimized == true : isMinimizedParent // TODO: this causes XCB error message when being visible the first time property int winId: isWin && windows[flatIndex] != undefined ? windows[flatIndex] : 0 PlasmaCore.WindowThumbnail { anchors.fill: parent visible: !albumArtImage.visible && !thumbnailSourceItem.isMinimized winId: thumbnailSourceItem.winId ToolTipWindowMouseArea { anchors.fill: parent rootTask: parentTask modelIndex: submodelIndex winId: thumbnailSourceItem.winId } } Image { id: albumArtImage // also Image.Loading to prevent loading thumbnails just because the album art takes a split second to load readonly property bool available: status === Image.Ready || status === Image.Loading anchors.fill: parent sourceSize: Qt.size(parent.width, parent.height) asynchronous: true source: albumArt fillMode: Image.PreserveAspectCrop visible: available ToolTipWindowMouseArea { anchors.fill: parent rootTask: parentTask modelIndex: submodelIndex winId: thumbnailSourceItem.winId } } // when minimized, we don't have a preview, so show the icon PlasmaCore.IconItem { anchors.fill: parent source: icon animated: false usesPlasmaTheme: false visible: thumbnailSourceItem.isMinimized && !albumArtImage.visible ToolTipWindowMouseArea { anchors.fill: parent rootTask: parentTask modelIndex: submodelIndex winId: thumbnailSourceItem.winId } } } Loader { anchors.fill: thumbnail sourceComponent: hasPlayer ? playerControlsComp : undefined } Component { id: playerControlsComp Item { anchors.fill: parent // TODO: When could this really be the case? A not-launcher-task always has a window!? // if there's no window associated with this task, we might still be able to raise the player // MouseArea { // id: raisePlayerArea // anchors.fill: parent // visible: !isWin || !windows[0] && canRaise // onClicked: mpris2Source.raise(mprisSourceName) // } Item { id: playerControlsFrostedGlass anchors.fill: parent visible: false // OpacityMask would render it Rectangle { width: parent.width height: parent.height - playerControlsRow.height opacity: 0 } Rectangle { anchors.bottom: parent.bottom width: parent.width height: playerControlsRow.height color: theme.backgroundColor opacity: 0.8 } } OpacityMask { id: playerControlsOpacityMask anchors.fill: parent source: playerControlsFrostedGlass maskSource: thumbnailSourceItem } // prevent accidental click-through when a control is disabled MouseArea { anchors.fill: playerControlsRow } RowLayout { id: playerControlsRow anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom } width: parent.width spacing: 0 enabled: canControl ColumnLayout { Layout.fillWidth: true spacing: 0 PlasmaExtras.Heading { Layout.fillWidth: true level: 4 wrapMode: Text.NoWrap elide: Text.ElideRight text: track || "" } PlasmaExtras.Heading { Layout.fillWidth: true level: 5 wrapMode: Text.NoWrap elide: Text.ElideRight text: artist || "" } } MouseArea { height: units.iconSizes.smallMedium width: height enabled: canGoBack acceptedButtons: Qt.LeftButton hoverEnabled: true onClicked: mpris2Source.goPrevious(mprisSourceName) PlasmaCore.IconItem { anchors.fill: parent enabled: canGoBack active: parent.containsMouse source: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward" animated: false } } MouseArea { height: units.iconSizes.medium width: height + enabled: playing ? canPause : canPlay acceptedButtons: Qt.LeftButton hoverEnabled: true - onClicked: mpris2Source.playPause(mprisSourceName) + onClicked: { + if (!playing) { + mpris2Source.play(mprisSourceName); + } else { + mpris2Source.pause(mprisSourceName); + } + } PlasmaCore.IconItem { anchors.fill: parent + enabled: playing ? canPause : canPlay active: parent.containsMouse source: playing ? "media-playback-pause" : "media-playback-start" animated: false } } MouseArea { height: units.iconSizes.smallMedium width: height enabled: canGoNext acceptedButtons: Qt.LeftButton hoverEnabled: true onClicked: mpris2Source.goNext(mprisSourceName) PlasmaCore.IconItem { anchors.fill: parent enabled: canGoNext active: parent.containsMouse source: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward" animated: false } } } } } } function generateTitle() { if (!isWin) { return genericName != undefined ? genericName : ""; } var text; if (isGroup) { if (model.display == undefined) { return ""; } text = model.display.toString(); } else { text = displayParent; } // KWin appends increasing integers in between pointy brackets to otherwise equal window titles. // In this case save <#number> as counter and delete it at the end of text. var counter = text.match(/<\d+>\W*$/); text = text.replace(/\s*<\d+>\W*$/, ""); // Remove appName from the end of text. var appNameRegex = new RegExp(appName + "$", "i"); text = text.replace(appNameRegex, ""); text = text.replace(/\s*(?:-|—)*\s*$/, ""); // Add counter back at the end. if (counter != null) { if (text == "") { text = counter; } else { text = text + " " + counter; } } // In case the window title had only redundant informations (i.e. appName), text is now empty. // Add a hyphen to indicate that and avoid empty space. if (text == "") { text = "—"; } return text.toString(); } function generateSubText() { if (activitiesParent == undefined) { return ""; } var subTextEntries = []; var vd = isGroup ? VirtualDesktop : virtualDesktopParent; if (!plasmoid.configuration.showOnlyCurrentDesktop && virtualDesktopInfo.numberOfDesktops > 1 && (isGroup ? IsOnAllVirtualDesktops : isOnAllVirtualDesktopsParent) !== true && vd != -1 && vd != undefined && virtualDesktopInfo.desktopNames[vd - 1] != undefined) { subTextEntries.push(i18n("On %1", virtualDesktopInfo.desktopNames[vd - 1])); } var act = isGroup ? Activities : activitiesParent; if (act == undefined) { return subTextEntries.join("\n"); } if (act.length == 0 && activityInfo.numberOfRunningActivities > 1) { subTextEntries.push(i18nc("Which virtual desktop a window is currently on", "Available on all activities")); } else if (act.length > 0) { var activityNames = []; for (var i = 0; i < act.length; i++) { var activity = act[i]; var activityName = activityInfo.activityName(act[i]); if (activityName == "") { continue; } if (plasmoid.configuration.showOnlyCurrentActivity) { if (activity != activityInfo.currentActivity) { activityNames.push(activityName); } } else if (activity != activityInfo.currentActivity) { activityNames.push(activityName); } } if (plasmoid.configuration.showOnlyCurrentActivity) { if (activityNames.length > 0) { subTextEntries.push(i18nc("Activities a window is currently on (apart from the current one)", "Also available on %1", activityNames.join(", "))); } } else if (activityNames.length > 0) { subTextEntries.push(i18nc("Which activities a window is currently on", "Available on %1", activityNames.join(", "))); } } return subTextEntries.join("\n"); } } diff --git a/applets/taskmanager/package/contents/ui/main.qml b/applets/taskmanager/package/contents/ui/main.qml index 99cd57ef4..f114513b3 100644 --- a/applets/taskmanager/package/contents/ui/main.qml +++ b/applets/taskmanager/package/contents/ui/main.qml @@ -1,485 +1,491 @@ /*************************************************************************** * Copyright (C) 2012-2016 by Eike Hein * * * * 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 2 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ import QtQuick 2.0 import QtQuick.Layouts 1.1 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.taskmanager 0.1 as TaskManager import org.kde.plasma.private.taskmanager 0.1 as TaskManagerApplet import "../code/layout.js" as LayoutManager import "../code/tools.js" as TaskTools Item { id: tasks anchors.fill: parent property bool vertical: (plasmoid.formFactor == PlasmaCore.Types.Vertical) property bool iconsOnly: (plasmoid.pluginName == "org.kde.plasma.icontasks") property QtObject contextMenuComponent: Qt.createComponent("ContextMenu.qml"); Plasmoid.preferredRepresentation: Plasmoid.fullRepresentation Plasmoid.onUserConfiguringChanged: { if (plasmoid.userConfiguring) { groupDialog.visible = false; } } Layout.fillWidth: true Layout.fillHeight:true Layout.minimumWidth: tasks.vertical ? 0 : LayoutManager.preferredMinWidth() Layout.minimumHeight: !tasks.vertical ? 0 : LayoutManager.preferredMinHeight() //BEGIN TODO: this is not precise enough: launchers are smaller than full tasks Layout.preferredWidth: tasks.vertical ? units.gridUnit * 10 : ((LayoutManager.logicalTaskCount() * LayoutManager.preferredMaxWidth()) / LayoutManager.calculateStripes()); Layout.preferredHeight: tasks.vertical ? ((LayoutManager.logicalTaskCount() * LayoutManager.preferredMaxHeight()) / LayoutManager.calculateStripes()) : units.gridUnit * 2; //END TODO property Item dragSource: null signal requestLayout signal windowsHovered(variant winIds, bool hovered) signal presentWindows(variant winIds) onWidthChanged: { taskList.width = LayoutManager.layoutWidth(); if (plasmoid.configuration.forceStripes) { taskList.height = LayoutManager.layoutHeight(); } } onHeightChanged: { if (plasmoid.configuration.forceStripes) { taskList.width = LayoutManager.layoutWidth(); } taskList.height = LayoutManager.layoutHeight(); } onDragSourceChanged: { if (dragSource == null) { tasksModel.syncLaunchers(); } } TaskManager.TasksModel { id: tasksModel readonly property int logicalLauncherCount: { if (plasmoid.configuration.separateLaunchers) { return launcherCount; } var startupsWithLaunchers = 0; for (var i = 0; i < taskRepeater.count; ++i) { var item = taskRepeater.itemAt(i); if (item && item.m.IsStartup === true && item.m.HasLauncher === true) { ++startupsWithLaunchers; } } return launcherCount + startupsWithLaunchers; } virtualDesktop: virtualDesktopInfo.currentDesktop screenGeometry: plasmoid.screenGeometry activity: activityInfo.currentActivity filterByVirtualDesktop: plasmoid.configuration.showOnlyCurrentDesktop filterByScreen: plasmoid.configuration.showOnlyCurrentScreen filterByActivity: plasmoid.configuration.showOnlyCurrentActivity filterNotMinimized: plasmoid.configuration.showOnlyMinimized sortMode: iconsOnly ? TaskManager.TasksModel.SortManual : sortModeEnumValue(plasmoid.configuration.sortingStrategy) launchInPlace: iconsOnly separateLaunchers: { if (!iconsOnly && !plasmoid.configuration.separateLaunchers && plasmoid.configuration.sortingStrategy == 1) { return false; } return true; } groupMode: iconsOnly ? TaskManager.TasksModel.GroupApplications : groupModeEnumValue(plasmoid.configuration.groupingStrategy) groupInline: !plasmoid.configuration.groupPopups groupingWindowTasksThreshold: (plasmoid.configuration.onlyGroupWhenFull && !iconsOnly ? LayoutManager.optimumCapacity(width, height) + 1 : -1) onLauncherListChanged: { layoutTimer.restart(); plasmoid.configuration.launchers = launcherList; } onGroupingAppIdBlacklistChanged: { plasmoid.configuration.groupingAppIdBlacklist = groupingAppIdBlacklist; } onGroupingLauncherUrlBlacklistChanged: { plasmoid.configuration.groupingLauncherUrlBlacklist = groupingLauncherUrlBlacklist; } function sortModeEnumValue(index) { switch (index) { case 0: return TaskManager.TasksModel.SortDisabled; case 1: return TaskManager.TasksModel.SortManual; case 2: return TaskManager.TasksModel.SortAlpha; case 3: return TaskManager.TasksModel.SortVirtualDesktop; case 4: return TaskManager.TasksModel.SortActivity; default: return TaskManager.TasksModel.SortDisabled; } } function groupModeEnumValue(index) { switch (index) { case 0: return TaskManager.TasksModel.GroupDisabled; case 1: return TaskManager.TasksModel.GroupApplications; } } Component.onCompleted: { launcherList = plasmoid.configuration.launchers; groupingAppIdBlacklist = plasmoid.configuration.groupingAppIdBlacklist; groupingLauncherUrlBlacklist = plasmoid.configuration.groupingLauncherUrlBlacklist; // Only hook up view only after the above churn is done. taskRepeater.model = tasksModel; } } Connections { target: tasksModel onActiveTaskChanged: { if (!plasmoid.configuration.groupPopups) { return; } if (tasksModel.activeTask.parent.valid) { groupDialog.activeTask = tasksModel.activeTask; } } } TaskManager.VirtualDesktopInfo { id: virtualDesktopInfo } TaskManager.ActivityInfo { id: activityInfo } TaskManagerApplet.Backend { id: backend taskManagerItem: tasks toolTipItem: toolTipDelegate groupDialog: groupDialog highlightWindows: plasmoid.configuration.highlightWindows onAddLauncher: { tasks.addLauncher(url); } } PlasmaCore.DataSource { id: mpris2Source engine: "mpris2" connectedSources: sources function sourceNameForLauncherUrl(launcherUrl, pid) { if (!launcherUrl || launcherUrl == "") { return ""; } // MPRIS spec explicitly mentions that "DesktopEntry" is with .desktop extension trimmed // Moreover, remove URL parameters, like wmClass (part after the question mark) var desktopFileName = launcherUrl.toString().split('/').pop().split('?')[0].replace(".desktop", "") if (desktopFileName.indexOf("applications:") === 0) { desktopFileName = desktopFileName.substr(13) } for (var i = 0, length = connectedSources.length; i < length; ++i) { var source = connectedSources[i]; // we intend to connect directly, otherwise the multiplexer steals the connection away if (source === "@multiplex") { continue; } var sourceData = data[source]; if (!sourceData || sourceData.DesktopEntry !== desktopFileName) { continue; } if (pid === undefined || sourceData.InstancePid === pid) { return source; } var metadata = sourceData.Metadata; if (metadata) { var kdePid = metadata["kde:pid"]; if (kdePid && pid === kdePid) { return source; } } } return "" } function startOperation(source, op) { var service = serviceForSource(source) var operation = service.operationDescription(op) return service.startOperationCall(operation) } function goPrevious(source) { startOperation(source, "Previous"); } function goNext(source) { startOperation(source, "Next"); } + function play(source) { + startOperation(source, "Play"); + } + function pause(source) { + startOperation(source, "Pause"); + } function playPause(source) { startOperation(source, "PlayPause"); } function stop(source) { startOperation(source, "Stop"); } function raise(source) { startOperation(source, "Raise"); } function quit(source) { startOperation(source, "Quit"); } } Loader { id: pulseAudio source: "PulseAudio.qml" active: plasmoid.configuration.indicateAudioStreams } Timer { id: iconGeometryTimer interval: 500 repeat: false onTriggered: { TaskTools.publishIconGeometries(taskList.children); } } Binding { target: plasmoid property: "status" value: (tasksModel.anyTaskDemandsAttention ? PlasmaCore.Types.NeedsAttentionStatus : PlasmaCore.Types.PassiveStatus) } Connections { target: plasmoid onLocationChanged: { // This is on a timer because the panel may not have // settled into position yet when the location prop- // erty updates. iconGeometryTimer.start(); } } Connections { target: plasmoid.configuration onLaunchersChanged: tasksModel.launcherList = plasmoid.configuration.launchers onGroupingAppIdBlacklistChanged: tasksModel.groupingAppIdBlacklist = plasmoid.configuration.groupingAppIdBlacklist; onGroupingLauncherUrlBlacklistChanged: tasksModel.groupingLauncherUrlBlacklist = plasmoid.configuration.groupingLauncherUrlBlacklist; } TaskManagerApplet.DragHelper { id: dragHelper dragIconSize: units.iconSizes.medium } PlasmaCore.FrameSvgItem { id: taskFrame visible: false; imagePath: "widgets/tasks"; prefix: "normal" } PlasmaCore.Svg { id: taskSvg imagePath: "widgets/tasks" } MouseHandler { id: mouseHandler anchors.fill: parent target: taskList onUrlsDropped: { // If all dropped URLs point to application desktop files, we'll add a launcher for each of them. var createLaunchers = urls.every(function (item) { return backend.isApplication(item) }); if (createLaunchers) { urls.forEach(function (item) { addLauncher(item); }); return; } if (!hoveredItem) { return; } // DeclarativeMimeData urls is a QJsonArray but requestOpenUrls expects a proper QList. var urlsList = backend.jsonArrayToUrlList(urls); // Otherwise we'll just start a new instance of the application with the URLs as argument, // as you probably don't expect some of your files to open in the app and others to spawn launchers. tasksModel.requestOpenUrls(hoveredItem.modelIndex(), urlsList); } } ToolTipDelegate { id: toolTipDelegate visible: false } TaskList { id: taskList anchors { left: parent.left top: parent.top } onWidthChanged: LayoutManager.layout(taskRepeater) onHeightChanged: LayoutManager.layout(taskRepeater) flow: { if (tasks.vertical) { return plasmoid.configuration.forceStripes ? Flow.LeftToRight : Flow.TopToBottom } return plasmoid.configuration.forceStripes ? Flow.TopToBottom : Flow.LeftToRight } onAnimatingChanged: { if (!animating) { TaskTools.publishIconGeometries(children); } } function layout() { taskList.width = LayoutManager.layoutWidth(); taskList.height = LayoutManager.layoutHeight(); LayoutManager.layout(taskRepeater); } Timer { id: layoutTimer interval: 0 repeat: false onTriggered: taskList.layout() } Repeater { id: taskRepeater delegate: Task {} onItemAdded: taskList.layout() onItemRemoved: taskList.layout() } } GroupDialog { id: groupDialog } function hasLauncher(url) { return tasksModel.launcherPosition(url) != -1; } function addLauncher(url) { if (plasmoid.immutability !== PlasmaCore.Types.SystemImmutable) { tasksModel.requestAddLauncher(url); } } // This is called by plasmashell in response to a Meta+number shortcut. function activateTaskAtIndex(index) { if (typeof index !== "number") { return; } var task = taskRepeater.itemAt(index); if (task) { TaskTools.activateTask(task.modelIndex(), task.m, null, task); } } function resetDragSource() { dragSource = null; } function createContextMenu(rootTask, modelIndex, args) { var initialArgs = args || {} initialArgs.visualParent = rootTask; initialArgs.modelIndex = modelIndex; initialArgs.mpris2Source = mpris2Source; initialArgs.backend = backend; return tasks.contextMenuComponent.createObject(rootTask, initialArgs); } Component.onCompleted: { tasks.requestLayout.connect(layoutTimer.restart); tasks.requestLayout.connect(iconGeometryTimer.restart); tasks.windowsHovered.connect(backend.windowsHovered); tasks.presentWindows.connect(backend.presentWindows); dragHelper.dropped.connect(resetDragSource); } }