mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-04 03:22:12 -04:00
Compare commits
4 Commits
fce120fa31
...
8c01deba86
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c01deba86 | ||
|
|
b645487e79 | ||
|
|
91569affd7 | ||
|
|
1ed44ee6f3 |
@@ -14,7 +14,7 @@ Singleton {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
duration: Theme.expressiveDurations.expressiveEffects
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
}
|
||||
}
|
||||
@@ -31,16 +31,16 @@ Singleton {
|
||||
readonly property Transition displaced: Transition {
|
||||
DankAnim {
|
||||
property: "y"
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
duration: Theme.expressiveDurations.normal
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
||||
}
|
||||
}
|
||||
|
||||
readonly property Transition move: Transition {
|
||||
DankAnim {
|
||||
property: "y"
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.bezierCurve: Theme.expressiveCurves.standard
|
||||
duration: Theme.expressiveDurations.normal
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +121,8 @@ Singleton {
|
||||
|
||||
property string vpnLastConnected: ""
|
||||
|
||||
property var deviceMaxVolumes: ({})
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!isGreeterMode) {
|
||||
loadSettings();
|
||||
@@ -1052,6 +1054,35 @@ Singleton {
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setDeviceMaxVolume(nodeName, maxPercent) {
|
||||
if (!nodeName)
|
||||
return;
|
||||
const updated = Object.assign({}, deviceMaxVolumes);
|
||||
const clamped = Math.max(100, Math.min(200, Math.round(maxPercent)));
|
||||
if (clamped === 100) {
|
||||
delete updated[nodeName];
|
||||
} else {
|
||||
updated[nodeName] = clamped;
|
||||
}
|
||||
deviceMaxVolumes = updated;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function getDeviceMaxVolume(nodeName) {
|
||||
if (!nodeName)
|
||||
return 100;
|
||||
return deviceMaxVolumes[nodeName] ?? 100;
|
||||
}
|
||||
|
||||
function removeDeviceMaxVolume(nodeName) {
|
||||
if (!nodeName)
|
||||
return;
|
||||
const updated = Object.assign({}, deviceMaxVolumes);
|
||||
delete updated[nodeName];
|
||||
deviceMaxVolumes = updated;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function syncWallpaperForCurrentMode() {
|
||||
if (!perModeWallpaper)
|
||||
return;
|
||||
|
||||
@@ -72,7 +72,9 @@ var SPEC = {
|
||||
appOverrides: { def: {} },
|
||||
searchAppActions: { def: true },
|
||||
|
||||
vpnLastConnected: { def: "" }
|
||||
vpnLastConnected: { def: "" },
|
||||
|
||||
deviceMaxVolumes: { def: {} }
|
||||
};
|
||||
|
||||
function getValidKeys() {
|
||||
|
||||
@@ -451,10 +451,11 @@ Column {
|
||||
if (!AudioService.sink || !AudioService.sink.audio)
|
||||
return;
|
||||
let delta = wheelEvent.angleDelta.y;
|
||||
let maxVol = AudioService.sinkMaxVolume;
|
||||
let currentVolume = AudioService.sink.audio.volume * 100;
|
||||
let newVolume;
|
||||
if (delta > 0)
|
||||
newVolume = Math.min(100, currentVolume + 5);
|
||||
newVolume = Math.min(maxVol, currentVolume + 5);
|
||||
else
|
||||
newVolume = Math.max(0, currentVolume - 5);
|
||||
AudioService.sink.audio.muted = false;
|
||||
|
||||
@@ -102,8 +102,8 @@ Rectangle {
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||
enabled: AudioService.sink && AudioService.sink.audio
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
value: AudioService.sink && AudioService.sink.audio ? Math.min(100, Math.round(AudioService.sink.audio.volume * 100)) : 0
|
||||
maximum: AudioService.sinkMaxVolume
|
||||
value: AudioService.sink && AudioService.sink.audio ? Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100)) : 0
|
||||
showValue: true
|
||||
unit: "%"
|
||||
valueOverride: actualVolumePercent
|
||||
@@ -136,15 +136,15 @@ Rectangle {
|
||||
|
||||
function normalizePinList(value) {
|
||||
if (Array.isArray(value))
|
||||
return value.filter(v => v)
|
||||
return value.filter(v => v);
|
||||
if (typeof value === "string" && value.length > 0)
|
||||
return [value]
|
||||
return []
|
||||
return [value];
|
||||
return [];
|
||||
}
|
||||
|
||||
function getPinnedOutputs() {
|
||||
const pins = SettingsData.audioOutputDevicePins || {}
|
||||
return normalizePinList(pins["preferredOutput"])
|
||||
const pins = SettingsData.audioOutputDevicePins || {};
|
||||
return normalizePinList(pins["preferredOutput"]);
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -163,14 +163,14 @@ Rectangle {
|
||||
let sorted = [...nodes];
|
||||
sorted.sort((a, b) => {
|
||||
// Pinned device first
|
||||
const aPinnedIndex = pinnedList.indexOf(a.name)
|
||||
const bPinnedIndex = pinnedList.indexOf(b.name)
|
||||
const aPinnedIndex = pinnedList.indexOf(a.name);
|
||||
const bPinnedIndex = pinnedList.indexOf(b.name);
|
||||
if (aPinnedIndex !== -1 || bPinnedIndex !== -1) {
|
||||
if (aPinnedIndex === -1)
|
||||
return 1
|
||||
return 1;
|
||||
if (bPinnedIndex === -1)
|
||||
return -1
|
||||
return aPinnedIndex - bPinnedIndex
|
||||
return -1;
|
||||
return aPinnedIndex - bPinnedIndex;
|
||||
}
|
||||
// Then active device
|
||||
if (a === AudioService.sink && b !== AudioService.sink)
|
||||
@@ -292,24 +292,24 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
const pins = JSON.parse(JSON.stringify(SettingsData.audioOutputDevicePins || {}))
|
||||
let pinnedList = audioContent.normalizePinList(pins["preferredOutput"])
|
||||
const pinIndex = pinnedList.indexOf(modelData.name)
|
||||
const pins = JSON.parse(JSON.stringify(SettingsData.audioOutputDevicePins || {}));
|
||||
let pinnedList = audioContent.normalizePinList(pins["preferredOutput"]);
|
||||
const pinIndex = pinnedList.indexOf(modelData.name);
|
||||
|
||||
if (pinIndex !== -1) {
|
||||
pinnedList.splice(pinIndex, 1)
|
||||
pinnedList.splice(pinIndex, 1);
|
||||
} else {
|
||||
pinnedList.unshift(modelData.name)
|
||||
pinnedList.unshift(modelData.name);
|
||||
if (pinnedList.length > audioContent.maxPinnedOutputs)
|
||||
pinnedList = pinnedList.slice(0, audioContent.maxPinnedOutputs)
|
||||
pinnedList = pinnedList.slice(0, audioContent.maxPinnedOutputs);
|
||||
}
|
||||
|
||||
if (pinnedList.length > 0)
|
||||
pins["preferredOutput"] = pinnedList
|
||||
pins["preferredOutput"] = pinnedList;
|
||||
else
|
||||
delete pins["preferredOutput"]
|
||||
delete pins["preferredOutput"];
|
||||
|
||||
SettingsData.set("audioOutputDevicePins", pins)
|
||||
SettingsData.set("audioOutputDevicePins", pins);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ Row {
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||
enabled: defaultSink !== null
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
maximum: AudioService.sinkMaxVolume
|
||||
showValue: true
|
||||
unit: "%"
|
||||
valueOverride: actualVolumePercent
|
||||
@@ -91,7 +91,7 @@ Row {
|
||||
Binding {
|
||||
target: volumeSlider
|
||||
property: "value"
|
||||
value: defaultSink ? Math.min(100, Math.round(defaultSink.audio.volume * 100)) : 0
|
||||
value: defaultSink ? Math.min(AudioService.sinkMaxVolume, Math.round(defaultSink.audio.volume * 100)) : 0
|
||||
when: !volumeSlider.isDragging
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,70 @@ BasePill {
|
||||
property real volumeAccumulator: 0
|
||||
property real brightnessAccumulator: 0
|
||||
readonly property real vIconSize: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
property var _hRow: null
|
||||
property var _vCol: null
|
||||
property var _hAudio: null
|
||||
property var _hBrightness: null
|
||||
property var _hMic: null
|
||||
property var _vAudio: null
|
||||
property var _vBrightness: null
|
||||
property var _vMic: null
|
||||
|
||||
onWheel: function (wheelEvent) {
|
||||
const delta = wheelEvent.angleDelta.y;
|
||||
if (delta === 0)
|
||||
return;
|
||||
|
||||
const rootX = wheelEvent.x - root.leftMargin;
|
||||
const rootY = wheelEvent.y - root.topMargin;
|
||||
|
||||
if (root.isVerticalOrientation && _vCol) {
|
||||
const pos = root.mapToItem(_vCol, rootX, rootY);
|
||||
if (_vBrightness?.visible && pos.y >= _vBrightness.y && pos.y < _vBrightness.y + _vBrightness.height) {
|
||||
root.handleBrightnessWheel(delta);
|
||||
} else if (_vMic?.visible && pos.y >= _vMic.y && pos.y < _vMic.y + _vMic.height) {
|
||||
root.handleMicWheel(delta);
|
||||
} else {
|
||||
root.handleVolumeWheel(delta);
|
||||
}
|
||||
} else if (_hRow) {
|
||||
const pos = root.mapToItem(_hRow, rootX, rootY);
|
||||
if (_hBrightness?.visible && pos.x >= _hBrightness.x && pos.x < _hBrightness.x + _hBrightness.width) {
|
||||
root.handleBrightnessWheel(delta);
|
||||
} else if (_hMic?.visible && pos.x >= _hMic.x && pos.x < _hMic.x + _hMic.width) {
|
||||
root.handleMicWheel(delta);
|
||||
} else {
|
||||
root.handleVolumeWheel(delta);
|
||||
}
|
||||
} else {
|
||||
root.handleVolumeWheel(delta);
|
||||
}
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
|
||||
onRightClicked: function (rootX, rootY) {
|
||||
if (root.isVerticalOrientation && _vCol) {
|
||||
const pos = root.mapToItem(_vCol, rootX, rootY);
|
||||
if (_vAudio?.visible && pos.y >= _vAudio.y && pos.y < _vAudio.y + _vAudio.height) {
|
||||
AudioService.toggleMute();
|
||||
return;
|
||||
}
|
||||
if (_vMic?.visible && pos.y >= _vMic.y && pos.y < _vMic.y + _vMic.height) {
|
||||
AudioService.toggleMicMute();
|
||||
return;
|
||||
}
|
||||
} else if (_hRow) {
|
||||
const pos = root.mapToItem(_hRow, rootX, rootY);
|
||||
if (_hAudio?.visible && pos.x >= _hAudio.x && pos.x < _hAudio.x + _hAudio.width) {
|
||||
AudioService.toggleMute();
|
||||
return;
|
||||
}
|
||||
if (_hMic?.visible && pos.x >= _hMic.x && pos.x < _hMic.x + _hMic.width) {
|
||||
AudioService.toggleMicMute();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.showPrinterIcon
|
||||
@@ -140,8 +204,9 @@ BasePill {
|
||||
volumeAccumulator = 0;
|
||||
}
|
||||
|
||||
const maxVol = AudioService.sinkMaxVolume;
|
||||
const currentVolume = AudioService.sink.audio.volume * 100;
|
||||
const newVolume = delta > 0 ? Math.min(100, currentVolume + step) : Math.max(0, currentVolume - step);
|
||||
const newVolume = delta > 0 ? Math.min(maxVol, currentVolume + step) : Math.max(0, currentVolume - step);
|
||||
AudioService.sink.audio.muted = false;
|
||||
AudioService.sink.audio.volume = newVolume / 100;
|
||||
AudioService.playVolumeChangeSoundIfEnabled();
|
||||
@@ -241,15 +306,26 @@ BasePill {
|
||||
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : controlIndicators.implicitWidth
|
||||
implicitHeight: root.isVerticalOrientation ? controlColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
|
||||
|
||||
Component.onCompleted: {
|
||||
root._hRow = controlIndicators;
|
||||
root._vCol = controlColumn;
|
||||
root._hAudio = audioIcon.parent;
|
||||
root._hBrightness = brightnessIcon.parent;
|
||||
root._hMic = micIcon.parent;
|
||||
root._vAudio = audioIconV.parent;
|
||||
root._vBrightness = brightnessIconV.parent;
|
||||
root._vMic = micIconV.parent;
|
||||
}
|
||||
|
||||
Column {
|
||||
id: controlColumn
|
||||
visible: root.isVerticalOrientation
|
||||
width: root.vIconSize
|
||||
width: parent.width
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Item {
|
||||
width: root.vIconSize
|
||||
width: parent.width
|
||||
height: root.vIconSize
|
||||
visible: root.showScreenSharingIcon && NiriService.hasCasts
|
||||
|
||||
@@ -262,7 +338,7 @@ BasePill {
|
||||
}
|
||||
|
||||
Item {
|
||||
width: root.vIconSize
|
||||
width: parent.width
|
||||
height: root.vIconSize
|
||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
||||
|
||||
@@ -275,7 +351,7 @@ BasePill {
|
||||
}
|
||||
|
||||
Item {
|
||||
width: root.vIconSize
|
||||
width: parent.width
|
||||
height: root.vIconSize
|
||||
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
|
||||
|
||||
@@ -288,7 +364,7 @@ BasePill {
|
||||
}
|
||||
|
||||
Item {
|
||||
width: root.vIconSize
|
||||
width: parent.width
|
||||
height: root.vIconSize
|
||||
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
||||
|
||||
@@ -301,7 +377,7 @@ BasePill {
|
||||
}
|
||||
|
||||
Item {
|
||||
width: root.vIconSize
|
||||
width: parent.width
|
||||
height: root.vIconSize + (root.showAudioPercent ? audioPercentV.implicitHeight + 2 : 0)
|
||||
visible: root.showAudioIcon
|
||||
|
||||
@@ -324,22 +400,10 @@ BasePill {
|
||||
anchors.top: audioIconV.bottom
|
||||
anchors.topMargin: 2
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onWheel: function (wheelEvent) {
|
||||
root.handleVolumeWheel(wheelEvent.angleDelta.y);
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
onClicked: {
|
||||
AudioService.toggleMute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: root.vIconSize
|
||||
width: parent.width
|
||||
height: root.vIconSize + (root.showMicPercent ? micPercentV.implicitHeight + 2 : 0)
|
||||
visible: root.showMicIcon
|
||||
|
||||
@@ -362,22 +426,10 @@ BasePill {
|
||||
anchors.top: micIconV.bottom
|
||||
anchors.topMargin: 2
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onWheel: function (wheelEvent) {
|
||||
root.handleMicWheel(wheelEvent.angleDelta.y);
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
onClicked: {
|
||||
AudioService.toggleMicMute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: root.vIconSize
|
||||
width: parent.width
|
||||
height: root.vIconSize + (root.showBrightnessPercent ? brightnessPercentV.implicitHeight + 2 : 0)
|
||||
visible: root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice()
|
||||
|
||||
@@ -400,19 +452,10 @@ BasePill {
|
||||
anchors.top: brightnessIconV.bottom
|
||||
anchors.topMargin: 2
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
onWheel: function (wheelEvent) {
|
||||
root.handleBrightnessWheel(wheelEvent.angleDelta.y);
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: root.vIconSize
|
||||
width: parent.width
|
||||
height: root.vIconSize
|
||||
visible: root.showBatteryIcon && BatteryService.batteryAvailable
|
||||
|
||||
@@ -425,7 +468,7 @@ BasePill {
|
||||
}
|
||||
|
||||
Item {
|
||||
width: root.vIconSize
|
||||
width: parent.width
|
||||
height: root.vIconSize
|
||||
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
|
||||
|
||||
@@ -438,7 +481,7 @@ BasePill {
|
||||
}
|
||||
|
||||
Item {
|
||||
width: root.vIconSize
|
||||
width: parent.width
|
||||
height: root.vIconSize
|
||||
visible: root.hasNoVisibleIcons()
|
||||
|
||||
@@ -494,7 +537,7 @@ BasePill {
|
||||
|
||||
Rectangle {
|
||||
width: audioIcon.implicitWidth + (root.showAudioPercent ? audioPercent.implicitWidth : 0) + 4
|
||||
height: audioIcon.implicitHeight + 4
|
||||
height: root.widgetThickness - root.horizontalPadding * 2
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showAudioIcon
|
||||
@@ -519,24 +562,11 @@ BasePill {
|
||||
anchors.left: audioIcon.right
|
||||
anchors.leftMargin: 2
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: audioWheelArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onWheel: function (wheelEvent) {
|
||||
root.handleVolumeWheel(wheelEvent.angleDelta.y);
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
onClicked: {
|
||||
AudioService.toggleMute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: micIcon.implicitWidth + (root.showMicPercent ? micPercent.implicitWidth : 0) + 4
|
||||
height: micIcon.implicitHeight + 4
|
||||
height: root.widgetThickness - root.horizontalPadding * 2
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showMicIcon
|
||||
@@ -561,24 +591,11 @@ BasePill {
|
||||
anchors.left: micIcon.right
|
||||
anchors.leftMargin: 2
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: micWheelArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onWheel: function (wheelEvent) {
|
||||
root.handleMicWheel(wheelEvent.angleDelta.y);
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
onClicked: {
|
||||
AudioService.toggleMicMute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: brightnessIcon.implicitWidth + (root.showBrightnessPercent ? brightnessPercent.implicitWidth : 0) + 4
|
||||
height: brightnessIcon.implicitHeight + 4
|
||||
height: root.widgetThickness - root.horizontalPadding * 2
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice()
|
||||
@@ -603,16 +620,6 @@ BasePill {
|
||||
anchors.left: brightnessIcon.right
|
||||
anchors.leftMargin: 2
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: brightnessWheelArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
onWheel: function (wheelEvent) {
|
||||
root.handleBrightnessWheel(wheelEvent.angleDelta.y);
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
|
||||
@@ -201,8 +201,9 @@ Item {
|
||||
function adjustVolume(step) {
|
||||
if (!volumeAvailable)
|
||||
return;
|
||||
const maxVol = usePlayerVolume ? 100 : AudioService.sinkMaxVolume;
|
||||
const current = Math.round(currentVolume * 100);
|
||||
const newVolume = Math.min(100, Math.max(0, current + step));
|
||||
const newVolume = Math.min(maxVol, Math.max(0, current + step));
|
||||
|
||||
SessionData.suppressOSDTemporarily();
|
||||
if (usePlayerVolume) {
|
||||
@@ -778,7 +779,8 @@ Item {
|
||||
SessionData.suppressOSDTemporarily();
|
||||
const delta = wheelEvent.angleDelta.y;
|
||||
const current = (currentVolume * 100) || 0;
|
||||
const newVolume = delta > 0 ? Math.min(100, current + 5) : Math.max(0, current - 5);
|
||||
const maxVol = usePlayerVolume ? 100 : AudioService.sinkMaxVolume;
|
||||
const newVolume = delta > 0 ? Math.min(maxVol, current + 5) : Math.max(0, current - 5);
|
||||
|
||||
if (usePlayerVolume) {
|
||||
activePlayer.volume = newVolume / 100;
|
||||
|
||||
@@ -95,7 +95,7 @@ DankOSD {
|
||||
x: parent.gap * 2 + Theme.iconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
maximum: AudioService.sinkMaxVolume
|
||||
enabled: AudioService.sink && AudioService.sink.audio
|
||||
showValue: true
|
||||
unit: "%"
|
||||
@@ -105,7 +105,7 @@ DankOSD {
|
||||
|
||||
Component.onCompleted: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100));
|
||||
value = Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ DankOSD {
|
||||
|
||||
function onVolumeChanged() {
|
||||
if (volumeSlider && !volumeSlider.pressed) {
|
||||
volumeSlider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100));
|
||||
volumeSlider.value = Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +179,7 @@ DankOSD {
|
||||
y: gap * 2 + Theme.iconSize
|
||||
|
||||
property bool dragging: false
|
||||
property int value: AudioService.sink && AudioService.sink.audio ? Math.min(100, Math.round(AudioService.sink.audio.volume * 100)) : 0
|
||||
property int value: AudioService.sink && AudioService.sink.audio ? Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100)) : 0
|
||||
|
||||
Rectangle {
|
||||
id: vertTrack
|
||||
@@ -193,7 +193,7 @@ DankOSD {
|
||||
Rectangle {
|
||||
id: vertFill
|
||||
width: parent.width
|
||||
height: (vertSlider.value / 100) * parent.height
|
||||
height: (vertSlider.value / AudioService.sinkMaxVolume) * parent.height
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Theme.primary
|
||||
@@ -206,7 +206,7 @@ DankOSD {
|
||||
height: 8
|
||||
radius: Theme.cornerRadius
|
||||
y: {
|
||||
const ratio = vertSlider.value / 100;
|
||||
const ratio = vertSlider.value / AudioService.sinkMaxVolume;
|
||||
const travel = parent.height - height;
|
||||
return Math.max(0, Math.min(travel, travel * (1 - ratio)));
|
||||
}
|
||||
@@ -249,8 +249,9 @@ DankOSD {
|
||||
|
||||
function updateVolume(mouse) {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
const maxVol = AudioService.sinkMaxVolume;
|
||||
const ratio = 1.0 - (mouse.y / height);
|
||||
const volume = Math.max(0, Math.min(100, Math.round(ratio * 100)));
|
||||
const volume = Math.max(0, Math.min(maxVol, Math.round(ratio * maxVol)));
|
||||
SessionData.suppressOSDTemporarily();
|
||||
AudioService.sink.audio.volume = volume / 100;
|
||||
resetHideTimer();
|
||||
@@ -262,7 +263,7 @@ DankOSD {
|
||||
target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null
|
||||
|
||||
function onVolumeChanged() {
|
||||
vertSlider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100));
|
||||
vertSlider.value = Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,7 +285,7 @@ DankOSD {
|
||||
if (!useVertical) {
|
||||
const slider = contentLoader.item.item.children[0].children[1];
|
||||
if (slider && slider.value !== undefined) {
|
||||
slider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100));
|
||||
slider.value = Math.min(AudioService.sinkMaxVolume, Math.round(AudioService.sink.audio.volume * 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ Item {
|
||||
readonly property real bottomMargin: isVerticalOrientation ? (isBottomBarEdge && isLast ? barEdgeExtension : (isLast ? gapExtension : gapExtension / 2)) : 0
|
||||
|
||||
signal clicked
|
||||
signal rightClicked
|
||||
signal rightClicked(real rootX, real rootY)
|
||||
signal wheel(var wheelEvent)
|
||||
|
||||
width: isVerticalOrientation ? barThickness : visualWidth
|
||||
@@ -125,7 +125,8 @@ Item {
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onPressed: function (mouse) {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
root.rightClicked();
|
||||
const rPos = mouseArea.mapToItem(root, mouse.x, mouse.y);
|
||||
root.rightClicked(rPos.x, rPos.y);
|
||||
return;
|
||||
}
|
||||
if (popoutTarget) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
@@ -131,21 +130,64 @@ Item {
|
||||
Repeater {
|
||||
model: root.outputDevices
|
||||
|
||||
delegate: DeviceAliasRow {
|
||||
delegate: Column {
|
||||
required property var modelData
|
||||
width: parent?.width ?? 0
|
||||
spacing: 0
|
||||
|
||||
deviceNode: modelData
|
||||
deviceType: "output"
|
||||
DeviceAliasRow {
|
||||
deviceNode: modelData
|
||||
deviceType: "output"
|
||||
|
||||
onEditRequested: device => {
|
||||
root.editingDevice = device;
|
||||
root.editingDeviceType = "output";
|
||||
root.newDeviceName = AudioService.displayName(device);
|
||||
root.showEditDialog = true;
|
||||
onEditRequested: device => {
|
||||
root.editingDevice = device;
|
||||
root.editingDeviceType = "output";
|
||||
root.newDeviceName = AudioService.displayName(device);
|
||||
root.showEditDialog = true;
|
||||
}
|
||||
|
||||
onResetRequested: device => {
|
||||
AudioService.removeDeviceAlias(device.name);
|
||||
}
|
||||
}
|
||||
|
||||
onResetRequested: device => {
|
||||
AudioService.removeDeviceAlias(device.name);
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 36
|
||||
|
||||
StyledText {
|
||||
id: maxVolLabel
|
||||
text: I18n.tr("Max Volume", "Audio settings: maximum volume limit per device")
|
||||
x: Theme.spacingM + Theme.iconSize + Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
id: maxVolSlider
|
||||
anchors.left: maxVolLabel.right
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: 36
|
||||
minimum: 100
|
||||
maximum: 200
|
||||
step: 5
|
||||
showValue: true
|
||||
unit: "%"
|
||||
onSliderValueChanged: newValue => {
|
||||
SessionData.setDeviceMaxVolume(modelData.name, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: maxVolSlider
|
||||
property: "value"
|
||||
value: SessionData.deviceMaxVolumes[modelData.name] ?? 100
|
||||
when: !maxVolSlider.isDragging
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,28 +624,40 @@ Singleton {
|
||||
const result = {};
|
||||
const lines = content.split("\n");
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^\s*monitorrule=([^,]+),([^,]+),([^,]+),([^,]+),(\d+),([\d.]+),(-?\d+),(-?\d+),(\d+),(\d+),(\d+)/);
|
||||
if (!match)
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed.startsWith("monitorrule="))
|
||||
continue;
|
||||
const name = match[1].trim();
|
||||
|
||||
const params = {};
|
||||
for (const pair of trimmed.substring("monitorrule=".length).split(",")) {
|
||||
const colonIdx = pair.indexOf(":");
|
||||
if (colonIdx < 0)
|
||||
continue;
|
||||
params[pair.substring(0, colonIdx).trim()] = pair.substring(colonIdx + 1).trim();
|
||||
}
|
||||
|
||||
const name = params.name;
|
||||
if (!name)
|
||||
continue;
|
||||
|
||||
result[name] = {
|
||||
"name": name,
|
||||
"logical": {
|
||||
"x": parseInt(match[7]),
|
||||
"y": parseInt(match[8]),
|
||||
"scale": parseFloat(match[6]),
|
||||
"transform": mangoToTransform(parseInt(match[5]))
|
||||
"x": parseInt(params.x || "0"),
|
||||
"y": parseInt(params.y || "0"),
|
||||
"scale": parseFloat(params.scale || "1"),
|
||||
"transform": mangoToTransform(parseInt(params.rr || "0"))
|
||||
},
|
||||
"modes": [
|
||||
{
|
||||
"width": parseInt(match[9]),
|
||||
"height": parseInt(match[10]),
|
||||
"refresh_rate": parseInt(match[11]) * 1000
|
||||
"width": parseInt(params.width || "1920"),
|
||||
"height": parseInt(params.height || "1080"),
|
||||
"refresh_rate": parseFloat(params.refresh || "60") * 1000
|
||||
}
|
||||
],
|
||||
"current_mode": 0,
|
||||
"vrr_enabled": false,
|
||||
"vrr_supported": false
|
||||
"vrr_enabled": parseInt(params.vrr || "0") === 1,
|
||||
"vrr_supported": true
|
||||
};
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -39,12 +39,37 @@ Singleton {
|
||||
}
|
||||
property bool wireplumberReloading: false
|
||||
|
||||
readonly property int sinkMaxVolume: {
|
||||
const name = sink?.name ?? "";
|
||||
if (!name)
|
||||
return 100;
|
||||
return SessionData.deviceMaxVolumes[name] ?? 100;
|
||||
}
|
||||
|
||||
signal micMuteChanged
|
||||
signal audioOutputCycled(string deviceName)
|
||||
signal deviceAliasChanged(string nodeName, string newAlias)
|
||||
signal wireplumberReloadStarted()
|
||||
signal wireplumberReloadStarted
|
||||
signal wireplumberReloadCompleted(bool success)
|
||||
|
||||
function getMaxVolumePercent(node) {
|
||||
if (!node?.name)
|
||||
return 100;
|
||||
return SessionData.deviceMaxVolumes[node.name] ?? 100;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onDeviceMaxVolumesChanged() {
|
||||
if (!root.sink?.audio)
|
||||
return;
|
||||
const maxVol = root.sinkMaxVolume;
|
||||
const currentPercent = Math.round(root.sink.audio.volume * 100);
|
||||
if (currentPercent > maxVol)
|
||||
root.sink.audio.volume = maxVol / 100;
|
||||
}
|
||||
}
|
||||
|
||||
function getAvailableSinks() {
|
||||
return Pipewire.nodes.values.filter(node => node.audio && node.isSink && !node.isStream);
|
||||
}
|
||||
@@ -814,11 +839,11 @@ EOFCONFIG
|
||||
}
|
||||
|
||||
function setVolume(percentage) {
|
||||
if (!root.sink?.audio) {
|
||||
if (!root.sink?.audio)
|
||||
return "No audio sink available";
|
||||
}
|
||||
|
||||
const clampedVolume = Math.max(0, Math.min(100, percentage));
|
||||
const maxVol = root.sinkMaxVolume;
|
||||
const clampedVolume = Math.max(0, Math.min(maxVol, percentage));
|
||||
root.sink.audio.volume = clampedVolume / 100;
|
||||
return `Volume set to ${clampedVolume}%`;
|
||||
}
|
||||
@@ -859,34 +884,32 @@ EOFCONFIG
|
||||
}
|
||||
|
||||
function increment(step: string): string {
|
||||
if (!root.sink?.audio) {
|
||||
if (!root.sink?.audio)
|
||||
return "No audio sink available";
|
||||
}
|
||||
|
||||
if (root.sink.audio.muted) {
|
||||
if (root.sink.audio.muted)
|
||||
root.sink.audio.muted = false;
|
||||
}
|
||||
|
||||
const maxVol = root.sinkMaxVolume;
|
||||
const currentVolume = Math.round(root.sink.audio.volume * 100);
|
||||
const stepValue = parseInt(step || "5");
|
||||
const newVolume = Math.max(0, Math.min(100, currentVolume + stepValue));
|
||||
const newVolume = Math.max(0, Math.min(maxVol, currentVolume + stepValue));
|
||||
|
||||
root.sink.audio.volume = newVolume / 100;
|
||||
return `Volume increased to ${newVolume}%`;
|
||||
}
|
||||
|
||||
function decrement(step: string): string {
|
||||
if (!root.sink?.audio) {
|
||||
if (!root.sink?.audio)
|
||||
return "No audio sink available";
|
||||
}
|
||||
|
||||
if (root.sink.audio.muted) {
|
||||
if (root.sink.audio.muted)
|
||||
root.sink.audio.muted = false;
|
||||
}
|
||||
|
||||
const maxVol = root.sinkMaxVolume;
|
||||
const currentVolume = Math.round(root.sink.audio.volume * 100);
|
||||
const stepValue = parseInt(step || "5");
|
||||
const newVolume = Math.max(0, Math.min(100, currentVolume - stepValue));
|
||||
const newVolume = Math.max(0, Math.min(maxVol, currentVolume - stepValue));
|
||||
|
||||
root.sink.audio.volume = newVolume / 100;
|
||||
return `Volume decreased to ${newVolume}%`;
|
||||
@@ -912,7 +935,8 @@ EOFCONFIG
|
||||
if (root.sink?.audio) {
|
||||
const volume = Math.round(root.sink.audio.volume * 100);
|
||||
const muteStatus = root.sink.audio.muted ? " (muted)" : "";
|
||||
result += `Output: ${volume}%${muteStatus}\n`;
|
||||
const maxVol = root.sinkMaxVolume;
|
||||
result += `Output: ${volume}%${muteStatus} (max: ${maxVol}%)\n`;
|
||||
} else {
|
||||
result += "Output: No sink available\n";
|
||||
}
|
||||
@@ -928,6 +952,36 @@ EOFCONFIG
|
||||
return result;
|
||||
}
|
||||
|
||||
function getmaxvolume(): string {
|
||||
return `${root.sinkMaxVolume}`;
|
||||
}
|
||||
|
||||
function setmaxvolume(percent: string): string {
|
||||
if (!root.sink?.name)
|
||||
return "No audio sink available";
|
||||
const val = parseInt(percent);
|
||||
if (isNaN(val))
|
||||
return "Invalid percentage";
|
||||
SessionData.setDeviceMaxVolume(root.sink.name, val);
|
||||
return `Max volume set to ${SessionData.getDeviceMaxVolume(root.sink.name)}%`;
|
||||
}
|
||||
|
||||
function getmaxvolumefor(nodeName: string): string {
|
||||
if (!nodeName)
|
||||
return "No node name specified";
|
||||
return `${SessionData.getDeviceMaxVolume(nodeName)}`;
|
||||
}
|
||||
|
||||
function setmaxvolumefor(nodeName: string, percent: string): string {
|
||||
if (!nodeName)
|
||||
return "No node name specified";
|
||||
const val = parseInt(percent);
|
||||
if (isNaN(val))
|
||||
return "Invalid percentage";
|
||||
SessionData.setDeviceMaxVolume(nodeName, val);
|
||||
return `Max volume for ${nodeName} set to ${SessionData.getDeviceMaxVolume(nodeName)}%`;
|
||||
}
|
||||
|
||||
function cycleoutput(): string {
|
||||
const result = root.cycleAudioOutput();
|
||||
if (!result)
|
||||
|
||||
@@ -298,7 +298,7 @@ Singleton {
|
||||
function generateOutputsConfig(outputsData) {
|
||||
if (!outputsData || Object.keys(outputsData).length === 0)
|
||||
return;
|
||||
let lines = ["# Auto-generated by DMS - do not edit manually", "# VRR is global: set adaptive_sync=1 in config.conf", ""];
|
||||
let lines = ["# Auto-generated by DMS - do not edit manually", ""];
|
||||
|
||||
for (const outputName in outputsData) {
|
||||
const output = outputsData[outputName];
|
||||
@@ -320,8 +320,9 @@ Singleton {
|
||||
const y = output.logical?.y ?? 0;
|
||||
const scale = output.logical?.scale ?? 1.0;
|
||||
const transform = transformToMango(output.logical?.transform ?? "Normal");
|
||||
const vrr = output.vrr_enabled ? 1 : 0;
|
||||
|
||||
const rule = [outputName, "0.55", "1", "tile", transform, scale, x, y, width, height, refreshRate].join(",");
|
||||
const rule = ["name:" + outputName, "width:" + width, "height:" + height, "refresh:" + refreshRate, "x:" + x, "y:" + y, "scale:" + scale, "rr:" + transform, "vrr:" + vrr].join(",");
|
||||
|
||||
lines.push("monitorrule=" + rule);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user