2 Commits

Author SHA1 Message Date
Aram Markarov 7861ba5c51 cava reintroduced : uses libcava or cava for nix : needs nixos fixing, but could be isolated issue 2026-02-25 19:14:58 +01:00
Zacharias-Brohn 6faebc986d ideas 2026-02-25 17:30:25 +01:00
217 changed files with 13780 additions and 17609 deletions
-10
View File
@@ -1,10 +0,0 @@
[General]
FunctionsSpacing=true
IndentWidth=4
MaxColumnWidth=-1
NewlineType=native
NormalizeOrder=true
ObjectsSpacing=true
SemicolonRule=always
SortImports=false
UseTabs=true
+197
View File
@@ -0,0 +1,197 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.Daemons
import qs.Components
import qs.Modules
import qs.Modules.Bar
import qs.Config
import qs.Helpers
import qs.Drawers
Variants {
model: Quickshell.screens
Scope {
id: scope
required property var modelData
PanelWindow {
id: bar
property bool trayMenuVisible: false
screen: scope.modelData
color: "transparent"
property var root: Quickshell.shellDir
WlrLayershell.namespace: "ZShell-Bar"
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: visibilities.launcher || visibilities.sidebar || visibilities.dashboard ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
contentItem.focus: true
contentItem.Keys.onEscapePressed: {
if ( Config.barConfig.autoHide )
visibilities.bar = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
}
PanelWindow {
id: exclusionZone
WlrLayershell.namespace: "ZShell-Bar-Exclusion"
screen: bar.screen
WlrLayershell.layer: WlrLayer.Bottom
WlrLayershell.exclusionMode: Config.barConfig.autoHide ? ExclusionMode.Ignore : ExclusionMode.Auto
anchors {
left: true
right: true
top: true
}
color: "transparent"
implicitHeight: 34
}
anchors {
top: true
left: true
right: true
bottom: true
}
mask: Region {
id: region
x: 0
y: Config.barConfig.autoHide && !visibilities.bar ? 4 : 34
property list<Region> nullRegions: []
width: bar.width
height: bar.screen.height - backgroundRect.implicitHeight
intersection: Intersection.Xor
regions: popoutRegions.instances
}
Variants {
id: popoutRegions
model: panels.children
Region {
required property Item modelData
x: modelData.x
y: modelData.y + backgroundRect.implicitHeight
width: modelData.width
height: modelData.height
intersection: Intersection.Subtract
}
}
HyprlandFocusGrab {
id: focusGrab
active: visibilities.launcher || visibilities.sidebar || visibilities.dashboard || ( panels.popouts.hasCurrent && panels.popouts.currentName.startsWith( "traymenu" ))
windows: [bar]
onCleared: {
visibilities.launcher = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
panels.popouts.hasCurrent = false;
}
}
PersistentProperties {
id: visibilities
property bool sidebar
property bool dashboard
property bool bar
property bool osd
property bool launcher
property bool notif: NotifServer.popups.length > 0
Component.onCompleted: Visibilities.load(scope.modelData, this)
}
Binding {
target: visibilities
property: "bar"
value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || visibilities.notif
when: Config.barConfig.autoHide
}
Item {
anchors.fill: parent
opacity: Appearance.transparency.enabled ? DynamicColors.transparency.base : 1
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
blurMax: 32
shadowColor: Qt.alpha(DynamicColors.palette.m3shadow, 1)
}
Border {
bar: backgroundRect
visibilities: visibilities
}
Backgrounds {
visibilities: visibilities
panels: panels
bar: backgroundRect
}
}
Interactions {
id: mouseArea
screen: scope.modelData
popouts: panels.popouts
visibilities: visibilities
panels: panels
bar: barLoader
anchors.fill: parent
Panels {
id: panels
screen: scope.modelData
bar: backgroundRect
visibilities: visibilities
}
CustomRect {
id: backgroundRect
property Wrapper popouts: panels.popouts
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: 34
anchors.topMargin: Config.barConfig.autoHide && !visibilities.bar ? -30 : 0
color: "transparent"
radius: 0
Behavior on color {
CAnim {}
}
Behavior on anchors.topMargin {
Anim {}
}
BarLoader {
id: barLoader
anchors.fill: parent
popouts: panels.popouts
bar: bar
visibilities: visibilities
screen: scope.modelData
}
}
}
}
}
}
+1 -1
View File
@@ -30,5 +30,5 @@ if("shell" IN_LIST ENABLE_MODULES)
foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths) foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths)
install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}") install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}")
endforeach() endforeach()
install(FILES shell.qml DESTINATION "${INSTALL_QSCONFDIR}") install(FILES shell.qml Bar.qml Wallpaper.qml DESTINATION "${INSTALL_QSCONFDIR}")
endif() endif()
-8
View File
@@ -1,8 +0,0 @@
import QtQuick
import qs.Config
NumberAnimation {
duration: MaterialEasing.standardTime
easing.bezierCurve: MaterialEasing.standard
easing.type: Easing.BezierSpline
}
-8
View File
@@ -1,8 +0,0 @@
import QtQuick
import qs.Config
ColorAnimation {
duration: MaterialEasing.standardTime
easing.bezierCurve: MaterialEasing.standard
easing.type: Easing.BezierSpline
}
+90 -84
View File
@@ -1,102 +1,108 @@
import qs.Helpers
import qs.Config import qs.Config
import qs.Modules
import ZShell.Internal import ZShell.Internal
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
BusyIndicator { BusyIndicator {
id: root id: root
enum AnimState { enum AnimType {
Stopped, Advance = 0,
Running, Retreat
Completing }
}
enum AnimType {
Advance = 0,
Retreat
}
property int animState enum AnimState {
property color bgColour: DynamicColors.palette.m3secondaryContainer Stopped,
property color fgColour: DynamicColors.palette.m3primary Running,
property real implicitSize: Appearance.font.size.normal * 3 Completing
property real internalStrokeWidth: strokeWidth }
readonly property alias progress: manager.progress
property real strokeWidth: Appearance.padding.small * 0.8
property alias type: manager.indeterminateAnimationType
implicitHeight: implicitSize property real implicitSize: Appearance.font.size.normal * 3
implicitWidth: implicitSize property real strokeWidth: Appearance.padding.small * 0.8
padding: 0 property color fgColour: DynamicColors.palette.m3primary
property color bgColour: DynamicColors.palette.m3secondaryContainer
contentItem: CircularProgress { property alias type: manager.indeterminateAnimationType
anchors.fill: parent readonly property alias progress: manager.progress
bgColour: root.bgColour
fgColour: root.fgColour
padding: root.padding
rotation: manager.rotation
startAngle: manager.startFraction * 360
strokeWidth: root.internalStrokeWidth
value: manager.endFraction - manager.startFraction
}
states: State {
name: "stopped"
when: !root.running
PropertyChanges { property real internalStrokeWidth: strokeWidth
root.internalStrokeWidth: root.strokeWidth / 3 property int animState
root.opacity: 0
}
}
transitions: Transition {
Anim {
duration: manager.completeEndDuration * Appearance.anim.durations.scale
properties: "opacity,internalStrokeWidth"
}
}
Component.onCompleted: { padding: 0
if (running) { implicitWidth: implicitSize
running = false; implicitHeight: implicitSize
running = true;
}
}
onRunningChanged: {
if (running) {
manager.completeEndProgress = 0;
animState = CircularIndicator.Running;
} else {
if (animState == CircularIndicator.Running)
animState = CircularIndicator.Completing;
}
}
CircularIndicatorManager { Component.onCompleted: {
id: manager if (running) {
running = false;
running = true;
}
}
} onRunningChanged: {
if (running) {
manager.completeEndProgress = 0;
animState = CircularIndicator.Running;
} else {
if (animState == CircularIndicator.Running)
animState = CircularIndicator.Completing;
}
}
NumberAnimation { states: State {
duration: manager.duration * Appearance.anim.durations.scale name: "stopped"
from: 0 when: !root.running
loops: Animation.Infinite
property: "progress"
running: root.animState !== CircularIndicator.Stopped
target: manager
to: 1
}
NumberAnimation { PropertyChanges {
duration: manager.completeEndDuration * Appearance.anim.durations.scale root.opacity: 0
from: 0 root.internalStrokeWidth: root.strokeWidth / 3
property: "completeEndProgress" }
running: root.animState === CircularIndicator.Completing }
target: manager
to: 1
onFinished: { transitions: Transition {
if (root.animState === CircularIndicator.Completing) Anim {
root.animState = CircularIndicator.Stopped; properties: "opacity,internalStrokeWidth"
} duration: manager.completeEndDuration * Appearance.anim.durations.scale
} }
}
contentItem: CircularProgress {
anchors.fill: parent
strokeWidth: root.internalStrokeWidth
fgColour: root.fgColour
bgColour: root.bgColour
padding: root.padding
rotation: manager.rotation
startAngle: manager.startFraction * 360
value: manager.endFraction - manager.startFraction
}
CircularIndicatorManager {
id: manager
}
NumberAnimation {
running: root.animState !== CircularIndicator.Stopped
loops: Animation.Infinite
target: manager
property: "progress"
from: 0
to: 1
duration: manager.duration * Appearance.anim.durations.scale
}
NumberAnimation {
running: root.animState === CircularIndicator.Completing
target: manager
property: "completeEndProgress"
from: 0
to: 1
duration: manager.completeEndDuration * Appearance.anim.durations.scale
onFinished: {
if (root.animState === CircularIndicator.Completing)
root.animState = CircularIndicator.Stopped;
}
}
} }
+55 -52
View File
@@ -1,66 +1,69 @@
import QtQuick import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import qs.Helpers
import qs.Config import qs.Config
import qs.Modules
Shape { Shape {
id: root id: root
readonly property real arcRadius: (size - padding - strokeWidth) / 2 property real value
property color bgColour: DynamicColors.palette.m3secondaryContainer property int startAngle: -90
property color fgColour: DynamicColors.palette.m3primary property int strokeWidth: Appearance.padding.smaller
readonly property real gapAngle: ((spacing + strokeWidth) / (arcRadius || 1)) * (180 / Math.PI) property int padding: 0
property int padding: 0 property int spacing: Appearance.spacing.small
readonly property real size: Math.min(width, height) property color fgColour: DynamicColors.palette.m3primary
property int spacing: Appearance.spacing.small property color bgColour: DynamicColors.palette.m3secondaryContainer
property int startAngle: -90
property int strokeWidth: Appearance.padding.smaller
readonly property real vValue: value || 1 / 360
property real value
asynchronous: true readonly property real size: Math.min(width, height)
preferredRendererType: Shape.CurveRenderer readonly property real arcRadius: (size - padding - strokeWidth) / 2
readonly property real vValue: value || 1 / 360
readonly property real gapAngle: ((spacing + strokeWidth) / (arcRadius || 1)) * (180 / Math.PI)
ShapePath { preferredRendererType: Shape.CurveRenderer
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap asynchronous: true
fillColor: "transparent"
strokeColor: root.bgColour
strokeWidth: root.strokeWidth
Behavior on strokeColor { ShapePath {
CAnim { fillColor: "transparent"
duration: Appearance.anim.durations.large strokeColor: root.bgColour
} strokeWidth: root.strokeWidth
} capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
PathAngleArc { PathAngleArc {
centerX: root.size / 2 startAngle: root.startAngle + 360 * root.vValue + root.gapAngle
centerY: root.size / 2 sweepAngle: Math.max(-root.gapAngle, 360 * (1 - root.vValue) - root.gapAngle * 2)
radiusX: root.arcRadius radiusX: root.arcRadius
radiusY: root.arcRadius radiusY: root.arcRadius
startAngle: root.startAngle + 360 * root.vValue + root.gapAngle centerX: root.size / 2
sweepAngle: Math.max(-root.gapAngle, 360 * (1 - root.vValue) - root.gapAngle * 2) centerY: root.size / 2
} }
}
ShapePath { Behavior on strokeColor {
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap CAnim {
fillColor: "transparent" duration: Appearance.anim.durations.large
strokeColor: root.fgColour }
strokeWidth: root.strokeWidth }
}
Behavior on strokeColor { ShapePath {
CAnim { fillColor: "transparent"
duration: Appearance.anim.durations.large strokeColor: root.fgColour
} strokeWidth: root.strokeWidth
} capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
PathAngleArc { PathAngleArc {
centerX: root.size / 2 startAngle: root.startAngle
centerY: root.size / 2 sweepAngle: 360 * root.vValue
radiusX: root.arcRadius radiusX: root.arcRadius
radiusY: root.arcRadius radiusY: root.arcRadius
startAngle: root.startAngle centerX: root.size / 2
sweepAngle: 360 * root.vValue centerY: root.size / 2
} }
}
Behavior on strokeColor {
CAnim {
duration: Appearance.anim.durations.large
}
}
}
} }
-135
View File
@@ -1,135 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
ColumnLayout {
id: root
default property alias content: contentColumn.data
property string description: ""
property bool expanded: false
property bool nested: false
property bool showBackground: false
required property string title
signal toggleRequested
Layout.fillWidth: true
spacing: Appearance.spacing.small
Item {
id: sectionHeaderItem
Layout.fillWidth: true
Layout.preferredHeight: Math.max(titleRow.implicitHeight + Appearance.padding.normal * 2, 48)
RowLayout {
id: titleRow
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.normal
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.normal
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.normal
CustomText {
font.pointSize: Appearance.font.size.larger
font.weight: 500
text: root.title
}
Item {
Layout.fillWidth: true
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
rotation: root.expanded ? 180 : 0
text: "expand_more"
Behavior on rotation {
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}
}
StateLayer {
function onClicked(): void {
root.toggleRequested();
root.expanded = !root.expanded;
}
anchors.fill: parent
color: DynamicColors.palette.m3onSurface
radius: Appearance.rounding.normal
showHoverBackground: false
}
}
Item {
id: contentWrapper
Layout.fillWidth: true
Layout.preferredHeight: root.expanded ? (contentColumn.implicitHeight + Appearance.spacing.small * 2) : 0
clip: true
Behavior on Layout.preferredHeight {
Anim {
easing.bezierCurve: Appearance.anim.curves.standard
}
}
CustomRect {
id: backgroundRect
anchors.fill: parent
color: DynamicColors.transparency.enabled ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, root.nested ? 3 : 2) : (root.nested ? DynamicColors.palette.m3surfaceContainerHigh : DynamicColors.palette.m3surfaceContainer)
opacity: root.showBackground && root.expanded ? 1.0 : 0.0
radius: Appearance.rounding.normal
visible: root.showBackground
Behavior on opacity {
Anim {
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}
ColumnLayout {
id: contentColumn
anchors.bottomMargin: Appearance.spacing.small
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.normal
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.normal
opacity: root.expanded ? 1.0 : 0.0
spacing: Appearance.spacing.small
y: Appearance.spacing.small
Behavior on opacity {
Anim {
easing.bezierCurve: Appearance.anim.curves.standard
}
}
CustomText {
id: descriptionText
Layout.bottomMargin: root.description !== "" ? Appearance.spacing.small : 0
Layout.fillWidth: true
Layout.topMargin: root.description !== "" ? Appearance.spacing.smaller : 0
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
text: root.description
visible: root.description !== ""
wrapMode: Text.Wrap
}
}
}
}
+21 -20
View File
@@ -5,30 +5,31 @@ import Quickshell.Widgets
import QtQuick import QtQuick
IconImage { IconImage {
id: root id: root
required property color color required property color color
asynchronous: true asynchronous: true
layer.enabled: true
layer.effect: Coloriser { layer.enabled: true
colorizationColor: root.color layer.effect: Coloriser {
sourceColor: analyser.dominantColour sourceColor: analyser.dominantColour
} colorizationColor: root.color
}
layer.onEnabledChanged: { layer.onEnabledChanged: {
if (layer.enabled && status === Image.Ready) if (layer.enabled && status === Image.Ready)
analyser.requestUpdate(); analyser.requestUpdate();
} }
onStatusChanged: {
if (layer.enabled && status === Image.Ready)
analyser.requestUpdate();
}
ImageAnalyser { onStatusChanged: {
id: analyser if (layer.enabled && status === Image.Ready)
analyser.requestUpdate();
}
sourceItem: root ImageAnalyser {
} id: analyser
sourceItem: root
}
} }
+7 -7
View File
@@ -1,14 +1,14 @@
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import qs.Modules
MultiEffect { MultiEffect {
property color sourceColor: "black" property color sourceColor: "black"
brightness: 1 - sourceColor.hslLightness colorization: 1
colorization: 1 brightness: 1 - sourceColor.hslLightness
Behavior on colorizationColor { Behavior on colorizationColor {
CAnim { CAnim {}
} }
}
} }
+60 -47
View File
@@ -1,70 +1,83 @@
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import qs.Config import qs.Config
import qs.Modules
Slider { Slider {
id: root id: root
property color nonPeakColor: DynamicColors.tPalette.m3primary
required property real peak required property real peak
property color nonPeakColor: DynamicColors.tPalette.m3primary
property color peakColor: DynamicColors.palette.m3primary property color peakColor: DynamicColors.palette.m3primary
background: Item { background: Item {
CustomRect { CustomRect {
anchors.bottom: parent.bottom anchors.top: parent.top
anchors.bottomMargin: root.implicitHeight / 3 anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.topMargin: root.implicitHeight / 3
anchors.topMargin: root.implicitHeight / 3 anchors.bottomMargin: root.implicitHeight / 3
bottomRightRadius: root.implicitHeight / 15
color: root.nonPeakColor implicitWidth: root.handle.x - root.implicitHeight
implicitWidth: root.handle.x - root.implicitHeight
radius: 1000 color: root.nonPeakColor
topRightRadius: root.implicitHeight / 15 radius: 1000
topRightRadius: root.implicitHeight / 15
bottomRightRadius: root.implicitHeight / 15
CustomRect { CustomRect {
anchors.top: parent.top
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top
bottomRightRadius: root.implicitHeight / 15
color: root.peakColor
implicitWidth: parent.width * root.peak implicitWidth: parent.width * root.peak
radius: 1000 radius: 1000
topRightRadius: root.implicitHeight / 15 topRightRadius: root.implicitHeight / 15
bottomRightRadius: root.implicitHeight / 15
color: root.peakColor
Behavior on implicitWidth { Behavior on implicitWidth {
Anim { Anim { duration: 50 }
duration: 50
}
} }
} }
} }
CustomRect { CustomRect {
anchors.bottom: parent.bottom anchors.top: parent.top
anchors.bottomMargin: root.implicitHeight / 3 anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.topMargin: root.implicitHeight / 3
anchors.topMargin: root.implicitHeight / 3 anchors.bottomMargin: root.implicitHeight / 3
bottomLeftRadius: root.implicitHeight / 15
color: DynamicColors.tPalette.m3surfaceContainer
implicitWidth: root.implicitWidth - root.handle.x - root.handle.implicitWidth - root.implicitHeight
radius: 1000
topLeftRadius: root.implicitHeight / 15
}
}
handle: CustomRect {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3primary
implicitHeight: 15
implicitWidth: 5
radius: 1000
x: root.visualPosition * root.availableWidth - implicitWidth / 2
MouseArea { implicitWidth: root.implicitWidth - root.handle.x - root.handle.implicitWidth - root.implicitHeight
acceptedButtons: Qt.NoButton
anchors.fill: parent Component.onCompleted: {
cursorShape: Qt.PointingHandCursor console.log(root.handle.x, implicitWidth)
} }
}
color: DynamicColors.tPalette.m3surfaceContainer
radius: 1000
topLeftRadius: root.implicitHeight / 15
bottomLeftRadius: root.implicitHeight / 15
}
}
handle: CustomRect {
x: root.visualPosition * root.availableWidth - implicitWidth / 2
implicitWidth: 5
implicitHeight: 15
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3primary
radius: 1000
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: Qt.PointingHandCursor
}
}
} }
+51 -55
View File
@@ -2,68 +2,64 @@ import QtQuick
import QtQuick.Controls.Basic import QtQuick.Controls.Basic
BusyIndicator { BusyIndicator {
id: control id: control
property int busySize: 64
property color color: delegate.color property color color: delegate.color
property int busySize: 64
contentItem: Item { contentItem: Item {
implicitHeight: control.busySize implicitWidth: control.busySize
implicitWidth: control.busySize implicitHeight: control.busySize
Item { Item {
id: item id: item
x: parent.width / 2 - (control.busySize / 2)
y: parent.height / 2 - (control.busySize / 2)
width: control.busySize
height: control.busySize
opacity: control.running ? 1 : 0
height: control.busySize Behavior on opacity {
opacity: control.running ? 1 : 0 OpacityAnimator {
width: control.busySize duration: 250
x: parent.width / 2 - (control.busySize / 2) }
y: parent.height / 2 - (control.busySize / 2) }
Behavior on opacity { RotationAnimator {
OpacityAnimator { target: item
duration: 250 running: control.visible && control.running
} from: 0
} to: 360
loops: Animation.Infinite
duration: 1250
}
RotationAnimator { Repeater {
duration: 1250 id: repeater
from: 0 model: 6
loops: Animation.Infinite
running: control.visible && control.running
target: item
to: 360
}
Repeater { CustomRect {
id: repeater id: delegate
x: item.width / 2 - width / 2
y: item.height / 2 - height / 2
implicitWidth: 10
implicitHeight: 10
radius: 5
color: control.color
model: 6 required property int index
CustomRect { transform: [
id: delegate Translate {
y: -Math.min(item.width, item.height) * 0.5 + 5
required property int index },
Rotation {
color: control.color angle: delegate.index / repeater.count * 360
implicitHeight: 10 origin.x: 5
implicitWidth: 10 origin.y: 5
radius: 5 }
x: item.width / 2 - width / 2 ]
y: item.height / 2 - height / 2 }
}
transform: [ }
Translate { }
y: -Math.min(item.width, item.height) * 0.5 + 5
},
Rotation {
angle: delegate.index / repeater.count * 360
origin.x: 5
origin.y: 5
}
]
}
}
}
}
} }
+12 -10
View File
@@ -4,28 +4,30 @@ import QtQuick.Controls
Button { Button {
id: control id: control
required property color textColor
required property color bgColor required property color bgColor
property int radius: 4 property int radius: 4
required property color textColor
background: CustomRect {
color: control.bgColor
opacity: control.enabled ? 1.0 : 0.5
radius: control.radius
}
contentItem: CustomText { contentItem: CustomText {
text: control.text
opacity: control.enabled ? 1.0 : 0.5
color: control.textColor color: control.textColor
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
opacity: control.enabled ? 1.0 : 0.5
text: control.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
background: CustomRect {
opacity: control.enabled ? 1.0 : 0.5
radius: control.radius
color: control.bgColor
}
StateLayer { StateLayer {
radius: control.radius
function onClicked(): void { function onClicked(): void {
control.clicked(); control.clicked();
} }
radius: control.radius
} }
} }
+16 -14
View File
@@ -5,33 +5,35 @@ import qs.Config
CheckBox { CheckBox {
id: control id: control
property int checkHeight: 20
property int checkWidth: 20 property int checkWidth: 20
property int checkHeight: 20
contentItem: CustomText {
anchors.left: parent.left
anchors.leftMargin: control.checkWidth + control.leftPadding + 8
anchors.verticalCenter: parent.verticalCenter
font.pointSize: control.font.pointSize
text: control.text
}
indicator: CustomRect { indicator: CustomRect {
implicitWidth: control.checkWidth
implicitHeight: control.checkHeight
// x: control.leftPadding // x: control.leftPadding
// y: parent.implicitHeight / 2 - implicitHeight / 2 // y: parent.implicitHeight / 2 - implicitHeight / 2
border.color: control.checked ? DynamicColors.palette.m3primary : "transparent" border.color: control.checked ? DynamicColors.palette.m3primary : "transparent"
color: DynamicColors.palette.m3surfaceVariant color: DynamicColors.palette.m3surfaceVariant
implicitHeight: control.checkHeight
implicitWidth: control.checkWidth
radius: 4 radius: 4
CustomRect { CustomRect {
color: DynamicColors.palette.m3primary
implicitHeight: control.checkHeight - (y * 2)
implicitWidth: control.checkWidth - (x * 2) implicitWidth: control.checkWidth - (x * 2)
radius: 3 implicitHeight: control.checkHeight - (y * 2)
visible: control.checked
x: 4 x: 4
y: 4 y: 4
radius: 3
color: DynamicColors.palette.m3primary
visible: control.checked
} }
} }
contentItem: CustomText {
text: control.text
font.pointSize: control.font.pointSize
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: control.checkWidth + control.leftPadding + 8
}
} }
+6 -6
View File
@@ -1,13 +1,13 @@
import Quickshell.Widgets import Quickshell.Widgets
import QtQuick import QtQuick
import qs.Modules
ClippingRectangle { ClippingRectangle {
id: root id: root
color: "transparent" color: "transparent"
Behavior on color { Behavior on color {
CAnim { CAnim {}
} }
}
} }
+8 -7
View File
@@ -1,13 +1,14 @@
import QtQuick import QtQuick
import qs.Modules
Flickable { Flickable {
id: root id: root
maximumFlickVelocity: 3000 maximumFlickVelocity: 3000
rebound: Transition { rebound: Transition {
Anim { Anim {
properties: "x,y" properties: "x,y"
} }
} }
} }
+2 -2
View File
@@ -4,7 +4,7 @@ import Quickshell.Widgets
import QtQuick import QtQuick
IconImage { IconImage {
id: root id: root
asynchronous: true asynchronous: true
} }
+9 -7
View File
@@ -1,13 +1,15 @@
import QtQuick import QtQuick
import qs.Config
import qs.Modules
ListView { ListView {
id: root id: root
maximumFlickVelocity: 3000 maximumFlickVelocity: 3000
rebound: Transition { rebound: Transition {
Anim { Anim {
properties: "x,y" properties: "x,y"
} }
} }
} }
+12 -12
View File
@@ -1,19 +1,19 @@
import QtQuick import QtQuick
MouseArea { MouseArea {
property int scrollAccumulatedY: 0 property int scrollAccumulatedY: 0
function onWheel(event: WheelEvent): void { function onWheel(event: WheelEvent): void {
} }
onWheel: event => { onWheel: event => {
if (Math.sign(event.angleDelta.y) !== Math.sign(scrollAccumulatedY)) if (Math.sign(event.angleDelta.y) !== Math.sign(scrollAccumulatedY))
scrollAccumulatedY = 0; scrollAccumulatedY = 0;
scrollAccumulatedY += event.angleDelta.y; scrollAccumulatedY += event.angleDelta.y;
if (Math.abs(scrollAccumulatedY) >= 120) { if (Math.abs(scrollAccumulatedY) >= 120) {
onWheel(event); onWheel(event);
scrollAccumulatedY = 0; scrollAccumulatedY = 0;
} }
} }
} }
+43 -40
View File
@@ -1,53 +1,56 @@
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import qs.Config import qs.Config
import qs.Modules
RadioButton { RadioButton {
id: root id: root
font.pointSize: 12 font.pointSize: 12
implicitHeight: Math.max(implicitIndicatorHeight, implicitContentHeight)
implicitWidth: implicitIndicatorWidth + implicitContentWidth + contentItem.anchors.leftMargin
contentItem: CustomText { implicitWidth: implicitIndicatorWidth + implicitContentWidth + contentItem.anchors.leftMargin
anchors.left: outerCircle.right implicitHeight: Math.max(implicitIndicatorHeight, implicitContentHeight)
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
font.pointSize: root.font.pointSize
text: root.text
}
indicator: Rectangle {
id: outerCircle
anchors.verticalCenter: parent.verticalCenter indicator: Rectangle {
border.color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant id: outerCircle
border.width: 2
color: "transparent"
implicitHeight: 16
implicitWidth: 16
radius: 1000
Behavior on border.color { implicitWidth: 16
CAnim { implicitHeight: 16
} radius: 1000
} color: "transparent"
border.color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant
border.width: 2
anchors.verticalCenter: parent.verticalCenter
StateLayer { StateLayer {
function onClicked(): void { anchors.margins: -7
root.click(); color: root.checked ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3primary
} z: -1
anchors.margins: -7 function onClicked(): void {
color: root.checked ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3primary root.click();
z: -1 }
} }
CustomRect { CustomRect {
anchors.centerIn: parent anchors.centerIn: parent
color: Qt.alpha(DynamicColors.palette.m3primary, root.checked ? 1 : 0) implicitWidth: 8
implicitHeight: 8 implicitHeight: 8
implicitWidth: 8
radius: 1000 radius: 1000
} color: Qt.alpha(DynamicColors.palette.m3primary, root.checked ? 1 : 0)
} }
Behavior on border.color {
CAnim {}
}
}
contentItem: CustomText {
text: root.text
font.pointSize: root.font.pointSize
anchors.verticalCenter: parent.verticalCenter
anchors.left: outerCircle.right
anchors.leftMargin: 10
}
} }
+6 -6
View File
@@ -1,12 +1,12 @@
import QtQuick import QtQuick
import qs.Modules
Rectangle { Rectangle {
id: root id: root
color: "transparent" color: "transparent"
Behavior on color { Behavior on color {
CAnim { CAnim {}
} }
}
} }
+163 -163
View File
@@ -1,189 +1,189 @@
import qs.Config import qs.Config
import qs.Modules
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
ScrollBar { ScrollBar {
id: root id: root
property bool _updatingFromFlickable: false required property Flickable flickable
property bool _updatingFromUser: false property bool shouldBeActive
property bool animating property real nonAnimPosition
required property Flickable flickable property bool animating
property real nonAnimPosition
property bool shouldBeActive
implicitWidth: 8 onHoveredChanged: {
if (hovered)
shouldBeActive = true;
else
shouldBeActive = flickable.moving;
}
contentItem: CustomRect { property bool _updatingFromFlickable: false
anchors.left: parent.left property bool _updatingFromUser: false
anchors.right: parent.right
color: DynamicColors.palette.m3secondary
opacity: {
if (root.size === 1)
return 0;
if (fullMouse.pressed)
return 1;
if (mouse.containsMouse)
return 0.8;
if (root.policy === ScrollBar.AlwaysOn || root.shouldBeActive)
return 0.6;
return 0;
}
radius: 1000
Behavior on opacity { // Sync nonAnimPosition with Qt's automatic position binding
Anim { onPositionChanged: {
} if (_updatingFromUser) {
} _updatingFromUser = false;
return;
}
if (position === nonAnimPosition) {
animating = false;
return;
}
if (!animating && !_updatingFromFlickable && !fullMouse.pressed) {
nonAnimPosition = position;
}
}
MouseArea { // Sync nonAnimPosition with flickable when not animating
id: mouse Connections {
target: flickable
function onContentYChanged() {
if (!animating && !fullMouse.pressed) {
_updatingFromFlickable = true;
const contentHeight = flickable.contentHeight;
const height = flickable.height;
if (contentHeight > height) {
nonAnimPosition = Math.max(0, Math.min(1, flickable.contentY / (contentHeight - height)));
} else {
nonAnimPosition = 0;
}
_updatingFromFlickable = false;
}
}
}
acceptedButtons: Qt.NoButton Component.onCompleted: {
anchors.fill: parent if (flickable) {
cursorShape: Qt.PointingHandCursor const contentHeight = flickable.contentHeight;
hoverEnabled: true const height = flickable.height;
} if (contentHeight > height) {
} nonAnimPosition = Math.max(0, Math.min(1, flickable.contentY / (contentHeight - height)));
Behavior on position { }
enabled: !fullMouse.pressed }
}
implicitWidth: 8
Anim { contentItem: CustomRect {
} anchors.left: parent.left
} anchors.right: parent.right
opacity: {
if (root.size === 1)
return 0;
if (fullMouse.pressed)
return 1;
if (mouse.containsMouse)
return 0.8;
if (root.policy === ScrollBar.AlwaysOn || root.shouldBeActive)
return 0.6;
return 0;
}
radius: 1000
color: DynamicColors.palette.m3secondary
Component.onCompleted: { MouseArea {
if (flickable) { id: mouse
const contentHeight = flickable.contentHeight;
const height = flickable.height;
if (contentHeight > height) {
nonAnimPosition = Math.max(0, Math.min(1, flickable.contentY / (contentHeight - height)));
}
}
}
onHoveredChanged: {
if (hovered)
shouldBeActive = true;
else
shouldBeActive = flickable.moving;
}
// Sync nonAnimPosition with Qt's automatic position binding anchors.fill: parent
onPositionChanged: { cursorShape: Qt.PointingHandCursor
if (_updatingFromUser) { hoverEnabled: true
_updatingFromUser = false; acceptedButtons: Qt.NoButton
return; }
}
if (position === nonAnimPosition) {
animating = false;
return;
}
if (!animating && !_updatingFromFlickable && !fullMouse.pressed) {
nonAnimPosition = position;
}
}
// Sync nonAnimPosition with flickable when not animating Behavior on opacity {
Connections { Anim {}
function onContentYChanged() { }
if (!animating && !fullMouse.pressed) { }
_updatingFromFlickable = true;
const contentHeight = flickable.contentHeight;
const height = flickable.height;
if (contentHeight > height) {
nonAnimPosition = Math.max(0, Math.min(1, flickable.contentY / (contentHeight - height)));
} else {
nonAnimPosition = 0;
}
_updatingFromFlickable = false;
}
}
target: flickable Connections {
} target: root.flickable
Connections { function onMovingChanged(): void {
function onMovingChanged(): void { if (root.flickable.moving)
if (root.flickable.moving) root.shouldBeActive = true;
root.shouldBeActive = true; else
else hideDelay.restart();
hideDelay.restart(); }
} }
target: root.flickable Timer {
} id: hideDelay
Timer { interval: 600
id: hideDelay onTriggered: root.shouldBeActive = root.flickable.moving || root.hovered
}
interval: 600 CustomMouseArea {
id: fullMouse
onTriggered: root.shouldBeActive = root.flickable.moving || root.hovered anchors.fill: parent
} preventStealing: true
CustomMouseArea { onPressed: event => {
id: fullMouse root.animating = true;
root._updatingFromUser = true;
const newPos = Math.max(0, Math.min(1 - root.size, event.y / root.height - root.size / 2));
root.nonAnimPosition = newPos;
// Update flickable position
// Map scrollbar position [0, 1-size] to contentY [0, maxContentY]
if (root.flickable) {
const contentHeight = root.flickable.contentHeight;
const height = root.flickable.height;
if (contentHeight > height) {
const maxContentY = contentHeight - height;
const maxPos = 1 - root.size;
const contentY = maxPos > 0 ? (newPos / maxPos) * maxContentY : 0;
root.flickable.contentY = Math.max(0, Math.min(maxContentY, contentY));
}
}
}
function onWheel(event: WheelEvent): void { onPositionChanged: event => {
root.animating = true; root._updatingFromUser = true;
root._updatingFromUser = true; const newPos = Math.max(0, Math.min(1 - root.size, event.y / root.height - root.size / 2));
let newPos = root.nonAnimPosition; root.nonAnimPosition = newPos;
if (event.angleDelta.y > 0) // Update flickable position
newPos = Math.max(0, root.nonAnimPosition - 0.1); // Map scrollbar position [0, 1-size] to contentY [0, maxContentY]
else if (event.angleDelta.y < 0) if (root.flickable) {
newPos = Math.min(1 - root.size, root.nonAnimPosition + 0.1); const contentHeight = root.flickable.contentHeight;
root.nonAnimPosition = newPos; const height = root.flickable.height;
// Update flickable position if (contentHeight > height) {
// Map scrollbar position [0, 1-size] to contentY [0, maxContentY] const maxContentY = contentHeight - height;
if (root.flickable) { const maxPos = 1 - root.size;
const contentHeight = root.flickable.contentHeight; const contentY = maxPos > 0 ? (newPos / maxPos) * maxContentY : 0;
const height = root.flickable.height; root.flickable.contentY = Math.max(0, Math.min(maxContentY, contentY));
if (contentHeight > height) { }
const maxContentY = contentHeight - height; }
const maxPos = 1 - root.size; }
const contentY = maxPos > 0 ? (newPos / maxPos) * maxContentY : 0;
root.flickable.contentY = Math.max(0, Math.min(maxContentY, contentY));
}
}
}
anchors.fill: parent function onWheel(event: WheelEvent): void {
preventStealing: true root.animating = true;
root._updatingFromUser = true;
let newPos = root.nonAnimPosition;
if (event.angleDelta.y > 0)
newPos = Math.max(0, root.nonAnimPosition - 0.1);
else if (event.angleDelta.y < 0)
newPos = Math.min(1 - root.size, root.nonAnimPosition + 0.1);
root.nonAnimPosition = newPos;
// Update flickable position
// Map scrollbar position [0, 1-size] to contentY [0, maxContentY]
if (root.flickable) {
const contentHeight = root.flickable.contentHeight;
const height = root.flickable.height;
if (contentHeight > height) {
const maxContentY = contentHeight - height;
const maxPos = 1 - root.size;
const contentY = maxPos > 0 ? (newPos / maxPos) * maxContentY : 0;
root.flickable.contentY = Math.max(0, Math.min(maxContentY, contentY));
}
}
}
}
onPositionChanged: event => { Behavior on position {
root._updatingFromUser = true; enabled: !fullMouse.pressed
const newPos = Math.max(0, Math.min(1 - root.size, event.y / root.height - root.size / 2));
root.nonAnimPosition = newPos; Anim {}
// Update flickable position }
// Map scrollbar position [0, 1-size] to contentY [0, maxContentY]
if (root.flickable) {
const contentHeight = root.flickable.contentHeight;
const height = root.flickable.height;
if (contentHeight > height) {
const maxContentY = contentHeight - height;
const maxPos = 1 - root.size;
const contentY = maxPos > 0 ? (newPos / maxPos) * maxContentY : 0;
root.flickable.contentY = Math.max(0, Math.min(maxContentY, contentY));
}
}
}
onPressed: event => {
root.animating = true;
root._updatingFromUser = true;
const newPos = Math.max(0, Math.min(1 - root.size, event.y / root.height - root.size / 2));
root.nonAnimPosition = newPos;
// Update flickable position
// Map scrollbar position [0, 1-size] to contentY [0, maxContentY]
if (root.flickable) {
const contentHeight = root.flickable.contentHeight;
const height = root.flickable.height;
if (contentHeight > height) {
const maxContentY = contentHeight - height;
const maxPos = 1 - root.size;
const contentY = maxPos > 0 ? (newPos / maxPos) * maxContentY : 0;
root.flickable.contentY = Math.max(0, Math.min(maxContentY, contentY));
}
}
}
}
} }
+44 -36
View File
@@ -1,45 +1,53 @@
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import qs.Config import qs.Config
import qs.Modules
Slider { Slider {
id: root id: root
background: Item { background: Item {
CustomRect { CustomRect {
anchors.bottom: parent.bottom anchors.top: parent.top
anchors.left: parent.left anchors.bottom: parent.bottom
anchors.top: parent.top anchors.left: parent.left
bottomRightRadius: root.implicitHeight / 6
color: DynamicColors.palette.m3primary
implicitWidth: root.handle.x - root.implicitHeight / 2
radius: 1000
topRightRadius: root.implicitHeight / 6
}
CustomRect { implicitWidth: root.handle.x - root.implicitHeight / 2
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.top: parent.top
bottomLeftRadius: root.implicitHeight / 6
color: DynamicColors.tPalette.m3surfaceContainer
implicitWidth: parent.width - root.handle.x - root.handle.implicitWidth - root.implicitHeight / 2
radius: 1000
topLeftRadius: root.implicitHeight / 6
}
}
handle: CustomRect {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3primary
implicitHeight: 15
implicitWidth: 5
radius: 1000
x: root.visualPosition * root.availableWidth - implicitWidth / 2
MouseArea { color: DynamicColors.palette.m3primary
acceptedButtons: Qt.NoButton radius: 1000
anchors.fill: parent topRightRadius: root.implicitHeight / 6
cursorShape: Qt.PointingHandCursor bottomRightRadius: root.implicitHeight / 6
} }
}
CustomRect {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
implicitWidth: parent.width - root.handle.x - root.handle.implicitWidth - root.implicitHeight / 2
color: DynamicColors.tPalette.m3surfaceContainer
radius: 1000
topLeftRadius: root.implicitHeight / 6
bottomLeftRadius: root.implicitHeight / 6
}
}
handle: CustomRect {
x: root.visualPosition * root.availableWidth - implicitWidth / 2
implicitWidth: 5
implicitHeight: 15
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3primary
radius: 1000
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: Qt.PointingHandCursor
}
}
} }
-166
View File
@@ -1,166 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
RowLayout {
id: root
property string displayText: root.value.toString()
property bool isEditing: false
property real max: Infinity
property real min: -Infinity
property alias repeatRate: timer.interval
property real step: 1
property real value
signal valueModified(value: real)
spacing: Appearance.spacing.small
onValueChanged: {
if (!root.isEditing) {
root.displayText = root.value.toString();
}
}
CustomTextField {
id: textField
inputMethodHints: Qt.ImhFormattedNumbersOnly
leftPadding: Appearance.padding.normal
padding: Appearance.padding.small
rightPadding: Appearance.padding.normal
text: root.isEditing ? text : root.displayText
background: CustomRect {
color: DynamicColors.tPalette.m3surfaceContainerHigh
implicitWidth: 100
radius: Appearance.rounding.small
}
validator: DoubleValidator {
bottom: root.min
decimals: root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0
top: root.max
}
onAccepted: {
const numValue = parseFloat(text);
if (!isNaN(numValue)) {
const clampedValue = Math.max(root.min, Math.min(root.max, numValue));
root.value = clampedValue;
root.displayText = clampedValue.toString();
root.valueModified(clampedValue);
} else {
text = root.displayText;
}
root.isEditing = false;
}
onActiveFocusChanged: {
if (activeFocus) {
root.isEditing = true;
} else {
root.isEditing = false;
root.displayText = root.value.toString();
}
}
onEditingFinished: {
if (text !== root.displayText) {
const numValue = parseFloat(text);
if (!isNaN(numValue)) {
const clampedValue = Math.max(root.min, Math.min(root.max, numValue));
root.value = clampedValue;
root.displayText = clampedValue.toString();
root.valueModified(clampedValue);
} else {
text = root.displayText;
}
}
root.isEditing = false;
}
}
CustomRect {
color: DynamicColors.palette.m3primary
implicitHeight: upIcon.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight
radius: Appearance.rounding.small
StateLayer {
id: upState
function onClicked(): void {
let newValue = Math.min(root.max, root.value + root.step);
// Round to avoid floating point precision errors
const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0;
newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals);
root.value = newValue;
root.displayText = newValue.toString();
root.valueModified(newValue);
}
color: DynamicColors.palette.m3onPrimary
onPressAndHold: timer.start()
onReleased: timer.stop()
}
MaterialIcon {
id: upIcon
anchors.centerIn: parent
color: DynamicColors.palette.m3onPrimary
text: "keyboard_arrow_up"
}
}
CustomRect {
color: DynamicColors.palette.m3primary
implicitHeight: downIcon.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight
radius: Appearance.rounding.small
StateLayer {
id: downState
function onClicked(): void {
let newValue = Math.max(root.min, root.value - root.step);
// Round to avoid floating point precision errors
const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0;
newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals);
root.value = newValue;
root.displayText = newValue.toString();
root.valueModified(newValue);
}
color: DynamicColors.palette.m3onPrimary
onPressAndHold: timer.start()
onReleased: timer.stop()
}
MaterialIcon {
id: downIcon
anchors.centerIn: parent
color: DynamicColors.palette.m3onPrimary
text: "keyboard_arrow_down"
}
}
Timer {
id: timer
interval: 100
repeat: true
triggeredOnStart: true
onTriggered: {
if (upState.pressed)
upState.onClicked();
else if (downState.pressed)
downState.onClicked();
}
}
}
+44 -133
View File
@@ -1,155 +1,66 @@
import qs.Config
import qs.Modules
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import QtQuick.Shapes import QtQuick.Shapes
import qs.Config
Switch { Switch {
id: root id: root
property int cLayer: 1 property int cLayer: 1
implicitHeight: implicitIndicatorHeight implicitWidth: implicitIndicatorWidth
implicitWidth: implicitIndicatorWidth implicitHeight: implicitIndicatorHeight
indicator: CustomRect { indicator: CustomRect {
color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer) radius: 1000
implicitHeight: 13 + 7 * 2 color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer)
implicitWidth: implicitHeight * 1.7
radius: 1000
CustomRect { implicitWidth: implicitHeight * 1.7
readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.3 : implicitHeight implicitHeight: 13 + 7 * 2
anchors.verticalCenter: parent.verticalCenter CustomRect {
color: root.checked ? DynamicColors.palette.m3onPrimary : DynamicColors.layer(DynamicColors.palette.m3outline, root.cLayer + 1) readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.3 : implicitHeight
implicitHeight: parent.implicitHeight - 10
implicitWidth: nonAnimWidth
radius: 1000
x: root.checked ? parent.implicitWidth - nonAnimWidth - 10 / 2 : 10 / 2
Behavior on implicitWidth { radius: 1000
Anim { color: root.checked ? DynamicColors.palette.m3onPrimary : DynamicColors.layer(DynamicColors.palette.m3outline, root.cLayer + 1)
}
}
Behavior on x {
Anim {
}
}
CustomRect { x: root.checked ? parent.implicitWidth - nonAnimWidth - 10 / 2 : 10 / 2
anchors.fill: parent implicitWidth: nonAnimWidth
color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface implicitHeight: parent.implicitHeight - 10
opacity: root.pressed ? 0.1 : root.hovered ? 0.08 : 0 anchors.verticalCenter: parent.verticalCenter
radius: parent.radius
Behavior on opacity { CustomRect {
Anim { anchors.fill: parent
} radius: parent.radius
}
}
Shape { color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface
id: icon opacity: root.pressed ? 0.1 : root.hovered ? 0.08 : 0
property point end1: { Behavior on opacity {
if (root.pressed) { Anim {}
if (root.checked) }
return Qt.point(width * 0.4, height / 2); }
return Qt.point(width * 0.8, height / 2);
}
if (root.checked)
return Qt.point(width * 0.4, height * 0.7);
return Qt.point(width * 0.85, height * 0.85);
}
property point end2: {
if (root.pressed)
return Qt.point(width, height / 2);
if (root.checked)
return Qt.point(width * 0.85, height * 0.2);
return Qt.point(width * 0.85, height * 0.15);
}
property point start1: {
if (root.pressed)
return Qt.point(width * 0.1, height / 2);
if (root.checked)
return Qt.point(width * 0.15, height / 2);
return Qt.point(width * 0.15, height * 0.15);
}
property point start2: {
if (root.pressed) {
if (root.checked)
return Qt.point(width * 0.4, height / 2);
return Qt.point(width * 0.2, height / 2);
}
if (root.checked)
return Qt.point(width * 0.4, height * 0.7);
return Qt.point(width * 0.15, height * 0.85);
}
anchors.centerIn: parent Behavior on x {
asynchronous: true Anim {}
height: parent.implicitHeight - Appearance.padding.small * 2 }
preferredRendererType: Shape.CurveRenderer
width: height
Behavior on end1 { Behavior on implicitWidth {
PropAnim { Anim {}
} }
} }
Behavior on end2 { }
PropAnim {
}
}
Behavior on start1 {
PropAnim {
}
}
Behavior on start2 {
PropAnim {
}
}
ShapePath { MouseArea {
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap anchors.fill: parent
fillColor: "transparent" cursorShape: Qt.PointingHandCursor
startX: icon.start1.x enabled: false
startY: icon.start1.y }
strokeColor: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3surfaceContainerHighest
strokeWidth: Appearance.font.size.larger * 0.15
Behavior on strokeColor { component PropAnim: PropertyAnimation {
CAnim {
}
}
PathLine {
x: icon.end1.x
y: icon.end1.y
}
PathMove {
x: icon.start2.x
y: icon.start2.y
}
PathLine {
x: icon.end2.x
y: icon.end2.y
}
}
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
component PropAnim: PropertyAnimation {
duration: MaterialEasing.expressiveEffectsTime duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects easing.bezierCurve: MaterialEasing.expressiveEffects
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
} }
} }
+36 -38
View File
@@ -2,50 +2,48 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import qs.Config import qs.Config
import qs.Modules
Text { Text {
id: root id: root
property bool animate: false property bool animate: false
property int animateDuration: 400 property string animateProp: "scale"
property real animateFrom: 0 property real animateFrom: 0
property string animateProp: "scale" property real animateTo: 1
property real animateTo: 1 property int animateDuration: 400
color: DynamicColors.palette.m3onSurface renderType: Text.NativeRendering
font.family: Appearance.font.family.sans textFormat: Text.PlainText
font.pointSize: Appearance.font.size.normal color: DynamicColors.palette.m3onSurface
renderType: Text.NativeRendering font.family: Appearance.font.family.sans
textFormat: Text.PlainText font.pointSize: 12
Behavior on color { Behavior on color {
CAnim { CAnim {}
} }
}
Behavior on text {
enabled: root.animate
SequentialAnimation { Behavior on text {
Anim { enabled: root.animate
easing.bezierCurve: MaterialEasing.standardAccel
to: root.animateFrom
}
PropertyAction { SequentialAnimation {
} Anim {
to: root.animateFrom
easing.bezierCurve: MaterialEasing.standardAccel
}
PropertyAction {}
Anim {
to: root.animateTo
easing.bezierCurve: MaterialEasing.standardDecel
}
}
}
Anim { component Anim: NumberAnimation {
easing.bezierCurve: MaterialEasing.standardDecel target: root
to: root.animateTo property: root.animateProp.split(",").length === 1 ? root.animateProp : ""
} properties: root.animateProp.split(",").length > 1 ? root.animateProp : ""
} duration: root.animateDuration / 2
} easing.type: Easing.BezierSpline
}
component Anim: NumberAnimation {
duration: root.animateDuration / 2
easing.type: Easing.BezierSpline
properties: root.animateProp.split(",").length > 1 ? root.animateProp : ""
property: root.animateProp.split(",").length === 1 ? root.animateProp : ""
target: root
}
} }
+55 -54
View File
@@ -2,74 +2,75 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Helpers
import qs.Config import qs.Config
import qs.Modules
TextField { TextField {
id: root id: root
background: null color: DynamicColors.palette.m3onSurface
color: DynamicColors.palette.m3onSurface placeholderTextColor: DynamicColors.palette.m3outline
cursorVisible: !readOnly font.family: Appearance.font.family.sans
font.family: Appearance.font.family.sans font.pointSize: Appearance.font.size.smaller
font.pointSize: Appearance.font.size.smaller renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering
placeholderTextColor: DynamicColors.palette.m3outline cursorVisible: !readOnly
renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering
Behavior on color { background: null
CAnim {
}
}
cursorDelegate: CustomRect {
id: cursor
property bool disableBlink cursorDelegate: CustomRect {
id: cursor
color: DynamicColors.palette.m3primary property bool disableBlink
implicitWidth: 2
radius: Appearance.rounding.normal
Behavior on opacity { implicitWidth: 2
Anim { color: DynamicColors.palette.m3primary
duration: Appearance.anim.durations.small radius: Appearance.rounding.normal
}
}
Connections { Connections {
function onCursorPositionChanged(): void { target: root
if (root.activeFocus && root.cursorVisible) {
cursor.opacity = 1;
cursor.disableBlink = true;
enableBlink.restart();
}
}
target: root function onCursorPositionChanged(): void {
} if (root.activeFocus && root.cursorVisible) {
cursor.opacity = 1;
cursor.disableBlink = true;
enableBlink.restart();
}
}
}
Timer { Timer {
id: enableBlink id: enableBlink
interval: 100 interval: 100
onTriggered: cursor.disableBlink = false
}
onTriggered: cursor.disableBlink = false Timer {
} running: root.activeFocus && root.cursorVisible && !cursor.disableBlink
repeat: true
triggeredOnStart: true
interval: 500
onTriggered: parent.opacity = parent.opacity === 1 ? 0 : 1
}
Timer { Binding {
interval: 500 when: !root.activeFocus || !root.cursorVisible
repeat: true cursor.opacity: 0
running: root.activeFocus && root.cursorVisible && !cursor.disableBlink }
triggeredOnStart: true
onTriggered: parent.opacity = parent.opacity === 1 ? 0 : 1 Behavior on opacity {
} Anim {
duration: Appearance.anim.durations.small
}
}
}
Binding { Behavior on color {
cursor.opacity: 0 CAnim {}
when: !root.activeFocus || !root.cursorVisible }
}
} Behavior on placeholderTextColor {
Behavior on placeholderTextColor { CAnim {}
CAnim { }
}
}
} }
+16 -18
View File
@@ -3,23 +3,21 @@ import QtQuick.Controls
import qs.Components import qs.Components
ToolTip { ToolTip {
id: root id: root
property bool extraVisibleCondition: true
property bool alternativeVisibleCondition: false
readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
verticalPadding: 5
horizontalPadding: 10
background: null
property bool alternativeVisibleCondition: false visible: internalVisibleCondition
property bool extraVisibleCondition: true
readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition contentItem: CustomTooltipContent {
id: contentItem
background: null text: root.text
horizontalPadding: 10 shown: root.internalVisibleCondition
verticalPadding: 5 horizontalPadding: root.horizontalPadding
visible: internalVisibleCondition verticalPadding: root.verticalPadding
}
contentItem: CustomTooltipContent {
id: contentItem
horizontalPadding: root.horizontalPadding
shown: root.internalVisibleCondition
text: root.text
verticalPadding: root.verticalPadding
}
} }
+38 -44
View File
@@ -1,54 +1,48 @@
import QtQuick import QtQuick
import qs.Components import qs.Components
import qs.Modules
import qs.Config import qs.Config
Item { Item {
id: root id: root
required property string text
property bool shown: false
property real horizontalPadding: 10
property real verticalPadding: 5
implicitWidth: tooltipTextObject.implicitWidth + 2 * root.horizontalPadding
implicitHeight: tooltipTextObject.implicitHeight + 2 * root.verticalPadding
property real horizontalPadding: 10 property bool isVisible: backgroundRectangle.implicitHeight > 0
property bool isVisible: backgroundRectangle.implicitHeight > 0
property bool shown: false
required property string text
property real verticalPadding: 5
implicitHeight: tooltipTextObject.implicitHeight + 2 * root.verticalPadding Rectangle {
implicitWidth: tooltipTextObject.implicitWidth + 2 * root.horizontalPadding id: backgroundRectangle
anchors {
bottom: root.bottom
horizontalCenter: root.horizontalCenter
}
color: DynamicColors.tPalette.m3inverseSurface ?? "#3C4043"
radius: 8
opacity: shown ? 1 : 0
implicitWidth: shown ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0
implicitHeight: shown ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0
clip: true
Rectangle { Behavior on implicitWidth {
id: backgroundRectangle Anim {}
}
Behavior on implicitHeight {
Anim {}
}
Behavior on opacity {
Anim {}
}
clip: true CustomText {
color: DynamicColors.tPalette.m3inverseSurface ?? "#3C4043" id: tooltipTextObject
implicitHeight: shown ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0 anchors.centerIn: parent
implicitWidth: shown ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0 text: root.text
opacity: shown ? 1 : 0 color: DynamicColors.palette.m3inverseOnSurface ?? "#FFFFFF"
radius: 8 wrapMode: Text.Wrap
}
Behavior on implicitHeight { }
Anim {
}
}
Behavior on implicitWidth {
Anim {
}
}
Behavior on opacity {
Anim {
}
}
anchors {
bottom: root.bottom
horizontalCenter: root.horizontalCenter
}
CustomText {
id: tooltipTextObject
anchors.centerIn: parent
color: DynamicColors.palette.m3inverseOnSurface ?? "#FFFFFF"
text: root.text
wrapMode: Text.Wrap
}
}
} }
+10 -10
View File
@@ -1,18 +1,18 @@
import qs.Config import qs.Config
import qs.Modules
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
RectangularShadow { RectangularShadow {
property real dp: [0, 1, 3, 6, 8, 12][level] property int level
property int level property real dp: [0, 1, 3, 6, 8, 12][level]
blur: (dp * 5) ** 0.7 color: Qt.alpha(DynamicColors.palette.m3shadow, 0.7)
color: Qt.alpha(DynamicColors.palette.m3shadow, 0.7) blur: (dp * 5) ** 0.7
offset.y: dp / 2 spread: -dp * 0.3 + (dp * 0.1) ** 2
spread: -dp * 0.3 + (dp * 0.1) ** 2 offset.y: dp / 2
Behavior on dp { Behavior on dp {
Anim { Anim {}
} }
}
} }
+39 -34
View File
@@ -1,44 +1,49 @@
import qs.Config import qs.Config
import qs.Modules
import QtQuick import QtQuick
CustomRect { CustomRect {
required property int extra required property int extra
anchors.margins: 8 anchors.right: parent.right
anchors.right: parent.right anchors.margins: 8
color: DynamicColors.palette.m3tertiary
implicitHeight: count.implicitHeight + 4 * 2
implicitWidth: count.implicitWidth + 8 * 2
opacity: extra > 0 ? 1 : 0
radius: 8
scale: extra > 0 ? 1 : 0.5
Behavior on opacity { color: DynamicColors.palette.m3tertiary
Anim { radius: 8
implicitWidth: count.implicitWidth + 8 * 2
implicitHeight: count.implicitHeight + 4 * 2
opacity: extra > 0 ? 1 : 0
scale: extra > 0 ? 1 : 0.5
Elevation {
anchors.fill: parent
radius: parent.radius
opacity: parent.opacity
z: -1
level: 2
}
CustomText {
id: count
anchors.centerIn: parent
animate: parent.opacity > 0
text: qsTr("+%1").arg(parent.extra)
color: DynamicColors.palette.m3onTertiary
}
Behavior on opacity {
Anim {
duration: MaterialEasing.expressiveEffectsTime duration: MaterialEasing.expressiveEffectsTime
} }
} }
Behavior on scale {
Anim { Behavior on scale {
Anim {
duration: MaterialEasing.expressiveEffectsTime duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects easing.bezierCurve: MaterialEasing.expressiveEffects
} }
} }
Elevation {
anchors.fill: parent
level: 2
opacity: parent.opacity
radius: parent.radius
z: -1
}
CustomText {
id: count
anchors.centerIn: parent
animate: parent.opacity > 0
color: DynamicColors.palette.m3onTertiary
text: qsTr("+%1").arg(parent.extra)
}
} }
+113 -108
View File
@@ -1,141 +1,146 @@
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import qs.Helpers
import qs.Config import qs.Config
import qs.Modules
Slider { Slider {
id: root id: root
required property string icon
property real oldValue
property bool initialized
property color color: DynamicColors.palette.m3secondary property color color: DynamicColors.palette.m3secondary
required property string icon
property bool initialized
property real oldValue
orientation: Qt.Vertical orientation: Qt.Vertical
background: CustomRect { background: CustomRect {
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2) color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
radius: Appearance.rounding.full radius: Appearance.rounding.full
CustomRect { CustomRect {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
color: root.color
implicitHeight: parent.height - y
radius: parent.radius
y: root.handle.y
}
}
handle: Item {
id: handle
property alias moving: icon.moving y: root.handle.y
implicitHeight: parent.height - y
implicitHeight: root.width color: root.color
implicitWidth: root.width radius: parent.radius
y: root.visualPosition * (root.availableHeight - height) }
}
Elevation { handle: Item {
anchors.fill: parent id: handle
level: handleInteraction.containsMouse ? 2 : 1
radius: rect.radius
}
CustomRect { property alias moving: icon.moving
id: rect
anchors.fill: parent y: root.visualPosition * (root.availableHeight - height)
color: DynamicColors.palette.m3inverseSurface implicitWidth: root.width
radius: Appearance.rounding.full implicitHeight: root.width
MouseArea { Elevation {
id: handleInteraction anchors.fill: parent
radius: rect.radius
level: handleInteraction.containsMouse ? 2 : 1
}
acceptedButtons: Qt.NoButton CustomRect {
anchors.fill: parent id: rect
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
}
MaterialIcon { anchors.fill: parent
id: icon
property bool moving color: DynamicColors.palette.m3inverseSurface
radius: Appearance.rounding.full
function update(): void { MouseArea {
animate = !moving; id: handleInteraction
binding.when = moving;
font.pointSize = moving ? Appearance.font.size.small : Appearance.font.size.larger;
font.family = moving ? Appearance.font.family.sans : Appearance.font.family.material;
}
anchors.centerIn: parent anchors.fill: parent
color: DynamicColors.palette.m3inverseOnSurface hoverEnabled: true
text: root.icon cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.NoButton
}
onMovingChanged: anim.restart() MaterialIcon {
id: icon
Binding { property bool moving
id: binding
property: "text" function update(): void {
target: icon animate = !moving;
value: Math.round(root.value * 100) binding.when = moving;
when: false font.pointSize = moving ? Appearance.font.size.small : Appearance.font.size.larger;
} font.family = moving ? Appearance.font.family.sans : Appearance.font.family.material;
}
SequentialAnimation { text: root.icon
id: anim color: DynamicColors.palette.m3inverseOnSurface
anchors.centerIn: parent
Anim { onMovingChanged: anim.restart()
duration: Appearance.anim.durations.normal / 2
easing.bezierCurve: Appearance.anim.curves.standardAccel
property: "scale"
target: icon
to: 0
}
ScriptAction { Binding {
script: icon.update() id: binding
}
Anim { target: icon
duration: Appearance.anim.durations.normal / 2 property: "text"
easing.bezierCurve: Appearance.anim.curves.standardDecel value: Math.round(root.value * 100)
property: "scale" when: false
target: icon }
to: 1
}
}
}
}
}
Behavior on value {
Anim {
duration: Appearance.anim.durations.large
}
}
onPressedChanged: handle.moving = pressed SequentialAnimation {
onValueChanged: { id: anim
if (!initialized) {
initialized = true;
return;
}
if (Math.abs(value - oldValue) < 0.01)
return;
oldValue = value;
handle.moving = true;
stateChangeDelay.restart();
}
Timer { Anim {
id: stateChangeDelay target: icon
property: "scale"
to: 0
duration: Appearance.anim.durations.normal / 2
easing.bezierCurve: Appearance.anim.curves.standardAccel
}
ScriptAction {
script: icon.update()
}
Anim {
target: icon
property: "scale"
to: 1
duration: Appearance.anim.durations.normal / 2
easing.bezierCurve: Appearance.anim.curves.standardDecel
}
}
}
}
}
interval: 500 onPressedChanged: handle.moving = pressed
onTriggered: { onValueChanged: {
if (!root.pressed) if (!initialized) {
handle.moving = false; initialized = true;
} return;
} }
if (Math.abs(value - oldValue) < 0.01)
return;
oldValue = value;
handle.moving = true;
stateChangeDelay.restart();
}
Timer {
id: stateChangeDelay
interval: 500
onTriggered: {
if (!root.pressed)
handle.moving = false;
}
}
Behavior on value {
Anim {
duration: Appearance.anim.durations.large
}
}
} }
+64 -62
View File
@@ -1,80 +1,82 @@
import qs.Config import qs.Config
import qs.Modules
import QtQuick import QtQuick
CustomRect { CustomRect {
id: root id: root
enum Type { enum Type {
Filled, Filled,
Tonal, Tonal,
Text Text
} }
property color activeColour: type === IconButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondary property alias icon: label.text
property color activeOnColour: type === IconButton.Filled ? DynamicColors.palette.m3onPrimary : type === IconButton.Tonal ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3primary property bool checked
property bool checked property bool toggle
property bool disabled property real padding: type === IconButton.Text ? 10 / 2 : 7
property color disabledColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1) property alias font: label.font
property color disabledOnColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38) property int type: IconButton.Filled
property alias font: label.font property bool disabled
property alias icon: label.text
property color inactiveColour: {
if (!toggle && type === IconButton.Filled)
return DynamicColors.palette.m3primary;
return type === IconButton.Filled ? DynamicColors.tPalette.m3surfaceContainer : DynamicColors.palette.m3secondaryContainer;
}
property color inactiveOnColour: {
if (!toggle && type === IconButton.Filled)
return DynamicColors.palette.m3onPrimary;
return type === IconButton.Tonal ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurfaceVariant;
}
property bool internalChecked
property alias label: label
property real padding: type === IconButton.Text ? 10 / 2 : 7
property alias radiusAnim: radiusAnim
property alias stateLayer: stateLayer
property bool toggle
property int type: IconButton.Filled
signal clicked property alias stateLayer: stateLayer
property alias label: label
property alias radiusAnim: radiusAnim
color: type === IconButton.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour property bool internalChecked
implicitHeight: label.implicitHeight + padding * 2 property color activeColour: type === IconButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondary
implicitWidth: implicitHeight property color inactiveColour: {
radius: internalChecked ? 6 : implicitHeight / 2 * Math.min(1, 1) if (!toggle && type === IconButton.Filled)
return DynamicColors.palette.m3primary;
return type === IconButton.Filled ? DynamicColors.tPalette.m3surfaceContainer : DynamicColors.palette.m3secondaryContainer;
}
property color activeOnColour: type === IconButton.Filled ? DynamicColors.palette.m3onPrimary : type === IconButton.Tonal ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3primary
property color inactiveOnColour: {
if (!toggle && type === IconButton.Filled)
return DynamicColors.palette.m3onPrimary;
return type === IconButton.Tonal ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurfaceVariant;
}
property color disabledColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1)
property color disabledOnColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
Behavior on radius { signal clicked
Anim {
id: radiusAnim
} onCheckedChanged: internalChecked = checked
}
onCheckedChanged: internalChecked = checked radius: internalChecked ? 6 : implicitHeight / 2 * Math.min(1, 1)
color: type === IconButton.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour
StateLayer { implicitWidth: implicitHeight
id: stateLayer implicitHeight: label.implicitHeight + padding * 2
function onClicked(): void { StateLayer {
if (root.toggle) id: stateLayer
root.internalChecked = !root.internalChecked;
root.clicked();
}
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
disabled: root.disabled disabled: root.disabled
}
MaterialIcon { function onClicked(): void {
id: label if (root.toggle)
root.internalChecked = !root.internalChecked;
root.clicked();
}
}
anchors.centerIn: parent MaterialIcon {
color: root.disabled ? root.disabledOnColour : root.internalChecked ? root.activeOnColour : root.inactiveOnColour id: label
fill: !root.toggle || root.internalChecked ? 1 : 0
Behavior on fill { anchors.centerIn: parent
Anim { color: root.disabled ? root.disabledOnColour : root.internalChecked ? root.activeOnColour : root.inactiveOnColour
} fill: !root.toggle || root.internalChecked ? 1 : 0
}
} Behavior on fill {
Anim {}
}
}
Behavior on radius {
Anim {
id: radiusAnim
}
}
} }
-221
View File
@@ -1,221 +0,0 @@
import QtQuick
import qs.Config
Item {
id: root
property alias anim: marqueeAnim
property bool animate: false
property color color: DynamicColors.palette.m3onSurface
property int fadeStrengthAnimMs: 180
property real fadeStrengthIdle: 0.0
property real fadeStrengthMoving: 1.0
property alias font: elideText.font
property int gap: 40
property alias horizontalAlignment: elideText.horizontalAlignment
property bool leftFadeEnabled: false
property real leftFadeStrength: overflowing && leftFadeEnabled ? fadeStrengthMoving : fadeStrengthIdle
property int leftFadeWidth: 28
property bool marqueeEnabled: true
readonly property bool overflowing: metrics.width > root.width
property int pauseMs: 1200
property real pixelsPerSecond: 40
property real rightFadeStrength: overflowing ? fadeStrengthMoving : fadeStrengthIdle
property int rightFadeWidth: 28
property bool sliding: false
property alias text: elideText.text
function durationForDistance(px): int {
return Math.max(1, Math.round(Math.abs(px) / root.pixelsPerSecond * 1000));
}
function resetMarquee() {
marqueeAnim.stop();
strip.x = 0;
root.sliding = false;
root.leftFadeEnabled = false;
if (root.marqueeEnabled && root.overflowing && root.visible) {
marqueeAnim.restart();
}
}
clip: true
implicitHeight: elideText.implicitHeight
Behavior on leftFadeStrength {
Anim {
}
}
Behavior on rightFadeStrength {
Anim {
}
}
onTextChanged: resetMarquee()
onVisibleChanged: if (!visible)
resetMarquee()
onWidthChanged: resetMarquee()
TextMetrics {
id: metrics
font: elideText.font
text: elideText.text
}
CustomText {
id: elideText
anchors.verticalCenter: parent.verticalCenter
animate: root.animate
animateProp: "scale,opacity"
color: root.color
elide: Text.ElideNone
visible: !root.overflowing
width: root.width
}
Item {
id: marqueeViewport
anchors.fill: parent
clip: true
layer.enabled: true
visible: root.overflowing
layer.effect: OpacityMask {
maskSource: rightFadeMask
}
Item {
id: strip
anchors.verticalCenter: parent.verticalCenter
height: t1.implicitHeight
width: t1.width + root.gap + t2.width
x: 0
CustomText {
id: t1
animate: root.animate
animateProp: "opacity"
color: root.color
text: elideText.text
}
CustomText {
id: t2
animate: root.animate
animateProp: "opacity"
color: root.color
text: t1.text
x: t1.width + root.gap
}
}
SequentialAnimation {
id: marqueeAnim
running: false
onFinished: pauseTimer.restart()
ScriptAction {
script: {
root.sliding = true;
root.leftFadeEnabled = true;
}
}
Anim {
duration: root.durationForDistance(t1.width)
easing.bezierCurve: Easing.Linear
easing.type: Easing.Linear
from: 0
property: "x"
target: strip
to: -t1.width
}
ScriptAction {
script: {
root.leftFadeEnabled = false;
}
}
Anim {
duration: root.durationForDistance(root.gap)
easing.bezierCurve: Easing.Linear
easing.type: Easing.Linear
from: -t1.width
property: "x"
target: strip
to: -(t1.width + root.gap)
}
ScriptAction {
script: {
root.sliding = false;
strip.x = 0;
}
}
}
Timer {
id: pauseTimer
interval: root.pauseMs
repeat: false
running: true
onTriggered: {
if (root.marqueeEnabled)
marqueeAnim.start();
}
}
}
Rectangle {
id: rightFadeMask
readonly property real fadeStartPos: {
const w = Math.max(1, width);
return Math.max(0, Math.min(1, (w - root.rightFadeWidth) / w));
}
readonly property real leftFadeEndPos: {
const w = Math.max(1, width);
return Math.max(0, Math.min(1, root.leftFadeWidth / w));
}
anchors.fill: marqueeViewport
layer.enabled: true
visible: false
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0 - root.leftFadeStrength)
position: 0.0
}
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0)
position: rightFadeMask.leftFadeEndPos
}
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0)
position: rightFadeMask.fadeStartPos
}
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0 - root.rightFadeStrength)
position: 1.0
}
}
}
}
+10 -10
View File
@@ -1,15 +1,15 @@
import qs.Config import qs.Config
CustomText { CustomText {
property real fill property real fill
property int grade: DynamicColors.light ? 0 : -25 property int grade: DynamicColors.light ? 0 : -25
font.family: "Material Symbols Rounded" font.family: "Material Symbols Rounded"
font.pointSize: 15 font.pointSize: 15
font.variableAxes: ({ font.variableAxes: ({
FILL: fill.toFixed(1), FILL: fill.toFixed(1),
GRAD: grade, GRAD: grade,
opsz: fontInfo.pixelSize, opsz: fontInfo.pixelSize,
wght: fontInfo.weight wght: fontInfo.weight
}) })
} }
-107
View File
@@ -1,107 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
Elevation {
id: root
property MenuItem active: items[0] ?? null
property bool expanded
property list<MenuItem> items
signal itemSelected(item: MenuItem)
implicitHeight: root.expanded ? column.implicitHeight : 0
implicitWidth: Math.max(200, column.implicitWidth)
level: 2
opacity: root.expanded ? 1 : 0
radius: Appearance.rounding.small / 2
Behavior on implicitHeight {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
}
}
CustomClippingRect {
anchors.fill: parent
color: DynamicColors.palette.m3surfaceContainer
radius: parent.radius
ColumnLayout {
id: column
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
Repeater {
model: root.items
CustomRect {
id: item
readonly property bool active: modelData === root.active
required property int index
required property MenuItem modelData
Layout.fillWidth: true
color: Qt.alpha(DynamicColors.palette.m3secondaryContainer, active ? 1 : 0)
implicitHeight: menuOptionRow.implicitHeight + Appearance.padding.normal * 2
implicitWidth: menuOptionRow.implicitWidth + Appearance.padding.normal * 2
StateLayer {
function onClicked(): void {
root.itemSelected(item.modelData);
root.active = item.modelData;
root.expanded = false;
}
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
disabled: !root.expanded
}
RowLayout {
id: menuOptionRow
anchors.fill: parent
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.small
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurfaceVariant
text: item.modelData.icon
}
CustomText {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
text: item.modelData.text
}
Loader {
Layout.alignment: Qt.AlignVCenter
active: item.modelData.trailingIcon.length > 0
visible: active
sourceComponent: MaterialIcon {
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
text: item.modelData.trailingIcon
}
}
}
}
}
}
}
}
-12
View File
@@ -1,12 +0,0 @@
import QtQuick
QtObject {
property string activeIcon: icon
property string activeText: text
property string icon
required property string text
property string trailingIcon
property var value
signal clicked
}
+3 -3
View File
@@ -2,8 +2,8 @@ import Quickshell
import QtQuick import QtQuick
ShaderEffect { ShaderEffect {
required property Item maskSource required property Item source
required property Item source required property Item maskSource
fragmentShader: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/shaders/opacitymask.frag.qsb`) fragmentShader: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/shaders/opacitymask.frag.qsb`)
} }
+3 -3
View File
@@ -1,8 +1,8 @@
import QtQuick import QtQuick
QtObject { QtObject {
required property var service required property var service
Component.onCompleted: service.refCount++ Component.onCompleted: service.refCount++
Component.onDestruction: service.refCount-- Component.onDestruction: service.refCount--
} }
-50
View File
@@ -1,50 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
CustomRect {
id: root
required property string label
required property real max
required property real min
property var onValueModified: function (value) {}
property real step: 1
required property real value
Layout.fillWidth: true
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
implicitHeight: row.implicitHeight + Appearance.padding.large * 2
radius: Appearance.rounding.normal
Behavior on implicitHeight {
Anim {
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.normal
CustomText {
Layout.fillWidth: true
text: root.label
}
CustomSpinBox {
max: root.max
min: root.min
step: root.step
value: root.value
onValueModified: value => {
root.onValueModified(value);
}
}
}
}
+72 -72
View File
@@ -1,96 +1,96 @@
import qs.Config import qs.Config
import qs.Modules
import QtQuick import QtQuick
MouseArea { MouseArea {
id: root id: root
property color color: DynamicColors.palette.m3onSurface property bool disabled
property bool disabled property color color: DynamicColors.palette.m3onSurface
property real radius: parent?.radius ?? 0 property real radius: parent?.radius ?? 0
property alias rect: hoverLayer property alias rect: hoverLayer
function onClicked(): void { function onClicked(): void {
} }
anchors.fill: parent anchors.fill: parent
cursorShape: disabled ? undefined : Qt.PointingHandCursor
enabled: !disabled
hoverEnabled: true
onClicked: event => !disabled && onClicked(event) enabled: !disabled
onPressed: event => { cursorShape: disabled ? undefined : Qt.PointingHandCursor
if (disabled) hoverEnabled: true
return;
rippleAnim.x = event.x; onPressed: event => {
rippleAnim.y = event.y; if (disabled)
return;
const dist = (ox, oy) => ox * ox + oy * oy; rippleAnim.x = event.x;
rippleAnim.radius = Math.sqrt(Math.max(dist(event.x, event.y), dist(event.x, height - event.y), dist(width - event.x, event.y), dist(width - event.x, height - event.y))); rippleAnim.y = event.y;
rippleAnim.restart(); const dist = (ox, oy) => ox * ox + oy * oy;
} rippleAnim.radius = Math.sqrt(Math.max(dist(event.x, event.y), dist(event.x, height - event.y), dist(width - event.x, event.y), dist(width - event.x, height - event.y)));
SequentialAnimation { rippleAnim.restart();
id: rippleAnim }
property real radius onClicked: event => !disabled && onClicked(event)
property real x
property real y
PropertyAction { SequentialAnimation {
property: "x" id: rippleAnim
target: ripple
value: rippleAnim.x
}
PropertyAction { property real x
property: "y" property real y
target: ripple property real radius
value: rippleAnim.y
}
PropertyAction { PropertyAction {
property: "opacity" target: ripple
target: ripple property: "x"
value: 0.08 value: rippleAnim.x
} }
PropertyAction {
target: ripple
property: "y"
value: rippleAnim.y
}
PropertyAction {
target: ripple
property: "opacity"
value: 0.08
}
Anim {
target: ripple
properties: "implicitWidth,implicitHeight"
from: 0
to: rippleAnim.radius * 2
easing.bezierCurve: MaterialEasing.standardDecel
}
Anim {
target: ripple
property: "opacity"
to: 0
}
}
Anim { CustomClippingRect {
easing.bezierCurve: MaterialEasing.standardDecel id: hoverLayer
from: 0
properties: "implicitWidth,implicitHeight"
target: ripple
to: rippleAnim.radius * 2
}
Anim { anchors.fill: parent
property: "opacity"
target: ripple
to: 0
}
}
CustomClippingRect {
id: hoverLayer
anchors.fill: parent
border.pixelAligned: false border.pixelAligned: false
color: Qt.alpha(root.color, root.disabled ? 0 : root.pressed ? 0.1 : root.containsMouse ? 0.08 : 0)
radius: root.radius
CustomRect { color: Qt.alpha(root.color, root.disabled ? 0 : root.pressed ? 0.1 : root.containsMouse ? 0.08 : 0)
id: ripple radius: root.radius
CustomRect {
id: ripple
radius: 1000
color: root.color
opacity: 0
border.pixelAligned: false border.pixelAligned: false
color: root.color
opacity: 0
radius: 1000
transform: Translate { transform: Translate {
x: -ripple.width / 2 x: -ripple.width / 2
y: -ripple.height / 2 y: -ripple.height / 2
} }
} }
} }
} }
+114 -110
View File
@@ -1,131 +1,135 @@
import ZShell import ZShell
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Modules
import qs.Components import qs.Components
import qs.Helpers
import qs.Config import qs.Config
CustomRect { CustomRect {
id: root id: root
required property Toast modelData required property Toast modelData
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
border.color: { implicitHeight: layout.implicitHeight + Appearance.padding.smaller * 2
let colour = DynamicColors.palette.m3outlineVariant;
if (root.modelData.type === Toast.Success)
colour = DynamicColors.palette.m3success;
if (root.modelData.type === Toast.Warning)
colour = DynamicColors.palette.m3secondaryContainer;
if (root.modelData.type === Toast.Error)
colour = DynamicColors.palette.m3error;
return Qt.alpha(colour, 0.3);
}
border.width: 1
color: {
if (root.modelData.type === Toast.Success)
return DynamicColors.palette.m3successContainer;
if (root.modelData.type === Toast.Warning)
return DynamicColors.palette.m3secondary;
if (root.modelData.type === Toast.Error)
return DynamicColors.palette.m3errorContainer;
return DynamicColors.palette.m3surface;
}
implicitHeight: layout.implicitHeight + Appearance.padding.smaller * 2
radius: Appearance.rounding.normal
Behavior on border.color { radius: Appearance.rounding.normal
CAnim { color: {
} if (root.modelData.type === Toast.Success)
} return DynamicColors.palette.m3successContainer;
if (root.modelData.type === Toast.Warning)
return DynamicColors.palette.m3secondary;
if (root.modelData.type === Toast.Error)
return DynamicColors.palette.m3errorContainer;
return DynamicColors.palette.m3surface;
}
Elevation { border.width: 1
anchors.fill: parent border.color: {
level: 3 let colour = DynamicColors.palette.m3outlineVariant;
opacity: parent.opacity if (root.modelData.type === Toast.Success)
radius: parent.radius colour = DynamicColors.palette.m3success;
z: -1 if (root.modelData.type === Toast.Warning)
} colour = DynamicColors.palette.m3secondaryContainer;
if (root.modelData.type === Toast.Error)
colour = DynamicColors.palette.m3error;
return Qt.alpha(colour, 0.3);
}
RowLayout { Elevation {
id: layout anchors.fill: parent
radius: parent.radius
opacity: parent.opacity
z: -1
level: 3
}
anchors.fill: parent RowLayout {
anchors.leftMargin: Appearance.padding.normal id: layout
anchors.margins: Appearance.padding.smaller
anchors.rightMargin: Appearance.padding.normal
spacing: Appearance.spacing.normal
CustomRect { anchors.fill: parent
color: { anchors.margins: Appearance.padding.smaller
if (root.modelData.type === Toast.Success) anchors.leftMargin: Appearance.padding.normal
return DynamicColors.palette.m3success; anchors.rightMargin: Appearance.padding.normal
if (root.modelData.type === Toast.Warning) spacing: Appearance.spacing.normal
return DynamicColors.palette.m3secondaryContainer;
if (root.modelData.type === Toast.Error)
return DynamicColors.palette.m3error;
return DynamicColors.palette.m3surfaceContainerHigh;
}
implicitHeight: icon.implicitHeight + Appearance.padding.smaller * 2
implicitWidth: implicitHeight
radius: Appearance.rounding.normal
MaterialIcon { CustomRect {
id: icon radius: Appearance.rounding.normal
color: {
if (root.modelData.type === Toast.Success)
return DynamicColors.palette.m3success;
if (root.modelData.type === Toast.Warning)
return DynamicColors.palette.m3secondaryContainer;
if (root.modelData.type === Toast.Error)
return DynamicColors.palette.m3error;
return DynamicColors.palette.m3surfaceContainerHigh;
}
anchors.centerIn: parent implicitWidth: implicitHeight
color: { implicitHeight: icon.implicitHeight + Appearance.padding.smaller * 2
if (root.modelData.type === Toast.Success)
return DynamicColors.palette.m3onSuccess;
if (root.modelData.type === Toast.Warning)
return DynamicColors.palette.m3onSecondaryContainer;
if (root.modelData.type === Toast.Error)
return DynamicColors.palette.m3onError;
return DynamicColors.palette.m3onSurfaceVariant;
}
font.pointSize: Math.round(Appearance.font.size.large * 1.2)
text: root.modelData.icon
}
}
ColumnLayout { MaterialIcon {
Layout.fillWidth: true id: icon
spacing: 0
CustomText { anchors.centerIn: parent
id: title text: root.modelData.icon
color: {
if (root.modelData.type === Toast.Success)
return DynamicColors.palette.m3onSuccess;
if (root.modelData.type === Toast.Warning)
return DynamicColors.palette.m3onSecondaryContainer;
if (root.modelData.type === Toast.Error)
return DynamicColors.palette.m3onError;
return DynamicColors.palette.m3onSurfaceVariant;
}
font.pointSize: Math.round(Appearance.font.size.large * 1.2)
}
}
Layout.fillWidth: true ColumnLayout {
color: { Layout.fillWidth: true
if (root.modelData.type === Toast.Success) spacing: 0
return DynamicColors.palette.m3onSuccessContainer;
if (root.modelData.type === Toast.Warning)
return DynamicColors.palette.m3onSecondary;
if (root.modelData.type === Toast.Error)
return DynamicColors.palette.m3onErrorContainer;
return DynamicColors.palette.m3onSurface;
}
elide: Text.ElideRight
font.pointSize: Appearance.font.size.normal
text: root.modelData.title
}
CustomText { CustomText {
Layout.fillWidth: true id: title
color: {
if (root.modelData.type === Toast.Success) Layout.fillWidth: true
return DynamicColors.palette.m3onSuccessContainer; text: root.modelData.title
if (root.modelData.type === Toast.Warning) color: {
return DynamicColors.palette.m3onSecondary; if (root.modelData.type === Toast.Success)
if (root.modelData.type === Toast.Error) return DynamicColors.palette.m3onSuccessContainer;
return DynamicColors.palette.m3onErrorContainer; if (root.modelData.type === Toast.Warning)
return DynamicColors.palette.m3onSurface; return DynamicColors.palette.m3onSecondary;
} if (root.modelData.type === Toast.Error)
elide: Text.ElideRight return DynamicColors.palette.m3onErrorContainer;
opacity: 0.8 return DynamicColors.palette.m3onSurface;
text: root.modelData.message }
textFormat: Text.StyledText font.pointSize: Appearance.font.size.normal
} elide: Text.ElideRight
} }
}
CustomText {
Layout.fillWidth: true
textFormat: Text.StyledText
text: root.modelData.message
color: {
if (root.modelData.type === Toast.Success)
return DynamicColors.palette.m3onSuccessContainer;
if (root.modelData.type === Toast.Warning)
return DynamicColors.palette.m3onSecondary;
if (root.modelData.type === Toast.Error)
return DynamicColors.palette.m3onErrorContainer;
return DynamicColors.palette.m3onSurface;
}
opacity: 0.8
elide: Text.ElideRight
}
}
}
Behavior on border.color {
CAnim {}
}
} }
+116 -114
View File
@@ -1,142 +1,144 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import qs.Components
import qs.Config
import qs.Modules
import ZShell import ZShell
import Quickshell import Quickshell
import QtQuick import QtQuick
import qs.Components
import qs.Config
Item { Item {
id: root id: root
property bool flag readonly property int spacing: Appearance.spacing.small
readonly property int spacing: Appearance.spacing.small property bool flag
implicitHeight: { implicitWidth: Config.utilities.sizes.toastWidth - Appearance.padding.normal * 2
let h = -spacing; implicitHeight: {
for (let i = 0; i < repeater.count; i++) { let h = -spacing;
const item = repeater.itemAt(i) as ToastWrapper; for (let i = 0; i < repeater.count; i++) {
if (!item.modelData.closed && !item.previewHidden) const item = repeater.itemAt(i) as ToastWrapper;
h += item.implicitHeight + spacing; if (!item.modelData.closed && !item.previewHidden)
} h += item.implicitHeight + spacing;
return h; }
} return h;
implicitWidth: Config.utilities.sizes.toastWidth - Appearance.padding.normal * 2 }
Repeater { Repeater {
id: repeater id: repeater
model: ScriptModel { model: ScriptModel {
values: { values: {
const toasts = []; const toasts = [];
let count = 0; let count = 0;
for (const toast of Toaster.toasts) { for (const toast of Toaster.toasts) {
toasts.push(toast); toasts.push(toast);
if (!toast.closed) { if (!toast.closed) {
count++; count++;
if (count > Config.utilities.maxToasts) if (count > Config.utilities.maxToasts)
break; break;
} }
} }
return toasts; return toasts;
} }
onValuesChanged: root.flagChanged()
}
onValuesChanged: root.flagChanged() ToastWrapper {}
} }
ToastWrapper { component ToastWrapper: MouseArea {
} id: toast
}
component ToastWrapper: MouseArea { required property int index
id: toast required property Toast modelData
required property int index readonly property bool previewHidden: {
required property Toast modelData let extraHidden = 0;
readonly property bool previewHidden: { for (let i = 0; i < index; i++)
let extraHidden = 0; if (Toaster.toasts[i].closed)
for (let i = 0; i < index; i++) extraHidden++;
if (Toaster.toasts[i].closed) return index >= Config.utilities.maxToasts + extraHidden;
extraHidden++; }
return index >= Config.utilities.maxToasts + extraHidden;
}
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton onPreviewHiddenChanged: {
anchors.bottom: parent.bottom if (initAnim.running && previewHidden)
anchors.bottomMargin: { initAnim.stop();
root.flag; // Force update }
let y = 0;
for (let i = 0; i < index; i++) {
const item = repeater.itemAt(i) as ToastWrapper;
if (item && !item.modelData.closed && !item.previewHidden)
y += item.implicitHeight + root.spacing;
}
return y;
}
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: toastInner.implicitHeight
opacity: modelData.closed || previewHidden ? 0 : 1
scale: modelData.closed || previewHidden ? 0.7 : 1
Behavior on anchors.bottomMargin { opacity: modelData.closed || previewHidden ? 0 : 1
Anim { scale: modelData.closed || previewHidden ? 0.7 : 1
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Component.onCompleted: modelData.lock(this) anchors.bottomMargin: {
onClicked: modelData.close() root.flag; // Force update
onPreviewHiddenChanged: { let y = 0;
if (initAnim.running && previewHidden) for (let i = 0; i < index; i++) {
initAnim.stop(); const item = repeater.itemAt(i) as ToastWrapper;
} if (item && !item.modelData.closed && !item.previewHidden)
y += item.implicitHeight + root.spacing;
}
return y;
}
Anim { anchors.left: parent.left
id: initAnim anchors.right: parent.right
anchors.bottom: parent.bottom
implicitHeight: toastInner.implicitHeight
duration: Appearance.anim.durations.expressiveDefaultSpatial acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial onClicked: modelData.close()
from: 0
properties: "opacity,scale"
target: toast
to: 1
Component.onCompleted: running = !toast.previewHidden Component.onCompleted: modelData.lock(this)
}
ParallelAnimation { Anim {
running: toast.modelData.closed id: initAnim
onFinished: toast.modelData.unlock(toast) Component.onCompleted: running = !toast.previewHidden
onStarted: toast.anchors.bottomMargin = toast.anchors.bottomMargin
Anim { target: toast
property: "opacity" properties: "opacity,scale"
target: toast from: 0
to: 0 to: 1
} duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
Anim { ParallelAnimation {
property: "scale" running: toast.modelData.closed
target: toast onStarted: toast.anchors.bottomMargin = toast.anchors.bottomMargin
to: 0.7 onFinished: toast.modelData.unlock(toast)
}
}
ToastItem { Anim {
id: toastInner target: toast
property: "opacity"
to: 0
}
Anim {
target: toast
property: "scale"
to: 0.7
}
}
modelData: toast.modelData ToastItem {
} id: toastInner
}
modelData: toast.modelData
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {}
}
Behavior on anchors.bottomMargin {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
}
} }
+7 -8
View File
@@ -1,13 +1,12 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property Accents accents: Accents { property Accents accents: Accents {}
}
component Accents: JsonObject { component Accents: JsonObject {
property string primary: "#4080ff" property string primary: "#4080ff"
property string primaryAlt: "#60a0ff" property string primaryAlt: "#60a0ff"
property string warning: "#ff6b6b" property string warning: "#ff6b6b"
property string warningAlt: "#ff8787" property string warningAlt: "#ff8787"
} }
} }
+8 -8
View File
@@ -3,12 +3,12 @@ pragma Singleton
import Quickshell import Quickshell
Singleton { Singleton {
readonly property AppearanceConf.Anim anim: Config.appearance.anim // Literally just here to shorten accessing stuff :woe:
readonly property AppearanceConf.FontStuff font: Config.appearance.font // Also kinda so I can keep accessing it with `Appearance.xxx` instead of `Conf.appearance.xxx`
readonly property AppearanceConf.Padding padding: Config.appearance.padding readonly property AppearanceConf.Rounding rounding: Config.appearance.rounding
// Literally just here to shorten accessing stuff :woe: readonly property AppearanceConf.Spacing spacing: Config.appearance.spacing
// Also kinda so I can keep accessing it with `Appearance.xxx` instead of `Conf.appearance.xxx` readonly property AppearanceConf.Padding padding: Config.appearance.padding
readonly property AppearanceConf.Rounding rounding: Config.appearance.rounding readonly property AppearanceConf.FontStuff font: Config.appearance.font
readonly property AppearanceConf.Spacing spacing: Config.appearance.spacing readonly property AppearanceConf.Anim anim: Config.appearance.anim
readonly property AppearanceConf.Transparency transparency: Config.appearance.transparency readonly property AppearanceConf.Transparency transparency: Config.appearance.transparency
} }
+89 -90
View File
@@ -1,95 +1,94 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property Anim anim: Anim { property Rounding rounding: Rounding {}
} property Spacing spacing: Spacing {}
property FontStuff font: FontStuff { property Padding padding: Padding {}
} property FontStuff font: FontStuff {}
property Padding padding: Padding { property Anim anim: Anim {}
} property Transparency transparency: Transparency {}
property Rounding rounding: Rounding {
}
property Spacing spacing: Spacing {
}
property Transparency transparency: Transparency {
}
component Anim: JsonObject { component Rounding: JsonObject {
property AnimCurves curves: AnimCurves { property real scale: 1
} property int small: 12 * scale
property AnimDurations durations: AnimDurations { property int normal: 17 * scale
} property int large: 25 * scale
property real mediaGifSpeedAdjustment: 300 property int full: 1000 * scale
property real sessionGifSpeed: 0.7 }
}
component AnimCurves: JsonObject { component Spacing: JsonObject {
property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] property real scale: 1
property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] property int small: 7 * scale
property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1] property int smaller: 10 * scale
property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1] property int normal: 12 * scale
property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1] property int larger: 15 * scale
property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1] property int large: 20 * scale
property list<real> standard: [0.2, 0, 0, 1, 1, 1] }
property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
property list<real> standardDecel: [0, 0, 0, 1, 1, 1] component Padding: JsonObject {
} property real scale: 1
component AnimDurations: JsonObject { property int small: 5 * scale
property int expressiveDefaultSpatial: 500 * scale property int smaller: 7 * scale
property int expressiveEffects: 200 * scale property int normal: 10 * scale
property int expressiveFastSpatial: 350 * scale property int larger: 12 * scale
property int extraLarge: 1000 * scale property int large: 15 * scale
property int large: 600 * scale }
property int normal: 400 * scale
property real scale: 1 component FontFamily: JsonObject {
property int small: 200 * scale property string sans: "Segoe UI Variable Text"
} property string mono: "CaskaydiaCove NF"
component FontFamily: JsonObject { property string material: "Material Symbols Rounded"
property string clock: "Rubik" property string clock: "Rubik"
property string material: "Material Symbols Rounded" }
property string mono: "CaskaydiaCove NF"
property string sans: "Segoe UI Variable Text" component FontSize: JsonObject {
} property real scale: 1
component FontSize: JsonObject { property int small: 11 * scale
property int extraLarge: 28 * scale property int smaller: 12 * scale
property int large: 18 * scale property int normal: 13 * scale
property int larger: 15 * scale property int larger: 15 * scale
property int normal: 13 * scale property int large: 18 * scale
property real scale: 1 property int extraLarge: 28 * scale
property int small: 11 * scale }
property int smaller: 12 * scale
} component FontStuff: JsonObject {
component FontStuff: JsonObject { property FontFamily family: FontFamily {}
property FontFamily family: FontFamily { property FontSize size: FontSize {}
} }
property FontSize size: FontSize {
} component AnimCurves: JsonObject {
} property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
component Padding: JsonObject { property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
property int large: 15 * scale property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
property int larger: 12 * scale property list<real> standard: [0.2, 0, 0, 1, 1, 1]
property int normal: 10 * scale property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
property real scale: 1 property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
property int small: 5 * scale property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1]
property int smaller: 7 * scale property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1]
} property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1]
component Rounding: JsonObject { }
property int full: 1000 * scale
property int large: 25 * scale component AnimDurations: JsonObject {
property int normal: 17 * scale property real scale: 1
property real scale: 1 property int small: 200 * scale
property int small: 12 * scale property int normal: 400 * scale
} property int large: 600 * scale
component Spacing: JsonObject { property int extraLarge: 1000 * scale
property int large: 20 * scale property int expressiveFastSpatial: 350 * scale
property int larger: 15 * scale property int expressiveDefaultSpatial: 500 * scale
property int normal: 12 * scale property int expressiveEffects: 200 * scale
property real scale: 1 }
property int small: 7 * scale
property int smaller: 10 * scale component Anim: JsonObject {
} property real mediaGifSpeedAdjustment: 300
component Transparency: JsonObject { property real sessionGifSpeed: 0.7
property real base: 0.85 property AnimCurves curves: AnimCurves {}
property bool enabled: false property AnimDurations durations: AnimDurations {}
property real layers: 0.4 }
}
component Transparency: JsonObject {
property bool enabled: false
property real base: 0.85
property real layers: 0.4
}
} }
+2 -2
View File
@@ -2,6 +2,6 @@ import Quickshell.Io
import qs.Config import qs.Config
JsonObject { JsonObject {
property bool enabled: true property int wallFadeDuration: MaterialEasing.standardTime
property int wallFadeDuration: MaterialEasing.standardTime property bool enabled: true
} }
+6 -10
View File
@@ -2,6 +2,9 @@ import Quickshell.Io
JsonObject { JsonObject {
property bool autoHide: false property bool autoHide: false
property int rounding: 8
property Popouts popouts: Popouts {}
property list<var> entries: [ property list<var> entries: [
{ {
id: "workspaces", id: "workspaces",
@@ -11,10 +14,6 @@ JsonObject {
id: "audio", id: "audio",
enabled: true enabled: true
}, },
{
id: "media",
enabled: true
},
{ {
id: "resources", id: "resources",
enabled: true enabled: true
@@ -60,17 +59,14 @@ JsonObject {
enabled: true enabled: true
}, },
] ]
property Popouts popouts: Popouts {
}
property int rounding: 8
component Popouts: JsonObject { component Popouts: JsonObject {
property bool activeWindow: true property bool tray: true
property bool audio: true property bool audio: true
property bool activeWindow: true
property bool resources: true
property bool clock: true property bool clock: true
property bool network: true property bool network: true
property bool resources: true
property bool tray: true
property bool upower: true property bool upower: true
} }
} }
+296 -320
View File
@@ -4,27 +4,27 @@ import Quickshell
import Quickshell.Io import Quickshell.Io
import ZShell import ZShell
import QtQuick import QtQuick
import qs.Modules as Modules
import qs.Helpers import qs.Helpers
import qs.Paths import qs.Paths
Singleton { Singleton {
id: root id: root
property alias appearance: adapter.appearance property alias background: adapter.background
property alias background: adapter.background property alias barConfig: adapter.barConfig
property alias barConfig: adapter.barConfig property alias lock: adapter.lock
property alias colors: adapter.colors
property alias dashboard: adapter.dashboard
property alias general: adapter.general
property alias launcher: adapter.launcher
property alias lock: adapter.lock
property alias notifs: adapter.notifs
property alias osd: adapter.osd
property alias overview: adapter.overview property alias overview: adapter.overview
property bool recentlySaved: false
property alias services: adapter.services property alias services: adapter.services
property alias notifs: adapter.notifs
property alias sidebar: adapter.sidebar property alias sidebar: adapter.sidebar
property alias utilities: adapter.utilities property alias utilities: adapter.utilities
property alias general: adapter.general
property alias dashboard: adapter.dashboard
property alias appearance: adapter.appearance
property alias osd: adapter.osd
property alias launcher: adapter.launcher
property alias colors: adapter.colors
function save(): void { function save(): void {
saveTimer.restart(); saveTimer.restart();
@@ -32,278 +32,16 @@ Singleton {
recentSaveCooldown.restart(); recentSaveCooldown.restart();
} }
function serializeAppearance(): var { property bool recentlySaved: false
return {
rounding: {
scale: appearance.rounding.scale
},
spacing: {
scale: appearance.spacing.scale
},
padding: {
scale: appearance.padding.scale
},
font: {
family: {
sans: appearance.font.family.sans,
mono: appearance.font.family.mono,
material: appearance.font.family.material,
clock: appearance.font.family.clock
},
size: {
scale: appearance.font.size.scale
}
},
anim: {
mediaGifSpeedAdjustment: 300,
sessionGifSpeed: 0.7,
durations: {
scale: appearance.anim.durations.scale
}
},
transparency: {
enabled: appearance.transparency.enabled,
base: appearance.transparency.base,
layers: appearance.transparency.layers
}
};
}
function serializeBackground(): var {
return {
wallFadeDuration: background.wallFadeDuration,
enabled: background.enabled
};
}
function serializeBar(): var {
return {
autoHide: barConfig.autoHide,
rounding: barConfig.rounding,
popouts: {
tray: barConfig.popouts.tray,
audio: barConfig.popouts.audio,
activeWindow: barConfig.popouts.activeWindow,
resources: barConfig.popouts.resources,
clock: barConfig.popouts.clock,
network: barConfig.popouts.network,
upower: barConfig.popouts.upower
},
entries: barConfig.entries
};
}
function serializeColors(): var {
return {
schemeType: colors.schemeType
};
}
function serializeConfig(): var {
return {
barConfig: serializeBar(),
lock: serializeLock(),
general: serializeGeneral(),
services: serializeServices(),
notifs: serializeNotifs(),
sidebar: serializeSidebar(),
utilities: serializeUtilities(),
dashboard: serializeDashboard(),
appearance: serializeAppearance(),
osd: serializeOsd(),
background: serializeBackground(),
launcher: serializeLauncher(),
colors: serializeColors()
};
}
function serializeDashboard(): var {
return {
enabled: dashboard.enabled,
mediaUpdateInterval: dashboard.mediaUpdateInterval,
resourceUpdateInterval: dashboard.resourceUpdateInterval,
dragThreshold: dashboard.dragThreshold,
performance: {
showBattery: dashboard.performance.showBattery,
showGpu: dashboard.performance.showGpu,
showCpu: dashboard.performance.showCpu,
showMemory: dashboard.performance.showMemory,
showStorage: dashboard.performance.showStorage,
showNetwork: dashboard.performance.showNetwork
},
sizes: {
tabIndicatorHeight: dashboard.sizes.tabIndicatorHeight,
tabIndicatorSpacing: dashboard.sizes.tabIndicatorSpacing,
infoWidth: dashboard.sizes.infoWidth,
infoIconSize: dashboard.sizes.infoIconSize,
dateTimeWidth: dashboard.sizes.dateTimeWidth,
mediaWidth: dashboard.sizes.mediaWidth,
mediaProgressSweep: dashboard.sizes.mediaProgressSweep,
mediaProgressThickness: dashboard.sizes.mediaProgressThickness,
resourceProgessThickness: dashboard.sizes.resourceProgessThickness,
weatherWidth: dashboard.sizes.weatherWidth,
mediaCoverArtSize: dashboard.sizes.mediaCoverArtSize,
mediaVisualiserSize: dashboard.sizes.mediaVisualiserSize,
resourceSize: dashboard.sizes.resourceSize
}
};
}
function serializeGeneral(): var {
return {
logo: general.logo,
wallpaperPath: general.wallpaperPath,
color: {
wallust: general.color.wallust,
mode: general.color.mode,
schemeGeneration: general.color.schemeGeneration,
scheduleDarkStart: general.color.scheduleDarkStart,
scheduleDarkEnd: general.color.scheduleDarkEnd,
neovimColors: general.color.neovimColors
},
apps: {
terminal: general.apps.terminal,
audio: general.apps.audio,
playback: general.apps.playback,
explorer: general.apps.explorer
},
idle: {
timouts: general.idle.timeouts
}
};
}
function serializeLauncher(): var {
return {
maxAppsShown: launcher.maxAppsShown,
maxWallpapers: launcher.maxWallpapers,
actionPrefix: launcher.actionPrefix,
specialPrefix: launcher.specialPrefix,
useFuzzy: {
apps: launcher.useFuzzy.apps,
actions: launcher.useFuzzy.actions,
schemes: launcher.useFuzzy.schemes,
variants: launcher.useFuzzy.variants,
wallpapers: launcher.useFuzzy.wallpapers
},
sizes: {
itemWidth: launcher.sizes.itemWidth,
itemHeight: launcher.sizes.itemHeight,
wallpaperWidth: launcher.sizes.wallpaperWidth,
wallpaperHeight: launcher.sizes.wallpaperHeight
},
actions: launcher.actions
};
}
function serializeLock(): var {
return {
recolorLogo: lock.recolorLogo,
enableFprint: lock.enableFprint,
maxFprintTries: lock.maxFprintTries,
blurAmount: lock.blurAmount,
sizes: {
heightMult: lock.sizes.heightMult,
ratio: lock.sizes.ratio,
centerWidth: lock.sizes.centerWidth
}
};
}
function serializeNotifs(): var {
return {
expire: notifs.expire,
defaultExpireTimeout: notifs.defaultExpireTimeout,
appNotifCooldown: notifs.appNotifCooldown,
clearThreshold: notifs.clearThreshold,
expandThreshold: notifs.expandThreshold,
actionOnClick: notifs.actionOnClick,
groupPreviewNum: notifs.groupPreviewNum,
sizes: {
width: notifs.sizes.width,
image: notifs.sizes.image,
badge: notifs.sizes.badge
}
};
}
function serializeOsd(): var {
return {
enabled: osd.enabled,
hideDelay: osd.hideDelay,
enableBrightness: osd.enableBrightness,
enableMicrophone: osd.enableMicrophone,
allMonBrightness: osd.allMonBrightness,
sizes: {
sliderWidth: osd.sizes.sliderWidth,
sliderHeight: osd.sizes.sliderHeight
}
};
}
function serializeServices(): var {
return {
weatherLocation: services.weatherLocation,
useFahrenheit: services.useFahrenheit,
ddcutilService: services.ddcutilService,
useTwelveHourClock: services.useTwelveHourClock,
gpuType: services.gpuType,
audioIncrement: services.audioIncrement,
brightnessIncrement: services.brightnessIncrement,
maxVolume: services.maxVolume,
defaultPlayer: services.defaultPlayer,
playerAliases: services.playerAliases,
visualizerBars: services.visualizerBars
};
}
function serializeSidebar(): var {
return {
enabled: sidebar.enabled,
sizes: {
width: sidebar.sizes.width
}
};
}
function serializeUtilities(): var {
return {
enabled: utilities.enabled,
maxToasts: utilities.maxToasts,
sizes: {
width: utilities.sizes.width,
toastWidth: utilities.sizes.toastWidth
},
toasts: {
configLoaded: utilities.toasts.configLoaded,
chargingChanged: utilities.toasts.chargingChanged,
gameModeChanged: utilities.toasts.gameModeChanged,
dndChanged: utilities.toasts.dndChanged,
audioOutputChanged: utilities.toasts.audioOutputChanged,
audioInputChanged: utilities.toasts.audioInputChanged,
capsLockChanged: utilities.toasts.capsLockChanged,
numLockChanged: utilities.toasts.numLockChanged,
kbLayoutChanged: utilities.toasts.kbLayoutChanged,
vpnChanged: utilities.toasts.vpnChanged,
nowPlaying: utilities.toasts.nowPlaying
},
vpn: {
enabled: utilities.vpn.enabled,
provider: utilities.vpn.provider
}
};
}
ElapsedTimer { ElapsedTimer {
id: timer id: timer
} }
Timer { Timer {
id: saveTimer id: saveTimer
interval: 500 interval: 500
onTriggered: { onTriggered: {
timer.restart(); timer.restart();
try { try {
@@ -327,78 +65,316 @@ Singleton {
id: recentSaveCooldown id: recentSaveCooldown
interval: 2000 interval: 2000
onTriggered: { onTriggered: {
root.recentlySaved = false; root.recentlySaved = false;
} }
} }
FileView { function serializeConfig(): var {
id: fileView return {
barConfig: serializeBar(),
lock: serializeLock(),
general: serializeGeneral(),
services: serializeServices(),
notifs: serializeNotifs(),
sidebar: serializeSidebar(),
utilities: serializeUtilities(),
dashboard: serializeDashboard(),
appearance: serializeAppearance(),
osd: serializeOsd(),
background: serializeBackground(),
launcher: serializeLauncher(),
colors: serializeColors()
}
}
path: `${Paths.config}/config.json` function serializeBar(): var {
watchChanges: true return {
autoHide: barConfig.autoHide,
rounding: barConfig.rounding,
popouts: {
tray: barConfig.popouts.tray,
audio: barConfig.popouts.audio,
activeWindow: barConfig.popouts.activeWindow,
resources: barConfig.popouts.resources,
clock: barConfig.popouts.clock,
network: barConfig.popouts.network,
upower: barConfig.popouts.upower
},
entries: barConfig.entries
}
}
onFileChanged: { function serializeLock(): var {
if (!root.recentlySaved) { return {
recolorLogo: lock.recolorLogo,
enableFprint: lock.enableFprint,
maxFprintTries: lock.maxFprintTries,
blurAmount: lock.blurAmount,
sizes: {
heightMult: lock.sizes.heightMult,
ratio: lock.sizes.ratio,
centerWidth: lock.sizes.centerWidth
}
};
}
function serializeGeneral(): var {
return {
logo: general.logo,
wallpaperPath: general.wallpaperPath,
color: {
wallust: general.color.wallust,
mode: general.color.mode,
schemeGeneration: general.color.schemeGeneration,
scheduleDarkStart: general.color.scheduleDarkStart,
scheduleDarkEnd: general.color.scheduleDarkEnd,
neovimColors: general.color.neovimColors
},
apps: {
terminal: general.apps.terminal,
audio: general.apps.audio,
playback: general.apps.playback,
explorer: general.apps.explorer,
},
idle: {
timouts: general.idle.timeouts
}
}
}
function serializeServices(): var {
return {
weatherLocation: services.weatherLocation,
useFahrenheit: services.useFahrenheit,
useTwelveHourClock: services.useTwelveHourClock,
gpuType: services.gpuType,
audioIncrement: services.audioIncrement,
brightnessIncrement: services.brightnessIncrement,
maxVolume: services.maxVolume,
defaultPlayer: services.defaultPlayer,
playerAliases: services.playerAliases
};
}
function serializeNotifs(): var {
return {
expire: notifs.expire,
defaultExpireTimeout: notifs.defaultExpireTimeout,
clearThreshold: notifs.clearThreshold,
expandThreshold: notifs.expandThreshold,
actionOnClick: notifs.actionOnClick,
groupPreviewNum: notifs.groupPreviewNum,
sizes: {
width: notifs.sizes.width,
image: notifs.sizes.image,
badge: notifs.sizes.badge
}
};
}
function serializeSidebar(): var {
return {
enabled: sidebar.enabled,
sizes: {
width: sidebar.sizes.width
}
};
}
function serializeUtilities(): var {
return {
enabled: utilities.enabled,
maxToasts: utilities.maxToasts,
sizes: {
width: utilities.sizes.width,
toastWidth: utilities.sizes.toastWidth
},
toasts: {
configLoaded: utilities.toasts.configLoaded,
chargingChanged: utilities.toasts.chargingChanged,
gameModeChanged: utilities.toasts.gameModeChanged,
dndChanged: utilities.toasts.dndChanged,
audioOutputChanged: utilities.toasts.audioOutputChanged,
audioInputChanged: utilities.toasts.audioInputChanged,
capsLockChanged: utilities.toasts.capsLockChanged,
numLockChanged: utilities.toasts.numLockChanged,
kbLayoutChanged: utilities.toasts.kbLayoutChanged,
vpnChanged: utilities.toasts.vpnChanged,
nowPlaying: utilities.toasts.nowPlaying
},
vpn: {
enabled: utilities.vpn.enabled,
provider: utilities.vpn.provider
}
};
}
function serializeDashboard(): var {
return {
enabled: dashboard.enabled,
mediaUpdateInterval: dashboard.mediaUpdateInterval,
dragThreshold: dashboard.dragThreshold,
sizes: {
tabIndicatorHeight: dashboard.sizes.tabIndicatorHeight,
tabIndicatorSpacing: dashboard.sizes.tabIndicatorSpacing,
infoWidth: dashboard.sizes.infoWidth,
infoIconSize: dashboard.sizes.infoIconSize,
dateTimeWidth: dashboard.sizes.dateTimeWidth,
mediaWidth: dashboard.sizes.mediaWidth,
mediaProgressSweep: dashboard.sizes.mediaProgressSweep,
mediaProgressThickness: dashboard.sizes.mediaProgressThickness,
resourceProgessThickness: dashboard.sizes.resourceProgessThickness,
weatherWidth: dashboard.sizes.weatherWidth,
mediaCoverArtSize: dashboard.sizes.mediaCoverArtSize,
mediaVisualiserSize: dashboard.sizes.mediaVisualiserSize,
resourceSize: dashboard.sizes.resourceSize
}
};
}
function serializeOsd(): var {
return {
enabled: osd.enabled,
hideDelay: osd.hideDelay,
enableBrightness: osd.enableBrightness,
enableMicrophone: osd.enableMicrophone,
allMonBrightness: osd.allMonBrightness,
sizes: {
sliderWidth: osd.sizes.sliderWidth,
sliderHeight: osd.sizes.sliderHeight
}
};
}
function serializeLauncher(): var {
return {
maxAppsShown: launcher.maxAppsShown,
maxWallpapers: launcher.maxWallpapers,
actionPrefix: launcher.actionPrefix,
specialPrefix: launcher.specialPrefix,
useFuzzy: {
apps: launcher.useFuzzy.apps,
actions: launcher.useFuzzy.actions,
schemes: launcher.useFuzzy.schemes,
variants: launcher.useFuzzy.variants,
wallpapers: launcher.useFuzzy.wallpapers
},
sizes: {
itemWidth: launcher.sizes.itemWidth,
itemHeight: launcher.sizes.itemHeight,
wallpaperWidth: launcher.sizes.wallpaperWidth,
wallpaperHeight: launcher.sizes.wallpaperHeight
},
actions: launcher.actions
}
}
function serializeBackground(): var {
return {
wallFadeDuration: background.wallFadeDuration,
enabled: background.enabled
}
}
function serializeAppearance(): var {
return {
rounding: {
scale: appearance.rounding.scale
},
spacing: {
scale: appearance.spacing.scale
},
padding: {
scale: appearance.padding.scale
},
font: {
family: {
sans: appearance.font.family.sans,
mono: appearance.font.family.mono,
material: appearance.font.family.material,
clock: appearance.font.family.clock
},
size: {
scale: appearance.font.size.scale
}
},
anim: {
mediaGifSpeedAdjustment: 300,
sessionGifSpeed: 0.7,
durations: {
scale: appearance.anim.durations.scale
}
},
transparency: {
enabled: appearance.transparency.enabled,
base: appearance.transparency.base,
layers: appearance.transparency.layers
}
};
}
function serializeColors(): var {
return {
schemeType: colors.schemeType,
}
}
FileView {
id: fileView
path: `${Paths.config}/config.json`
watchChanges: true
onFileChanged: {
if ( !root.recentlySaved ) {
timer.restart(); timer.restart();
reload(); reload();
} else { } else {
reload(); reload();
} }
} }
onLoadFailed: err => {
if (err !== FileViewError.FileNotFound)
Toaster.toast(qsTr("Failed to read config"), FileViewError.toString(err), "settings_alert", Toast.Warning);
}
onLoaded: { onLoaded: {
ModeScheduler.checkStartup(); ModeScheduler.checkStartup();
try { try {
JSON.parse(text()); JSON.parse(text());
const elapsed = timer.elapsedMs(); const elapsed = timer.elapsedMs();
if (adapter.utilities.toasts.configLoaded && !root.recentlySaved) { if ( adapter.utilities.toasts.configLoaded && !root.recentlySaved ) {
Toaster.toast(qsTr("Config loaded"), qsTr("Config loaded in %1ms").arg(elapsed), "rule_settings"); Toaster.toast(qsTr("Config loaded"), qsTr("Config loaded in %1ms").arg(elapsed), "rule_settings");
} else if (adapter.utilities.toasts.configLoaded && root.recentlySaved) { } else if ( adapter.utilities.toasts.configLoaded && root.recentlySaved ) {
Toaster.toast(qsTr("Config saved"), qsTr("Config reloaded in %1ms").arg(elapsed), "settings_alert"); Toaster.toast(qsTr("Config saved"), qsTr("Config reloaded in %1ms").arg(elapsed), "settings_alert");
} }
} catch (e) { } catch (e) {
Toaster.toast(qsTr("Failed to load config"), e.message, "settings_alert", Toast.Error); Toaster.toast(qsTr("Failed to load config"), e.message, "settings_alert", Toast.Error);
} }
} }
onLoadFailed: err => {
if ( err !== FileViewError.FileNotFound )
Toaster.toast(qsTr("Failed to read config"), FileViewError.toString(err), "settings_alert", Toast.Warning);
}
onSaveFailed: err => Toaster.toast(qsTr("Failed to save config"), FileViewError.toString(err), "settings_alert", Toast.Error) onSaveFailed: err => Toaster.toast(qsTr("Failed to save config"), FileViewError.toString(err), "settings_alert", Toast.Error)
JsonAdapter { JsonAdapter {
id: adapter id: adapter
property BackgroundConfig background: BackgroundConfig {}
property AppearanceConf appearance: AppearanceConf { property BarConfig barConfig: BarConfig {}
} property LockConf lock: LockConf {}
property BackgroundConfig background: BackgroundConfig { property Overview overview: Overview {}
} property Services services: Services {}
property BarConfig barConfig: BarConfig { property NotifConfig notifs: NotifConfig {}
} property SidebarConfig sidebar: SidebarConfig {}
property Colors colors: Colors { property UtilConfig utilities: UtilConfig {}
} property General general: General {}
property DashboardConfig dashboard: DashboardConfig { property DashboardConfig dashboard: DashboardConfig {}
} property AppearanceConf appearance: AppearanceConf {}
property General general: General { property Osd osd: Osd {}
} property Launcher launcher: Launcher {}
property Launcher launcher: Launcher { property Colors colors: Colors {}
} }
property LockConf lock: LockConf { }
}
property NotifConfig notifs: NotifConfig {
}
property Osd osd: Osd {
}
property Overview overview: Overview {
}
property Services services: Services {
}
property SidebarConfig sidebar: SidebarConfig {
}
property UtilConfig utilities: UtilConfig {
}
}
}
} }
+19 -31
View File
@@ -1,36 +1,24 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property int dragThreshold: 50 property bool enabled: true
property bool enabled: true property int mediaUpdateInterval: 500
property int mediaUpdateInterval: 500 property int dragThreshold: 50
property Performance performance: Performance { property Sizes sizes: Sizes {}
}
property int resourceUpdateInterval: 1000
property Sizes sizes: Sizes {
}
component Performance: JsonObject { component Sizes: JsonObject {
property bool showBattery: true readonly property int tabIndicatorHeight: 3
property bool showCpu: true readonly property int tabIndicatorSpacing: 5
property bool showGpu: true readonly property int infoWidth: 200
property bool showMemory: true readonly property int infoIconSize: 25
property bool showNetwork: true readonly property int dateTimeWidth: 110
property bool showStorage: true readonly property int mediaWidth: 200
} readonly property int mediaProgressSweep: 180
component Sizes: JsonObject { readonly property int mediaProgressThickness: 8
readonly property int dateTimeWidth: 110 readonly property int resourceProgessThickness: 10
readonly property int infoIconSize: 25 readonly property int weatherWidth: 250
readonly property int infoWidth: 200 readonly property int mediaCoverArtSize: 150
readonly property int mediaCoverArtSize: 150 readonly property int mediaVisualiserSize: 80
readonly property int mediaProgressSweep: 180 readonly property int resourceSize: 200
readonly property int mediaProgressThickness: 8 }
readonly property int mediaVisualiserSize: 80
readonly property int mediaWidth: 200
readonly property int resourceProgessThickness: 10
readonly property int resourceSize: 200
readonly property int tabIndicatorHeight: 3
readonly property int tabIndicatorSpacing: 5
readonly property int weatherWidth: 250
}
} }
+233 -235
View File
@@ -9,272 +9,270 @@ import qs.Helpers
import qs.Paths import qs.Paths
Singleton { Singleton {
id: root id: root
readonly property M3Palette current: M3Palette { property bool showPreview
} property string scheme
property bool currentLight property string flavour
property string flavour readonly property bool light: showPreview ? previewLight : currentLight
readonly property bool light: showPreview ? previewLight : currentLight property bool currentLight
readonly property M3Palette palette: showPreview ? preview : current property bool previewLight
readonly property M3Palette preview: M3Palette { readonly property M3Palette palette: showPreview ? preview : current
} readonly property M3TPalette tPalette: M3TPalette {}
property bool previewLight readonly property M3Palette current: M3Palette {}
property string scheme readonly property M3Palette preview: M3Palette {}
property bool showPreview readonly property Transparency transparency: Transparency {}
readonly property M3TPalette tPalette: M3TPalette { readonly property alias wallLuminance: analyser.luminance
}
readonly property Transparency transparency: Transparency {
}
readonly property alias wallLuminance: analyser.luminance
function alterColor(c: color, a: real, layer: int): color { function getLuminance(c: color): real {
const luminance = getLuminance(c); if (c.r == 0 && c.g == 0 && c.b == 0)
return 0;
return Math.sqrt(0.299 * (c.r ** 2) + 0.587 * (c.g ** 2) + 0.114 * (c.b ** 2));
}
const offset = (!light || layer == 1 ? 1 : -layer / 2) * (light ? 0.2 : 0.3) * (1 - transparency.base) * (1 + wallLuminance * (light ? (layer == 1 ? 3 : 1) : 2.5)); function alterColor(c: color, a: real, layer: int): color {
const scale = (luminance + offset) / luminance; const luminance = getLuminance(c);
const r = Math.max(0, Math.min(1, c.r * scale));
const g = Math.max(0, Math.min(1, c.g * scale));
const b = Math.max(0, Math.min(1, c.b * scale));
return Qt.rgba(r, g, b, a); const offset = (!light || layer == 1 ? 1 : -layer / 2) * (light ? 0.2 : 0.3) * (1 - transparency.base) * (1 + wallLuminance * (light ? (layer == 1 ? 3 : 1) : 2.5));
} const scale = (luminance + offset) / luminance;
const r = Math.max(0, Math.min(1, c.r * scale));
const g = Math.max(0, Math.min(1, c.g * scale));
const b = Math.max(0, Math.min(1, c.b * scale));
function getLuminance(c: color): real { return Qt.rgba(r, g, b, a);
if (c.r == 0 && c.g == 0 && c.b == 0) }
return 0;
return Math.sqrt(0.299 * (c.r ** 2) + 0.587 * (c.g ** 2) + 0.114 * (c.b ** 2));
}
function layer(c: color, layer: var): color { function layer(c: color, layer: var): color {
if (!transparency.enabled) if (!transparency.enabled)
return c; return c;
return layer === 0 ? Qt.alpha(c, transparency.base) : alterColor(c, transparency.layers, layer ?? 1); return layer === 0 ? Qt.alpha(c, transparency.base) : alterColor(c, transparency.layers, layer ?? 1);
} }
function load(data: string, isPreview: bool): void { function on(c: color): color {
const colors = isPreview ? preview : current; if (c.hslLightness < 0.5)
const scheme = JSON.parse(data); return Qt.hsla(c.hslHue, c.hslSaturation, 0.9, 1);
return Qt.hsla(c.hslHue, c.hslSaturation, 0.1, 1);
}
if (!isPreview) { function load(data: string, isPreview: bool): void {
root.scheme = scheme.name; const colors = isPreview ? preview : current;
flavour = scheme.flavor; const scheme = JSON.parse(data);
currentLight = scheme.mode === "light";
} else {
previewLight = scheme.mode === "light";
}
for (const [name, color] of Object.entries(scheme.colors)) { if (!isPreview) {
const propName = name.startsWith("term") ? name : `m3${name}`; root.scheme = scheme.name;
if (colors.hasOwnProperty(propName)) flavour = scheme.flavor;
colors[propName] = `${color}`; currentLight = scheme.mode === "light";
} } else {
} previewLight = scheme.mode === "light";
}
function on(c: color): color { for (const [name, color] of Object.entries(scheme.colors)) {
if (c.hslLightness < 0.5) const propName = name.startsWith("term") ? name : `m3${name}`;
return Qt.hsla(c.hslHue, c.hslSaturation, 0.9, 1); if (colors.hasOwnProperty(propName))
return Qt.hsla(c.hslHue, c.hslSaturation, 0.1, 1); colors[propName] = `${color}`;
} }
}
FileView { FileView {
path: `${Paths.state}/scheme.json` path: `${Paths.state}/scheme.json`
watchChanges: true watchChanges: true
onFileChanged: reload()
onLoaded: root.load(text(), false)
}
onFileChanged: reload() ImageAnalyser {
onLoaded: root.load(text(), false) id: analyser
}
ImageAnalyser { source: WallpaperPath.currentWallpaperPath
id: analyser }
source: WallpaperPath.currentWallpaperPath component Transparency: QtObject {
} readonly property bool enabled: Appearance.transparency.enabled
readonly property real base: Appearance.transparency.base - (root.light ? 0.1 : 0)
readonly property real layers: Appearance.transparency.layers
}
component M3TPalette: QtObject {
readonly property color m3primary_paletteKeyColor: root.layer(root.palette.m3primary_paletteKeyColor)
readonly property color m3secondary_paletteKeyColor: root.layer(root.palette.m3secondary_paletteKeyColor)
readonly property color m3tertiary_paletteKeyColor: root.layer(root.palette.m3tertiary_paletteKeyColor)
readonly property color m3neutral_paletteKeyColor: root.layer(root.palette.m3neutral_paletteKeyColor)
readonly property color m3neutral_variant_paletteKeyColor: root.layer(root.palette.m3neutral_variant_paletteKeyColor)
readonly property color m3background: root.layer(root.palette.m3background, 0)
readonly property color m3onBackground: root.layer(root.palette.m3onBackground)
readonly property color m3surface: root.layer(root.palette.m3surface, 0)
readonly property color m3surfaceDim: root.layer(root.palette.m3surfaceDim, 0)
readonly property color m3surfaceBright: root.layer(root.palette.m3surfaceBright, 0)
readonly property color m3surfaceContainerLowest: root.layer(root.palette.m3surfaceContainerLowest)
readonly property color m3surfaceContainerLow: root.layer(root.palette.m3surfaceContainerLow)
readonly property color m3surfaceContainer: root.layer(root.palette.m3surfaceContainer)
readonly property color m3surfaceContainerHigh: root.layer(root.palette.m3surfaceContainerHigh)
readonly property color m3surfaceContainerHighest: root.layer(root.palette.m3surfaceContainerHighest)
readonly property color m3onSurface: root.layer(root.palette.m3onSurface)
readonly property color m3surfaceVariant: root.layer(root.palette.m3surfaceVariant, 0)
readonly property color m3onSurfaceVariant: root.layer(root.palette.m3onSurfaceVariant)
readonly property color m3inverseSurface: root.layer(root.palette.m3inverseSurface, 0)
readonly property color m3inverseOnSurface: root.layer(root.palette.m3inverseOnSurface)
readonly property color m3outline: root.layer(root.palette.m3outline)
readonly property color m3outlineVariant: root.layer(root.palette.m3outlineVariant)
readonly property color m3shadow: root.layer(root.palette.m3shadow)
readonly property color m3scrim: root.layer(root.palette.m3scrim)
readonly property color m3surfaceTint: root.layer(root.palette.m3surfaceTint)
readonly property color m3primary: root.layer(root.palette.m3primary)
readonly property color m3onPrimary: root.layer(root.palette.m3onPrimary)
readonly property color m3primaryContainer: root.layer(root.palette.m3primaryContainer)
readonly property color m3onPrimaryContainer: root.layer(root.palette.m3onPrimaryContainer)
readonly property color m3inversePrimary: root.layer(root.palette.m3inversePrimary)
readonly property color m3secondary: root.layer(root.palette.m3secondary)
readonly property color m3onSecondary: root.layer(root.palette.m3onSecondary)
readonly property color m3secondaryContainer: root.layer(root.palette.m3secondaryContainer)
readonly property color m3onSecondaryContainer: root.layer(root.palette.m3onSecondaryContainer)
readonly property color m3tertiary: root.layer(root.palette.m3tertiary)
readonly property color m3onTertiary: root.layer(root.palette.m3onTertiary)
readonly property color m3tertiaryContainer: root.layer(root.palette.m3tertiaryContainer)
readonly property color m3onTertiaryContainer: root.layer(root.palette.m3onTertiaryContainer)
readonly property color m3error: root.layer(root.palette.m3error)
readonly property color m3onError: root.layer(root.palette.m3onError)
readonly property color m3errorContainer: root.layer(root.palette.m3errorContainer)
readonly property color m3onErrorContainer: root.layer(root.palette.m3onErrorContainer)
readonly property color m3success: root.layer(root.palette.m3success)
readonly property color m3onSuccess: root.layer(root.palette.m3onSuccess)
readonly property color m3successContainer: root.layer(root.palette.m3successContainer)
readonly property color m3onSuccessContainer: root.layer(root.palette.m3onSuccessContainer)
readonly property color m3primaryFixed: root.layer(root.palette.m3primaryFixed)
readonly property color m3primaryFixedDim: root.layer(root.palette.m3primaryFixedDim)
readonly property color m3onPrimaryFixed: root.layer(root.palette.m3onPrimaryFixed)
readonly property color m3onPrimaryFixedVariant: root.layer(root.palette.m3onPrimaryFixedVariant)
readonly property color m3secondaryFixed: root.layer(root.palette.m3secondaryFixed)
readonly property color m3secondaryFixedDim: root.layer(root.palette.m3secondaryFixedDim)
readonly property color m3onSecondaryFixed: root.layer(root.palette.m3onSecondaryFixed)
readonly property color m3onSecondaryFixedVariant: root.layer(root.palette.m3onSecondaryFixedVariant)
readonly property color m3tertiaryFixed: root.layer(root.palette.m3tertiaryFixed)
readonly property color m3tertiaryFixedDim: root.layer(root.palette.m3tertiaryFixedDim)
readonly property color m3onTertiaryFixed: root.layer(root.palette.m3onTertiaryFixed)
readonly property color m3onTertiaryFixedVariant: root.layer(root.palette.m3onTertiaryFixedVariant)
}
component M3Palette: QtObject {
property color m3primary_paletteKeyColor: "#a8627b"
property color m3secondary_paletteKeyColor: "#8e6f78"
property color m3tertiary_paletteKeyColor: "#986e4c"
property color m3neutral_paletteKeyColor: "#807477"
property color m3neutral_variant_paletteKeyColor: "#837377"
property color m3background: "#191114"
property color m3onBackground: "#efdfe2"
property color m3surface: "#191114"
property color m3surfaceDim: "#191114"
property color m3surfaceBright: "#403739"
property color m3surfaceContainerLowest: "#130c0e"
property color m3surfaceContainerLow: "#22191c"
property color m3surfaceContainer: "#261d20"
property color m3surfaceContainerHigh: "#31282a"
property color m3surfaceContainerHighest: "#3c3235"
property color m3onSurface: "#efdfe2"
property color m3surfaceVariant: "#514347"
property color m3onSurfaceVariant: "#d5c2c6"
property color m3inverseSurface: "#efdfe2"
property color m3inverseOnSurface: "#372e30"
property color m3outline: "#9e8c91"
property color m3outlineVariant: "#514347"
property color m3shadow: "#000000"
property color m3scrim: "#000000"
property color m3surfaceTint: "#ffb0ca"
property color m3primary: "#ffb0ca"
property color m3onPrimary: "#541d34"
property color m3primaryContainer: "#6f334a"
property color m3onPrimaryContainer: "#ffd9e3"
property color m3inversePrimary: "#8b4a62"
property color m3secondary: "#e2bdc7"
property color m3onSecondary: "#422932"
property color m3secondaryContainer: "#5a3f48"
property color m3onSecondaryContainer: "#ffd9e3"
property color m3tertiary: "#f0bc95"
property color m3onTertiary: "#48290c"
property color m3tertiaryContainer: "#b58763"
property color m3onTertiaryContainer: "#000000"
property color m3error: "#ffb4ab"
property color m3onError: "#690005"
property color m3errorContainer: "#93000a"
property color m3onErrorContainer: "#ffdad6"
property color m3success: "#B5CCBA"
property color m3onSuccess: "#213528"
property color m3successContainer: "#374B3E"
property color m3onSuccessContainer: "#D1E9D6"
property color m3primaryFixed: "#ffd9e3"
property color m3primaryFixedDim: "#ffb0ca"
property color m3onPrimaryFixed: "#39071f"
property color m3onPrimaryFixedVariant: "#6f334a"
property color m3secondaryFixed: "#ffd9e3"
property color m3secondaryFixedDim: "#e2bdc7"
property color m3onSecondaryFixed: "#2b151d"
property color m3onSecondaryFixedVariant: "#5a3f48"
property color m3tertiaryFixed: "#ffdcc3"
property color m3tertiaryFixedDim: "#f0bc95"
property color m3onTertiaryFixed: "#2f1500"
property color m3onTertiaryFixedVariant: "#623f21"
}
component M3MaccchiatoPalette: QtObject { component M3MaccchiatoPalette: QtObject {
property color m3background: "#131317" property color m3primary_paletteKeyColor: "#6a73ac"
property color m3error: "#ffb4ab" property color m3secondary_paletteKeyColor: "#72758e"
property color m3errorContainer: "#93000a" property color m3tertiary_paletteKeyColor: "#9b6592"
property color m3inverseOnSurface: "#303034"
property color m3inversePrimary: "#525b92"
property color m3inverseSurface: "#e4e1e7"
property color m3neutral_paletteKeyColor: "#77767b" property color m3neutral_paletteKeyColor: "#77767b"
property color m3neutral_variant_paletteKeyColor: "#767680" property color m3neutral_variant_paletteKeyColor: "#767680"
property color m3background: "#131317"
property color m3onBackground: "#e4e1e7" property color m3onBackground: "#e4e1e7"
property color m3onError: "#690005"
property color m3onErrorContainer: "#ffdad6"
property color m3onPrimary: "#232c60"
property color m3onPrimaryContainer: "#ffffff"
property color m3onPrimaryFixed: "#0b154b"
property color m3onPrimaryFixedVariant: "#3a4378"
property color m3onSecondary: "#2c2f44"
property color m3onSecondaryContainer: "#b1b3ce"
property color m3onSecondaryFixed: "#171a2e"
property color m3onSecondaryFixedVariant: "#42455c"
property color m3onSuccess: "#213528"
property color m3onSuccessContainer: "#D1E9D6"
property color m3onSurface: "#e4e1e7"
property color m3onSurfaceVariant: "#c6c5d1"
property color m3onTertiary: "#4c1f48"
property color m3onTertiaryContainer: "#000000"
property color m3onTertiaryFixed: "#340831"
property color m3onTertiaryFixedVariant: "#66365f"
property color m3outline: "#90909a"
property color m3outlineVariant: "#46464f"
property color m3primary: "#bac3ff"
property color m3primaryContainer: "#6a73ac"
property color m3primaryFixed: "#dee0ff"
property color m3primaryFixedDim: "#bac3ff"
property color m3primary_paletteKeyColor: "#6a73ac"
property color m3scrim: "#000000"
property color m3secondary: "#c3c5e0"
property color m3secondaryContainer: "#42455c"
property color m3secondaryFixed: "#dfe1fd"
property color m3secondaryFixedDim: "#c3c5e0"
property color m3secondary_paletteKeyColor: "#72758e"
property color m3shadow: "#000000"
property color m3success: "#B5CCBA"
property color m3successContainer: "#374B3E"
property color m3surface: "#131317" property color m3surface: "#131317"
property color m3surfaceDim: "#131317"
property color m3surfaceBright: "#39393d" property color m3surfaceBright: "#39393d"
property color m3surfaceContainerLowest: "#0e0e12"
property color m3surfaceContainerLow: "#1b1b1f"
property color m3surfaceContainer: "#1f1f23" property color m3surfaceContainer: "#1f1f23"
property color m3surfaceContainerHigh: "#2a2a2e" property color m3surfaceContainerHigh: "#2a2a2e"
property color m3surfaceContainerHighest: "#353438" property color m3surfaceContainerHighest: "#353438"
property color m3surfaceContainerLow: "#1b1b1f" property color m3onSurface: "#e4e1e7"
property color m3surfaceContainerLowest: "#0e0e12"
property color m3surfaceDim: "#131317"
property color m3surfaceTint: "#bac3ff"
property color m3surfaceVariant: "#46464f" property color m3surfaceVariant: "#46464f"
property color m3onSurfaceVariant: "#c6c5d1"
property color m3inverseSurface: "#e4e1e7"
property color m3inverseOnSurface: "#303034"
property color m3outline: "#90909a"
property color m3outlineVariant: "#46464f"
property color m3shadow: "#000000"
property color m3scrim: "#000000"
property color m3surfaceTint: "#bac3ff"
property color m3primary: "#bac3ff"
property color m3onPrimary: "#232c60"
property color m3primaryContainer: "#6a73ac"
property color m3onPrimaryContainer: "#ffffff"
property color m3inversePrimary: "#525b92"
property color m3secondary: "#c3c5e0"
property color m3onSecondary: "#2c2f44"
property color m3secondaryContainer: "#42455c"
property color m3onSecondaryContainer: "#b1b3ce"
property color m3tertiary: "#f1b3e5" property color m3tertiary: "#f1b3e5"
property color m3onTertiary: "#4c1f48"
property color m3tertiaryContainer: "#b77ead" property color m3tertiaryContainer: "#b77ead"
property color m3onTertiaryContainer: "#000000"
property color m3error: "#ffb4ab"
property color m3onError: "#690005"
property color m3errorContainer: "#93000a"
property color m3onErrorContainer: "#ffdad6"
property color m3primaryFixed: "#dee0ff"
property color m3primaryFixedDim: "#bac3ff"
property color m3onPrimaryFixed: "#0b154b"
property color m3onPrimaryFixedVariant: "#3a4378"
property color m3secondaryFixed: "#dfe1fd"
property color m3secondaryFixedDim: "#c3c5e0"
property color m3onSecondaryFixed: "#171a2e"
property color m3onSecondaryFixedVariant: "#42455c"
property color m3tertiaryFixed: "#ffd7f4" property color m3tertiaryFixed: "#ffd7f4"
property color m3tertiaryFixedDim: "#f1b3e5" property color m3tertiaryFixedDim: "#f1b3e5"
property color m3tertiary_paletteKeyColor: "#9b6592" property color m3onTertiaryFixed: "#340831"
} property color m3onTertiaryFixedVariant: "#66365f"
component M3Palette: QtObject {
property color m3background: "#191114"
property color m3error: "#ffb4ab"
property color m3errorContainer: "#93000a"
property color m3inverseOnSurface: "#372e30"
property color m3inversePrimary: "#8b4a62"
property color m3inverseSurface: "#efdfe2"
property color m3neutral_paletteKeyColor: "#807477"
property color m3neutral_variant_paletteKeyColor: "#837377"
property color m3onBackground: "#efdfe2"
property color m3onError: "#690005"
property color m3onErrorContainer: "#ffdad6"
property color m3onPrimary: "#541d34"
property color m3onPrimaryContainer: "#ffd9e3"
property color m3onPrimaryFixed: "#39071f"
property color m3onPrimaryFixedVariant: "#6f334a"
property color m3onSecondary: "#422932"
property color m3onSecondaryContainer: "#ffd9e3"
property color m3onSecondaryFixed: "#2b151d"
property color m3onSecondaryFixedVariant: "#5a3f48"
property color m3onSuccess: "#213528"
property color m3onSuccessContainer: "#D1E9D6"
property color m3onSurface: "#efdfe2"
property color m3onSurfaceVariant: "#d5c2c6"
property color m3onTertiary: "#48290c"
property color m3onTertiaryContainer: "#000000"
property color m3onTertiaryFixed: "#2f1500"
property color m3onTertiaryFixedVariant: "#623f21"
property color m3outline: "#9e8c91"
property color m3outlineVariant: "#514347"
property color m3primary: "#ffb0ca"
property color m3primaryContainer: "#6f334a"
property color m3primaryFixed: "#ffd9e3"
property color m3primaryFixedDim: "#ffb0ca"
property color m3primary_paletteKeyColor: "#a8627b"
property color m3scrim: "#000000"
property color m3secondary: "#e2bdc7"
property color m3secondaryContainer: "#5a3f48"
property color m3secondaryFixed: "#ffd9e3"
property color m3secondaryFixedDim: "#e2bdc7"
property color m3secondary_paletteKeyColor: "#8e6f78"
property color m3shadow: "#000000"
property color m3success: "#B5CCBA" property color m3success: "#B5CCBA"
property color m3onSuccess: "#213528"
property color m3successContainer: "#374B3E" property color m3successContainer: "#374B3E"
property color m3surface: "#191114" property color m3onSuccessContainer: "#D1E9D6"
property color m3surfaceBright: "#403739"
property color m3surfaceContainer: "#261d20"
property color m3surfaceContainerHigh: "#31282a"
property color m3surfaceContainerHighest: "#3c3235"
property color m3surfaceContainerLow: "#22191c"
property color m3surfaceContainerLowest: "#130c0e"
property color m3surfaceDim: "#191114"
property color m3surfaceTint: "#ffb0ca"
property color m3surfaceVariant: "#514347"
property color m3tertiary: "#f0bc95"
property color m3tertiaryContainer: "#b58763"
property color m3tertiaryFixed: "#ffdcc3"
property color m3tertiaryFixedDim: "#f0bc95"
property color m3tertiary_paletteKeyColor: "#986e4c"
}
component M3TPalette: QtObject {
readonly property color m3background: root.layer(root.palette.m3background, 0)
readonly property color m3error: root.layer(root.palette.m3error)
readonly property color m3errorContainer: root.layer(root.palette.m3errorContainer)
readonly property color m3inverseOnSurface: root.layer(root.palette.m3inverseOnSurface)
readonly property color m3inversePrimary: root.layer(root.palette.m3inversePrimary)
readonly property color m3inverseSurface: root.layer(root.palette.m3inverseSurface, 0)
readonly property color m3neutral_paletteKeyColor: root.layer(root.palette.m3neutral_paletteKeyColor)
readonly property color m3neutral_variant_paletteKeyColor: root.layer(root.palette.m3neutral_variant_paletteKeyColor)
readonly property color m3onBackground: root.layer(root.palette.m3onBackground)
readonly property color m3onError: root.layer(root.palette.m3onError)
readonly property color m3onErrorContainer: root.layer(root.palette.m3onErrorContainer)
readonly property color m3onPrimary: root.layer(root.palette.m3onPrimary)
readonly property color m3onPrimaryContainer: root.layer(root.palette.m3onPrimaryContainer)
readonly property color m3onPrimaryFixed: root.layer(root.palette.m3onPrimaryFixed)
readonly property color m3onPrimaryFixedVariant: root.layer(root.palette.m3onPrimaryFixedVariant)
readonly property color m3onSecondary: root.layer(root.palette.m3onSecondary)
readonly property color m3onSecondaryContainer: root.layer(root.palette.m3onSecondaryContainer)
readonly property color m3onSecondaryFixed: root.layer(root.palette.m3onSecondaryFixed)
readonly property color m3onSecondaryFixedVariant: root.layer(root.palette.m3onSecondaryFixedVariant)
readonly property color m3onSuccess: root.layer(root.palette.m3onSuccess)
readonly property color m3onSuccessContainer: root.layer(root.palette.m3onSuccessContainer)
readonly property color m3onSurface: root.layer(root.palette.m3onSurface)
readonly property color m3onSurfaceVariant: root.layer(root.palette.m3onSurfaceVariant)
readonly property color m3onTertiary: root.layer(root.palette.m3onTertiary)
readonly property color m3onTertiaryContainer: root.layer(root.palette.m3onTertiaryContainer)
readonly property color m3onTertiaryFixed: root.layer(root.palette.m3onTertiaryFixed)
readonly property color m3onTertiaryFixedVariant: root.layer(root.palette.m3onTertiaryFixedVariant)
readonly property color m3outline: root.layer(root.palette.m3outline)
readonly property color m3outlineVariant: root.layer(root.palette.m3outlineVariant)
readonly property color m3primary: root.layer(root.palette.m3primary)
readonly property color m3primaryContainer: root.layer(root.palette.m3primaryContainer)
readonly property color m3primaryFixed: root.layer(root.palette.m3primaryFixed)
readonly property color m3primaryFixedDim: root.layer(root.palette.m3primaryFixedDim)
readonly property color m3primary_paletteKeyColor: root.layer(root.palette.m3primary_paletteKeyColor)
readonly property color m3scrim: root.layer(root.palette.m3scrim)
readonly property color m3secondary: root.layer(root.palette.m3secondary)
readonly property color m3secondaryContainer: root.layer(root.palette.m3secondaryContainer)
readonly property color m3secondaryFixed: root.layer(root.palette.m3secondaryFixed)
readonly property color m3secondaryFixedDim: root.layer(root.palette.m3secondaryFixedDim)
readonly property color m3secondary_paletteKeyColor: root.layer(root.palette.m3secondary_paletteKeyColor)
readonly property color m3shadow: root.layer(root.palette.m3shadow)
readonly property color m3success: root.layer(root.palette.m3success)
readonly property color m3successContainer: root.layer(root.palette.m3successContainer)
readonly property color m3surface: root.layer(root.palette.m3surface, 0)
readonly property color m3surfaceBright: root.layer(root.palette.m3surfaceBright, 0)
readonly property color m3surfaceContainer: root.layer(root.palette.m3surfaceContainer)
readonly property color m3surfaceContainerHigh: root.layer(root.palette.m3surfaceContainerHigh)
readonly property color m3surfaceContainerHighest: root.layer(root.palette.m3surfaceContainerHighest)
readonly property color m3surfaceContainerLow: root.layer(root.palette.m3surfaceContainerLow)
readonly property color m3surfaceContainerLowest: root.layer(root.palette.m3surfaceContainerLowest)
readonly property color m3surfaceDim: root.layer(root.palette.m3surfaceDim, 0)
readonly property color m3surfaceTint: root.layer(root.palette.m3surfaceTint)
readonly property color m3surfaceVariant: root.layer(root.palette.m3surfaceVariant, 0)
readonly property color m3tertiary: root.layer(root.palette.m3tertiary)
readonly property color m3tertiaryContainer: root.layer(root.palette.m3tertiaryContainer)
readonly property color m3tertiaryFixed: root.layer(root.palette.m3tertiaryFixed)
readonly property color m3tertiaryFixedDim: root.layer(root.palette.m3tertiaryFixedDim)
readonly property color m3tertiary_paletteKeyColor: root.layer(root.palette.m3tertiary_paletteKeyColor)
}
component Transparency: QtObject {
readonly property real base: Appearance.transparency.base - (root.light ? 0.1 : 0)
readonly property bool enabled: Appearance.transparency.enabled
readonly property real layers: Appearance.transparency.layers
} }
} }
+16 -17
View File
@@ -2,29 +2,28 @@ import Quickshell.Io
import Quickshell import Quickshell
JsonObject { JsonObject {
property Apps apps: Apps {
}
property Color color: Color {
}
property Idle idle: Idle {
}
property string logo: "" property string logo: ""
property string wallpaperPath: Quickshell.env("HOME") + "/Pictures/Wallpapers" property string wallpaperPath: Quickshell.env("HOME") + "/Pictures/Wallpapers"
property Color color: Color {}
property Apps apps: Apps {}
property Idle idle: Idle {}
component Apps: JsonObject {
property list<string> audio: ["pavucontrol"]
property list<string> explorer: ["dolphin"]
property list<string> playback: ["mpv"]
property list<string> terminal: ["kitty"]
}
component Color: JsonObject { component Color: JsonObject {
property string mode: "dark"
property bool neovimColors: false
property int scheduleDarkEnd: 0
property int scheduleDarkStart: 0
property bool schemeGeneration: true
property bool wallust: false property bool wallust: false
property bool schemeGeneration: true
property string mode: "dark"
property int scheduleDarkStart: 0
property int scheduleDarkEnd: 0
property bool neovimColors: false
} }
component Apps: JsonObject {
property list<string> terminal: ["kitty"]
property list<string> audio: ["pavucontrol"]
property list<string> playback: ["mpv"]
property list<string> explorer: ["dolphin"]
}
component Idle: JsonObject { component Idle: JsonObject {
property list<var> timeouts: [ property list<var> timeouts: [
{ {
+69 -69
View File
@@ -1,7 +1,28 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property int maxAppsShown: 10
property int maxWallpapers: 7
property string actionPrefix: ">" property string actionPrefix: ">"
property string specialPrefix: "@"
property Sizes sizes: Sizes {}
property UseFuzzy useFuzzy: UseFuzzy {}
component UseFuzzy: JsonObject {
property bool apps: false
property bool actions: false
property bool schemes: false
property bool variants: false
property bool wallpapers: false
}
component Sizes: JsonObject {
property int itemWidth: 600
property int itemHeight: 50
property int wallpaperWidth: 280
property int wallpaperHeight: 200
}
property list<var> actions: [ property list<var> actions: [
{ {
name: "Calculator", name: "Calculator",
@@ -11,74 +32,53 @@ JsonObject {
enabled: true, enabled: true,
dangerous: false dangerous: false
}, },
{ {
name: "Wallpaper", name: "Wallpaper",
icon: "image", icon: "image",
description: "Change the current wallpaper", description: "Change the current wallpaper",
command: ["autocomplete", "wallpaper"], command: ["autocomplete", "wallpaper"],
enabled: true, enabled: true,
dangerous: false dangerous: false
}, },
{ {
name: "Shutdown", name: "Shutdown",
icon: "power_settings_new", icon: "power_settings_new",
description: "Shutdown the system", description: "Shutdown the system",
command: ["systemctl", "poweroff"], command: ["systemctl", "poweroff"],
enabled: true, enabled: true,
dangerous: true dangerous: true
}, },
{ {
name: "Reboot", name: "Reboot",
icon: "cached", icon: "cached",
description: "Reboot the system", description: "Reboot the system",
command: ["systemctl", "reboot"], command: ["systemctl", "reboot"],
enabled: true, enabled: true,
dangerous: true dangerous: true
}, },
{ {
name: "Logout", name: "Logout",
icon: "exit_to_app", icon: "exit_to_app",
description: "Log out of the current session", description: "Log out of the current session",
command: ["loginctl", "terminate-user", ""], command: ["loginctl", "terminate-user", ""],
enabled: true, enabled: true,
dangerous: true dangerous: true
}, },
{ {
name: "Lock", name: "Lock",
icon: "lock", icon: "lock",
description: "Lock the current session", description: "Lock the current session",
command: ["loginctl", "lock-session"], command: ["loginctl", "lock-session"],
enabled: true, enabled: true,
dangerous: false dangerous: false
}, },
{ {
name: "Sleep", name: "Sleep",
icon: "bedtime", icon: "bedtime",
description: "Suspend then hibernate", description: "Suspend then hibernate",
command: ["systemctl", "suspend-then-hibernate"], command: ["systemctl", "suspend-then-hibernate"],
enabled: true, enabled: true,
dangerous: false dangerous: false
}, },
] ]
property int maxAppsShown: 10
property int maxWallpapers: 7
property Sizes sizes: Sizes {
}
property string specialPrefix: "@"
property UseFuzzy useFuzzy: UseFuzzy {
}
component Sizes: JsonObject {
property int itemHeight: 50
property int itemWidth: 600
property int wallpaperHeight: 200
property int wallpaperWidth: 280
}
component UseFuzzy: JsonObject {
property bool actions: false
property bool apps: false
property bool schemes: false
property bool variants: false
property bool wallpapers: false
}
} }
+9 -10
View File
@@ -1,16 +1,15 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property bool recolorLogo: false
property bool enableFprint: true
property int maxFprintTries: 3
property Sizes sizes: Sizes {}
property int blurAmount: 40 property int blurAmount: 40
property bool enableFprint: true
property int maxFprintTries: 3
property bool recolorLogo: false
property Sizes sizes: Sizes {
}
component Sizes: JsonObject { component Sizes: JsonObject {
property int centerWidth: 600 property real heightMult: 0.7
property real heightMult: 0.7 property real ratio: 16 / 9
property real ratio: 16 / 9 property int centerWidth: 600
} }
} }
+21 -20
View File
@@ -2,25 +2,26 @@ pragma Singleton
import Quickshell import Quickshell
Singleton { Singleton {
id: root id: root
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] property real scale: Appearance.anim.durations.scale
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
readonly property int emphasizedAccelTime: 200 * scale readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1] readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
readonly property int emphasizedDecelTime: 400 * scale readonly property int emphasizedAccelTime: 200 * scale
readonly property int emphasizedTime: 500 * scale readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1] readonly property int emphasizedDecelTime: 400 * scale
readonly property int expressiveDefaultSpatialTime: 500 * scale readonly property int emphasizedTime: 500 * scale
readonly property list<real> expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1] readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1]
readonly property int expressiveEffectsTime: 200 * scale readonly property int expressiveDefaultSpatialTime: 500 * scale
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1] readonly property list<real> expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1]
readonly property int expressiveFastSpatialTime: 350 * scale readonly property int expressiveEffectsTime: 200 * scale
property real scale: Appearance.anim.durations.scale readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1]
readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1] readonly property int expressiveFastSpatialTime: 350 * scale
readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1] readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1]
readonly property int standardAccelTime: 200 * scale readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1] readonly property int standardAccelTime: 200 * scale
readonly property int standardDecelTime: 250 * scale readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
readonly property int standardTime: 300 * scale readonly property int standardDecelTime: 250 * scale
readonly property int standardTime: 300 * scale
} }
+13 -15
View File
@@ -1,20 +1,18 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property bool actionOnClick: false property bool expire: true
property int appNotifCooldown: 0 property int defaultExpireTimeout: 5000
property real clearThreshold: 0.3 property real clearThreshold: 0.3
property int defaultExpireTimeout: 5000 property int expandThreshold: 20
property int expandThreshold: 20 property bool actionOnClick: false
property bool expire: true property int groupPreviewNum: 3
property int groupPreviewNum: 3 property bool openExpanded: false
property bool openExpanded: false property Sizes sizes: Sizes {}
property Sizes sizes: Sizes {
}
component Sizes: JsonObject { component Sizes: JsonObject {
property int badge: 20 property int width: 400
property int image: 41 property int image: 41
property int width: 400 property int badge: 20
} }
} }
+9 -10
View File
@@ -1,16 +1,15 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property bool enabled: true
property int hideDelay: 3000
property bool enableBrightness: true
property bool enableMicrophone: true
property bool allMonBrightness: false property bool allMonBrightness: false
property bool enableBrightness: true property Sizes sizes: Sizes {}
property bool enableMicrophone: true
property bool enabled: true
property int hideDelay: 3000
property Sizes sizes: Sizes {
}
component Sizes: JsonObject { component Sizes: JsonObject {
property int sliderHeight: 150 property int sliderWidth: 30
property int sliderWidth: 30 property int sliderHeight: 150
} }
} }
+2 -2
View File
@@ -1,8 +1,8 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property int columns: 5
property bool enable: false
property int rows: 2 property int rows: 2
property int columns: 5
property real scale: 0.16 property real scale: 0.16
property bool enable: false
} }
+14 -16
View File
@@ -2,20 +2,18 @@ import Quickshell.Io
import QtQuick import QtQuick
JsonObject { JsonObject {
property real audioIncrement: 0.1 property string weatherLocation: ""
property real brightnessIncrement: 0.1 property bool useFahrenheit: false
property bool ddcutilService: false property bool useTwelveHourClock: Qt.locale().timeFormat(Locale.ShortFormat).toLowerCase().includes("a")
property string defaultPlayer: "Spotify" property string gpuType: ""
property string gpuType: "" property real audioIncrement: 0.1
property real maxVolume: 1.0 property real brightnessIncrement: 0.1
property list<var> playerAliases: [ property real maxVolume: 1.0
{ property string defaultPlayer: "Spotify"
"from": "com.github.th_ch.youtube_music", property list<var> playerAliases: [
"to": "YT Music" {
} "from": "com.github.th_ch.youtube_music",
] "to": "YT Music"
property bool useFahrenheit: false }
property bool useTwelveHourClock: Qt.locale().timeFormat(Locale.ShortFormat).toLowerCase().includes("a") ]
property int visualizerBars: 30
property string weatherLocation: ""
} }
+5 -6
View File
@@ -1,11 +1,10 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property bool enabled: true property bool enabled: true
property Sizes sizes: Sizes { property Sizes sizes: Sizes {}
}
component Sizes: JsonObject { component Sizes: JsonObject {
property int width: 430 property int width: 430
} }
} }
+3 -3
View File
@@ -1,7 +1,7 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property real base: 0.85 property bool enabled: false
property bool enabled: false property real base: 0.85
property real layers: 0.4 property real layers: 0.4
} }
+30 -30
View File
@@ -1,35 +1,35 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property bool enabled: true property bool enabled: true
property int maxToasts: 4 property int maxToasts: 4
property Sizes sizes: Sizes {
}
property Toasts toasts: Toasts {
}
property Vpn vpn: Vpn {
}
component Sizes: JsonObject { property Sizes sizes: Sizes {}
property int toastWidth: 430 property Toasts toasts: Toasts {}
property int width: 430 property Vpn vpn: Vpn {}
}
component Toasts: JsonObject { component Sizes: JsonObject {
property bool audioInputChanged: true property int width: 430
property bool audioOutputChanged: true property int toastWidth: 430
property bool capsLockChanged: true }
property bool chargingChanged: true
property bool configLoaded: true component Toasts: JsonObject {
property bool dndChanged: true property bool configLoaded: true
property bool gameModeChanged: true property bool chargingChanged: true
property bool kbLayoutChanged: true property bool gameModeChanged: true
property bool kbLimit: true property bool dndChanged: true
property bool nowPlaying: false property bool audioOutputChanged: true
property bool numLockChanged: true property bool audioInputChanged: true
property bool vpnChanged: true property bool capsLockChanged: true
} property bool numLockChanged: true
component Vpn: JsonObject { property bool kbLayoutChanged: true
property bool enabled: false property bool kbLimit: true
property list<var> provider: ["netbird"] property bool vpnChanged: true
} property bool nowPlaying: false
}
component Vpn: JsonObject {
property bool enabled: false
property list<var> provider: ["netbird"]
}
} }
+2 -2
View File
@@ -1,6 +1,6 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property string inactiveTextColor: "white" property string textColor: "black"
property string textColor: "black" property string inactiveTextColor: "white"
} }
+108 -114
View File
@@ -1,150 +1,144 @@
pragma Singleton pragma Singleton
import qs.Config
import ZShell.Services import ZShell.Services
import ZShell import ZShell
import Quickshell import Quickshell
import Quickshell.Services.Pipewire import Quickshell.Services.Pipewire
import QtQuick import QtQuick
import qs.Config
Singleton { Singleton {
id: root id: root
readonly property alias beatTracker: beatTracker property string previousSinkName: ""
readonly property alias cava: cava property string previousSourceName: ""
readonly property bool muted: !!sink?.audio?.muted
readonly property var nodes: Pipewire.nodes.values.reduce((acc, node) => {
if (!node.isStream) {
if (node.isSink)
acc.sinks.push(node);
else if (node.audio)
acc.sources.push(node);
} else if (node.isStream && node.audio) {
// Application streams (output streams)
acc.streams.push(node);
}
return acc;
}, {
sources: [],
sinks: [],
streams: []
})
property string previousSinkName: ""
property string previousSourceName: ""
readonly property PwNode sink: Pipewire.defaultAudioSink
readonly property list<PwNode> sinks: nodes.sinks
readonly property PwNode source: Pipewire.defaultAudioSource
readonly property bool sourceMuted: !!source?.audio?.muted
readonly property real sourceVolume: source?.audio?.volume ?? 0
readonly property list<PwNode> sources: nodes.sources
readonly property list<PwNode> streams: nodes.streams
readonly property real volume: sink?.audio?.volume ?? 0
function decrementSourceVolume(amount: real): void { readonly property var nodes: Pipewire.nodes.values.reduce((acc, node) => {
setSourceVolume(sourceVolume - (amount || Config.services.audioIncrement)); if (!node.isStream) {
} if (node.isSink)
acc.sinks.push(node);
else if (node.audio)
acc.sources.push(node);
} else if (node.isStream && node.audio) {
// Application streams (output streams)
acc.streams.push(node);
}
return acc;
}, {
sources: [],
sinks: [],
streams: []
})
function decrementVolume(amount: real): void { readonly property list<PwNode> sinks: nodes.sinks
setVolume(volume - (amount || Config.services.audioIncrement)); readonly property list<PwNode> sources: nodes.sources
} readonly property list<PwNode> streams: nodes.streams
function getStreamMuted(stream: PwNode): bool { readonly property PwNode sink: Pipewire.defaultAudioSink
return !!stream?.audio?.muted; readonly property PwNode source: Pipewire.defaultAudioSource
}
function getStreamName(stream: PwNode): string { readonly property bool muted: !!sink?.audio?.muted
if (!stream) readonly property real volume: sink?.audio?.volume ?? 0
return qsTr("Unknown");
// Try application name first, then description, then name
return stream.applicationName || stream.description || stream.name || qsTr("Unknown Application");
}
function getStreamVolume(stream: PwNode): real { readonly property bool sourceMuted: !!source?.audio?.muted
return stream?.audio?.volume ?? 0; readonly property real sourceVolume: source?.audio?.volume ?? 0
}
function incrementSourceVolume(amount: real): void { function setVolume(newVolume: real): void {
setSourceVolume(sourceVolume + (amount || Config.services.audioIncrement)); if (sink?.ready && sink?.audio) {
} sink.audio.muted = false;
sink.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
}
}
function incrementVolume(amount: real): void { function incrementVolume(amount: real): void {
setVolume(volume + (amount || Config.services.audioIncrement)); setVolume(volume + (amount || Config.services.audioIncrement));
} }
function setAudioSink(newSink: PwNode): void { function decrementVolume(amount: real): void {
Pipewire.preferredDefaultAudioSink = newSink; setVolume(volume - (amount || Config.services.audioIncrement));
} }
function setAudioSource(newSource: PwNode): void { function setSourceVolume(newVolume: real): void {
Pipewire.preferredDefaultAudioSource = newSource; if (source?.ready && source?.audio) {
} source.audio.muted = false;
source.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
}
}
function setSourceVolume(newVolume: real): void { function incrementSourceVolume(amount: real): void {
if (source?.ready && source?.audio) { setSourceVolume(sourceVolume + (amount || Config.services.audioIncrement));
source.audio.muted = false; }
source.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
}
}
function setStreamMuted(stream: PwNode, muted: bool): void { function decrementSourceVolume(amount: real): void {
if (stream?.ready && stream?.audio) { setSourceVolume(sourceVolume - (amount || Config.services.audioIncrement));
stream.audio.muted = muted; }
}
}
function setStreamVolume(stream: PwNode, newVolume: real): void { function setAudioSink(newSink: PwNode): void {
if (stream?.ready && stream?.audio) { Pipewire.preferredDefaultAudioSink = newSink;
stream.audio.muted = false; }
stream.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
}
}
function setVolume(newVolume: real): void { function setAudioSource(newSource: PwNode): void {
if (sink?.ready && sink?.audio) { Pipewire.preferredDefaultAudioSource = newSource;
sink.audio.muted = false; }
sink.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
}
}
Component.onCompleted: { function setStreamVolume(stream: PwNode, newVolume: real): void {
previousSinkName = sink?.description || sink?.name || qsTr("Unknown Device"); if (stream?.ready && stream?.audio) {
previousSourceName = source?.description || source?.name || qsTr("Unknown Device"); stream.audio.muted = false;
} stream.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
onSinkChanged: { }
if (!sink?.ready) }
return;
const newSinkName = sink.description || sink.name || qsTr("Unknown Device"); function setStreamMuted(stream: PwNode, muted: bool): void {
if (stream?.ready && stream?.audio) {
stream.audio.muted = muted;
}
}
if (previousSinkName && previousSinkName !== newSinkName && Config.utilities.toasts.audioOutputChanged) function getStreamVolume(stream: PwNode): real {
Toaster.toast(qsTr("Audio output changed"), qsTr("Now using: %1").arg(newSinkName), "volume_up"); return stream?.audio?.volume ?? 0;
}
previousSinkName = newSinkName; function getStreamMuted(stream: PwNode): bool {
} return !!stream?.audio?.muted;
onSourceChanged: { }
if (!source?.ready)
return;
const newSourceName = source.description || source.name || qsTr("Unknown Device"); function getStreamName(stream: PwNode): string {
if (!stream)
return qsTr("Unknown");
// Try application name first, then description, then name
return stream.applicationName || stream.description || stream.name || qsTr("Unknown Application");
}
if (previousSourceName && previousSourceName !== newSourceName && Config.utilities.toasts.audioInputChanged) onSinkChanged: {
Toaster.toast(qsTr("Audio input changed"), qsTr("Now using: %1").arg(newSourceName), "mic"); if (!sink?.ready)
return;
previousSourceName = newSourceName; const newSinkName = sink.description || sink.name || qsTr("Unknown Device");
}
CavaProvider { if (previousSinkName && previousSinkName !== newSinkName && Config.utilities.toasts.audioOutputChanged)
id: cava Toaster.toast(qsTr("Audio output changed"), qsTr("Now using: %1").arg(newSinkName), "volume_up");
bars: Config.services.visualizerBars previousSinkName = newSinkName;
} }
BeatTracker { onSourceChanged: {
id: beatTracker if (!source?.ready)
return;
} const newSourceName = source.description || source.name || qsTr("Unknown Device");
PwObjectTracker { if (previousSourceName && previousSourceName !== newSourceName && Config.utilities.toasts.audioInputChanged)
objects: [...root.sinks, ...root.sources, ...root.streams] Toaster.toast(qsTr("Audio input changed"), qsTr("Now using: %1").arg(newSourceName), "mic");
}
previousSourceName = newSourceName;
}
Component.onCompleted: {
previousSinkName = sink?.description || sink?.name || qsTr("Unknown Device");
previousSourceName = source?.description || source?.name || qsTr("Unknown Device");
}
PwObjectTracker {
objects: [...root.sinks, ...root.sources, ...root.streams]
}
} }
-326
View File
@@ -1,326 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Io
import QtQuick
Singleton {
id: root
readonly property AccessPoint active: networks.find(n => n.active) ?? null
readonly property var activeEthernet: ethernetDevices.find(d => d.connected) ?? null
property int ethernetDeviceCount: 0
property var ethernetDeviceDetails: null
property list<var> ethernetDevices: []
property bool ethernetProcessRunning: false
readonly property list<AccessPoint> networks: []
property var pendingConnection: null
property list<string> savedConnectionSsids: []
property list<string> savedConnections: []
readonly property bool scanning: Nmcli.scanning
property bool wifiEnabled: true
property var wirelessDeviceDetails: null
signal connectionFailed(string ssid)
function cidrToSubnetMask(cidr: string): string {
// Convert CIDR notation (e.g., "24") to subnet mask (e.g., "255.255.255.0")
const cidrNum = parseInt(cidr);
if (isNaN(cidrNum) || cidrNum < 0 || cidrNum > 32) {
return "";
}
const mask = (0xffffffff << (32 - cidrNum)) >>> 0;
const octets = [(mask >>> 24) & 0xff, (mask >>> 16) & 0xff, (mask >>> 8) & 0xff, mask & 0xff];
return octets.join(".");
}
function connectEthernet(connectionName: string, interfaceName: string): void {
Nmcli.connectEthernet(connectionName, interfaceName, result => {
if (result.success) {
getEthernetDevices();
// Refresh device details after connection
Qt.callLater(() => {
const activeDevice = root.ethernetDevices.find(function (d) {
return d.connected;
});
if (activeDevice && activeDevice.interface) {
updateEthernetDeviceDetails(activeDevice.interface);
}
}, 1000);
}
});
}
function connectToNetwork(ssid: string, password: string, bssid: string, callback: var): void {
// Set up pending connection tracking if callback provided
if (callback) {
const hasBssid = bssid !== undefined && bssid !== null && bssid.length > 0;
root.pendingConnection = {
ssid: ssid,
bssid: hasBssid ? bssid : "",
callback: callback
};
}
Nmcli.connectToNetwork(ssid, password, bssid, result => {
if (result && result.success) {
// Connection successful
if (callback)
callback(result);
root.pendingConnection = null;
} else if (result && result.needsPassword) {
// Password needed - callback will handle showing dialog
if (callback)
callback(result);
} else {
// Connection failed
if (result && result.error) {
root.connectionFailed(ssid);
}
if (callback)
callback(result);
root.pendingConnection = null;
}
});
}
function connectToNetworkWithPasswordCheck(ssid: string, isSecure: bool, callback: var, bssid: string): void {
// Set up pending connection tracking
const hasBssid = bssid !== undefined && bssid !== null && bssid.length > 0;
root.pendingConnection = {
ssid: ssid,
bssid: hasBssid ? bssid : "",
callback: callback
};
Nmcli.connectToNetworkWithPasswordCheck(ssid, isSecure, result => {
if (result && result.success) {
// Connection successful
if (callback)
callback(result);
root.pendingConnection = null;
} else if (result && result.needsPassword) {
// Password needed - callback will handle showing dialog
if (callback)
callback(result);
} else {
// Connection failed
if (result && result.error) {
root.connectionFailed(ssid);
}
if (callback)
callback(result);
root.pendingConnection = null;
}
}, bssid);
}
function disconnectEthernet(connectionName: string): void {
Nmcli.disconnectEthernet(connectionName, result => {
if (result.success) {
getEthernetDevices();
// Clear device details after disconnection
Qt.callLater(() => {
root.ethernetDeviceDetails = null;
});
}
});
}
function disconnectFromNetwork(): void {
// Try to disconnect - use connection name if available, otherwise use device
Nmcli.disconnectFromNetwork();
// Refresh network list after disconnection
Qt.callLater(() => {
Nmcli.getNetworks(() => {
syncNetworksFromNmcli();
});
}, 500);
}
function enableWifi(enabled: bool): void {
Nmcli.enableWifi(enabled, result => {
if (result.success) {
root.getWifiStatus();
Nmcli.getNetworks(() => {
syncNetworksFromNmcli();
});
}
});
}
function forgetNetwork(ssid: string): void {
// Delete the connection profile for this network
// This will remove the saved password and connection settings
Nmcli.forgetNetwork(ssid, result => {
if (result.success) {
// Refresh network list after deletion
Qt.callLater(() => {
Nmcli.getNetworks(() => {
syncNetworksFromNmcli();
});
}, 500);
}
});
}
function getEthernetDevices(): void {
root.ethernetProcessRunning = true;
Nmcli.getEthernetInterfaces(interfaces => {
root.ethernetDevices = Nmcli.ethernetDevices;
root.ethernetDeviceCount = Nmcli.ethernetDevices.length;
root.ethernetProcessRunning = false;
});
}
function getWifiStatus(): void {
Nmcli.getWifiStatus(enabled => {
root.wifiEnabled = enabled;
});
}
function hasSavedProfile(ssid: string): bool {
// Use Nmcli's hasSavedProfile which has the same logic
return Nmcli.hasSavedProfile(ssid);
}
function rescanWifi(): void {
Nmcli.rescanWifi();
}
function syncNetworksFromNmcli(): void {
const rNetworks = root.networks;
const nNetworks = Nmcli.networks;
// Build a map of existing networks by key
const existingMap = new Map();
for (const rn of rNetworks) {
const key = `${rn.frequency}:${rn.ssid}:${rn.bssid}`;
existingMap.set(key, rn);
}
// Build a map of new networks by key
const newMap = new Map();
for (const nn of nNetworks) {
const key = `${nn.frequency}:${nn.ssid}:${nn.bssid}`;
newMap.set(key, nn);
}
// Remove networks that no longer exist
for (const [key, network] of existingMap) {
if (!newMap.has(key)) {
const index = rNetworks.indexOf(network);
if (index >= 0) {
rNetworks.splice(index, 1);
network.destroy();
}
}
}
// Add or update networks from Nmcli
for (const [key, nNetwork] of newMap) {
const existing = existingMap.get(key);
if (existing) {
// Update existing network's lastIpcObject
existing.lastIpcObject = nNetwork.lastIpcObject;
} else {
// Create new AccessPoint from Nmcli's data
rNetworks.push(apComp.createObject(root, {
lastIpcObject: nNetwork.lastIpcObject
}));
}
}
}
function toggleWifi(): void {
Nmcli.toggleWifi(result => {
if (result.success) {
root.getWifiStatus();
Nmcli.getNetworks(() => {
syncNetworksFromNmcli();
});
}
});
}
function updateEthernetDeviceDetails(interfaceName: string): void {
Nmcli.getEthernetDeviceDetails(interfaceName, details => {
root.ethernetDeviceDetails = details;
});
}
function updateWirelessDeviceDetails(): void {
// Find the wireless interface by looking for wifi devices
// Pass empty string to let Nmcli find the active interface automatically
Nmcli.getWirelessDeviceDetails("", details => {
root.wirelessDeviceDetails = details;
});
}
Component.onCompleted: {
// Trigger ethernet device detection after initialization
Qt.callLater(() => {
getEthernetDevices();
});
// Load saved connections on startup
Nmcli.loadSavedConnections(() => {
root.savedConnections = Nmcli.savedConnections;
root.savedConnectionSsids = Nmcli.savedConnectionSsids;
});
// Get initial WiFi status
Nmcli.getWifiStatus(enabled => {
root.wifiEnabled = enabled;
});
// Sync networks from Nmcli on startup
Qt.callLater(() => {
syncNetworksFromNmcli();
}, 100);
}
// Sync saved connections from Nmcli when they're updated
Connections {
function onSavedConnectionSsidsChanged() {
root.savedConnectionSsids = Nmcli.savedConnectionSsids;
}
function onSavedConnectionsChanged() {
root.savedConnections = Nmcli.savedConnections;
}
target: Nmcli
}
Component {
id: apComp
AccessPoint {
}
}
Process {
command: ["nmcli", "m"]
running: true
stdout: SplitParser {
onRead: {
Nmcli.getNetworks(() => {
syncNetworksFromNmcli();
});
getEthernetDevices();
}
}
}
component AccessPoint: QtObject {
readonly property bool active: lastIpcObject.active
readonly property string bssid: lastIpcObject.bssid
readonly property int frequency: lastIpcObject.frequency
readonly property bool isSecure: security.length > 0
required property var lastIpcObject
readonly property string security: lastIpcObject.security
readonly property string ssid: lastIpcObject.ssid
readonly property int strength: lastIpcObject.strength
}
}
-1358
View File
File diff suppressed because it is too large Load Diff
+288 -313
View File
@@ -14,360 +14,335 @@ import qs.Paths
import qs.Config import qs.Config
Singleton { Singleton {
id: root id: root
readonly property var appCooldownMap: new Map() property list<Notif> list: []
property alias dnd: props.dnd readonly property list<Notif> notClosed: list.filter( n => !n.closed )
property list<Notif> list: [] readonly property list<Notif> popups: list.filter( n => n.popup )
property bool loaded property alias dnd: props.dnd
readonly property list<Notif> notClosed: list.filter(n => !n.closed) property alias server: server
readonly property list<Notif> popups: list.filter(n => n.popup)
property alias server: server
function shouldThrottle(appName: string): bool { property bool loaded
if (props.dnd)
return false;
const key = (appName || "unknown").trim().toLowerCase(); onListChanged: {
const cooldownSec = Config.notifs.appNotifCooldown; if ( loaded ) {
const cooldownMs = Math.max(0, cooldownSec * 1000); saveTimer.restart();
}
if ( root.list.length > 0 ) {
HasNotifications.hasNotifications = true;
} else {
HasNotifications.hasNotifications = false;
}
}
if (cooldownMs <= 0) Timer {
return true; id: saveTimer
interval: 1000
onTriggered: storage.setText( JSON.stringify( root.notClosed.map( n => ({
time: n.time,
id: n.id,
summary: n.summary,
body: n.body,
appIcon: n.appIcon,
appName: n.appName,
image: n.image,
expireTimeout: n.expireTimeout,
urgency: n.urgency,
resident: n.resident,
hasActionIcons: n.hasActionIcons,
actions: n.actions
}))));
}
const now = Date.now(); PersistentProperties {
const until = appCooldownMap.get(key) ?? 0; id: props
if (now < until) property bool dnd
return false;
appCooldownMap.set(key, now + cooldownMs); reloadableId: "notifs"
return true; }
}
onListChanged: { NotificationServer {
if (loaded) { id: server
saveTimer.restart();
}
if (root.list.length > 0) {
HasNotifications.hasNotifications = true;
} else {
HasNotifications.hasNotifications = false;
}
}
Timer { keepOnReload: false
id: saveTimer actionsSupported: true
bodyHyperlinksSupported: true
bodyImagesSupported: true
bodyMarkupSupported: true
imageSupported: true
persistenceSupported: true
interval: 1000 onNotification: notif => {
notif.tracked = true;
onTriggered: storage.setText(JSON.stringify(root.notClosed.map(n => ({ const comp = notifComp.createObject(root, {
time: n.time, popup: !props.dnd,
id: n.id, notification: notif
summary: n.summary, });
body: n.body, root.list = [comp, ...root.list];
appIcon: n.appIcon, }
appName: n.appName, }
image: n.image,
expireTimeout: n.expireTimeout,
urgency: n.urgency,
resident: n.resident,
hasActionIcons: n.hasActionIcons,
actions: n.actions
}))))
}
PersistentProperties { FileView {
id: props id: storage
path: `${Paths.state}/notifs.json`
property bool dnd onLoaded: {
const data = JSON.parse(text());
for (const notif of data)
root.list.push(notifComp.createObject(root, notif));
root.list.sort((a, b) => b.time - a.time);
root.loaded = true;
}
reloadableId: "notifs" onLoadFailed: err => {
} if (err === FileViewError.FileNotFound) {
root.loaded = true;
setText("[]");
}
}
}
NotificationServer { CustomShortcut {
id: server name: "clearnotifs"
description: "Clear all notifications"
onPressed: {
for (const notif of root.list.slice())
notif.close();
}
}
actionsSupported: true IpcHandler {
bodyHyperlinksSupported: true target: "notifs"
bodyImagesSupported: true
bodyMarkupSupported: true
imageSupported: true
keepOnReload: false
persistenceSupported: true
onNotification: notif => { function clear(): void {
notif.tracked = true; for (const notif of root.list.slice())
notif.close();
}
const is_popup = root.shouldThrottle(notif.appName); function isDndEnabled(): bool {
return props.dnd;
}
const comp = notifComp.createObject(root, { function toggleDnd(): void {
popup: is_popup, props.dnd = !props.dnd;
notification: notif }
});
root.list = [comp, ...root.list];
}
}
FileView { function enableDnd(): void {
id: storage props.dnd = true;
}
path: `${Paths.state}/notifs.json` function disableDnd(): void {
props.dnd = false;
}
}
onLoadFailed: err => { component Notif: QtObject {
if (err === FileViewError.FileNotFound) { id: notif
root.loaded = true;
setText("[]");
}
}
onLoaded: {
const data = JSON.parse(text());
for (const notif of data)
root.list.push(notifComp.createObject(root, notif));
root.list.sort((a, b) => b.time - a.time);
root.loaded = true;
}
}
CustomShortcut { property bool popup
description: "Clear all notifications" property bool closed
name: "clearnotifs" property var locks: new Set()
onPressed: { property date time: new Date()
for (const notif of root.list.slice()) readonly property string timeStr: {
notif.close(); const diff = Time.date.getTime() - time.getTime();
} const m = Math.floor(diff / 60000);
}
IpcHandler { if (m < 1)
function clear(): void { return qsTr("now");
for (const notif of root.list.slice())
notif.close();
}
function disableDnd(): void { const h = Math.floor(m / 60);
props.dnd = false; const d = Math.floor(h / 24);
}
function enableDnd(): void { if (d > 0)
props.dnd = true; return `${d}d`;
} if (h > 0)
return `${h}h`;
return `${m}m`;
}
function isDndEnabled(): bool { property Notification notification
return props.dnd; property string id
} property string summary
property string body
property string appIcon
property string appName
property string image
property real expireTimeout: 5
property int urgency: NotificationUrgency.Normal
property bool resident
property bool hasActionIcons
property list<var> actions
function toggleDnd(): void { readonly property Timer timer: Timer {
props.dnd = !props.dnd;
}
target: "notifs"
}
Component {
id: notifComp
Notif {
}
}
component Notif: QtObject {
id: notif
property list<var> actions
property string appIcon
property string appName
property string body
property bool closed
readonly property Connections conn: Connections {
function onActionsChanged(): void {
notif.actions = notif.notification.actions.map(a => ({
identifier: a.identifier,
text: a.text,
invoke: () => a.invoke()
}));
}
function onAppIconChanged(): void {
notif.appIcon = notif.notification.appIcon;
}
function onAppNameChanged(): void {
notif.appName = notif.notification.appName;
}
function onBodyChanged(): void {
notif.body = notif.notification.body;
}
function onClosed(): void {
notif.close();
}
function onExpireTimeoutChanged(): void {
notif.expireTimeout = notif.notification.expireTimeout;
}
function onHasActionIconsChanged(): void {
notif.hasActionIcons = notif.notification.hasActionIcons;
}
function onImageChanged(): void {
notif.image = notif.notification.image;
if (notif.notification?.image)
notif.dummyImageLoader.active = true;
}
function onResidentChanged(): void {
notif.resident = notif.notification.resident;
}
function onSummaryChanged(): void {
notif.summary = notif.notification.summary;
}
function onUrgencyChanged(): void {
notif.urgency = notif.notification.urgency;
}
target: notif.notification
}
readonly property LazyLoader dummyImageLoader: LazyLoader {
active: false
PanelWindow {
color: "transparent"
implicitHeight: Config.notifs.sizes.image
implicitWidth: Config.notifs.sizes.image
mask: Region {
}
Image {
function tryCache(): void {
if (status !== Image.Ready || width != Config.notifs.sizes.image || height != Config.notifs.sizes.image)
return;
const cacheKey = notif.appName + notif.summary + notif.id;
let h1 = 0xdeadbeef, h2 = 0x41c6ce57, ch;
for (let i = 0; i < cacheKey.length; i++) {
ch = cacheKey.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
const hash = (h2 >>> 0).toString(16).padStart(8, 0) + (h1 >>> 0).toString(16).padStart(8, 0);
const cache = `${Paths.notifimagecache}/${hash}.png`;
ZShellIo.saveItem(this, Qt.resolvedUrl(cache), () => {
notif.image = cache;
notif.dummyImageLoader.active = false;
});
}
anchors.fill: parent
asynchronous: true
cache: false
fillMode: Image.PreserveAspectCrop
opacity: 0
source: Qt.resolvedUrl(notif.image)
onHeightChanged: tryCache()
onStatusChanged: tryCache()
onWidthChanged: tryCache()
}
}
}
property real expireTimeout: 5
property bool hasActionIcons
property string id
property string image
property var locks: new Set()
property Notification notification
property bool popup
property bool resident
property string summary
property date time: new Date()
readonly property string timeStr: {
const diff = Time.date.getTime() - time.getTime();
const m = Math.floor(diff / 60000);
if (m < 1)
return qsTr("now");
const h = Math.floor(m / 60);
const d = Math.floor(h / 24);
if (d > 0)
return `${d}d`;
if (h > 0)
return `${h}h`;
return `${m}m`;
}
readonly property Timer timer: Timer {
property bool paused: false
property int remainingTime: totalTime
property int totalTime: Config.notifs.defaultExpireTimeout property int totalTime: Config.notifs.defaultExpireTimeout
property int remainingTime: totalTime
property bool paused: false
interval: 50 running: !paused
repeat: true repeat: true
running: !paused interval: 50
onTriggered: {
onTriggered: {
remainingTime -= interval; remainingTime -= interval;
if (remainingTime <= 0) { if ( remainingTime <= 0 ) {
remainingTime = 0; remainingTime = 0;
notif.popup = false; notif.popup = false;
stop(); stop();
} }
} }
} }
property int urgency: NotificationUrgency.Normal
function close(): void { readonly property LazyLoader dummyImageLoader: LazyLoader {
closed = true; active: false
if (locks.size === 0 && root.list.includes(this)) {
root.list = root.list.filter(n => n !== this);
notification?.dismiss();
destroy();
}
}
function lock(item: Item): void { PanelWindow {
locks.add(item); implicitWidth: Config.notifs.sizes.image
} implicitHeight: Config.notifs.sizes.image
color: "transparent"
mask: Region {}
function unlock(item: Item): void { Image {
locks.delete(item); function tryCache(): void {
if (closed) if (status !== Image.Ready || width != Config.notifs.sizes.image || height != Config.notifs.sizes.image)
close(); return;
}
Component.onCompleted: { const cacheKey = notif.appName + notif.summary + notif.id;
if (!notification) let h1 = 0xdeadbeef, h2 = 0x41c6ce57, ch;
return; for (let i = 0; i < cacheKey.length; i++) {
ch = cacheKey.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
const hash = (h2 >>> 0).toString(16).padStart(8, 0) + (h1 >>> 0).toString(16).padStart(8, 0);
id = notification.id; const cache = `${Paths.notifimagecache}/${hash}.png`;
summary = notification.summary; ZShellIo.saveItem(this, Qt.resolvedUrl(cache), () => {
body = notification.body; notif.image = cache;
appIcon = notification.appIcon; notif.dummyImageLoader.active = false;
appName = notification.appName; });
image = notification.image; }
if (notification?.image)
dummyImageLoader.active = true; anchors.fill: parent
expireTimeout = notification.expireTimeout; source: Qt.resolvedUrl(notif.image)
urgency = notification.urgency; fillMode: Image.PreserveAspectCrop
resident = notification.resident; cache: false
hasActionIcons = notification.hasActionIcons; asynchronous: true
actions = notification.actions.map(a => ({ opacity: 0
identifier: a.identifier,
text: a.text, onStatusChanged: tryCache()
invoke: () => a.invoke() onWidthChanged: tryCache()
})); onHeightChanged: tryCache()
} }
} }
}
readonly property Connections conn: Connections {
target: notif.notification
function onClosed(): void {
notif.close();
}
function onSummaryChanged(): void {
notif.summary = notif.notification.summary;
}
function onBodyChanged(): void {
notif.body = notif.notification.body;
}
function onAppIconChanged(): void {
notif.appIcon = notif.notification.appIcon;
}
function onAppNameChanged(): void {
notif.appName = notif.notification.appName;
}
function onImageChanged(): void {
notif.image = notif.notification.image;
if (notif.notification?.image)
notif.dummyImageLoader.active = true;
}
function onExpireTimeoutChanged(): void {
notif.expireTimeout = notif.notification.expireTimeout;
}
function onUrgencyChanged(): void {
notif.urgency = notif.notification.urgency;
}
function onResidentChanged(): void {
notif.resident = notif.notification.resident;
}
function onHasActionIconsChanged(): void {
notif.hasActionIcons = notif.notification.hasActionIcons;
}
function onActionsChanged(): void {
notif.actions = notif.notification.actions.map(a => ({
identifier: a.identifier,
text: a.text,
invoke: () => a.invoke()
}));
}
}
function lock(item: Item): void {
locks.add(item);
}
function unlock(item: Item): void {
locks.delete(item);
if (closed)
close();
}
function close(): void {
closed = true;
if (locks.size === 0 && root.list.includes(this)) {
root.list = root.list.filter(n => n !== this);
notification?.dismiss();
destroy();
}
}
Component.onCompleted: {
if (!notification)
return;
id = notification.id;
summary = notification.summary;
body = notification.body;
appIcon = notification.appIcon;
appName = notification.appName;
image = notification.image;
if (notification?.image)
dummyImageLoader.active = true;
expireTimeout = notification.expireTimeout;
urgency = notification.urgency;
resident = notification.resident;
hasActionIcons = notification.hasActionIcons;
actions = notification.actions.map(a => ({
identifier: a.identifier,
text: a.text,
invoke: () => a.invoke()
}));
}
}
Component {
id: notifComp
Notif {}
}
} }
+29 -44
View File
@@ -1,8 +1,6 @@
import Quickshell import Quickshell
import QtQuick import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import qs.Components
import qs.Config
import qs.Modules as Modules import qs.Modules as Modules
import qs.Modules.Notifications as Notifications import qs.Modules.Notifications as Notifications
import qs.Modules.Notifications.Sidebar as Sidebar import qs.Modules.Notifications.Sidebar as Sidebar
@@ -10,86 +8,73 @@ import qs.Modules.Notifications.Sidebar.Utils as Utils
import qs.Modules.Dashboard as Dashboard import qs.Modules.Dashboard as Dashboard
import qs.Modules.Osd as Osd import qs.Modules.Osd as Osd
import qs.Modules.Launcher as Launcher import qs.Modules.Launcher as Launcher
import qs.Modules.Resources as Resources
// import qs.Modules.Settings as Settings
Shape { Shape {
id: root id: root
required property Item bar required property Panels panels
required property Panels panels required property Item bar
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.fill: parent anchors.fill: parent
// anchors.margins: 8 // anchors.margins: 8
anchors.topMargin: Config.barConfig.autoHide && !visibilities.bar ? 0 : bar.implicitHeight anchors.topMargin: bar.implicitHeight
preferredRendererType: Shape.CurveRenderer preferredRendererType: Shape.CurveRenderer
Behavior on anchors.topMargin { Component.onCompleted: console.log(root.bar.implicitHeight, root.bar.anchors.topMargin)
Anim {
}
}
Resources.Background {
startX: 0 - rounding
startY: 0
wrapper: root.panels.resources
}
Osd.Background { Osd.Background {
startX: root.width - root.panels.sidebar.width
startY: (root.height - wrapper.height) / 2 - rounding
wrapper: root.panels.osd wrapper: root.panels.osd
startX: root.width - root.panels.sidebar.width
startY: ( root.height - wrapper.height ) / 2 - rounding
} }
Modules.Background { Modules.Background {
invertBottomRounding: wrapper.x <= 0 wrapper: root.panels.popouts
startX: wrapper.x - 8 invertBottomRounding: wrapper.x <= 0
startY: wrapper.y
wrapper: root.panels.popouts startX: wrapper.x - 8
} startY: wrapper.y
}
Notifications.Background { Notifications.Background {
wrapper: root.panels.notifications
sidebar: sidebar sidebar: sidebar
startX: root.width startX: root.width
startY: 0 startY: 0
wrapper: root.panels.notifications
} }
Launcher.Background { Launcher.Background {
startX: (root.width - wrapper.width) / 2 - rounding
startY: root.height
wrapper: root.panels.launcher wrapper: root.panels.launcher
startX: ( root.width - wrapper.width ) / 2 - rounding
startY: root.height
} }
Dashboard.Background { Dashboard.Background {
wrapper: root.panels.dashboard
startX: root.width - root.panels.dashboard.width - rounding startX: root.width - root.panels.dashboard.width - rounding
startY: 0 startY: 0
wrapper: root.panels.dashboard
} }
Utils.Background { Utils.Background {
wrapper: root.panels.utilities
sidebar: sidebar sidebar: sidebar
startX: root.width startX: root.width
startY: root.height startY: root.height
wrapper: root.panels.utilities
} }
Sidebar.Background { Sidebar.Background {
id: sidebar id: sidebar
wrapper: root.panels.sidebar
panels: root.panels panels: root.panels
startX: root.width startX: root.width
startY: root.panels.notifications.height startY: root.panels.notifications.height
wrapper: root.panels.sidebar
} }
// Settings.Background {
// id: settings
//
// startX: (root.width - wrapper.width) / 2 - rounding
// startY: 0
// wrapper: root.panels.settings
// }
} }
-213
View File
@@ -1,213 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.Daemons
import qs.Components
import qs.Modules
import qs.Modules.Bar
import qs.Config
import qs.Helpers
import qs.Drawers
Variants {
model: Quickshell.screens
Scope {
id: scope
required property var modelData
PanelWindow {
id: bar
property var root: Quickshell.shellDir
property bool trayMenuVisible: false
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.resources ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
WlrLayershell.namespace: "ZShell-Bar"
color: "transparent"
contentItem.focus: true
screen: scope.modelData
mask: Region {
id: region
height: bar.screen.height - backgroundRect.implicitHeight
intersection: Intersection.Xor
regions: popoutRegions.instances
width: bar.width
x: 0
y: Config.barConfig.autoHide && !visibilities.bar ? 4 : 34
}
contentItem.Keys.onEscapePressed: {
if (Config.barConfig.autoHide)
visibilities.bar = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
}
PanelWindow {
id: exclusionZone
WlrLayershell.exclusionMode: Config.barConfig.autoHide ? ExclusionMode.Ignore : ExclusionMode.Auto
WlrLayershell.layer: WlrLayer.Bottom
WlrLayershell.namespace: "ZShell-Bar-Exclusion"
color: "transparent"
implicitHeight: 34
screen: bar.screen
anchors {
left: true
right: true
top: true
}
}
anchors {
bottom: true
left: true
right: true
top: true
}
Variants {
id: popoutRegions
model: panels.children
Region {
required property Item modelData
height: modelData.height
intersection: Intersection.Subtract
width: modelData.width
x: modelData.x
y: modelData.y + backgroundRect.implicitHeight
}
}
HyprlandFocusGrab {
id: focusGrab
active: visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu"))
windows: [bar]
onCleared: {
visibilities.launcher = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
panels.popouts.hasCurrent = false;
}
}
PersistentProperties {
id: visibilities
property bool bar
property bool dashboard
property bool launcher
property bool notif: NotifServer.popups.length > 0
property bool osd
property bool resources
property bool settings
property bool sidebar
Component.onCompleted: Visibilities.load(scope.modelData, this)
}
Binding {
property: "bar"
target: visibilities
value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || visibilities.notif || visibilities.resources
when: Config.barConfig.autoHide
}
Item {
anchors.fill: parent
layer.enabled: true
opacity: Appearance.transparency.enabled ? DynamicColors.transparency.base : 1
layer.effect: MultiEffect {
blurMax: 32
shadowColor: Qt.alpha(DynamicColors.palette.m3shadow, 1)
shadowEnabled: true
}
Border {
bar: backgroundRect
visibilities: visibilities
}
Backgrounds {
bar: backgroundRect
panels: panels
visibilities: visibilities
}
}
Interactions {
id: mouseArea
anchors.fill: parent
bar: barLoader
panels: panels
popouts: panels.popouts
screen: scope.modelData
visibilities: visibilities
Panels {
id: panels
bar: backgroundRect
screen: scope.modelData
visibilities: visibilities
}
CustomRect {
id: backgroundRect
property Wrapper popouts: panels.popouts
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: Config.barConfig.autoHide && !visibilities.bar ? -30 : 0
color: "transparent"
implicitHeight: 34
radius: 0
Behavior on anchors.topMargin {
Anim {
}
}
Behavior on color {
CAnim {
}
}
BarLoader {
id: barLoader
anchors.fill: parent
bar: bar
popouts: panels.popouts
screen: scope.modelData
visibilities: visibilities
}
}
}
}
}
}
+227 -223
View File
@@ -5,52 +5,53 @@ import qs.Config
import qs.Modules as BarPopouts import qs.Modules as BarPopouts
CustomMouseArea { CustomMouseArea {
id: root id: root
required property Item bar required property ShellScreen screen
property bool dashboardShortcutActive required property BarPopouts.Wrapper popouts
property point dragStart required property PersistentProperties visibilities
property bool osdShortcutActive required property Panels panels
required property Panels panels required property Item bar
required property BarPopouts.Wrapper popouts
required property ShellScreen screen
property bool utilitiesShortcutActive
required property PersistentProperties visibilities
function inBottomPanel(panel: Item, x: real, y: real): bool { property point dragStart
return y > root.height - panel.height && withinPanelWidth(panel, x, y); property bool dashboardShortcutActive
} property bool osdShortcutActive
property bool utilitiesShortcutActive
function inLeftPanel(panel: Item, x: real, y: real): bool { function withinPanelHeight(panel: Item, x: real, y: real): bool {
return x < panel.x + panel.width && withinPanelHeight(panel, x, y); const panelY = panel.y + bar.implicitHeight;
} return y >= panelY && y <= panelY + panel.height;
}
function inRightPanel(panel: Item, x: real, y: real): bool { function withinPanelWidth(panel: Item, x: real, y: real): bool {
return x > panel.x && withinPanelHeight(panel, x, y); const panelX = panel.x;
} return x >= panelX && x <= panelX + panel.width;
}
function inTopPanel(panel: Item, x: real, y: real): bool { function inLeftPanel(panel: Item, x: real, y: real): bool {
return y < bar.implicitHeight + panel.height && withinPanelWidth(panel, x, y); return x < panel.x + panel.width && withinPanelHeight(panel, x, y);
} }
function onWheel(event: WheelEvent): void { function inRightPanel(panel: Item, x: real, y: real): bool {
if (event.x < bar.implicitWidth) { return x > panel.x && withinPanelHeight(panel, x, y);
bar.handleWheel(event.y, event.angleDelta); }
}
}
function withinPanelHeight(panel: Item, x: real, y: real): bool { function inTopPanel(panel: Item, x: real, y: real): bool {
const panelY = panel.y + bar.implicitHeight; return y < bar.implicitHeight + panel.height && withinPanelWidth(panel, x, y);
return y >= panelY && y <= panelY + panel.height; }
}
function withinPanelWidth(panel: Item, x: real, y: real): bool { function inBottomPanel(panel: Item, x: real, y: real): bool {
const panelX = panel.x; return y > root.height - panel.height && withinPanelWidth(panel, x, y);
return x >= panelX && x <= panelX + panel.width; }
}
anchors.fill: parent function onWheel(event: WheelEvent): void {
hoverEnabled: true if (event.x < bar.implicitWidth) {
bar.handleWheel(event.y, event.angleDelta);
}
}
anchors.fill: parent
hoverEnabled: true
// onPressed: event => { // onPressed: event => {
// if ( root.popouts.hasCurrent && !inTopPanel( root.popouts, event.x, event.y )) { // if ( root.popouts.hasCurrent && !inTopPanel( root.popouts, event.x, event.y )) {
@@ -62,213 +63,216 @@ CustomMouseArea {
// } // }
// } // }
onContainsMouseChanged: { onContainsMouseChanged: {
if (!containsMouse) { if (!containsMouse) {
// Only hide if not activated by shortcut // Only hide if not activated by shortcut
if (!osdShortcutActive) { if (!osdShortcutActive) {
visibilities.osd = false; visibilities.osd = false;
root.panels.osd.hovered = false; root.panels.osd.hovered = false;
} }
if (!popouts.currentName.startsWith("traymenu")) { if (!popouts.currentName.startsWith("traymenu")) {
popouts.hasCurrent = false; popouts.hasCurrent = false;
} }
if (Config.barConfig.autoHide && !root.visibilities.sidebar && !root.visibilities.dashboard) if (Config.barConfig.autoHide && !root.visibilities.sidebar && !root.visibilities.dashboard)
root.visibilities.bar = false; root.visibilities.bar = false;
} }
} }
onPositionChanged: event => {
if (popouts.isDetached)
return;
const x = event.x; onPositionChanged: event => {
const y = event.y; if (popouts.isDetached)
const dragX = x - dragStart.x; return;
const dragY = y - dragStart.y;
// Show bar in non-exclusive mode on hover const x = event.x;
if (!visibilities.bar && Config.barConfig.autoHide && y < bar.implicitHeight + bar.anchors.topMargin) const y = event.y;
visibilities.bar = true; const dragX = x - dragStart.x;
const dragY = y - dragStart.y;
if (panels.sidebar.width === 0) { // Show bar in non-exclusive mode on hover
// Show osd on hover if (!visibilities.bar && Config.barConfig.autoHide && y < bar.implicitHeight + bar.anchors.topMargin)
const showOsd = inRightPanel(panels.osd, x, y); visibilities.bar = true;
// // Always update visibility based on hover if not in shortcut mode if (panels.sidebar.width === 0) {
if (!osdShortcutActive) { // Show osd on hover
visibilities.osd = showOsd; const showOsd = inRightPanel(panels.osd, x, y);
root.panels.osd.hovered = showOsd;
} else if (showOsd) {
// If hovering over OSD area while in shortcut mode, transition to hover control
osdShortcutActive = false;
root.panels.osd.hovered = true;
}
// const showSidebar = pressed && dragStart.x > bar.implicitWidth + panels.sidebar.x; // // Always update visibility based on hover if not in shortcut mode
// if (!osdShortcutActive) {
// // Show/hide session on drag visibilities.osd = showOsd;
// if (pressed && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) { root.panels.osd.hovered = showOsd;
// if (dragX < -Config.session.dragThreshold) } else if (showOsd) {
// visibilities.session = true; // If hovering over OSD area while in shortcut mode, transition to hover control
// else if (dragX > Config.session.dragThreshold) osdShortcutActive = false;
// visibilities.session = false; root.panels.osd.hovered = true;
// }
// // Show sidebar on drag if in session area and session is nearly fully visible
// if (showSidebar && panels.session.width >= panels.session.nonAnimWidth && dragX < -Config.sidebar.dragThreshold)
// visibilities.sidebar = true;
// } else if (showSidebar && dragX < -Config.sidebar.dragThreshold) {
// // Show sidebar on drag if not in session area
// visibilities.sidebar = true;
// }
} else {
const outOfSidebar = x < width - panels.sidebar.width;
// Show osd on hover
const showOsd = outOfSidebar && inRightPanel(panels.osd, x, y);
// Always update visibility based on hover if not in shortcut mode // const showSidebar = pressed && dragStart.x > bar.implicitWidth + panels.sidebar.x;
if (!osdShortcutActive) { //
visibilities.osd = showOsd; // // Show/hide session on drag
root.panels.osd.hovered = showOsd; // if (pressed && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) {
} else if (showOsd) { // if (dragX < -Config.session.dragThreshold)
// If hovering over OSD area while in shortcut mode, transition to hover control // visibilities.session = true;
osdShortcutActive = false; // else if (dragX > Config.session.dragThreshold)
root.panels.osd.hovered = true; // visibilities.session = false;
} //
// // // Show sidebar on drag if in session area and session is nearly fully visible
// // Show/hide session on drag // if (showSidebar && panels.session.width >= panels.session.nonAnimWidth && dragX < -Config.sidebar.dragThreshold)
// if (pressed && outOfSidebar && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) { // visibilities.sidebar = true;
// if (dragX < -Config.session.dragThreshold) // } else if (showSidebar && dragX < -Config.sidebar.dragThreshold) {
// visibilities.session = true; // // Show sidebar on drag if not in session area
// else if (dragX > Config.session.dragThreshold) // visibilities.sidebar = true;
// visibilities.session = false; // }
// } } else {
// const outOfSidebar = x < width - panels.sidebar.width;
// // Hide sidebar on drag // Show osd on hover
// if (pressed && inRightPanel(panels.sidebar, dragStart.x, 0) && dragX > Config.sidebar.dragThreshold) const showOsd = outOfSidebar && inRightPanel(panels.osd, x, y);
// visibilities.sidebar = false;
}
// Show launcher on hover, or show/hide on drag if hover is disabled // Always update visibility based on hover if not in shortcut mode
// if (Config.launcher.showOnHover) { if (!osdShortcutActive) {
// if (!visibilities.launcher && inBottomPanel(panels.launcher, x, y)) visibilities.osd = showOsd;
// visibilities.launcher = true; root.panels.osd.hovered = showOsd;
// } else if (pressed && inBottomPanel(panels.launcher, dragStart.x, dragStart.y) && withinPanelWidth(panels.launcher, x, y)) { } else if (showOsd) {
// if (dragY < -Config.launcher.dragThreshold) // If hovering over OSD area while in shortcut mode, transition to hover control
// visibilities.launcher = true; osdShortcutActive = false;
// else if (dragY > Config.launcher.dragThreshold) root.panels.osd.hovered = true;
// visibilities.launcher = false; }
// } //
// // // Show/hide session on drag
// // Show dashboard on hover // if (pressed && outOfSidebar && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) {
// const showDashboard = Config.dashboard.showOnHover && inTopPanel(panels.dashboard, x, y); // if (dragX < -Config.session.dragThreshold)
// // visibilities.session = true;
// // Always update visibility based on hover if not in shortcut mode // else if (dragX > Config.session.dragThreshold)
// if (!dashboardShortcutActive) { // visibilities.session = false;
// visibilities.dashboard = showDashboard; // }
// } else if (showDashboard) { //
// // If hovering over dashboard area while in shortcut mode, transition to hover control // // Hide sidebar on drag
// dashboardShortcutActive = false; // if (pressed && inRightPanel(panels.sidebar, dragStart.x, 0) && dragX > Config.sidebar.dragThreshold)
// } // visibilities.sidebar = false;
// }
// // Show/hide dashboard on drag (for touchscreen devices)
// if (pressed && inTopPanel(panels.dashboard, dragStart.x, dragStart.y) && withinPanelWidth(panels.dashboard, x, y)) {
// if (dragY > Config.dashboard.dragThreshold)
// visibilities.dashboard = true;
// else if (dragY < -Config.dashboard.dragThreshold)
// visibilities.dashboard = false;
// }
//
// // Show utilities on hover
// const showUtilities = inBottomPanel(panels.utilities, x, y);
//
// // Always update visibility based on hover if not in shortcut mode
// if (!utilitiesShortcutActive) {
// visibilities.utilities = showUtilities;
// } else if (showUtilities) {
// // If hovering over utilities area while in shortcut mode, transition to hover control
// utilitiesShortcutActive = false;
// }
// Show popouts on hover // Show launcher on hover, or show/hide on drag if hover is disabled
if (y < bar.implicitHeight) { // if (Config.launcher.showOnHover) {
bar.checkPopout(x); // if (!visibilities.launcher && inBottomPanel(panels.launcher, x, y))
} // visibilities.launcher = true;
} // } else if (pressed && inBottomPanel(panels.launcher, dragStart.x, dragStart.y) && withinPanelWidth(panels.launcher, x, y)) {
// if (dragY < -Config.launcher.dragThreshold)
// visibilities.launcher = true;
// else if (dragY > Config.launcher.dragThreshold)
// visibilities.launcher = false;
// }
//
// // Show dashboard on hover
// const showDashboard = Config.dashboard.showOnHover && inTopPanel(panels.dashboard, x, y);
//
// // Always update visibility based on hover if not in shortcut mode
// if (!dashboardShortcutActive) {
// visibilities.dashboard = showDashboard;
// } else if (showDashboard) {
// // If hovering over dashboard area while in shortcut mode, transition to hover control
// dashboardShortcutActive = false;
// }
//
// // Show/hide dashboard on drag (for touchscreen devices)
// if (pressed && inTopPanel(panels.dashboard, dragStart.x, dragStart.y) && withinPanelWidth(panels.dashboard, x, y)) {
// if (dragY > Config.dashboard.dragThreshold)
// visibilities.dashboard = true;
// else if (dragY < -Config.dashboard.dragThreshold)
// visibilities.dashboard = false;
// }
//
// // Show utilities on hover
// const showUtilities = inBottomPanel(panels.utilities, x, y);
//
// // Always update visibility based on hover if not in shortcut mode
// if (!utilitiesShortcutActive) {
// visibilities.utilities = showUtilities;
// } else if (showUtilities) {
// // If hovering over utilities area while in shortcut mode, transition to hover control
// utilitiesShortcutActive = false;
// }
// Monitor individual visibility changes // Show popouts on hover
Connections { if (y < bar.implicitHeight) {
function onDashboardChanged() { bar.checkPopout(x);
if (root.visibilities.dashboard) { }
// Dashboard became visible, immediately check if this should be shortcut mode }
const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY);
if (!inDashboardArea) {
root.dashboardShortcutActive = true;
}
root.visibilities.sidebar = false; // Monitor individual visibility changes
root.popouts.hasCurrent = false; Connections {
} else { target: root.visibilities
// Dashboard hidden, clear shortcut flag
root.dashboardShortcutActive = false;
// root.visibilities.bar = false;
}
}
function onLauncherChanged() { function onLauncherChanged() {
// If launcher is hidden, clear shortcut flags for dashboard and OSD // If launcher is hidden, clear shortcut flags for dashboard and OSD
if (!root.visibilities.launcher) { if (!root.visibilities.launcher) {
root.dashboardShortcutActive = false; root.dashboardShortcutActive = false;
root.osdShortcutActive = false; root.osdShortcutActive = false;
root.utilitiesShortcutActive = false; root.utilitiesShortcutActive = false;
// Also hide dashboard and OSD if they're not being hovered // Also hide dashboard and OSD if they're not being hovered
const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY); const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY);
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY); const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inDashboardArea) { if (!inDashboardArea) {
root.visibilities.dashboard = false; root.visibilities.dashboard = false;
} }
if (!inOsdArea) { if (!inOsdArea) {
root.visibilities.osd = false; root.visibilities.osd = false;
root.panels.osd.hovered = false; root.panels.osd.hovered = false;
} }
} }
} }
function onOsdChanged() {
if (root.visibilities.osd) {
// OSD became visible, immediately check if this should be shortcut mode
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inOsdArea) {
root.osdShortcutActive = true;
}
} else {
// OSD hidden, clear shortcut flag
root.osdShortcutActive = false;
}
}
function onSidebarChanged() { function onSidebarChanged() {
if (root.visibilities.sidebar) { if ( root.visibilities.sidebar ) {
root.visibilities.dashboard = false; root.visibilities.dashboard = false;
root.popouts.hasCurrent = false; root.popouts.hasCurrent = false;
} }
} }
function onUtilitiesChanged() { function onDashboardChanged() {
if (root.visibilities.utilities) { if (root.visibilities.dashboard) {
// Utilities became visible, immediately check if this should be shortcut mode // Dashboard became visible, immediately check if this should be shortcut mode
const inUtilitiesArea = root.inBottomPanel(root.panels.utilities, root.mouseX, root.mouseY); const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY);
if (!inUtilitiesArea) { if (!inDashboardArea) {
root.utilitiesShortcutActive = true; root.dashboardShortcutActive = true;
} }
} else {
// Utilities hidden, clear shortcut flag
root.utilitiesShortcutActive = false;
}
}
target: root.visibilities root.visibilities.sidebar = false;
} root.popouts.hasCurrent = false;
} else {
// Dashboard hidden, clear shortcut flag
root.dashboardShortcutActive = false;
// root.visibilities.bar = false;
}
}
function onOsdChanged() {
if (root.visibilities.osd) {
// OSD became visible, immediately check if this should be shortcut mode
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
console.log(inOsdArea);
if (!inOsdArea) {
root.osdShortcutActive = true;
}
} else {
// OSD hidden, clear shortcut flag
root.osdShortcutActive = false;
}
}
function onUtilitiesChanged() {
if (root.visibilities.utilities) {
// Utilities became visible, immediately check if this should be shortcut mode
const inUtilitiesArea = root.inBottomPanel(root.panels.utilities, root.mouseX, root.mouseY);
if (!inUtilitiesArea) {
root.utilitiesShortcutActive = true;
}
} else {
// Utilities hidden, clear shortcut flag
root.utilitiesShortcutActive = false;
}
}
}
} }
+53 -67
View File
@@ -1,6 +1,6 @@
import Quickshell import Quickshell
import QtQuick import QtQuick
import qs.Components import QtQuick.Shapes
import qs.Modules as Modules import qs.Modules as Modules
import qs.Modules.Notifications as Notifications import qs.Modules.Notifications as Notifications
import qs.Modules.Notifications.Sidebar as Sidebar import qs.Modules.Notifications.Sidebar as Sidebar
@@ -9,130 +9,116 @@ import qs.Modules.Dashboard as Dashboard
import qs.Modules.Osd as Osd import qs.Modules.Osd as Osd
import qs.Components.Toast as Toasts import qs.Components.Toast as Toasts
import qs.Modules.Launcher as Launcher import qs.Modules.Launcher as Launcher
import qs.Modules.Resources as Resources
// import qs.Modules.Settings as Settings
import qs.Config import qs.Config
Item { Item {
id: root id: root
required property Item bar required property ShellScreen screen
readonly property alias dashboard: dashboard required property Item bar
readonly property alias launcher: launcher
readonly property alias notifications: notifications
readonly property alias osd: osd
readonly property alias popouts: popouts
readonly property alias resources: resources
required property ShellScreen screen
// readonly property alias settings: settings
readonly property alias sidebar: sidebar
readonly property alias toasts: toasts
readonly property alias utilities: utilities
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.fill: parent readonly property alias popouts: popouts
// anchors.margins: 8 readonly property alias sidebar: sidebar
anchors.topMargin: Config.barConfig.autoHide && !visibilities.bar ? 0 : bar.implicitHeight readonly property alias notifications: notifications
readonly property alias utilities: utilities
readonly property alias dashboard: dashboard
readonly property alias osd: osd
readonly property alias toasts: toasts
readonly property alias launcher: launcher
anchors.fill: parent
// anchors.margins: 8
anchors.topMargin: Config.barConfig.autoHide && !visibilities.bar ? 0 : bar.implicitHeight
Behavior on anchors.topMargin { Behavior on anchors.topMargin {
Anim { Modules.Anim {}
}
}
Resources.Wrapper {
id: resources
anchors.left: parent.left
anchors.top: parent.top
visibilities: root.visibilities
} }
Osd.Wrapper { Osd.Wrapper {
id: osd id: osd
anchors.right: parent.right
anchors.rightMargin: sidebar.width
anchors.verticalCenter: parent.verticalCenter
clip: sidebar.width > 0 clip: sidebar.width > 0
screen: root.screen screen: root.screen
visibilities: root.visibilities visibilities: root.visibilities
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: sidebar.width
} }
Modules.Wrapper { Modules.Wrapper {
id: popouts id: popouts
anchors.top: parent.top screen: root.screen
screen: root.screen
x: { anchors.top: parent.top
const off = currentCenter - nonAnimWidth / 2;
const diff = root.width - Math.floor(off + nonAnimWidth); x: {
if (diff < 0) const off = currentCenter - nonAnimWidth / 2;
return off + diff; const diff = root.width - Math.floor(off + nonAnimWidth);
return Math.floor(Math.max(off, 0)); if ( diff < 0 )
} return off + diff;
} return Math.floor( Math.max( off, 0 ));
}
}
Toasts.Toasts { Toasts.Toasts {
id: toasts id: toasts
anchors.bottom: sidebar.visible ? parent.bottom : utilities.top anchors.bottom: sidebar.visible ? parent.bottom : utilities.top
anchors.margins: Appearance.padding.normal
anchors.right: sidebar.left anchors.right: sidebar.left
anchors.margins: Appearance.padding.normal
} }
Notifications.Wrapper { Notifications.Wrapper {
id: notifications id: notifications
anchors.right: parent.right
anchors.top: parent.top
panels: root
visibilities: root.visibilities visibilities: root.visibilities
panels: root
anchors.top: parent.top
anchors.right: parent.right
} }
Launcher.Wrapper { Launcher.Wrapper {
id: launcher id: launcher
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
panels: root
screen: root.screen screen: root.screen
visibilities: root.visibilities visibilities: root.visibilities
panels: root
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
} }
Utils.Wrapper { Utils.Wrapper {
id: utilities id: utilities
visibilities: root.visibilities
sidebar: sidebar
popouts: popouts
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
popouts: popouts
sidebar: sidebar
visibilities: root.visibilities
} }
Dashboard.Wrapper { Dashboard.Wrapper {
id: dashboard id: dashboard
visibilities: root.visibilities
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
visibilities: root.visibilities
} }
Sidebar.Wrapper { Sidebar.Wrapper {
id: sidebar id: sidebar
visibilities: root.visibilities
panels: root
anchors.top: notifications.bottom
anchors.bottom: utilities.top anchors.bottom: utilities.top
anchors.right: parent.right anchors.right: parent.right
anchors.top: notifications.bottom
panels: root
visibilities: root.visibilities
} }
// Settings.Wrapper {
// id: settings
//
// anchors.horizontalCenter: parent.horizontalCenter
// anchors.top: parent.top
// panels: root
// visibilities: root.visibilities
// }
} }
+23 -26
View File
@@ -2,32 +2,29 @@ import QtQuick
import QtQuick.Effects import QtQuick.Effects
Item { Item {
id: root id: root
property real radius
property real radius Rectangle {
id: shadowRect
anchors.fill: root
radius: root.radius
layer.enabled: true
color: "black"
visible: false
}
Rectangle { MultiEffect {
id: shadowRect id: effects
source: shadowRect
anchors.fill: root anchors.fill: shadowRect
color: "black" shadowBlur: 2.0
layer.enabled: true shadowEnabled: true
radius: root.radius shadowOpacity: 1
visible: false shadowColor: "black"
} maskSource: shadowRect
maskEnabled: true
MultiEffect { maskInverted: true
id: effects autoPaddingEnabled: true
}
anchors.fill: shadowRect
autoPaddingEnabled: true
maskEnabled: true
maskInverted: true
maskSource: shadowRect
shadowBlur: 2.0
shadowColor: "black"
shadowEnabled: true
shadowOpacity: 1
source: shadowRect
}
} }
+50 -54
View File
@@ -8,49 +8,49 @@ import ZShell
import qs.Components import qs.Components
Scope { Scope {
LazyLoader { LazyLoader {
id: root id: root
property bool closing property bool freeze
property bool freeze property bool closing
Variants { Variants {
model: Quickshell.screens model: Quickshell.screens
PanelWindow {
id: win
color: "transparent"
PanelWindow { required property ShellScreen modelData
id: win
required property ShellScreen modelData screen: modelData
WlrLayershell.namespace: "areapicker"
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: root.closing ? WlrKeyboardFocus.None : WlrKeyboardFocus.Exclusive
mask: root.closing ? empty : null
WlrLayershell.exclusionMode: ExclusionMode.Ignore anchors {
WlrLayershell.keyboardFocus: root.closing ? WlrKeyboardFocus.None : WlrKeyboardFocus.Exclusive top: true
WlrLayershell.layer: WlrLayer.Overlay bottom: true
WlrLayershell.namespace: "areapicker" left: true
color: "transparent" right: true
mask: root.closing ? empty : null }
screen: modelData
anchors { Region {
bottom: true id: empty
left: true }
right: true
top: true
}
Region { Picker {
id: empty loader: root
screen: win.modelData
} }
}
Picker { }
loader: root }
screen: win.modelData
}
}
}
}
IpcHandler { IpcHandler {
target: "picker"
function open(): void { function open(): void {
root.freeze = false; root.freeze = false;
root.closing = false; root.closing = false;
@@ -62,27 +62,23 @@ Scope {
root.closing = false; root.closing = false;
root.activeAsync = true; root.activeAsync = true;
} }
target: "picker"
} }
CustomShortcut { CustomShortcut {
name: "screenshot" name: "screenshot"
onPressed: {
root.freeze = false;
root.closing = false;
root.activeAsync = true;
}
}
onPressed: { CustomShortcut {
root.freeze = false; name: "screenshotFreeze"
root.closing = false; onPressed: {
root.activeAsync = true; root.freeze = true;
} root.closing = false;
} root.activeAsync = true;
}
CustomShortcut { }
name: "screenshotFreeze"
onPressed: {
root.freeze = true;
root.closing = false;
root.activeAsync = true;
}
}
} }
+176 -214
View File
@@ -8,257 +8,219 @@ import qs.Config
import qs.Components import qs.Components
Singleton { Singleton {
id: root id: root
property bool appleDisplayPresent: false property list<var> ddcMonitors: []
property list<var> ddcMonitors: [] readonly property list<Monitor> monitors: variants.instances
property list<var> ddcServiceMon: [] property bool appleDisplayPresent: false
readonly property list<Monitor> monitors: variants.instances
function decreaseBrightness(): void { function getMonitorForScreen(screen: ShellScreen): var {
const monitor = getMonitor("active"); return monitors.find(m => m.modelData === screen);
if (monitor) }
monitor.setBrightness(monitor.brightness - Config.services.brightnessIncrement);
}
function getMonitor(query: string): var { function getMonitor(query: string): var {
if (query === "active") { if (query === "active") {
return monitors.find(m => Hypr.monitorFor(m.modelData)?.focused); return monitors.find(m => Hypr.monitorFor(m.modelData)?.focused);
} }
if (query.startsWith("model:")) { if (query.startsWith("model:")) {
const model = query.slice(6); const model = query.slice(6);
return monitors.find(m => m.modelData.model === model); return monitors.find(m => m.modelData.model === model);
} }
if (query.startsWith("serial:")) { if (query.startsWith("serial:")) {
const serial = query.slice(7); const serial = query.slice(7);
return monitors.find(m => m.modelData.serialNumber === serial); return monitors.find(m => m.modelData.serialNumber === serial);
} }
if (query.startsWith("id:")) { if (query.startsWith("id:")) {
const id = parseInt(query.slice(3), 10); const id = parseInt(query.slice(3), 10);
return monitors.find(m => Hypr.monitorFor(m.modelData)?.id === id); return monitors.find(m => Hypr.monitorFor(m.modelData)?.id === id);
} }
return monitors.find(m => m.modelData.name === query); return monitors.find(m => m.modelData.name === query);
} }
function getMonitorForScreen(screen: ShellScreen): var { function increaseBrightness(): void {
return monitors.find(m => m.modelData === screen); const monitor = getMonitor("active");
} if (monitor)
monitor.setBrightness(monitor.brightness + Config.services.brightnessIncrement);
}
function increaseBrightness(): void { function decreaseBrightness(): void {
const monitor = getMonitor("active"); const monitor = getMonitor("active");
if (monitor) if (monitor)
monitor.setBrightness(monitor.brightness + Config.services.brightnessIncrement); monitor.setBrightness(monitor.brightness - Config.services.brightnessIncrement);
} }
onMonitorsChanged: { onMonitorsChanged: {
ddcMonitors = []; ddcMonitors = [];
ddcServiceMon = []; ddcProc.running = true;
ddcServiceProc.running = true; }
ddcProc.running = true;
}
Variants { Variants {
id: variants id: variants
model: Quickshell.screens model: Quickshell.screens
Monitor { Monitor {}
} }
}
Process { Process {
command: ["sh", "-c", "asdbctl get"] running: true
running: true command: ["sh", "-c", "asdbctl get"] // To avoid warnings if asdbctl is not installed
stdout: StdioCollector {
onStreamFinished: root.appleDisplayPresent = text.trim().length > 0
}
}
stdout: StdioCollector { Process {
onStreamFinished: root.appleDisplayPresent = text.trim().length > 0 id: ddcProc
}
}
Process { command: ["ddcutil", "detect", "--brief"]
id: ddcProc stdout: StdioCollector {
onStreamFinished: root.ddcMonitors = text.trim().split("\n\n").filter(d => d.startsWith("Display ")).map(d => ({
busNum: d.match(/I2C bus:[ ]*\/dev\/i2c-([0-9]+)/)[1],
connector: d.match(/DRM connector:\s+(.*)/)[1].replace(/^card\d+-/, "") // strip "card1-"
}))
}
}
command: ["ddcutil", "detect", "--brief"] CustomShortcut {
name: "brightnessUp"
description: "Increase brightness"
onPressed: root.increaseBrightness()
}
stdout: StdioCollector { CustomShortcut {
onStreamFinished: root.ddcMonitors = text.trim().split("\n\n").filter(d => d.startsWith("Display ")).map(d => ({ name: "brightnessDown"
busNum: d.match(/I2C bus:[ ]*\/dev\/i2c-([0-9]+)/)[1], description: "Decrease brightness"
connector: d.match(/DRM connector:\s+(.*)/)[1].replace(/^card\d+-/, "") // strip "card1-" onPressed: root.decreaseBrightness()
})) }
}
}
Process { IpcHandler {
id: ddcServiceProc target: "brightness"
command: ["ddcutil-client", "detect"] function get(): real {
return getFor("active");
}
// running: true // Allows searching by active/model/serial/id/name
function getFor(query: string): real {
return root.getMonitor(query)?.brightness ?? -1;
}
stdout: StdioCollector { function set(value: string): string {
onStreamFinished: { return setFor("active", value);
const t = text.replace(/\r\n/g, "\n").trim(); }
const output = ("\n" + t).split(/\n(?=display:\s*\d+\s*\n)/).filter(b => b.startsWith("display:")).map(b => ({ // Handles brightness value like brightnessctl: 0.1, +0.1, 0.1-, 10%, +10%, 10%-
display: Number(b.match(/^display:\s*(\d+)/m)?.[1] ?? -1), function setFor(query: string, value: string): string {
name: (b.match(/^\s*product_name:\s*(.*)$/m)?.[1] ?? "").trim() const monitor = root.getMonitor(query);
})).filter(d => d.display > 0); if (!monitor)
root.ddcServiceMon = output; return "Invalid monitor: " + query;
}
}
}
CustomShortcut { let targetBrightness;
description: "Increase brightness" if (value.endsWith("%-")) {
name: "brightnessUp" const percent = parseFloat(value.slice(0, -2));
targetBrightness = monitor.brightness - (percent / 100);
} else if (value.startsWith("+") && value.endsWith("%")) {
const percent = parseFloat(value.slice(1, -1));
targetBrightness = monitor.brightness + (percent / 100);
} else if (value.endsWith("%")) {
const percent = parseFloat(value.slice(0, -1));
targetBrightness = percent / 100;
} else if (value.startsWith("+")) {
const increment = parseFloat(value.slice(1));
targetBrightness = monitor.brightness + increment;
} else if (value.endsWith("-")) {
const decrement = parseFloat(value.slice(0, -1));
targetBrightness = monitor.brightness - decrement;
} else if (value.includes("%") || value.includes("-") || value.includes("+")) {
return `Invalid brightness format: ${value}\nExpected: 0.1, +0.1, 0.1-, 10%, +10%, 10%-`;
} else {
targetBrightness = parseFloat(value);
}
onPressed: root.increaseBrightness() if (isNaN(targetBrightness))
} return `Failed to parse value: ${value}\nExpected: 0.1, +0.1, 0.1-, 10%, +10%, 10%-`;
CustomShortcut { monitor.setBrightness(targetBrightness);
description: "Decrease brightness"
name: "brightnessDown"
onPressed: root.decreaseBrightness() return `Set monitor ${monitor.modelData.name} brightness to ${+monitor.brightness.toFixed(2)}`;
} }
}
IpcHandler { component Monitor: QtObject {
function get(): real { id: monitor
return getFor("active");
}
// Allows searching by active/model/serial/id/name required property ShellScreen modelData
function getFor(query: string): real { readonly property bool isDdc: root.ddcMonitors.some(m => m.connector === modelData.name)
return root.getMonitor(query)?.brightness ?? -1; readonly property string busNum: root.ddcMonitors.find(m => m.connector === modelData.name)?.busNum ?? ""
} readonly property bool isAppleDisplay: root.appleDisplayPresent && modelData.model.startsWith("StudioDisplay")
property real brightness
property real queuedBrightness: NaN
function set(value: string): string { readonly property Process initProc: Process {
return setFor("active", value); stdout: StdioCollector {
} onStreamFinished: {
if (monitor.isAppleDisplay) {
const val = parseInt(text.trim());
monitor.brightness = val / 101;
} else {
const [, , , cur, max] = text.split(" ");
monitor.brightness = parseInt(cur) / parseInt(max);
}
}
}
}
// Handles brightness value like brightnessctl: 0.1, +0.1, 0.1-, 10%, +10%, 10%- readonly property Timer timer: Timer {
function setFor(query: string, value: string): string { interval: 500
const monitor = root.getMonitor(query); onTriggered: {
if (!monitor) if (!isNaN(monitor.queuedBrightness)) {
return "Invalid monitor: " + query; monitor.setBrightness(monitor.queuedBrightness);
monitor.queuedBrightness = NaN;
}
}
}
let targetBrightness; function setBrightness(value: real): void {
if (value.endsWith("%-")) { value = Math.max(0, Math.min(1, value));
const percent = parseFloat(value.slice(0, -2)); const rounded = Math.round(value * 100);
targetBrightness = monitor.brightness - (percent / 100); if (Math.round(brightness * 100) === rounded)
} else if (value.startsWith("+") && value.endsWith("%")) { return;
const percent = parseFloat(value.slice(1, -1));
targetBrightness = monitor.brightness + (percent / 100);
} else if (value.endsWith("%")) {
const percent = parseFloat(value.slice(0, -1));
targetBrightness = percent / 100;
} else if (value.startsWith("+")) {
const increment = parseFloat(value.slice(1));
targetBrightness = monitor.brightness + increment;
} else if (value.endsWith("-")) {
const decrement = parseFloat(value.slice(0, -1));
targetBrightness = monitor.brightness - decrement;
} else if (value.includes("%") || value.includes("-") || value.includes("+")) {
return `Invalid brightness format: ${value}\nExpected: 0.1, +0.1, 0.1-, 10%, +10%, 10%-`;
} else {
targetBrightness = parseFloat(value);
}
if (isNaN(targetBrightness)) if (isDdc && timer.running) {
return `Failed to parse value: ${value}\nExpected: 0.1, +0.1, 0.1-, 10%, +10%, 10%-`; queuedBrightness = value;
return;
}
monitor.setBrightness(targetBrightness); brightness = value;
return `Set monitor ${monitor.modelData.name} brightness to ${+monitor.brightness.toFixed(2)}`; if (isAppleDisplay)
} Quickshell.execDetached(["asdbctl", "set", rounded]);
else if (isDdc)
Quickshell.execDetached(["ddcutil", "--disable-dynamic-sleep", "--sleep-multiplier", ".1", "--skip-ddc-checks", "-b", busNum, "setvcp", "10", rounded]);
else
Quickshell.execDetached(["brightnessctl", "s", `${rounded}%`]);
target: "brightness" if (isDdc)
} timer.restart();
}
component Monitor: QtObject { function initBrightness(): void {
id: monitor if (isAppleDisplay)
initProc.command = ["asdbctl", "get"];
else if (isDdc)
initProc.command = ["ddcutil", "-b", busNum, "getvcp", "10", "--brief"];
else
initProc.command = ["sh", "-c", "echo a b c $(brightnessctl g) $(brightnessctl m)"];
property real brightness initProc.running = true;
readonly property string busNum: root.ddcMonitors.find(m => m.connector === modelData.name)?.busNum ?? "" }
readonly property string displayNum: root.ddcServiceMon.find(m => m.name === modelData.model)?.display ?? ""
readonly property Process initProc: Process {
stdout: StdioCollector {
onStreamFinished: {
if (monitor.isDdcService) {
const output = text.split("\n").filter(o => o.startsWith("vcp_current_value:"))[0].split(":")[1];
const val = parseInt(output.trim());
monitor.brightness = val / 100;
} else if (monitor.isAppleDisplay) {
const val = parseInt(text.trim());
monitor.brightness = val / 101;
} else {
const [, , , cur, max] = text.split(" ");
monitor.brightness = parseInt(cur) / parseInt(max);
}
}
}
}
readonly property bool isAppleDisplay: root.appleDisplayPresent && modelData.model.startsWith("StudioDisplay")
readonly property bool isDdc: root.ddcMonitors.some(m => m.connector === modelData.name)
readonly property bool isDdcService: Config.services.ddcutilService
required property ShellScreen modelData
property real queuedBrightness: NaN
readonly property Timer timer: Timer {
interval: 500
onTriggered: { onBusNumChanged: initBrightness()
if (!isNaN(monitor.queuedBrightness)) { Component.onCompleted: initBrightness()
monitor.setBrightness(monitor.queuedBrightness); }
monitor.queuedBrightness = NaN;
}
}
}
function initBrightness(): void {
if (isDdcService)
initProc.command = ["ddcutil-client", "-d", displayNum, "getvcp", "10"];
else if (isAppleDisplay)
initProc.command = ["asdbctl", "get"];
else if (isDdc)
initProc.command = ["ddcutil", "-b", busNum, "getvcp", "10", "--brief"];
else
initProc.command = ["sh", "-c", "echo a b c $(brightnessctl g) $(brightnessctl m)"];
initProc.running = true;
}
function setBrightness(value: real): void {
value = Math.max(0, Math.min(1, value));
const rounded = Math.round(value * 100);
if (Math.round(brightness * 100) === rounded)
return;
if ((isDdc || isDdcService) && timer.running) {
queuedBrightness = value;
return;
}
brightness = value;
if (isDdcService)
Quickshell.execDetached(["ddcutil-client", "-d", displayNum, "setvcp", "10", rounded]);
else if (isAppleDisplay)
Quickshell.execDetached(["asdbctl", "set", rounded]);
else if (isDdc)
Quickshell.execDetached(["ddcutil", "--disable-dynamic-sleep", "--sleep-multiplier", ".1", "--skip-ddc-checks", "-b", busNum, "setvcp", "10", rounded]);
else
Quickshell.execDetached(["brightnessctl", "s", `${rounded}%`]);
if (isDdc || isDdcService)
timer.restart();
}
Component.onCompleted: initBrightness()
onBusNumChanged: initBrightness()
onDisplayNumChanged: initBrightness()
}
} }
+15 -15
View File
@@ -4,25 +4,25 @@ import QtQuick
import qs.Paths import qs.Paths
Image { Image {
id: root id: root
property alias path: manager.path property alias path: manager.path
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
Connections { Connections {
function onDevicePixelRatioChanged(): void { target: QsWindow.window
manager.updateSource();
}
target: QsWindow.window function onDevicePixelRatioChanged(): void {
} manager.updateSource();
}
}
CachingImageManager { CachingImageManager {
id: manager id: manager
cacheDir: Qt.resolvedUrl(Paths.imagecache) item: root
item: root cacheDir: Qt.resolvedUrl(Paths.imagecache)
} }
} }
+50 -48
View File
@@ -10,53 +10,6 @@ Singleton {
property int displayYear: new Date().getFullYear() property int displayYear: new Date().getFullYear()
readonly property int weekStartDay: 1 // 0 = Sunday, 1 = Monday readonly property int weekStartDay: 1 // 0 = Sunday, 1 = Monday
function getISOWeekNumber(date: var): int {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
return Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
}
function getWeekNumbers(month: int, year: int): var {
const days = getWeeksForMonth(month, year);
const weekNumbers = [];
let lastWeekNumber = -1;
for (let i = 0; i < days.length; i++) {
// Only add week numbers for days that belong to the current month
if (days[i].isCurrentMonth) {
const dayDate = new Date(days[i].year, days[i].month, days[i].day);
const weekNumber = getISOWeekNumber(dayDate);
// Only push if this is a new week
if (weekNumber !== lastWeekNumber) {
weekNumbers.push(weekNumber);
lastWeekNumber = weekNumber;
}
}
}
return weekNumbers;
}
function getWeekStartIndex(month: int, year: int): int {
const today = new Date();
if (today.getMonth() !== month || today.getFullYear() !== year) {
return 0;
}
const days = getWeeksForMonth(month, year);
for (let i = 0; i < days.length; i++) {
if (days[i].isToday) {
// Return the start index of the week containing today
return Math.floor(i / 7) * 7;
}
}
return 0;
}
function getWeeksForMonth(month: int, year: int): var { function getWeeksForMonth(month: int, year: int): var {
const firstDayOfMonth = new Date(year, month, 1); const firstDayOfMonth = new Date(year, month, 1);
const lastDayOfMonth = new Date(year, month + 1, 0); const lastDayOfMonth = new Date(year, month + 1, 0);
@@ -90,8 +43,57 @@ Singleton {
return days; return days;
} }
function getWeekNumbers(month: int, year: int): var {
const days = getWeeksForMonth(month, year);
const weekNumbers = [];
let lastWeekNumber = -1;
for (let i = 0; i < days.length; i++) {
// Only add week numbers for days that belong to the current month
if (days[i].isCurrentMonth) {
const dayDate = new Date(days[i].year, days[i].month, days[i].day);
const weekNumber = getISOWeekNumber(dayDate);
// Only push if this is a new week
if (weekNumber !== lastWeekNumber) {
weekNumbers.push(weekNumber);
lastWeekNumber = weekNumber;
}
}
}
return weekNumbers;
}
function getISOWeekNumber(date: var): int {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
return Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
}
function isDateToday(date: var): bool { function isDateToday(date: var): bool {
const today = new Date(); const today = new Date();
return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear(); return date.getDate() === today.getDate() &&
date.getMonth() === today.getMonth() &&
date.getFullYear() === today.getFullYear();
}
function getWeekStartIndex(month: int, year: int): int {
const today = new Date();
if (today.getMonth() !== month || today.getFullYear() !== year) {
return 0;
}
const days = getWeeksForMonth(month, year);
for (let i = 0; i < days.length; i++) {
if (days[i].isToday) {
// Return the start index of the week containing today
return Math.floor(i / 7) * 7;
}
}
return 0;
} }
} }
-17
View File
@@ -1,17 +0,0 @@
pragma Singleton
import Quickshell
Singleton {
id: root
function getTrayIcon(id: string, icon: string): string {
if (icon.includes("?path=")) {
const [name, path] = icon.split("?path=");
icon = Qt.resolvedUrl(`${path}/${name.slice(name.lastIndexOf("/") + 1)}`);
} else if (icon.includes("qspixmap") && id === "chrome_status_icon_1") {
icon = icon.replace("qspixmap", "icon/discord-tray");
}
return icon;
}
}
+6 -7
View File
@@ -4,13 +4,12 @@ import Quickshell
import Quickshell.Io import Quickshell.Io
Singleton { Singleton {
id: root id: root
property alias hasNotifications: adapter.hasNotifications property alias hasNotifications: adapter.hasNotifications
JsonObject { JsonObject {
id: adapter id: adapter
property bool hasNotifications: false
property bool hasNotifications: false }
}
} }
+127 -125
View File
@@ -9,155 +9,157 @@ import QtQuick
import qs.Components import qs.Components
Singleton { Singleton {
id: root id: root
property string activeName readonly property var toplevels: Hyprland.toplevels
readonly property HyprlandToplevel activeToplevel: Hyprland.activeToplevel readonly property var workspaces: Hyprland.workspaces
readonly property int activeWsId: focusedWorkspace?.id ?? 1 readonly property var monitors: Hyprland.monitors
property string applicationDir: "/usr/share/applications/"
readonly property bool capsLock: keyboard?.capsLock ?? false
readonly property string defaultKbLayout: keyboard?.layout.split(",")[0] ?? "??"
property string desktopName: ""
readonly property alias devices: extras.devices
readonly property alias extras: extras
readonly property HyprlandMonitor focusedMonitor: Hyprland.focusedMonitor
readonly property HyprlandWorkspace focusedWorkspace: Hyprland.focusedWorkspace
property bool hadKeyboard
readonly property string kbLayout: kbMap.get(kbLayoutFull) ?? "??"
readonly property string kbLayoutFull: keyboard?.activeKeymap ?? "Unknown"
readonly property var kbMap: new Map()
readonly property HyprKeyboard keyboard: extras.devices.keyboards.find(kb => kb.main) ?? null
readonly property var monitors: Hyprland.monitors
readonly property bool numLock: keyboard?.numLock ?? false
readonly property alias options: extras.options
readonly property var toplevels: Hyprland.toplevels
readonly property var workspaces: Hyprland.workspaces
signal configReloaded readonly property HyprlandToplevel activeToplevel: Hyprland.activeToplevel
readonly property HyprlandWorkspace focusedWorkspace: Hyprland.focusedWorkspace
readonly property HyprlandMonitor focusedMonitor: Hyprland.focusedMonitor
readonly property int activeWsId: focusedWorkspace?.id ?? 1
function dispatch(request: string): void { property string activeName
Hyprland.dispatch(request); property string applicationDir: "/usr/share/applications/"
} property string desktopName: ""
readonly property HyprKeyboard keyboard: extras.devices.keyboards.find(kb => kb.main) ?? null
readonly property bool capsLock: keyboard?.capsLock ?? false
readonly property bool numLock: keyboard?.numLock ?? false
readonly property string defaultKbLayout: keyboard?.layout.split(",")[0] ?? "??"
readonly property string kbLayoutFull: keyboard?.activeKeymap ?? "Unknown"
readonly property string kbLayout: kbMap.get(kbLayoutFull) ?? "??"
readonly property var kbMap: new Map()
readonly property alias extras: extras
readonly property alias options: extras.options
readonly property alias devices: extras.devices
property bool hadKeyboard
signal configReloaded
function getActiveScreen(): ShellScreen { function getActiveScreen(): ShellScreen {
return Quickshell.screens.find(screen => root.monitorFor(screen) === root.focusedMonitor); return Quickshell.screens.find(screen => root.monitorFor(screen) === root.focusedMonitor)
} }
function monitorFor(screen: ShellScreen): HyprlandMonitor { function dispatch(request: string): void {
return Hyprland.monitorFor(screen); Hyprland.dispatch(request);
} }
function reloadDynamicConfs(): void { function monitorFor(screen: ShellScreen): HyprlandMonitor {
extras.batchMessage(["keyword bindlni ,Caps_Lock,global,zshell:refreshDevices", "keyword bindlni ,Num_Lock,global,zshell:refreshDevices"]); return Hyprland.monitorFor(screen);
} }
Component.onCompleted: reloadDynamicConfs() function reloadDynamicConfs(): void {
extras.batchMessage(["keyword bindlni ,Caps_Lock,global,zshell:refreshDevices", "keyword bindlni ,Num_Lock,global,zshell:refreshDevices"]);
}
// function updateActiveWindow(): void { Component.onCompleted: reloadDynamicConfs()
// root.desktopName = root.applicationDir + root.activeToplevel?.lastIpcObject.class + ".desktop";
// }
Connections { // function updateActiveWindow(): void {
function onRawEvent(event: HyprlandEvent): void { // root.desktopName = root.applicationDir + root.activeToplevel?.lastIpcObject.class + ".desktop";
const n = event.name; // }
if (n.endsWith("v2"))
return;
if (n === "configreloaded") { Connections {
root.configReloaded(); target: Hyprland
root.reloadDynamicConfs();
} else if (["workspace", "moveworkspace", "activespecial", "focusedmon"].includes(n)) {
Hyprland.refreshWorkspaces();
Hyprland.refreshMonitors();
// Qt.callLater( root.updateActiveWindow );
} else if (["openwindow", "closewindow", "movewindow"].includes(n)) {
Hyprland.refreshToplevels();
Hyprland.refreshWorkspaces();
// Qt.callLater( root.updateActiveWindow );
} else if (n.includes("mon")) {
Hyprland.refreshMonitors();
// Qt.callLater( root.updateActiveWindow );
} else if (n.includes("workspace")) {
Hyprland.refreshWorkspaces();
// Qt.callLater( root.updateActiveWindow );
} else if (n.includes("window") || n.includes("group") || ["pin", "fullscreen", "changefloatingmode", "minimize"].includes(n)) {
Hyprland.refreshToplevels();
// Qt.callLater( root.updateActiveWindow );
}
}
target: Hyprland function onRawEvent(event: HyprlandEvent): void {
} const n = event.name;
if (n.endsWith("v2"))
return;
FileView { if (n === "configreloaded") {
id: desktopEntryName root.configReloaded();
root.reloadDynamicConfs();
} else if (["workspace", "moveworkspace", "activespecial", "focusedmon"].includes(n)) {
Hyprland.refreshWorkspaces();
Hyprland.refreshMonitors();
// Qt.callLater( root.updateActiveWindow );
} else if (["openwindow", "closewindow", "movewindow"].includes(n)) {
Hyprland.refreshToplevels();
Hyprland.refreshWorkspaces();
// Qt.callLater( root.updateActiveWindow );
} else if (n.includes("mon")) {
Hyprland.refreshMonitors();
// Qt.callLater( root.updateActiveWindow );
} else if (n.includes("workspace")) {
Hyprland.refreshWorkspaces();
// Qt.callLater( root.updateActiveWindow );
} else if (n.includes("window") || n.includes("group") || ["pin", "fullscreen", "changefloatingmode", "minimize"].includes(n)) {
Hyprland.refreshToplevels();
// Qt.callLater( root.updateActiveWindow );
}
}
}
path: root.desktopName FileView {
id: desktopEntryName
onLoaded: { path: root.desktopName
const lines = text().split("\n");
for (const line of lines) {
if (line.startsWith("Name=")) {
let name = line.replace("Name=", "");
let caseFix = name[0].toUpperCase() + name.slice(1);
root.activeName = caseFix;
break;
}
}
}
}
FileView { onLoaded: {
id: kbLayoutFile const lines = text().split( "\n" );
for ( const line of lines ) {
if ( line.startsWith( "Name=" )) {
let name = line.replace( "Name=", "" );
let caseFix = name[ 0 ].toUpperCase() + name.slice( 1 );
root.activeName = caseFix;
break;
}
}
}
}
path: Quickshell.env("ZSHELL_XKB_RULES_PATH") || "/usr/share/X11/xkb/rules/base.lst" FileView {
id: kbLayoutFile
onLoaded: { path: Quickshell.env("ZSHELL_XKB_RULES_PATH") || "/usr/share/X11/xkb/rules/base.lst"
const layoutMatch = text().match(/! layout\n([\s\S]*?)\n\n/); onLoaded: {
if (layoutMatch) { const layoutMatch = text().match(/! layout\n([\s\S]*?)\n\n/);
const lines = layoutMatch[1].split("\n"); if (layoutMatch) {
for (const line of lines) { const lines = layoutMatch[1].split("\n");
if (!line.trim() || line.trim().startsWith("!")) for (const line of lines) {
continue; if (!line.trim() || line.trim().startsWith("!"))
continue;
const match = line.match(/^\s*([a-z]{2,})\s+([a-zA-Z() ]+)$/); const match = line.match(/^\s*([a-z]{2,})\s+([a-zA-Z() ]+)$/);
if (match) if (match)
root.kbMap.set(match[2], match[1]); root.kbMap.set(match[2], match[1]);
} }
} }
const variantMatch = text().match(/! variant\n([\s\S]*?)\n\n/); const variantMatch = text().match(/! variant\n([\s\S]*?)\n\n/);
if (variantMatch) { if (variantMatch) {
const lines = variantMatch[1].split("\n"); const lines = variantMatch[1].split("\n");
for (const line of lines) { for (const line of lines) {
if (!line.trim() || line.trim().startsWith("!")) if (!line.trim() || line.trim().startsWith("!"))
continue; continue;
const match = line.match(/^\s*([a-zA-Z0-9_-]+)\s+([a-z]{2,}): (.+)$/); const match = line.match(/^\s*([a-zA-Z0-9_-]+)\s+([a-z]{2,}): (.+)$/);
if (match) if (match)
root.kbMap.set(match[3], match[2]); root.kbMap.set(match[3], match[2]);
} }
} }
} }
} }
IpcHandler { IpcHandler {
function refreshDevices(): void { target: "hypr"
extras.refreshDevices();
}
target: "hypr" function refreshDevices(): void {
} extras.refreshDevices();
}
}
CustomShortcut { CustomShortcut {
name: "refreshDevices" name: "refreshDevices"
onPressed: extras.refreshDevices()
onReleased: extras.refreshDevices()
}
onPressed: extras.refreshDevices() HyprExtras {
onReleased: extras.refreshDevices() id: extras
} }
HyprExtras {
id: extras
}
} }
+168 -167
View File
@@ -6,181 +6,182 @@ import Quickshell.Services.Notifications
import QtQuick import QtQuick
Singleton { Singleton {
id: root id: root
readonly property var categoryIcons: ({ readonly property var weatherIcons: ({
WebBrowser: "web", "0": "clear_day",
Printing: "print", "1": "clear_day",
Security: "security", "2": "partly_cloudy_day",
Network: "chat", "3": "cloud",
Archiving: "archive", "45": "foggy",
Compression: "archive", "48": "foggy",
Development: "code", "51": "rainy",
IDE: "code", "53": "rainy",
TextEditor: "edit_note", "55": "rainy",
Audio: "music_note", "56": "rainy",
Music: "music_note", "57": "rainy",
Player: "music_note", "61": "rainy",
Recorder: "mic", "63": "rainy",
Game: "sports_esports", "65": "rainy",
FileTools: "files", "66": "rainy",
FileManager: "files", "67": "rainy",
Filesystem: "files", "71": "cloudy_snowing",
FileTransfer: "files", "73": "cloudy_snowing",
Settings: "settings", "75": "snowing_heavy",
DesktopSettings: "settings", "77": "cloudy_snowing",
HardwareSettings: "settings", "80": "rainy",
TerminalEmulator: "terminal", "81": "rainy",
ConsoleOnly: "terminal", "82": "rainy",
Utility: "build", "85": "cloudy_snowing",
Monitor: "monitor_heart", "86": "snowing_heavy",
Midi: "graphic_eq", "95": "thunderstorm",
Mixer: "graphic_eq", "96": "thunderstorm",
AudioVideoEditing: "video_settings", "99": "thunderstorm"
AudioVideo: "music_video", })
Video: "videocam",
Building: "construction",
Graphics: "photo_library",
"2DGraphics": "photo_library",
RasterGraphics: "photo_library",
TV: "tv",
System: "host",
Office: "content_paste"
})
readonly property var weatherIcons: ({
"0": "clear_day",
"1": "clear_day",
"2": "partly_cloudy_day",
"3": "cloud",
"45": "foggy",
"48": "foggy",
"51": "rainy",
"53": "rainy",
"55": "rainy",
"56": "rainy",
"57": "rainy",
"61": "rainy",
"63": "rainy",
"65": "rainy",
"66": "rainy",
"67": "rainy",
"71": "cloudy_snowing",
"73": "cloudy_snowing",
"75": "snowing_heavy",
"77": "cloudy_snowing",
"80": "rainy",
"81": "rainy",
"82": "rainy",
"85": "cloudy_snowing",
"86": "snowing_heavy",
"95": "thunderstorm",
"96": "thunderstorm",
"99": "thunderstorm"
})
function getAppCategoryIcon(name: string, fallback: string): string { readonly property var categoryIcons: ({
const categories = DesktopEntries.heuristicLookup(name)?.categories; WebBrowser: "web",
Printing: "print",
Security: "security",
Network: "chat",
Archiving: "archive",
Compression: "archive",
Development: "code",
IDE: "code",
TextEditor: "edit_note",
Audio: "music_note",
Music: "music_note",
Player: "music_note",
Recorder: "mic",
Game: "sports_esports",
FileTools: "files",
FileManager: "files",
Filesystem: "files",
FileTransfer: "files",
Settings: "settings",
DesktopSettings: "settings",
HardwareSettings: "settings",
TerminalEmulator: "terminal",
ConsoleOnly: "terminal",
Utility: "build",
Monitor: "monitor_heart",
Midi: "graphic_eq",
Mixer: "graphic_eq",
AudioVideoEditing: "video_settings",
AudioVideo: "music_video",
Video: "videocam",
Building: "construction",
Graphics: "photo_library",
"2DGraphics": "photo_library",
RasterGraphics: "photo_library",
TV: "tv",
System: "host",
Office: "content_paste"
})
if (categories) function getAppIcon(name: string, fallback: string): string {
for (const [key, value] of Object.entries(categoryIcons)) const icon = DesktopEntries.heuristicLookup(name)?.icon;
if (categories.includes(key)) if (fallback !== "undefined")
return value; return Quickshell.iconPath(icon, fallback);
return fallback; return Quickshell.iconPath(icon);
} }
function getAppIcon(name: string, fallback: string): string { function getAppCategoryIcon(name: string, fallback: string): string {
const icon = DesktopEntries.heuristicLookup(name)?.icon; const categories = DesktopEntries.heuristicLookup(name)?.categories;
if (fallback !== "undefined")
return Quickshell.iconPath(icon, fallback);
return Quickshell.iconPath(icon);
}
function getBluetoothIcon(icon: string): string { if (categories)
if (icon.includes("headset") || icon.includes("headphones")) for (const [key, value] of Object.entries(categoryIcons))
return "headphones"; if (categories.includes(key))
if (icon.includes("audio")) return value;
return "speaker"; return fallback;
if (icon.includes("phone")) }
return "smartphone";
if (icon.includes("mouse"))
return "mouse";
if (icon.includes("keyboard"))
return "keyboard";
return "bluetooth";
}
function getMicVolumeIcon(volume: real, isMuted: bool): string { function getNetworkIcon(strength: int, isSecure = false): string {
if (!isMuted && volume > 0) if (isSecure) {
return "mic"; if (strength >= 80)
return "mic_off"; return "network_wifi_locked";
} if (strength >= 60)
return "network_wifi_3_bar_locked";
if (strength >= 40)
return "network_wifi_2_bar_locked";
if (strength >= 20)
return "network_wifi_1_bar_locked";
return "signal_wifi_0_bar";
} else {
if (strength >= 80)
return "network_wifi";
if (strength >= 60)
return "network_wifi_3_bar";
if (strength >= 40)
return "network_wifi_2_bar";
if (strength >= 20)
return "network_wifi_1_bar";
return "signal_wifi_0_bar";
}
}
function getNetworkIcon(strength: int, isSecure = false): string { function getBluetoothIcon(icon: string): string {
if (isSecure) { if (icon.includes("headset") || icon.includes("headphones"))
if (strength >= 80) return "headphones";
return "network_wifi_locked"; if (icon.includes("audio"))
if (strength >= 60) return "speaker";
return "network_wifi_3_bar_locked"; if (icon.includes("phone"))
if (strength >= 40) return "smartphone";
return "network_wifi_2_bar_locked"; if (icon.includes("mouse"))
if (strength >= 20) return "mouse";
return "network_wifi_1_bar_locked"; if (icon.includes("keyboard"))
return "signal_wifi_0_bar"; return "keyboard";
} else { return "bluetooth";
if (strength >= 80) }
return "network_wifi";
if (strength >= 60)
return "network_wifi_3_bar";
if (strength >= 40)
return "network_wifi_2_bar";
if (strength >= 20)
return "network_wifi_1_bar";
return "signal_wifi_0_bar";
}
}
function getNotifIcon(summary: string, urgency: int): string { function getWeatherIcon(code: string): string {
summary = summary.toLowerCase(); if (weatherIcons.hasOwnProperty(code))
if (summary.includes("reboot")) return weatherIcons[code];
return "restart_alt"; return "air";
if (summary.includes("recording")) }
return "screen_record";
if (summary.includes("battery"))
return "power";
if (summary.includes("screenshot"))
return "screenshot_monitor";
if (summary.includes("welcome"))
return "waving_hand";
if (summary.includes("time") || summary.includes("a break"))
return "schedule";
if (summary.includes("installed"))
return "download";
if (summary.includes("update"))
return "update";
if (summary.includes("unable to"))
return "deployed_code_alert";
if (summary.includes("profile"))
return "person";
if (summary.includes("file"))
return "folder_copy";
if (urgency === NotificationUrgency.Critical)
return "release_alert";
return "chat";
}
function getVolumeIcon(volume: real, isMuted: bool): string { function getNotifIcon(summary: string, urgency: int): string {
if (isMuted) summary = summary.toLowerCase();
return "no_sound"; if (summary.includes("reboot"))
if (volume >= 0.5) return "restart_alt";
return "volume_up"; if (summary.includes("recording"))
if (volume > 0) return "screen_record";
return "volume_down"; if (summary.includes("battery"))
return "volume_mute"; return "power";
} if (summary.includes("screenshot"))
return "screenshot_monitor";
if (summary.includes("welcome"))
return "waving_hand";
if (summary.includes("time") || summary.includes("a break"))
return "schedule";
if (summary.includes("installed"))
return "download";
if (summary.includes("update"))
return "update";
if (summary.includes("unable to"))
return "deployed_code_alert";
if (summary.includes("profile"))
return "person";
if (summary.includes("file"))
return "folder_copy";
if (urgency === NotificationUrgency.Critical)
return "release_alert";
return "chat";
}
function getWeatherIcon(code: string): string { function getVolumeIcon(volume: real, isMuted: bool): string {
if (weatherIcons.hasOwnProperty(code)) if (isMuted)
return weatherIcons[code]; return "no_sound";
return "air"; if (volume >= 0.5)
} return "volume_up";
if (volume > 0)
return "volume_down";
return "volume_mute";
}
function getMicVolumeIcon(volume: real, isMuted: bool): string {
if (!isMuted && volume > 0)
return "mic";
return "mic_off";
}
} }
+37 -41
View File
@@ -5,56 +5,52 @@ import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
Singleton { Singleton {
id: root id: root
property alias enabled: props.enabled property alias enabled: props.enabled
readonly property alias enabledSince: props.enabledSince readonly property alias enabledSince: props.enabledSince
onEnabledChanged: { onEnabledChanged: {
if (enabled) if (enabled)
props.enabledSince = new Date(); props.enabledSince = new Date();
} }
PersistentProperties { PersistentProperties {
id: props id: props
property bool enabled property bool enabled
property date enabledSince property date enabledSince
reloadableId: "idleInhibitor" reloadableId: "idleInhibitor"
} }
IdleInhibitor { IdleInhibitor {
enabled: props.enabled enabled: props.enabled
window: PanelWindow {
implicitWidth: 0
implicitHeight: 0
color: "transparent"
mask: Region {}
}
}
window: PanelWindow { IpcHandler {
WlrLayershell.namespace: "ZShell-IdleInhibitor" target: "idleInhibitor"
color: "transparent"
implicitHeight: 0
implicitWidth: 0
mask: Region { function isEnabled(): bool {
} return props.enabled;
} }
}
IpcHandler { function toggle(): void {
function disable(): void { props.enabled = !props.enabled;
props.enabled = false; }
}
function enable(): void { function enable(): void {
props.enabled = true; props.enabled = true;
} }
function isEnabled(): bool { function disable(): void {
return props.enabled; props.enabled = false;
} }
}
function toggle(): void {
props.enabled = !props.enabled;
}
target: "idleInhibitor"
}
} }
+9 -7
View File
@@ -5,12 +5,14 @@ import Quickshell.Hyprland
import qs.Helpers import qs.Helpers
Singleton { Singleton {
function getInitialTitle(callback) { function getInitialTitle(callback) {
let activeWindow = Hypr.activeToplevel.title; let activeWindow = Hypr.activeToplevel.title
let activeClass = Hypr.activeToplevel.lastIpcObject.class.toString(); let activeClass = Hypr.activeToplevel.lastIpcObject.class.toString()
let regex = new RegExp(activeClass, "i"); let regex = new RegExp(activeClass, "i")
const evalTitle = activeWindow.match(regex); console.log("ActiveWindow", activeWindow, "ActiveClass", activeClass, "Regex", regex)
callback(evalTitle);
} const evalTitle = activeWindow.match(regex)
callback(evalTitle)
}
} }
+35 -35
View File
@@ -10,11 +10,32 @@ import qs.Paths
Singleton { Singleton {
id: root id: root
readonly property int darkEnd: Config.general.color.scheduleDarkEnd
readonly property int darkStart: Config.general.color.scheduleDarkStart readonly property int darkStart: Config.general.color.scheduleDarkStart
readonly property int darkEnd: Config.general.color.scheduleDarkEnd
Timer {
id: darkModeTimer
interval: 5000
running: true
repeat: true
onTriggered: {
if ( darkStart === darkEnd )
return;
var now = new Date();
if ( now.getHours() >= darkStart || now.getHours() < darkEnd ) {
if ( DynamicColors.light )
applyDarkMode();
} else {
if ( !DynamicColors.light )
applyLightMode();
}
}
}
function applyDarkMode() { function applyDarkMode() {
if (Config.general.color.schemeGeneration) { if ( Config.general.color.schemeGeneration ) {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${WallpaperPath.currentWallpaperPath}`, "--thumbnail-path", `${Paths.cache}/imagecache/thumbnail.jpg`, "--output", `${Paths.state}/scheme.json`, "--scheme", `${Config.colors.schemeType}`, "--mode", "dark"]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${WallpaperPath.currentWallpaperPath}`, "--thumbnail-path", `${Paths.cache}/imagecache/thumbnail.jpg`, "--output", `${Paths.state}/scheme.json`, "--scheme", `${Config.colors.schemeType}`, "--mode", "dark"]);
} else { } else {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${DynamicColors.scheme}:${DynamicColors.flavour}`, "--output", `${Paths.state}/scheme.json`, "--mode", "dark"]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${DynamicColors.scheme}:${DynamicColors.flavour}`, "--output", `${Paths.state}/scheme.json`, "--mode", "dark"]);
@@ -22,18 +43,18 @@ Singleton {
Config.general.color.mode = "dark"; Config.general.color.mode = "dark";
Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-dark'"]); Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-dark'"])
Quickshell.execDetached(["sh", "-c", `sed -i 's/color_scheme_path=\\(.*\\)Light.colors/color_scheme_path=\\1Dark.colors/' ${Paths.home}/.config/qt6ct/qt6ct.conf`]); Quickshell.execDetached(["sh", "-c", `sed -i 's/color_scheme_path=\\(.*\\)Light.colors/color_scheme_path=\\1Dark.colors/' ${Paths.home}/.config/qt6ct/qt6ct.conf`])
Quickshell.execDetached(["sed", "-i", "'s/\\(vim.cmd.colorscheme \\).*/\\1\"tokyodark\"/'", "~/.config/nvim/lua/config/load-colorscheme.lua"]); Quickshell.execDetached(["sed", "-i", "'s/\\(vim.cmd.colorscheme \\).*/\\1\"tokyodark\"/'", "~/.config/nvim/lua/config/load-colorscheme.lua"])
if (Config.general.color.wallust) if( Config.general.color.wallust )
Wallust.generateColors(WallpaperPath.currentWallpaperPath); Wallust.generateColors(WallpaperPath.currentWallpaperPath);
} }
function applyLightMode() { function applyLightMode() {
if (Config.general.color.neovimColors) { if ( Config.general.color.neovimColors ) {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${WallpaperPath.currentWallpaperPath}`, "--thumbnail-path", `${Paths.cache}/imagecache/thumbnail.jpg`, "--output", `${Paths.state}/scheme.json`, "--scheme", `${Config.colors.schemeType}`, "--mode", "light"]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${WallpaperPath.currentWallpaperPath}`, "--thumbnail-path", `${Paths.cache}/imagecache/thumbnail.jpg`, "--output", `${Paths.state}/scheme.json`, "--scheme", `${Config.colors.schemeType}`, "--mode", "light"]);
} else { } else {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${DynamicColors.scheme}:${DynamicColors.flavour}`, "--output", `${Paths.state}/scheme.json`, "--mode", "light"]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${DynamicColors.scheme}:${DynamicColors.flavour}`, "--output", `${Paths.state}/scheme.json`, "--mode", "light"]);
@@ -41,46 +62,25 @@ Singleton {
Config.general.color.mode = "light"; Config.general.color.mode = "light";
Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-light'"]); Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-light'"])
Quickshell.execDetached(["sh", "-c", `sed -i 's/color_scheme_path=\\(.*\\)Dark.colors/color_scheme_path=\\1Light.colors/' ${Paths.home}/.config/qt6ct/qt6ct.conf`]); Quickshell.execDetached(["sh", "-c", `sed -i 's/color_scheme_path=\\(.*\\)Dark.colors/color_scheme_path=\\1Light.colors/' ${Paths.home}/.config/qt6ct/qt6ct.conf`])
if (Config.general.color.neovimColors) if ( Config.general.color.neovimColors )
Quickshell.execDetached(["sed", "-i", "'s/\\(vim.cmd.colorscheme \\).*/\\1\"onelight\"/'", "~/.config/nvim/lua/config/load-colorscheme.lua"]); Quickshell.execDetached(["sed", "-i", "'s/\\(vim.cmd.colorscheme \\).*/\\1\"onelight\"/'", "~/.config/nvim/lua/config/load-colorscheme.lua"])
if (Config.general.color.wallust) if( Config.general.color.wallust )
Wallust.generateColors(WallpaperPath.currentWallpaperPath); Wallust.generateColors(WallpaperPath.currentWallpaperPath);
} }
function checkStartup() { function checkStartup() {
if (darkStart === darkEnd) if ( darkStart === darkEnd )
return; return;
var now = new Date(); var now = new Date();
if (now.getHours() >= darkStart || now.getHours() < darkEnd) { if ( now.getHours() >= darkStart || now.getHours() < darkEnd ) {
applyDarkMode(); applyDarkMode();
} else { } else {
applyLightMode(); applyLightMode();
} }
} }
Timer {
id: darkModeTimer
interval: 5000
repeat: true
running: true
onTriggered: {
if (darkStart === darkEnd)
return;
var now = new Date();
if (now.getHours() >= darkStart || now.getHours() < darkEnd) {
if (DynamicColors.light)
applyDarkMode();
} else {
if (!DynamicColors.light)
applyLightMode();
}
}
}
} }
+1 -1
View File
@@ -6,6 +6,6 @@ import Quickshell.Networking
Singleton { Singleton {
id: root id: root
property NetworkDevice activeDevice: devices.find(d => d.connected)
property list<NetworkDevice> devices: Networking.devices.values property list<NetworkDevice> devices: Networking.devices.values
property NetworkDevice activeDevice: devices.find(d => d.connected)
} }
-234
View File
@@ -1,234 +0,0 @@
pragma Singleton
import qs.Config
import Quickshell
import Quickshell.Io
import QtQuick
Singleton {
id: root
property var _downloadHistory: []
// Private properties
property real _downloadSpeed: 0
property real _downloadTotal: 0
// Initial readings for calculating totals
property real _initialRxBytes: 0
property real _initialTxBytes: 0
property bool _initialized: false
// Previous readings for calculating speed
property real _prevRxBytes: 0
property real _prevTimestamp: 0
property real _prevTxBytes: 0
property var _uploadHistory: []
property real _uploadSpeed: 0
property real _uploadTotal: 0
// History of speeds for sparkline (most recent at end)
readonly property var downloadHistory: _downloadHistory
// Current speeds in bytes per second
readonly property real downloadSpeed: _downloadSpeed
// Total bytes transferred since tracking started
readonly property real downloadTotal: _downloadTotal
readonly property int historyLength: 30
property int refCount: 0
readonly property var uploadHistory: _uploadHistory
readonly property real uploadSpeed: _uploadSpeed
readonly property real uploadTotal: _uploadTotal
function formatBytes(bytes: real): var {
// Handle negative or invalid values
if (bytes < 0 || isNaN(bytes) || !isFinite(bytes)) {
return {
value: 0,
unit: "B/s"
};
}
if (bytes < 1024) {
return {
value: bytes,
unit: "B/s"
};
} else if (bytes < 1024 * 1024) {
return {
value: bytes / 1024,
unit: "KB/s"
};
} else if (bytes < 1024 * 1024 * 1024) {
return {
value: bytes / (1024 * 1024),
unit: "MB/s"
};
} else {
return {
value: bytes / (1024 * 1024 * 1024),
unit: "GB/s"
};
}
}
function formatBytesTotal(bytes: real): var {
// Handle negative or invalid values
if (bytes < 0 || isNaN(bytes) || !isFinite(bytes)) {
return {
value: 0,
unit: "B"
};
}
if (bytes < 1024) {
return {
value: bytes,
unit: "B"
};
} else if (bytes < 1024 * 1024) {
return {
value: bytes / 1024,
unit: "KB"
};
} else if (bytes < 1024 * 1024 * 1024) {
return {
value: bytes / (1024 * 1024),
unit: "MB"
};
} else {
return {
value: bytes / (1024 * 1024 * 1024),
unit: "GB"
};
}
}
function parseNetDev(content: string): var {
const lines = content.split("\n");
let totalRx = 0;
let totalTx = 0;
for (let i = 2; i < lines.length; i++) {
const line = lines[i].trim();
if (!line)
continue;
const parts = line.split(/\s+/);
if (parts.length < 10)
continue;
const iface = parts[0].replace(":", "");
// Skip loopback interface
if (iface === "lo")
continue;
const rxBytes = parseFloat(parts[1]) || 0;
const txBytes = parseFloat(parts[9]) || 0;
totalRx += rxBytes;
totalTx += txBytes;
}
return {
rx: totalRx,
tx: totalTx
};
}
FileView {
id: netDevFile
path: "/proc/net/dev"
}
Timer {
interval: Config.dashboard.resourceUpdateInterval
repeat: true
running: root.refCount > 0
triggeredOnStart: true
onTriggered: {
netDevFile.reload();
const content = netDevFile.text();
if (!content)
return;
const data = root.parseNetDev(content);
const now = Date.now();
if (!root._initialized) {
root._initialRxBytes = data.rx;
root._initialTxBytes = data.tx;
root._prevRxBytes = data.rx;
root._prevTxBytes = data.tx;
root._prevTimestamp = now;
root._initialized = true;
return;
}
const timeDelta = (now - root._prevTimestamp) / 1000; // seconds
if (timeDelta > 0) {
// Calculate byte deltas
let rxDelta = data.rx - root._prevRxBytes;
let txDelta = data.tx - root._prevTxBytes;
// Handle counter overflow (when counters wrap around from max to 0)
// This happens when counters exceed 32-bit or 64-bit limits
if (rxDelta < 0) {
// Counter wrapped around - assume 64-bit counter
rxDelta += Math.pow(2, 64);
}
if (txDelta < 0) {
txDelta += Math.pow(2, 64);
}
// Calculate speeds
root._downloadSpeed = rxDelta / timeDelta;
root._uploadSpeed = txDelta / timeDelta;
const maxHistory = root.historyLength + 1;
if (root._downloadSpeed >= 0 && isFinite(root._downloadSpeed)) {
let newDownHist = root._downloadHistory.slice();
newDownHist.push(root._downloadSpeed);
if (newDownHist.length > maxHistory) {
newDownHist.shift();
}
root._downloadHistory = newDownHist;
}
if (root._uploadSpeed >= 0 && isFinite(root._uploadSpeed)) {
let newUpHist = root._uploadHistory.slice();
newUpHist.push(root._uploadSpeed);
if (newUpHist.length > maxHistory) {
newUpHist.shift();
}
root._uploadHistory = newUpHist;
}
}
// Calculate totals with overflow handling
let downTotal = data.rx - root._initialRxBytes;
let upTotal = data.tx - root._initialTxBytes;
// Handle counter overflow for totals
if (downTotal < 0) {
downTotal += Math.pow(2, 64);
}
if (upTotal < 0) {
upTotal += Math.pow(2, 64);
}
root._downloadTotal = downTotal;
root._uploadTotal = upTotal;
root._prevRxBytes = data.rx;
root._prevTxBytes = data.tx;
root._prevTimestamp = now;
}
}
}
+6 -6
View File
@@ -4,13 +4,13 @@ import Quickshell
import Quickshell.Io import Quickshell.Io
Singleton { Singleton {
id: root id: root
property alias centerX: notifCenterSpacing.centerX property alias centerX: notifCenterSpacing.centerX
JsonAdapter { JsonAdapter {
id: notifCenterSpacing id: notifCenterSpacing
property int centerX property int centerX
} }
} }
+6 -6
View File
@@ -4,13 +4,13 @@ import Quickshell.Io
import Quickshell import Quickshell
Singleton { Singleton {
id: root id: root
property alias notifPath: storage.notifPath property alias notifPath: storage.notifPath
JsonAdapter { JsonAdapter {
id: storage id: storage
property string notifPath: Quickshell.statePath("notifications.json") property string notifPath: Quickshell.statePath("notifications.json")
} }
} }
+244 -235
View File
@@ -5,282 +5,291 @@ import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import qs.Components import qs.Modules
import qs.Config import qs.Config
import qs.Helpers import qs.Helpers
MouseArea { MouseArea {
id: root id: root
property list<var> clients: { required property LazyLoader loader
const mon = Hypr.monitorFor(screen); required property ShellScreen screen
if (!mon)
return [];
const special = mon.lastIpcObject.specialWorkspace; property bool onClient
const wsId = special.name ? special.id : mon.activeWorkspace.id;
return Hypr.toplevels.values.filter(c => c.workspace?.id === wsId).sort((a, b) => { property real realBorderWidth: onClient ? (Hypr.options["general:border_size"] ?? 1) : 2
const ac = a.lastIpcObject; property real realRounding: onClient ? (Hypr.options["decoration:rounding"] ?? 0) : 0
const bc = b.lastIpcObject;
return (bc.pinned - ac.pinned) || ((bc.fullscreen !== 0) - (ac.fullscreen !== 0)) || (bc.floating - ac.floating);
});
}
property real ex: screen.width
property real ey: screen.height
required property LazyLoader loader
property bool onClient
property real realBorderWidth: onClient ? (Hypr.options["general:border_size"] ?? 1) : 2
property real realRounding: onClient ? (Hypr.options["decoration:rounding"] ?? 0) : 0
property real rsx: Math.min(sx, ex)
property real rsy: Math.min(sy, ey)
required property ShellScreen screen
property real sh: Math.abs(sy - ey)
property real ssx
property real ssy
property real sw: Math.abs(sx - ex)
property real sx: 0
property real sy: 0
function checkClientRects(x: real, y: real): void { property real ssx
for (const client of clients) { property real ssy
if (!client)
continue;
let { property real sx: 0
at: [cx, cy], property real sy: 0
size: [cw, ch] property real ex: screen.width
} = client.lastIpcObject; property real ey: screen.height
cx -= screen.x;
cy -= screen.y;
if (cx <= x && cy <= y && cx + cw >= x && cy + ch >= y) {
onClient = true;
sx = cx;
sy = cy;
ex = cx + cw;
ey = cy + ch;
break;
}
}
}
function save(): void { property real rsx: Math.min(sx, ex)
const tmpfile = Qt.resolvedUrl(`/tmp/zshell-picker-${Quickshell.processId}-${Date.now()}.png`); property real rsy: Math.min(sy, ey)
ZShellIo.saveItem(screencopy, tmpfile, Qt.rect(Math.ceil(rsx), Math.ceil(rsy), Math.floor(sw), Math.floor(sh)), path => Quickshell.execDetached(["swappy", "-f", path])); property real sw: Math.abs(sx - ex)
closeAnim.start(); property real sh: Math.abs(sy - ey)
}
anchors.fill: parent property list<var> clients: {
cursorShape: Qt.CrossCursor const mon = Hypr.monitorFor(screen);
focus: true if (!mon)
hoverEnabled: true return [];
opacity: 0
Behavior on opacity { const special = mon.lastIpcObject.specialWorkspace;
Anim { const wsId = special.name ? special.id : mon.activeWorkspace.id;
duration: 300
}
}
Behavior on rsx {
enabled: !root.pressed
ExAnim { return Hypr.toplevels.values.filter(c => c.workspace?.id === wsId).sort((a, b) => {
} const ac = a.lastIpcObject;
} const bc = b.lastIpcObject;
Behavior on rsy { return (bc.pinned - ac.pinned) || ((bc.fullscreen !== 0) - (ac.fullscreen !== 0)) || (bc.floating - ac.floating);
enabled: !root.pressed });
}
ExAnim { function checkClientRects(x: real, y: real): void {
} for (const client of clients) {
} if (!client)
Behavior on sh { continue;
enabled: !root.pressed
ExAnim { let {
} at: [cx, cy],
} size: [cw, ch]
Behavior on sw { } = client.lastIpcObject;
enabled: !root.pressed cx -= screen.x;
cy -= screen.y;
if (cx <= x && cy <= y && cx + cw >= x && cy + ch >= y) {
onClient = true;
sx = cx;
sy = cy;
ex = cx + cw;
ey = cy + ch;
break;
}
}
}
ExAnim { function save(): void {
} const tmpfile = Qt.resolvedUrl(`/tmp/zshell-picker-${Quickshell.processId}-${Date.now()}.png`);
} ZShellIo.saveItem(screencopy, tmpfile, Qt.rect(Math.ceil(rsx), Math.ceil(rsy), Math.floor(sw), Math.floor(sh)), path => Quickshell.execDetached(["swappy", "-f", path]));
closeAnim.start();
}
Component.onCompleted: { onClientsChanged: checkClientRects(mouseX, mouseY)
Hypr.extras.refreshOptions();
if (loader.freeze)
clients = clients;
opacity = 1; anchors.fill: parent
opacity: 0
hoverEnabled: true
cursorShape: Qt.CrossCursor
const c = clients[0]; Component.onCompleted: {
if (c) { Hypr.extras.refreshOptions();
const cx = c.lastIpcObject.at[0] - screen.x; if (loader.freeze)
const cy = c.lastIpcObject.at[1] - screen.y; clients = clients;
onClient = true;
sx = cx;
sy = cy;
ex = cx + c.lastIpcObject.size[0];
ey = cy + c.lastIpcObject.size[1];
} else {
sx = screen.width / 2 - 100;
sy = screen.height / 2 - 100;
ex = screen.width / 2 + 100;
ey = screen.height / 2 + 100;
}
}
Keys.onEscapePressed: closeAnim.start()
onClientsChanged: checkClientRects(mouseX, mouseY)
onPositionChanged: event => {
const x = event.x;
const y = event.y;
if (pressed) { opacity = 1;
onClient = false;
sx = ssx;
sy = ssy;
ex = x;
ey = y;
} else {
checkClientRects(x, y);
}
}
onPressed: event => {
ssx = event.x;
ssy = event.y;
}
onReleased: {
if (closeAnim.running)
return;
if (root.loader.freeze) { const c = clients[0];
save(); if (c) {
} else { const cx = c.lastIpcObject.at[0] - screen.x;
overlay.visible = border.visible = false; const cy = c.lastIpcObject.at[1] - screen.y;
screencopy.visible = false; onClient = true;
screencopy.active = true; sx = cx;
} sy = cy;
} ex = cx + c.lastIpcObject.size[0];
ey = cy + c.lastIpcObject.size[1];
} else {
sx = screen.width / 2 - 100;
sy = screen.height / 2 - 100;
ex = screen.width / 2 + 100;
ey = screen.height / 2 + 100;
}
}
SequentialAnimation { onPressed: event => {
id: closeAnim ssx = event.x;
ssy = event.y;
}
PropertyAction { onReleased: {
property: "closing" if (closeAnim.running)
target: root.loader return;
value: true
}
ParallelAnimation { if (root.loader.freeze) {
Anim { save();
duration: 300 } else {
property: "opacity" overlay.visible = border.visible = false;
target: root screencopy.visible = false;
to: 0 screencopy.active = true;
} }
}
ExAnim { onPositionChanged: event => {
properties: "rsx,rsy" const x = event.x;
target: root const y = event.y;
to: 0
}
ExAnim { if (pressed) {
property: "sw" onClient = false;
target: root sx = ssx;
to: root.screen.width sy = ssy;
} ex = x;
ey = y;
} else {
checkClientRects(x, y);
}
}
ExAnim { focus: true
property: "sh" Keys.onEscapePressed: closeAnim.start()
target: root
to: root.screen.height
}
}
PropertyAction { SequentialAnimation {
property: "activeAsync" id: closeAnim
target: root.loader
value: false
}
}
Loader { PropertyAction {
id: screencopy target: root.loader
property: "closing"
value: true
}
ParallelAnimation {
Anim {
target: root
property: "opacity"
to: 0
duration: 300
}
ExAnim {
target: root
properties: "rsx,rsy"
to: 0
}
ExAnim {
target: root
property: "sw"
to: root.screen.width
}
ExAnim {
target: root
property: "sh"
to: root.screen.height
}
}
PropertyAction {
target: root.loader
property: "activeAsync"
value: false
}
}
active: root.loader.freeze Loader {
anchors.fill: parent id: screencopy
asynchronous: true
sourceComponent: ScreencopyView { anchors.fill: parent
captureSource: root.screen
paintCursor: false
onHasContentChanged: { active: root.loader.freeze
if (hasContent && !root.loader.freeze) { asynchronous: true
overlay.visible = border.visible = true;
root.save();
}
}
}
}
Rectangle { sourceComponent: ScreencopyView {
id: overlay captureSource: root.screen
anchors.fill: parent paintCursor: false
color: "white"
layer.enabled: true
opacity: 0.3
radius: root.realRounding
layer.effect: MultiEffect { onHasContentChanged: {
maskEnabled: true if (hasContent && !root.loader.freeze) {
maskInverted: true overlay.visible = border.visible = true;
maskSource: selectionWrapper root.save();
maskSpreadAtMin: 1 }
maskThresholdMin: 0.5 }
} }
} }
Item { Rectangle {
id: selectionWrapper id: overlay
anchors.fill: parent anchors.fill: parent
layer.enabled: true color: "white"
visible: false opacity: 0.3
Rectangle { radius: root.realRounding
id: selectionRect
implicitHeight: root.sh layer.enabled: true
implicitWidth: root.sw layer.effect: MultiEffect {
radius: root.realRounding maskSource: selectionWrapper
x: root.rsx maskEnabled: true
y: root.rsy maskInverted: true
} maskSpreadAtMin: 1
} maskThresholdMin: 0.5
}
}
Rectangle { Item {
id: border id: selectionWrapper
border.color: DynamicColors.palette.m3primary anchors.fill: parent
border.width: root.realBorderWidth layer.enabled: true
color: "transparent" visible: false
implicitHeight: selectionRect.implicitHeight + root.realBorderWidth * 2
implicitWidth: selectionRect.implicitWidth + root.realBorderWidth * 2
radius: root.realRounding > 0 ? root.realRounding + root.realBorderWidth : 0
x: selectionRect.x - root.realBorderWidth
y: selectionRect.y - root.realBorderWidth
Behavior on border.color { Rectangle {
Anim { id: selectionRect
}
}
}
component ExAnim: Anim { radius: root.realRounding
duration: MaterialEasing.expressiveEffectsTime x: root.rsx
easing.bezierCurve: MaterialEasing.expressiveEffects y: root.rsy
} implicitWidth: root.sw
implicitHeight: root.sh
}
}
Rectangle {
id: border
color: "transparent"
radius: root.realRounding > 0 ? root.realRounding + root.realBorderWidth : 0
border.width: root.realBorderWidth
border.color: DynamicColors.palette.m3primary
x: selectionRect.x - root.realBorderWidth
y: selectionRect.y - root.realBorderWidth
implicitWidth: selectionRect.implicitWidth + root.realBorderWidth * 2
implicitHeight: selectionRect.implicitHeight + root.realBorderWidth * 2
Behavior on border.color {
Anim {}
}
}
Behavior on opacity {
Anim {
duration: 300
}
}
Behavior on rsx {
enabled: !root.pressed
ExAnim {}
}
Behavior on rsy {
enabled: !root.pressed
ExAnim {}
}
Behavior on sw {
enabled: !root.pressed
ExAnim {}
}
Behavior on sh {
enabled: !root.pressed
ExAnim {}
}
component ExAnim: Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
} }
+91 -95
View File
@@ -9,119 +9,115 @@ import qs.Config
import qs.Components import qs.Components
Singleton { Singleton {
id: root id: root
readonly property MprisPlayer active: props.manualActive ?? list.find(p => getIdentity(p) === Config.services.defaultPlayer) ?? list[0] ?? null readonly property list<MprisPlayer> list: Mpris.players.values
readonly property list<MprisPlayer> list: Mpris.players.values readonly property MprisPlayer active: props.manualActive ?? list.find(p => getIdentity(p) === Config.services.defaultPlayer) ?? list[0] ?? null
property alias manualActive: props.manualActive property alias manualActive: props.manualActive
function getIdentity(player: MprisPlayer): string { function getIdentity(player: MprisPlayer): string {
const alias = Config.services.playerAliases.find(a => a.from === player.identity); const alias = Config.services.playerAliases.find(a => a.from === player.identity);
return alias?.to ?? player.identity; return alias?.to ?? player.identity;
} }
Connections { Connections {
function onPostTrackChanged() { target: active
if (!Config.utilities.toasts.nowPlaying) {
return;
}
}
target: active function onPostTrackChanged() {
} if (!Config.utilities.toasts.nowPlaying) {
return;
}
}
}
PersistentProperties { PersistentProperties {
id: props id: props
property MprisPlayer manualActive property MprisPlayer manualActive
reloadableId: "players" reloadableId: "players"
} }
CustomShortcut { CustomShortcut {
description: "Toggle media playback" name: "mediaToggle"
name: "mediaToggle" description: "Toggle media playback"
onPressed: {
const active = root.active;
if (active && active.canTogglePlaying)
active.togglePlaying();
}
}
onPressed: { CustomShortcut {
const active = root.active; name: "mediaPrev"
if (active && active.canTogglePlaying) description: "Previous track"
active.togglePlaying(); onPressed: {
} const active = root.active;
} if (active && active.canGoPrevious)
active.previous();
}
}
CustomShortcut { CustomShortcut {
description: "Previous track" name: "mediaNext"
name: "mediaPrev" description: "Next track"
onPressed: {
const active = root.active;
if (active && active.canGoNext)
active.next();
}
}
onPressed: { CustomShortcut {
const active = root.active; name: "mediaStop"
if (active && active.canGoPrevious) description: "Stop media playback"
active.previous(); onPressed: root.active?.stop()
} }
}
CustomShortcut { IpcHandler {
description: "Next track" target: "mpris"
name: "mediaNext"
onPressed: { function getActive(prop: string): string {
const active = root.active; const active = root.active;
if (active && active.canGoNext) return active ? active[prop] ?? "Invalid property" : "No active player";
active.next(); }
}
}
CustomShortcut { function list(): string {
description: "Stop media playback" return root.list.map(p => root.getIdentity(p)).join("\n");
name: "mediaStop" }
onPressed: root.active?.stop() function play(): void {
} const active = root.active;
if (active?.canPlay)
active.play();
}
IpcHandler { function pause(): void {
function getActive(prop: string): string { const active = root.active;
const active = root.active; if (active?.canPause)
return active ? active[prop] ?? "Invalid property" : "No active player"; active.pause();
} }
function list(): string { function playPause(): void {
return root.list.map(p => root.getIdentity(p)).join("\n"); const active = root.active;
} if (active?.canTogglePlaying)
active.togglePlaying();
}
function next(): void { function previous(): void {
const active = root.active; const active = root.active;
if (active?.canGoNext) if (active?.canGoPrevious)
active.next(); active.previous();
} }
function pause(): void { function next(): void {
const active = root.active; const active = root.active;
if (active?.canPause) if (active?.canGoNext)
active.pause(); active.next();
} }
function play(): void { function stop(): void {
const active = root.active; root.active?.stop();
if (active?.canPlay) }
active.play(); }
}
function playPause(): void {
const active = root.active;
if (active?.canTogglePlaying)
active.togglePlaying();
}
function previous(): void {
const active = root.active;
if (active?.canGoPrevious)
active.previous();
}
function stop(): void {
root.active?.stop();
}
target: "mpris"
}
} }
+30 -30
View File
@@ -9,41 +9,41 @@ import qs.Helpers
import qs.Paths import qs.Paths
Searcher { Searcher {
id: root id: root
property string actualCurrent: WallpaperPath.currentWallpaperPath property bool showPreview: false
readonly property string current: showPreview ? previewPath : actualCurrent readonly property string current: showPreview ? previewPath : actualCurrent
property string previewPath property string previewPath
property bool showPreview: false property string actualCurrent: WallpaperPath.currentWallpaperPath
function preview(path: string): void { function setWallpaper(path: string): void {
previewPath = path; actualCurrent = path;
showPreview = true; WallpaperPath.currentWallpaperPath = path;
}
function setWallpaper(path: string): void {
actualCurrent = path;
WallpaperPath.currentWallpaperPath = path;
Quickshell.execDetached(["sh", "-c", `python3 ${Quickshell.shellPath("scripts/LockScreenBg.py")} --input_image=${root.actualCurrent} --output_path=${Paths.state}/lockscreen_bg.png`]); Quickshell.execDetached(["sh", "-c", `python3 ${Quickshell.shellPath("scripts/LockScreenBg.py")} --input_image=${root.actualCurrent} --output_path=${Paths.state}/lockscreen_bg.png`]);
} }
function stopPreview(): void { function preview(path: string): void {
showPreview = false; previewPath = path;
Quickshell.execDetached(["sh", "-c", `python3 ${Quickshell.shellPath("scripts/SchemeColorGen.py")} --path=${root.actualCurrent} --thumbnail=${Paths.cache}/imagecache/thumbnail.jpg --output=${Paths.state}/scheme.json --scheme=${Config.colors.schemeType}`]); showPreview = true;
} }
extraOpts: useFuzzy ? ({}) : ({ function stopPreview(): void {
forward: false showPreview = false;
}) Quickshell.execDetached(["sh", "-c", `python3 ${Quickshell.shellPath("scripts/SchemeColorGen.py")} --path=${root.actualCurrent} --thumbnail=${Paths.cache}/imagecache/thumbnail.jpg --output=${Paths.state}/scheme.json --scheme=${Config.colors.schemeType}`]);
key: "relativePath" }
list: wallpapers.entries
useFuzzy: true
FileSystemModel { list: wallpapers.entries
id: wallpapers key: "relativePath"
useFuzzy: true
extraOpts: useFuzzy ? ({}) : ({
forward: false
})
filter: FileSystemModel.Images FileSystemModel {
path: Config.general.wallpaperPath id: wallpapers
recursive: true
} recursive: true
path: Config.general.wallpaperPath
filter: FileSystemModel.Images
}
} }
+42 -41
View File
@@ -4,51 +4,52 @@ import "../scripts/fuzzysort.js" as Fuzzy
import QtQuick import QtQuick
Singleton { Singleton {
property var extraOpts: ({}) required property list<QtObject> list
readonly property list<var> fuzzyPrepped: useFuzzy ? list.map(e => { property string key: "name"
const obj = { property bool useFuzzy: false
_item: e property var extraOpts: ({})
};
for (const k of keys)
obj[k] = Fuzzy.prepare(e[k]);
return obj;
}) : []
readonly property var fzf: useFuzzy ? [] : new Fzf.Finder(list, Object.assign({
selector
}, extraOpts))
property string key: "name"
// Extra stuff for fuzzy // Extra stuff for fuzzy
property list<string> keys: [key] property list<string> keys: [key]
required property list<QtObject> list property list<real> weights: [1]
property bool useFuzzy: false
property list<real> weights: [1]
function query(search: string): list<var> { readonly property var fzf: useFuzzy ? [] : new Fzf.Finder(list, Object.assign({
search = transformSearch(search); selector
if (!search) }, extraOpts))
return [...list]; readonly property list<var> fuzzyPrepped: useFuzzy ? list.map(e => {
const obj = {
_item: e
};
for (const k of keys)
obj[k] = Fuzzy.prepare(e[k]);
return obj;
}) : []
if (useFuzzy) function transformSearch(search: string): string {
return Fuzzy.go(search, fuzzyPrepped, Object.assign({ return search;
all: true, }
keys,
scoreFn: r => weights.reduce((a, w, i) => a + r[i].score * w, 0)
}, extraOpts)).map(r => r.obj._item);
return fzf.find(search).sort((a, b) => { function selector(item: var): string {
if (a.score === b.score) // Only for fzf
return selector(a.item).trim().length - selector(b.item).trim().length; return item[key];
return b.score - a.score; }
}).map(r => r.item);
}
function selector(item: var): string { function query(search: string): list<var> {
// Only for fzf search = transformSearch(search);
return item[key]; if (!search)
} return [...list];
function transformSearch(search: string): string { if (useFuzzy)
return search; return Fuzzy.go(search, fuzzyPrepped, Object.assign({
} all: true,
keys,
scoreFn: r => weights.reduce((a, w, i) => a + r[i].score * w, 0)
}, extraOpts)).map(r => r.obj._item);
return fzf.find(search).sort((a, b) => {
if (a.score === b.score)
return selector(a.item).trim().length - selector(b.item).trim().length;
return b.score - a.score;
}).map(r => r.item);
}
} }
+61 -60
View File
@@ -6,78 +6,79 @@ import Quickshell.Io
import QtQuick import QtQuick
Singleton { Singleton {
id: root id: root
property bool isDefaultLogo: true property string osName
property string osId property string osPrettyName
property list<string> osIdLike property string osId
property string osLogo property list<string> osIdLike
property string osName property string osLogo
property string osPrettyName property bool isDefaultLogo: true
readonly property string shell: Quickshell.env("SHELL").split("/").pop()
property string uptime
readonly property string user: Quickshell.env("USER")
readonly property string wm: Quickshell.env("XDG_CURRENT_DESKTOP") || Quickshell.env("XDG_SESSION_DESKTOP")
FileView { property string uptime
id: osRelease readonly property string user: Quickshell.env("USER")
readonly property string wm: Quickshell.env("XDG_CURRENT_DESKTOP") || Quickshell.env("XDG_SESSION_DESKTOP")
readonly property string shell: Quickshell.env("SHELL").split("/").pop()
path: "/etc/os-release" FileView {
id: osRelease
onLoaded: { path: "/etc/os-release"
const lines = text().split("\n"); onLoaded: {
const lines = text().split("\n");
const fd = key => lines.find(l => l.startsWith(`${key}=`))?.split("=")[1].replace(/"/g, "") ?? ""; const fd = key => lines.find(l => l.startsWith(`${key}=`))?.split("=")[1].replace(/"/g, "") ?? "";
root.osName = fd("NAME"); root.osName = fd("NAME");
root.osPrettyName = fd("PRETTY_NAME"); root.osPrettyName = fd("PRETTY_NAME");
root.osId = fd("ID"); root.osId = fd("ID");
root.osIdLike = fd("ID_LIKE").split(" "); root.osIdLike = fd("ID_LIKE").split(" ");
const logo = Quickshell.iconPath(fd("LOGO"), true); const logo = Quickshell.iconPath(fd("LOGO"), true);
if (Config.general.logo) { if (Config.general.logo) {
root.osLogo = Quickshell.iconPath(Config.general.logo, true) || "file://" + Paths.absolutePath(Config.general.logo); root.osLogo = Quickshell.iconPath(Config.general.logo, true) || "file://" + Paths.absolutePath(Config.general.logo);
root.isDefaultLogo = false; root.isDefaultLogo = false;
} else if (logo) { } else if (logo) {
root.osLogo = logo; root.osLogo = logo;
root.isDefaultLogo = false; root.isDefaultLogo = false;
} }
} }
} }
Connections { Connections {
function onLogoChanged(): void { target: Config.general
osRelease.reload();
}
target: Config.general function onLogoChanged(): void {
} osRelease.reload();
}
}
Timer { Timer {
interval: 15000 running: true
repeat: true repeat: true
running: true interval: 15000
onTriggered: fileUptime.reload()
}
onTriggered: fileUptime.reload() FileView {
} id: fileUptime
FileView { path: "/proc/uptime"
id: fileUptime onLoaded: {
const up = parseInt(text().split(" ")[0] ?? 0);
path: "/proc/uptime" const days = Math.floor(up / 86400);
const hours = Math.floor((up % 86400) / 3600);
const minutes = Math.floor((up % 3600) / 60);
onLoaded: { let str = "";
const up = parseInt(text().split(" ")[0] ?? 0); if (days > 0)
str += `${days} day${days === 1 ? "" : "s"}`;
const hours = Math.floor(up / 3600); if (hours > 0)
const minutes = Math.floor((up % 3600) / 60); str += `${str ? ", " : ""}${hours} hour${hours === 1 ? "" : "s"}`;
if (minutes > 0 || !str)
let str = ""; str += `${str ? ", " : ""}${minutes} minute${minutes === 1 ? "" : "s"}`;
if (hours > 0) root.uptime = str;
str += `${hours} hour${hours === 1 ? "" : "s"}`; }
if (minutes > 0 || !str) }
str += `${str ? ", " : ""}${minutes} minute${minutes === 1 ? "" : "s"}`;
root.uptime = str;
}
}
} }
+193 -305
View File
@@ -6,344 +6,232 @@ import QtQuick
import qs.Config import qs.Config
Singleton { Singleton {
id: root id: root
property string autoGpuType: "NONE" property real cpuPerc
property string cpuName: "" property real cpuTemp
property real cpuPerc readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType
property real cpuTemp property string autoGpuType: "NONE"
property real gpuPerc
// Individual disks: Array of { mount, used, total, free, perc } property real gpuTemp
property var disks: []
property real gpuMemTotal: 0
property real gpuMemUsed property real gpuMemUsed
property string gpuName: "" property real gpuMemTotal: 0
property real gpuPerc property real memUsed
property real gpuTemp property real memTotal
readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType readonly property real memPerc: memTotal > 0 ? memUsed / memTotal : 0
property real lastCpuIdle property real storageUsed
property real lastCpuTotal property real storageTotal
readonly property real memPerc: memTotal > 0 ? memUsed / memTotal : 0 property real storagePerc: storageTotal > 0 ? storageUsed / storageTotal : 0
property real memTotal
property real memUsed
property int refCount
readonly property real storagePerc: {
let totalUsed = 0;
let totalSize = 0;
for (const disk of disks) {
totalUsed += disk.used;
totalSize += disk.total;
}
return totalSize > 0 ? totalUsed / totalSize : 0;
}
function cleanCpuName(name: string): string { property real lastCpuIdle
return name.replace(/\(R\)/gi, "").replace(/\(TM\)/gi, "").replace(/CPU/gi, "").replace(/\d+th Gen /gi, "").replace(/\d+nd Gen /gi, "").replace(/\d+rd Gen /gi, "").replace(/\d+st Gen /gi, "").replace(/Core /gi, "").replace(/Processor/gi, "").replace(/\s+/g, " ").trim(); property real lastCpuTotal
}
function cleanGpuName(name: string): string { property int refCount
return name.replace(/NVIDIA GeForce /gi, "").replace(/NVIDIA /gi, "").replace(/AMD Radeon /gi, "").replace(/AMD /gi, "").replace(/Intel /gi, "").replace(/\(R\)/gi, "").replace(/\(TM\)/gi, "").replace(/Graphics/gi, "").replace(/\s+/g, " ").trim();
}
function formatKib(kib: real): var { function formatKib(kib: real): var {
const mib = 1024; const mib = 1024;
const gib = 1024 ** 2; const gib = 1024 ** 2;
const tib = 1024 ** 3; const tib = 1024 ** 3;
if (kib >= tib) if (kib >= tib)
return { return {
value: kib / tib, value: kib / tib,
unit: "TiB" unit: "TiB"
}; };
if (kib >= gib) if (kib >= gib)
return { return {
value: kib / gib, value: kib / gib,
unit: "GiB" unit: "GiB"
}; };
if (kib >= mib) if (kib >= mib)
return { return {
value: kib / mib, value: kib / mib,
unit: "MiB" unit: "MiB"
}; };
return { return {
value: kib, value: kib,
unit: "KiB" unit: "KiB"
}; };
} }
Timer { Timer {
interval: Config.dashboard.resourceUpdateInterval running: root.refCount > 0
repeat: true interval: 3000
running: root.refCount > 0 repeat: true
triggeredOnStart: true triggeredOnStart: true
onTriggered: {
stat.reload();
meminfo.reload();
storage.running = true;
gpuUsage.running = true;
sensors.running = true;
}
}
onTriggered: { FileView {
stat.reload(); id: stat
meminfo.reload();
storage.running = true;
gpuUsage.running = true;
sensors.running = true;
}
}
FileView { path: "/proc/stat"
id: cpuinfoInit onLoaded: {
const data = text().match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/);
if (data) {
const stats = data.slice(1).map(n => parseInt(n, 10));
const total = stats.reduce((a, b) => a + b, 0);
const idle = stats[3] + (stats[4] ?? 0);
path: "/proc/cpuinfo" const totalDiff = total - root.lastCpuTotal;
const idleDiff = idle - root.lastCpuIdle;
root.cpuPerc = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0;
onLoaded: { root.lastCpuTotal = total;
const nameMatch = text().match(/model name\s*:\s*(.+)/); root.lastCpuIdle = idle;
if (nameMatch) }
root.cpuName = root.cleanCpuName(nameMatch[1]); }
} }
}
FileView { FileView {
id: stat id: meminfo
path: "/proc/stat" path: "/proc/meminfo"
onLoaded: {
const data = text();
root.memTotal = parseInt(data.match(/MemTotal: *(\d+)/)[1], 10) || 1;
root.memUsed = (root.memTotal - parseInt(data.match(/MemAvailable: *(\d+)/)[1], 10)) || 0;
}
}
onLoaded: { Process {
const data = text().match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/); id: storage
if (data) {
const stats = data.slice(1).map(n => parseInt(n, 10));
const total = stats.reduce((a, b) => a + b, 0);
const idle = stats[3] + (stats[4] ?? 0);
const totalDiff = total - root.lastCpuTotal; command: ["sh", "-c", "df | grep '^/dev/' | awk '{print $1, $3, $4}'"]
const idleDiff = idle - root.lastCpuIdle; stdout: StdioCollector {
root.cpuPerc = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0; onStreamFinished: {
const deviceMap = new Map();
root.lastCpuTotal = total; for (const line of text.trim().split("\n")) {
root.lastCpuIdle = idle; if (line.trim() === "")
} continue;
}
}
FileView { const parts = line.trim().split(/\s+/);
id: meminfo if (parts.length >= 3) {
const device = parts[0];
const used = parseInt(parts[1], 10) || 0;
const avail = parseInt(parts[2], 10) || 0;
path: "/proc/meminfo" // Only keep the entry with the largest total space for each device
if (!deviceMap.has(device) || (used + avail) > (deviceMap.get(device).used + deviceMap.get(device).avail)) {
deviceMap.set(device, {
used: used,
avail: avail
});
}
}
}
onLoaded: { let totalUsed = 0;
const data = text(); let totalAvail = 0;
root.memTotal = parseInt(data.match(/MemTotal: *(\d+)/)[1], 10) || 1;
root.memUsed = (root.memTotal - parseInt(data.match(/MemAvailable: *(\d+)/)[1], 10)) || 0;
}
}
Process { for (const [device, stats] of deviceMap) {
id: storage totalUsed += stats.used;
totalAvail += stats.avail;
}
command: ["lsblk", "-b", "-o", "NAME,SIZE,TYPE,FSUSED,FSSIZE", "-P"] root.storageUsed = totalUsed;
root.storageTotal = totalUsed + totalAvail;
}
}
}
stdout: StdioCollector { Process {
onStreamFinished: { id: gpuTypeCheck
const diskMap = {}; // Map disk name -> { name, totalSize, used, fsTotal }
const lines = text.trim().split("\n");
for (const line of lines) { running: !Config.services.gpuType
if (line.trim() === "") command: ["sh", "-c", "if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; then echo NVIDIA; elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC; else echo NONE; fi"]
continue; stdout: StdioCollector {
const nameMatch = line.match(/NAME="([^"]+)"/); onStreamFinished: root.autoGpuType = text.trim()
const sizeMatch = line.match(/SIZE="([^"]+)"/); }
const typeMatch = line.match(/TYPE="([^"]+)"/); }
const fsusedMatch = line.match(/FSUSED="([^"]*)"/);
const fssizeMatch = line.match(/FSSIZE="([^"]*)"/);
if (!nameMatch || !typeMatch) Process {
continue; id: oneshotMem
command: ["nvidia-smi", "--query-gpu=memory.total", "--format=csv,noheader,nounits"]
running: root.gpuType === "NVIDIA" && root.gpuMemTotal === 0
stdout: StdioCollector {
onStreamFinished: {
root.gpuMemTotal = Number(this.text.trim())
oneshotMem.running = false
}
}
}
const name = nameMatch[1]; Process {
const type = typeMatch[1]; id: gpuUsage
const size = parseInt(sizeMatch?.[1] || "0", 10);
const fsused = parseInt(fsusedMatch?.[1] || "0", 10);
const fssize = parseInt(fssizeMatch?.[1] || "0", 10);
if (type === "disk") { command: root.gpuType === "GENERIC" ? ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"] : root.gpuType === "NVIDIA" ? ["nvidia-smi", "--query-gpu=utilization.gpu,temperature.gpu,memory.used", "--format=csv,noheader,nounits"] : ["echo"]
// Skip zram (swap) devices stdout: StdioCollector {
if (name.startsWith("zram")) onStreamFinished: {
continue; if (root.gpuType === "GENERIC") {
const percs = text.trim().split("\n");
// Initialize disk entry const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
if (!diskMap[name]) { root.gpuPerc = sum / percs.length / 100;
diskMap[name] = { } else if (root.gpuType === "NVIDIA") {
name: name, const [usage, temp, mem] = text.trim().split(",");
totalSize: size, root.gpuPerc = parseInt(usage, 10) / 100;
used: 0, root.gpuTemp = parseInt(temp, 10);
fsTotal: 0
};
}
} else if (type === "part") {
// Find parent disk (remove trailing numbers/p+numbers)
let parentDisk = name.replace(/p?\d+$/, "");
// For nvme devices like nvme0n1p1, parent is nvme0n1
if (name.match(/nvme\d+n\d+p\d+/))
parentDisk = name.replace(/p\d+$/, "");
// Aggregate partition usage to parent disk
if (diskMap[parentDisk]) {
diskMap[parentDisk].used += fsused;
diskMap[parentDisk].fsTotal += fssize;
}
}
}
const diskList = [];
let totalUsed = 0;
let totalSize = 0;
for (const diskName of Object.keys(diskMap).sort()) {
const disk = diskMap[diskName];
// Use filesystem total if available, otherwise use disk size
const total = disk.fsTotal > 0 ? disk.fsTotal : disk.totalSize;
const used = disk.used;
const perc = total > 0 ? used / total : 0;
// Convert bytes to KiB for consistency with formatKib
diskList.push({
mount: disk.name // Using 'mount' property for compatibility
,
used: used / 1024,
total: total / 1024,
free: (total - used) / 1024,
perc: perc
});
totalUsed += used;
totalSize += total;
}
root.disks = diskList;
}
}
}
Process {
id: gpuNameDetect
command: ["sh", "-c", "nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null || lspci 2>/dev/null | grep -i 'vga\\|3d\\|display' | head -1"]
running: true
stdout: StdioCollector {
onStreamFinished: {
const output = text.trim();
if (!output)
return;
// Check if it's from nvidia-smi (clean GPU name)
if (output.toLowerCase().includes("nvidia") || output.toLowerCase().includes("geforce") || output.toLowerCase().includes("rtx") || output.toLowerCase().includes("gtx")) {
root.gpuName = root.cleanGpuName(output);
} else {
// Parse lspci output: extract name from brackets or after colon
const bracketMatch = output.match(/\[([^\]]+)\]/);
if (bracketMatch) {
root.gpuName = root.cleanGpuName(bracketMatch[1]);
} else {
const colonMatch = output.match(/:\s*(.+)/);
if (colonMatch)
root.gpuName = root.cleanGpuName(colonMatch[1]);
}
}
}
}
}
Process {
id: gpuTypeCheck
command: ["sh", "-c", "if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; then echo NVIDIA; elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC; else echo NONE; fi"]
running: !Config.services.gpuType
stdout: StdioCollector {
onStreamFinished: root.autoGpuType = text.trim()
}
}
Process {
id: oneshotMem
command: ["nvidia-smi", "--query-gpu=memory.total", "--format=csv,noheader,nounits"]
running: root.gpuType === "NVIDIA" && root.gpuMemTotal === 0
stdout: StdioCollector {
onStreamFinished: {
root.gpuMemTotal = Number(this.text.trim());
oneshotMem.running = false;
}
}
}
Process {
id: gpuUsage
command: root.gpuType === "GENERIC" ? ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"] : root.gpuType === "NVIDIA" ? ["nvidia-smi", "--query-gpu=utilization.gpu,temperature.gpu,memory.used", "--format=csv,noheader,nounits"] : ["echo"]
stdout: StdioCollector {
onStreamFinished: {
if (root.gpuType === "GENERIC") {
const percs = text.trim().split("\n");
const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
root.gpuPerc = sum / percs.length / 100;
} else if (root.gpuType === "NVIDIA") {
const [usage, temp, mem] = text.trim().split(",");
root.gpuPerc = parseInt(usage, 10) / 100;
root.gpuTemp = parseInt(temp, 10);
root.gpuMemUsed = parseInt(mem, 10) / root.gpuMemTotal; root.gpuMemUsed = parseInt(mem, 10) / root.gpuMemTotal;
} else { } else {
root.gpuPerc = 0; root.gpuPerc = 0;
root.gpuTemp = 0; root.gpuTemp = 0;
} }
} }
} }
} }
Process { Process {
id: sensors id: sensors
command: ["sensors"] command: ["sensors"]
environment: ({ environment: ({
LANG: "C.UTF-8", LANG: "C.UTF-8",
LC_ALL: "C.UTF-8" LC_ALL: "C.UTF-8"
}) })
stdout: StdioCollector {
onStreamFinished: {
let cpuTemp = text.match(/(?:Package id [0-9]+|Tdie):\s+((\+|-)[0-9.]+)(°| )C/);
if (!cpuTemp)
// If AMD Tdie pattern failed, try fallback on Tctl
cpuTemp = text.match(/Tctl:\s+((\+|-)[0-9.]+)(°| )C/);
stdout: StdioCollector { if (cpuTemp)
onStreamFinished: { root.cpuTemp = parseFloat(cpuTemp[1]);
let cpuTemp = text.match(/(?:Package id [0-9]+|Tdie):\s+((\+|-)[0-9.]+)(°| )C/);
if (!cpuTemp)
// If AMD Tdie pattern failed, try fallback on Tctl
cpuTemp = text.match(/Tctl:\s+((\+|-)[0-9.]+)(°| )C/);
if (cpuTemp) if (root.gpuType !== "GENERIC")
root.cpuTemp = parseFloat(cpuTemp[1]); return;
if (root.gpuType !== "GENERIC") let eligible = false;
return; let sum = 0;
let count = 0;
let eligible = false; for (const line of text.trim().split("\n")) {
let sum = 0; if (line === "Adapter: PCI adapter")
let count = 0; eligible = true;
else if (line === "")
eligible = false;
else if (eligible) {
let match = line.match(/^(temp[0-9]+|GPU core|edge)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
if (!match)
// Fall back to junction/mem if GPU doesn't have edge temp (for AMD GPUs)
match = line.match(/^(junction|mem)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
for (const line of text.trim().split("\n")) { if (match) {
if (line === "Adapter: PCI adapter") sum += parseFloat(match[2]);
eligible = true; count++;
else if (line === "") }
eligible = false; }
else if (eligible) { }
let match = line.match(/^(temp[0-9]+|GPU core|edge)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
if (!match)
// Fall back to junction/mem if GPU doesn't have edge temp (for AMD GPUs)
match = line.match(/^(junction|mem)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
if (match) { root.gpuTemp = count > 0 ? sum / count : 0;
sum += parseFloat(match[2]); }
count++; }
} }
}
}
root.gpuTemp = count > 0 ? sum / count : 0;
}
}
}
} }
+2 -2
View File
@@ -1,6 +1,6 @@
import QtQuick import QtQuick
Text { Text {
renderType: Text.NativeRendering renderType: Text.NativeRendering
textFormat: Text.PlainText textFormat: Text.PlainText
} }
+187 -189
View File
@@ -6,30 +6,6 @@ import Quickshell
Singleton { Singleton {
id: root id: root
property list<DesktopEntry> entryList: []
property var preppedIcons: []
property var preppedIds: []
property var preppedNames: []
// Dynamic fixups
property var regexSubstitutions: [
{
"regex": /^steam_app_(\d+)$/,
"replace": "steam_icon_$1"
},
{
"regex": /Minecraft.*/,
"replace": "minecraft-launcher"
},
{
"regex": /.*polkit.*/,
"replace": "system-lock-screen"
},
{
"regex": /gcr.prompter/,
"replace": "system-lock-screen"
}
]
property real scoreThreshold: 0.2 property real scoreThreshold: 0.2
// Manual overrides for tricky apps // Manual overrides for tricky apps
@@ -41,23 +17,178 @@ Singleton {
"wps": "wps-office2019-kprometheus", "wps": "wps-office2019-kprometheus",
"wpsoffice": "wps-office2019-kprometheus", "wpsoffice": "wps-office2019-kprometheus",
"footclient": "foot" "footclient": "foot"
}) })
function checkCleanMatch(str) { // Dynamic fixups
if (!str || str.length <= 3) property var regexSubstitutions: [
{
"regex": /^steam_app_(\d+)$/,
"replace": "steam_icon_$1"
},
{
"regex": /Minecraft.*/,
"replace": "minecraft-launcher"
},
{
"regex": /.*polkit.*/,
"replace": "system-lock-screen"
},
{
"regex": /gcr.prompter/,
"replace": "system-lock-screen"
}
]
property list<DesktopEntry> entryList: []
property var preppedNames: []
property var preppedIcons: []
property var preppedIds: []
Component.onCompleted: refreshEntries()
Connections {
target: DesktopEntries.applications
function onValuesChanged() {
refreshEntries();
}
}
function refreshEntries() {
if (typeof DesktopEntries === 'undefined')
return;
const values = Array.from(DesktopEntries.applications.values);
if (values) {
entryList = values.sort((a, b) => a.name.localeCompare(b.name));
updatePreppedData();
}
}
function updatePreppedData() {
if (typeof FuzzySort === 'undefined')
return;
const list = Array.from(entryList);
preppedNames = list.map(a => ({
name: FuzzySort.prepare(`${a.name} `), entry: a}));
preppedIcons = list.map(a => ({
name: FuzzySort.prepare(`${a.icon} `),
entry: a
}));
preppedIds = list.map(a => ({
name: FuzzySort.prepare(`${a.id} `),
entry: a
}));
}
function iconForAppId(appId, fallbackName) {
const fallback = fallbackName || "application-x-executable";
if (!appId)
return iconFromName(fallback, fallback);
const entry = findAppEntry(appId);
if (entry) {
return iconFromName(entry.icon, fallback);
}
return iconFromName(appId, fallback);
}
// Robust lookup strategy
function findAppEntry(str) {
if (!str || str.length === 0)
return null; return null;
let result = null;
if (result = checkHeuristic(str))
return result;
if (result = checkSubstitutions(str))
return result;
if (result = checkRegex(str))
return result;
if (result = checkSimpleTransforms(str))
return result;
if (result = checkFuzzySearch(str))
return result;
if (result = checkCleanMatch(str))
return result;
return null;
}
function iconFromName(iconName, fallbackName) {
const fallback = fallbackName || "application-x-executable";
try {
if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
const p = Quickshell.iconPath(iconName, fallback);
if (p && p !== "")
return p;
}
} catch (e) {}
try {
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : "";
} catch (e2) {
return "";
}
}
function distroLogoPath() {
try {
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : "";
} catch (e) {
return "";
}
}
// --- Lookup Helpers ---
function checkHeuristic(str) {
if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) {
const entry = DesktopEntries.heuristicLookup(str);
if (entry)
return entry;
}
return null;
}
function checkSubstitutions(str) {
let effectiveStr = substitutions[str];
if (!effectiveStr)
effectiveStr = substitutions[str.toLowerCase()];
if (effectiveStr && effectiveStr !== str) {
return findAppEntry(effectiveStr);
}
return null;
}
function checkRegex(str) {
for (let i = 0; i < regexSubstitutions.length; i++) {
const sub = regexSubstitutions[i];
const replaced = str.replace(sub.regex, sub.replace);
if (replaced !== str) {
return findAppEntry(replaced);
}
}
return null;
}
function checkSimpleTransforms(str) {
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId) if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
return null; return null;
// Aggressive fallback: strip all separators const lower = str.toLowerCase();
const cleanStr = str.toLowerCase().replace(/[\.\-_]/g, '');
const list = Array.from(entryList);
for (let i = 0; i < list.length; i++) { const variants = [str, lower, getFromReverseDomain(str), getFromReverseDomain(str)?.toLowerCase(), normalizeWithHyphens(str), str.replace(/_/g, '-').toLowerCase(), str.replace(/-/g, '_').toLowerCase()];
const entry = list[i];
const cleanId = (entry.id || "").toLowerCase().replace(/[\.\-_]/g, ''); for (let i = 0; i < variants.length; i++) {
if (cleanId.includes(cleanStr) || cleanStr.includes(cleanId)) { const variant = variants[i];
return entry; if (variant) {
const entry = DesktopEntries.byId(variant);
if (entry)
return entry;
} }
} }
return null; return null;
@@ -96,108 +227,33 @@ Singleton {
return null; return null;
} }
// --- Lookup Helpers --- function checkCleanMatch(str) {
if (!str || str.length <= 3)
function checkHeuristic(str) { return null;
if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) {
const entry = DesktopEntries.heuristicLookup(str);
if (entry)
return entry;
}
return null;
}
function checkRegex(str) {
for (let i = 0; i < regexSubstitutions.length; i++) {
const sub = regexSubstitutions[i];
const replaced = str.replace(sub.regex, sub.replace);
if (replaced !== str) {
return findAppEntry(replaced);
}
}
return null;
}
function checkSimpleTransforms(str) {
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId) if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
return null; return null;
const lower = str.toLowerCase(); // Aggressive fallback: strip all separators
const cleanStr = str.toLowerCase().replace(/[\.\-_]/g, '');
const list = Array.from(entryList);
const variants = [str, lower, getFromReverseDomain(str), getFromReverseDomain(str)?.toLowerCase(), normalizeWithHyphens(str), str.replace(/_/g, '-').toLowerCase(), str.replace(/-/g, '_').toLowerCase()]; for (let i = 0; i < list.length; i++) {
const entry = list[i];
for (let i = 0; i < variants.length; i++) { const cleanId = (entry.id || "").toLowerCase().replace(/[\.\-_]/g, '');
const variant = variants[i]; if (cleanId.includes(cleanStr) || cleanStr.includes(cleanId)) {
if (variant) { return entry;
const entry = DesktopEntries.byId(variant);
if (entry)
return entry;
} }
} }
return null; return null;
} }
function checkSubstitutions(str) {
let effectiveStr = substitutions[str];
if (!effectiveStr)
effectiveStr = substitutions[str.toLowerCase()];
if (effectiveStr && effectiveStr !== str) {
return findAppEntry(effectiveStr);
}
return null;
}
function distroLogoPath() {
try {
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : "";
} catch (e) {
return "";
}
}
// Robust lookup strategy
function findAppEntry(str) {
if (!str || str.length === 0)
return null;
let result = null;
if (result = checkHeuristic(str))
return result;
if (result = checkSubstitutions(str))
return result;
if (result = checkRegex(str))
return result;
if (result = checkSimpleTransforms(str))
return result;
if (result = checkFuzzySearch(str))
return result;
if (result = checkCleanMatch(str))
return result;
return null;
}
function fuzzyQuery(search, preppedData) { function fuzzyQuery(search, preppedData) {
if (!search || !preppedData || preppedData.length === 0) if (!search || !preppedData || preppedData.length === 0)
return []; return [];
return FuzzySort.go(search, preppedData, { return FuzzySort.go(search, preppedData, {
all: true, all: true,
key: "name" key: "name"
}).map(r => r.obj.entry); }).map(r => r.obj.entry);
}
function getFromReverseDomain(str) {
if (!str)
return "";
return str.split('.').slice(-1)[0];
}
// Deprecated shim
function guessIcon(str) {
const entry = findAppEntry(str);
return entry ? entry.icon : "image-missing";
} }
function iconExists(iconName) { function iconExists(iconName) {
@@ -210,34 +266,10 @@ Singleton {
return path && path.length > 0 && !path.includes("image-missing"); return path && path.length > 0 && !path.includes("image-missing");
} }
function iconForAppId(appId, fallbackName) { function getFromReverseDomain(str) {
const fallback = fallbackName || "application-x-executable"; if (!str)
if (!appId)
return iconFromName(fallback, fallback);
const entry = findAppEntry(appId);
if (entry) {
return iconFromName(entry.icon, fallback);
}
return iconFromName(appId, fallback);
}
function iconFromName(iconName, fallbackName) {
const fallback = fallbackName || "application-x-executable";
try {
if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
const p = Quickshell.iconPath(iconName, fallback);
if (p && p !== "")
return p;
}
} catch (e) {}
try {
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : "";
} catch (e2) {
return ""; return "";
} return str.split('.').slice(-1)[0];
} }
function normalizeWithHyphens(str) { function normalizeWithHyphens(str) {
@@ -246,43 +278,9 @@ Singleton {
return str.toLowerCase().replace(/\s+/g, "-"); return str.toLowerCase().replace(/\s+/g, "-");
} }
function refreshEntries() { // Deprecated shim
if (typeof DesktopEntries === 'undefined') function guessIcon(str) {
return; const entry = findAppEntry(str);
return entry ? entry.icon : "image-missing";
const values = Array.from(DesktopEntries.applications.values);
if (values) {
entryList = values.sort((a, b) => a.name.localeCompare(b.name));
updatePreppedData();
}
}
function updatePreppedData() {
if (typeof FuzzySort === 'undefined')
return;
const list = Array.from(entryList);
preppedNames = list.map(a => ({
name: FuzzySort.prepare(`${a.name} `),
entry: a
}));
preppedIcons = list.map(a => ({
name: FuzzySort.prepare(`${a.icon} `),
entry: a
}));
preppedIds = list.map(a => ({
name: FuzzySort.prepare(`${a.id} `),
entry: a
}));
}
Component.onCompleted: refreshEntries()
Connections {
function onValuesChanged() {
refreshEntries();
}
target: DesktopEntries.applications
} }
} }
+17 -17
View File
@@ -3,24 +3,24 @@ pragma Singleton
import Quickshell import Quickshell
Singleton { Singleton {
readonly property string amPmStr: timeComponents[2] ?? "" property alias enabled: clock.enabled
readonly property date date: clock.date readonly property date date: clock.date
property alias enabled: clock.enabled readonly property int hours: clock.hours
readonly property string hourStr: timeComponents[0] ?? "" readonly property int minutes: clock.minutes
readonly property int hours: clock.hours readonly property int seconds: clock.seconds
readonly property string minuteStr: timeComponents[1] ?? ""
readonly property int minutes: clock.minutes
readonly property int seconds: clock.seconds
readonly property list<string> timeComponents: timeStr.split(":")
readonly property string timeStr: format("hh:mm")
function format(fmt: string): string { readonly property string timeStr: format("hh:mm")
return Qt.formatDateTime(clock.date, fmt); readonly property list<string> timeComponents: timeStr.split(":")
} readonly property string hourStr: timeComponents[0] ?? ""
readonly property string minuteStr: timeComponents[1] ?? ""
readonly property string amPmStr: timeComponents[2] ?? ""
SystemClock { function format(fmt: string): string {
id: clock return Qt.formatDateTime(clock.date, fmt);
}
precision: SystemClock.Seconds SystemClock {
} id: clock
precision: SystemClock.Seconds
}
} }
+2 -1
View File
@@ -3,10 +3,11 @@ pragma Singleton
import Quickshell import Quickshell
import Quickshell.Services.UPower import Quickshell.Services.UPower
Singleton { Singleton {
id: root id: root
readonly property list<UPowerDevice> devices: UPower.devices.values readonly property list<UPowerDevice> devices: UPower.devices.values
readonly property UPowerDevice displayDevice: UPower.displayDevice
readonly property bool onBattery: UPower.onBattery readonly property bool onBattery: UPower.onBattery
readonly property UPowerDevice displayDevice: UPower.displayDevice
} }
+8 -8
View File
@@ -3,14 +3,14 @@ pragma Singleton
import Quickshell import Quickshell
Singleton { Singleton {
property var bars: new Map() property var screens: new Map()
property var screens: new Map() property var bars: new Map()
function getForActive(): PersistentProperties { function load(screen: ShellScreen, visibilities: var): void {
return screens.get(Hypr.focusedMonitor); screens.set(Hypr.monitorFor(screen), visibilities);
} }
function load(screen: ShellScreen, visibilities: var): void { function getForActive(): PersistentProperties {
screens.set(Hypr.monitorFor(screen), visibilities); return screens.get(Hypr.focusedMonitor);
} }
} }
+13 -16
View File
@@ -5,25 +5,22 @@ import Quickshell.Io
import qs.Paths import qs.Paths
Singleton { Singleton {
id: root id: root
property alias currentWallpaperPath: adapter.currentWallpaperPath property alias currentWallpaperPath: adapter.currentWallpaperPath
property alias lockscreenBg: adapter.lockscreenBg property alias lockscreenBg: adapter.lockscreenBg
FileView { FileView {
id: fileView id: fileView
path: `${Paths.state}/wallpaper_path.json`
path: `${Paths.state}/wallpaper_path.json` watchChanges: true
watchChanges: true onFileChanged: reload()
onAdapterUpdated: writeAdapter()
onAdapterUpdated: writeAdapter() JsonAdapter {
onFileChanged: reload() id: adapter
property string currentWallpaperPath: ""
JsonAdapter {
id: adapter
property string currentWallpaperPath: ""
property string lockscreenBg: `${Paths.state}/lockscreen_bg.png` property string lockscreenBg: `${Paths.state}/lockscreen_bg.png`
} }
} }
} }
+35 -35
View File
@@ -9,54 +9,54 @@ import qs.Helpers
import qs.Paths import qs.Paths
Searcher { Searcher {
id: root id: root
property string actualCurrent: WallpaperPath.currentWallpaperPath property bool showPreview: false
readonly property string current: showPreview ? previewPath : actualCurrent readonly property string current: showPreview ? previewPath : actualCurrent
property string previewPath property string previewPath
property bool showPreview: false property string actualCurrent: WallpaperPath.currentWallpaperPath
function preview(path: string): void { function setWallpaper(path: string): void {
previewPath = path; actualCurrent = path;
if (Config.general.color.schemeGeneration) WallpaperPath.currentWallpaperPath = path;
Quickshell.execDetached(["sh", "-c", `zshell-cli scheme generate --image-path ${previewPath} --thumbnail-path ${Paths.cache}/imagecache/thumbnail.jpg --output ${Paths.state}/scheme.json --scheme ${Config.colors.schemeType} --mode ${Config.general.color.mode}`]); if ( Config.general.color.wallust )
showPreview = true;
}
function setWallpaper(path: string): void {
actualCurrent = path;
WallpaperPath.currentWallpaperPath = path;
if (Config.general.color.wallust)
Wallust.generateColors(WallpaperPath.currentWallpaperPath); Wallust.generateColors(WallpaperPath.currentWallpaperPath);
Quickshell.execDetached(["sh", "-c", `zshell-cli wallpaper lockscreen --input-image=${root.actualCurrent} --output-path=${Paths.state}/lockscreen_bg.png --blur-amount=${Config.lock.blurAmount}`]); Quickshell.execDetached(["sh", "-c", `zshell-cli wallpaper lockscreen --input-image=${root.actualCurrent} --output-path=${Paths.state}/lockscreen_bg.png --blur-amount=${Config.lock.blurAmount}`]);
} }
function stopPreview(): void { function preview(path: string): void {
showPreview = false; previewPath = path;
if (Config.general.color.schemeGeneration) if ( Config.general.color.schemeGeneration )
Quickshell.execDetached(["sh", "-c", `zshell-cli scheme generate --image-path ${previewPath} --thumbnail-path ${Paths.cache}/imagecache/thumbnail.jpg --output ${Paths.state}/scheme.json --scheme ${Config.colors.schemeType} --mode ${Config.general.color.mode}`]);
showPreview = true;
}
function stopPreview(): void {
showPreview = false;
if ( Config.general.color.schemeGeneration )
Quickshell.execDetached(["sh", "-c", `zshell-cli scheme generate --image-path ${root.actualCurrent} --thumbnail-path ${Paths.cache}/imagecache/thumbnail.jpg --output ${Paths.state}/scheme.json --scheme ${Config.colors.schemeType} --mode ${Config.general.color.mode}`]); Quickshell.execDetached(["sh", "-c", `zshell-cli scheme generate --image-path ${root.actualCurrent} --thumbnail-path ${Paths.cache}/imagecache/thumbnail.jpg --output ${Paths.state}/scheme.json --scheme ${Config.colors.schemeType} --mode ${Config.general.color.mode}`]);
} }
extraOpts: useFuzzy ? ({}) : ({ list: wallpapers.entries
forward: false key: "relativePath"
}) useFuzzy: true
key: "relativePath" extraOpts: useFuzzy ? ({}) : ({
list: wallpapers.entries forward: false
useFuzzy: true })
IpcHandler { IpcHandler {
target: "wallpaper"
function set(path: string): void { function set(path: string): void {
root.setWallpaper(path); root.setWallpaper(path);
} }
target: "wallpaper"
} }
FileSystemModel { FileSystemModel {
id: wallpapers id: wallpapers
filter: FileSystemModel.Images recursive: true
path: Config.general.wallpaperPath path: Config.general.wallpaperPath
recursive: true filter: FileSystemModel.Images
} }
} }

Some files were not shown because too many files have changed in this diff Show More