diff --git a/applet/contents/ui/ListItemBase.qml b/applet/contents/ui/ListItemBase.qml index 4c2c23f..63da424 100644 --- a/applet/contents/ui/ListItemBase.qml +++ b/applet/contents/ui/ListItemBase.qml @@ -1,332 +1,332 @@ /* Copyright 2014-2015 Harald Sitter 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 . */ import QtQuick 2.4 import QtQuick.Controls 1.0 import QtQuick.Layouts 1.0 import org.kde.kquickcontrolsaddons 2.0 import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.draganddrop 2.0 as DragAndDrop import org.kde.plasma.private.volume 0.1 import "../code/icon.js" as Icon PlasmaComponents.ListItem { id: item property alias label: textLabel.text property alias draggable: dragArea.enabled property alias icon: clientIcon.source property string type checked: dropArea.containsDrag opacity: (draggedStream && draggedStream.deviceIndex == Index) ? 0.3 : 1.0 ListView.delayRemove: dragArea.dragActive Item { width: parent.width height: rowLayout.height RowLayout { id: rowLayout width: parent.width spacing: units.smallSpacing PlasmaCore.IconItem { id: clientIcon Layout.alignment: Qt.AlignHCenter Layout.preferredHeight: column.height * 0.75 Layout.preferredWidth: Layout.preferredHeight source: "unknown" onSourceChanged: { if (!valid && source != "unknown") { source = "unknown"; } } DragAndDrop.DragArea { id: dragArea anchors.fill: parent delegate: parent mimeData { source: item } onDragStarted: { draggedStream = PulseObject; - main.beginMoveStream(type == "sink-input" ? "sink" : "source"); + beginMoveStream(type == "sink-input" ? "sink" : "source"); } onDrop: { draggedStream = null; - main.endMoveStream(); + endMoveStream(); } MouseArea { anchors.fill: parent cursorShape: dragArea.enabled ? (pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor) : undefined } } } ColumnLayout { id: column spacing: 1 RowLayout { Layout.fillWidth: true PlasmaExtras.Heading { id: textLabel Layout.fillWidth: true height: undefined level: 5 opacity: 0.6 wrapMode: Text.NoWrap elide: Text.ElideRight } SmallToolButton { id: contextMenuButton icon: "application-menu" checkable: true onClicked: contextMenu.show() tooltip: i18n("Show additional options for %1", textLabel.text) } } RowLayout { SmallToolButton { readonly property bool isPlayback: type.substring(0, 4) == "sink" icon: Icon.name(Volume, Muted, isPlayback ? "audio-volume" : "microphone-sensitivity") onClicked: Muted = !Muted tooltip: i18n("Mute %1", textLabel.text) } PlasmaComponents.Slider { id: slider // Helper properties to allow async slider updates. // While we are sliding we must not react to value updates // as otherwise we can easily end up in a loop where value // changes trigger volume changes trigger value changes. property int volume: Volume property bool ignoreValueChange: true property bool forceRaiseMaxVolume: false readonly property bool raiseMaxVolume: forceRaiseMaxVolume || volume >= PulseAudio.NormalVolume * 1.01 Layout.fillWidth: true minimumValue: PulseAudio.MinimalVolume maximumValue: raiseMaxVolume ? PulseAudio.MaximalVolume : maxVolumeValue stepSize: maximumValue / (maximumValue / PulseAudio.NormalVolume * 100.0) visible: HasVolume enabled: VolumeWritable opacity: Muted ? 0.5 : 1 Accessible.name: i18nc("Accessibility data on volume slider", "Adjust volume for %1", textLabel.text) Component.onCompleted: { ignoreValueChange = false; } onVolumeChanged: { var oldIgnoreValueChange = ignoreValueChange; ignoreValueChange = true; value = Volume; ignoreValueChange = oldIgnoreValueChange; } onValueChanged: { if (!ignoreValueChange) { Volume = value; Muted = value == 0; if (type == "sink") { playFeedback(Index); } if (!pressed) { updateTimer.restart(); } } } onPressedChanged: { if (!pressed) { // Make sure to sync the volume once the button was // released. // Otherwise it might be that the slider is at v10 // whereas PA rejected the volume change and is // still at v15 (e.g.). updateTimer.restart(); } } Timer { id: updateTimer interval: 200 onTriggered: slider.value = Volume } } PlasmaComponents.Label { id: percentText readonly property real value: PulseObject.volume > slider.maximumValue ? PulseObject.volume : slider.value Layout.alignment: Qt.AlignHCenter Layout.minimumWidth: percentMetrics.advanceWidth horizontalAlignment: Qt.AlignRight text: i18nc("volume percentage", "%1%", Math.round(value / PulseAudio.NormalVolume * 100.0)) } TextMetrics { id: percentMetrics font: percentText.font text: i18nc("only used for sizing, should be widest possible string", "100%") } } } } DragAndDrop.DropArea { id: dropArea anchors.fill: parent enabled: draggedStream onDragEnter: { if (draggedStream.deviceIndex == Index) { event.ignore(); } } onDrop: { draggedStream.deviceIndex = Index; } } MouseArea { anchors.fill: parent acceptedButtons: Qt.MiddleButton onClicked: Muted = !Muted } } PlasmaComponents.ContextMenu { id: contextMenu visualParent: contextMenuButton placement: PlasmaCore.Types.BottomPosedLeftAlignedPopup onStatusChanged: { if (status == PlasmaComponents.DialogStatus.Closed) { contextMenuButton.checked = false; } } function newSeperator() { return Qt.createQmlObject("import org.kde.plasma.components 2.0 as PlasmaComponents; PlasmaComponents.MenuItem { separator: true }", contextMenu); } function newMenuItem() { return Qt.createQmlObject("import org.kde.plasma.components 2.0 as PlasmaComponents; PlasmaComponents.MenuItem {}", contextMenu); } function loadDynamicActions() { contextMenu.clearMenuItems(); // Mute var menuItem = newMenuItem(); menuItem.text = i18nc("Checkable switch for (un-)muting sound output.", "Mute"); menuItem.checkable = true; menuItem.checked = Muted; menuItem.clicked.connect(function() { Muted = !Muted }); contextMenu.addMenuItem(menuItem); // Default if (typeof PulseObject.default === "boolean") { var menuItem = newMenuItem(); menuItem.text = i18nc("Checkable switch to change the current default output.", "Default"); menuItem.checkable = true; menuItem.checked = PulseObject.default menuItem.clicked.connect(function() { PulseObject.default = true }); contextMenu.addMenuItem(menuItem); } // Raise max volume menuItem = newMenuItem(); menuItem.text = i18n("Raise maximum volume"); menuItem.checkable = true; menuItem.checked = slider.forceRaiseMaxVolume; menuItem.clicked.connect(function() { slider.forceRaiseMaxVolume = !slider.forceRaiseMaxVolume; if (!slider.forceRaiseMaxVolume && Volume > PulseAudio.NormalVolume) { Volume = PulseAudio.NormalVolume; } }); contextMenu.addMenuItem(menuItem); // Ports if (PulseObject.ports && PulseObject.ports.length > 0) { contextMenu.addMenuItem(newSeperator()); var isMultiplePorts = (1 < PulseObject.ports.length); var menuItem = newMenuItem(); menuItem.text = i18nc("Heading for a list of ports of a device (for example built-in laptop speakers or a plug for headphones)", "Ports"); menuItem.section = true; contextMenu.addMenuItem(menuItem); for (var i = 0; i < PulseObject.ports.length; i++) { var port = PulseObject.ports[i]; var menuItem = newMenuItem(); menuItem.text = port.description; if (port.availability == Port.Unavailable) { if (port.name == "analog-output-speaker" || port.name == "analog-input-microphone-internal") { menuItem.text += i18nc("Port is unavailable", " (unavailable)"); } else { menuItem.text += i18nc("Port is unplugged", " (unplugged)"); } } menuItem.enabled = isMultiplePorts; menuItem.checkable = true; menuItem.checked = i === PulseObject.activePortIndex; var setActivePort = function(portIndex) { return function() { PulseObject.activePortIndex = portIndex; }; }; menuItem.clicked.connect(setActivePort(i)); contextMenu.addMenuItem(menuItem); } } } function show() { loadDynamicActions(); openRelative(); } } } diff --git a/applet/contents/ui/main.qml b/applet/contents/ui/main.qml index a9c3207..ef218ad 100644 --- a/applet/contents/ui/main.qml +++ b/applet/contents/ui/main.qml @@ -1,464 +1,464 @@ /* Copyright 2014-2015 Harald Sitter 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 . */ import QtQuick 2.2 import QtQuick.Layouts 1.0 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.plasma.plasmoid 2.0 import org.kde.plasma.private.volume 0.1 import "../code/icon.js" as Icon Item { id: main property bool volumeFeedback: Plasmoid.configuration.volumeFeedback property int maxVolumeValue: Math.round(Plasmoid.configuration.maximumVolume * PulseAudio.NormalVolume / 100.0) property int volumeStep: Math.round(Plasmoid.configuration.volumeStep * PulseAudio.NormalVolume / 100.0) property string displayName: i18n("Audio Volume") property QtObject draggedStream: null Layout.minimumHeight: units.gridUnit * 12 Layout.minimumWidth: units.gridUnit * 12 Layout.preferredHeight: units.gridUnit * 20 Layout.preferredWidth: units.gridUnit * 20 Plasmoid.icon: sinkModel.preferredSink ? Icon.name(sinkModel.preferredSink.volume, sinkModel.preferredSink.muted) : Icon.name(0, true) Plasmoid.switchWidth: units.gridUnit * 12 Plasmoid.switchHeight: units.gridUnit * 12 Plasmoid.toolTipMainText: { var sink = sinkModel.preferredSink; if (!sink) { return displayName; } if (sink.muted) { return i18n("Audio Muted"); } else { return i18n("Volume at %1%", volumePercent(sink.volume)); } } Plasmoid.toolTipSubText: sinkModel.preferredSink ? sinkModel.preferredSink.description : "" function boundVolume(volume) { return Math.max(PulseAudio.MinimalVolume, Math.min(volume, maxVolumeValue)); } function volumePercent(volume, max) { if (!max) { max = PulseAudio.NormalVolume; } return Math.round(volume / max * 100.0); } function increaseVolume() { if (!sinkModel.preferredSink) { return; } var volume = boundVolume(sinkModel.preferredSink.volume + volumeStep); var percent = volumePercent(volume, maxVolumeValue); sinkModel.preferredSink.muted = percent == 0; sinkModel.preferredSink.volume = volume; osd.show(percent); playFeedback(); } function decreaseVolume() { if (!sinkModel.preferredSink) { return; } var volume = boundVolume(sinkModel.preferredSink.volume - volumeStep); var percent = volumePercent(volume, maxVolumeValue); sinkModel.preferredSink.muted = percent == 0; sinkModel.preferredSink.volume = volume; osd.show(percent); playFeedback(); } function muteVolume() { if (!sinkModel.preferredSink) { return; } var toMute = !sinkModel.preferredSink.muted; sinkModel.preferredSink.muted = toMute; osd.show(toMute ? 0 : volumePercent(sinkModel.preferredSink.volume, maxVolumeValue)); playFeedback(); } function increaseMicrophoneVolume() { if (!sourceModel.defaultSource) { return; } var volume = boundVolume(sourceModel.defaultSource.volume + volumeStep); var percent = volumePercent(volume); sourceModel.defaultSource.muted = percent == 0; sourceModel.defaultSource.volume = volume; osd.showMicrophone(percent); } function decreaseMicrophoneVolume() { if (!sourceModel.defaultSource) { return; } var volume = boundVolume(sourceModel.defaultSource.volume - volumeStep); var percent = volumePercent(volume); sourceModel.defaultSource.muted = percent == 0; sourceModel.defaultSource.volume = volume; osd.showMicrophone(percent); } function muteMicrophone() { if (!sourceModel.defaultSource) { return; } var toMute = !sourceModel.defaultSource.muted; sourceModel.defaultSource.muted = toMute; osd.showMicrophone(toMute? 0 : volumePercent(sourceModel.defaultSource.volume)); } - function beginMoveStream(type, stream) { - if (type == "sink") { - sourceView.visible = false; - sourceViewHeader.visible = false; - } else if (type == "source") { - sinkView.visible = false; - sinkViewHeader.visible = false; - } - - tabBar.currentTab = devicesTab; - } - - function endMoveStream() { - tabBar.currentTab = streamsTab; - - sourceView.visible = true; - sourceViewHeader.visible = true; - sinkView.visible = true; - sinkViewHeader.visible = true; - } - function playFeedback(sinkIndex) { if (!volumeFeedback) { return; } if (sinkIndex == undefined) { sinkIndex = sinkModel.preferredSink.index; } feedback.play(sinkIndex); } SinkModel { id: sinkModel } Plasmoid.compactRepresentation: PlasmaCore.IconItem { source: plasmoid.icon active: mouseArea.containsMouse colorGroup: PlasmaCore.ColorScope.colorGroup MouseArea { id: mouseArea property int wheelDelta: 0 property bool wasExpanded: false anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.LeftButton | Qt.MiddleButton onPressed: { if (mouse.button == Qt.LeftButton) { wasExpanded = plasmoid.expanded; } else if (mouse.button == Qt.MiddleButton) { muteVolume(); } } onClicked: { if (mouse.button == Qt.LeftButton) { plasmoid.expanded = !wasExpanded; } } onWheel: { var delta = wheel.angleDelta.y || wheel.angleDelta.x; wheelDelta += delta; // Magic number 120 for common "one click" // See: http://qt-project.org/doc/qt-5/qml-qtquick-wheelevent.html#angleDelta-prop while (wheelDelta >= 120) { wheelDelta -= 120; increaseVolume(); } while (wheelDelta <= -120) { wheelDelta += 120; decreaseVolume(); } } } } GlobalActionCollection { // KGlobalAccel cannot transition from kmix to something else, so if // the user had a custom shortcut set for kmix those would get lost. // To avoid this we hijack kmix name and actions. Entirely mental but // best we can do to not cause annoyance for the user. // The display name actually is updated to whatever registered last // though, so as far as user visible strings go we should be fine. // As of 2015-07-21: // componentName: kmix // actions: increase_volume, decrease_volume, mute name: "kmix" displayName: main.displayName GlobalAction { objectName: "increase_volume" text: i18n("Increase Volume") shortcut: Qt.Key_VolumeUp onTriggered: increaseVolume() } GlobalAction { objectName: "decrease_volume" text: i18n("Decrease Volume") shortcut: Qt.Key_VolumeDown onTriggered: decreaseVolume() } GlobalAction { objectName: "mute" text: i18n("Mute") shortcut: Qt.Key_VolumeMute onTriggered: muteVolume() } GlobalAction { objectName: "increase_microphone_volume" text: i18n("Increase Microphone Volume") shortcut: Qt.Key_MicVolumeUp onTriggered: increaseMicrophoneVolume() } GlobalAction { objectName: "decrease_microphone_volume" text: i18n("Decrease Microphone Volume") shortcut: Qt.Key_MicVolumeDown onTriggered: decreaseMicrophoneVolume() } GlobalAction { objectName: "mic_mute" text: i18n("Mute Microphone") shortcut: Qt.Key_MicMute onTriggered: muteMicrophone() } } VolumeOSD { id: osd } VolumeFeedback { id: feedback } Plasmoid.fullRepresentation: ColumnLayout { spacing: units.smallSpacing + function beginMoveStream(type, stream) { + if (type == "sink") { + sourceView.visible = false; + sourceViewHeader.visible = false; + } else if (type == "source") { + sinkView.visible = false; + sinkViewHeader.visible = false; + } + + tabBar.currentTab = devicesTab; + } + + function endMoveStream() { + tabBar.currentTab = streamsTab; + + sourceView.visible = true; + sourceViewHeader.visible = true; + sinkView.visible = true; + sinkViewHeader.visible = true; + } + RowLayout { spacing: units.smallSpacing Layout.fillWidth: true PlasmaComponents.TabBar { id: tabBar Layout.fillWidth: true activeFocusOnTab: true PlasmaComponents.TabButton { id: devicesTab text: i18n("Devices") } PlasmaComponents.TabButton { id: streamsTab text: i18n("Applications") } } PlasmaComponents.ToolButton { Layout.alignment: Qt.AlignBottom tooltip: plasmoid.action("configure").text iconName: "configure" Accessible.name: tooltip onClicked: { plasmoid.action("configure").trigger(); } } } PlasmaExtras.ScrollArea { id: scrollView; Layout.fillWidth: true Layout.fillHeight: true horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff flickableItem.boundsBehavior: Flickable.StopAtBounds; //our scroll isn't a list of delegates, all internal items are tab focussable, making this redundant activeFocusOnTab: false Item { width: streamsView.visible ? streamsView.width : devicesView.width height: streamsView.visible ? streamsView.height : devicesView.height ColumnLayout { id: streamsView visible: tabBar.currentTab == streamsTab property int maximumWidth: scrollView.viewport.width width: maximumWidth Layout.maximumWidth: maximumWidth Header { Layout.fillWidth: true visible: sinkInputView.count > 0 text: i18n("Playback Streams") } ListView { id: sinkInputView Layout.fillWidth: true Layout.minimumHeight: contentHeight Layout.maximumHeight: contentHeight model: PulseObjectFilterModel { filters: [ { role: "VirtualStream", value: false } ] sourceModel: SinkInputModel {} } boundsBehavior: Flickable.StopAtBounds; delegate: StreamListItem { type: "sink-input" draggable: sinkView.count > 1 } } Header { Layout.fillWidth: true visible: sourceOutputView.count > 0 text: i18n("Capture Streams") } ListView { id: sourceOutputView Layout.fillWidth: true Layout.minimumHeight: contentHeight Layout.maximumHeight: contentHeight model: PulseObjectFilterModel { filters: [ { role: "VirtualStream", value: false } ] sourceModel: SourceOutputModel {} } boundsBehavior: Flickable.StopAtBounds; delegate: StreamListItem { type: "source-input" draggable: sourceView.count > 1 } } } ColumnLayout { id: devicesView visible: tabBar.currentTab == devicesTab property int maximumWidth: scrollView.viewport.width width: maximumWidth Layout.maximumWidth: maximumWidth Header { id: sinkViewHeader Layout.fillWidth: true visible: sinkView.count > 0 text: i18n("Playback Devices") } ListView { id: sinkView Layout.fillWidth: true Layout.minimumHeight: contentHeight Layout.maximumHeight: contentHeight model: PulseObjectFilterModel { sortRole: "SortByDefault" sortOrder: Qt.DescendingOrder sourceModel: sinkModel } boundsBehavior: Flickable.StopAtBounds; delegate: DeviceListItem { type: "sink" } } Header { id: sourceViewHeader Layout.fillWidth: true visible: sourceView.count > 0 text: i18n("Capture Devices") } ListView { id: sourceView Layout.fillWidth: true Layout.minimumHeight: contentHeight Layout.maximumHeight: contentHeight model: PulseObjectFilterModel { sortRole: "SortByDefault" sortOrder: Qt.DescendingOrder sourceModel: SourceModel { id: sourceModel } } boundsBehavior: Flickable.StopAtBounds; delegate: DeviceListItem { type: "source" } } } PlasmaExtras.Heading { level: 4 opacity: 0.8 width: parent.width height: scrollView.height visible: streamsView.visible && !sinkInputView.count && !sourceOutputView.count text: i18n("No applications playing or recording audio") wrapMode: Text.WordWrap verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } PlasmaExtras.Heading { level: 4 opacity: 0.8 width: parent.width height: scrollView.height visible: devicesView.visible && !sinkView.count && !sourceView.count text: i18n("No output or input devices found") wrapMode: Text.WordWrap verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } } } } } diff --git a/applet/metadata.desktop b/applet/metadata.desktop index 6480ad9..44600ae 100644 --- a/applet/metadata.desktop +++ b/applet/metadata.desktop @@ -1,98 +1,99 @@ [Desktop Entry] Name=Audio Volume Name[ar]=مستوى الصّوت Name[ast]=Volume d'audiu Name[ca]=Volum de l'àudio Name[ca@valencia]=Volum de l'àudio Name[cs]=Hlasitost zvuku Name[da]=Lydstyrke Name[de]=Lautstärke Name[el]=Ένταση ήχου Name[en_GB]=Audio Volume Name[es]=Volumen del sonido Name[et]=Helitugevus Name[eu]=Audioaren bolumena Name[fi]=Äänenvoimakkuus Name[fr]=Volume audio Name[gl]=Volume do son Name[he]=עוצמת שמע Name[hu]=Hangerő Name[it]=Volume audio Name[ko]=오디오 음량 Name[lt]=Garsumas Name[nl]=Geluidsvolume Name[nn]=Lydstyrke Name[pa]=ਆਡੀਓ ਵਾਲੀਅਮ Name[pl]=Głośność dźwięku Name[pt]=Volume do Áudio Name[pt_BR]=Volume do áudio Name[ru]=Громкость Name[sk]=Hlasitosť zvuku Name[sl]=Glasnost zvoka Name[sr]=Јачина звука Name[sr@ijekavian]=Јачина звука Name[sr@ijekavianlatin]=Jačina zvuka Name[sr@latin]=Jačina zvuka Name[sv]=Ljudvolym Name[tr]=Ses Seviyesi Name[uk]=Гучність Name[x-test]=xxAudio Volumexx Name[zh_CN]=音频音量 Name[zh_TW]=音效音量 Comment=Adjust the volume of devices and applications +Comment[ar]=اضبط مستوى صوت الأجهزة والتّطبيقات Comment[ca]=Ajusta el volum dels dispositius i les aplicacions Comment[ca@valencia]=Ajusta el volum dels dispositius i les aplicacions Comment[cs]=Upravit hlasitost zařízení a aplikací Comment[da]=Justér lydstyrke for enheder og programmer Comment[de]=Anpassung der Lautstärke von Geräten und Anwendungen Comment[el]=Προσαρμογή της έντασης των συσκευών και εφαρμογών Comment[en_GB]=Adjust the volume of devices and applications Comment[es]=Ajustar el volumen de dispositivos y aplicaciones Comment[et]=Seadmete ja rakenduste helitugevuse kohendamine Comment[eu]=Doitu gailuen eta aplikazioen bolumena Comment[fi]=Laitteiden ja sovellusten äänenvoimakkuuden säätö Comment[fr]=Ajuster le volume des périphériques et des applications Comment[gl]=Axustar o volume de dispositivos e aplicativos. Comment[he]=התאמת עוצמת שמע להתקנים ויישומים Comment[hu]=Beállítja az eszközök és alkalmazások hangerejét Comment[it]=Regola il volume dei dispositivi e delle applicazioni Comment[ko]=장치와 프로그램의 음량 조정 Comment[nl]=Geluidsniveau van apparaten en toepassingen aanpassen Comment[nn]=Juster lydstyrka til einingar og program Comment[pa]=ਡਿਵਾਈਸ ਅਤੇ ਐਪਲੀਕੇਸ਼ਨਾਂ ਲਈ ਵਾਲੀਅਮ ਨੂੰ ਢੁਕਵਾਂ ਬਣਾਓ Comment[pl]=Dostosuj głośność urządzeń i aplikacji Comment[pt]=Ajustar o volume dos dispositivos e aplicações Comment[pt_BR]=Ajuste o volume dos dispositivos e aplicativos Comment[ru]=Настройка громкости устройств и звука в приложениях Comment[sk]=Upraviť hlasitosť zariadení a aplikácií Comment[sl]=Prilagodi glasnost naprav in programov Comment[sr]=Подешавање јачине звука уређаја и програма Comment[sr@ijekavian]=Подешавање јачине звука уређаја и програма Comment[sr@ijekavianlatin]=Podešavanje jačine zvuka uređaja i programa Comment[sr@latin]=Podešavanje jačine zvuka uređaja i programa Comment[sv]=Justera enhets- och programvolym Comment[tr]=Cihazların ve uygulamaların ses düzeyini ayarla Comment[uk]=Коригування гучності пристроїв і звуків програм Comment[x-test]=xxAdjust the volume of devices and applicationsxx Comment[zh_CN]=调整设备和程序的音量 Comment[zh_TW]=調整裝置與應用程式的音量 Icon=org.kde.plasma.volume Type=Service X-KDE-ServiceTypes=Plasma/Applet X-Plasma-API=declarativeappletscript X-Plasma-MainScript=ui/main.qml X-Plasma-NotificationArea=true X-Plasma-NotificationAreaCategory=Hardware X-Plasma-ConfigPlugins=kcm_pulseaudio X-KDE-PluginInfo-Name=org.kde.plasma.volume X-KDE-PluginInfo-Category=Multimedia X-KDE-PluginInfo-Author=Harald Sitter X-KDE-PluginInfo-Email=sitter@kde.org X-KDE-PluginInfo-Version=1.0.0 X-KDE-PluginInfo-Website= X-KDE-PluginInfo-License=GPL X-KDE-PluginInfo-EnabledByDefault=true