diff --git a/applets/taskmanager/package/contents/config/main.xml b/applets/taskmanager/package/contents/config/main.xml
--- a/applets/taskmanager/package/contents/config/main.xml
+++ b/applets/taskmanager/package/contents/config/main.xml
@@ -89,6 +89,10 @@
true
+
+
+ true
+
diff --git a/applets/taskmanager/package/contents/ui/ConfigGeneral.qml b/applets/taskmanager/package/contents/ui/ConfigGeneral.qml
--- a/applets/taskmanager/package/contents/ui/ConfigGeneral.qml
+++ b/applets/taskmanager/package/contents/ui/ConfigGeneral.qml
@@ -34,6 +34,7 @@
property alias cfg_wheelEnabled: wheelEnabled.checked
property alias cfg_highlightWindows: highlightWindows.checked
property alias cfg_smartLaunchersEnabled: smartLaunchers.checked
+ property alias cfg_indicateAudioStreams: indicateAudioStreams.checked
property alias cfg_maxStripes: maxStripes.value
property alias cfg_groupingStrategy: groupingStrategy.currentIndex
property alias cfg_middleClickAction: middleClickAction.currentIndex
@@ -107,6 +108,12 @@
text: i18n("Show progress and status information in task buttons")
}
+ CheckBox {
+ id: indicateAudioStreams
+ Layout.fillWidth: true
+ text: i18n("Denote applications that play audio")
+ }
+
RowLayout {
Label {
text: i18n("On middle-click:")
diff --git a/applets/taskmanager/package/contents/ui/ContextMenu.qml b/applets/taskmanager/package/contents/ui/ContextMenu.qml
--- a/applets/taskmanager/package/contents/ui/ContextMenu.qml
+++ b/applets/taskmanager/package/contents/ui/ContextMenu.qml
@@ -188,6 +188,26 @@
}
}
}
+
+ // We allow mute/unmute whenever an application has a stream, regardless of whether it
+ // is actually playing sound.
+ // This way you can unmute, e.g. a telephony app, even after the conversation has ended,
+ // so you still have it ringing later on.
+ if (menu.visualParent.hasAudioStream) {
+ var muteItem = menu.newMenuItem(menu);
+ muteItem.checkable = true;
+ muteItem.checked = Qt.binding(function() {
+ return menu.visualParent && menu.visualParent.muted;
+ });
+ muteItem.clicked.connect(function() {
+ menu.visualParent.toggleMuted();
+ });
+ muteItem.text = i18n("Mute");
+ muteItem.icon = "audio-volume-muted";
+ menu.addMenuItem(muteItem, virtualDesktopsMenuItem);
+
+ menu.addMenuItem(newSeparator(menu), virtualDesktopsMenuItem);
+ }
}
PlasmaComponents.MenuItem {
diff --git a/applets/taskmanager/package/contents/ui/Task.qml b/applets/taskmanager/package/contents/ui/Task.qml
--- a/applets/taskmanager/package/contents/ui/Task.qml
+++ b/applets/taskmanager/package/contents/ui/Task.qml
@@ -41,6 +41,7 @@
readonly property var m: model
+ readonly property int pid: model.AppPid
property int itemIndex: index
property bool inPopup: false
property bool isWindow: model.IsWindow === true
@@ -55,14 +56,26 @@
readonly property bool smartLauncherEnabled: plasmoid.configuration.smartLaunchersEnabled && !inPopup && model.IsStartup !== true
property QtObject smartLauncherItem: null
+ property Item audioStreamOverlay
+ property var audioStreams: []
+ readonly property bool hasAudioStream: plasmoid.configuration.indicateAudioStreams && audioStreams.length > 0
+ readonly property bool playingAudio: hasAudioStream && audioStreams.some(function (item) {
+ return !item.corked
+ })
+ readonly property bool muted: hasAudioStream && audioStreams.every(function (item) {
+ return item.muted
+ })
+
readonly property bool highlighted: (inPopup && activeFocus) || (!inPopup && containsMouse)
function hideToolTipTemporarily() {
toolTipArea.hideToolTip();
}
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MidButton
+ onPidChanged: updateAudioStreams()
+
onIsWindowChanged: {
if (isWindow) {
taskInitComponent.createObject(task);
@@ -167,14 +180,49 @@
}
}
+ onHasAudioStreamChanged: {
+ if (hasAudioStream) {
+ audioStreamIconLoader.active = true
+ }
+ }
+
Keys.onReturnPressed: TaskTools.activateTask(modelIndex(), model, event.modifiers, task)
Keys.onEnterPressed: Keys.onReturnPressed(event);
function modelIndex() {
return (inPopup ? tasksModel.makeModelIndex(groupDialog.visualParent.itemIndex, index)
: tasksModel.makeModelIndex(index));
}
+ function updateAudioStreams() {
+ if (!pid) {
+ task.audioStreams = [];
+ return;
+ }
+
+ var pa = pulseAudio.item;
+ if (!pa) {
+ task.audioStreams = [];
+ return;
+ }
+
+ task.audioStreams = pa.streamsForPid(pid);
+ }
+
+ function toggleMuted() {
+ if (muted) {
+ task.audioStreams.forEach(function (item) { item.unmute(); });
+ } else {
+ task.audioStreams.forEach(function (item) { item.mute(); });
+ }
+ }
+
+ Connections {
+ target: pulseAudio.item
+ ignoreUnknownSignals: true // Plasma-PA might not be available
+ onStreamsChanged: task.updateAudioStreams()
+ }
+
Component {
id: taskInitComponent
@@ -300,9 +348,7 @@
topMargin: adjustMargin(false, parent.height, taskFrame.margins.top)
}
- width: (label.visible ? height
- : parent.width - adjustMargin(true, parent.width, taskFrame.margins.left)
- - adjustMargin(true, parent.width, taskFrame.margins.right))
+ width: height
height: (parent.height - adjustMargin(false, parent.height, taskFrame.margins.top)
- adjustMargin(false, parent.height, taskFrame.margins.bottom))
@@ -351,7 +397,7 @@
// the text label margin, which derives from the icon width.
State {
name: "standalone"
- when: !label.visible
+ when: !label.visible && !audioStreamIconLoader.shown
AnchorChanges {
target: iconBox
@@ -362,6 +408,8 @@
PropertyChanges {
target: iconBox
anchors.leftMargin: 0
+ width: parent.width - adjustMargin(true, task.width, taskFrame.margins.left)
+ - adjustMargin(true, task.width, taskFrame.margins.right)
}
}
]
@@ -380,6 +428,23 @@
}
}
+ Loader {
+ id: audioStreamIconLoader
+
+ readonly property bool shown: item && item.visible
+
+ source: "AudioStream.qml"
+ width: Math.min(units.iconSizes.medium, iconBox.width)
+ height: Math.min(units.iconSizes.medium, iconBox.height)
+
+ anchors {
+ right: parent.right
+ rightMargin: iconBox.adjustMargin(true, parent.width, taskFrame.margins.right)
+ top: parent.top
+ topMargin: iconBox.adjustMargin(false, parent.height, taskFrame.margins.top)
+ }
+ }
+
PlasmaComponents.Label {
id: label
@@ -390,7 +455,7 @@
fill: parent
leftMargin: taskFrame.margins.left + iconBox.width + units.smallSpacing
topMargin: taskFrame.margins.top
- rightMargin: taskFrame.margins.right
+ rightMargin: taskFrame.margins.right + (audioStreamIconLoader.shown ? (audioStreamIconLoader.width + units.smallSpacing) : 0)
bottomMargin: taskFrame.margins.bottom
}
@@ -458,5 +523,7 @@
if (!inPopup && model.IsWindow !== true) {
taskInitComponent.createObject(task);
}
+
+ updateAudioStreams()
}
}
diff --git a/applets/taskmanager/package/contents/ui/ToolTipInstance.qml b/applets/taskmanager/package/contents/ui/ToolTipInstance.qml
--- a/applets/taskmanager/package/contents/ui/ToolTipInstance.qml
+++ b/applets/taskmanager/package/contents/ui/ToolTipInstance.qml
@@ -460,6 +460,14 @@
}
}
+ if (parentTask.playingAudio) {
+ if (parentTask.muted) {
+ subTextEntries.push(i18nc("The application this window belongs to is muted", "Currently Muted"));
+ } else {
+ subTextEntries.push(i18nc("The application this window belongs to is playing audio", "Currently Playing Audio"));
+ }
+ }
+
return subTextEntries.join("\n");
}
}
diff --git a/applets/taskmanager/package/contents/ui/main.qml b/applets/taskmanager/package/contents/ui/main.qml
--- a/applets/taskmanager/package/contents/ui/main.qml
+++ b/applets/taskmanager/package/contents/ui/main.qml
@@ -233,6 +233,12 @@
}
}
+ Loader {
+ id: pulseAudio
+ source: "PulseAudio.qml"
+ active: plasmoid.configuration.indicateAudioStreams
+ }
+
Timer {
id: iconGeometryTimer