Merge branch 'main' into weather-settings

This commit is contained in:
vaguesyntax
2025-10-30 18:13:49 +03:00
54 changed files with 1963 additions and 502 deletions
@@ -445,7 +445,7 @@ Variants {
color: bgRoot.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
animateChange: true
animateChange: Config.options.background.clock.digital.animateChange
}
component ClockStatusText: Row {
id: statusTextRow
@@ -50,9 +50,9 @@ Item {
print("[Cookie clock] Setting clock preset for category: " + category)
// "abstract", "anime", "city", "minimalist", "landscape", "plants", "person", "space"
if (category == "abstract") {
applyStyle(10, "dots", "fill", "medium", "dot", "bubble")
applyStyle(9, "none", "fill", "medium", "dot", "bubble")
} else if (category == "anime") {
applyStyle(12, "dots", "fill", "bold", "dot", "bubble")
applyStyle(7, "none", "fill", "bold", "dot", "bubble")
} else if (category == "city" || category == "space") {
applyStyle(23, "full", "hollow", "thin", "classic", "bubble")
} else if (category == "minimalist") {
@@ -83,121 +83,141 @@ Item {
}
}
property bool useSineCookie: Config.options.background.clock.cookie.useSineCookie
DropShadow {
source: cookie
source: useSineCookie ? sineCookieLoader : roundedPolygonCookieLoader
anchors.fill: source
horizontalOffset: 0
verticalOffset: 1
radius: 8
samples: radius * 2 + 1
color: root.colShadow
transparentBorder: true
RotationAnimation on rotation {
running: Config.options.background.clock.cookie.constantlyRotate
duration: 30000
easing.type: Easing.Linear
loops: Animation.Infinite
from: 360
to: 0
}
}
Loader {
id: sineCookieLoader
z: 0
visible: false // The DropShadow already draws it
active: useSineCookie
sourceComponent: SineCookie {
implicitSize: root.implicitSize
sides: Config.options.background.clock.cookie.sides
color: root.colBackground
}
}
Loader {
id: roundedPolygonCookieLoader
z: 0
visible: false // The DropShadow already draws it
active: !useSineCookie
sourceComponent: MaterialCookie {
implicitSize: root.implicitSize
sides: Config.options.background.clock.cookie.sides
color: root.colBackground
}
}
MaterialCookie {
id: cookie
z: 0
implicitSize: root.implicitSize
amplitude: implicitSize / 70
sides: Config.options.background.clock.cookie.sides
color: root.colBackground
constantlyRotate: Config.options.background.clock.cookie.constantlyRotate
// Hour/minutes numbers/dots/lines
MinuteMarks {
anchors.fill: parent
// Hour/minutes numbers/dots/lines
MinuteMarks {
anchors.fill: parent
color: root.colOnBackground
}
// Stupid extra hour marks in the middle
FadeLoader {
id: hourMarksLoader
anchors.centerIn: parent
shown: Config.options.background.clock.cookie.hourMarks
sourceComponent: HourMarks {
implicitSize: 135 * (1.75 - 0.75 * hourMarksLoader.opacity)
color: root.colOnBackground
colOnBackground: ColorUtils.mix(root.colBackgroundInfo, root.colOnBackground, 0.5)
}
}
// Number column in the middle
FadeLoader {
id: timeColumnLoader
anchors.centerIn: parent
shown: Config.options.background.clock.cookie.timeIndicators
scale: 1.4 - 0.4 * timeColumnLoader.shown
Behavior on scale {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
// Stupid extra hour marks in the middle
FadeLoader {
id: hourMarksLoader
anchors.centerIn: parent
shown: Config.options.background.clock.cookie.hourMarks
sourceComponent: HourMarks {
implicitSize: 135 * (1.75 - 0.75 * hourMarksLoader.opacity)
color: root.colOnBackground
colOnBackground: ColorUtils.mix(root.colBackgroundInfo, root.colOnBackground, 0.5)
}
sourceComponent: TimeColumn {
color: root.colBackgroundInfo
}
}
// Number column in the middle
FadeLoader {
id: timeColumnLoader
anchors.centerIn: parent
shown: Config.options.background.clock.cookie.timeIndicators
scale: 1.4 - 0.4 * timeColumnLoader.shown
Behavior on scale {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
sourceComponent: TimeColumn {
color: root.colBackgroundInfo
}
// Hour hand
FadeLoader {
anchors.fill: parent
z: 1
shown: Config.options.background.clock.cookie.hourHandStyle !== "hide"
sourceComponent: HourHand {
clockHour: root.clockHour
clockMinute: root.clockMinute
style: Config.options.background.clock.cookie.hourHandStyle
color: root.colHourHand
}
}
// Hour hand
FadeLoader {
// Minute hand
FadeLoader {
anchors.fill: parent
z: 2
shown: Config.options.background.clock.cookie.minuteHandStyle !== "hide"
sourceComponent: MinuteHand {
anchors.fill: parent
z: 1
shown: Config.options.background.clock.cookie.hourHandStyle !== "hide"
sourceComponent: HourHand {
clockHour: root.clockHour
clockMinute: root.clockMinute
style: Config.options.background.clock.cookie.hourHandStyle
color: root.colHourHand
}
clockMinute: root.clockMinute
style: Config.options.background.clock.cookie.minuteHandStyle
color: root.colMinuteHand
}
}
// Minute hand
FadeLoader {
anchors.fill: parent
z: 2
shown: Config.options.background.clock.cookie.minuteHandStyle !== "hide"
sourceComponent: MinuteHand {
anchors.fill: parent
clockMinute: root.clockMinute
style: Config.options.background.clock.cookie.minuteHandStyle
color: root.colMinuteHand
}
// Second hand
FadeLoader {
id: secondHandLoader
z: (Config.options.background.clock.cookie.secondHandStyle === "line") ? 2 : 3
shown: Config.options.time.secondPrecision && Config.options.background.clock.cookie.secondHandStyle !== "hide"
anchors.fill: parent
sourceComponent: SecondHand {
id: secondHand
clockSecond: root.clockSecond
style: Config.options.background.clock.cookie.secondHandStyle
color: root.colSecondHand
}
}
// Second hand
FadeLoader {
id: secondHandLoader
z: (Config.options.background.clock.cookie.secondHandStyle === "line") ? 2 : 3
shown: Config.options.time.secondPrecision && Config.options.background.clock.cookie.secondHandStyle !== "hide"
anchors.fill: parent
sourceComponent: SecondHand {
id: secondHand
clockSecond: root.clockSecond
style: Config.options.background.clock.cookie.secondHandStyle
color: root.colSecondHand
}
// Center dot
FadeLoader {
z: 4
anchors.centerIn: parent
shown: Config.options.background.clock.cookie.minuteHandStyle !== "bold"
sourceComponent: Rectangle {
color: Config.options.background.clock.cookie.minuteHandStyle === "medium" ? root.colBackground : root.colMinuteHand
implicitWidth: 6
implicitHeight: implicitWidth
radius: width / 2
}
}
// Center dot
FadeLoader {
z: 4
anchors.centerIn: parent
shown: Config.options.background.clock.cookie.minuteHandStyle !== "bold"
sourceComponent: Rectangle {
color: Config.options.background.clock.cookie.minuteHandStyle === "medium" ? root.colBackground : root.colMinuteHand
implicitWidth: 6
implicitHeight: implicitWidth
radius: width / 2
}
}
// Date
FadeLoader {
anchors.fill: parent
shown: Config.options.background.clock.cookie.dateStyle !== "hide"
// Date
FadeLoader {
anchors.fill: parent
shown: Config.options.background.clock.cookie.dateStyle !== "hide"
sourceComponent: DateIndicator {
color: root.colBackgroundInfo
style: Config.options.background.clock.cookie.dateStyle
}
sourceComponent: DateIndicator {
color: root.colBackgroundInfo
style: Config.options.background.clock.cookie.dateStyle
}
}
}
@@ -9,7 +9,7 @@ Item {
required property int clockHour
required property int clockMinute
property real handLength: 72
property real handWidth: 18
property real handWidth: 20
property string style: "fill"
property color color: Appearance.colors.colPrimary
@@ -19,6 +19,14 @@ Item {
}
rotation: -90 + (360 / 12) * (root.clockHour + root.clockMinute / 60)
Behavior on rotation {
animation: RotationAnimation {
direction: RotationAnimation.Clockwise
duration: 300
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.emphasized
}
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
@@ -10,13 +10,17 @@ Item {
required property int clockMinute
property string style: "medium"
property real handLength: 95
property real handWidth: style === "bold" ? 18 : style === "medium" ? 12 : 5
property real handWidth: style === "bold" ? 20 : style === "medium" ? 12 : 5
property color color: Appearance.colors.colSecondary
rotation: -90 + (360 / 60) * root.clockMinute
Behavior on rotation {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
animation: RotationAnimation {
direction: RotationAnimation.Clockwise
duration: 300
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.emphasized
}
}
Rectangle {
@@ -10,7 +10,7 @@ Item {
required property int clockSecond
property real handWidth: 2
property real handLength: 100
property real handLength: 95
property real dotSize: 20
property string style: "hide"
property color color: Appearance.colors.colSecondary
@@ -19,7 +19,8 @@ Item {
Behavior on rotation {
enabled: Config.options.background.clock.cookie.constantlyRotate // Animating every second is expensive...
animation: NumberAnimation {
animation: RotationAnimation {
direction: RotationAnimation.Clockwise
duration: 1000 // 1 second
easing.type: Easing.InOutQuad
}
@@ -29,7 +30,7 @@ Item {
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: 10
leftMargin: 10 + (root.style === "dot" ? root.dotSize : 0)
}
implicitWidth: root.style === "dot" ? root.dotSize : root.handLength
implicitHeight: root.style === "dot" ? root.dotSize : root.handWidth
@@ -14,7 +14,10 @@ Item {
// 12 Dots
FadeLoader {
id: dotsLoader
anchors.fill: parent
anchors {
fill: parent
margins: 10
}
shown: root.style === "dots"
sourceComponent: Dots {
color: root.color
@@ -37,7 +40,10 @@ Item {
// Lines
FadeLoader {
id: linesLoader
anchors.fill: parent
anchors {
fill: parent
margins: 10
}
shown: root.style === "full"
sourceComponent: Lines {
color: root.color
@@ -5,6 +5,7 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Qt.labs.synchronizer
import Quickshell.Io
import Quickshell
import Quickshell.Wayland
@@ -22,7 +23,6 @@ Scope { // Scope
"name": Translation.tr("Elements")
},
]
property int selectedTab: 0
Loader {
id: cheatsheetLoader
@@ -31,6 +31,7 @@ Scope { // Scope
sourceComponent: PanelWindow { // Window
id: cheatsheetRoot
visible: cheatsheetLoader.active
property int selectedTab: 0
anchors {
top: true
@@ -85,16 +86,16 @@ Scope { // Scope
}
if (event.modifiers === Qt.ControlModifier) {
if (event.key === Qt.Key_PageDown) {
root.selectedTab = Math.min(root.selectedTab + 1, root.tabButtonList.length - 1);
cheatsheetRoot.selectedTab = Math.min(cheatsheetRoot.selectedTab + 1, root.tabButtonList.length - 1);
event.accepted = true;
} else if (event.key === Qt.Key_PageUp) {
root.selectedTab = Math.max(root.selectedTab - 1, 0);
cheatsheetRoot.selectedTab = Math.max(cheatsheetRoot.selectedTab - 1, 0);
event.accepted = true;
} else if (event.key === Qt.Key_Tab) {
root.selectedTab = (root.selectedTab + 1) % root.tabButtonList.length;
cheatsheetRoot.selectedTab = (cheatsheetRoot.selectedTab + 1) % root.tabButtonList.length;
event.accepted = true;
} else if (event.key === Qt.Key_Backtab) {
root.selectedTab = (root.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length;
cheatsheetRoot.selectedTab = (cheatsheetRoot.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length;
event.accepted = true;
}
}
@@ -140,9 +141,8 @@ Scope { // Scope
PrimaryTabBar { // Tab strip
id: tabBar
tabButtonList: root.tabButtonList
externalTrackedTab: root.selectedTab
function onCurrentIndexChanged(currentIndex) {
root.selectedTab = currentIndex;
Synchronizer on currentIndex {
property alias source: cheatsheetRoot.selectedTab
}
}
@@ -164,12 +164,12 @@ Scope { // Scope
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
currentIndex: tabBar.externalTrackedTab
currentIndex: cheatsheetRoot.selectedTab
onCurrentIndexChanged: {
contentWidthBehavior.enabled = true;
contentHeightBehavior.enabled = true;
tabBar.enableIndicatorAnimation = true;
root.selectedTab = currentIndex;
cheatsheetRoot.selectedTab = currentIndex;
}
clip: true
@@ -164,6 +164,10 @@ Singleton {
property bool hourMarks: false
property bool dateInClock: true
property bool constantlyRotate: false
property bool useSineCookie: false
}
property JsonObject digital: JsonObject {
property bool animateChange: true
}
}
@@ -324,6 +328,7 @@ Singleton {
property bool unlockKeyring: true
property bool requirePasswordToPower: false
}
property bool materialShapeChars: true
}
property JsonObject media: JsonObject {
@@ -2,7 +2,7 @@ pragma Singleton
pragma ComponentBehavior: Bound
import qs.modules.common.functions
import Qt.labs.platform
import QtCore
import QtQuick
import Quickshell
@@ -2,69 +2,38 @@ import QtQuick
import QtQuick.Shapes
import Quickshell
import qs.modules.common
import qs.modules.common.widgets.shapes
import "shapes/geometry/offset.js" as Offset
import "shapes/shapes/corner-rounding.js" as CornerRounding
import "shapes/shapes/rounded-polygon.js" as RoundedPolygon
import "shapes/material-shapes.js" as MaterialShapes
Item {
id: root
property real sides: 12
property int sides: 12
property int implicitSize: 100
property real amplitude: implicitSize / 50
property int renderPoints: 360
property color color: "#605790"
property alias strokeWidth: shapePath.strokeWidth
property bool constantlyRotate: false
property alias color: shapeCanvas.color
implicitWidth: implicitSize
implicitHeight: implicitSize
property real shapeRotation: 0
property var cornerRounding: new CornerRounding.CornerRounding((sides < 17 ? 1.5 : 1.1) / Math.max(sides, 1))
Loader {
active: constantlyRotate
sourceComponent: FrameAnimation {
running: true
onTriggered: {
shapeRotation += 0.05
}
}
}
Behavior on sides {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Shape {
id: shape
ShapeCanvas {
id: shapeCanvas
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
ShapePath {
id: shapePath
strokeWidth: 0
fillColor: root.color
pathHints: ShapePath.PathSolid & ShapePath.PathNonIntersecting
PathPolyline {
property var pointsList: {
var points = []
var cx = shape.width / 2 // center x
var cy = shape.height / 2 // center y
var steps = root.renderPoints
var radius = root.implicitSize / 2 - root.amplitude
for (var i = 0; i <= steps; i++) {
var angle = (i / steps) * 2 * Math.PI
var rotatedAngle = angle * root.sides + Math.PI/2 + (root.shapeRotation * root.constantlyRotate)
var wave = Math.sin(rotatedAngle) * root.amplitude
var x = Math.cos(angle) * (radius + wave) + cx
var y = Math.sin(angle) * (radius + wave) + cy
points.push(Qt.point(x, y))
}
return points
}
path: pointsList
}
roundedPolygon: switch(sides) {
case 0: return MaterialShapes.getCircle();
case 1: return MaterialShapes.getCircle();
case 4: return MaterialShapes.getCookie4Sided();
case 6: return MaterialShapes.getCookie6Sided();
case 7: return MaterialShapes.getCookie7Sided();
case 9: return MaterialShapes.getCookie9Sided();
case 12: return MaterialShapes.getCookie12Sided();
default: return RoundedPolygon.RoundedPolygon.star(sides, 1, 0.8, root.cornerRounding)
.transformed((x, y) => MaterialShapes.rotate30.map(new Offset.Offset(x, y)))
.normalized();
}
}
}
@@ -44,6 +44,7 @@ ShapeCanvas {
property double implicitSize
implicitHeight: implicitSize
implicitWidth: implicitSize
polygonIsNormalized: true
roundedPolygon: {
switch (root.shape) {
case MaterialShape.Shape.Circle: return MaterialShapes.getCircle();
@@ -3,16 +3,20 @@ import qs.services
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt.labs.synchronizer
ColumnLayout {
id: root
spacing: 0
required property var tabButtonList // Something like [{"icon": "notifications", "name": Translation.tr("Notifications")}, {"icon": "volume_up", "name": Translation.tr("Volume mixer")}]
required property var externalTrackedTab
property int currentIndex
property bool enableIndicatorAnimation: false
property color colIndicator: Appearance?.colors.colPrimary ?? "#65558F"
property color colBorder: Appearance?.m3colors.m3outlineVariant ?? "#C6C6D0"
signal currentIndexChanged(int index)
onCurrentIndexChanged: {
enableIndicatorAnimation = true
}
property bool centerTabBar: parent.width > 500
Layout.fillWidth: !centerTabBar
@@ -22,9 +26,8 @@ ColumnLayout {
TabBar {
id: tabBar
Layout.fillWidth: true
currentIndex: root.externalTrackedTab
onCurrentIndexChanged: {
root.onCurrentIndexChanged(currentIndex)
Synchronizer on currentIndex {
property alias source: root.currentIndex
}
background: Item {
@@ -42,10 +45,11 @@ ColumnLayout {
Repeater {
model: root.tabButtonList
delegate: PrimaryTabButton {
selected: (index == root.externalTrackedTab)
selected: (index == root.currentIndex)
buttonText: modelData.name
buttonIcon: modelData.icon
minimumWidth: 160
onClicked: root.currentIndex = index
}
}
}
@@ -54,12 +58,6 @@ ColumnLayout {
id: tabIndicator
Layout.fillWidth: true
height: 3
Connections {
target: root
function onExternalTrackedTabChanged() {
root.enableIndicatorAnimation = true
}
}
Rectangle {
id: indicator
@@ -0,0 +1,70 @@
import QtQuick
import QtQuick.Shapes
import Quickshell
import qs.modules.common
Item {
id: root
property real sides: 12
property int implicitSize: 100
property real amplitude: implicitSize / 50
property int renderPoints: 360
property color color: "#605790"
property alias strokeWidth: shapePath.strokeWidth
property bool constantlyRotate: false
implicitWidth: implicitSize
implicitHeight: implicitSize
property real shapeRotation: 0
Loader {
active: constantlyRotate
sourceComponent: FrameAnimation {
running: true
onTriggered: {
shapeRotation += 0.05
}
}
}
Behavior on sides {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Shape {
id: shape
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
ShapePath {
id: shapePath
strokeWidth: 0
fillColor: root.color
pathHints: ShapePath.PathSolid & ShapePath.PathNonIntersecting
PathPolyline {
property var pointsList: {
var points = []
var cx = shape.width / 2 // center x
var cy = shape.height / 2 // center y
var steps = root.renderPoints
var radius = root.implicitSize / 2 - root.amplitude
for (var i = 0; i <= steps; i++) {
var angle = (i / steps) * 2 * Math.PI
var rotatedAngle = angle * root.sides + Math.PI/2 + (root.shapeRotation * root.constantlyRotate)
var wave = Math.sin(rotatedAngle) * root.amplitude
var x = Math.cos(angle) * (radius + wave) + cx
var y = Math.sin(angle) * (radius + wave) + cy
points.push(Qt.point(x, y))
}
return points
}
path: pointsList
}
}
}
}
@@ -45,13 +45,25 @@ Switch {
anchors.leftMargin: root.checked ? ((root.pressed || root.down) ? (22 * root.scale) : 24 * root.scale) : ((root.pressed || root.down) ? (2 * root.scale) : 8 * root.scale)
Behavior on anchors.leftMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
NumberAnimation {
duration: Appearance.animationCurves.expressiveFastSpatialDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
}
Behavior on width {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
NumberAnimation {
duration: Appearance.animationCurves.expressiveFastSpatialDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
}
Behavior on height {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
NumberAnimation {
duration: Appearance.animationCurves.expressiveFastSpatialDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
}
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
+104 -78
View File
@@ -1,4 +1,6 @@
pragma ComponentBehavior: Bound
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.lock
@@ -9,9 +11,9 @@ import Quickshell.Wayland
import Quickshell.Hyprland
Scope {
id: root
id: root
function unlockKeyring() {
function unlockKeyring() {
Quickshell.execDetached({
environment: ({
UNLOCK_PASSWORD: root.currentText
@@ -20,117 +22,141 @@ Scope {
})
}
// This stores all the information shared between the lock surfaces on each screen.
// https://github.com/quickshell-mirror/quickshell-examples/tree/master/lockscreen
LockContext {
id: lockContext
property var windowData: []
function saveWindowPositionAndTile() {
Hyprland.dispatch(`keyword dwindle:pseudotile true`)
root.windowData = HyprlandData.windowList.filter(w => (w.floating && w.workspace.id === HyprlandData.activeWorkspace.id))
root.windowData.forEach(w => {
Hyprland.dispatch(`pseudo address:${w.address}`)
Hyprland.dispatch(`settiled address:${w.address}`)
Hyprland.dispatch(`movetoworkspacesilent ${w.workspace.id},address:${w.address}`)
})
}
function restoreWindowPositionAndTile() {
root.windowData.forEach(w => {
Hyprland.dispatch(`setfloating address:${w.address}`)
Hyprland.dispatch(`movewindowpixel exact ${w.at[0]} ${w.at[1]}, address:${w.address}`)
Hyprland.dispatch(`pseudo address:${w.address}`)
})
Hyprland.dispatch(`keyword dwindle:pseudotile false`)
}
Connections {
target: GlobalStates
function onScreenLockedChanged() {
if (GlobalStates.screenLocked) lockContext.reset();
}
}
// This stores all the information shared between the lock surfaces on each screen.
// https://github.com/quickshell-mirror/quickshell-examples/tree/master/lockscreen
LockContext {
id: lockContext
onUnlocked: (targetAction) => {
// Perform the target action if it's not just unlocking
if (targetAction == LockContext.ActionEnum.Poweroff) {
Session.poweroff();
return;
} else if (targetAction == LockContext.ActionEnum.Reboot) {
Session.reboot();
return;
}
Connections {
target: GlobalStates
function onScreenLockedChanged() {
if (GlobalStates.screenLocked) {
lockContext.reset();
lockContext.tryFingerUnlock();
}
}
}
// Unlock the keyring if configured to do so
if (Config.options.lock.security.unlockKeyring) root.unlockKeyring();
onUnlocked: (targetAction) => {
// Perform the target action if it's not just unlocking
if (targetAction == LockContext.ActionEnum.Poweroff) {
Session.poweroff();
return;
} else if (targetAction == LockContext.ActionEnum.Reboot) {
Session.reboot();
return;
}
// Unlock the screen before exiting, or the compositor will display a
// fallback lock you can't interact with.
GlobalStates.screenLocked = false;
// Refocus last focused window on unlock (hack)
Quickshell.execDetached(["bash", "-c", `sleep 0.2; hyprctl --batch "dispatch togglespecialworkspace; dispatch togglespecialworkspace"`])
// Unlock the keyring if configured to do so
if (Config.options.lock.security.unlockKeyring) root.unlockKeyring();
// Unlock the screen before exiting, or the compositor will display a
// fallback lock you can't interact with.
GlobalStates.screenLocked = false;
// Refocus last focused window on unlock (hack)
Quickshell.execDetached(["bash", "-c", `sleep 0.2; hyprctl --batch "dispatch togglespecialworkspace; dispatch togglespecialworkspace"`])
// Reset
lockContext.reset();
}
}
}
}
WlSessionLock {
id: lock
locked: GlobalStates.screenLocked
WlSessionLock {
id: lock
locked: GlobalStates.screenLocked
WlSessionLockSurface {
color: "transparent"
Loader {
active: GlobalStates.screenLocked
anchors.fill: parent
opacity: active ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
sourceComponent: LockSurface {
context: lockContext
}
}
}
}
WlSessionLockSurface {
color: "transparent"
Loader {
active: GlobalStates.screenLocked
anchors.fill: parent
opacity: active ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
sourceComponent: LockSurface {
context: lockContext
}
}
}
}
// Blur layer hack
Variants {
// Blur layer hack
Variants {
model: Quickshell.screens
delegate: Scope {
required property ShellScreen modelData
property bool shouldPush: GlobalStates.screenLocked
property string targetMonitorName: modelData.name
property int verticalMovementDistance: modelData.height
property int horizontalSqueeze: modelData.width * 0.2
onShouldPushChanged: {
if (shouldPush) {
Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, ${verticalMovementDistance}, ${-verticalMovementDistance}, ${horizontalSqueeze}, ${horizontalSqueeze}`])
} else {
Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, 0, 0, 0, 0`])
}
}
}
}
delegate: Scope {
required property ShellScreen modelData
property bool shouldPush: GlobalStates.screenLocked
property string targetMonitorName: modelData.name
property int verticalMovementDistance: modelData.height
property int horizontalSqueeze: modelData.width * 0.2
onShouldPushChanged: {
if (shouldPush) {
root.saveWindowPositionAndTile();
Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, ${verticalMovementDistance}, ${-verticalMovementDistance}, ${horizontalSqueeze}, ${horizontalSqueeze}`])
} else {
Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, 0, 0, 0, 0`])
root.restoreWindowPositionAndTile();
}
}
}
}
IpcHandler {
IpcHandler {
target: "lock"
function activate(): void {
GlobalStates.screenLocked = true;
}
function focus(): void {
lockContext.shouldReFocus();
}
function focus(): void {
lockContext.shouldReFocus();
}
}
GlobalShortcut {
GlobalShortcut {
name: "lock"
description: "Locks the screen"
onPressed: {
if (Config.options.lock.useHyprlock) {
Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]);
return;
}
if (Config.options.lock.useHyprlock) {
Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]);
return;
}
GlobalStates.screenLocked = true;
}
}
GlobalShortcut {
GlobalShortcut {
name: "lockFocus"
description: "Re-focuses the lock screen. This is because Hyprland after waking up for whatever reason"
+ "decides to keyboard-unfocus the lock screen"
+ "decides to keyboard-unfocus the lock screen"
onPressed: {
lockContext.shouldReFocus();
}
}
Connections {
Connections {
target: Config
function onReadyChanged() {
if (Config.options.lock.launchOnStartup && Config.ready && Persistent.ready && Persistent.isNewHyprlandInstance) {
@@ -2,6 +2,7 @@ import qs
import qs.modules.common
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.Pam
Scope {
@@ -18,6 +19,7 @@ Scope {
property string currentText: ""
property bool unlockInProgress: false
property bool showFailure: false
property bool fingerprintsConfigured: false
property var targetAction: LockContext.ActionEnum.Unlock
function resetTargetAction() {
@@ -60,6 +62,34 @@ Scope {
pam.start();
}
function tryFingerUnlock() {
if (root.fingerprintsConfigured) {
fingerPam.start();
}
}
function stopFingerPam() {
fingerPam.abort();
}
Process {
id: fingerprintCheckProc
running: true
command: ["bash", "-c", "fprintd-list $(whoami)"]
stdout: StdioCollector {
id: fingerprintOutputCollector
onStreamFinished: {
root.fingerprintsConfigured = fingerprintOutputCollector.text.includes("Fingerprints for user");
}
}
onExited: (exitCode, exitStatus) => {
if (exitCode !== 0) {
console.warn("fprintd-list command exited with error:", exitCode, exitStatus);
root.fingerprintsConfigured = false;
}
}
}
PamContext {
id: pam
@@ -74,6 +104,7 @@ Scope {
onCompleted: result => {
if (result == PamResult.Success) {
root.unlocked(root.targetAction);
stopFingerPam();
} else {
root.clearText();
root.unlockInProgress = false;
@@ -83,4 +114,19 @@ Scope {
}
}
PamContext {
id: fingerPam
configDirectory: "pam"
config: "fprintd.conf"
onCompleted: result => {
if (result == PamResult.Success) {
root.unlocked(root.targetAction);
stopFingerPam();
} else if (result == PamResult.Error){ // if timeout or etc..
tryFingerUnlock()
}
}
}
}
@@ -100,6 +100,23 @@ MouseArea {
scale: root.toolbarScale
opacity: root.toolbarOpacity
// Fingerprint
Loader {
Layout.leftMargin: 10
Layout.rightMargin: 6
Layout.alignment: Qt.AlignVCenter
active: root.context.fingerprintsConfigured
visible: active
sourceComponent: MaterialSymbol {
id: fingerprintIcon
fill: 1
text: "fingerprint"
iconSize: Appearance.font.pixelSize.hugeass
color: Appearance.colors.colOnSurfaceVariant
}
}
ToolbarTextField {
id: passwordBox
placeholderText: GlobalStates.screenUnlockFailed ? Translation.tr("Incorrect password") : Translation.tr("Enter password")
@@ -126,7 +143,7 @@ MouseArea {
Keys.onPressed: event => {
root.context.resetClearTimer();
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
@@ -136,15 +153,35 @@ MouseArea {
}
}
// Shake when wrong password
SequentialAnimation {
id: wrongPasswordShakeAnim
NumberAnimation { target: passwordBox; property: "x"; to: -30; duration: 50 }
NumberAnimation { target: passwordBox; property: "x"; to: 30; duration: 50 }
NumberAnimation { target: passwordBox; property: "x"; to: -15; duration: 40 }
NumberAnimation { target: passwordBox; property: "x"; to: 15; duration: 40 }
NumberAnimation { target: passwordBox; property: "x"; to: 0; duration: 30 }
}
Connections {
target: GlobalStates
function onScreenUnlockFailedChanged() {
if (GlobalStates.screenUnlockFailed) wrongPasswordShakeAnim.restart();
}
}
// We're drawing dots manually
color: ColorUtils.transparentize(Appearance.colors.colOnLayer1)
PasswordChars {
property bool materialShapeChars: Config.options.lock.materialShapeChars
color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, materialShapeChars ? 1 : 0)
Loader {
active: passwordBox.materialShapeChars
anchors {
fill: parent
leftMargin: passwordBox.padding
rightMargin: passwordBox.padding
}
length: root.context.currentText.length
sourceComponent: PasswordChars {
length: root.context.currentText.length
}
}
}
@@ -15,7 +15,6 @@ StyledFlickable {
Behavior on contentX {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
rightMargin: 14
Row {
id: dotsRow
anchors {
@@ -0,0 +1 @@
auth sufficient pam_fprintd.so
@@ -21,7 +21,7 @@ Scope {
readonly property real osdWidth: Appearance.sizes.osdWidth
readonly property real widgetWidth: Appearance.sizes.mediaControlsWidth
readonly property real widgetHeight: Appearance.sizes.mediaControlsHeight
property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1
property list<real> visualizerPoints: []
property bool hasPlasmaIntegration: false
@@ -23,13 +23,25 @@ Toolbar {
// Signals
signal dismiss()
MaterialCookie {
MaterialShape {
Layout.fillHeight: true
Layout.leftMargin: 2
Layout.rightMargin: 2
implicitSize: 36 // Intentionally smaller because this one is brighter than others
sides: 10
amplitude: implicitSize / 44
shape: switch (root.action) {
case RegionSelection.SnipAction.Copy:
case RegionSelection.SnipAction.Edit:
return MaterialShape.Shape.Cookie4Sided;
case RegionSelection.SnipAction.Search:
return MaterialShape.Shape.Pentagon;
case RegionSelection.SnipAction.CharRecognition:
return MaterialShape.Shape.Sunny;
case RegionSelection.SnipAction.Record:
case RegionSelection.SnipAction.RecordWithSound:
return MaterialShape.Shape.Gem;
default:
return MaterialShape.Shape.Cookie12Sided;
}
color: Appearance.colors.colPrimary
MaterialSymbol {
anchors.centerIn: parent
@@ -5,11 +5,11 @@ import qs.modules.common.widgets
import qs.services
import QtQuick
import QtQuick.Controls
import Qt.labs.synchronizer
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
import Qt.labs.synchronizer
PanelWindow {
id: root
@@ -55,6 +55,20 @@ ContentPage {
}
}
ContentSubsection {
visible: Config.options.background.clock.style === "digital"
title: Translation.tr("Digital clock settings")
ConfigSwitch {
buttonIcon: "animation"
text: Translation.tr("Animate time change")
checked: Config.options.background.clock.digital.animateChange
onCheckedChanged: {
Config.options.background.clock.digital.animateChange = checked;
}
}
}
ContentSubsection {
visible: Config.options.background.clock.style === "cookie"
title: Translation.tr("Cookie clock settings")
@@ -71,6 +85,18 @@ ContentPage {
}
}
ConfigSwitch {
buttonIcon: "airwave"
text: Translation.tr("Use old sine wave cookie implementation")
checked: Config.options.background.clock.cookie.useSineCookie
onCheckedChanged: {
Config.options.background.clock.cookie.useSineCookie = checked;
}
StyledToolTip {
text: "Looks a bit softer and more consistent with different number of sides,\nbut has less impressive morphing"
}
}
ConfigSpinBox {
icon: "add_triangle"
text: Translation.tr("Sides")
@@ -530,6 +556,15 @@ ContentPage {
Config.options.lock.showLockedText = checked;
}
}
ConfigSwitch {
buttonIcon: "shapes"
text: Translation.tr('Use varying shapes for password characters')
checked: Config.options.lock.materialShapeChars
onCheckedChanged: {
Config.options.lock.materialShapeChars = checked;
}
}
}
ContentSubsection {
title: Translation.tr("Style: Blurred")
@@ -119,16 +119,17 @@ Item {
}
}
property real pageKeyScrollAmount: booruResponseListView.height / 2
Keys.onPressed: (event) => {
tagInputField.forceActiveFocus()
if (event.modifiers === Qt.NoModifier) {
if (event.key === Qt.Key_PageUp) {
if (booruResponseListView.atYBeginning) return;
booruResponseListView.contentY = Math.max(0, booruResponseListView.contentY - booruResponseListView.height / 2)
booruResponseListView.contentY = Math.max(0, booruResponseListView.contentY - root.pageKeyScrollAmount)
event.accepted = true
} else if (event.key === Qt.Key_PageDown) {
if (booruResponseListView.atYEnd) return;
booruResponseListView.contentY = Math.min(booruResponseListView.contentHeight - booruResponseListView.height / 2, booruResponseListView.contentY + booruResponseListView.height / 2)
booruResponseListView.contentY = Math.min(booruResponseListView.contentHeight, booruResponseListView.contentY + root.pageKeyScrollAmount)
event.accepted = true
}
}
@@ -171,17 +172,20 @@ Item {
mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4
property int lastResponseLength: 0
model: ScriptModel {
values: {
if(root.responses.length > booruResponseListView.lastResponseLength) {
Connections {
target: root
function onResponsesChanged() {
if (root.responses.length > booruResponseListView.lastResponseLength) {
if (booruResponseListView.lastResponseLength > 0 && root.responses[booruResponseListView.lastResponseLength].provider != "system")
booruResponseListView.contentY = booruResponseListView.contentY + root.scrollOnNewResponse
booruResponseListView.lastResponseLength = root.responses.length
}
return root.responses
}
}
model: ScriptModel {
values: root.responses
}
delegate: BooruResponse {
responseData: modelData
tagInputField: root.inputField
@@ -5,6 +5,7 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Qt.labs.synchronizer
Item {
id: root
@@ -58,9 +59,8 @@ Item {
id: tabBar
visible: root.tabButtonList.length > 1
tabButtonList: root.tabButtonList
externalTrackedTab: root.selectedTab
function onCurrentIndexChanged(currentIndex) {
root.selectedTab = currentIndex
Synchronizer on currentIndex {
property alias source: root.selectedTab
}
}
@@ -71,7 +71,7 @@ Item {
Layout.fillHeight: true
spacing: 10
currentIndex: tabBar.externalTrackedTab
currentIndex: root.selectedTab
onCurrentIndexChanged: {
tabBar.enableIndicatorAnimation = true
root.selectedTab = currentIndex
@@ -105,7 +105,7 @@ Rectangle {
anchors.right: parent.right
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 7
spacing: 12
Item {
Layout.alignment: Qt.AlignVCenter
@@ -177,6 +177,20 @@ Rectangle {
ButtonGroup {
spacing: 5
AiMessageControlButton {
id: regenButton
buttonIcon: "refresh"
visible: messageData?.role === 'assistant'
onClicked: {
Ai.regenerate(root.messageIndex)
}
StyledToolTip {
text: Translation.tr("Regenerate")
}
}
AiMessageControlButton {
id: copyButton
buttonIcon: activated ? "inventory" : "content_copy"
@@ -254,28 +268,50 @@ Rectangle {
spacing: 0
Repeater {
model: root.messageBlocks.length
delegate: Loader {
required property int index
property var thisBlock: root.messageBlocks[index]
Layout.fillWidth: true
// property var segment: thisBlock
property var segmentContent: thisBlock.content
property var segmentLang: thisBlock.lang
property var messageData: root.messageData
property var editing: root.editing
property var renderMarkdown: root.renderMarkdown
property var enableMouseSelection: root.enableMouseSelection
property bool thinking: root.messageData?.thinking ?? true
property bool done: root.messageData?.done ?? false
property bool completed: thisBlock.completed ?? false
model: ScriptModel {
values: Array.from({ length: root.messageBlocks.length }, (msg, i) => {
return ({
type: root.messageBlocks[i].type
})
});
}
property bool forceDisableChunkSplitting: root.messageData.content.includes("```")
source: thisBlock.type === "code" ? "MessageCodeBlock.qml" :
thisBlock.type === "think" ? "MessageThinkBlock.qml" :
"MessageTextBlock.qml"
delegate: DelegateChooser {
id: messageDelegate
role: "type"
DelegateChoice { roleValue: "code"; MessageCodeBlock {
required property int index
property var thisBlock: root.messageBlocks[index]
editing: root.editing
renderMarkdown: root.renderMarkdown
enableMouseSelection: root.enableMouseSelection
segmentContent: thisBlock.content
segmentLang: thisBlock.lang
messageData: root.messageData
} }
DelegateChoice { roleValue: "think"; MessageThinkBlock {
required property int index
property var thisBlock: root.messageBlocks[index]
editing: root.editing
renderMarkdown: root.renderMarkdown
enableMouseSelection: root.enableMouseSelection
segmentContent: thisBlock.content
messageData: root.messageData
done: root.messageData?.done ?? false
completed: thisBlock.completed ?? false
} }
DelegateChoice { roleValue: "text"; MessageTextBlock {
required property int index
property var thisBlock: root.messageBlocks[index]
editing: root.editing
renderMarkdown: root.renderMarkdown
enableMouseSelection: root.enableMouseSelection
segmentContent: thisBlock.content
messageData: root.messageData
done: root.messageData?.done ?? false
forceDisableChunkSplitting: root.messageData.content.includes("```")
} }
}
}
}
@@ -13,22 +13,20 @@ import org.kde.syntaxhighlighting
ColumnLayout {
id: root
// These are needed on the parent loader
property bool editing: parent?.editing ?? false
property bool renderMarkdown: parent?.renderMarkdown ?? true
property bool enableMouseSelection: parent?.enableMouseSelection ?? false
property var segmentContent: parent?.segmentContent ?? ({})
property var segmentLang: parent?.segmentLang ?? "txt"
property bool editing: false
property bool renderMarkdown: true
property bool enableMouseSelection: false
property var segmentContent: ({})
property var segmentLang: "txt"
property var messageData: {}
property bool isCommandRequest: segmentLang === "command"
property var displayLang: (isCommandRequest ? "bash" : segmentLang)
property var messageData: parent?.messageData ?? {}
property real codeBlockBackgroundRounding: Appearance.rounding.small
property real codeBlockHeaderPadding: 3
property real codeBlockComponentSpacing: 2
spacing: codeBlockComponentSpacing
anchors.left: parent.left
anchors.right: parent.right
Rectangle { // Code background
Layout.fillWidth: true
@@ -14,17 +14,17 @@ import Quickshell.Hyprland
ColumnLayout {
id: root
// These are needed on the parent loader
property bool editing: parent?.editing ?? false
property bool renderMarkdown: parent?.renderMarkdown ?? true
property bool enableMouseSelection: parent?.enableMouseSelection ?? false
property string segmentContent: parent?.segmentContent ?? ({})
property var messageData: parent?.messageData ?? {}
property bool done: parent?.done ?? true
property list<string> renderedLatexHashes: []
property bool editing: false
property bool renderMarkdown: true
property bool enableMouseSelection: false
property var segmentContent: ({})
property var messageData: {}
property bool done: true
property bool forceDisableChunkSplitting: false
property list<string> renderedLatexHashes: []
property string renderedSegmentContent: ""
property string shownText: ""
property bool forceDisableChunkSplitting: parent?.forceDisableChunkSplitting ?? false
property bool fadeChunkSplitting: !forceDisableChunkSplitting && !editing && !/\n\|/.test(shownText) && Config.options.sidebar.ai.textFadeIn
Layout.fillWidth: true
@@ -11,13 +11,13 @@ import Qt5Compat.GraphicalEffects
Item {
id: root
// These are needed on the parent loader
property bool editing: parent?.editing ?? false
property bool renderMarkdown: parent?.renderMarkdown ?? true
property bool enableMouseSelection: parent?.enableMouseSelection ?? false
property string segmentContent: parent?.segmentContent ?? ({})
property var messageData: parent?.messageData ?? {}
property bool done: parent?.done ?? true
property bool completed: parent?.completed ?? false
property bool editing: false
property bool renderMarkdown: true
property bool enableMouseSelection: false
property var segmentContent: ({})
property var messageData: {}
property bool done: true
property bool completed: false
property real thinkBlockBackgroundRounding: Appearance.rounding.small
property real thinkBlockHeaderPaddingVertical: 3
@@ -17,10 +17,10 @@ AndroidQuickToggleButton {
case PowerProfile.Performance: return "local_fire_department"
}
statusText: switch(PowerProfiles.profile) {
case PowerProfile.PowerSaver: return "Power Saver"
case PowerProfile.Balanced: return "Balanced"
case PowerProfile.Performance: return "Performance"
}
case PowerProfile.PowerSaver: return "Power Saver"
case PowerProfile.Balanced: return "Balanced"
case PowerProfile.Performance: return "Performance"
}
onClicked: (event) => {
if (PowerProfiles.hasPerformanceProfile) {
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
COLOR_FILE_PATH="${XDG_STATE_HOME:-$HOME/.local/state}/quickshell/user/generated/color.txt"
# Define an array of possible VSCode settings file paths for various forks
settings_paths=(
"${XDG_CONFIG_HOME:-$HOME/.config}/Code/User/settings.json"
"${XDG_CONFIG_HOME:-$HOME/.config}/VSCodium/User/settings.json"
"${XDG_CONFIG_HOME:-$HOME/.config}/Code - OSS/User/settings.json"
"${XDG_CONFIG_HOME:-$HOME/.config}/Code - Insiders/User/settings.json"
"${XDG_CONFIG_HOME:-$HOME/.config}/Cursor/User/settings.json"
# Add more paths as needed for other forks
)
new_color=$(cat "$COLOR_FILE_PATH")
# Loop through each settings file path
for CODE_SETTINGS_PATH in "${settings_paths[@]}"; do
if [[ -f "$CODE_SETTINGS_PATH" ]]; then
# Try to update the key if it exists
if grep -q '"material-code.primaryColor"' "$CODE_SETTINGS_PATH"; then
sed -i -E \
"s/(\"material-code.primaryColor\"\s*:\s*\")[^\"]*(\")/\1${new_color}\2/" \
"$CODE_SETTINGS_PATH"
else # If the key is not already there, add it
sed -i '$ s/}/,\n "material-code.primaryColor": "'${new_color}'"\n}/' "$CODE_SETTINGS_PATH"
sed -i '$ s/,\n,/,/' "$CODE_SETTINGS_PATH"
fi
fi
done
@@ -55,18 +55,8 @@ post_process() {
local screen_height="$2"
local wallpaper_path="$3"
handle_kde_material_you_colors &
# Determine the largest region on the wallpaper that's sufficiently un-busy to put widgets in
# if [ ! -f "$MATUGEN_DIR/scripts/least_busy_region.py" ]; then
# echo "Error: least_busy_region.py script not found in $MATUGEN_DIR/scripts/"
# else
# "$MATUGEN_DIR/scripts/least_busy_region.py" \
# --screen-width "$screen_width" --screen-height "$screen_height" \
# --width 300 --height 200 \
# "$wallpaper_path" > "$STATE_DIR"/user/generated/wallpaper/least_busy_region.json
# fi
"$SCRIPT_DIR/code/material-code-set-color.sh" &
}
check_and_prompt_upscale() {
@@ -769,6 +769,18 @@ Singleton {
root.pendingFilePath = CF.FileUtils.trimFileProtocol(filePath);
}
function regenerate(messageIndex) {
if (messageIndex < 0 || messageIndex >= messageIDs.length) return;
const id = root.messageIDs[messageIndex];
const message = root.messageByID[id];
if (message.role !== "assistant") return;
// Remove all messages after this one
for (let i = root.messageIDs.length - 1; i >= messageIndex; i--) {
root.removeMessage(i);
}
requester.makeRequest();
}
function createFunctionOutputMessage(name, output, includeOutputInChat = true) {
return aiMessageComponent.createObject(root, {
"role": "user",
+10 -7
View File
@@ -1,9 +1,9 @@
pragma Singleton
pragma ComponentBehavior: Bound
import qs.modules.common
import QtQuick
import Quickshell
import Quickshell.Services.Pipewire
pragma Singleton
pragma ComponentBehavior: Bound
/**
* A nice wrapper for default Pipewire audio sink and source.
@@ -29,12 +29,18 @@ Singleton {
property real lastVolume: 0
function onVolumeChanged() {
if (!Config.options.audio.protection.enable) return;
const newVolume = sink.audio.volume;
// when resuming from suspend, we should not write volume to avoid pipewire volume reset issues
if (isNaN(newVolume) || newVolume === undefined || newVolume === null) {
lastReady = false;
lastVolume = 0;
return;
}
if (!lastReady) {
lastVolume = sink.audio.volume;
lastVolume = newVolume;
lastReady = true;
return;
}
const newVolume = sink.audio.volume;
const maxAllowedIncrease = Config.options.audio.protection.maxAllowedIncrease / 100;
const maxAllowed = Config.options.audio.protection.maxAllowed / 100;
@@ -45,9 +51,6 @@ Singleton {
root.sinkProtectionTriggered(Translation.tr("Exceeded max allowed"));
sink.audio.volume = Math.min(lastVolume, maxAllowed);
}
if (sink.ready && (isNaN(sink.audio.volume) || sink.audio.volume === undefined || sink.audio.volume === null)) {
sink.audio.volume = 0;
}
lastVolume = sink.audio.volume;
}
}