From 2e466abf719d64cbc2d134dfcdcc43d19ec7afbb Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 10 Apr 2025 00:16:42 +0200 Subject: [PATCH 001/824] empty bar --- .config/quickshell/modules/bar/Bar.qml | 54 ++++++++++ .../quickshell/modules/bar/ClockWidget.qml | 6 ++ .../quickshell/modules/common/Appearance.qml | 101 ++++++++++++++++++ .../quickshell/modules/common/DateTime.qml | 19 ++++ .config/quickshell/shell.qml | 11 ++ 5 files changed, 191 insertions(+) create mode 100644 .config/quickshell/modules/bar/Bar.qml create mode 100644 .config/quickshell/modules/bar/ClockWidget.qml create mode 100644 .config/quickshell/modules/common/Appearance.qml create mode 100644 .config/quickshell/modules/common/DateTime.qml create mode 100644 .config/quickshell/shell.qml diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml new file mode 100644 index 000000000..21c97db90 --- /dev/null +++ b/.config/quickshell/modules/bar/Bar.qml @@ -0,0 +1,54 @@ +import "../common" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell + +Scope { + Variants { + model: Quickshell.screens + + PanelWindow { + property var modelData + + screen: modelData + height: 40 + color: Appearance.colors.colLayer0 + + // Left section + RowLayout { + anchors.left: parent.left + } + + // Middle section + RowLayout { + anchors.top: parent.top + anchors.bottom: parent.top + anchors.centerIn: parent + + // Rectangle { + + // } + + // ClockWidget { + // Layout.fillHeight: true + // } + + } + + // Right section + RowLayout { + anchors.right: parent.right + } + + anchors { + top: true + left: true + right: true + } + + } + + } + +} diff --git a/.config/quickshell/modules/bar/ClockWidget.qml b/.config/quickshell/modules/bar/ClockWidget.qml new file mode 100644 index 000000000..2ab37f1bc --- /dev/null +++ b/.config/quickshell/modules/bar/ClockWidget.qml @@ -0,0 +1,6 @@ +import QtQuick +import "../common" + +Text { + text: DateTime.time +} diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml new file mode 100644 index 000000000..dafe130dc --- /dev/null +++ b/.config/quickshell/modules/common/Appearance.qml @@ -0,0 +1,101 @@ +import QtQuick +import Quickshell +pragma Singleton + +Singleton { + property QtObject m3colors + property QtObject colors + + function mix(color1, color2, percentage) { + var c1 = Qt.color(color1); + var c2 = Qt.color(color2); + return Qt.rgba((1 - percentage) * c1.r + percentage * c2.r, (1 - percentage) * c1.g + percentage * c2.g, (1 - percentage) * c1.b + percentage * c2.b, (1 - percentage) * c1.a + percentage * c2.a); + } + + m3colors: QtObject { + property bool darkmode: false + property bool transparent: false + property color m3primary_paletteKeyColor: "#8A7175" + property color m3secondary_paletteKeyColor: "#847376" + property color m3tertiary_paletteKeyColor: "#8F6E74" + property color m3neutral_paletteKeyColor: "#7B7676" + property color m3neutral_variant_paletteKeyColor: "#7B7676" + property color m3background: "#FFF8F7" + property color m3onBackground: "#1E1B1B" + property color m3surface: "#FFF8F7" + property color m3surfaceDim: "#E0D8D8" + property color m3surfaceBright: "#FFF8F7" + property color m3surfaceContainerLowest: "#FFFFFF" + property color m3surfaceContainerLow: "#FAF2F2" + property color m3surfaceContainer: "#F4ECEC" + property color m3surfaceContainerHigh: "#EEE6E6" + property color m3surfaceContainerHighest: "#E8E1E1" + property color m3onSurface: "#1E1B1B" + property color m3surfaceVariant: "#E8E1E1" + property color m3onSurfaceVariant: "#4A4646" + property color m3inverseSurface: "#332F30" + property color m3inverseOnSurface: "#F7EFEF" + property color m3outline: "#797373" + property color m3outlineVariant: "#CCC5C5" + property color m3shadow: "#000000" + property color m3scrim: "#000000" + property color m3surfaceTint: "#70585D" + property color m3primary: "#70585D" + property color m3onPrimary: "#FFFFFF" + property color m3primaryContainer: "#FADBE0" + property color m3onPrimaryContainer: "#564145" + property color m3inversePrimary: "#DDBFC4" + property color m3secondary: "#6A5A5D" + property color m3onSecondary: "#FFFFFF" + property color m3secondaryContainer: "#F3DDE0" + property color m3onSecondaryContainer: "#524346" + property color m3tertiary: "#8D6C72" + property color m3onTertiary: "#FFFFFF" + property color m3tertiaryContainer: "#8D6C72" + property color m3onTertiaryContainer: "#FFFFFF" + property color m3error: "#BA1A1A" + property color m3onError: "#FFFFFF" + property color m3errorContainer: "#FFDAD6" + property color m3onErrorContainer: "#93000A" + property color m3primaryFixed: "#FADBE0" + property color m3primaryFixedDim: "#DDBFC4" + property color m3onPrimaryFixed: "#28171A" + property color m3onPrimaryFixedVariant: "#564145" + property color m3secondaryFixed: "#F3DDE0" + property color m3secondaryFixedDim: "#D6C2C4" + property color m3onSecondaryFixed: "#24191B" + property color m3onSecondaryFixedVariant: "#524346" + property color m3tertiaryFixed: "#FFD9DF" + property color m3tertiaryFixedDim: "#E4BDC3" + property color m3onTertiaryFixed: "#2B151A" + property color m3onTertiaryFixedVariant: "#5B3F45" + property color m3success: "#4F6354" + property color m3onSuccess: "#FFFFFF" + property color m3successContainer: "#D1E8D5" + property color m3onSuccessContainer: "#0C1F13" + property color term0: "#EDE4E4" + property color term1: "#B52755" + property color term2: "#A97363" + property color term3: "#AF535D" + property color term4: "#A67F7C" + property color term5: "#B2416B" + property color term6: "#8D76AD" + property color term7: "#272022" + property color term8: "#0E0D0D" + property color term9: "#B52755" + property color term10: "#A97363" + property color term11: "#AF535D" + property color term12: "#A67F7C" + property color term13: "#B2416B" + property color term14: "#8D76AD" + property color term15: "#221A1A" + } + + colors: QtObject { + property color colLayer0: m3colors.m3background + property color colOnLayer0: m3colors.m3onBackground + property color colLayer0Hover: mix(colLayer0, colOnLayer0, 0.85) + property color colLayer0Active: m3colors.m3surfaceContainerHigh + } + +} diff --git a/.config/quickshell/modules/common/DateTime.qml b/.config/quickshell/modules/common/DateTime.qml new file mode 100644 index 000000000..a217b1ac9 --- /dev/null +++ b/.config/quickshell/modules/common/DateTime.qml @@ -0,0 +1,19 @@ +import QtQuick +import Quickshell +import Quickshell.Io +// with this line our type becomes a singleton +pragma Singleton + +// your singletons should always have Singleton as the type +Singleton { + property string time: Qt.formatDateTime(clock.date, "hh:mm") + // something like Wednesday, 09/04 + property string date: Qt.formatDateTime(clock.date, "dddd, dd/MM") + + SystemClock { + id: clock + + precision: SystemClock.Minutes + } + +} diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml new file mode 100644 index 000000000..aeb4b2ba1 --- /dev/null +++ b/.config/quickshell/shell.qml @@ -0,0 +1,11 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import "./modules/bar" + +ShellRoot { + Bar { + } + +} From 179be02e0d79cb3ede42af319aa9b31990177a65 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 10 Apr 2025 00:19:17 +0200 Subject: [PATCH 002/824] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 39213c596..af4a74e69 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +# QUICKSHELL, WIP, DO NOT USE + +i hope ill actually properly learn quickshell this time +

【 end_4's Hyprland dotfiles 】

From 91cef5700a14c0198fd3ee0cd8ef01cd5f36e5f8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 10 Apr 2025 02:06:15 +0200 Subject: [PATCH 003/824] clock --- .config/quickshell/modules/bar/Bar.qml | 30 +++++++++---- .../quickshell/modules/bar/ClockWidget.qml | 44 +++++++++++++++++-- .../quickshell/modules/bar/UtilButtons.qml | 19 ++++++++ .../quickshell/modules/common/Appearance.qml | 38 +++++++++++++++- .../quickshell/modules/common/DateTime.qml | 1 - 5 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 .config/quickshell/modules/bar/UtilButtons.qml diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 21c97db90..f8d92db9b 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -22,17 +22,31 @@ Scope { // Middle section RowLayout { - anchors.top: parent.top - anchors.bottom: parent.top anchors.centerIn: parent + implicitWidth: 500 - // Rectangle { - - // } + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: true + } - // ClockWidget { - // Layout.fillHeight: true - // } + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: true + } + + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: true + + ClockWidget { + Layout.alignment: Qt.AlignVCenter + } + UtilButtons { + Layout.alignment: Qt.AlignVCenter + } + + } } diff --git a/.config/quickshell/modules/bar/ClockWidget.qml b/.config/quickshell/modules/bar/ClockWidget.qml index 2ab37f1bc..6e8785eff 100644 --- a/.config/quickshell/modules/bar/ClockWidget.qml +++ b/.config/quickshell/modules/bar/ClockWidget.qml @@ -1,6 +1,44 @@ -import QtQuick import "../common" +import QtQuick +import QtQuick.Layouts + +Rectangle { + implicitWidth: 200 + implicitHeight: 32 + color: Appearance.colors.colLayer1 + radius: Appearance.rounding.small + + RowLayout { + spacing: 4 + anchors.centerIn: parent + + Text { + renderType: Text.NativeRendering + verticalAlignment: Text.AlignVCenter + font.family: Appearance.font.family.title + font.pointSize: Appearance.font.pointSize.large + text: DateTime.time + color: Appearance.colors.colOnLayer1 + } + + Text { + renderType: Text.NativeRendering + verticalAlignment: Text.AlignVCenter + font.family: Appearance.font.family.main + font.pointSize: Appearance.font.pointSize.small + text: "•" + color: Appearance.colors.colOnLayer1 + } + + Text { + renderType: Text.NativeRendering + verticalAlignment: Text.AlignVCenter + font.family: Appearance.font.family.main + font.pointSize: Appearance.font.pointSize.small + text: DateTime.date + color: Appearance.colors.colOnLayer1 + } + + } -Text { - text: DateTime.time } diff --git a/.config/quickshell/modules/bar/UtilButtons.qml b/.config/quickshell/modules/bar/UtilButtons.qml new file mode 100644 index 000000000..def2bb629 --- /dev/null +++ b/.config/quickshell/modules/bar/UtilButtons.qml @@ -0,0 +1,19 @@ +import "../common" +import QtQuick +import QtQuick.Layouts + +Rectangle { + implicitWidth: 200 + implicitHeight: 32 + color: Appearance.colors.colLayer1 + radius: Appearance.rounding.small + + RowLayout { + spacing: 4 + anchors.centerIn: parent + + + + } + +} diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index dafe130dc..ce9a01be3 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -5,11 +5,13 @@ pragma Singleton Singleton { property QtObject m3colors property QtObject colors + property QtObject rounding + property QtObject font function mix(color1, color2, percentage) { var c1 = Qt.color(color1); var c2 = Qt.color(color2); - return Qt.rgba((1 - percentage) * c1.r + percentage * c2.r, (1 - percentage) * c1.g + percentage * c2.g, (1 - percentage) * c1.b + percentage * c2.b, (1 - percentage) * c1.a + percentage * c2.a); + return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a); } m3colors: QtObject { @@ -96,6 +98,40 @@ Singleton { property color colOnLayer0: m3colors.m3onBackground property color colLayer0Hover: mix(colLayer0, colOnLayer0, 0.85) property color colLayer0Active: m3colors.m3surfaceContainerHigh + property color colLayer1: m3colors.m3surfaceContainerLow; + property color colOnLayer1: m3colors.m3onSurfaceVariant; + property color colOnLayer1Inactive: mix(colOnLayer1, colLayer1, 0.45); + property color colLayer2: mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.55); + property color colOnLayer2: m3colors.m3onSurface; + property color colLayer3: mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96); + property color colOnLayer3: m3colors.m3onSurface; + } + + rounding: QtObject { + property int unsharpen: 2 + property int verysmall: 8 + property int small: 12 + property int normal: 17 + property int large: 25 + property int full: 9999 + } + + font: QtObject { + property QtObject family: QtObject { + property string main: "Rubik" + property string title: "Gabarito" + property string iconMaterial: "Material Symbols Rounded" + property string iconNerd: "SpaceMono NF" + property string monospace: "JetBrains Mono NF" + property string reading: "Readex Pro" + } + property QtObject pointSize: QtObject { + property int smaller: 10 + property int small: 11 + property int normal: 12 + property int large: 13 + property int larger: 16 + } } } diff --git a/.config/quickshell/modules/common/DateTime.qml b/.config/quickshell/modules/common/DateTime.qml index a217b1ac9..6e29ec968 100644 --- a/.config/quickshell/modules/common/DateTime.qml +++ b/.config/quickshell/modules/common/DateTime.qml @@ -7,7 +7,6 @@ pragma Singleton // your singletons should always have Singleton as the type Singleton { property string time: Qt.formatDateTime(clock.date, "hh:mm") - // something like Wednesday, 09/04 property string date: Qt.formatDateTime(clock.date, "dddd, dd/MM") SystemClock { From 15990bf8d1b3ca8754e183e78065e28fa84a841c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 10 Apr 2025 12:55:26 +0200 Subject: [PATCH 004/824] utilbuttons --- .../quickshell/modules/bar/ClockWidget.qml | 25 ++++------ .../quickshell/modules/bar/UtilButtons.qml | 46 ++++++++++++++++++- .../quickshell/modules/common/Appearance.qml | 6 ++- .../modules/common/widgets/MaterialSymbol.qml | 11 +++++ .../common/widgets/SmallCircleButton.qml | 25 ++++++++++ .../modules/common/widgets/StyledText.qml | 11 +++++ 6 files changed, 106 insertions(+), 18 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/MaterialSymbol.qml create mode 100644 .config/quickshell/modules/common/widgets/SmallCircleButton.qml create mode 100644 .config/quickshell/modules/common/widgets/StyledText.qml diff --git a/.config/quickshell/modules/bar/ClockWidget.qml b/.config/quickshell/modules/bar/ClockWidget.qml index 6e8785eff..d001f1db7 100644 --- a/.config/quickshell/modules/bar/ClockWidget.qml +++ b/.config/quickshell/modules/bar/ClockWidget.qml @@ -1,42 +1,37 @@ import "../common" +import "../common/widgets" import QtQuick import QtQuick.Layouts Rectangle { - implicitWidth: 200 + implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 6 // idk, text seems nicer w/ more padding implicitHeight: 32 color: Appearance.colors.colLayer1 radius: Appearance.rounding.small RowLayout { + id: rowLayout + spacing: 4 anchors.centerIn: parent - Text { - renderType: Text.NativeRendering - verticalAlignment: Text.AlignVCenter + StyledText { font.family: Appearance.font.family.title font.pointSize: Appearance.font.pointSize.large + color: Appearance.colors.colOnLayer1 text: DateTime.time - color: Appearance.colors.colOnLayer1 } - Text { - renderType: Text.NativeRendering - verticalAlignment: Text.AlignVCenter - font.family: Appearance.font.family.main + StyledText { font.pointSize: Appearance.font.pointSize.small + color: Appearance.colors.colOnLayer1 text: "•" - color: Appearance.colors.colOnLayer1 } - Text { - renderType: Text.NativeRendering - verticalAlignment: Text.AlignVCenter - font.family: Appearance.font.family.main + StyledText { font.pointSize: Appearance.font.pointSize.small - text: DateTime.date color: Appearance.colors.colOnLayer1 + text: DateTime.date } } diff --git a/.config/quickshell/modules/bar/UtilButtons.qml b/.config/quickshell/modules/bar/UtilButtons.qml index def2bb629..eb0af110f 100644 --- a/.config/quickshell/modules/bar/UtilButtons.qml +++ b/.config/quickshell/modules/bar/UtilButtons.qml @@ -1,18 +1,60 @@ import "../common" +import "../common/widgets" import QtQuick import QtQuick.Layouts +import Quickshell +import Quickshell.Io Rectangle { - implicitWidth: 200 + Layout.alignment: Qt.AlignVCenter + implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitHeight: 32 color: Appearance.colors.colLayer1 radius: Appearance.rounding.small + Process { + id: screenSnip + + command: ["grimblast", "copy", "area"] + } + + Process { + id: pickColor + + command: ["hyprpicker", "-a"] + } + RowLayout { + id: rowLayout + spacing: 4 anchors.centerIn: parent - + SmallCircleButton { + Layout.alignment: Qt.AlignVCenter + onClicked: screenSnip.running = true + + MaterialSymbol { + anchors.centerIn: parent + text: "screenshot_region" + font.pointSize: Appearance.font.pointSize.normal + color: Appearance.colors.colOnLayer2 + } + + } + + SmallCircleButton { + Layout.alignment: Qt.AlignVCenter + onClicked: pickColor.running = true + + MaterialSymbol { + anchors.centerIn: parent + text: "colorize" + font.pointSize: Appearance.font.pointSize.normal + color: Appearance.colors.colOnLayer2 + } + + } } diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index ce9a01be3..44f79a4a1 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -105,6 +105,10 @@ Singleton { property color colOnLayer2: m3colors.m3onSurface; property color colLayer3: mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96); property color colOnLayer3: m3colors.m3onSurface; + property color colLayer2Hover: mix(colLayer2, colOnLayer2, 0.90); + property color colLayer2Active: mix(colLayer2, colOnLayer2, 0.80); + property color colLayer3Hover: mix(colLayer3, colOnLayer3, 0.90); + property color colLayer3Active: mix(colLayer3, colOnLayer3, 0.80); } rounding: QtObject { @@ -119,7 +123,7 @@ Singleton { font: QtObject { property QtObject family: QtObject { property string main: "Rubik" - property string title: "Gabarito" + property string title: "Rubik" property string iconMaterial: "Material Symbols Rounded" property string iconNerd: "SpaceMono NF" property string monospace: "JetBrains Mono NF" diff --git a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml new file mode 100644 index 000000000..4f0f6a0d4 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml @@ -0,0 +1,11 @@ +import "../" +import QtQuick +import QtQuick.Layouts + +Text { + renderType: Text.NativeRendering + verticalAlignment: Text.AlignVCenter + font.family: Appearance.font.family.iconMaterial + font.pointSize: Appearance.font.pointSize.small + color: Appearance.colors.colOnBackground +} diff --git a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml b/.config/quickshell/modules/common/widgets/SmallCircleButton.qml new file mode 100644 index 000000000..c1df4855f --- /dev/null +++ b/.config/quickshell/modules/common/widgets/SmallCircleButton.qml @@ -0,0 +1,25 @@ +import "../" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland + +Button { + id: button + implicitWidth: 26 + implicitHeight: 26 + + required default property Item content + property bool extraActiveCondition: false + + contentItem: content + + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.full + color: (button.down || extraActiveCondition) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2) + } + +} diff --git a/.config/quickshell/modules/common/widgets/StyledText.qml b/.config/quickshell/modules/common/widgets/StyledText.qml new file mode 100644 index 000000000..190e0b1cf --- /dev/null +++ b/.config/quickshell/modules/common/widgets/StyledText.qml @@ -0,0 +1,11 @@ +import "../" +import QtQuick +import QtQuick.Layouts + +Text { + renderType: Text.NativeRendering + verticalAlignment: Text.AlignVCenter + font.family: Appearance.font.family.main + font.pointSize: Appearance.font.pointSize.small + color: Appearance.colors.colOnBackground +} From 5c88c6a5a6746fa27146215fea4c21384a771351 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 10 Apr 2025 13:09:49 +0200 Subject: [PATCH 005/824] button color anims --- .config/quickshell/modules/common/Appearance.qml | 9 +++++++++ .../modules/common/widgets/SmallCircleButton.qml | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 44f79a4a1..155f80dc3 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -4,6 +4,7 @@ pragma Singleton Singleton { property QtObject m3colors + property QtObject animation property QtObject colors property QtObject rounding property QtObject font @@ -138,4 +139,12 @@ Singleton { } } + animation: QtObject { + property QtObject elementDecel: QtObject { + property int duration: 100 + property int type: Easing.BezierSpline + property list bezierCurve: [0, 0.55, 0.45, 1] + } + } + } diff --git a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml b/.config/quickshell/modules/common/widgets/SmallCircleButton.qml index c1df4855f..545887bfa 100644 --- a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml +++ b/.config/quickshell/modules/common/widgets/SmallCircleButton.qml @@ -8,18 +8,28 @@ import Quickshell.Wayland Button { id: button - implicitWidth: 26 - implicitHeight: 26 required default property Item content property bool extraActiveCondition: false + implicitWidth: 26 + implicitHeight: 26 contentItem: content background: Rectangle { anchors.fill: parent radius: Appearance.rounding.full color: (button.down || extraActiveCondition) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve + } + + } + } } From ff93725b28f57b9c9df20e5decb2a4f685789dad Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 10 Apr 2025 17:03:57 +0200 Subject: [PATCH 006/824] the perfect workspace indicator --- .config/quickshell/modules/bar/Bar.qml | 11 + .config/quickshell/modules/bar/Workspaces.qml | 198 ++++++++++++++++++ .../quickshell/modules/common/Appearance.qml | 4 + .../modules/common/ConfigOptions.qml | 10 + .../common/widgets/SmallCircleButton.qml | 1 - 5 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 .config/quickshell/modules/bar/Workspaces.qml create mode 100644 .config/quickshell/modules/common/ConfigOptions.qml diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index f8d92db9b..acbc8a0b3 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -9,6 +9,7 @@ Scope { model: Quickshell.screens PanelWindow { + id: barRoot property var modelData screen: modelData @@ -24,8 +25,10 @@ Scope { RowLayout { anchors.centerIn: parent implicitWidth: 500 + spacing: 8 // TODO: Why is this halved when rendered?? RowLayout { + spacing: 4 Layout.fillWidth: true Layout.fillHeight: true } @@ -33,15 +36,23 @@ Scope { RowLayout { Layout.fillWidth: true Layout.fillHeight: true + spacing: 4 + + Workspaces { + bar: barRoot + } + } RowLayout { Layout.fillWidth: true Layout.fillHeight: true + spacing: 4 ClockWidget { Layout.alignment: Qt.AlignVCenter } + UtilButtons { Layout.alignment: Qt.AlignVCenter } diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml new file mode 100644 index 000000000..1f7c0dab3 --- /dev/null +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -0,0 +1,198 @@ +import "../common" +import "../common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland +import Quickshell.Io + +Rectangle { + required property var bar + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) + readonly property list workspaceOccupied: [] + readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / ConfigOptions.bar.workspacesShown) + property int widgetPadding: 4 + property int workspaceButtonWidth: 26 + property int activeWorkspaceMargin: 1 + property double animatedActiveWorkspaceIndex: (monitor.activeWorkspace?.id - 1) % ConfigOptions.bar.workspacesShown + + Behavior on animatedActiveWorkspaceIndex { + NumberAnimation { + duration: Appearance.animation.menuDecel.duration + easing.type: Appearance.animation.menuDecel.type + } + + } + + // Function to update workspaceOccupied + function updateWorkspaceOccupied() { + workspaceOccupied = Array.from({ length: ConfigOptions.bar.workspacesShown }, (_, i) => { + return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * ConfigOptions.bar.workspacesShown + i + 1); + }); + } + + // Initialize workspaceOccupied when the component is created + Component.onCompleted: updateWorkspaceOccupied() + + // Listen for changes in Hyprland.workspaces.values + Connections { + target: Hyprland.workspaces + function onValuesChanged() { + updateWorkspaceOccupied(); + } + } + + + Layout.fillHeight: true + implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 + implicitHeight: 40 + color: "transparent" + + // Background + Rectangle { + z: 0 + anchors.centerIn: parent + implicitHeight: 32 + implicitWidth: rowLayout.implicitWidth + widgetPadding * 2 + radius: Appearance.rounding.small + color: Appearance.colors.colLayer1 + } + + // Scroll to switch workspaces + WheelHandler { + onWheel: (event) => { + if (event.angleDelta.y < 0) + Hyprland.dispatch(`workspace r+1`); + else if (event.angleDelta.y > 0) + Hyprland.dispatch(`workspace r-1`); + } + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } + + // Workspaces - background + RowLayout { + id: rowLayout + z: 1 + + spacing: 0 + anchors.fill: parent + implicitHeight: 40 + + Repeater { + model: ConfigOptions.bar.workspacesShown + + Rectangle { + z: 1 + implicitWidth: workspaceButtonWidth + implicitHeight: workspaceButtonWidth + radius: Appearance.rounding.full + property var radiusLeft: workspaceOccupied[index-1] ? 0 : Appearance.rounding.full + property var radiusRight: workspaceOccupied[index+1] ? 0 : Appearance.rounding.full + + topLeftRadius: radiusLeft + bottomLeftRadius: radiusLeft + topRightRadius: radiusRight + bottomRightRadius: radiusRight + + color: Appearance.colors.colLayer2 + opacity: workspaceOccupied[index] ? 1 : 0 + // color: workspaceOccupied[index] ? Appearance.colors.colLayer2 : "transparent" + + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve + } + } + Behavior on radiusLeft { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve + } + } + + Behavior on radiusRight { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve + } + } + + } + + } + + } + + // Active workspace + Rectangle { + z: 2 + implicitWidth: workspaceButtonWidth - activeWorkspaceMargin * 2 + implicitHeight: workspaceButtonWidth - activeWorkspaceMargin * 2 + radius: Appearance.rounding.full + color: Appearance.m3colors.m3primary + anchors.verticalCenter: parent.verticalCenter + x: animatedActiveWorkspaceIndex * workspaceButtonWidth + activeWorkspaceMargin + } + + // Workspaces - numbers + RowLayout { + id: rowLayoutNumbers + z: 3 + + spacing: 0 + anchors.fill: parent + implicitHeight: 40 + + Repeater { + model: ConfigOptions.bar.workspacesShown + + Button { + id: button + Layout.fillHeight: true + topInset: 7 + bottomInset: 7 + onPressed: Hyprland.dispath(`workspace ${index+1}`) + width: workspaceButtonWidth + + contentItem: StyledText { + z: 3 + property int workspaceValue: workspaceGroup * ConfigOptions.bar.workspacesShown + index + 1 + + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pointSize: Appearance.font.pointSize.small + text: `${workspaceValue}` + elide: Text.ElideRight + color: (monitor.activeWorkspace?.id == workspaceValue) ? Appearance.m3colors.m3onPrimary : (workspaceOccupied[index] ? Appearance.colors.colOnLayer1 : Appearance.colors.colOnLayer1Inactive) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve + } + + } + + } + + background: Rectangle { + color: "transparent" // Transparent background + implicitWidth: workspaceButtonWidth + implicitHeight: workspaceButtonWidth + } + + + } + + } + + } + +} diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 155f80dc3..00783c13c 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -145,6 +145,10 @@ Singleton { property int type: Easing.BezierSpline property list bezierCurve: [0, 0.55, 0.45, 1] } + property QtObject menuDecel: QtObject { + property int duration: 250 + property int type: Easing.OutCubic + } } } diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml new file mode 100644 index 000000000..1ff2f2cba --- /dev/null +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -0,0 +1,10 @@ +import QtQuick +import Quickshell +pragma Singleton + +Singleton { + property QtObject bar: QtObject { + property int workspacesShown: 10 + } + +} diff --git a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml b/.config/quickshell/modules/common/widgets/SmallCircleButton.qml index 545887bfa..cd881f38b 100644 --- a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml +++ b/.config/quickshell/modules/common/widgets/SmallCircleButton.qml @@ -4,7 +4,6 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io -import Quickshell.Wayland Button { id: button From ad63ae699f41bf22e40b0806bb47f9dda7afb2b4 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 11 Apr 2025 00:05:34 +0200 Subject: [PATCH 007/824] battery --- .config/quickshell/modules/bar/Bar.qml | 6 +- .config/quickshell/modules/bar/Battery.qml | 98 +++++++++++ .config/quickshell/modules/bar/Workspaces.qml | 5 +- .../modules/common/ConfigOptions.qml | 1 + .../common/widgets/CircularProgress.qml | 69 ++++++++ licenses/LGPL-3.0.txt | 165 ++++++++++++++++++ 6 files changed, 339 insertions(+), 5 deletions(-) create mode 100644 .config/quickshell/modules/bar/Battery.qml create mode 100644 .config/quickshell/modules/common/widgets/CircularProgress.qml create mode 100644 licenses/LGPL-3.0.txt diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index acbc8a0b3..6785cb729 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -25,7 +25,7 @@ Scope { RowLayout { anchors.centerIn: parent implicitWidth: 500 - spacing: 8 // TODO: Why is this halved when rendered?? + spacing: 8 RowLayout { spacing: 4 @@ -57,6 +57,10 @@ Scope { Layout.alignment: Qt.AlignVCenter } + Battery { + Layout.alignment: Qt.AlignVCenter + } + } } diff --git a/.config/quickshell/modules/bar/Battery.qml b/.config/quickshell/modules/bar/Battery.qml new file mode 100644 index 000000000..c9c482003 --- /dev/null +++ b/.config/quickshell/modules/bar/Battery.qml @@ -0,0 +1,98 @@ +import "../common" +import "../common/widgets" +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Services.UPower + +Rectangle { + readonly property var chargeState: UPower.displayDevice.state + readonly property bool isCharging: chargeState == UPowerDeviceState.Charging + readonly property bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingCharge + readonly property real percentage: UPower.displayDevice.percentage + readonly property bool isLow: percentage <= ConfigOptions.bar.batteryLowThreshold / 100 + readonly property int batterySlideDistance: 10 + readonly property color batteryLowBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3error : Appearance.m3colors.m3errorContainer + readonly property color batteryLowOnBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3errorContainer : Appearance.m3colors.m3error + + implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 + implicitHeight: 32 + color: Appearance.colors.colLayer1 + radius: Appearance.rounding.small + + Process { + id: screenSnip + + command: ["grimblast", "copy", "area"] + } + + RowLayout { + id: rowLayout + + spacing: 4 + anchors.centerIn: parent + + Rectangle { + implicitWidth: (isCharging ? boltIcon.width : 0) - rowLayout.spacing + + Behavior on implicitWidth { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve + } + + } + + } + + StyledText { + Layout.alignment: Qt.AlignVCenter + color: Appearance.colors.colOnLayer1 + text: `${percentage * 100}%` + } + + CircularProgress { + Layout.alignment: Qt.AlignVCenter + lineWidth: 2 + value: percentage + size: 26 + secondaryColor: (isLow && !isCharging) ? batteryLowBackground : Appearance.m3colors.m3secondaryContainer + primaryColor: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer + fill: (isLow && !isCharging) + + MaterialSymbol { + anchors.centerIn: parent + text: "battery_full" + font.pointSize: Appearance.font.pointSize.normal + color: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer + } + + } + + } + + MaterialSymbol { + id: boltIcon + + anchors.left: rowLayout.left + anchors.verticalCenter: rowLayout.verticalCenter + text: "bolt" + font.pointSize: Appearance.font.pointSize.large + color: Appearance.m3colors.m3onSecondaryContainer + visible: opacity !== 0 // Only show when charging + opacity: isCharging ? 1 : 0 // Keep opacity for visibility + + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve + } + + } + + } + +} diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 1f7c0dab3..0eaf706a4 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -97,7 +97,6 @@ Rectangle { color: Appearance.colors.colLayer2 opacity: workspaceOccupied[index] ? 1 : 0 - // color: workspaceOccupied[index] ? Appearance.colors.colLayer2 : "transparent" Behavior on opacity { NumberAnimation { @@ -154,9 +153,7 @@ Rectangle { Button { id: button Layout.fillHeight: true - topInset: 7 - bottomInset: 7 - onPressed: Hyprland.dispath(`workspace ${index+1}`) + onPressed: Hyprland.dispatch(`workspace ${index+1}`) width: workspaceButtonWidth contentItem: StyledText { diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 1ff2f2cba..4ba8f8a6b 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -5,6 +5,7 @@ pragma Singleton Singleton { property QtObject bar: QtObject { property int workspacesShown: 10 + property int batteryLowThreshold: 20 } } diff --git a/.config/quickshell/modules/common/widgets/CircularProgress.qml b/.config/quickshell/modules/common/widgets/CircularProgress.qml new file mode 100644 index 000000000..d72760126 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/CircularProgress.qml @@ -0,0 +1,69 @@ +// From https://github.com/rafzby/circular-progressbar +// License: LGPL-3.0 - A copy can be found in `licenses` folder of repo +import QtQuick 2.9 + +Item { + id: root + + property int size: 30 + property int lineWidth: 2 + property real value: 0 + property color primaryColor: "#29b6f6" + property color secondaryColor: "#e0e0e0" + property bool fill: false + property int fillOverflow: 2 + property int animationDuration: 1000 + + width: size + height: size + onValueChanged: { + canvas.degree = value * 360; + } + + Canvas { + id: canvas + + property real degree: 0 + + anchors.fill: parent + antialiasing: true + onDegreeChanged: { + requestPaint(); + } + onPaint: { + var ctx = getContext("2d"); + var x = root.width / 2; + var y = root.height / 2; + var radius = root.size / 2 - root.lineWidth; + var startAngle = (Math.PI / 180) * 270; + var fullAngle = (Math.PI / 180) * (270 + 360); + var progressAngle = (Math.PI / 180) * (270 + degree); + ctx.reset(); + if (root.fill) { + ctx.fillStyle = root.secondaryColor; + ctx.beginPath(); + ctx.arc(x, y, radius + fillOverflow, startAngle, fullAngle); + ctx.fill(); + } + ctx.lineCap = 'round'; + ctx.lineWidth = root.lineWidth; + ctx.beginPath(); + ctx.arc(x, y, radius, startAngle, fullAngle); + ctx.strokeStyle = root.secondaryColor; + ctx.stroke(); + ctx.beginPath(); + ctx.arc(x, y, radius, startAngle, progressAngle); + ctx.strokeStyle = root.primaryColor; + ctx.stroke(); + } + + Behavior on degree { + NumberAnimation { + duration: root.animationDuration + } + + } + + } + +} diff --git a/licenses/LGPL-3.0.txt b/licenses/LGPL-3.0.txt new file mode 100644 index 000000000..0a041280b --- /dev/null +++ b/licenses/LGPL-3.0.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. From 742ec413f378574117214c9984d51252518b0ebf Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 11 Apr 2025 01:16:55 +0200 Subject: [PATCH 008/824] bar resource usage indicator --- .config/quickshell/modules/bar/Bar.qml | 5 ++ .config/quickshell/modules/bar/Battery.qml | 9 +-- .config/quickshell/modules/bar/Resource.qml | 36 ++++++++++++ .config/quickshell/modules/bar/Resources.qml | 35 ++++++++++++ .../modules/common/ConfigOptions.qml | 3 + .../quickshell/modules/common/DateTime.qml | 4 +- .../modules/common/ResourceUsage.qml | 56 +++++++++++++++++++ .../common/widgets/CircularProgress.qml | 5 +- 8 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 .config/quickshell/modules/bar/Resource.qml create mode 100644 .config/quickshell/modules/bar/Resources.qml create mode 100644 .config/quickshell/modules/common/ResourceUsage.qml diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 6785cb729..6871a964f 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -10,6 +10,7 @@ Scope { PanelWindow { id: barRoot + property var modelData screen: modelData @@ -31,6 +32,10 @@ Scope { spacing: 4 Layout.fillWidth: true Layout.fillHeight: true + + Resources { + } + } RowLayout { diff --git a/.config/quickshell/modules/bar/Battery.qml b/.config/quickshell/modules/bar/Battery.qml index c9c482003..215458ec6 100644 --- a/.config/quickshell/modules/bar/Battery.qml +++ b/.config/quickshell/modules/bar/Battery.qml @@ -12,7 +12,6 @@ Rectangle { readonly property bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingCharge readonly property real percentage: UPower.displayDevice.percentage readonly property bool isLow: percentage <= ConfigOptions.bar.batteryLowThreshold / 100 - readonly property int batterySlideDistance: 10 readonly property color batteryLowBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3error : Appearance.m3colors.m3errorContainer readonly property color batteryLowOnBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3errorContainer : Appearance.m3colors.m3error @@ -21,12 +20,6 @@ Rectangle { color: Appearance.colors.colLayer1 radius: Appearance.rounding.small - Process { - id: screenSnip - - command: ["grimblast", "copy", "area"] - } - RowLayout { id: rowLayout @@ -50,7 +43,7 @@ Rectangle { StyledText { Layout.alignment: Qt.AlignVCenter color: Appearance.colors.colOnLayer1 - text: `${percentage * 100}%` + text: `${Math.round(percentage * 100)}%` } CircularProgress { diff --git a/.config/quickshell/modules/bar/Resource.qml b/.config/quickshell/modules/bar/Resource.qml new file mode 100644 index 000000000..4f231835e --- /dev/null +++ b/.config/quickshell/modules/bar/Resource.qml @@ -0,0 +1,36 @@ +import "../common" +import "../common/widgets" +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Io + +RowLayout { + required property string iconName + required property double percentage + spacing: 4 + + CircularProgress { + Layout.alignment: Qt.AlignVCenter + lineWidth: 2 + value: percentage + size: 26 + secondaryColor: Appearance.m3colors.m3secondaryContainer + primaryColor: Appearance.m3colors.m3onSecondaryContainer + + MaterialSymbol { + anchors.centerIn: parent + text: iconName + font.pointSize: Appearance.font.pointSize.normal + color: Appearance.m3colors.m3onSecondaryContainer + } + + } + + StyledText { + Layout.alignment: Qt.AlignVCenter + color: Appearance.colors.colOnLayer1 + text: `${Math.round(percentage * 100)}%` + } + +} diff --git a/.config/quickshell/modules/bar/Resources.qml b/.config/quickshell/modules/bar/Resources.qml new file mode 100644 index 000000000..ea64c8ce6 --- /dev/null +++ b/.config/quickshell/modules/bar/Resources.qml @@ -0,0 +1,35 @@ +import "../common" +import "../common/widgets" +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Io + +Rectangle { + implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 + implicitHeight: 32 + color: Appearance.colors.colLayer1 + radius: Appearance.rounding.small + + RowLayout { + id: rowLayout + + spacing: 4 + anchors.centerIn: parent + + Resource { + iconName: "memory" + percentage: ResourceUsage.memoryUsedPercentage + } + Resource { + iconName: "swap_horiz" + percentage: ResourceUsage.swapUsedPercentage + } + Resource { + iconName: "settings_slow_motion" + percentage: ResourceUsage.cpuUsage + } + + } + +} diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 4ba8f8a6b..df7f5e154 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -7,5 +7,8 @@ Singleton { property int workspacesShown: 10 property int batteryLowThreshold: 20 } + property QtObject resources: QtObject { + property int updateInterval: 3000 + } } diff --git a/.config/quickshell/modules/common/DateTime.qml b/.config/quickshell/modules/common/DateTime.qml index 6e29ec968..ddeec093e 100644 --- a/.config/quickshell/modules/common/DateTime.qml +++ b/.config/quickshell/modules/common/DateTime.qml @@ -1,13 +1,11 @@ import QtQuick import Quickshell import Quickshell.Io -// with this line our type becomes a singleton pragma Singleton -// your singletons should always have Singleton as the type Singleton { property string time: Qt.formatDateTime(clock.date, "hh:mm") - property string date: Qt.formatDateTime(clock.date, "dddd, dd/MM") + property string date: Qt.formatDateTime(clock.date, "dddd, dd/MM") SystemClock { id: clock diff --git a/.config/quickshell/modules/common/ResourceUsage.qml b/.config/quickshell/modules/common/ResourceUsage.qml new file mode 100644 index 000000000..898abbb34 --- /dev/null +++ b/.config/quickshell/modules/common/ResourceUsage.qml @@ -0,0 +1,56 @@ +pragma Singleton +import QtQuick +import Quickshell +import Quickshell.Io + +Singleton { + property double memoryTotal: 1 + property double memoryFree: 1 + property double memoryUsed: memoryTotal - memoryFree + property double memoryUsedPercentage: memoryUsed / memoryTotal + property double swapTotal: 1 + property double swapFree: 1 + property double swapUsed: swapTotal - swapFree + property double swapUsedPercentage: swapUsed / swapTotal + property double cpuUsage: 0 + property var previousCpuStats + + Timer { + interval: 50 + running: true + repeat: true + onTriggered: { + // Reload files + fileMeminfo.reload() + fileStat.reload() + + // Parse memory and swap usage + const textMeminfo = fileMeminfo.text() + memoryTotal = Number(textMeminfo.match(/MemTotal: *(\d+)/)?.[1] ?? 1) + memoryFree = Number(textMeminfo.match(/MemAvailable: *(\d+)/)?.[1] ?? 0) + swapTotal = Number(textMeminfo.match(/SwapTotal: *(\d+)/)?.[1] ?? 1) + swapFree = Number(textMeminfo.match(/SwapFree: *(\d+)/)?.[1] ?? 0) + + // Parse CPU usage + const textStat = fileStat.text() + const cpuLine = textStat.match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/) + if (cpuLine) { + const stats = cpuLine.slice(1).map(Number) + const total = stats.reduce((a, b) => a + b, 0) + const idle = stats[3] + + if (previousCpuStats) { + const totalDiff = total - previousCpuStats.total + const idleDiff = idle - previousCpuStats.idle + cpuUsage = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0 + } + + previousCpuStats = { total, idle } + } + interval = ConfigOptions.resources.updateInterval + } + } + + FileView { id: fileMeminfo; path: "/proc/meminfo" } + FileView { id: fileStat; path: "/proc/stat" } +} \ No newline at end of file diff --git a/.config/quickshell/modules/common/widgets/CircularProgress.qml b/.config/quickshell/modules/common/widgets/CircularProgress.qml index d72760126..62970f797 100644 --- a/.config/quickshell/modules/common/widgets/CircularProgress.qml +++ b/.config/quickshell/modules/common/widgets/CircularProgress.qml @@ -8,8 +8,8 @@ Item { property int size: 30 property int lineWidth: 2 property real value: 0 - property color primaryColor: "#29b6f6" - property color secondaryColor: "#e0e0e0" + property color primaryColor: "#70585D" + property color secondaryColor: "#FFF8F7" property bool fill: false property int fillOverflow: 2 property int animationDuration: 1000 @@ -60,6 +60,7 @@ Item { Behavior on degree { NumberAnimation { duration: root.animationDuration + easing.type: Easing.OutCubic } } From 3bb67a9a4f55203f1aea12cd714a9b100e53857f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 11 Apr 2025 01:39:31 +0200 Subject: [PATCH 009/824] bar: workspaces: better occupied state --- .config/quickshell/modules/bar/Workspaces.qml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 0eaf706a4..6730a6515 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -4,14 +4,16 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell +import Quickshell.Wayland import Quickshell.Hyprland import Quickshell.Io Rectangle { required property var bar readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) - readonly property list workspaceOccupied: [] + readonly property Toplevel activeWindow: ToplevelManager.activeToplevel readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / ConfigOptions.bar.workspacesShown) + property list workspaceOccupied: [] property int widgetPadding: 4 property int workspaceButtonWidth: 26 property int activeWorkspaceMargin: 1 @@ -29,7 +31,10 @@ Rectangle { function updateWorkspaceOccupied() { workspaceOccupied = Array.from({ length: ConfigOptions.bar.workspacesShown }, (_, i) => { return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * ConfigOptions.bar.workspacesShown + i + 1); - }); + }) + if(!activeWindow?.activated) { + workspaceOccupied[(monitor.activeWorkspace?.id - 1) % ConfigOptions.bar.workspacesShown] = false; + } } // Initialize workspaceOccupied when the component is created From d9ed5434ac78ac68d1efccec3aa20113c0973883 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 11 Apr 2025 14:54:22 +0200 Subject: [PATCH 010/824] bar: media indicator --- .config/quickshell/modules/bar/Bar.qml | 17 +- .config/quickshell/modules/bar/Battery.qml | 3 +- .config/quickshell/modules/bar/Media.qml | 85 ++++++++++ .config/quickshell/modules/bar/Workspaces.qml | 4 - .../quickshell/modules/common/Appearance.qml | 9 +- .../modules/common/MprisController.qml | 159 ++++++++++++++++++ .../common/widgets/SmallCircleButton.qml | 1 - 7 files changed, 262 insertions(+), 16 deletions(-) create mode 100644 .config/quickshell/modules/bar/Media.qml create mode 100644 .config/quickshell/modules/common/MprisController.qml diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 6871a964f..0e7bcabd5 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -5,6 +5,10 @@ import QtQuick.Layouts import Quickshell Scope { + id: bar + readonly property int barHeight: 40 + readonly property int sideCenterModuleWidth: 360 + Variants { model: Quickshell.screens @@ -14,7 +18,7 @@ Scope { property var modelData screen: modelData - height: 40 + height: barHeight color: Appearance.colors.colLayer0 // Left section @@ -25,17 +29,21 @@ Scope { // Middle section RowLayout { anchors.centerIn: parent - implicitWidth: 500 spacing: 8 RowLayout { + Layout.preferredWidth: sideCenterModuleWidth spacing: 4 - Layout.fillWidth: true Layout.fillHeight: true + implicitWidth: 350 Resources { } + Media { + Layout.fillWidth: true + } + } RowLayout { @@ -50,12 +58,13 @@ Scope { } RowLayout { - Layout.fillWidth: true + Layout.preferredWidth: sideCenterModuleWidth Layout.fillHeight: true spacing: 4 ClockWidget { Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true } UtilButtons { diff --git a/.config/quickshell/modules/bar/Battery.qml b/.config/quickshell/modules/bar/Battery.qml index 215458ec6..b4cbea1f3 100644 --- a/.config/quickshell/modules/bar/Battery.qml +++ b/.config/quickshell/modules/bar/Battery.qml @@ -27,13 +27,12 @@ Rectangle { anchors.centerIn: parent Rectangle { - implicitWidth: (isCharging ? boltIcon.width : 0) - rowLayout.spacing + implicitWidth: (isCharging ? boltIcon.width : 0) Behavior on implicitWidth { NumberAnimation { duration: Appearance.animation.elementDecel.duration easing.type: Appearance.animation.elementDecel.type - easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve } } diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml new file mode 100644 index 000000000..44431f016 --- /dev/null +++ b/.config/quickshell/modules/bar/Media.qml @@ -0,0 +1,85 @@ +import "../common" +import "../common/widgets" +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Services.Mpris + +Rectangle { + readonly property MprisPlayer activePlayer: MprisController.activePlayer + readonly property string cleanedTitle: activePlayer?.trackTitle.replace(/【[^】]*】/, "") || "No media" + + Layout.fillHeight: true + implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 + implicitHeight: 40 + color: "transparent" + + // Background + Rectangle { + anchors.centerIn: parent + width: parent.width + implicitHeight: 32 + color: Appearance.colors.colLayer1 + radius: Appearance.rounding.small + } + + Timer { + running: activePlayer?.playbackState == MprisPlaybackState.Playing + interval: 1000 + repeat: true + onTriggered: activePlayer.positionChanged() + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.MiddleButton | Qt.BackButton | Qt.ForwardButton | Qt.RightButton + onPressed: (event) => { + if (event.button === Qt.MiddleButton) { + activePlayer.togglePlaying(); + } else if (event.button === Qt.BackButton) { + activePlayer.previous(); + } else if (event.button === Qt.ForwardButton || event.button === Qt.RightButton) { + activePlayer.next(); + } + } + } + + RowLayout { + id: rowLayout + + spacing: 4 + anchors.fill: parent + + CircularProgress { + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: rowLayout.spacing + lineWidth: 2 + value: activePlayer?.position / activePlayer?.length + size: 26 + secondaryColor: Appearance.m3colors.m3secondaryContainer + primaryColor: Appearance.m3colors.m3onSecondaryContainer + + MaterialSymbol { + anchors.centerIn: parent + text: activePlayer?.isPlaying ? "pause" : "play_arrow" + font.pointSize: Appearance.font.pointSize.normal + color: Appearance.m3colors.m3onSecondaryContainer + } + + } + + StyledText { + width: rowLayout.width - (CircularProgress.size + rowLayout.spacing * 2) // TODO ADJUST THIS + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true // Ensures the text takes up available space + Layout.rightMargin: rowLayout.spacing + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight // Truncates the text on the right + color: Appearance.colors.colOnLayer1 + text: `${cleanedTitle} • ${activePlayer?.trackArtist}` + } + + } + +} diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 6730a6515..26274d4a3 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -107,14 +107,12 @@ Rectangle { NumberAnimation { duration: Appearance.animation.elementDecel.duration easing.type: Appearance.animation.elementDecel.type - easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve } } Behavior on radiusLeft { NumberAnimation { duration: Appearance.animation.elementDecel.duration easing.type: Appearance.animation.elementDecel.type - easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve } } @@ -122,7 +120,6 @@ Rectangle { NumberAnimation { duration: Appearance.animation.elementDecel.duration easing.type: Appearance.animation.elementDecel.type - easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve } } @@ -177,7 +174,6 @@ Rectangle { ColorAnimation { duration: Appearance.animation.elementDecel.duration easing.type: Appearance.animation.elementDecel.type - easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve } } diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 00783c13c..d0b62f3a4 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -141,13 +141,12 @@ Singleton { animation: QtObject { property QtObject elementDecel: QtObject { - property int duration: 100 - property int type: Easing.BezierSpline - property list bezierCurve: [0, 0.55, 0.45, 1] + property int duration: 180 + property int type: Easing.OutCirc } property QtObject menuDecel: QtObject { - property int duration: 250 - property int type: Easing.OutCubic + property int duration: 350 + property int type: Easing.OutQuint } } diff --git a/.config/quickshell/modules/common/MprisController.qml b/.config/quickshell/modules/common/MprisController.qml new file mode 100644 index 000000000..24977f749 --- /dev/null +++ b/.config/quickshell/modules/common/MprisController.qml @@ -0,0 +1,159 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQml.Models +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Services.Mpris +import "../.." + +Singleton { + id: root; + property MprisPlayer trackedPlayer: null; + property MprisPlayer activePlayer: trackedPlayer ?? Mpris.players.values[0] ?? null; + signal trackChanged(reverse: bool); + + property bool __reverse: false; + + property var activeTrack; + + Instantiator { + model: Mpris.players; + + Connections { + required property MprisPlayer modelData; + target: modelData; + + Component.onCompleted: { + if (root.trackedPlayer == null || modelData.isPlaying) { + root.trackedPlayer = modelData; + } + } + + Component.onDestruction: { + if (root.trackedPlayer == null || !root.trackedPlayer.isPlaying) { + for (const player of Mpris.players.values) { + if (player.playbackState.isPlaying) { + root.trackedPlayer = player; + break; + } + } + + if (trackedPlayer == null && Mpris.players.values.length != 0) { + trackedPlayer = Mpris.players.values[0]; + } + } + } + + function onPlaybackStateChanged() { + if (root.trackedPlayer !== modelData) root.trackedPlayer = modelData; + } + } + } + + Connections { + target: activePlayer + + function onPostTrackChanged() { + root.updateTrack(); + } + + function onTrackArtUrlChanged() { + console.log("arturl:", activePlayer.trackArtUrl) + //root.updateTrack(); + if (root.activePlayer.uniqueId == root.activeTrack.uniqueId && root.activePlayer.trackArtUrl != root.activeTrack.artUrl) { + // cantata likes to send cover updates *BEFORE* updating the track info. + // as such, art url changes shouldn't be able to break the reverse animation + const r = root.__reverse; + root.updateTrack(); + root.__reverse = r; + + } + } + } + + onActivePlayerChanged: this.updateTrack(); + + function updateTrack() { + //console.log(`update: ${this.activePlayer?.trackTitle ?? ""} : ${this.activePlayer?.trackArtists}`) + this.activeTrack = { + uniqueId: this.activePlayer?.uniqueId ?? 0, + artUrl: this.activePlayer?.trackArtUrl ?? "", + title: this.activePlayer?.trackTitle || "Unknown Title", + artist: this.activePlayer?.trackArtist || "Unknown Artist", + album: this.activePlayer?.trackAlbum || "Unknown Album", + }; + + this.trackChanged(__reverse); + this.__reverse = false; + } + + property bool isPlaying: this.activePlayer && this.activePlayer.isPlaying; + property bool canTogglePlaying: this.activePlayer?.canTogglePlaying ?? false; + function togglePlaying() { + if (this.canTogglePlaying) this.activePlayer.togglePlaying(); + } + + property bool canGoPrevious: this.activePlayer?.canGoPrevious ?? false; + function previous() { + if (this.canGoPrevious) { + this.__reverse = true; + this.activePlayer.previous(); + } + } + + property bool canGoNext: this.activePlayer?.canGoNext ?? false; + function next() { + if (this.canGoNext) { + this.__reverse = false; + this.activePlayer.next(); + } + } + + property bool canChangeVolume: this.activePlayer && this.activePlayer.volumeSupported && this.activePlayer.canControl; + + property bool loopSupported: this.activePlayer && this.activePlayer.loopSupported && this.activePlayer.canControl; + property var loopState: this.activePlayer?.loopState ?? MprisLoopState.None; + function setLoopState(loopState: var) { + if (this.loopSupported) { + this.activePlayer.loopState = loopState; + } + } + + property bool shuffleSupported: this.activePlayer && this.activePlayer.shuffleSupported && this.activePlayer.canControl; + property bool hasShuffle: this.activePlayer?.shuffle ?? false; + function setShuffle(shuffle: bool) { + if (this.shuffleSupported) { + this.activePlayer.shuffle = shuffle; + } + } + + function setActivePlayer(player: MprisPlayer) { + const targetPlayer = player ?? Mpris.players[0]; + console.log(`setactive: ${targetPlayer} from ${activePlayer}`) + + if (targetPlayer && this.activePlayer) { + this.__reverse = Mpris.players.indexOf(targetPlayer) < Mpris.players.indexOf(this.activePlayer); + } else { + // always animate forward if going to null + this.__reverse = false; + } + + this.trackedPlayer = targetPlayer; + } + + IpcHandler { + target: "mpris" + + function pauseAll(): void { + for (const player of Mpris.players.values) { + if (player.canPause) player.pause(); + } + } + + function playPause(): void { root.togglePlaying(); } + function previous(): void { root.previous(); } + function next(): void { root.next(); } + } +} diff --git a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml b/.config/quickshell/modules/common/widgets/SmallCircleButton.qml index cd881f38b..4cb2e9e61 100644 --- a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml +++ b/.config/quickshell/modules/common/widgets/SmallCircleButton.qml @@ -24,7 +24,6 @@ Button { ColorAnimation { duration: Appearance.animation.elementDecel.duration easing.type: Appearance.animation.elementDecel.type - easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve } } From c29041aa9e15eaa298cc5ad5863791cb239816f3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 11 Apr 2025 16:03:54 +0200 Subject: [PATCH 011/824] bar: resources/music: dynamic show/hide --- .config/quickshell/modules/bar/Battery.qml | 1 - .config/quickshell/modules/bar/Media.qml | 2 +- .config/quickshell/modules/bar/Resource.qml | 64 +++++++++++++------ .config/quickshell/modules/bar/Resources.qml | 15 ++++- .../quickshell/modules/common/Appearance.qml | 2 +- .../modules/common/ConfigOptions.qml | 4 ++ .config/quickshell/shell.qml | 3 + 7 files changed, 65 insertions(+), 26 deletions(-) diff --git a/.config/quickshell/modules/bar/Battery.qml b/.config/quickshell/modules/bar/Battery.qml index b4cbea1f3..dc3b0d057 100644 --- a/.config/quickshell/modules/bar/Battery.qml +++ b/.config/quickshell/modules/bar/Battery.qml @@ -80,7 +80,6 @@ Rectangle { NumberAnimation { duration: Appearance.animation.elementDecel.duration easing.type: Appearance.animation.elementDecel.type - easing.bezierCurve: Appearance.animation.elementDecel.bezierCurve } } diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml index 44431f016..e681140ad 100644 --- a/.config/quickshell/modules/bar/Media.qml +++ b/.config/quickshell/modules/bar/Media.qml @@ -77,7 +77,7 @@ Rectangle { horizontalAlignment: Text.AlignHCenter elide: Text.ElideRight // Truncates the text on the right color: Appearance.colors.colOnLayer1 - text: `${cleanedTitle} • ${activePlayer?.trackArtist}` + text: `${cleanedTitle}${activePlayer?.trackArtist ? ' • ' + activePlayer.trackArtist : ''}` } } diff --git a/.config/quickshell/modules/bar/Resource.qml b/.config/quickshell/modules/bar/Resource.qml index 4f231835e..3b7f83bfc 100644 --- a/.config/quickshell/modules/bar/Resource.qml +++ b/.config/quickshell/modules/bar/Resource.qml @@ -5,32 +5,56 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io -RowLayout { +Rectangle { required property string iconName required property double percentage - spacing: 4 + property bool shown: true + clip: true + implicitWidth: resourceRowLayout.x < 0 ? 0 : childrenRect.width + implicitHeight: childrenRect.height + color: "transparent" - CircularProgress { - Layout.alignment: Qt.AlignVCenter - lineWidth: 2 - value: percentage - size: 26 - secondaryColor: Appearance.m3colors.m3secondaryContainer - primaryColor: Appearance.m3colors.m3onSecondaryContainer + RowLayout { + spacing: 4 + id: resourceRowLayout + x: shown ? 0 : -resourceRowLayout.width - MaterialSymbol { - anchors.centerIn: parent - text: iconName - font.pointSize: Appearance.font.pointSize.normal - color: Appearance.m3colors.m3onSecondaryContainer + CircularProgress { + Layout.alignment: Qt.AlignVCenter + lineWidth: 2 + value: percentage + size: 26 + secondaryColor: Appearance.m3colors.m3secondaryContainer + primaryColor: Appearance.m3colors.m3onSecondaryContainer + + MaterialSymbol { + anchors.centerIn: parent + text: iconName + font.pointSize: Appearance.font.pointSize.normal + color: Appearance.m3colors.m3onSecondaryContainer + } + + } + + StyledText { + Layout.alignment: Qt.AlignVCenter + color: Appearance.colors.colOnLayer1 + text: `${Math.round(percentage * 100)}%` + } + + Behavior on x { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } } } - StyledText { - Layout.alignment: Qt.AlignVCenter - color: Appearance.colors.colOnLayer1 - text: `${Math.round(percentage * 100)}%` + Behavior on implicitWidth { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } } - -} +} \ No newline at end of file diff --git a/.config/quickshell/modules/bar/Resources.qml b/.config/quickshell/modules/bar/Resources.qml index ea64c8ce6..4528f8b48 100644 --- a/.config/quickshell/modules/bar/Resources.qml +++ b/.config/quickshell/modules/bar/Resources.qml @@ -4,9 +4,10 @@ import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Io +import Quickshell.Services.Mpris Rectangle { - implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 + implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin implicitHeight: 32 color: Appearance.colors.colLayer1 radius: Appearance.rounding.small @@ -14,20 +15,28 @@ Rectangle { RowLayout { id: rowLayout - spacing: 4 - anchors.centerIn: parent + spacing: 0 + anchors.fill: parent + anchors.leftMargin: 4 + anchors.rightMargin: 4 Resource { iconName: "memory" percentage: ResourceUsage.memoryUsedPercentage } + Resource { iconName: "swap_horiz" percentage: ResourceUsage.swapUsedPercentage + shown: ConfigOptions.bar.resources.alwaysShowSwap || (MprisController.activePlayer == null) + Layout.leftMargin: shown ? 4 : 0 } + Resource { iconName: "settings_slow_motion" percentage: ResourceUsage.cpuUsage + shown: ConfigOptions.bar.resources.alwaysShowCpu || (MprisController.activePlayer == null) + Layout.leftMargin: shown ? 4 : 0 } } diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index d0b62f3a4..404656565 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -146,7 +146,7 @@ Singleton { } property QtObject menuDecel: QtObject { property int duration: 350 - property int type: Easing.OutQuint + property int type: Easing.OutExpo } } diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index df7f5e154..d86fb96a8 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -6,6 +6,10 @@ Singleton { property QtObject bar: QtObject { property int workspacesShown: 10 property int batteryLowThreshold: 20 + property QtObject resources: QtObject { + property bool alwaysShowSwap: true + property bool alwaysShowCpu: false + } } property QtObject resources: QtObject { property int updateInterval: 3000 diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index aeb4b2ba1..ceec43fd5 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -4,8 +4,11 @@ import QtQuick.Layouts import Quickshell import "./modules/bar" +import QtQuick.Window + ShellRoot { Bar { } + } From 06fc4baf4e90164a27dda2e5b0603ad6ab96e5a2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 11 Apr 2025 17:10:19 +0200 Subject: [PATCH 012/824] active window title + workspace fix --- .../quickshell/modules/bar/ActiveWindow.qml | 39 +++++++++++++++++++ .config/quickshell/modules/bar/Bar.qml | 7 ++++ .../quickshell/modules/bar/ClockWidget.qml | 1 - .config/quickshell/modules/bar/Workspaces.qml | 10 ++--- .../quickshell/modules/common/Appearance.qml | 2 + .../modules/common/MprisController.qml | 4 +- .../common/widgets/CircularProgress.qml | 8 ++++ 7 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 .config/quickshell/modules/bar/ActiveWindow.qml diff --git a/.config/quickshell/modules/bar/ActiveWindow.qml b/.config/quickshell/modules/bar/ActiveWindow.qml new file mode 100644 index 000000000..d51381580 --- /dev/null +++ b/.config/quickshell/modules/bar/ActiveWindow.qml @@ -0,0 +1,39 @@ +import "../common" +import "../common/widgets" +import QtQuick +import QtQuick.Layouts +import Quickshell.Wayland +import Quickshell.Hyprland + +Rectangle { + required property var bar + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) + readonly property Toplevel activeWindow: ToplevelManager.activeToplevel + + height: parent.height + width: colLayout.width + color: "transparent" + Layout.leftMargin: Appearance.rounding.screenRounding + + + ColumnLayout { + id: colLayout + + anchors.centerIn: parent + spacing: -4 + + StyledText { + font.pointSize: Appearance.font.pointSize.smaller + color: Appearance.colors.colSubtext + text: activeWindow.activated ? activeWindow?.appId : "Desktop" + } + + StyledText { + font.pointSize: Appearance.font.pointSize.small + color: Appearance.colors.colOnLayer0 + text: activeWindow.activated ? activeWindow?.title : `Workspace ${monitor.activeWorkspace?.id}` + } + + } + +} diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 0e7bcabd5..5289f42c8 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -6,6 +6,7 @@ import Quickshell Scope { id: bar + readonly property int barHeight: 40 readonly property int sideCenterModuleWidth: 360 @@ -24,6 +25,12 @@ Scope { // Left section RowLayout { anchors.left: parent.left + implicitHeight: barHeight + + ActiveWindow { + bar: barRoot + } + } // Middle section diff --git a/.config/quickshell/modules/bar/ClockWidget.qml b/.config/quickshell/modules/bar/ClockWidget.qml index d001f1db7..4568bbdcd 100644 --- a/.config/quickshell/modules/bar/ClockWidget.qml +++ b/.config/quickshell/modules/bar/ClockWidget.qml @@ -16,7 +16,6 @@ Rectangle { anchors.centerIn: parent StyledText { - font.family: Appearance.font.family.title font.pointSize: Appearance.font.pointSize.large color: Appearance.colors.colOnLayer1 text: DateTime.time diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 26274d4a3..1fd13c42c 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -32,9 +32,6 @@ Rectangle { workspaceOccupied = Array.from({ length: ConfigOptions.bar.workspacesShown }, (_, i) => { return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * ConfigOptions.bar.workspacesShown + i + 1); }) - if(!activeWindow?.activated) { - workspaceOccupied[(monitor.activeWorkspace?.id - 1) % ConfigOptions.bar.workspacesShown] = false; - } } // Initialize workspaceOccupied when the component is created @@ -48,7 +45,6 @@ Rectangle { } } - Layout.fillHeight: true implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitHeight: 40 @@ -92,8 +88,8 @@ Rectangle { implicitWidth: workspaceButtonWidth implicitHeight: workspaceButtonWidth radius: Appearance.rounding.full - property var radiusLeft: workspaceOccupied[index-1] ? 0 : Appearance.rounding.full - property var radiusRight: workspaceOccupied[index+1] ? 0 : Appearance.rounding.full + property var radiusLeft: (workspaceOccupied[index-1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index)) ? 0 : Appearance.rounding.full + property var radiusRight: (workspaceOccupied[index+1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+2)) ? 0 : Appearance.rounding.full topLeftRadius: radiusLeft bottomLeftRadius: radiusLeft @@ -101,7 +97,7 @@ Rectangle { bottomRightRadius: radiusRight color: Appearance.colors.colLayer2 - opacity: workspaceOccupied[index] ? 1 : 0 + opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+1)) ? 1 : 0 Behavior on opacity { NumberAnimation { diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 404656565..bfff14963 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -95,6 +95,7 @@ Singleton { } colors: QtObject { + property color colSubtext: m3colors.m3outline property color colLayer0: m3colors.m3background property color colOnLayer0: m3colors.m3onBackground property color colLayer0Hover: mix(colLayer0, colOnLayer0, 0.85) @@ -119,6 +120,7 @@ Singleton { property int normal: 17 property int large: 25 property int full: 9999 + property int screenRounding: large } font: QtObject { diff --git a/.config/quickshell/modules/common/MprisController.qml b/.config/quickshell/modules/common/MprisController.qml index 24977f749..590dcd86c 100644 --- a/.config/quickshell/modules/common/MprisController.qml +++ b/.config/quickshell/modules/common/MprisController.qml @@ -60,8 +60,8 @@ Singleton { } function onTrackArtUrlChanged() { - console.log("arturl:", activePlayer.trackArtUrl) - //root.updateTrack(); + // console.log("arturl:", activePlayer.trackArtUrl) + // root.updateTrack(); if (root.activePlayer.uniqueId == root.activeTrack.uniqueId && root.activePlayer.trackArtUrl != root.activeTrack.artUrl) { // cantata likes to send cover updates *BEFORE* updating the track info. // as such, art url changes shouldn't be able to break the reverse animation diff --git a/.config/quickshell/modules/common/widgets/CircularProgress.qml b/.config/quickshell/modules/common/widgets/CircularProgress.qml index 62970f797..73ab50f4b 100644 --- a/.config/quickshell/modules/common/widgets/CircularProgress.qml +++ b/.config/quickshell/modules/common/widgets/CircularProgress.qml @@ -19,6 +19,12 @@ Item { onValueChanged: { canvas.degree = value * 360; } + onPrimaryColorChanged: { + canvas.requestPaint(); + } + onSecondaryColorChanged: { + canvas.requestPaint(); + } Canvas { id: canvas @@ -27,9 +33,11 @@ Item { anchors.fill: parent antialiasing: true + onDegreeChanged: { requestPaint(); } + onPaint: { var ctx = getContext("2d"); var x = root.width / 2; From d77e4f14bf43f98f53b4195e998c836b63b2803d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 11 Apr 2025 20:43:18 +0200 Subject: [PATCH 013/824] bar brightness scroll --- .../quickshell/modules/bar/ActiveWindow.qml | 9 ++- .config/quickshell/modules/bar/Bar.qml | 11 ++++ .../quickshell/modules/common/Brightness.qml | 63 +++++++++++++++++++ 3 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 .config/quickshell/modules/common/Brightness.qml diff --git a/.config/quickshell/modules/bar/ActiveWindow.qml b/.config/quickshell/modules/bar/ActiveWindow.qml index d51381580..3a23182ac 100644 --- a/.config/quickshell/modules/bar/ActiveWindow.qml +++ b/.config/quickshell/modules/bar/ActiveWindow.qml @@ -9,6 +9,7 @@ Rectangle { required property var bar readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) readonly property Toplevel activeWindow: ToplevelManager.activeToplevel + property int preferredWidth: 400 height: parent.height width: colLayout.width @@ -25,13 +26,17 @@ Rectangle { StyledText { font.pointSize: Appearance.font.pointSize.smaller color: Appearance.colors.colSubtext - text: activeWindow.activated ? activeWindow?.appId : "Desktop" + Layout.preferredWidth: preferredWidth + elide: Text.ElideRight + text: activeWindow?.activated ? activeWindow?.appId : "Desktop" } StyledText { font.pointSize: Appearance.font.pointSize.small color: Appearance.colors.colOnLayer0 - text: activeWindow.activated ? activeWindow?.title : `Workspace ${monitor.activeWorkspace?.id}` + Layout.preferredWidth: preferredWidth + elide: Text.ElideRight + text: activeWindow?.activated ? activeWindow?.title : `Workspace ${monitor.activeWorkspace?.id}` } } diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 5289f42c8..3cb2ce7c8 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -30,6 +30,17 @@ Scope { ActiveWindow { bar: barRoot } + // Scroll to switch workspaces + + WheelHandler { + onWheel: (event) => { + if (event.angleDelta.y < 0) + Brightness.value = -1; + else if (event.angleDelta.y > 0) + Brightness.value = 1; + } + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } } diff --git a/.config/quickshell/modules/common/Brightness.qml b/.config/quickshell/modules/common/Brightness.qml new file mode 100644 index 000000000..57ad389d3 --- /dev/null +++ b/.config/quickshell/modules/common/Brightness.qml @@ -0,0 +1,63 @@ +import Quickshell +import Quickshell.Io +pragma Singleton + +Singleton { + id: root + + property string brightness + property int value: 0 + + function refresh() { + getBrightness.running = true; + } + + onValueChanged: () => { + if (value > 0) { + increaseBrightness.running = true; + root.value = 0; + } else if (value < 0) { + decreaseBrightness.running = true; + root.value = 0; + } + getBrightness.running = true; + } + + Process { + id: getBrightness + + command: ["sh", "-c", "brightnessctl -m i | cut -d, -f4"] + running: true + onExited: { + running = false; + } + + stdout: SplitParser { + onRead: (data) => { + root.brightness = data; + } + } + + } + + Process { + id: decreaseBrightness + + command: ["brightnessctl", "set", "5%-"] + running: false + onExited: { + running = false; + } + } + + Process { + id: increaseBrightness + + command: ["brightnessctl", "set", "5%+"] + running: false + onExited: { + running = false; + } + } + +} From dae28573919afa3c2693cf782777ad2c468b3ae3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 11 Apr 2025 23:35:05 +0200 Subject: [PATCH 014/824] system tray --- .../quickshell/modules/bar/ActiveWindow.qml | 3 +- .config/quickshell/modules/bar/Bar.qml | 27 +++++++++- .config/quickshell/modules/bar/Media.qml | 3 +- .config/quickshell/modules/bar/Resource.qml | 3 +- .config/quickshell/modules/bar/SysTray.qml | 39 +++++++++++++++ .../quickshell/modules/bar/SysTrayItem.qml | 49 +++++++++++++++++++ .config/quickshell/modules/bar/Workspaces.qml | 6 +-- .config/quickshell/modules/common/Audio.qml | 16 ++++++ .config/quickshell/shell.qml | 2 + 9 files changed, 137 insertions(+), 11 deletions(-) create mode 100644 .config/quickshell/modules/bar/SysTray.qml create mode 100644 .config/quickshell/modules/bar/SysTrayItem.qml create mode 100644 .config/quickshell/modules/common/Audio.qml diff --git a/.config/quickshell/modules/bar/ActiveWindow.qml b/.config/quickshell/modules/bar/ActiveWindow.qml index 3a23182ac..e2ab07c91 100644 --- a/.config/quickshell/modules/bar/ActiveWindow.qml +++ b/.config/quickshell/modules/bar/ActiveWindow.qml @@ -5,7 +5,7 @@ import QtQuick.Layouts import Quickshell.Wayland import Quickshell.Hyprland -Rectangle { +Item { required property var bar readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) readonly property Toplevel activeWindow: ToplevelManager.activeToplevel @@ -13,7 +13,6 @@ Rectangle { height: parent.height width: colLayout.width - color: "transparent" Layout.leftMargin: Appearance.rounding.screenRounding diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 3cb2ce7c8..a6d3b1ebb 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -1,4 +1,5 @@ import "../common" +import "../common/widgets" import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -30,8 +31,8 @@ Scope { ActiveWindow { bar: barRoot } - // Scroll to switch workspaces + // Scroll to change brightness WheelHandler { onWheel: (event) => { if (event.angleDelta.y < 0) @@ -100,6 +101,30 @@ Scope { // Right section RowLayout { anchors.right: parent.right + implicitHeight: barHeight + spacing: 20 + + SysTray { + bar: barRoot + } + + Item { // TODO make this wifi & bluetooth + Layout.leftMargin: Appearance.rounding.screenRounding + } + + // Scroll to change volume + WheelHandler { + onWheel: (event) => { + const currentVolume = Audio.sink?.audio.volume; + const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2; + if (event.angleDelta.y < 0) + Audio.sink.audio.volume -= step; + else if (event.angleDelta.y > 0) + Audio.sink.audio.volume += step; + } + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } + } anchors { diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml index e681140ad..268ea1042 100644 --- a/.config/quickshell/modules/bar/Media.qml +++ b/.config/quickshell/modules/bar/Media.qml @@ -6,14 +6,13 @@ import Quickshell import Quickshell.Io import Quickshell.Services.Mpris -Rectangle { +Item { readonly property MprisPlayer activePlayer: MprisController.activePlayer readonly property string cleanedTitle: activePlayer?.trackTitle.replace(/【[^】]*】/, "") || "No media" Layout.fillHeight: true implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitHeight: 40 - color: "transparent" // Background Rectangle { diff --git a/.config/quickshell/modules/bar/Resource.qml b/.config/quickshell/modules/bar/Resource.qml index 3b7f83bfc..fd9a0b87a 100644 --- a/.config/quickshell/modules/bar/Resource.qml +++ b/.config/quickshell/modules/bar/Resource.qml @@ -5,14 +5,13 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io -Rectangle { +Item { required property string iconName required property double percentage property bool shown: true clip: true implicitWidth: resourceRowLayout.x < 0 ? 0 : childrenRect.width implicitHeight: childrenRect.height - color: "transparent" RowLayout { spacing: 4 diff --git a/.config/quickshell/modules/bar/SysTray.qml b/.config/quickshell/modules/bar/SysTray.qml new file mode 100644 index 000000000..27d4e456e --- /dev/null +++ b/.config/quickshell/modules/bar/SysTray.qml @@ -0,0 +1,39 @@ +import "../common" +import "../common/widgets" +import QtQuick +import QtQuick.Layouts +import Quickshell.Hyprland +import Quickshell.Services.SystemTray +import Quickshell.Wayland +import Quickshell.Widgets + +Item { + id: root + + required property var bar + + height: parent.height + implicitWidth: rowLayout.implicitWidth + Layout.leftMargin: Appearance.rounding.screenRounding + + RowLayout { + id: rowLayout + + anchors.fill: parent + spacing: 15 + + Repeater { + model: SystemTray.items + + SysTrayItem { + required property SystemTrayItem modelData + + bar: root.bar + item: modelData + } + + } + + } + +} diff --git a/.config/quickshell/modules/bar/SysTrayItem.qml b/.config/quickshell/modules/bar/SysTrayItem.qml new file mode 100644 index 000000000..83eb267a5 --- /dev/null +++ b/.config/quickshell/modules/bar/SysTrayItem.qml @@ -0,0 +1,49 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.SystemTray +import Quickshell.Widgets + +MouseArea { + id: root + + required property var bar + required property SystemTrayItem item + property bool targetMenuOpen: false + property int trayItemWidth: 16 + + acceptedButtons: Qt.LeftButton | Qt.RightButton + Layout.fillHeight: true + implicitWidth: trayItemWidth + onClicked: (event) => { + switch (event.button) { + case Qt.LeftButton: + item.activate(); + break; + case Qt.RightButton: + item.hasMenu && menu.open(); + break; + default: + console.log("Buttonevent unhandled"); + } + } + + QsMenuAnchor { + id: menu + + menu: root.item.menu + anchor.window: bar + anchor.rect.x: root.x + bar.width + anchor.rect.y: root.y + anchor.rect.height: root.height * 3 + anchor.edges: Edges.Left | Edges.Bottom + } + + IconImage { + source: root.item.icon + anchors.centerIn: parent + width: parent.width + height: parent.height + } + +} diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 1fd13c42c..1df21c8be 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -8,7 +8,7 @@ import Quickshell.Wayland import Quickshell.Hyprland import Quickshell.Io -Rectangle { +Item { required property var bar readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) readonly property Toplevel activeWindow: ToplevelManager.activeToplevel @@ -48,7 +48,6 @@ Rectangle { Layout.fillHeight: true implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitHeight: 40 - color: "transparent" // Background Rectangle { @@ -176,8 +175,7 @@ Rectangle { } - background: Rectangle { - color: "transparent" // Transparent background + background: Item { implicitWidth: workspaceButtonWidth implicitHeight: workspaceButtonWidth } diff --git a/.config/quickshell/modules/common/Audio.qml b/.config/quickshell/modules/common/Audio.qml new file mode 100644 index 000000000..292efbf43 --- /dev/null +++ b/.config/quickshell/modules/common/Audio.qml @@ -0,0 +1,16 @@ +import QtQuick +import Quickshell +import Quickshell.Services.Pipewire +pragma Singleton + +Singleton { + id: root + + property var sink: Pipewire.defaultAudioSink + property var source: Pipewire.defaultAudioSource + + PwObjectTracker { + objects: [Pipewire.defaultAudioSink, Pipewire.defaultAudioSource] + } + +} diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index ceec43fd5..211e56d8d 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -1,3 +1,5 @@ +//@ pragma UseQApplication +//@ pragma IconTheme OneUI import QtQuick import QtQuick.Controls import QtQuick.Layouts From eff8d1e4c389f33cd73998378ca721a458712bf9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 12 Apr 2025 15:40:40 +0200 Subject: [PATCH 015/824] no more pragma needed --- .config/quickshell/shell.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 211e56d8d..9859b8db3 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -1,5 +1,5 @@ //@ pragma UseQApplication -//@ pragma IconTheme OneUI + import QtQuick import QtQuick.Controls import QtQuick.Layouts From 7b8582124deb8ba7fd1110ee7935c4c72f63df65 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 13 Apr 2025 02:27:58 +0200 Subject: [PATCH 016/824] fix stupid tooltip corner --- .config/Kvantum/Colloid/Colloid.kvconfig | 2 +- .config/Kvantum/Colloid/ColloidDark.kvconfig | 2 +- .config/Kvantum/MaterialAdw/MaterialAdw.kvconfig | 2 +- .config/quickshell/modules/bar/SysTrayItem.qml | 9 +++++---- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.config/Kvantum/Colloid/Colloid.kvconfig b/.config/Kvantum/Colloid/Colloid.kvconfig index 1294ba409..65470510c 100644 --- a/.config/Kvantum/Colloid/Colloid.kvconfig +++ b/.config/Kvantum/Colloid/Colloid.kvconfig @@ -10,7 +10,7 @@ group_toolbar_buttons=false toolbar_item_spacing=0 toolbar_interior_spacing=2 spread_progressbar=true -composite=false +composite=true menu_shadow_depth=6 spread_menuitems=false tooltip_shadow_depth=7 diff --git a/.config/Kvantum/Colloid/ColloidDark.kvconfig b/.config/Kvantum/Colloid/ColloidDark.kvconfig index 61493df5a..d2406c143 100644 --- a/.config/Kvantum/Colloid/ColloidDark.kvconfig +++ b/.config/Kvantum/Colloid/ColloidDark.kvconfig @@ -10,7 +10,7 @@ group_toolbar_buttons=false toolbar_item_spacing=0 toolbar_interior_spacing=2 spread_progressbar=true -composite=false +composite=true menu_shadow_depth=6 spread_menuitems=false tooltip_shadow_depth=7 diff --git a/.config/Kvantum/MaterialAdw/MaterialAdw.kvconfig b/.config/Kvantum/MaterialAdw/MaterialAdw.kvconfig index 85264e0d2..62faf0b55 100644 --- a/.config/Kvantum/MaterialAdw/MaterialAdw.kvconfig +++ b/.config/Kvantum/MaterialAdw/MaterialAdw.kvconfig @@ -10,7 +10,7 @@ group_toolbar_buttons=false toolbar_item_spacing=0 toolbar_interior_spacing=2 spread_progressbar=true -composite=false +composite=true menu_shadow_depth=6 spread_menuitems=false tooltip_shadow_depth=7 diff --git a/.config/quickshell/modules/bar/SysTrayItem.qml b/.config/quickshell/modules/bar/SysTrayItem.qml index 83eb267a5..d40ce778b 100644 --- a/.config/quickshell/modules/bar/SysTrayItem.qml +++ b/.config/quickshell/modules/bar/SysTrayItem.qml @@ -21,8 +21,9 @@ MouseArea { item.activate(); break; case Qt.RightButton: - item.hasMenu && menu.open(); - break; + if (item.hasMenu) { + menu.open(); + } default: console.log("Buttonevent unhandled"); } @@ -35,8 +36,8 @@ MouseArea { anchor.window: bar anchor.rect.x: root.x + bar.width anchor.rect.y: root.y - anchor.rect.height: root.height * 3 - anchor.edges: Edges.Left | Edges.Bottom + anchor.rect.height: root.height + anchor.edges: Edges.Bottom } IconImage { From 28bd79234d52704fbdcde02a0923bfcabb294f47 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 13 Apr 2025 16:37:30 +0200 Subject: [PATCH 017/824] rounding decorations --- .config/hypr/hyprland/rules.conf | 6 +- .config/quickshell/modules/bar/Bar.qml | 249 ++++++++++-------- .config/quickshell/modules/bar/Resources.qml | 4 +- .../quickshell/modules/bar/SysTrayItem.qml | 6 +- .../quickshell/modules/common/Appearance.qml | 9 + .../modules/common/widgets/RoundCorner.qml | 64 +++++ .../modules/screenCorners/ScreenCorners.qml | 66 +++++ .../modules/sidebarRight/SidebarRight.qml | 46 ++++ .config/quickshell/shell.qml | 12 +- 9 files changed, 343 insertions(+), 119 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/RoundCorner.qml create mode 100644 .config/quickshell/modules/screenCorners/ScreenCorners.qml create mode 100644 .config/quickshell/modules/sidebarRight/SidebarRight.qml diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index d67554148..701d5ebd6 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -69,8 +69,6 @@ layerrule = noanim, anyrun layerrule = noanim, indicator.* layerrule = noanim, osk layerrule = noanim, hyprpicker -layerrule = blur, shell:* -layerrule = ignorealpha 0.6, shell:* layerrule = noanim, noanim layerrule = blur, gtk-layer-shell @@ -105,3 +103,7 @@ layerrule = blur, indicator.* layerrule = ignorealpha 0.6, indicator.* layerrule = blur, osk[0-9]* layerrule = ignorealpha 0.6, osk[0-9]* + +# Quickshell +layerrule = animation fade, quickshell:screenCorners + diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index a6d3b1ebb..e84697e6e 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -8,8 +8,8 @@ import Quickshell Scope { id: bar - readonly property int barHeight: 40 - readonly property int sideCenterModuleWidth: 360 + readonly property int barHeight: Appearance.sizes.barHeight + readonly property int barCenterSideModuleWidth: Appearance.sizes.barCenterSideModuleWidth Variants { model: Quickshell.screens @@ -20,112 +20,12 @@ Scope { property var modelData screen: modelData - height: barHeight - color: Appearance.colors.colLayer0 - - // Left section - RowLayout { - anchors.left: parent.left - implicitHeight: barHeight - - ActiveWindow { - bar: barRoot - } - - // Scroll to change brightness - WheelHandler { - onWheel: (event) => { - if (event.angleDelta.y < 0) - Brightness.value = -1; - else if (event.angleDelta.y > 0) - Brightness.value = 1; - } - acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad - } - - } - - // Middle section - RowLayout { - anchors.centerIn: parent - spacing: 8 - - RowLayout { - Layout.preferredWidth: sideCenterModuleWidth - spacing: 4 - Layout.fillHeight: true - implicitWidth: 350 - - Resources { - } - - Media { - Layout.fillWidth: true - } - - } - - RowLayout { - Layout.fillWidth: true - Layout.fillHeight: true - spacing: 4 - - Workspaces { - bar: barRoot - } - - } - - RowLayout { - Layout.preferredWidth: sideCenterModuleWidth - Layout.fillHeight: true - spacing: 4 - - ClockWidget { - Layout.alignment: Qt.AlignVCenter - Layout.fillWidth: true - } - - UtilButtons { - Layout.alignment: Qt.AlignVCenter - } - - Battery { - Layout.alignment: Qt.AlignVCenter - } - - } - - } - - // Right section - RowLayout { - anchors.right: parent.right - implicitHeight: barHeight - spacing: 20 - - SysTray { - bar: barRoot - } - - Item { // TODO make this wifi & bluetooth - Layout.leftMargin: Appearance.rounding.screenRounding - } - - // Scroll to change volume - WheelHandler { - onWheel: (event) => { - const currentVolume = Audio.sink?.audio.volume; - const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2; - if (event.angleDelta.y < 0) - Audio.sink.audio.volume -= step; - else if (event.angleDelta.y > 0) - Audio.sink.audio.volume += step; - } - acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad - } - + height: barHeight + Appearance.rounding.screenRounding + exclusiveZone: barHeight + mask: Region { + item: barContent } + color: "transparent" anchors { top: true @@ -133,6 +33,141 @@ Scope { right: true } + Rectangle { + id: barContent + anchors.right: parent.right + anchors.left: parent.left + anchors.top: parent.top + color: Appearance.colors.colLayer0 + height: barHeight + // Left section + RowLayout { + anchors.left: parent.left + implicitHeight: barHeight + + ActiveWindow { + bar: barRoot + } + + // Scroll to change brightness + WheelHandler { + onWheel: (event) => { + if (event.angleDelta.y < 0) + Brightness.value = -1; + else if (event.angleDelta.y > 0) + Brightness.value = 1; + } + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } + + } + + // Middle section + RowLayout { + anchors.centerIn: parent + spacing: 8 + + RowLayout { + Layout.preferredWidth: barCenterSideModuleWidth + spacing: 4 + Layout.fillHeight: true + implicitWidth: 350 + + Resources { + } + + Media { + Layout.fillWidth: true + } + + } + + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 4 + + Workspaces { + bar: barRoot + } + + } + + RowLayout { + Layout.preferredWidth: barCenterSideModuleWidth + Layout.fillHeight: true + spacing: 4 + + ClockWidget { + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + } + + UtilButtons { + Layout.alignment: Qt.AlignVCenter + } + + Battery { + Layout.alignment: Qt.AlignVCenter + } + + } + + } + + // Right section + RowLayout { + anchors.right: parent.right + implicitHeight: barHeight + spacing: 20 + + SysTray { + bar: barRoot + } + + Item { // TODO make this wifi & bluetooth + Layout.leftMargin: Appearance.rounding.screenRounding + } + + // Scroll to change volume + WheelHandler { + onWheel: (event) => { + const currentVolume = Audio.sink?.audio.volume; + const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2; + if (event.angleDelta.y < 0) + Audio.sink.audio.volume -= step; + else if (event.angleDelta.y > 0) + Audio.sink.audio.volume += step; + } + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } + + } + } + + // Round decorators + Item { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: Appearance.rounding.screenRounding + + RoundCorner { + anchors.top: parent.top + anchors.left: parent.left + size: Appearance.rounding.screenRounding + corner: cornerEnum.topLeft + color: Appearance.colors.colLayer0 + } + RoundCorner { + anchors.top: parent.top + anchors.right: parent.right + size: Appearance.rounding.screenRounding + corner: cornerEnum.topRight + color: Appearance.colors.colLayer0 + } + } + } } diff --git a/.config/quickshell/modules/bar/Resources.qml b/.config/quickshell/modules/bar/Resources.qml index 4528f8b48..0a7f28f6e 100644 --- a/.config/quickshell/modules/bar/Resources.qml +++ b/.config/quickshell/modules/bar/Resources.qml @@ -28,14 +28,14 @@ Rectangle { Resource { iconName: "swap_horiz" percentage: ResourceUsage.swapUsedPercentage - shown: ConfigOptions.bar.resources.alwaysShowSwap || (MprisController.activePlayer == null) + shown: ConfigOptions.bar.resources.alwaysShowSwap || (MprisController.activePlayer?.trackTitle == null) Layout.leftMargin: shown ? 4 : 0 } Resource { iconName: "settings_slow_motion" percentage: ResourceUsage.cpuUsage - shown: ConfigOptions.bar.resources.alwaysShowCpu || (MprisController.activePlayer == null) + shown: ConfigOptions.bar.resources.alwaysShowCpu || !(MprisController.activePlayer?.trackTitle?.length > 0) Layout.leftMargin: shown ? 4 : 0 } diff --git a/.config/quickshell/modules/bar/SysTrayItem.qml b/.config/quickshell/modules/bar/SysTrayItem.qml index d40ce778b..c0c2e6bad 100644 --- a/.config/quickshell/modules/bar/SysTrayItem.qml +++ b/.config/quickshell/modules/bar/SysTrayItem.qml @@ -21,11 +21,9 @@ MouseArea { item.activate(); break; case Qt.RightButton: - if (item.hasMenu) { + if (item.hasMenu) menu.open(); - } - default: - console.log("Buttonevent unhandled"); + } } diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index bfff14963..e65a54193 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -8,6 +8,7 @@ Singleton { property QtObject colors property QtObject rounding property QtObject font + property QtObject sizes function mix(color1, color2, percentage) { var c1 = Qt.color(color1); @@ -152,4 +153,12 @@ Singleton { } } + sizes: QtObject { + property int barHeight: 40 + property int barCenterSideModuleWidth: 360 + property int sidebarWidth: 450 + property int hyprlandGapsOut: 5 + property int elevationMargin: 7 + } + } diff --git a/.config/quickshell/modules/common/widgets/RoundCorner.qml b/.config/quickshell/modules/common/widgets/RoundCorner.qml new file mode 100644 index 000000000..6a9020bc0 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/RoundCorner.qml @@ -0,0 +1,64 @@ +import QtQuick 2.9 + +Item { + id: root + + property int size: 25 + property color color: "#000000" + + property QtObject cornerEnum: QtObject { + property int topLeft: 0 + property int topRight: 1 + property int bottomLeft: 2 + property int bottomRight: 3 + } + + property int corner: cornerEnum.topLeft // Default to TopLeft + + width: size + height: size + + Canvas { + id: canvas + + anchors.fill: parent + antialiasing: true + + onPaint: { + var ctx = getContext("2d"); + var r = root.size; + + ctx.beginPath(); + switch (root.corner) { + case cornerEnum.topLeft: + ctx.arc(r, r, r, Math.PI, 3 * Math.PI / 2); + ctx.lineTo(0, 0); + break; + case cornerEnum.topRight: + ctx.arc(0, r, r, 3 * Math.PI / 2, 2 * Math.PI); + ctx.lineTo(r, 0); + break; + case cornerEnum.bottomLeft: + ctx.arc(r, 0, r, Math.PI / 2, Math.PI); + ctx.lineTo(0, r); + break; + case cornerEnum.bottomRight: + ctx.arc(0, 0, r, 0, Math.PI / 2); + ctx.lineTo(r, r); + break; + } + ctx.closePath(); + ctx.fillStyle = root.color; + ctx.fill(); + } + } + + Behavior on size { + NumberAnimation { + duration: root.animationDuration + easing.type: Easing.OutCubic + } + + } + +} diff --git a/.config/quickshell/modules/screenCorners/ScreenCorners.qml b/.config/quickshell/modules/screenCorners/ScreenCorners.qml new file mode 100644 index 000000000..49b2560ef --- /dev/null +++ b/.config/quickshell/modules/screenCorners/ScreenCorners.qml @@ -0,0 +1,66 @@ +import "../common" +import "../common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland + +Scope { + id: screenCorners + readonly property Toplevel activeWindow: ToplevelManager.activeToplevel + + Variants { + model: Quickshell.screens + + PanelWindow { + id: barRoot + visible: !activeWindow?.fullscreen + + property var modelData + + screen: modelData + exclusionMode: ExclusionMode.Ignore + mask: Region { + item: null + } + WlrLayershell.namespace: "quickshell:screenCorners" + color: "transparent" + + anchors { + top: true + left: true + right: true + bottom: true + } + + RoundCorner { + anchors.top: parent.top + anchors.left: parent.left + size: Appearance.rounding.screenRounding + corner: cornerEnum.topLeft + } + RoundCorner { + anchors.top: parent.top + anchors.right: parent.right + size: Appearance.rounding.screenRounding + corner: cornerEnum.topRight + } + RoundCorner { + anchors.bottom: parent.bottom + anchors.left: parent.left + size: Appearance.rounding.screenRounding + corner: cornerEnum.bottomLeft + } + RoundCorner { + anchors.bottom: parent.bottom + anchors.right: parent.right + size: Appearance.rounding.screenRounding + corner: cornerEnum.bottomRight + } + + } + + } + +} diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml new file mode 100644 index 000000000..fcb296431 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -0,0 +1,46 @@ +import "../common" +import "../common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell + +Scope { + id: bar + + readonly property int sidebarWidth: Appearance.sizes.sidebarWidth + + Variants { + model: Quickshell.screens + + PanelWindow { + id: barRoot + + property var modelData + + screen: modelData + exclusiveZone: 0 + width: sidebarWidth + color: "transparent" + + anchors { + top: true + right: true + bottom: true + } + + // Background + Rectangle { + id: sidebarRightBackground + anchors.centerIn: parent + width: parent.width - Appearance.sizes.hyprlandGapsOut * 2 + height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 + color: Appearance.colors.colLayer0 + radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 + } + + } + + } + +} diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 9859b8db3..869923f3a 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -1,16 +1,20 @@ //@ pragma UseQApplication +import "./modules/bar/" +import "./modules/sidebarRight/" +import "./modules/screenCorners/" import QtQuick import QtQuick.Controls import QtQuick.Layouts -import Quickshell -import "./modules/bar" - import QtQuick.Window +import Quickshell ShellRoot { Bar { } - + // SidebarRight { + // } + ScreenCorners { + } } From 248ff831abd123dec419f676860d85ea523fd9a6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 13 Apr 2025 16:42:55 +0200 Subject: [PATCH 018/824] make screen corner configurable --- .config/quickshell/modules/common/ConfigOptions.qml | 5 +++++ .config/quickshell/modules/screenCorners/ScreenCorners.qml | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index d86fb96a8..93fa413a9 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -3,6 +3,10 @@ import Quickshell pragma Singleton Singleton { + property QtObject appearance: QtObject { + property int fakeScreenRounding: 1 // 0: None | 1: Always | 2: When not fullscreen + } + property QtObject bar: QtObject { property int workspacesShown: 10 property int batteryLowThreshold: 20 @@ -11,6 +15,7 @@ Singleton { property bool alwaysShowCpu: false } } + property QtObject resources: QtObject { property int updateInterval: 3000 } diff --git a/.config/quickshell/modules/screenCorners/ScreenCorners.qml b/.config/quickshell/modules/screenCorners/ScreenCorners.qml index 49b2560ef..95764848a 100644 --- a/.config/quickshell/modules/screenCorners/ScreenCorners.qml +++ b/.config/quickshell/modules/screenCorners/ScreenCorners.qml @@ -15,7 +15,7 @@ Scope { PanelWindow { id: barRoot - visible: !activeWindow?.fullscreen + visible: (ConfigOptions.appearance.fakeScreenRounding === 1 || (ConfigOptions.appearance.fakeScreenRounding === 2 && !activeWindow?.fullscreen)) property var modelData @@ -25,6 +25,7 @@ Scope { item: null } WlrLayershell.namespace: "quickshell:screenCorners" + WlrLayershell.layer: WlrLayer.Overlay color: "transparent" anchors { From a139c29a2be0cd2f425114a3e53bbf53e76d602c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 13 Apr 2025 17:07:32 +0200 Subject: [PATCH 019/824] toggleable sidebar --- .config/hypr/hyprland/keybinds.conf | 7 ++--- .../modules/sidebarRight/SidebarRight.qml | 26 ++++++++++++++++++- .config/quickshell/shell.qml | 5 ++-- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 5d3b89ece..bfa6578b6 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -59,6 +59,7 @@ bind = Super, Down, movefocus, d # [hidden] bind = Super, BracketLeft, movefocus, l # [hidden] bind = Super, BracketRight, movefocus, r # [hidden] bindm = Super, mouse:272, movewindow +bindm = Super, mouse:274, movewindow bindm = Super, mouse:273, resizewindow bind = Super, Q, killactive, bind = Super+Shift+Alt, Q, exec, hyprctl kill # Pick and kill a window @@ -163,9 +164,9 @@ bindir = Super, Super_L, exec, agsv1 -t 'overview' # Toggle overview/launcher bind = Super, Tab, exec, agsv1 -t 'overview' # [hidden] bind = Super, Slash, exec, for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "cheatsheet""$i"; done # Show cheatsheet bind = Super, B, exec, agsv1 -t 'sideleft' # Toggle left sidebar -bind = Super, A, exec, agsv1 -t 'sideleft' # [hidden] -bind = Super, O, exec, agsv1 -t 'sideleft' # [hidden] -bind = Super, N, exec, agsv1 -t 'sideright' # Toggle right sidebar +bind = Super, A, exec, qs ipc call sidebarLeft toggle || agsv1 -t 'sideleft' # [hidden] +bind = Super, O, exec, qs ipc call sidebarLeft toggle || agsv1 -t 'sideleft' # [hidden] +bind = Super, N, exec, qs ipc call sidebarRight toggle || agsv1 -t 'sideright' # Toggle right sidebar bind = Super, M, exec, agsv1 run-js 'openMusicControls.value = (!mpris.getPlayer() ? false : !openMusicControls.value);' # Toggle music controls bind = Super, Comma, exec, agsv1 run-js 'openColorScheme.value = true; Utils.timeout(2000, () => openColorScheme.value = false);' # View color scheme and options bind = Super, K, exec, for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "osk""$i"; done # Toggle on-screen keyboard diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index fcb296431..8b6b6494e 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -3,7 +3,9 @@ import "../common/widgets" import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Quickshell.Io import Quickshell +import Quickshell.Wayland Scope { id: bar @@ -14,13 +16,15 @@ Scope { model: Quickshell.screens PanelWindow { - id: barRoot + id: sidebarRoot + visible: false property var modelData screen: modelData exclusiveZone: 0 width: sidebarWidth + WlrLayershell.namespace: "quickshell:sidebarRight" color: "transparent" anchors { @@ -32,6 +36,7 @@ Scope { // Background Rectangle { id: sidebarRightBackground + anchors.centerIn: parent width: parent.width - Appearance.sizes.hyprlandGapsOut * 2 height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 @@ -39,6 +44,25 @@ Scope { radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 } + // Shadow + // DropShadow { + // anchors.fill: sideRightBackground + // horizontalOffset: 0 + // verticalOffset: 2 + // radius: 3 + // samples: 17 + // color: Appearance.m3colors.m3shadow + // source: sideRightBackground + // } + + IpcHandler { + target: "sidebarRight" + + function toggle(): void { + sidebarRoot.visible = !sidebarRoot.visible + } + } + } } diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 869923f3a..5f822082e 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -13,8 +13,9 @@ ShellRoot { Bar { } - // SidebarRight { - // } + SidebarRight { + } + ScreenCorners { } } From 9f0deefec4a6e5cc28b63cd49d66e611ffca849d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 14 Apr 2025 12:18:33 +0200 Subject: [PATCH 020/824] per-monitor handling of sidebar opening --- .../quickshell/modules/common/Appearance.qml | 6 +++ .../quickshell/modules/common/DateTime.qml | 31 +++++++++++ .../modules/common/ResourceUsage.qml | 2 +- .../quickshell/modules/common/SystemInfo.qml | 51 +++++++++++++++++++ .../modules/sidebarRight/SidebarRight.qml | 40 +++++++++------ 5 files changed, 113 insertions(+), 17 deletions(-) create mode 100644 .config/quickshell/modules/common/SystemInfo.qml diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index e65a54193..6190a3ac8 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -16,6 +16,12 @@ Singleton { return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a); } + // Transparentize + function transparentize(color, percentage) { + var c = Qt.color(color); + return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage)); + } + m3colors: QtObject { property bool darkmode: false property bool transparent: false diff --git a/.config/quickshell/modules/common/DateTime.qml b/.config/quickshell/modules/common/DateTime.qml index ddeec093e..bbcedfb52 100644 --- a/.config/quickshell/modules/common/DateTime.qml +++ b/.config/quickshell/modules/common/DateTime.qml @@ -6,6 +6,7 @@ pragma Singleton Singleton { property string time: Qt.formatDateTime(clock.date, "hh:mm") property string date: Qt.formatDateTime(clock.date, "dddd, dd/MM") + property string uptime: "0h, 0m" SystemClock { id: clock @@ -13,4 +14,34 @@ Singleton { precision: SystemClock.Minutes } + Timer { + interval: 10 + running: true + repeat: true + onTriggered: { + fileUptime.reload() + const textUptime = fileUptime.text() + const uptimeSeconds = Number(textUptime.split(" ")[0] ?? 0) + + // Convert seconds to days, hours, and minutes + const days = Math.floor(uptimeSeconds / 86400) + const hours = Math.floor((uptimeSeconds % 86400) / 3600) + const minutes = Math.floor((uptimeSeconds % 3600) / 60) + + // Build the formatted uptime string + let formatted = "" + if (days > 0) formatted += `${days}d` + if (hours > 0) formatted += `${formatted ? ", " : ""}${hours}h` + if (minutes > 0 || !formatted) formatted += `${formatted ? ", " : ""}${minutes}m` + uptime = formatted + interval = ConfigOptions.resources.updateInterval; + } + } + + FileView { + id: fileUptime + + path: "/proc/uptime" + } + } diff --git a/.config/quickshell/modules/common/ResourceUsage.qml b/.config/quickshell/modules/common/ResourceUsage.qml index 898abbb34..6326baceb 100644 --- a/.config/quickshell/modules/common/ResourceUsage.qml +++ b/.config/quickshell/modules/common/ResourceUsage.qml @@ -16,7 +16,7 @@ Singleton { property var previousCpuStats Timer { - interval: 50 + interval: 10 running: true repeat: true onTriggered: { diff --git a/.config/quickshell/modules/common/SystemInfo.qml b/.config/quickshell/modules/common/SystemInfo.qml new file mode 100644 index 000000000..3db1237c8 --- /dev/null +++ b/.config/quickshell/modules/common/SystemInfo.qml @@ -0,0 +1,51 @@ +import QtQuick +import Quickshell +import Quickshell.Io +pragma Singleton + +Singleton { + property string distroName: "Unknown" + property string distroId: "unknown" + property string distroIcon: "linux-symbolic" + + Timer { + interval: 1 + running: true + repeat: false + onTriggered: { + fileOsRelease.reload() + const textOsRelease = fileOsRelease.text() + + // Extract the friendly name (PRETTY_NAME field, fallback to NAME) + const prettyNameMatch = textOsRelease.match(/^PRETTY_NAME="(.+?)"/m) + const nameMatch = textOsRelease.match(/^NAME="(.+?)"/m) + distroName = prettyNameMatch ? prettyNameMatch[1] : (nameMatch ? nameMatch[1].replace(/Linux/i, "").trim() : "Unknown") + + // Extract the ID (LOGO field, fallback to "unknown") + const logoMatch = textOsRelease.match(/^LOGO=(.+)$/m) + distroId = logoMatch ? logoMatch[1].replace(/"/g, "") : "unknown" + + // Update the distroIcon property based on distroId + switch (distroId) { + case "arch": distroIcon = "arch-symbolic"; break; + case "endeavouros": distroIcon = "endeavouros-symbolic"; break; + case "cachyos": distroIcon = "cachyos-symbolic"; break; + case "nixos": distroIcon = "nixos-symbolic"; break; + case "fedora": distroIcon = "fedora-symbolic"; break; + case "linuxmint": + case "ubuntu": + case "zorin": + case "popos": distroIcon = "ubuntu-symbolic"; break; + case "debian": + case "raspbian": + case "kali": distroIcon = "debian-symbolic"; break; + default: distroIcon = "linux-symbolic"; break; + } + } + } + + FileView { + id: fileOsRelease + path: "/etc/os-release" + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 8b6b6494e..f6643c893 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -6,6 +6,8 @@ import QtQuick.Layouts import Quickshell.Io import Quickshell import Quickshell.Wayland +import Quickshell.Hyprland +import Qt5Compat.GraphicalEffects Scope { id: bar @@ -13,6 +15,7 @@ Scope { readonly property int sidebarWidth: Appearance.sizes.sidebarWidth Variants { + id: sidebarVariants model: Quickshell.screens PanelWindow { @@ -45,26 +48,31 @@ Scope { } // Shadow - // DropShadow { - // anchors.fill: sideRightBackground - // horizontalOffset: 0 - // verticalOffset: 2 - // radius: 3 - // samples: 17 - // color: Appearance.m3colors.m3shadow - // source: sideRightBackground - // } - - IpcHandler { - target: "sidebarRight" - - function toggle(): void { - sidebarRoot.visible = !sidebarRoot.visible - } + DropShadow { + anchors.fill: sidebarRightBackground + horizontalOffset: 0 + verticalOffset: 2 + radius: Appearance.sizes.elevationMargin + samples: Appearance.sizes.elevationMargin * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs + color: Appearance.transparentize(Appearance.m3colors.m3shadow, 0.55) + source: sidebarRightBackground } } } + IpcHandler { + target: "sidebarRight" + + function toggle(): void { + for (let i = 0; i < sidebarVariants.instances.length; i++) { + let panelWindow = sidebarVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = !panelWindow.visible; + } + } + } + } + } From ab04d1e10d2e89f4566fb7577c6afcc4f889ca76 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 14 Apr 2025 12:19:02 +0200 Subject: [PATCH 021/824] add reload popup --- .config/quickshell/ReloadPopup.qml | 142 +++++++++++++++++++++++++++++ .config/quickshell/shell.qml | 6 +- 2 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 .config/quickshell/ReloadPopup.qml diff --git a/.config/quickshell/ReloadPopup.qml b/.config/quickshell/ReloadPopup.qml new file mode 100644 index 000000000..ec0a91538 --- /dev/null +++ b/.config/quickshell/ReloadPopup.qml @@ -0,0 +1,142 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Qt5Compat.GraphicalEffects + +Scope { + id: root + property bool failed; + property string errorString; + + // Connect to the Quickshell global to listen for the reload signals. + Connections { + target: Quickshell + + function onReloadCompleted() { + root.failed = false; + popupLoader.loading = true; + } + + function onReloadFailed(error: string) { + // Close any existing popup before making a new one. + popupLoader.active = false; + + root.failed = true; + root.errorString = error; + popupLoader.loading = true; + } + } + + // Keep the popup in a loader because it isn't needed most of the timeand will take up + // memory that could be used for something else. + LazyLoader { + id: popupLoader + + PanelWindow { + id: popup + + anchors.top: true + margins.top: 0 + + width: rect.width + shadow.radius * 2 + height: rect.height + shadow.radius * 2 + + // color blending is a bit odd as detailed in the type reference. + color: "transparent" + + Rectangle { + id: rect + anchors.centerIn: parent + color: failed ? "#ffe99195" : "#ffD1E8D5" + + implicitHeight: layout.implicitHeight + 30 + implicitWidth: layout.implicitWidth + 30 + radius: 12 + + // Fills the whole area of the rectangle, making any clicks go to it, + // which dismiss the popup. + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: popupLoader.active = false + + // makes the mouse area track mouse hovering, so the hide animation + // can be paused when hovering. + hoverEnabled: true + } + + ColumnLayout { + id: layout + anchors { + top: parent.top + topMargin: 10 + horizontalCenter: parent.horizontalCenter + } + + Text { + renderType: Text.NativeRendering + font.family: "Rubik" + font.pointSize: 14 + // color: Appearance.colors.colOnBackground + text: root.failed ? "Reload failed" : "Reload completed" + color: failed ? "#ff93000A" : "#ff0C1F13" + } + + Text { + renderType: Text.NativeRendering + font.family: "JetBrains Mono NF" + font.pointSize: 11 + text: root.errorString + color: failed ? "#ff93000A" : "#ff0C1F13" + // When visible is false, it also takes up no space. + visible: root.errorString != "" + } + } + + // A progress bar on the bottom of the screen, showing how long until the + // popup is removed. + Rectangle { + id: bar + color: failed ? "#ff93000A" : "#ff0C1F13" + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.margins: 10 + height: 5 + radius: 9999 + + PropertyAnimation { + id: anim + target: bar + property: "width" + from: rect.width + to: 0 + duration: failed ? 10000 : 800 + onFinished: popupLoader.active = false + + // Pause the animation when the mouse is hovering over the popup, + // so it stays onscreen while reading. This updates reactively + // when the mouse moves on and off the popup. + paused: mouseArea.containsMouse + } + } + + // We could set `running: true` inside the animation, but the width of the + // rectangle might not be calculated yet, due to the layout. + // In the `Component.onCompleted` event handler, all of the component's + // properties and children have been initialized. + Component.onCompleted: anim.start() + } + + DropShadow { + id: shadow + anchors.fill: rect + horizontalOffset: 0 + verticalOffset: 2 + radius: 6 + samples: radius * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs + color: "#44000000" + source: rect + } + } + } +} diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 5f822082e..d253053d6 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -1,8 +1,8 @@ //@ pragma UseQApplication import "./modules/bar/" -import "./modules/sidebarRight/" import "./modules/screenCorners/" +import "./modules/sidebarRight/" import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -18,4 +18,8 @@ ShellRoot { ScreenCorners { } + + ReloadPopup { + } + } From ab81e79eece2f95941776c34b9322962f5e53187 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 14 Apr 2025 12:59:52 +0200 Subject: [PATCH 022/824] sidebar esc to close --- .config/quickshell/ReloadPopup.qml | 3 +- .../quickshell/modules/bar/ActiveWindow.qml | 2 +- .config/quickshell/modules/bar/Bar.qml | 40 ++++++++++++++++--- .../quickshell/modules/common/Appearance.qml | 1 + .../modules/sidebarRight/SidebarRight.qml | 15 +++++++ 5 files changed, 54 insertions(+), 7 deletions(-) diff --git a/.config/quickshell/ReloadPopup.qml b/.config/quickshell/ReloadPopup.qml index ec0a91538..c3f0aab95 100644 --- a/.config/quickshell/ReloadPopup.qml +++ b/.config/quickshell/ReloadPopup.qml @@ -67,6 +67,7 @@ Scope { ColumnLayout { id: layout + spacing: 10 anchors { top: parent.top topMargin: 10 @@ -78,7 +79,7 @@ Scope { font.family: "Rubik" font.pointSize: 14 // color: Appearance.colors.colOnBackground - text: root.failed ? "Reload failed" : "Reload completed" + text: root.failed ? "Quickshell: Reload failed" : "Quickshell reloaded" color: failed ? "#ff93000A" : "#ff0C1F13" } diff --git a/.config/quickshell/modules/bar/ActiveWindow.qml b/.config/quickshell/modules/bar/ActiveWindow.qml index e2ab07c91..39b34fa15 100644 --- a/.config/quickshell/modules/bar/ActiveWindow.qml +++ b/.config/quickshell/modules/bar/ActiveWindow.qml @@ -9,7 +9,7 @@ Item { required property var bar readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) readonly property Toplevel activeWindow: ToplevelManager.activeToplevel - property int preferredWidth: 400 + property int preferredWidth: Appearance.sizes.barPreferredSideSectionWidth height: parent.height width: colLayout.width diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index e84697e6e..b31ac371a 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -4,6 +4,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell +import Quickshell.Io Scope { id: bar @@ -11,6 +12,15 @@ Scope { readonly property int barHeight: Appearance.sizes.barHeight readonly property int barCenterSideModuleWidth: Appearance.sizes.barCenterSideModuleWidth + Process { + id: toggleSidebarRight + command: ["qs", "ipc", "call", "sidebarRight", "toggle"] + } + Process { + id: toggleSidebarLeft + command: ["qs", "ipc", "call", "sidebarLeft", "toggle"] + } + Variants { model: Quickshell.screens @@ -42,6 +52,7 @@ Scope { height: barHeight // Left section RowLayout { + id: leftSection anchors.left: parent.left implicitHeight: barHeight @@ -64,6 +75,7 @@ Scope { // Middle section RowLayout { + id: middleSection anchors.centerIn: parent spacing: 8 @@ -117,18 +129,37 @@ Scope { // Right section RowLayout { + id: rightSection anchors.right: parent.right implicitHeight: barHeight + width: Appearance.sizes.barPreferredSideSectionWidth spacing: 20 - - SysTray { - bar: barRoot - } + layoutDirection: Qt.RightToLeft Item { // TODO make this wifi & bluetooth Layout.leftMargin: Appearance.rounding.screenRounding + Layout.fillWidth: false } + SysTray { + bar: barRoot + Layout.fillWidth: false + } + + Item { + Layout.fillWidth: true + } + + + } + MouseArea { + anchors.fill: rightSection + acceptedButtons: Qt.LeftButton + onPressed: (event) => { + if (event.button === Qt.LeftButton) { + toggleSidebarRight.running = true + } + } // Scroll to change volume WheelHandler { onWheel: (event) => { @@ -141,7 +172,6 @@ Scope { } acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad } - } } diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 6190a3ac8..b290c2a95 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -162,6 +162,7 @@ Singleton { sizes: QtObject { property int barHeight: 40 property int barCenterSideModuleWidth: 360 + property int barPreferredSideSectionWidth: 400 property int sidebarWidth: 450 property int hyprlandGapsOut: 5 property int elevationMargin: 7 diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index f6643c893..b26d59eed 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -21,6 +21,7 @@ Scope { PanelWindow { id: sidebarRoot visible: false + focusable: true property var modelData @@ -36,6 +37,12 @@ Scope { bottom: true } + HyprlandFocusGrab { + active: sidebarRoot.visible + id: grab + windows: [ sidebarRoot ] + } + // Background Rectangle { id: sidebarRightBackground @@ -45,6 +52,14 @@ Scope { height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 color: Appearance.colors.colLayer0 radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 + + focus: true + Keys.onPressed: { + if (event.key === Qt.Key_Escape) { + sidebarRoot.visible = false; + event.accepted = true; // Prevent further propagation of the event + } + } } // Shadow From c27366900389886df50f79b40a645ca126adf11c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 14 Apr 2025 23:35:40 +0200 Subject: [PATCH 023/824] sidebar progress --- .config/quickshell/ReloadPopup.qml | 17 +- .../assets/icons/ai-openai-symbolic.svg | 1 + .../quickshell/assets/icons/arch-symbolic.svg | 113 +++++++ .../assets/icons/cachyos-symbolic.svg | 318 ++++++++++++++++++ .../assets/icons/cloudflare-dns-symbolic.svg | 10 + .../assets/icons/crosshair-symbolic.svg | 65 ++++ .../assets/icons/debian-symbolic.svg | 91 +++++ .../assets/icons/deepseek-symbolic.svg | 47 +++ .../assets/icons/desktop-symbolic.svg | 4 + .../assets/icons/endeavouros-symbolic.svg | 96 ++++++ .../assets/icons/fedora-symbolic.svg | 38 +++ .../assets/icons/flatpak-symbolic.svg | 52 +++ .../assets/icons/github-symbolic.svg | 40 +++ .../assets/icons/google-gemini-symbolic.svg | 56 +++ .../assets/icons/linux-symbolic.svg | 113 +++++++ .../assets/icons/microsoft-symbolic.svg | 54 +++ .../assets/icons/nixos-symbolic.svg | 77 +++++ .../assets/icons/ollama-symbolic.svg | 60 ++++ .../assets/icons/openai-symbolic.svg | 38 +++ .../assets/icons/openrouter-symbolic.svg | 39 +++ .../assets/icons/ubuntu-symbolic.svg | 85 +++++ .../assets/images/default_wallpaper.png | Bin 0 -> 67685 bytes .../quickshell/modules/common/Appearance.qml | 4 + .../modules/common/ConfigOptions.qml | 4 + .../modules/common/widgets/CustomIcon.qml | 24 ++ .../sidebarRight/QuickToggleButton.qml | 33 ++ .../modules/sidebarRight/SidebarRight.qml | 93 ++++- .../scripts/wayland-idle-inhibitor.py | 86 +++++ 28 files changed, 1652 insertions(+), 6 deletions(-) create mode 120000 .config/quickshell/assets/icons/ai-openai-symbolic.svg create mode 100644 .config/quickshell/assets/icons/arch-symbolic.svg create mode 100644 .config/quickshell/assets/icons/cachyos-symbolic.svg create mode 100644 .config/quickshell/assets/icons/cloudflare-dns-symbolic.svg create mode 100644 .config/quickshell/assets/icons/crosshair-symbolic.svg create mode 100644 .config/quickshell/assets/icons/debian-symbolic.svg create mode 100644 .config/quickshell/assets/icons/deepseek-symbolic.svg create mode 100644 .config/quickshell/assets/icons/desktop-symbolic.svg create mode 100644 .config/quickshell/assets/icons/endeavouros-symbolic.svg create mode 100644 .config/quickshell/assets/icons/fedora-symbolic.svg create mode 100644 .config/quickshell/assets/icons/flatpak-symbolic.svg create mode 100644 .config/quickshell/assets/icons/github-symbolic.svg create mode 100644 .config/quickshell/assets/icons/google-gemini-symbolic.svg create mode 100644 .config/quickshell/assets/icons/linux-symbolic.svg create mode 100644 .config/quickshell/assets/icons/microsoft-symbolic.svg create mode 100644 .config/quickshell/assets/icons/nixos-symbolic.svg create mode 100644 .config/quickshell/assets/icons/ollama-symbolic.svg create mode 100644 .config/quickshell/assets/icons/openai-symbolic.svg create mode 100644 .config/quickshell/assets/icons/openrouter-symbolic.svg create mode 100644 .config/quickshell/assets/icons/ubuntu-symbolic.svg create mode 100644 .config/quickshell/assets/images/default_wallpaper.png create mode 100644 .config/quickshell/modules/common/widgets/CustomIcon.qml create mode 100644 .config/quickshell/modules/sidebarRight/QuickToggleButton.qml create mode 100755 .config/quickshell/scripts/wayland-idle-inhibitor.py diff --git a/.config/quickshell/ReloadPopup.qml b/.config/quickshell/ReloadPopup.qml index c3f0aab95..eb19cbd91 100644 --- a/.config/quickshell/ReloadPopup.qml +++ b/.config/quickshell/ReloadPopup.qml @@ -97,6 +97,7 @@ Scope { // A progress bar on the bottom of the screen, showing how long until the // popup is removed. Rectangle { + z: 2 id: bar color: failed ? "#ff93000A" : "#ff0C1F13" anchors.bottom: parent.bottom @@ -109,9 +110,9 @@ Scope { id: anim target: bar property: "width" - from: rect.width + from: rect.width - bar.anchors.margins * 2 to: 0 - duration: failed ? 10000 : 800 + duration: failed ? 10000 : 1000 onFinished: popupLoader.active = false // Pause the animation when the mouse is hovering over the popup, @@ -120,6 +121,18 @@ Scope { paused: mouseArea.containsMouse } } + // Its bg + Rectangle { + z: 1 + id: bar_bg + color: failed ? "#30af1b25" : "#4027643e" + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.margins: 10 + height: 5 + radius: 9999 + width: rect.width - bar.anchors.margins * 2 + } // We could set `running: true` inside the animation, but the width of the // rectangle might not be calculated yet, due to the layout. diff --git a/.config/quickshell/assets/icons/ai-openai-symbolic.svg b/.config/quickshell/assets/icons/ai-openai-symbolic.svg new file mode 120000 index 000000000..c9ee0b32f --- /dev/null +++ b/.config/quickshell/assets/icons/ai-openai-symbolic.svg @@ -0,0 +1 @@ +openai-symbolic.svg \ No newline at end of file diff --git a/.config/quickshell/assets/icons/arch-symbolic.svg b/.config/quickshell/assets/icons/arch-symbolic.svg new file mode 100644 index 000000000..7de9094e0 --- /dev/null +++ b/.config/quickshell/assets/icons/arch-symbolic.svg @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.config/quickshell/assets/icons/cachyos-symbolic.svg b/.config/quickshell/assets/icons/cachyos-symbolic.svg new file mode 100644 index 000000000..4a9db19ab --- /dev/null +++ b/.config/quickshell/assets/icons/cachyos-symbolic.svg @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.config/quickshell/assets/icons/cloudflare-dns-symbolic.svg b/.config/quickshell/assets/icons/cloudflare-dns-symbolic.svg new file mode 100644 index 000000000..bd48d3c93 --- /dev/null +++ b/.config/quickshell/assets/icons/cloudflare-dns-symbolic.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.config/quickshell/assets/icons/crosshair-symbolic.svg b/.config/quickshell/assets/icons/crosshair-symbolic.svg new file mode 100644 index 000000000..22967493d --- /dev/null +++ b/.config/quickshell/assets/icons/crosshair-symbolic.svg @@ -0,0 +1,65 @@ + + + + + + + ionicons-v5_logos + + + + ionicons-v5_logos + + + + + + diff --git a/.config/quickshell/assets/icons/debian-symbolic.svg b/.config/quickshell/assets/icons/debian-symbolic.svg new file mode 100644 index 000000000..252f85334 --- /dev/null +++ b/.config/quickshell/assets/icons/debian-symbolic.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/.config/quickshell/assets/icons/deepseek-symbolic.svg b/.config/quickshell/assets/icons/deepseek-symbolic.svg new file mode 100644 index 000000000..029e12663 --- /dev/null +++ b/.config/quickshell/assets/icons/deepseek-symbolic.svg @@ -0,0 +1,47 @@ + + + + + + + + + diff --git a/.config/quickshell/assets/icons/desktop-symbolic.svg b/.config/quickshell/assets/icons/desktop-symbolic.svg new file mode 100644 index 000000000..04f7a3b5e --- /dev/null +++ b/.config/quickshell/assets/icons/desktop-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/.config/quickshell/assets/icons/endeavouros-symbolic.svg b/.config/quickshell/assets/icons/endeavouros-symbolic.svg new file mode 100644 index 000000000..3be4cc406 --- /dev/null +++ b/.config/quickshell/assets/icons/endeavouros-symbolic.svg @@ -0,0 +1,96 @@ + + + + + EndeavourOS Logo + + + + image/svg+xml + + EndeavourOS Logo + + + + + + + + + + + + + + + + + + + diff --git a/.config/quickshell/assets/icons/fedora-symbolic.svg b/.config/quickshell/assets/icons/fedora-symbolic.svg new file mode 100644 index 000000000..1a4e8c873 --- /dev/null +++ b/.config/quickshell/assets/icons/fedora-symbolic.svg @@ -0,0 +1,38 @@ + + + + + + + diff --git a/.config/quickshell/assets/icons/flatpak-symbolic.svg b/.config/quickshell/assets/icons/flatpak-symbolic.svg new file mode 100644 index 000000000..0c2bf6280 --- /dev/null +++ b/.config/quickshell/assets/icons/flatpak-symbolic.svg @@ -0,0 +1,52 @@ + + + + + Flatpak + + + + + Flatpak + + + + diff --git a/.config/quickshell/assets/icons/github-symbolic.svg b/.config/quickshell/assets/icons/github-symbolic.svg new file mode 100644 index 000000000..c1c9f19c4 --- /dev/null +++ b/.config/quickshell/assets/icons/github-symbolic.svg @@ -0,0 +1,40 @@ + + + + + + diff --git a/.config/quickshell/assets/icons/google-gemini-symbolic.svg b/.config/quickshell/assets/icons/google-gemini-symbolic.svg new file mode 100644 index 000000000..9de741be6 --- /dev/null +++ b/.config/quickshell/assets/icons/google-gemini-symbolic.svg @@ -0,0 +1,56 @@ + + + + + + + ionicons-v5_logos + + + + + ionicons-v5_logos + + + + diff --git a/.config/quickshell/assets/icons/linux-symbolic.svg b/.config/quickshell/assets/icons/linux-symbolic.svg new file mode 100644 index 000000000..63f9c7e58 --- /dev/null +++ b/.config/quickshell/assets/icons/linux-symbolic.svg @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.config/quickshell/assets/icons/microsoft-symbolic.svg b/.config/quickshell/assets/icons/microsoft-symbolic.svg new file mode 100644 index 000000000..b90cfc637 --- /dev/null +++ b/.config/quickshell/assets/icons/microsoft-symbolic.svg @@ -0,0 +1,54 @@ + + + + + + + + + diff --git a/.config/quickshell/assets/icons/nixos-symbolic.svg b/.config/quickshell/assets/icons/nixos-symbolic.svg new file mode 100644 index 000000000..b697b0d1a --- /dev/null +++ b/.config/quickshell/assets/icons/nixos-symbolic.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + diff --git a/.config/quickshell/assets/icons/ollama-symbolic.svg b/.config/quickshell/assets/icons/ollama-symbolic.svg new file mode 100644 index 000000000..014548151 --- /dev/null +++ b/.config/quickshell/assets/icons/ollama-symbolic.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + diff --git a/.config/quickshell/assets/icons/openai-symbolic.svg b/.config/quickshell/assets/icons/openai-symbolic.svg new file mode 100644 index 000000000..8ffc912ae --- /dev/null +++ b/.config/quickshell/assets/icons/openai-symbolic.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/.config/quickshell/assets/icons/openrouter-symbolic.svg b/.config/quickshell/assets/icons/openrouter-symbolic.svg new file mode 100644 index 000000000..32aaaf50b --- /dev/null +++ b/.config/quickshell/assets/icons/openrouter-symbolic.svg @@ -0,0 +1,39 @@ + + + + + + diff --git a/.config/quickshell/assets/icons/ubuntu-symbolic.svg b/.config/quickshell/assets/icons/ubuntu-symbolic.svg new file mode 100644 index 000000000..07746c9f6 --- /dev/null +++ b/.config/quickshell/assets/icons/ubuntu-symbolic.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.config/quickshell/assets/images/default_wallpaper.png b/.config/quickshell/assets/images/default_wallpaper.png new file mode 100644 index 0000000000000000000000000000000000000000..77d890c21b201652214d41195a15bb586beac1be GIT binary patch literal 67685 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 { + // sidebarRoot.visible = false + if (!active) sidebarRoot.visible = false + } + } + + Connections { + target: sidebarRoot + function onVisibleChanged() { + delayedGrabTimer.start() + } + } + + Timer { + id: delayedGrabTimer + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + onTriggered: { + grab.active = sidebarRoot.visible + } } // Background @@ -60,6 +81,70 @@ Scope { event.accepted = true; // Prevent further propagation of the event } } + + ColumnLayout { + anchors.centerIn: parent + height: parent.height - sidebarPadding * 2 + width: parent.width - sidebarPadding * 2 + spacing: sidebarPadding + + RowLayout { + Layout.fillHeight: false + spacing: 10 + Layout.margins: 10 + Layout.topMargin: 10 + + CustomIcon { + width: 25 + height: 25 + source: SystemInfo.distroIcon + } + + StyledText { + font.pointSize: Appearance.font.pointSize.normal + color: Appearance.colors.colOnLayer0 + text: `Uptime: ${DateTime.uptime}` + } + + Item { + Layout.fillHeight: true + } + + + } + + Rectangle { + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: false + radius: Appearance.rounding.full + color: Appearance.colors.colLayer1 + implicitWidth: sidebarQuickControlsRow.implicitWidth + 10 + implicitHeight: sidebarQuickControlsRow.implicitHeight + 10 + + + RowLayout { + id: sidebarQuickControlsRow + anchors.fill: parent + anchors.margins: 5 + spacing: 5 + Rectangle { + width: 40 + height: 40 + color: "#77000000" + radius: Appearance.rounding.full + } + + QuickToggleButton { + toggled: false + } + + } + } + + Item { + Layout.fillHeight: true + } + } } // Shadow diff --git a/.config/quickshell/scripts/wayland-idle-inhibitor.py b/.config/quickshell/scripts/wayland-idle-inhibitor.py new file mode 100755 index 000000000..9bdaabb04 --- /dev/null +++ b/.config/quickshell/scripts/wayland-idle-inhibitor.py @@ -0,0 +1,86 @@ +#!/usr/bin/env -S\_/bin/sh\_-xc\_"source\_\$(eval\_echo\_\$ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate&&exec\_python\_-E\_"\$0"\_"\$@"" + +# From https://github.com/stwa/wayland-idle-inhibitor +# License: WTFPL Version 2 + +import sys +from dataclasses import dataclass +from signal import SIGINT, SIGTERM, signal +from threading import Event +import setproctitle + +from pywayland.client.display import Display +from pywayland.protocol.idle_inhibit_unstable_v1.zwp_idle_inhibit_manager_v1 import ( + ZwpIdleInhibitManagerV1, +) +from pywayland.protocol.wayland.wl_compositor import WlCompositor +from pywayland.protocol.wayland.wl_registry import WlRegistryProxy +from pywayland.protocol.wayland.wl_surface import WlSurface + + +@dataclass +class GlobalRegistry: + surface: WlSurface | None = None + inhibit_manager: ZwpIdleInhibitManagerV1 | None = None + + +def handle_registry_global( + wl_registry: WlRegistryProxy, id_num: int, iface_name: str, version: int +) -> None: + global_registry: GlobalRegistry = wl_registry.user_data or GlobalRegistry() + + if iface_name == "wl_compositor": + compositor = wl_registry.bind(id_num, WlCompositor, version) + global_registry.surface = compositor.create_surface() # type: ignore + elif iface_name == "zwp_idle_inhibit_manager_v1": + global_registry.inhibit_manager = wl_registry.bind( + id_num, ZwpIdleInhibitManagerV1, version + ) + + +def main() -> None: + done = Event() + signal(SIGINT, lambda _, __: done.set()) + signal(SIGTERM, lambda _, __: done.set()) + + global_registry = GlobalRegistry() + + display = Display() + display.connect() + + registry = display.get_registry() # type: ignore + registry.user_data = global_registry + registry.dispatcher["global"] = handle_registry_global + + def shutdown() -> None: + display.dispatch() + display.roundtrip() + display.disconnect() + + display.dispatch() + display.roundtrip() + + if global_registry.surface is None or global_registry.inhibit_manager is None: + print("Wayland seems not to support idle_inhibit_unstable_v1 protocol.") + shutdown() + sys.exit(1) + + inhibitor = global_registry.inhibit_manager.create_inhibitor( # type: ignore + global_registry.surface + ) + + display.dispatch() + display.roundtrip() + + print("Inhibiting idle...") + done.wait() + print("Shutting down...") + + inhibitor.destroy() + + shutdown() + + +if __name__ == "__main__": + setproctitle.setproctitle("wayland-idle-inhibitor.py") + main() From ff8cee9ddecb1f8921fdfcf6366e306df0ca4997 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 15 Apr 2025 08:58:08 +0200 Subject: [PATCH 024/824] cleaner import; sidebar quick toggles --- .../quickshell/modules/bar/ActiveWindow.qml | 4 +- .config/quickshell/modules/bar/Bar.qml | 4 +- .config/quickshell/modules/bar/Battery.qml | 4 +- .../quickshell/modules/bar/ClockWidget.qml | 4 +- .config/quickshell/modules/bar/Media.qml | 4 +- .config/quickshell/modules/bar/Resource.qml | 4 +- .config/quickshell/modules/bar/Resources.qml | 4 +- .config/quickshell/modules/bar/SysTray.qml | 4 +- .../quickshell/modules/bar/UtilButtons.qml | 4 +- .config/quickshell/modules/bar/Workspaces.qml | 4 +- .../quickshell/modules/common/Appearance.qml | 5 +- .../modules/common/MprisController.qml | 1 - .../modules/common/widgets/MaterialSymbol.qml | 2 +- .../common/widgets/SmallCircleButton.qml | 2 +- .../modules/common/widgets/StyledText.qml | 2 +- .../modules/common/widgets/StyledToolTip.qml | 21 ++++++++ .../modules/screenCorners/ScreenCorners.qml | 4 +- .../sidebarRight/QuickToggleButton.qml | 28 ++++++++--- .../modules/sidebarRight/SidebarRight.qml | 48 +++++++++++++++---- 19 files changed, 110 insertions(+), 43 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/StyledToolTip.qml diff --git a/.config/quickshell/modules/bar/ActiveWindow.qml b/.config/quickshell/modules/bar/ActiveWindow.qml index 39b34fa15..6ae04b3af 100644 --- a/.config/quickshell/modules/bar/ActiveWindow.qml +++ b/.config/quickshell/modules/bar/ActiveWindow.qml @@ -1,5 +1,5 @@ -import "../common" -import "../common/widgets" +import "root:/modules/common" +import "root:/modules/common/widgets" import QtQuick import QtQuick.Layouts import Quickshell.Wayland diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index b31ac371a..25fe1ddf5 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -1,5 +1,5 @@ -import "../common" -import "../common/widgets" +import "root:/modules/common" +import "root:/modules/common/widgets" import QtQuick import QtQuick.Controls import QtQuick.Layouts diff --git a/.config/quickshell/modules/bar/Battery.qml b/.config/quickshell/modules/bar/Battery.qml index dc3b0d057..a751bf3b1 100644 --- a/.config/quickshell/modules/bar/Battery.qml +++ b/.config/quickshell/modules/bar/Battery.qml @@ -1,5 +1,5 @@ -import "../common" -import "../common/widgets" +import "root:/modules/common" +import "root:/modules/common/widgets" import QtQuick import QtQuick.Layouts import Quickshell diff --git a/.config/quickshell/modules/bar/ClockWidget.qml b/.config/quickshell/modules/bar/ClockWidget.qml index 4568bbdcd..3e1840463 100644 --- a/.config/quickshell/modules/bar/ClockWidget.qml +++ b/.config/quickshell/modules/bar/ClockWidget.qml @@ -1,5 +1,5 @@ -import "../common" -import "../common/widgets" +import "root:/modules/common" +import "root:/modules/common/widgets" import QtQuick import QtQuick.Layouts diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml index 268ea1042..1facc6673 100644 --- a/.config/quickshell/modules/bar/Media.qml +++ b/.config/quickshell/modules/bar/Media.qml @@ -1,5 +1,5 @@ -import "../common" -import "../common/widgets" +import "root:/modules/common" +import "root:/modules/common/widgets" import QtQuick import QtQuick.Layouts import Quickshell diff --git a/.config/quickshell/modules/bar/Resource.qml b/.config/quickshell/modules/bar/Resource.qml index fd9a0b87a..c55e4a8b8 100644 --- a/.config/quickshell/modules/bar/Resource.qml +++ b/.config/quickshell/modules/bar/Resource.qml @@ -1,5 +1,5 @@ -import "../common" -import "../common/widgets" +import "root:/modules/common" +import "root:/modules/common/widgets" import QtQuick import QtQuick.Layouts import Quickshell diff --git a/.config/quickshell/modules/bar/Resources.qml b/.config/quickshell/modules/bar/Resources.qml index 0a7f28f6e..6d6514632 100644 --- a/.config/quickshell/modules/bar/Resources.qml +++ b/.config/quickshell/modules/bar/Resources.qml @@ -1,5 +1,5 @@ -import "../common" -import "../common/widgets" +import "root:/modules/common" +import "root:/modules/common/widgets" import QtQuick import QtQuick.Layouts import Quickshell diff --git a/.config/quickshell/modules/bar/SysTray.qml b/.config/quickshell/modules/bar/SysTray.qml index 27d4e456e..5bf3e823f 100644 --- a/.config/quickshell/modules/bar/SysTray.qml +++ b/.config/quickshell/modules/bar/SysTray.qml @@ -1,5 +1,5 @@ -import "../common" -import "../common/widgets" +import "root:/modules/common" +import "root:/modules/common/widgets" import QtQuick import QtQuick.Layouts import Quickshell.Hyprland diff --git a/.config/quickshell/modules/bar/UtilButtons.qml b/.config/quickshell/modules/bar/UtilButtons.qml index eb0af110f..b6e6a276e 100644 --- a/.config/quickshell/modules/bar/UtilButtons.qml +++ b/.config/quickshell/modules/bar/UtilButtons.qml @@ -1,5 +1,5 @@ -import "../common" -import "../common/widgets" +import "root:/modules/common" +import "root:/modules/common/widgets" import QtQuick import QtQuick.Layouts import Quickshell diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 1df21c8be..d502c5a20 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -1,5 +1,5 @@ -import "../common" -import "../common/widgets" +import "root:/modules/common" +import "root:/modules/common/widgets" import QtQuick import QtQuick.Controls import QtQuick.Layouts diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index bbbf733fc..630f1afae 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -122,6 +122,8 @@ Singleton { property color colLayer3Active: mix(colLayer3, colOnLayer3, 0.80); property color colPrimaryHover: mix(m3colors.m3primary, colLayer1Hover, 0.7) property color colPrimaryActive: mix(m3colors.m3primary, colLayer1Active, 0.4) + property color colTooltip: m3colors.m3inverseSurface + property color colOnTooltip: m3colors.m3inverseOnSurface } rounding: QtObject { @@ -148,7 +150,8 @@ Singleton { property int small: 11 property int normal: 12 property int large: 13 - property int larger: 16 + property int larger: 14 + property int huge: 16 } } diff --git a/.config/quickshell/modules/common/MprisController.qml b/.config/quickshell/modules/common/MprisController.qml index 590dcd86c..be0c1939f 100644 --- a/.config/quickshell/modules/common/MprisController.qml +++ b/.config/quickshell/modules/common/MprisController.qml @@ -6,7 +6,6 @@ import QtQuick import Quickshell import Quickshell.Io import Quickshell.Services.Mpris -import "../.." Singleton { id: root; diff --git a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml index 4f0f6a0d4..57a46c5e7 100644 --- a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml +++ b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml @@ -1,4 +1,4 @@ -import "../" +import "root:/modules/common" import QtQuick import QtQuick.Layouts diff --git a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml b/.config/quickshell/modules/common/widgets/SmallCircleButton.qml index 4cb2e9e61..e1b85dd41 100644 --- a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml +++ b/.config/quickshell/modules/common/widgets/SmallCircleButton.qml @@ -1,4 +1,4 @@ -import "../" +import "root:/modules/common" import QtQuick import QtQuick.Controls import QtQuick.Layouts diff --git a/.config/quickshell/modules/common/widgets/StyledText.qml b/.config/quickshell/modules/common/widgets/StyledText.qml index 190e0b1cf..c97b5895a 100644 --- a/.config/quickshell/modules/common/widgets/StyledText.qml +++ b/.config/quickshell/modules/common/widgets/StyledText.qml @@ -1,4 +1,4 @@ -import "../" +import "root:/modules/common" import QtQuick import QtQuick.Layouts diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml new file mode 100644 index 000000000..31b67ad34 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -0,0 +1,21 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ToolTip { + property string content + parent: parent + visible: parent.hovered + padding: 7 + background: Rectangle { + color: Appearance.colors.colTooltip + radius: Appearance.rounding.small + } + StyledText { + text: content + id: tooltipText + color: Appearance.colors.colOnTooltip + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/screenCorners/ScreenCorners.qml b/.config/quickshell/modules/screenCorners/ScreenCorners.qml index 95764848a..f7075305f 100644 --- a/.config/quickshell/modules/screenCorners/ScreenCorners.qml +++ b/.config/quickshell/modules/screenCorners/ScreenCorners.qml @@ -1,5 +1,5 @@ -import "../common" -import "../common/widgets" +import "root:/modules/common" +import "root:/modules/common/widgets" import QtQuick import QtQuick.Controls import QtQuick.Layouts diff --git a/.config/quickshell/modules/sidebarRight/QuickToggleButton.qml b/.config/quickshell/modules/sidebarRight/QuickToggleButton.qml index 0c3cb82c1..20d3be402 100644 --- a/.config/quickshell/modules/sidebarRight/QuickToggleButton.qml +++ b/.config/quickshell/modules/sidebarRight/QuickToggleButton.qml @@ -1,19 +1,17 @@ -import "../common" -import "../common/widgets" +import "root:/modules/common" +import "root:/modules/common/widgets" import QtQuick import QtQuick.Controls +import Quickshell.Io Button { id: button property bool toggled - - signal clicked() + property string buttonIcon implicitWidth: 40 implicitHeight: 40 - onClicked: { - } background: Rectangle { anchors.fill: parent @@ -22,10 +20,26 @@ Button { (button.down ? Appearance.colors.colPrimaryActive : button.hovered ? Appearance.colors.colPrimaryHover : Appearance.m3colors.m3primary) : (button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.colors.colLayer1Hover, 1)) + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } + MaterialSymbol { anchors.centerIn: parent - text: "coffee" + font.pointSize: Appearance.font.pointSize.larger + text: buttonIcon color: toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1 + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } } } diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 4e8463367..82e86dd9c 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -1,5 +1,5 @@ -import "../common" -import "../common/widgets" +import "root:/modules/common" +import "root:/modules/common/widgets" import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -92,7 +92,7 @@ Scope { Layout.fillHeight: false spacing: 10 Layout.margins: 10 - Layout.topMargin: 10 + Layout.bottomMargin: 5 CustomIcon { width: 25 @@ -127,15 +127,45 @@ Scope { anchors.fill: parent anchors.margins: 5 spacing: 5 - Rectangle { - width: 40 - height: 40 - color: "#77000000" - radius: Appearance.rounding.full + + QuickToggleButton { + property bool enabled: false + buttonIcon: "gamepad" + toggled: enabled + onClicked: { + enabled = !enabled + if (enabled) { + gameModeOn.running = true + } else { + gameModeOff.running = true + } + } + Process { + id: gameModeOn + command: ['bash', '-c', `hyprctl --batch "keyword animations:enabled 0; keyword decoration:shadow:enabled 0; keyword decoration:blur:enabled 0; keyword general:gaps_in 0; keyword general:gaps_out 0; keyword general:border_size 1; keyword decoration:rounding 0; keyword general:allow_tearing 1"`] + } + Process { + id: gameModeOff + command: ['bash', '-c', `hyprctl reload`] + } + StyledToolTip { + content: "Game mode" + } } QuickToggleButton { - toggled: false + toggled: idleInhibitor.running + buttonIcon: "coffee" + onClicked: { + idleInhibitor.running = !idleInhibitor.running + } + Process { + id: idleInhibitor + command: ["bash", "-c", "${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/scripts/wayland-idle-inhibitor.py"] + } + StyledToolTip { + content: "Keep system awake" + } } } From 62ef2fc4215797d15d6d76835d33a68f32e3b396 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 15 Apr 2025 20:10:52 +0200 Subject: [PATCH 025/824] bluetooth and wifi --- .config/quickshell/modules/bar/Bar.qml | 23 ++++++- .config/quickshell/modules/bar/SysTray.qml | 12 ++++ .config/quickshell/modules/bar/Workspaces.qml | 11 +++ .../quickshell/modules/common/Bluetooth.qml | 69 +++++++++++++++++++ .../modules/common/ConfigOptions.qml | 9 +++ .config/quickshell/modules/common/Network.qml | 53 ++++++++++++++ .../modules/common/widgets/StyledToolTip.qml | 4 +- .../modules/sidebarRight/SidebarRight.qml | 54 ++++----------- .../sidebarRight/quickToggles/Bluetooth.qml | 41 +++++++++++ .../sidebarRight/quickToggles/GameMode.qml | 30 ++++++++ .../quickToggles/IdleInhibitor.qml | 20 ++++++ .../sidebarRight/quickToggles/Network.qml | 47 +++++++++++++ .../sidebarRight/quickToggles/NightLight.qml | 42 +++++++++++ 13 files changed, 373 insertions(+), 42 deletions(-) create mode 100644 .config/quickshell/modules/common/Bluetooth.qml create mode 100644 .config/quickshell/modules/common/Network.qml create mode 100644 .config/quickshell/modules/sidebarRight/quickToggles/Bluetooth.qml create mode 100644 .config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml create mode 100644 .config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml create mode 100644 .config/quickshell/modules/sidebarRight/quickToggles/Network.qml create mode 100644 .config/quickshell/modules/sidebarRight/quickToggles/NightLight.qml diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 25fe1ddf5..dd1ecdab2 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -136,9 +136,27 @@ Scope { spacing: 20 layoutDirection: Qt.RightToLeft - Item { // TODO make this wifi & bluetooth - Layout.leftMargin: Appearance.rounding.screenRounding + RowLayout { // TODO make this wifi & bluetooth + Layout.rightMargin: Appearance.rounding.screenRounding Layout.fillWidth: false + spacing: 15 + + MaterialSymbol { + text: (Network.networkName.length > 0 && Network.networkName != "lo") ? ( + Network.networkStrength > 80 ? "signal_wifi_4_bar" : + Network.networkStrength > 60 ? "network_wifi_3_bar" : + Network.networkStrength > 40 ? "network_wifi_2_bar" : + Network.networkStrength > 20 ? "network_wifi_1_bar" : + "signal_wifi_0_bar" + ) : "signal_wifi_off" + font.pointSize: Appearance.font.pointSize.larger + color: Appearance.colors.colOnLayer0 + } + MaterialSymbol { + text: Bluetooth.bluetoothConnected ? "bluetooth_connected" : Bluetooth.bluetoothEnabled ? "bluetooth" : "bluetooth_disabled" + font.pointSize: Appearance.font.pointSize.larger + color: Appearance.colors.colOnLayer0 + } } SysTray { @@ -155,6 +173,7 @@ Scope { MouseArea { anchors.fill: rightSection acceptedButtons: Qt.LeftButton + propagateComposedEvents: true onPressed: (event) => { if (event.button === Qt.LeftButton) { toggleSidebarRight.running = true diff --git a/.config/quickshell/modules/bar/SysTray.qml b/.config/quickshell/modules/bar/SysTray.qml index 5bf3e823f..e5dbdfe28 100644 --- a/.config/quickshell/modules/bar/SysTray.qml +++ b/.config/quickshell/modules/bar/SysTray.qml @@ -7,6 +7,7 @@ import Quickshell.Services.SystemTray import Quickshell.Wayland import Quickshell.Widgets +// TODO: More fancy animation Item { id: root @@ -34,6 +35,17 @@ Item { } + StyledText { + Layout.alignment: Qt.AlignVCenter + font.pointSize: Appearance.font.pointSize.larger + color: Appearance.colors.colSubtext + text: "•" + visible: { + console.log("SystemTray.values.length", SystemTray.items.values.length) + SystemTray.items.values.length > 0 + } + } + } } diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index d502c5a20..ec01dcfa9 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -12,6 +12,7 @@ Item { required property var bar readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) readonly property Toplevel activeWindow: ToplevelManager.activeToplevel + readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / ConfigOptions.bar.workspacesShown) property list workspaceOccupied: [] property int widgetPadding: 4 @@ -70,6 +71,16 @@ Item { acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad } + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.BackButton + onPressed: (event) => { + if (event.button === Qt.BackButton) { + Hyprland.dispatch(`togglespecialworkspace`); + } + } + } + // Workspaces - background RowLayout { id: rowLayout diff --git a/.config/quickshell/modules/common/Bluetooth.qml b/.config/quickshell/modules/common/Bluetooth.qml new file mode 100644 index 000000000..fb654c417 --- /dev/null +++ b/.config/quickshell/modules/common/Bluetooth.qml @@ -0,0 +1,69 @@ +pragma Singleton + +import Quickshell; +import Quickshell.Io; +import QtQuick; + +Singleton { + id: root + + property int updateInterval: 1000 + property string bluetoothDeviceName: "" + property string bluetoothDeviceAddress: "" + property bool bluetoothEnabled: false + property bool bluetoothConnected: false + + function update() { + updateBluetoothDevice.running = true + updateBluetoothStatus.running = true + updateBluetoothEnabled.running = true + } + + Timer { + interval: 10 + running: true + repeat: true + onTriggered: { + update() + interval = root.updateInterval + } + } + + // Check if Bluetooth is enabled (controller powered on) + Process { + id: updateBluetoothEnabled + command: ["sh", "-c", "bluetoothctl show | grep -q 'Powered: yes' && echo 1 || echo 0"] + running: true + stdout: SplitParser { + onRead: data => { + root.bluetoothEnabled = (parseInt(data) === 1) + } + } + } + + // Get the name and address of the first connected Bluetooth device + Process { + id: updateBluetoothDevice + command: ["sh", "-c", "bluetoothctl info | awk -F': ' '/Name: /{name=$2} /Device /{addr=$2} END{print name \":\" addr}'"] + running: true + stdout: SplitParser { + onRead: data => { + let parts = data.split(":") + root.bluetoothDeviceName = parts[0] || "" + root.bluetoothDeviceAddress = parts[1] || "" + } + } + } + + // Check if any device is connected + Process { + id: updateBluetoothStatus + command: ["sh", "-c", "bluetoothctl info | grep -q 'Connected: yes' && echo 1 || echo 0"] + running: true + stdout: SplitParser { + onRead: data => { + root.bluetoothConnected = (parseInt(data) === 1) + } + } + } +} diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 216a7dfd0..f225d9737 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -7,6 +7,15 @@ Singleton { property int fakeScreenRounding: 1 // 0: None | 1: Always | 2: When not fullscreen } + property QtObject apps: QtObject { + property string bluetooth: "blueberry" + property string imageViewer: "loupe" + property string network: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi" + property string settings: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center" + property string taskManager: "gnome-usage" + property string terminal: "foot" // This is only for shell actions + } + property QtObject bar: QtObject { property int workspacesShown: 10 property int batteryLowThreshold: 20 diff --git a/.config/quickshell/modules/common/Network.qml b/.config/quickshell/modules/common/Network.qml new file mode 100644 index 000000000..9a3a30345 --- /dev/null +++ b/.config/quickshell/modules/common/Network.qml @@ -0,0 +1,53 @@ +pragma Singleton + +import Quickshell; +import Quickshell.Io; +import Quickshell.Services.Pipewire; +import QtQuick; + +Singleton { + id: root + + property int updateInterval: 1000 + property string networkName: ""; + property int networkStrength; + function update() { + updateNetworkName.running = true + updateNetworkStrength.running = true + } + + Timer { + interval: 10 + running: true + repeat: true + onTriggered: { + update() + interval = root.updateInterval; + } + } + + Process { + id: updateNetworkName + command: ["sh", "-c", "nmcli -t -f NAME c show --active | head -1"] + running: true; + stdout: SplitParser { + onRead: data => { + root.networkName = data + // console.log("Network: " + data); + } + } + } + + Process { + id: updateNetworkStrength + running: true + command: ["sh", "-c", "nmcli -f IN-USE,SIGNAL,SSID device wifi | awk '/^\*/{if (NR!=1) {print $2}}'"]; + stdout: SplitParser { + onRead: data => { + root.networkStrength = parseInt(data); + // console.log("Network Strength: " + data); + } + } + } +} + diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index 31b67ad34..407fecf78 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -12,10 +12,12 @@ ToolTip { background: Rectangle { color: Appearance.colors.colTooltip radius: Appearance.rounding.small + implicitWidth: tooltipText.implicitWidth + 2 * padding } StyledText { - text: content id: tooltipText + text: content color: Appearance.colors.colOnTooltip + wrapMode: Text.WordWrap } } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 82e86dd9c..ec5dfc989 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -1,5 +1,6 @@ import "root:/modules/common" import "root:/modules/common/widgets" +import "./quickToggles/" import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -128,45 +129,11 @@ Scope { anchors.margins: 5 spacing: 5 - QuickToggleButton { - property bool enabled: false - buttonIcon: "gamepad" - toggled: enabled - onClicked: { - enabled = !enabled - if (enabled) { - gameModeOn.running = true - } else { - gameModeOff.running = true - } - } - Process { - id: gameModeOn - command: ['bash', '-c', `hyprctl --batch "keyword animations:enabled 0; keyword decoration:shadow:enabled 0; keyword decoration:blur:enabled 0; keyword general:gaps_in 0; keyword general:gaps_out 0; keyword general:border_size 1; keyword decoration:rounding 0; keyword general:allow_tearing 1"`] - } - Process { - id: gameModeOff - command: ['bash', '-c', `hyprctl reload`] - } - StyledToolTip { - content: "Game mode" - } - } - - QuickToggleButton { - toggled: idleInhibitor.running - buttonIcon: "coffee" - onClicked: { - idleInhibitor.running = !idleInhibitor.running - } - Process { - id: idleInhibitor - command: ["bash", "-c", "${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/scripts/wayland-idle-inhibitor.py"] - } - StyledToolTip { - content: "Keep system awake" - } - } + Network {} + Bluetooth {} + NightLight {} + GameMode {} + IdleInhibitor {} } } @@ -203,6 +170,15 @@ Scope { } } } + + function close(): void { + for (let i = 0; i < sidebarVariants.instances.length; i++) { + let panelWindow = sidebarVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = false; + } + } + } } } diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/Bluetooth.qml b/.config/quickshell/modules/sidebarRight/quickToggles/Bluetooth.qml new file mode 100644 index 000000000..1517e9689 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/quickToggles/Bluetooth.qml @@ -0,0 +1,41 @@ +import "../" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import Quickshell +import Quickshell.Io + +QuickToggleButton { + toggled: Bluetooth.bluetoothEnabled + buttonIcon: Bluetooth.bluetoothConnected ? "bluetooth_connected" : Bluetooth.bluetoothEnabled ? "bluetooth" : "bluetooth_disabled" + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton | Qt.LeftButton + onClicked: { + if (mouse.button === Qt.LeftButton) { + toggleBluetooth.running = true + } + if (mouse.button === Qt.RightButton) { + configureBluetooth.running = true + } + } + hoverEnabled: false + propagateComposedEvents: true + } + Process { + id: configureBluetooth + command: ["bash", "-c", `${ConfigOptions.apps.bluetooth} & qs ipc call sidebarRight close`] + } + Process { + id: toggleBluetooth + command: ["bash", "-c", `bluetoothctl power ${Bluetooth.bluetoothEnabled ? "off" : "on"}`] + onRunningChanged: { + if(!running) { + Bluetooth.update() + } + } + } + StyledToolTip { + content: `${Bluetooth.bluetoothEnabled ? Bluetooth.bluetoothDeviceName : "Bluetooth"} | Right-click to configure` + } +} diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml b/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml new file mode 100644 index 000000000..94f8ebb6c --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml @@ -0,0 +1,30 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "../" +import Quickshell.Io +import Quickshell + +QuickToggleButton { + property bool enabled: false + buttonIcon: "gamepad" + toggled: enabled + onClicked: { + enabled = !enabled + if (enabled) { + gameModeOn.running = true + } else { + gameModeOff.running = true + } + } + Process { + id: gameModeOn + command: ['bash', '-c', `hyprctl --batch "keyword animations:enabled 0; keyword decoration:shadow:enabled 0; keyword decoration:blur:enabled 0; keyword general:gaps_in 0; keyword general:gaps_out 0; keyword general:border_size 1; keyword decoration:rounding 0; keyword general:allow_tearing 1"`] + } + Process { + id: gameModeOff + command: ['bash', '-c', `hyprctl reload`] + } + StyledToolTip { + content: "Game mode" + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml b/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml new file mode 100644 index 000000000..086836975 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml @@ -0,0 +1,20 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "../" +import Quickshell.Io +import Quickshell + +QuickToggleButton { + toggled: idleInhibitor.running + buttonIcon: "coffee" + onClicked: { + idleInhibitor.running = !idleInhibitor.running + } + Process { + id: idleInhibitor + command: ["bash", "-c", "${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/scripts/wayland-idle-inhibitor.py"] + } + StyledToolTip { + content: "Keep system awake" + } +} diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/Network.qml b/.config/quickshell/modules/sidebarRight/quickToggles/Network.qml new file mode 100644 index 000000000..c3d8173ca --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/quickToggles/Network.qml @@ -0,0 +1,47 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "../" +import Quickshell.Io +import Quickshell +import QtQuick + +QuickToggleButton { + toggled: Network.networkName.length > 0 && Network.networkName != "lo" + buttonIcon: toggled ? ( + Network.networkStrength > 80 ? "signal_wifi_4_bar" : + Network.networkStrength > 60 ? "network_wifi_3_bar" : + Network.networkStrength > 40 ? "network_wifi_2_bar" : + Network.networkStrength > 20 ? "network_wifi_1_bar" : + "signal_wifi_0_bar" + ) : "signal_wifi_off" + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton | Qt.LeftButton + onClicked: { + if (mouse.button === Qt.LeftButton) { + toggleNetwork.running = true + } + if (mouse.button === Qt.RightButton) { + configureNetwork.running = true + } + } + hoverEnabled: false + propagateComposedEvents: true + } + Process { + id: configureNetwork + command: ["bash", "-c", `${ConfigOptions.apps.network} & qs ipc call sidebarRight close`] + } + Process { + id: toggleNetwork + command: ["bash", "-c", "nmcli radio wifi | grep -q enabled && nmcli radio wifi off || nmcli radio wifi on"] + onRunningChanged: { + if(!running) { + Network.update() + } + } + } + StyledToolTip { + content: `${Network.networkName} | Right-click to configure` + } +} diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/NightLight.qml b/.config/quickshell/modules/sidebarRight/quickToggles/NightLight.qml new file mode 100644 index 000000000..e69a81c04 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/quickToggles/NightLight.qml @@ -0,0 +1,42 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "../" +import Quickshell.Io +import Quickshell + +QuickToggleButton { + id: nightLightButton + property bool enabled: false + toggled: enabled + buttonIcon: "nightlight" + onClicked: { + nightLightButton.enabled = !nightLightButton.enabled + if (enabled) { + nightLightOn.running = true + } + else { + nightLightOff.running = true + } + } + Process { + id: nightLightOn + command: ["gammastep"] + } + Process { + id: nightLightOff + command: ["pkill", "gammastep"] + } + Process { + id: updateNightLightState + running: true + command: ["pidof", "gammastep"] + stdout: SplitParser { + onRead: (data) => { // if not empty then set toggled to true + nightLightButton.enabled = data.length > 0 + } + } + } + StyledToolTip { + content: "Night Light" + } +} From 7c217dc25c08646f0aad40b76fde991473e743f9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 15 Apr 2025 22:46:18 +0200 Subject: [PATCH 026/824] sidebar calendar thingie: navrail --- .../quickshell/modules/bar/ActiveWindow.qml | 4 +- .config/quickshell/modules/bar/Bar.qml | 67 ++++++++++++------- .config/quickshell/modules/bar/Battery.qml | 4 +- .../quickshell/modules/bar/ClockWidget.qml | 6 +- .config/quickshell/modules/bar/Media.qml | 2 +- .config/quickshell/modules/bar/Resource.qml | 2 +- .config/quickshell/modules/bar/SysTray.qml | 3 +- .../quickshell/modules/bar/UtilButtons.qml | 4 +- .config/quickshell/modules/bar/Workspaces.qml | 2 +- .../quickshell/modules/common/Appearance.qml | 19 ++++-- .../modules/common/widgets/CustomIcon.qml | 3 +- .../modules/common/widgets/MaterialSymbol.qml | 2 +- .../modules/common/widgets/NavRailButton.qml | 64 ++++++++++++++++++ .../modules/common/widgets/StyledText.qml | 2 +- .../sidebarRight/QuickToggleButton.qml | 2 +- .../modules/sidebarRight/SidebarCalendar.qml | 50 ++++++++++++++ .../modules/sidebarRight/SidebarRight.qml | 22 +++++- 17 files changed, 204 insertions(+), 54 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/NavRailButton.qml create mode 100644 .config/quickshell/modules/sidebarRight/SidebarCalendar.qml diff --git a/.config/quickshell/modules/bar/ActiveWindow.qml b/.config/quickshell/modules/bar/ActiveWindow.qml index 6ae04b3af..9d5ecb89e 100644 --- a/.config/quickshell/modules/bar/ActiveWindow.qml +++ b/.config/quickshell/modules/bar/ActiveWindow.qml @@ -23,7 +23,7 @@ Item { spacing: -4 StyledText { - font.pointSize: Appearance.font.pointSize.smaller + font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colSubtext Layout.preferredWidth: preferredWidth elide: Text.ElideRight @@ -31,7 +31,7 @@ Item { } StyledText { - font.pointSize: Appearance.font.pointSize.small + font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnLayer0 Layout.preferredWidth: preferredWidth elide: Text.ElideRight diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index dd1ecdab2..5ddd10d0e 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -13,12 +13,12 @@ Scope { readonly property int barCenterSideModuleWidth: Appearance.sizes.barCenterSideModuleWidth Process { - id: toggleSidebarRight - command: ["qs", "ipc", "call", "sidebarRight", "toggle"] + id: openSidebarRight + command: ["qs", "ipc", "call", "sidebarRight", "open"] } Process { - id: toggleSidebarLeft - command: ["qs", "ipc", "call", "sidebarLeft", "toggle"] + id: openSidebarLeft + command: ["qs", "ipc", "call", "sidebarLeft", "open"] } Variants { @@ -135,27 +135,35 @@ Scope { width: Appearance.sizes.barPreferredSideSectionWidth spacing: 20 layoutDirection: Qt.RightToLeft - - RowLayout { // TODO make this wifi & bluetooth + + Rectangle { + Layout.margins: 4 Layout.rightMargin: Appearance.rounding.screenRounding - Layout.fillWidth: false - spacing: 15 - - MaterialSymbol { - text: (Network.networkName.length > 0 && Network.networkName != "lo") ? ( - Network.networkStrength > 80 ? "signal_wifi_4_bar" : - Network.networkStrength > 60 ? "network_wifi_3_bar" : - Network.networkStrength > 40 ? "network_wifi_2_bar" : - Network.networkStrength > 20 ? "network_wifi_1_bar" : - "signal_wifi_0_bar" - ) : "signal_wifi_off" - font.pointSize: Appearance.font.pointSize.larger - color: Appearance.colors.colOnLayer0 - } - MaterialSymbol { - text: Bluetooth.bluetoothConnected ? "bluetooth_connected" : Bluetooth.bluetoothEnabled ? "bluetooth" : "bluetooth_disabled" - font.pointSize: Appearance.font.pointSize.larger - color: Appearance.colors.colOnLayer0 + Layout.fillHeight: true + implicitWidth: rowLayout.implicitWidth + 10*2 + radius: Appearance.rounding.full + color: barRightSideMouseArea.pressed ? Appearance.colors.colLayer1Active : barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : "transparent" + RowLayout { + id: rowLayout + anchors.centerIn: parent + spacing: 15 + + MaterialSymbol { + text: (Network.networkName.length > 0 && Network.networkName != "lo") ? ( + Network.networkStrength > 80 ? "signal_wifi_4_bar" : + Network.networkStrength > 60 ? "network_wifi_3_bar" : + Network.networkStrength > 40 ? "network_wifi_2_bar" : + Network.networkStrength > 20 ? "network_wifi_1_bar" : + "signal_wifi_0_bar" + ) : "signal_wifi_off" + font.pixelSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer0 + } + MaterialSymbol { + text: Bluetooth.bluetoothConnected ? "bluetooth_connected" : Bluetooth.bluetoothEnabled ? "bluetooth" : "bluetooth_disabled" + font.pixelSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer0 + } } } @@ -171,12 +179,21 @@ Scope { } MouseArea { + id: barRightSideMouseArea + property bool hovered: false anchors.fill: rightSection acceptedButtons: Qt.LeftButton + hoverEnabled: true propagateComposedEvents: true + onEntered: (event) => { + barRightSideMouseArea.hovered = true + } + onExited: (event) => { + barRightSideMouseArea.hovered = false + } onPressed: (event) => { if (event.button === Qt.LeftButton) { - toggleSidebarRight.running = true + openSidebarRight.running = true } } // Scroll to change volume diff --git a/.config/quickshell/modules/bar/Battery.qml b/.config/quickshell/modules/bar/Battery.qml index a751bf3b1..6dc6a92dc 100644 --- a/.config/quickshell/modules/bar/Battery.qml +++ b/.config/quickshell/modules/bar/Battery.qml @@ -57,7 +57,7 @@ Rectangle { MaterialSymbol { anchors.centerIn: parent text: "battery_full" - font.pointSize: Appearance.font.pointSize.normal + font.pixelSize: Appearance.font.pixelSize.normal color: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer } @@ -71,7 +71,7 @@ Rectangle { anchors.left: rowLayout.left anchors.verticalCenter: rowLayout.verticalCenter text: "bolt" - font.pointSize: Appearance.font.pointSize.large + font.pixelSize: Appearance.font.pixelSize.large color: Appearance.m3colors.m3onSecondaryContainer visible: opacity !== 0 // Only show when charging opacity: isCharging ? 1 : 0 // Keep opacity for visibility diff --git a/.config/quickshell/modules/bar/ClockWidget.qml b/.config/quickshell/modules/bar/ClockWidget.qml index 3e1840463..6093eaf8b 100644 --- a/.config/quickshell/modules/bar/ClockWidget.qml +++ b/.config/quickshell/modules/bar/ClockWidget.qml @@ -16,19 +16,19 @@ Rectangle { anchors.centerIn: parent StyledText { - font.pointSize: Appearance.font.pointSize.large + font.pixelSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer1 text: DateTime.time } StyledText { - font.pointSize: Appearance.font.pointSize.small + font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnLayer1 text: "•" } StyledText { - font.pointSize: Appearance.font.pointSize.small + font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnLayer1 text: DateTime.date } diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml index 1facc6673..c5c29093f 100644 --- a/.config/quickshell/modules/bar/Media.qml +++ b/.config/quickshell/modules/bar/Media.qml @@ -62,7 +62,7 @@ Item { MaterialSymbol { anchors.centerIn: parent text: activePlayer?.isPlaying ? "pause" : "play_arrow" - font.pointSize: Appearance.font.pointSize.normal + font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3onSecondaryContainer } diff --git a/.config/quickshell/modules/bar/Resource.qml b/.config/quickshell/modules/bar/Resource.qml index c55e4a8b8..9457c4487 100644 --- a/.config/quickshell/modules/bar/Resource.qml +++ b/.config/quickshell/modules/bar/Resource.qml @@ -29,7 +29,7 @@ Item { MaterialSymbol { anchors.centerIn: parent text: iconName - font.pointSize: Appearance.font.pointSize.normal + font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3onSecondaryContainer } diff --git a/.config/quickshell/modules/bar/SysTray.qml b/.config/quickshell/modules/bar/SysTray.qml index e5dbdfe28..4f76ddfa8 100644 --- a/.config/quickshell/modules/bar/SysTray.qml +++ b/.config/quickshell/modules/bar/SysTray.qml @@ -37,11 +37,10 @@ Item { StyledText { Layout.alignment: Qt.AlignVCenter - font.pointSize: Appearance.font.pointSize.larger + font.pixelSize: Appearance.font.pixelSize.larger color: Appearance.colors.colSubtext text: "•" visible: { - console.log("SystemTray.values.length", SystemTray.items.values.length) SystemTray.items.values.length > 0 } } diff --git a/.config/quickshell/modules/bar/UtilButtons.qml b/.config/quickshell/modules/bar/UtilButtons.qml index b6e6a276e..64f24a0c9 100644 --- a/.config/quickshell/modules/bar/UtilButtons.qml +++ b/.config/quickshell/modules/bar/UtilButtons.qml @@ -37,7 +37,7 @@ Rectangle { MaterialSymbol { anchors.centerIn: parent text: "screenshot_region" - font.pointSize: Appearance.font.pointSize.normal + font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.colors.colOnLayer2 } @@ -50,7 +50,7 @@ Rectangle { MaterialSymbol { anchors.centerIn: parent text: "colorize" - font.pointSize: Appearance.font.pointSize.normal + font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.colors.colOnLayer2 } diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index ec01dcfa9..ef4356ef9 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -171,7 +171,7 @@ Item { anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - font.pointSize: Appearance.font.pointSize.small + font.pixelSize: Appearance.font.pixelSize.small text: `${workspaceValue}` elide: Text.ElideRight color: (monitor.activeWorkspace?.id == workspaceValue) ? Appearance.m3colors.m3onPrimary : (workspaceOccupied[index] ? Appearance.colors.colOnLayer1 : Appearance.colors.colOnLayer1Inactive) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 630f1afae..09a71b6c2 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -122,6 +122,10 @@ Singleton { property color colLayer3Active: mix(colLayer3, colOnLayer3, 0.80); property color colPrimaryHover: mix(m3colors.m3primary, colLayer1Hover, 0.7) property color colPrimaryActive: mix(m3colors.m3primary, colLayer1Active, 0.4) + property color colSecondaryHover: mix(m3colors.m3secondary, colLayer1Hover, 0.7) + property color colSecondaryActive: mix(m3colors.m3secondary, colLayer1Active, 0.4) + property color colSecondaryContainerHover: mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.8) + property color colSecondaryContainerActive: mix(m3colors.m3secondaryContainer, colLayer1Active, 0.6) property color colTooltip: m3colors.m3inverseSurface property color colOnTooltip: m3colors.m3inverseOnSurface } @@ -145,13 +149,14 @@ Singleton { property string monospace: "JetBrains Mono NF" property string reading: "Readex Pro" } - property QtObject pointSize: QtObject { - property int smaller: 10 - property int small: 11 - property int normal: 12 - property int large: 13 - property int larger: 14 - property int huge: 16 + property QtObject pixelSize: QtObject { + property int smaller: 13 + property int small: 15 + property int normal: 16 + property int large: 17 + property int larger: 19 + property int huge: 22 + property int hugeass: 23 } } diff --git a/.config/quickshell/modules/common/widgets/CustomIcon.qml b/.config/quickshell/modules/common/widgets/CustomIcon.qml index 1e8ab368a..25f5af00a 100644 --- a/.config/quickshell/modules/common/widgets/CustomIcon.qml +++ b/.config/quickshell/modules/common/widgets/CustomIcon.qml @@ -1,5 +1,4 @@ import QtQuick -import Qt.labs.platform import Quickshell import Quickshell.Widgets @@ -7,7 +6,7 @@ Item { id: root property string source: "" - property string iconFolder: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0] + "/quickshell/assets/icons" // The folder to check first + property string iconFolder: "root:/assets/icons" // The folder to check first width: 30 height: 30 diff --git a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml index 57a46c5e7..7891da690 100644 --- a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml +++ b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml @@ -6,6 +6,6 @@ Text { renderType: Text.NativeRendering verticalAlignment: Text.AlignVCenter font.family: Appearance.font.family.iconMaterial - font.pointSize: Appearance.font.pointSize.small + font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnBackground } diff --git a/.config/quickshell/modules/common/widgets/NavRailButton.qml b/.config/quickshell/modules/common/widgets/NavRailButton.qml new file mode 100644 index 000000000..cc40503fc --- /dev/null +++ b/.config/quickshell/modules/common/widgets/NavRailButton.qml @@ -0,0 +1,64 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io + +Button { + id: button + + property bool toggled + property string buttonIcon + property string buttonText + + Layout.alignment: Qt.AlignHCenter + implicitHeight: columnLayout.implicitHeight + implicitWidth: columnLayout.implicitWidth + + background: Item{} // No ugly bg + + // Real stuff + ColumnLayout { + id: columnLayout + spacing: 5 + Rectangle { + width: 62 + implicitHeight: navRailButtonIcon.height + 2*2 + Layout.alignment: Qt.AlignHCenter + radius: Appearance.rounding.full + color: toggled ? + (button.down ? Appearance.colors.colSecondaryContainerActive : button.hovered ? Appearance.colors.colSecondaryContainerHover : Appearance.m3colors.m3secondaryContainer) : + (button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.colors.colLayer1Hover, 1)) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } + MaterialSymbol { + id: navRailButtonIcon + anchors.centerIn: parent + font.pixelSize: Appearance.font.pixelSize.hugeass + text: buttonIcon + color: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1 + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: buttonText + color: Appearance.colors.colOnLayer1 + } + } + +} diff --git a/.config/quickshell/modules/common/widgets/StyledText.qml b/.config/quickshell/modules/common/widgets/StyledText.qml index c97b5895a..96c75a053 100644 --- a/.config/quickshell/modules/common/widgets/StyledText.qml +++ b/.config/quickshell/modules/common/widgets/StyledText.qml @@ -6,6 +6,6 @@ Text { renderType: Text.NativeRendering verticalAlignment: Text.AlignVCenter font.family: Appearance.font.family.main - font.pointSize: Appearance.font.pointSize.small + font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnBackground } diff --git a/.config/quickshell/modules/sidebarRight/QuickToggleButton.qml b/.config/quickshell/modules/sidebarRight/QuickToggleButton.qml index 20d3be402..012784201 100644 --- a/.config/quickshell/modules/sidebarRight/QuickToggleButton.qml +++ b/.config/quickshell/modules/sidebarRight/QuickToggleButton.qml @@ -30,7 +30,7 @@ Button { MaterialSymbol { anchors.centerIn: parent - font.pointSize: Appearance.font.pointSize.larger + font.pixelSize: Appearance.font.pixelSize.larger text: buttonIcon color: toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1 diff --git a/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml b/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml new file mode 100644 index 000000000..49b242705 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml @@ -0,0 +1,50 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell + +Rectangle { + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: false + Layout.fillWidth: true + radius: Appearance.rounding.normal + color: Appearance.colors.colLayer1 + height: 300 + + RowLayout { + id: calendarRow + anchors.centerIn: parent + width: parent.width - 10 * 2 + height: parent.height - 10 * 2 + spacing: 10 + property int selectedTab: 0 + + ColumnLayout { + id: tabBar + Layout.fillHeight: true + Layout.leftMargin: 10 + spacing: 10 + Repeater { + model: [ + {"name": "Calendar", "icon": "calendar_month"}, + {"name": "To Do", "icon": "done_outline"} + ] + NavRailButton { + toggled: calendarRow.selectedTab == index + buttonText: modelData.name + buttonIcon: modelData.icon + onClicked: { + calendarRow.selectedTab = index + console.log("Selected tab:", calendarRow.selectedTab) + } + } + } + } + Item { // Todo the real content goes here! + Layout.fillWidth: true + Layout.fillHeight: true + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index ec5dfc989..ce343ee24 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -44,7 +44,6 @@ Scope { windows: [ sidebarRoot ] active: false onCleared: () => { - // sidebarRoot.visible = false if (!active) sidebarRoot.visible = false } } @@ -102,9 +101,10 @@ Scope { } StyledText { - font.pointSize: Appearance.font.pointSize.normal + font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.colors.colOnLayer0 text: `Uptime: ${DateTime.uptime}` + textFormat: Text.MarkdownText } Item { @@ -138,9 +138,16 @@ Scope { } } - Item { + Rectangle { + Layout.alignment: Qt.AlignHCenter Layout.fillHeight: true + Layout.fillWidth: true + radius: Appearance.rounding.normal + color: Appearance.colors.colLayer1 } + + // Calendar + SidebarCalendar {} } } @@ -179,6 +186,15 @@ Scope { } } } + + function open(): void { + for (let i = 0; i < sidebarVariants.instances.length; i++) { + let panelWindow = sidebarVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = true; + } + } + } } } From 199bc99fb54b47677e7cec785550f30d08b41f72 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 16 Apr 2025 00:01:14 +0200 Subject: [PATCH 027/824] sidebar navigation item anims --- .config/quickshell/modules/bar/Bar.qml | 2 +- .../modules/common/widgets/StyledToolTip.qml | 2 +- .../modules/sidebarRight/SidebarCalendar.qml | 82 ++++++++++++++++++- .../sidebarRight/quickToggles/Bluetooth.qml | 4 +- 4 files changed, 85 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 5ddd10d0e..f451f8744 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -133,7 +133,7 @@ Scope { anchors.right: parent.right implicitHeight: barHeight width: Appearance.sizes.barPreferredSideSectionWidth - spacing: 20 + spacing: 5 layoutDirection: Qt.RightToLeft Rectangle { diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index 407fecf78..df97fc8e8 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -12,7 +12,7 @@ ToolTip { background: Rectangle { color: Appearance.colors.colTooltip radius: Appearance.rounding.small - implicitWidth: tooltipText.implicitWidth + 2 * padding + width: tooltipText.width + 2 * padding } StyledText { id: tooltipText diff --git a/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml b/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml index 49b242705..9163c056d 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml @@ -37,14 +37,92 @@ Rectangle { buttonIcon: modelData.icon onClicked: { calendarRow.selectedTab = index - console.log("Selected tab:", calendarRow.selectedTab) } } } } - Item { // Todo the real content goes here! + StackLayout { + id: tabStack Layout.fillWidth: true Layout.fillHeight: true + property int realIndex: 0 + // currentIndex: 0 + Connections { + target: calendarRow + function onSelectedTabChanged() { + // console.log("Real index changed to: " + tabStack.realIndex) + delayedStackSwitch.start() + tabStack.realIndex = calendarRow.selectedTab + } + } + Timer { + id: delayedStackSwitch + interval: Appearance.animation.elementDecel.duration + repeat: false + onTriggered: { + tabStack.currentIndex = calendarRow.selectedTab + } + } + + Component { + id: calendarWidget + Rectangle { + anchors.fill: parent + color: "pink" + width: 30; height: 30; + radius: Appearance.rounding.small + StyledText { + anchors.margins: 10 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text: "## Calendar\n- Lorem ipsum\n- Dolor shit amet\n\nSigma Ohayo rc1 Pro+ Premium Hippuland hi ask vaxry for pleas fix 123 Billions must lorem ipsum ipsum yesterdays tears are tomorrows coom awawawa" + wrapMode: Text.WordWrap + textFormat: Text.MarkdownText + } + } + } + Component { + id: todoWidget + Rectangle { + anchors.fill: parent + color: "lavender" + width: 30; height: 30; + radius: Appearance.rounding.small + StyledText { + anchors.margins: 10 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text: "## To Do\n- Lorem ipsum\n- Dolor shit amet\n\nSigma Ohayo rc1 Pro+ Premium Hippuland hi ask vaxry for pleas fix 123 Billions must lorem ipsum ipsum yesterdays tears are tomorrows coom awawawa" + wrapMode: Text.WordWrap + textFormat: Text.MarkdownText + } + } + } + + Repeater { + model: [ + { type: "calendar" }, + { type: "todo" } + ] + Item { + id: tabItem + property int tabIndex: index + property string tabType: modelData.type + property int animDistance: 5 + opacity: (tabStack.currentIndex === tabItem.tabIndex && tabStack.realIndex === tabItem.tabIndex) ? 1 : + (tabStack.currentIndex === tabItem.tabIndex && tabStack.realIndex !== tabItem.tabIndex) ? 0 : + (tabStack.realIndex === tabItem.tabIndex) ? 1 : 0 + y: (tabStack.realIndex === tabItem.tabIndex) ? 0 : (tabStack.realIndex < tabItem.tabIndex) ? animDistance : -animDistance + Behavior on opacity { NumberAnimation { duration: Appearance.animation.elementDecel.duration; easing.type: Easing.OutCubic } } + Behavior on y { NumberAnimation { duration: Appearance.animation.elementDecel.duration * 2; easing.type: Easing.OutCubic } } + Loader { + anchors.fill: parent + sourceComponent: (tabType === "calendar") ? calendarWidget : todoWidget + } + } + } } } } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/Bluetooth.qml b/.config/quickshell/modules/sidebarRight/quickToggles/Bluetooth.qml index 1517e9689..682334957 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/Bluetooth.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/Bluetooth.qml @@ -36,6 +36,8 @@ QuickToggleButton { } } StyledToolTip { - content: `${Bluetooth.bluetoothEnabled ? Bluetooth.bluetoothDeviceName : "Bluetooth"} | Right-click to configure` + content: `${(Bluetooth.bluetoothEnabled && Bluetooth.bluetoothDeviceName.length > 0) ? + Bluetooth.bluetoothDeviceName : "Bluetooth"} | Right-click to configure` + } } From f7c73130876a736caa42442f5a3bbfc755cac2ec Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 16 Apr 2025 10:41:08 +0200 Subject: [PATCH 028/824] sidebar: static calendar --- .../quickshell/modules/common/Appearance.qml | 4 + .../sidebarRight/CalendarDayButton.qml | 39 +++++++ .../modules/sidebarRight/SidebarCalendar.qml | 103 +++++++++++------- .../modules/sidebarRight/calendar_layout.js | 95 ++++++++++++++++ 4 files changed, 199 insertions(+), 42 deletions(-) create mode 100644 .config/quickshell/modules/sidebarRight/CalendarDayButton.qml create mode 100644 .config/quickshell/modules/sidebarRight/calendar_layout.js diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 09a71b6c2..de95e5b08 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -169,6 +169,10 @@ Singleton { property int duration: 350 property int type: Easing.OutExpo } + property QtObject positionShift: QtObject { + property int duration: 160 + property int type: Easing.InOutExpo + } } sizes: QtObject { diff --git a/.config/quickshell/modules/sidebarRight/CalendarDayButton.qml b/.config/quickshell/modules/sidebarRight/CalendarDayButton.qml new file mode 100644 index 000000000..0ec588d02 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/CalendarDayButton.qml @@ -0,0 +1,39 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Button { + id: button + property string day + property int isToday + property bool bold + + Layout.fillWidth: false + Layout.fillHeight: false + implicitWidth: 38; + implicitHeight: 38; + + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.full + color: (isToday == 1) ? (button.down ? Appearance.colors.colPrimaryActive : + button.hovered ? Appearance.colors.colPrimaryHover : + Appearance.m3colors.m3primary) : + button.down ? Appearance.colors.colLayer1Active : + button.hovered ? Appearance.colors.colLayer1Hover : + Appearance.transparentize(Appearance.colors.colLayer1, 1) + } + + contentItem: StyledText { + anchors.fill: parent + text: day + horizontalAlignment: Text.AlignHCenter + font.weight: bold ? Font.Bold : isToday == -1 ? Font.Normal : Font.DemiBold + color: (isToday == 1) ? Appearance.m3colors.m3onPrimary : + (isToday == 0) ? Appearance.colors.colOnLayer1 : + Appearance.m3colors.m3outline + } +} + diff --git a/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml b/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml index 9163c056d..c70b9a7c6 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml @@ -4,6 +4,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell +import "calendar_layout.js" as CalendarLayout Rectangle { Layout.alignment: Qt.AlignHCenter @@ -11,11 +12,11 @@ Rectangle { Layout.fillWidth: true radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 - height: 300 + implicitHeight: 300 RowLayout { id: calendarRow - anchors.centerIn: parent + anchors.fill: parent width: parent.width - 10 * 2 height: parent.height - 10 * 2 spacing: 10 @@ -24,8 +25,8 @@ Rectangle { ColumnLayout { id: tabBar Layout.fillHeight: true - Layout.leftMargin: 10 - spacing: 10 + Layout.leftMargin: 15 + spacing: 15 Repeater { model: [ {"name": "Calendar", "icon": "calendar_month"}, @@ -46,49 +47,53 @@ Rectangle { Layout.fillWidth: true Layout.fillHeight: true property int realIndex: 0 - // currentIndex: 0 - Connections { - target: calendarRow - function onSelectedTabChanged() { - // console.log("Real index changed to: " + tabStack.realIndex) - delayedStackSwitch.start() - tabStack.realIndex = calendarRow.selectedTab - } - } - Timer { - id: delayedStackSwitch - interval: Appearance.animation.elementDecel.duration - repeat: false - onTriggered: { - tabStack.currentIndex = calendarRow.selectedTab - } - } + property int animationDuration: Appearance.animation.elementDecel.duration * 1.5 + // Calendar Component { id: calendarWidget - Rectangle { - anchors.fill: parent - color: "pink" - width: 30; height: 30; - radius: Appearance.rounding.small - StyledText { - anchors.margins: 10 - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - text: "## Calendar\n- Lorem ipsum\n- Dolor shit amet\n\nSigma Ohayo rc1 Pro+ Premium Hippuland hi ask vaxry for pleas fix 123 Billions must lorem ipsum ipsum yesterdays tears are tomorrows coom awawawa" - wrapMode: Text.WordWrap - textFormat: Text.MarkdownText + ColumnLayout { + anchors.centerIn: parent + spacing: 5 + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: false + spacing: 5 + Repeater { + model: CalendarLayout.weekDays + delegate: CalendarDayButton { + day: modelData.day + isToday: modelData.today + bold: true + } + } + } + Repeater { + model: CalendarLayout.getCalendarLayout(null, true) + delegate: RowLayout { + Layout.fillWidth: true + Layout.fillHeight: false + spacing: 5 + Repeater { + model: modelData + delegate: CalendarDayButton { + day: modelData.day + isToday: modelData.today + } + } + } } } } + + // To Do Component { id: todoWidget - Rectangle { + Item { anchors.fill: parent - color: "lavender" + // color: "lavender" + // radius: Appearance.rounding.small width: 30; height: 30; - radius: Appearance.rounding.small StyledText { anchors.margins: 10 anchors.left: parent.left @@ -101,6 +106,22 @@ Rectangle { } } + Connections { + target: calendarRow + function onSelectedTabChanged() { + delayedStackSwitch.start() + tabStack.realIndex = calendarRow.selectedTab + } + } + Timer { + id: delayedStackSwitch + interval: tabStack.animationDuration / 2 + repeat: false + onTriggered: { + tabStack.currentIndex = calendarRow.selectedTab + } + } + Repeater { model: [ { type: "calendar" }, @@ -111,12 +132,10 @@ Rectangle { property int tabIndex: index property string tabType: modelData.type property int animDistance: 5 - opacity: (tabStack.currentIndex === tabItem.tabIndex && tabStack.realIndex === tabItem.tabIndex) ? 1 : - (tabStack.currentIndex === tabItem.tabIndex && tabStack.realIndex !== tabItem.tabIndex) ? 0 : - (tabStack.realIndex === tabItem.tabIndex) ? 1 : 0 + opacity: (tabStack.currentIndex === tabItem.tabIndex && tabStack.realIndex === tabItem.tabIndex) ? 1 : 0 y: (tabStack.realIndex === tabItem.tabIndex) ? 0 : (tabStack.realIndex < tabItem.tabIndex) ? animDistance : -animDistance - Behavior on opacity { NumberAnimation { duration: Appearance.animation.elementDecel.duration; easing.type: Easing.OutCubic } } - Behavior on y { NumberAnimation { duration: Appearance.animation.elementDecel.duration * 2; easing.type: Easing.OutCubic } } + Behavior on opacity { NumberAnimation { duration: tabStack.animationDuration / 2; easing.type: Easing.OutCubic } } + Behavior on y { NumberAnimation { duration: tabStack.animationDuration; easing.type: Easing.OutExpo } } Loader { anchors.fill: parent sourceComponent: (tabType === "calendar") ? calendarWidget : todoWidget diff --git a/.config/quickshell/modules/sidebarRight/calendar_layout.js b/.config/quickshell/modules/sidebarRight/calendar_layout.js new file mode 100644 index 000000000..097320acd --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/calendar_layout.js @@ -0,0 +1,95 @@ +const weekDays = [ // MONDAY IS THE FIRST DAY OF THE WEEK :HESRIGHTYOUKNOW: + { day: 'Mo', today: 0 }, + { day: 'Tu', today: 0 }, + { day: 'We', today: 0 }, + { day: 'Th', today: 0 }, + { day: 'Fr', today: 0 }, + { day: 'Sa', today: 0 }, + { day: 'Su', today: 0 }, +] + +function checkLeapYear(year) { + return ( + year % 400 == 0 || + (year % 4 == 0 && year % 100 != 0)); +} + +function getMonthDays(month, year) { + const leapYear = checkLeapYear(year); + if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 31; + if (month == 2 && leapYear) return 29; + if (month == 2 && !leapYear) return 28; + return 30; +} + +function getNextMonthDays(month, year) { + const leapYear = checkLeapYear(year); + if (month == 1 && leapYear) return 29; + if (month == 1 && !leapYear) return 28; + if (month == 12) return 31; + if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 30; + return 31; +} + +function getPrevMonthDays(month, year) { + const leapYear = checkLeapYear(year); + if (month == 3 && leapYear) return 29; + if (month == 3 && !leapYear) return 28; + if (month == 1) return 31; + if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 30; + return 31; +} + +function getCalendarLayout(dateObject, highlight) { + if (!dateObject) dateObject = new Date(); + const weekday = (dateObject.getDay() + 6) % 7; // MONDAY IS THE FIRST DAY OF THE WEEK + const day = dateObject.getDate(); + const month = dateObject.getMonth() + 1; + const year = dateObject.getFullYear(); + const weekdayOfMonthFirst = (weekday + 35 - (day - 1)) % 7; + const daysInMonth = getMonthDays(month, year); + const daysInNextMonth = getNextMonthDays(month, year); + const daysInPrevMonth = getPrevMonthDays(month, year); + + // Fill + var monthDiff = (weekdayOfMonthFirst == 0 ? 0 : -1); + var toFill, dim; + if(weekdayOfMonthFirst == 0) { + toFill = 1; + dim = daysInMonth; + } + else { + toFill = (daysInPrevMonth - (weekdayOfMonthFirst - 1)); + dim = daysInPrevMonth; + } + var calendar = [...Array(6)].map(() => Array(7)); + var i = 0, j = 0; + while (i < 6 && j < 7) { + calendar[i][j] = { + "day": toFill, + "today": ((toFill == day && monthDiff == 0 && highlight) ? 1 : ( + monthDiff == 0 ? 0 : + -1 + )) + }; + // Increment + toFill++; + if (toFill > dim) { // Next month? + monthDiff++; + if (monthDiff == 0) + dim = daysInMonth; + else if (monthDiff == 1) + dim = daysInNextMonth; + toFill = 1; + } + // Next tile + j++; + if (j == 7) { + j = 0; + i++; + } + + } + return calendar; +} + From f5d47335e82aa3fabcd691103e22a3d10736ed35 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 16 Apr 2025 10:44:15 +0200 Subject: [PATCH 029/824] sidebar calendar some anims --- .../sidebarRight/CalendarDayButton.qml | 25 ++++++++++++++++--- .../modules/sidebarRight/SidebarCalendar.qml | 3 ++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/modules/sidebarRight/CalendarDayButton.qml b/.config/quickshell/modules/sidebarRight/CalendarDayButton.qml index 0ec588d02..da4f5c8f5 100644 --- a/.config/quickshell/modules/sidebarRight/CalendarDayButton.qml +++ b/.config/quickshell/modules/sidebarRight/CalendarDayButton.qml @@ -9,6 +9,7 @@ Button { property string day property int isToday property bool bold + property bool interactable: true Layout.fillWidth: false Layout.fillHeight: false @@ -18,12 +19,20 @@ Button { background: Rectangle { anchors.fill: parent radius: Appearance.rounding.full - color: (isToday == 1) ? (button.down ? Appearance.colors.colPrimaryActive : - button.hovered ? Appearance.colors.colPrimaryHover : + color: (isToday == 1) ? ((interactable && button.down) ? Appearance.colors.colPrimaryActive : + (interactable && button.hovered) ? Appearance.colors.colPrimaryHover : Appearance.m3colors.m3primary) : - button.down ? Appearance.colors.colLayer1Active : - button.hovered ? Appearance.colors.colLayer1Hover : + (interactable && button.down) ? Appearance.colors.colLayer1Active : + (interactable && button.hovered) ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.colors.colLayer1, 1) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } } contentItem: StyledText { @@ -34,6 +43,14 @@ Button { color: (isToday == 1) ? Appearance.m3colors.m3onPrimary : (isToday == 0) ? Appearance.colors.colOnLayer1 : Appearance.m3colors.m3outline + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } } } diff --git a/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml b/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml index c70b9a7c6..b85a5ca31 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml @@ -28,7 +28,7 @@ Rectangle { Layout.leftMargin: 15 spacing: 15 Repeater { - model: [ + model: [ {"name": "Calendar", "icon": "calendar_month"}, {"name": "To Do", "icon": "done_outline"} ] @@ -65,6 +65,7 @@ Rectangle { day: modelData.day isToday: modelData.today bold: true + interactable: false } } } From 586c349f1f16bb58e90f1cc1f94e339ae1b32723 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 16 Apr 2025 20:18:22 +0200 Subject: [PATCH 030/824] sidebar calendar done --- .../quickshell/modules/common/Appearance.qml | 4 +- .../quickshell/modules/common/DateTime.qml | 2 + .../common/widgets/SmallCircleButton.qml | 4 +- .../modules/common/widgets/StyledText.qml | 2 +- .../modules/sidebarRight/SidebarCalendar.qml | 122 ++++++++++++++---- .../{ => calendar}/CalendarDayButton.qml | 0 .../calendar/CalendarHeaderButton.qml | 31 +++++ .../{ => calendar}/calendar_layout.js | 20 +++ 8 files changed, 158 insertions(+), 27 deletions(-) rename .config/quickshell/modules/sidebarRight/{ => calendar}/CalendarDayButton.qml (100%) create mode 100644 .config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml rename .config/quickshell/modules/sidebarRight/{ => calendar}/calendar_layout.js (79%) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index de95e5b08..d3c03520a 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -114,8 +114,8 @@ Singleton { property color colOnLayer2: m3colors.m3onSurface; property color colLayer3: mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96); property color colOnLayer3: m3colors.m3onSurface; - property color colLayer1Hover: mix(colLayer1, colOnLayer1, 0.85); - property color colLayer1Active: mix(colLayer1, colOnLayer1, 0.70); + property color colLayer1Hover: mix(colLayer1, colOnLayer1, 0.88); + property color colLayer1Active: mix(colLayer1, colOnLayer1, 0.77); property color colLayer2Hover: mix(colLayer2, colOnLayer2, 0.90); property color colLayer2Active: mix(colLayer2, colOnLayer2, 0.80); property color colLayer3Hover: mix(colLayer3, colOnLayer3, 0.90); diff --git a/.config/quickshell/modules/common/DateTime.qml b/.config/quickshell/modules/common/DateTime.qml index bbcedfb52..3e09be16c 100644 --- a/.config/quickshell/modules/common/DateTime.qml +++ b/.config/quickshell/modules/common/DateTime.qml @@ -6,6 +6,8 @@ pragma Singleton Singleton { property string time: Qt.formatDateTime(clock.date, "hh:mm") property string date: Qt.formatDateTime(clock.date, "dddd, dd/MM") + property string month: Qt.formatDateTime(clock.date, "MMMM") + property string year: Qt.formatDateTime(clock.date, "yyyy") property string uptime: "0h, 0m" SystemClock { diff --git a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml b/.config/quickshell/modules/common/widgets/SmallCircleButton.qml index e1b85dd41..14d7c19e3 100644 --- a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml +++ b/.config/quickshell/modules/common/widgets/SmallCircleButton.qml @@ -11,8 +11,8 @@ Button { required default property Item content property bool extraActiveCondition: false - implicitWidth: 26 - implicitHeight: 26 + implicitHeight: Math.max(content.implicitHeight, 26, content.implicitHeight) + implicitWidth: Math.max(content.implicitHeight, 26, content.implicitWidth) contentItem: content background: Rectangle { diff --git a/.config/quickshell/modules/common/widgets/StyledText.qml b/.config/quickshell/modules/common/widgets/StyledText.qml index 96c75a053..14cec33b0 100644 --- a/.config/quickshell/modules/common/widgets/StyledText.qml +++ b/.config/quickshell/modules/common/widgets/StyledText.qml @@ -7,5 +7,5 @@ Text { verticalAlignment: Text.AlignVCenter font.family: Appearance.font.family.main font.pixelSize: Appearance.font.pixelSize.small - color: Appearance.colors.colOnBackground + color: Appearance.m3colors.m3onBackground } diff --git a/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml b/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml index b85a5ca31..a4e27241a 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml @@ -1,10 +1,11 @@ import "root:/modules/common" import "root:/modules/common/widgets" +import "./calendar" +import "./calendar/calendar_layout.js" as CalendarLayout import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell -import "calendar_layout.js" as CalendarLayout Rectangle { Layout.alignment: Qt.AlignHCenter @@ -12,19 +13,21 @@ Rectangle { Layout.fillWidth: true radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 - implicitHeight: 300 + implicitHeight: 343 // TODO NO HARD CODE RowLayout { id: calendarRow anchors.fill: parent - width: parent.width - 10 * 2 + // width: parent.width - 10 * 2 height: parent.height - 10 * 2 spacing: 10 property int selectedTab: 0 + // Navigation rail ColumnLayout { id: tabBar Layout.fillHeight: true + Layout.fillWidth: false Layout.leftMargin: 15 spacing: 15 Repeater { @@ -42,44 +45,119 @@ Rectangle { } } } + + // Content area StackLayout { id: tabStack Layout.fillWidth: true - Layout.fillHeight: true + // Layout.fillHeight: true + Layout.alignment: Qt.AlignVCenter property int realIndex: 0 property int animationDuration: Appearance.animation.elementDecel.duration * 1.5 // Calendar Component { id: calendarWidget - ColumnLayout { + + Item { anchors.centerIn: parent - spacing: 5 - RowLayout { - Layout.fillWidth: true - Layout.fillHeight: false - spacing: 5 - Repeater { - model: CalendarLayout.weekDays - delegate: CalendarDayButton { - day: modelData.day - isToday: modelData.today - bold: true - interactable: false + width: calendarColumn.width + height: calendarColumn.height + property int monthShift: 0 + property var viewingDate: CalendarLayout.getDateInXMonthsTime(monthShift) + + MouseArea { + anchors.fill: parent + onWheel: { + if (wheel.angleDelta.y > 0) { + monthShift--; + } else if (wheel.angleDelta.y < 0) { + monthShift++; } } } - Repeater { - model: CalendarLayout.getCalendarLayout(null, true) - delegate: RowLayout { + ColumnLayout { + id: calendarColumn + anchors.centerIn: parent + spacing: 5 + + // Calendar header + RowLayout { Layout.fillWidth: true Layout.fillHeight: false spacing: 5 + CalendarHeaderButton { + onClicked: { + monthShift = 0; + } + content: StyledText { + text: `${monthShift != 0 ? "• " : ""}${viewingDate.toLocaleDateString(Qt.locale(), "MMMM yyyy")}` + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer1 + } + } + Item { + Layout.fillWidth: true + Layout.fillHeight: false + } + CalendarHeaderButton { + forceCircle: true + onClicked: { + monthShift--; + } + content: MaterialSymbol { + text: "chevron_left" + font.pixelSize: Appearance.font.pixelSize.large + horizontalAlignment: Text.AlignHCenter + color: Appearance.colors.colOnLayer1 + } + } + CalendarHeaderButton { + forceCircle: true + onClicked: { + monthShift++; + } + content: MaterialSymbol { + text: "chevron_right" + font.pixelSize: Appearance.font.pixelSize.large + horizontalAlignment: Text.AlignHCenter + color: Appearance.colors.colOnLayer1 + } + } + } + + // Week days row + RowLayout { + id: weekDaysRow + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: false + spacing: 5 Repeater { - model: modelData + model: CalendarLayout.weekDays delegate: CalendarDayButton { day: modelData.day isToday: modelData.today + bold: true + interactable: false + } + } + } + + // Real week rows + Repeater { + id: calendarRows + model: CalendarLayout.getCalendarLayout(viewingDate, monthShift === 0) + delegate: RowLayout { + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: false + spacing: 5 + Repeater { + model: modelData + delegate: CalendarDayButton { + day: modelData.day + isToday: modelData.today + } } } } @@ -128,7 +206,7 @@ Rectangle { { type: "calendar" }, { type: "todo" } ] - Item { + Item { // TODO: make behavior on y also act for the item that's switched to id: tabItem property int tabIndex: index property string tabType: modelData.type diff --git a/.config/quickshell/modules/sidebarRight/CalendarDayButton.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml similarity index 100% rename from .config/quickshell/modules/sidebarRight/CalendarDayButton.qml rename to .config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml new file mode 100644 index 000000000..447dc6a37 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml @@ -0,0 +1,31 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Button { + id: button + required default property Item content + property bool forceCircle: false + + implicitHeight: 30 + implicitWidth: forceCircle ? implicitHeight : (contentItem.implicitWidth + 10 * 2) + + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.full + color: (button.down) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } + + } + contentItem: content + +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/calendar_layout.js b/.config/quickshell/modules/sidebarRight/calendar/calendar_layout.js similarity index 79% rename from .config/quickshell/modules/sidebarRight/calendar_layout.js rename to .config/quickshell/modules/sidebarRight/calendar/calendar_layout.js index 097320acd..7f750b411 100644 --- a/.config/quickshell/modules/sidebarRight/calendar_layout.js +++ b/.config/quickshell/modules/sidebarRight/calendar/calendar_layout.js @@ -40,6 +40,26 @@ function getPrevMonthDays(month, year) { return 31; } +function getDateInXMonthsTime(x) { + var currentDate = new Date(); // Get the current date + if (x == 0) return currentDate; // If x is 0, return the current date + + var targetMonth = currentDate.getMonth() + x; // Calculate the target month + var targetYear = currentDate.getFullYear(); // Get the current year + + // Adjust the year and month if necessary + targetYear += Math.floor(targetMonth / 12); + targetMonth = (targetMonth % 12 + 12) % 12; + + // Create a new date object with the target year and month + var targetDate = new Date(targetYear, targetMonth, 1); + + // Set the day to the last day of the month to get the desired date + // targetDate.setDate(0); + + return targetDate; +} + function getCalendarLayout(dateObject, highlight) { if (!dateObject) dateObject = new Date(); const weekday = (dateObject.getDay() + 6) % 7; // MONDAY IS THE FIRST DAY OF THE WEEK From c62b9f8d4b3c0c5f90052b1834a068a0498f4e86 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:48:47 +0200 Subject: [PATCH 031/824] new folder for services, fancy calendar month button --- .config/quickshell/modules/bar/Bar.qml | 1 + .../quickshell/modules/bar/ClockWidget.qml | 1 + .config/quickshell/modules/bar/Media.qml | 1 + .config/quickshell/modules/bar/Resources.qml | 1 + .../quickshell/modules/common/Appearance.qml | 1 + .../modules/common/widgets/StyledToolTip.qml | 17 +++++-- .../modules/sidebarRight/SidebarCalendar.qml | 49 +++++++++---------- .../modules/sidebarRight/SidebarRight.qml | 5 +- .../calendar/CalendarHeaderButton.qml | 20 +++++++- .../{Bluetooth.qml => BluetoothToggle.qml} | 1 + .../{Network.qml => NetworkToggle.qml} | 1 + .../{modules/common => services}/Audio.qml | 0 .../common => services}/Bluetooth.qml | 0 .../common => services}/Brightness.qml | 0 .../{modules/common => services}/DateTime.qml | 0 .../common => services}/MprisController.qml | 0 .../{modules/common => services}/Network.qml | 0 .../common => services}/ResourceUsage.qml | 0 .../common => services}/SystemInfo.qml | 0 19 files changed, 64 insertions(+), 34 deletions(-) rename .config/quickshell/modules/sidebarRight/quickToggles/{Bluetooth.qml => BluetoothToggle.qml} (98%) rename .config/quickshell/modules/sidebarRight/quickToggles/{Network.qml => NetworkToggle.qml} (98%) rename .config/quickshell/{modules/common => services}/Audio.qml (100%) rename .config/quickshell/{modules/common => services}/Bluetooth.qml (100%) rename .config/quickshell/{modules/common => services}/Brightness.qml (100%) rename .config/quickshell/{modules/common => services}/DateTime.qml (100%) rename .config/quickshell/{modules/common => services}/MprisController.qml (100%) rename .config/quickshell/{modules/common => services}/Network.qml (100%) rename .config/quickshell/{modules/common => services}/ResourceUsage.qml (100%) rename .config/quickshell/{modules/common => services}/SystemInfo.qml (100%) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index f451f8744..cbaaad833 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -1,5 +1,6 @@ import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/services" import QtQuick import QtQuick.Controls import QtQuick.Layouts diff --git a/.config/quickshell/modules/bar/ClockWidget.qml b/.config/quickshell/modules/bar/ClockWidget.qml index 6093eaf8b..e44ac7f5a 100644 --- a/.config/quickshell/modules/bar/ClockWidget.qml +++ b/.config/quickshell/modules/bar/ClockWidget.qml @@ -1,5 +1,6 @@ import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/services" import QtQuick import QtQuick.Layouts diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml index c5c29093f..4200a77c5 100644 --- a/.config/quickshell/modules/bar/Media.qml +++ b/.config/quickshell/modules/bar/Media.qml @@ -1,5 +1,6 @@ import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/services" import QtQuick import QtQuick.Layouts import Quickshell diff --git a/.config/quickshell/modules/bar/Resources.qml b/.config/quickshell/modules/bar/Resources.qml index 6d6514632..8de5dcfc1 100644 --- a/.config/quickshell/modules/bar/Resources.qml +++ b/.config/quickshell/modules/bar/Resources.qml @@ -1,5 +1,6 @@ import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/services" import QtQuick import QtQuick.Layouts import Quickshell diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index d3c03520a..4fb519793 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -164,6 +164,7 @@ Singleton { property QtObject elementDecel: QtObject { property int duration: 180 property int type: Easing.OutCirc + property int velocity: 650 } property QtObject menuDecel: QtObject { property int duration: 350 diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index df97fc8e8..98945ceec 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -6,16 +6,25 @@ import QtQuick.Layouts ToolTip { property string content - parent: parent - visible: parent.hovered + property bool extraVisibleCondition: true padding: 7 + + visible: (extraVisibleCondition && parent.hovered) + background: Rectangle { color: Appearance.colors.colTooltip radius: Appearance.rounding.small - width: tooltipText.width + 2 * padding + width: tooltipTextObject.width + 2 * padding + Behavior on opacity { + OpacityAnimator { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + opacity: visible ? 1 : 0 } StyledText { - id: tooltipText + id: tooltipTextObject text: content color: Appearance.colors.colOnTooltip wrapMode: Text.WordWrap diff --git a/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml b/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml index a4e27241a..bf749f0c2 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml @@ -13,13 +13,13 @@ Rectangle { Layout.fillWidth: true radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 - implicitHeight: 343 // TODO NO HARD CODE + // implicitHeight: 343 // TODO NO HARD CODE + height: calendarWidgetRow.height RowLayout { - id: calendarRow - anchors.fill: parent - // width: parent.width - 10 * 2 - height: parent.height - 10 * 2 + id: calendarWidgetRow + anchors.fill: parent + height: tabStack.height spacing: 10 property int selectedTab: 0 @@ -36,11 +36,11 @@ Rectangle { {"name": "To Do", "icon": "done_outline"} ] NavRailButton { - toggled: calendarRow.selectedTab == index + toggled: calendarWidgetRow.selectedTab == index buttonText: modelData.name buttonIcon: modelData.icon onClicked: { - calendarRow.selectedTab = index + calendarWidgetRow.selectedTab = index } } } @@ -50,7 +50,7 @@ Rectangle { StackLayout { id: tabStack Layout.fillWidth: true - // Layout.fillHeight: true + height: 358 // ???? wtf Layout.alignment: Qt.AlignVCenter property int realIndex: 0 property int animationDuration: Appearance.animation.elementDecel.duration * 1.5 @@ -65,6 +65,7 @@ Rectangle { height: calendarColumn.height property int monthShift: 0 property var viewingDate: CalendarLayout.getDateInXMonthsTime(monthShift) + property var calendarLayout: CalendarLayout.getCalendarLayout(viewingDate, monthShift === 0) MouseArea { anchors.fill: parent @@ -84,18 +85,13 @@ Rectangle { // Calendar header RowLayout { Layout.fillWidth: true - Layout.fillHeight: false spacing: 5 CalendarHeaderButton { + buttonText: `${monthShift != 0 ? "• " : ""}${viewingDate.toLocaleDateString(Qt.locale(), "MMMM yyyy")}` + tooltipText: (monthShift === 0) ? "" : "Jump to current month" onClicked: { monthShift = 0; } - content: StyledText { - text: `${monthShift != 0 ? "• " : ""}${viewingDate.toLocaleDateString(Qt.locale(), "MMMM yyyy")}` - horizontalAlignment: Text.AlignHCenter - font.pixelSize: Appearance.font.pixelSize.larger - color: Appearance.colors.colOnLayer1 - } } Item { Layout.fillWidth: true @@ -106,9 +102,9 @@ Rectangle { onClicked: { monthShift--; } - content: MaterialSymbol { + contentItem: MaterialSymbol { text: "chevron_left" - font.pixelSize: Appearance.font.pixelSize.large + font.pixelSize: Appearance.font.pixelSize.larger horizontalAlignment: Text.AlignHCenter color: Appearance.colors.colOnLayer1 } @@ -118,9 +114,9 @@ Rectangle { onClicked: { monthShift++; } - content: MaterialSymbol { + contentItem: MaterialSymbol { text: "chevron_right" - font.pixelSize: Appearance.font.pixelSize.large + font.pixelSize: Appearance.font.pixelSize.larger horizontalAlignment: Text.AlignHCenter color: Appearance.colors.colOnLayer1 } @@ -147,16 +143,17 @@ Rectangle { // Real week rows Repeater { id: calendarRows - model: CalendarLayout.getCalendarLayout(viewingDate, monthShift === 0) + // model: calendarLayout + model: 6 delegate: RowLayout { Layout.alignment: Qt.AlignHCenter Layout.fillHeight: false spacing: 5 Repeater { - model: modelData + model: Array(7).fill(modelData) delegate: CalendarDayButton { - day: modelData.day - isToday: modelData.today + day: calendarLayout[modelData][index].day + isToday: calendarLayout[modelData][index].today } } } @@ -186,10 +183,10 @@ Rectangle { } Connections { - target: calendarRow + target: calendarWidgetRow function onSelectedTabChanged() { delayedStackSwitch.start() - tabStack.realIndex = calendarRow.selectedTab + tabStack.realIndex = calendarWidgetRow.selectedTab } } Timer { @@ -197,7 +194,7 @@ Rectangle { interval: tabStack.animationDuration / 2 repeat: false onTriggered: { - tabStack.currentIndex = calendarRow.selectedTab + tabStack.currentIndex = calendarWidgetRow.selectedTab } } diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index ce343ee24..381cf3977 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -1,5 +1,6 @@ import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/services" import "./quickToggles/" import QtQuick import QtQuick.Controls @@ -129,8 +130,8 @@ Scope { anchors.margins: 5 spacing: 5 - Network {} - Bluetooth {} + NetworkToggle {} + BluetoothToggle {} NightLight {} GameMode {} IdleInhibitor {} diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml index 447dc6a37..babe3a26f 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml @@ -6,12 +6,19 @@ import QtQuick.Layouts Button { id: button - required default property Item content + property string buttonText: "" + property string tooltipText: "" property bool forceCircle: false implicitHeight: 30 implicitWidth: forceCircle ? implicitHeight : (contentItem.implicitWidth + 10 * 2) + Behavior on implicitWidth { + SmoothedAnimation { + velocity: Appearance.animation.elementDecel.velocity + } + } + background: Rectangle { anchors.fill: parent radius: Appearance.rounding.full @@ -26,6 +33,15 @@ Button { } } - contentItem: content + contentItem: StyledText { + text: buttonText + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer1 + } + StyledToolTip { + content: tooltipText + extraVisibleCondition: tooltipText.length > 0 + } } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/Bluetooth.qml b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml similarity index 98% rename from .config/quickshell/modules/sidebarRight/quickToggles/Bluetooth.qml rename to .config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml index 682334957..a02caea38 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/Bluetooth.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml @@ -1,4 +1,5 @@ import "../" +import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" import QtQuick diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/Network.qml b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml similarity index 98% rename from .config/quickshell/modules/sidebarRight/quickToggles/Network.qml rename to .config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml index c3d8173ca..4a15071eb 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/Network.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml @@ -1,5 +1,6 @@ import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/services" import "../" import Quickshell.Io import Quickshell diff --git a/.config/quickshell/modules/common/Audio.qml b/.config/quickshell/services/Audio.qml similarity index 100% rename from .config/quickshell/modules/common/Audio.qml rename to .config/quickshell/services/Audio.qml diff --git a/.config/quickshell/modules/common/Bluetooth.qml b/.config/quickshell/services/Bluetooth.qml similarity index 100% rename from .config/quickshell/modules/common/Bluetooth.qml rename to .config/quickshell/services/Bluetooth.qml diff --git a/.config/quickshell/modules/common/Brightness.qml b/.config/quickshell/services/Brightness.qml similarity index 100% rename from .config/quickshell/modules/common/Brightness.qml rename to .config/quickshell/services/Brightness.qml diff --git a/.config/quickshell/modules/common/DateTime.qml b/.config/quickshell/services/DateTime.qml similarity index 100% rename from .config/quickshell/modules/common/DateTime.qml rename to .config/quickshell/services/DateTime.qml diff --git a/.config/quickshell/modules/common/MprisController.qml b/.config/quickshell/services/MprisController.qml similarity index 100% rename from .config/quickshell/modules/common/MprisController.qml rename to .config/quickshell/services/MprisController.qml diff --git a/.config/quickshell/modules/common/Network.qml b/.config/quickshell/services/Network.qml similarity index 100% rename from .config/quickshell/modules/common/Network.qml rename to .config/quickshell/services/Network.qml diff --git a/.config/quickshell/modules/common/ResourceUsage.qml b/.config/quickshell/services/ResourceUsage.qml similarity index 100% rename from .config/quickshell/modules/common/ResourceUsage.qml rename to .config/quickshell/services/ResourceUsage.qml diff --git a/.config/quickshell/modules/common/SystemInfo.qml b/.config/quickshell/services/SystemInfo.qml similarity index 100% rename from .config/quickshell/modules/common/SystemInfo.qml rename to .config/quickshell/services/SystemInfo.qml From d6914a4ea2b04d757c84b8ba7e81d4035f2428ce Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 17 Apr 2025 01:32:35 +0200 Subject: [PATCH 032/824] sidebar todo --- .config/quickshell/modules/bar/Battery.qml | 2 - .../quickshell/modules/common/Appearance.qml | 5 +- .../modules/common/widgets/NavRailButton.qml | 1 - .../common/widgets/StyledTabButton.qml | 41 ++++ .../sidebarRight/BottomWidgetGroup.qml | 115 +++++++++ .../modules/sidebarRight/SidebarCalendar.qml | 223 ------------------ .../modules/sidebarRight/SidebarRight.qml | 2 +- .../sidebarRight/calendar/CalendarWidget.qml | 107 +++++++++ .../modules/sidebarRight/todo/TaskList.qml | 37 +++ .../modules/sidebarRight/todo/TodoWidget.qml | 82 +++++++ .config/quickshell/services/DateTime.qml | 1 + .config/quickshell/services/ResourceUsage.qml | 1 + .config/quickshell/services/Todo.qml | 59 +++++ 13 files changed, 447 insertions(+), 229 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/StyledTabButton.qml create mode 100644 .config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml delete mode 100644 .config/quickshell/modules/sidebarRight/SidebarCalendar.qml create mode 100644 .config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml create mode 100644 .config/quickshell/modules/sidebarRight/todo/TaskList.qml create mode 100644 .config/quickshell/modules/sidebarRight/todo/TodoWidget.qml create mode 100644 .config/quickshell/services/Todo.qml diff --git a/.config/quickshell/modules/bar/Battery.qml b/.config/quickshell/modules/bar/Battery.qml index 6dc6a92dc..5e5e731b3 100644 --- a/.config/quickshell/modules/bar/Battery.qml +++ b/.config/quickshell/modules/bar/Battery.qml @@ -34,9 +34,7 @@ Rectangle { duration: Appearance.animation.elementDecel.duration easing.type: Appearance.animation.elementDecel.type } - } - } StyledText { diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 4fb519793..b43e29545 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -162,7 +162,7 @@ Singleton { animation: QtObject { property QtObject elementDecel: QtObject { - property int duration: 180 + property int duration: 200 property int type: Easing.OutCirc property int velocity: 650 } @@ -171,8 +171,9 @@ Singleton { property int type: Easing.OutExpo } property QtObject positionShift: QtObject { - property int duration: 160 + property int duration: 300 property int type: Easing.InOutExpo + property int velocity: 650 } } diff --git a/.config/quickshell/modules/common/widgets/NavRailButton.qml b/.config/quickshell/modules/common/widgets/NavRailButton.qml index cc40503fc..935d98063 100644 --- a/.config/quickshell/modules/common/widgets/NavRailButton.qml +++ b/.config/quickshell/modules/common/widgets/NavRailButton.qml @@ -36,7 +36,6 @@ Button { duration: Appearance.animation.elementDecel.duration easing.type: Appearance.animation.elementDecel.type } - } MaterialSymbol { id: navRailButtonIcon diff --git a/.config/quickshell/modules/common/widgets/StyledTabButton.qml b/.config/quickshell/modules/common/widgets/StyledTabButton.qml new file mode 100644 index 000000000..f1dc611e7 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/StyledTabButton.qml @@ -0,0 +1,41 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io + +TabButton { + id: button + property string buttonText + property string buttonIcon + property bool selected: false + height: buttonBackground.height + + background: Rectangle { + id: buttonBackground + radius: Appearance.rounding.small + implicitHeight: 37 + color: (button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.colors.colLayer1Hover, 1)) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } + contentItem: StyledText { + id: buttonTextWidget + anchors.centerIn: buttonBackground + horizontalAlignment: Text.AlignHCenter + text: buttonText + color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml new file mode 100644 index 000000000..9fdd9b580 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -0,0 +1,115 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import "./calendar" +import "./todo" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell + +Rectangle { + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: false + Layout.fillWidth: true + radius: Appearance.rounding.normal + color: Appearance.colors.colLayer1 + // implicitHeight: 343 // TODO NO HARD CODE + height: bottomWidgetGroupRow.height + + RowLayout { + id: bottomWidgetGroupRow + anchors.fill: parent + height: tabStack.height + spacing: 10 + property int selectedTab: 0 + + // Navigation rail + ColumnLayout { + id: tabBar + Layout.fillHeight: true + Layout.fillWidth: false + Layout.leftMargin: 15 + spacing: 15 + Repeater { + model: [ + {"name": "Calendar", "icon": "calendar_month"}, + {"name": "To Do", "icon": "done_outline"} + ] + NavRailButton { + toggled: bottomWidgetGroupRow.selectedTab == index + buttonText: modelData.name + buttonIcon: modelData.icon + onClicked: { + bottomWidgetGroupRow.selectedTab = index + } + } + } + } + + // Content area + StackLayout { + id: tabStack + Layout.fillWidth: true + height: 358 // ???? wtf + Layout.alignment: Qt.AlignVCenter + property int realIndex: 0 + property int animationDuration: Appearance.animation.elementDecel.duration * 1.5 + + // Calendar + Component { + id: calendarWidget + + CalendarWidget { + anchors.centerIn: parent + } + } + + // To Do + Component { + id: todoWidget + TodoWidget { + anchors.fill: parent + anchors.margins: 5 + } + } + + Connections { + target: bottomWidgetGroupRow + function onSelectedTabChanged() { + delayedStackSwitch.start() + tabStack.realIndex = bottomWidgetGroupRow.selectedTab + } + } + Timer { + id: delayedStackSwitch + interval: tabStack.animationDuration / 2 + repeat: false + onTriggered: { + tabStack.currentIndex = bottomWidgetGroupRow.selectedTab + } + } + + Repeater { + model: [ + { type: "calendar" }, + { type: "todo" } + ] + Item { // TODO: make behavior on y also act for the item that's switched to + id: tabItem + property int tabIndex: index + property string tabType: modelData.type + property int animDistance: 5 + opacity: (tabStack.currentIndex === tabItem.tabIndex && tabStack.realIndex === tabItem.tabIndex) ? 1 : 0 + y: (tabStack.realIndex === tabItem.tabIndex) ? 0 : (tabStack.realIndex < tabItem.tabIndex) ? animDistance : -animDistance + Behavior on opacity { NumberAnimation { duration: tabStack.animationDuration / 2; easing.type: Easing.OutCubic } } + Behavior on y { NumberAnimation { duration: tabStack.animationDuration; easing.type: Easing.OutExpo } } + Loader { + anchors.fill: parent + sourceComponent: (tabType === "calendar") ? calendarWidget : todoWidget + } + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml b/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml deleted file mode 100644 index bf749f0c2..000000000 --- a/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml +++ /dev/null @@ -1,223 +0,0 @@ -import "root:/modules/common" -import "root:/modules/common/widgets" -import "./calendar" -import "./calendar/calendar_layout.js" as CalendarLayout -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Quickshell - -Rectangle { - Layout.alignment: Qt.AlignHCenter - Layout.fillHeight: false - Layout.fillWidth: true - radius: Appearance.rounding.normal - color: Appearance.colors.colLayer1 - // implicitHeight: 343 // TODO NO HARD CODE - height: calendarWidgetRow.height - - RowLayout { - id: calendarWidgetRow - anchors.fill: parent - height: tabStack.height - spacing: 10 - property int selectedTab: 0 - - // Navigation rail - ColumnLayout { - id: tabBar - Layout.fillHeight: true - Layout.fillWidth: false - Layout.leftMargin: 15 - spacing: 15 - Repeater { - model: [ - {"name": "Calendar", "icon": "calendar_month"}, - {"name": "To Do", "icon": "done_outline"} - ] - NavRailButton { - toggled: calendarWidgetRow.selectedTab == index - buttonText: modelData.name - buttonIcon: modelData.icon - onClicked: { - calendarWidgetRow.selectedTab = index - } - } - } - } - - // Content area - StackLayout { - id: tabStack - Layout.fillWidth: true - height: 358 // ???? wtf - Layout.alignment: Qt.AlignVCenter - property int realIndex: 0 - property int animationDuration: Appearance.animation.elementDecel.duration * 1.5 - - // Calendar - Component { - id: calendarWidget - - Item { - anchors.centerIn: parent - width: calendarColumn.width - height: calendarColumn.height - property int monthShift: 0 - property var viewingDate: CalendarLayout.getDateInXMonthsTime(monthShift) - property var calendarLayout: CalendarLayout.getCalendarLayout(viewingDate, monthShift === 0) - - MouseArea { - anchors.fill: parent - onWheel: { - if (wheel.angleDelta.y > 0) { - monthShift--; - } else if (wheel.angleDelta.y < 0) { - monthShift++; - } - } - } - ColumnLayout { - id: calendarColumn - anchors.centerIn: parent - spacing: 5 - - // Calendar header - RowLayout { - Layout.fillWidth: true - spacing: 5 - CalendarHeaderButton { - buttonText: `${monthShift != 0 ? "• " : ""}${viewingDate.toLocaleDateString(Qt.locale(), "MMMM yyyy")}` - tooltipText: (monthShift === 0) ? "" : "Jump to current month" - onClicked: { - monthShift = 0; - } - } - Item { - Layout.fillWidth: true - Layout.fillHeight: false - } - CalendarHeaderButton { - forceCircle: true - onClicked: { - monthShift--; - } - contentItem: MaterialSymbol { - text: "chevron_left" - font.pixelSize: Appearance.font.pixelSize.larger - horizontalAlignment: Text.AlignHCenter - color: Appearance.colors.colOnLayer1 - } - } - CalendarHeaderButton { - forceCircle: true - onClicked: { - monthShift++; - } - contentItem: MaterialSymbol { - text: "chevron_right" - font.pixelSize: Appearance.font.pixelSize.larger - horizontalAlignment: Text.AlignHCenter - color: Appearance.colors.colOnLayer1 - } - } - } - - // Week days row - RowLayout { - id: weekDaysRow - Layout.alignment: Qt.AlignHCenter - Layout.fillHeight: false - spacing: 5 - Repeater { - model: CalendarLayout.weekDays - delegate: CalendarDayButton { - day: modelData.day - isToday: modelData.today - bold: true - interactable: false - } - } - } - - // Real week rows - Repeater { - id: calendarRows - // model: calendarLayout - model: 6 - delegate: RowLayout { - Layout.alignment: Qt.AlignHCenter - Layout.fillHeight: false - spacing: 5 - Repeater { - model: Array(7).fill(modelData) - delegate: CalendarDayButton { - day: calendarLayout[modelData][index].day - isToday: calendarLayout[modelData][index].today - } - } - } - } - } - } - } - - // To Do - Component { - id: todoWidget - Item { - anchors.fill: parent - // color: "lavender" - // radius: Appearance.rounding.small - width: 30; height: 30; - StyledText { - anchors.margins: 10 - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - text: "## To Do\n- Lorem ipsum\n- Dolor shit amet\n\nSigma Ohayo rc1 Pro+ Premium Hippuland hi ask vaxry for pleas fix 123 Billions must lorem ipsum ipsum yesterdays tears are tomorrows coom awawawa" - wrapMode: Text.WordWrap - textFormat: Text.MarkdownText - } - } - } - - Connections { - target: calendarWidgetRow - function onSelectedTabChanged() { - delayedStackSwitch.start() - tabStack.realIndex = calendarWidgetRow.selectedTab - } - } - Timer { - id: delayedStackSwitch - interval: tabStack.animationDuration / 2 - repeat: false - onTriggered: { - tabStack.currentIndex = calendarWidgetRow.selectedTab - } - } - - Repeater { - model: [ - { type: "calendar" }, - { type: "todo" } - ] - Item { // TODO: make behavior on y also act for the item that's switched to - id: tabItem - property int tabIndex: index - property string tabType: modelData.type - property int animDistance: 5 - opacity: (tabStack.currentIndex === tabItem.tabIndex && tabStack.realIndex === tabItem.tabIndex) ? 1 : 0 - y: (tabStack.realIndex === tabItem.tabIndex) ? 0 : (tabStack.realIndex < tabItem.tabIndex) ? animDistance : -animDistance - Behavior on opacity { NumberAnimation { duration: tabStack.animationDuration / 2; easing.type: Easing.OutCubic } } - Behavior on y { NumberAnimation { duration: tabStack.animationDuration; easing.type: Easing.OutExpo } } - Loader { - anchors.fill: parent - sourceComponent: (tabType === "calendar") ? calendarWidget : todoWidget - } - } - } - } - } -} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 381cf3977..92ebf0447 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -148,7 +148,7 @@ Scope { } // Calendar - SidebarCalendar {} + BottomWidgetGroup {} } } diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml new file mode 100644 index 000000000..0000e2577 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml @@ -0,0 +1,107 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "./calendar_layout.js" as CalendarLayout +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + property int monthShift: 0 + property var viewingDate: CalendarLayout.getDateInXMonthsTime(monthShift) + property var calendarLayout: CalendarLayout.getCalendarLayout(viewingDate, monthShift === 0) + width: calendarColumn.width + height: calendarColumn.height + + MouseArea { + anchors.fill: parent + onWheel: { + if (wheel.angleDelta.y > 0) { + monthShift--; + } else if (wheel.angleDelta.y < 0) { + monthShift++; + } + } + } + ColumnLayout { + id: calendarColumn + anchors.centerIn: parent + spacing: 5 + + // Calendar header + RowLayout { + Layout.fillWidth: true + spacing: 5 + CalendarHeaderButton { + buttonText: `${monthShift != 0 ? "• " : ""}${viewingDate.toLocaleDateString(Qt.locale(), "MMMM yyyy")}` + tooltipText: (monthShift === 0) ? "" : "Jump to current month" + onClicked: { + monthShift = 0; + } + } + Item { + Layout.fillWidth: true + Layout.fillHeight: false + } + CalendarHeaderButton { + forceCircle: true + onClicked: { + monthShift--; + } + contentItem: MaterialSymbol { + text: "chevron_left" + font.pixelSize: Appearance.font.pixelSize.larger + horizontalAlignment: Text.AlignHCenter + color: Appearance.colors.colOnLayer1 + } + } + CalendarHeaderButton { + forceCircle: true + onClicked: { + monthShift++; + } + contentItem: MaterialSymbol { + text: "chevron_right" + font.pixelSize: Appearance.font.pixelSize.larger + horizontalAlignment: Text.AlignHCenter + color: Appearance.colors.colOnLayer1 + } + } + } + + // Week days row + RowLayout { + id: weekDaysRow + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: false + spacing: 5 + Repeater { + model: CalendarLayout.weekDays + delegate: CalendarDayButton { + day: modelData.day + isToday: modelData.today + bold: true + interactable: false + } + } + } + + // Real week rows + Repeater { + id: calendarRows + // model: calendarLayout + model: 6 + delegate: RowLayout { + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: false + spacing: 5 + Repeater { + model: Array(7).fill(modelData) + delegate: CalendarDayButton { + day: calendarLayout[modelData][index].day + isToday: calendarLayout[modelData][index].today + } + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml new file mode 100644 index 000000000..8248a14ca --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -0,0 +1,37 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + required property var taskList; + + Flickable { + anchors.fill: parent + contentHeight: column.height + clip: true + + ColumnLayout { + id: column + width: parent.width + Repeater { + model: taskList + delegate: Rectangle { + Layout.fillWidth: true + width: parent.width + height: 40 + color: Appearance.colors.colLayer2 + radius: Appearance.rounding.small + Text { + text: modelData.content + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 8 + } + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml new file mode 100644 index 000000000..b7923d45a --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -0,0 +1,82 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + property int currentTab: 0 + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + TabBar { + id: tabBar + property var tabButtonList: [{"icon": "checklist", "name": "Unfinished"}, {"name": "Done", "icon": "check_circle"}] + Layout.fillWidth: true + currentIndex: currentTab + onCurrentIndexChanged: currentTab = currentIndex + + background: Item { + WheelHandler { + onWheel: (event) => { + if (event.angleDelta.y < 0) + currentTab = Math.min(currentTab + 1, tabBar.tabButtonList.length - 1) + else if (event.angleDelta.y > 0) + currentTab = Math.max(currentTab - 1, 0) + } + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } + } + + Repeater { + model: tabBar.tabButtonList + delegate: StyledTabButton { + selected: (index == currentTab) + buttonText: modelData.name + // buttonIcon: modelData.icon + } + } + } + + Item { + Layout.fillWidth: true + Rectangle { + property int indicatorPadding: 15 + id: indicator + color: Appearance.m3colors.m3primary + height: 3 + radius: Appearance.rounding.full + + width: tabBar.width / tabBar.tabButtonList.length - indicatorPadding * 2 + x: indicatorPadding + tabBar.width / tabBar.tabButtonList.length * currentTab + z: 2 + Behavior on x { SmoothedAnimation { + velocity: Appearance.animation.positionShift.velocity + } } + + } + } + + SwipeView { + id: swipeView + Layout.topMargin: 10 + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + currentIndex: currentTab + onCurrentIndexChanged: currentTab = currentIndex + + // To Do tab + TaskList { + taskList: Todo.list.filter(item => !item.done) + } + TaskList { + taskList: Todo.list.filter(item => item.done) + } + + } + } +} \ No newline at end of file diff --git a/.config/quickshell/services/DateTime.qml b/.config/quickshell/services/DateTime.qml index 3e09be16c..fc1c501be 100644 --- a/.config/quickshell/services/DateTime.qml +++ b/.config/quickshell/services/DateTime.qml @@ -1,3 +1,4 @@ +import "root:/modules/common" import QtQuick import Quickshell import Quickshell.Io diff --git a/.config/quickshell/services/ResourceUsage.qml b/.config/quickshell/services/ResourceUsage.qml index 6326baceb..239217a6f 100644 --- a/.config/quickshell/services/ResourceUsage.qml +++ b/.config/quickshell/services/ResourceUsage.qml @@ -1,3 +1,4 @@ +import "root:/modules/common" pragma Singleton import QtQuick import Quickshell diff --git a/.config/quickshell/services/Todo.qml b/.config/quickshell/services/Todo.qml new file mode 100644 index 000000000..7bd82e76d --- /dev/null +++ b/.config/quickshell/services/Todo.qml @@ -0,0 +1,59 @@ +pragma Singleton + +import Quickshell; +import Quickshell.Io; +import Quickshell.Services.Pipewire; +import Qt.labs.platform +import QtQuick; + +Singleton { + id: root + property var filePath: `${StandardPaths.standardLocations(StandardPaths.StateLocation)[0]}/user/todo.json` + property var list: [] + + function addItem(item) { + list.push(item) + todoFileView.setText(JSON.stringify(root.list)) + } + + function markDone(index) { + if (index >= 0 && index < list.length) { + list[index].done = true + todoFileView.setText(JSON.stringify(root.list)) + } + } + + function deleteItem(index) { + if (index >= 0 && index < list.length) { + list.splice(index, 1) + todoFileView.setText(JSON.stringify(root.list)) + } + } + + function refresh() { + todoFileView.reload() + } + + Component.onCompleted: { + refresh() + } + + FileView { + id: todoFileView + path: filePath + onLoaded: { + const fileContents = todoFileView.text() + root.list = JSON.parse(fileContents) + } + onLoadFailed: (error) => { + if(error == FileViewError.FileNotFound) { + console.log("[To Do] File not found, creating new file.") + root.list = [] + todoFileView.setText(JSON.stringify(root.list)) + } else { + console.log("[To Do] Error loading file: " + error) + } + } + } +} + From 4db72d941e02fefe51a216f80c4e9b83c0aa1be8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 17 Apr 2025 12:31:51 +0200 Subject: [PATCH 033/824] todo list --- .config/quickshell/modules/bar/Media.qml | 2 +- .../quickshell/modules/common/Appearance.qml | 5 + .../common/widgets/StyledTabButton.qml | 37 ++++-- .../modules/common/widgets/StyledToolTip.qml | 2 +- .../sidebarRight/BottomWidgetGroup.qml | 72 +++++------ .../sidebarRight/calendar/CalendarWidget.qml | 4 +- .../modules/sidebarRight/todo/TaskList.qml | 115 ++++++++++++++++-- .../todo/TodoItemActionButton.qml | 46 +++++++ .../modules/sidebarRight/todo/TodoWidget.qml | 10 +- .config/quickshell/services/Todo.qml | 13 ++ .config/quickshell/shell.qml | 17 +-- 11 files changed, 249 insertions(+), 74 deletions(-) create mode 100644 .config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml index 4200a77c5..434a8a24d 100644 --- a/.config/quickshell/modules/bar/Media.qml +++ b/.config/quickshell/modules/bar/Media.qml @@ -70,7 +70,7 @@ Item { } StyledText { - width: rowLayout.width - (CircularProgress.size + rowLayout.spacing * 2) // TODO ADJUST THIS + width: rowLayout.width - (CircularProgress.size + rowLayout.spacing * 2) Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true // Ensures the text takes up available space Layout.rightMargin: rowLayout.spacing diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index b43e29545..8929e348f 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -166,6 +166,11 @@ Singleton { property int type: Easing.OutCirc property int velocity: 650 } + property QtObject elementDecelFast: QtObject { + property int duration: 140 + property int type: Easing.OutCirc + property int velocity: 750 + } property QtObject menuDecel: QtObject { property int duration: 350 property int type: Easing.OutExpo diff --git a/.config/quickshell/modules/common/widgets/StyledTabButton.qml b/.config/quickshell/modules/common/widgets/StyledTabButton.qml index f1dc611e7..47d447b78 100644 --- a/.config/quickshell/modules/common/widgets/StyledTabButton.qml +++ b/.config/quickshell/modules/common/widgets/StyledTabButton.qml @@ -4,6 +4,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell.Io +import Quickshell.Widgets TabButton { id: button @@ -25,16 +26,34 @@ TabButton { } } } - contentItem: StyledText { - id: buttonTextWidget + contentItem: Item { anchors.centerIn: buttonBackground - horizontalAlignment: Text.AlignHCenter - text: buttonText - color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 - Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + RowLayout { + anchors.centerIn: parent + spacing: 0 + MaterialSymbol { + Layout.rightMargin: 5 + text: buttonIcon + font.pixelSize: Appearance.font.pixelSize.larger + color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } + StyledText { + id: buttonTextWidget + horizontalAlignment: Text.AlignHCenter + text: buttonText + color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } } } } diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index 98945ceec..388a4bad7 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -27,6 +27,6 @@ ToolTip { id: tooltipTextObject text: content color: Appearance.colors.colOnTooltip - wrapMode: Text.WordWrap + wrapMode: Text.Wrap } } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index 9fdd9b580..162077f53 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -9,20 +9,41 @@ import QtQuick.Layouts import Quickshell Rectangle { + id: root Layout.alignment: Qt.AlignHCenter Layout.fillHeight: false Layout.fillWidth: true radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 - // implicitHeight: 343 // TODO NO HARD CODE height: bottomWidgetGroupRow.height + property int selectedTab: 0 + property var tabs: [ + {"type": "calendar", "name": "Calendar", "icon": "calendar_month", "widget": calendarWidget}, + {"type": "todo", "name": "To Do", "icon": "done_outline", "widget": todoWidget} + ] + // Calendar + Component { + id: calendarWidget + + CalendarWidget { + anchors.centerIn: parent + } + } + + // To Do + Component { + id: todoWidget + TodoWidget { + anchors.fill: parent + anchors.margins: 5 + } + } RowLayout { id: bottomWidgetGroupRow anchors.fill: parent height: tabStack.height spacing: 10 - property int selectedTab: 0 // Navigation rail ColumnLayout { @@ -32,16 +53,13 @@ Rectangle { Layout.leftMargin: 15 spacing: 15 Repeater { - model: [ - {"name": "Calendar", "icon": "calendar_month"}, - {"name": "To Do", "icon": "done_outline"} - ] + model: root.tabs NavRailButton { - toggled: bottomWidgetGroupRow.selectedTab == index + toggled: root.selectedTab == index buttonText: modelData.name buttonIcon: modelData.icon onClicked: { - bottomWidgetGroupRow.selectedTab = index + root.selectedTab = index } } } @@ -51,34 +69,17 @@ Rectangle { StackLayout { id: tabStack Layout.fillWidth: true - height: 358 // ???? wtf + height: tabStack.children[0]?.tabLoader?.implicitHeight // TODO: make this less stupid Layout.alignment: Qt.AlignVCenter property int realIndex: 0 property int animationDuration: Appearance.animation.elementDecel.duration * 1.5 - // Calendar - Component { - id: calendarWidget - - CalendarWidget { - anchors.centerIn: parent - } - } - - // To Do - Component { - id: todoWidget - TodoWidget { - anchors.fill: parent - anchors.margins: 5 - } - } - + // Switch the tab on halfway of the anim duration Connections { - target: bottomWidgetGroupRow + target: root function onSelectedTabChanged() { delayedStackSwitch.start() - tabStack.realIndex = bottomWidgetGroupRow.selectedTab + tabStack.realIndex = root.selectedTab } } Timer { @@ -86,27 +87,28 @@ Rectangle { interval: tabStack.animationDuration / 2 repeat: false onTriggered: { - tabStack.currentIndex = bottomWidgetGroupRow.selectedTab + tabStack.currentIndex = root.selectedTab } } Repeater { - model: [ - { type: "calendar" }, - { type: "todo" } - ] + model: tabs Item { // TODO: make behavior on y also act for the item that's switched to id: tabItem property int tabIndex: index property string tabType: modelData.type property int animDistance: 5 + property var tabLoader: tabLoader + // Opacity: show up only when being animated to opacity: (tabStack.currentIndex === tabItem.tabIndex && tabStack.realIndex === tabItem.tabIndex) ? 1 : 0 + // Y: starts animating when user selects a different tab y: (tabStack.realIndex === tabItem.tabIndex) ? 0 : (tabStack.realIndex < tabItem.tabIndex) ? animDistance : -animDistance Behavior on opacity { NumberAnimation { duration: tabStack.animationDuration / 2; easing.type: Easing.OutCubic } } Behavior on y { NumberAnimation { duration: tabStack.animationDuration; easing.type: Easing.OutExpo } } Loader { + id: tabLoader anchors.fill: parent - sourceComponent: (tabType === "calendar") ? calendarWidget : todoWidget + sourceComponent: modelData.widget } } } diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml index 0000e2577..8da0e1e07 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml @@ -6,11 +6,13 @@ import QtQuick.Controls import QtQuick.Layouts Item { + // Layout.topMargin: 10 + anchors.topMargin: 10 property int monthShift: 0 property var viewingDate: CalendarLayout.getDateInXMonthsTime(monthShift) property var calendarLayout: CalendarLayout.getCalendarLayout(viewingDate, monthShift === 0) width: calendarColumn.width - height: calendarColumn.height + implicitHeight: calendarColumn.height + 10 * 2 MouseArea { anchors.fill: parent diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml index 8248a14ca..0edab9613 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -6,29 +6,120 @@ import QtQuick.Controls import QtQuick.Layouts Item { + id: root required property var taskList; + property int todoListItemSpacing: 5 + property int todoListItemPadding: 8 - Flickable { + Flickable { // Scrolled window anchors.fill: parent - contentHeight: column.height + contentHeight: columnLayout.height clip: true ColumnLayout { - id: column + id: columnLayout width: parent.width + spacing: 0 Repeater { model: taskList - delegate: Rectangle { + delegate: Item { + id: todoItem + property bool pendingDoneToggle: false + property bool pendingDelete: false + Layout.fillWidth: true - width: parent.width - height: 40 - color: Appearance.colors.colLayer2 - radius: Appearance.rounding.small - Text { - text: modelData.content - anchors.verticalCenter: parent.verticalCenter + implicitHeight: todoItemRectangle.implicitHeight + todoListItemSpacing + height: implicitHeight + clip: true + + // Behavior on implicitHeight { + // NumberAnimation { + // duration: Appearance.animation.elementDecel.duration + // easing.type: Appearance.animation.elementDecel.type + // } + // } + + function startAction() { + todoItem.implicitHeight = 0 + actionTimer.start() + } + + Timer { + id: actionTimer + interval: Appearance.animation.elementDecelFast.duration + ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + onTriggered: { + if (todoItem.pendingDelete) { + Todo.deleteItem(modelData.originalIndex) + } else if (todoItem.pendingDoneToggle) { + if (!modelData.done) Todo.markDone(modelData.originalIndex) + else Todo.markUnfinished(modelData.originalIndex) + } + } + } + + Rectangle { + id: todoItemRectangle anchors.left: parent.left - anchors.leftMargin: 8 + anchors.right: parent.right + anchors.bottom: parent.bottom + implicitHeight: todoContentRowLayout.implicitHeight + todoListItemPadding * 2 + color: Appearance.colors.colLayer2 + radius: Appearance.rounding.small + ColumnLayout { + id: todoContentRowLayout + anchors.left: parent.left + anchors.right: parent.right + + StyledText { + Layout.fillWidth: true // Needed for wrapping + Layout.leftMargin: 10 + Layout.rightMargin: 10 + Layout.topMargin: todoListItemPadding + id: todoContentText + text: modelData.content + wrapMode: Text.Wrap + } + RowLayout { + Layout.leftMargin: 10 + Layout.rightMargin: 10 + Item { + Layout.fillWidth: true + } + // layoutDirection: Qt.RightToLeft + TodoItemActionButton { + Layout.fillWidth: false + onClicked: { + todoItem.pendingDoneToggle = true + todoItem.startAction() + // if (!modelData.done) Todo.markDone(modelData.originalIndex) + // else Todo.markUnfinished(modelData.originalIndex) + } + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: modelData.done ? "remove_done" : "check" + font.pixelSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer1 + } + } + TodoItemActionButton { + Layout.fillWidth: false + onClicked: { + todoItem.pendingDelete = true + todoItem.startAction() + // Todo.deleteItem(modelData.originalIndex) + } + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: "delete_forever" + font.pixelSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer1 + } + } + } + } } } } diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml b/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml new file mode 100644 index 000000000..4bfdf61fe --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml @@ -0,0 +1,46 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Button { + id: button + property string buttonText: "" + property string tooltipText: "" + + implicitHeight: 30 + implicitWidth: implicitHeight + + Behavior on implicitWidth { + SmoothedAnimation { + velocity: Appearance.animation.elementDecel.velocity + } + } + + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.full + color: (button.down) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.transparentize(Appearance.colors.colLayer2, 1)) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } + + } + contentItem: StyledText { + text: buttonText + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer1 + } + + StyledToolTip { + content: tooltipText + extraVisibleCondition: tooltipText.length > 0 + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index b7923d45a..f7ec48ee1 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -36,7 +36,7 @@ Item { delegate: StyledTabButton { selected: (index == currentTab) buttonText: modelData.name - // buttonIcon: modelData.icon + buttonIcon: modelData.icon } } } @@ -71,10 +71,14 @@ Item { // To Do tab TaskList { - taskList: Todo.list.filter(item => !item.done) + taskList: Todo.list + .map(function(item, i) { return Object.assign({}, item, {originalIndex: i}); }) + .filter(function(item) { return !item.done; }) } TaskList { - taskList: Todo.list.filter(item => item.done) + taskList: Todo.list + .map(function(item, i) { return Object.assign({}, item, {originalIndex: i}); }) + .filter(function(item) { return item.done; }) } } diff --git a/.config/quickshell/services/Todo.qml b/.config/quickshell/services/Todo.qml index 7bd82e76d..96b90de95 100644 --- a/.config/quickshell/services/Todo.qml +++ b/.config/quickshell/services/Todo.qml @@ -19,6 +19,17 @@ Singleton { function markDone(index) { if (index >= 0 && index < list.length) { list[index].done = true + // Reassign to trigger onListChanged + root.list = list.slice(0) + todoFileView.setText(JSON.stringify(root.list)) + } + } + + function markUnfinished(index) { + if (index >= 0 && index < list.length) { + list[index].done = false + // Reassign to trigger onListChanged + root.list = list.slice(0) todoFileView.setText(JSON.stringify(root.list)) } } @@ -26,6 +37,8 @@ Singleton { function deleteItem(index) { if (index >= 0 && index < list.length) { list.splice(index, 1) + // Reassign to trigger onListChanged + root.list = list.slice(0) todoFileView.setText(JSON.stringify(root.list)) } } diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index d253053d6..cda323564 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -10,16 +10,9 @@ import QtQuick.Window import Quickshell ShellRoot { - Bar { - } - - SidebarRight { - } - - ScreenCorners { - } - - ReloadPopup { - } - + Bar {} + SidebarRight {} + ScreenCorners {} + ReloadPopup {} } + From 1bb4bf83720d46b4d0f67fea552c43a3b558dfe6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 17 Apr 2025 12:41:52 +0200 Subject: [PATCH 034/824] todo list proper anims --- .../modules/sidebarRight/todo/TaskList.qml | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml index 0edab9613..25cb0e218 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -26,27 +26,30 @@ Item { id: todoItem property bool pendingDoneToggle: false property bool pendingDelete: false + property bool enableHeightAnimation: false Layout.fillWidth: true implicitHeight: todoItemRectangle.implicitHeight + todoListItemSpacing height: implicitHeight clip: true - // Behavior on implicitHeight { - // NumberAnimation { - // duration: Appearance.animation.elementDecel.duration - // easing.type: Appearance.animation.elementDecel.type - // } - // } + Behavior on implicitHeight { + enabled: enableHeightAnimation + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } function startAction() { + enableHeightAnimation = true todoItem.implicitHeight = 0 actionTimer.start() } Timer { id: actionTimer - interval: Appearance.animation.elementDecelFast.duration + ConfigOptions.hacks.arbitraryRaceConditionDelay + interval: Appearance.animation.elementDecelFast.duration repeat: false onTriggered: { if (todoItem.pendingDelete) { @@ -63,7 +66,7 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom - implicitHeight: todoContentRowLayout.implicitHeight + todoListItemPadding * 2 + implicitHeight: todoContentRowLayout.implicitHeight color: Appearance.colors.colLayer2 radius: Appearance.rounding.small ColumnLayout { @@ -83,6 +86,7 @@ Item { RowLayout { Layout.leftMargin: 10 Layout.rightMargin: 10 + Layout.bottomMargin: todoListItemPadding Item { Layout.fillWidth: true } @@ -92,8 +96,6 @@ Item { onClicked: { todoItem.pendingDoneToggle = true todoItem.startAction() - // if (!modelData.done) Todo.markDone(modelData.originalIndex) - // else Todo.markUnfinished(modelData.originalIndex) } contentItem: MaterialSymbol { anchors.centerIn: parent @@ -108,7 +110,6 @@ Item { onClicked: { todoItem.pendingDelete = true todoItem.startAction() - // Todo.deleteItem(modelData.originalIndex) } contentItem: MaterialSymbol { anchors.centerIn: parent From 1d9b543f57d2f647313e91aff95d00d7ab46600f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 17 Apr 2025 16:27:03 +0200 Subject: [PATCH 035/824] todo widget: add ability to add items --- .../quickshell/modules/common/Appearance.qml | 12 +- .../modules/common/widgets/DialogButton.qml | 48 +++++ .../sidebarRight/BottomWidgetGroup.qml | 44 ++-- .../modules/sidebarRight/SidebarRight.qml | 2 +- .../sidebarRight/calendar/CalendarWidget.qml | 14 +- .../modules/sidebarRight/todo/TaskList.qml | 33 +++ .../modules/sidebarRight/todo/TodoWidget.qml | 203 +++++++++++++++++- .config/quickshell/services/Todo.qml | 10 + 8 files changed, 340 insertions(+), 26 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/DialogButton.qml diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 8929e348f..e9635fbeb 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -120,9 +120,11 @@ Singleton { property color colLayer2Active: mix(colLayer2, colOnLayer2, 0.80); property color colLayer3Hover: mix(colLayer3, colOnLayer3, 0.90); property color colLayer3Active: mix(colLayer3, colOnLayer3, 0.80); - property color colPrimaryHover: mix(m3colors.m3primary, colLayer1Hover, 0.7) - property color colPrimaryActive: mix(m3colors.m3primary, colLayer1Active, 0.4) - property color colSecondaryHover: mix(m3colors.m3secondary, colLayer1Hover, 0.7) + property color colPrimaryHover: mix(m3colors.m3primary, colLayer1Hover, 0.85) + property color colPrimaryActive: mix(m3colors.m3primary, colLayer1Active, 0.7) + property color colPrimaryContainerHover: mix(m3colors.m3primaryContainer, colLayer1Hover, 0.7) + property color colPrimaryContainerActive: mix(m3colors.m3primaryContainer, colLayer1Active, 0.6) + property color colSecondaryHover: mix(m3colors.m3secondary, colLayer1Hover, 0.85) property color colSecondaryActive: mix(m3colors.m3secondary, colLayer1Active, 0.4) property color colSecondaryContainerHover: mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.8) property color colSecondaryContainerActive: mix(m3colors.m3secondaryContainer, colLayer1Active, 0.6) @@ -144,7 +146,7 @@ Singleton { property QtObject family: QtObject { property string main: "Rubik" property string title: "Rubik" - property string iconMaterial: "Material Symbols Rounded" + property string iconMaterial: "Material Symbols Outlined" property string iconNerd: "SpaceMono NF" property string monospace: "JetBrains Mono NF" property string reading: "Readex Pro" @@ -189,6 +191,8 @@ Singleton { property int sidebarWidth: 450 property int hyprlandGapsOut: 5 property int elevationMargin: 7 + property int fabShadowRadius: 5 + property int fabHoveredShadowRadius: 7 } } diff --git a/.config/quickshell/modules/common/widgets/DialogButton.qml b/.config/quickshell/modules/common/widgets/DialogButton.qml new file mode 100644 index 000000000..46dfa16d8 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/DialogButton.qml @@ -0,0 +1,48 @@ +import "root:/modules/common" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io + +Button { + id: button + + property string buttonText + implicitHeight: 30 + implicitWidth: buttonTextWidget.implicitWidth + 15 * 2 + + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.full + color: (button.down && button.enabled) ? Appearance.colors.colLayer1Active : ((button.hovered && button.enabled) ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.m3colors.m3surfaceContainerHigh, 1)) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } + + } + + contentItem: StyledText { + id: buttonTextWidget + anchors.fill: parent + anchors.leftMargin: 15 + anchors.rightMargin: 15 + text: buttonText + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.normal + color: button.enabled ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } + +} diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index 162077f53..8479c2f49 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -21,21 +21,16 @@ Rectangle { {"type": "calendar", "name": "Calendar", "icon": "calendar_month", "widget": calendarWidget}, {"type": "todo", "name": "To Do", "icon": "done_outline", "widget": todoWidget} ] - // Calendar - Component { - id: calendarWidget - CalendarWidget { - anchors.centerIn: parent - } - } - - // To Do - Component { - id: todoWidget - TodoWidget { - anchors.fill: parent - anchors.margins: 5 + Keys.onPressed: (event) => { + if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) + && event.modifiers === Qt.ControlModifier) { + if (event.key === Qt.Key_PageDown) { + root.selectedTab = Math.min(root.selectedTab + 1, root.tabs.length - 1) + } else if (event.key === Qt.Key_PageUp) { + root.selectedTab = Math.max(root.selectedTab - 1, 0) + } + event.accepted = true; } } @@ -72,7 +67,7 @@ Rectangle { height: tabStack.children[0]?.tabLoader?.implicitHeight // TODO: make this less stupid Layout.alignment: Qt.AlignVCenter property int realIndex: 0 - property int animationDuration: Appearance.animation.elementDecel.duration * 1.5 + property int animationDuration: Appearance.animation.elementDecelFast.duration * 1.5 // Switch the tab on halfway of the anim duration Connections { @@ -109,9 +104,28 @@ Rectangle { id: tabLoader anchors.fill: parent sourceComponent: modelData.widget + focus: root.selectedTab === tabItem.tabIndex } } } } } + + // Calendar component + Component { + id: calendarWidget + + CalendarWidget { + anchors.centerIn: parent + } + } + + // To Do component + Component { + id: todoWidget + TodoWidget { + anchors.fill: parent + anchors.margins: 5 + } + } } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 92ebf0447..086dfb33e 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -76,7 +76,7 @@ Scope { radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 focus: true - Keys.onPressed: { + Keys.onPressed: (event) => { if (event.key === Qt.Key_Escape) { sidebarRoot.visible = false; event.accepted = true; // Prevent further propagation of the event diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml index 8da0e1e07..13eb59182 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml @@ -14,9 +14,20 @@ Item { width: calendarColumn.width implicitHeight: calendarColumn.height + 10 * 2 + Keys.onPressed: (event) => { + if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) + && event.modifiers === Qt.NoModifier) { + if (event.key === Qt.Key_PageDown) { + monthShift++; + } else if (event.key === Qt.Key_PageUp) { + monthShift--; + } + event.accepted = true; + } + } MouseArea { anchors.fill: parent - onWheel: { + onWheel: (event) => { if (wheel.angleDelta.y > 0) { monthShift--; } else if (wheel.angleDelta.y < 0) { @@ -24,6 +35,7 @@ Item { } } } + ColumnLayout { id: calendarColumn anchors.centerIn: parent diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml index 25cb0e218..16e773465 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -8,10 +8,14 @@ import QtQuick.Layouts Item { id: root required property var taskList; + property string emptyPlaceholderIcon + property string emptyPlaceholderText property int todoListItemSpacing: 5 property int todoListItemPadding: 8 + property int listBottomPadding: 80 Flickable { // Scrolled window + id: flickable anchors.fill: parent contentHeight: columnLayout.height clip: true @@ -123,6 +127,35 @@ Item { } } } + + } + // Bottom padding + Item { + implicitHeight: listBottomPadding + } + } + } + // Placeholder when list is empty + Item { + visible: taskList.length === 0 + anchors.fill: parent + + ColumnLayout { + anchors.centerIn: parent + spacing: 5 + + MaterialSymbol { + Layout.alignment: Qt.AlignHCenter + text: emptyPlaceholderIcon + font.pixelSize: 55 + color: Appearance.m3colors.m3outline + } + StyledText { + Layout.alignment: Qt.AlignHCenter + text: emptyPlaceholderText + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3outline + horizontalAlignment: Text.AlignHCenter } } } diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index f7ec48ee1..539db74eb 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -4,9 +4,37 @@ import "root:/services" import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Qt5Compat.GraphicalEffects Item { + id: root property int currentTab: 0 + property var tabButtonList: [{"icon": "checklist", "name": "Unfinished"}, {"name": "Done", "icon": "check_circle"}] + property bool showAddDialog: false + property int dialogMargins: 25 + property int fabSize: 48 + property int fabMargins: 14 + + Keys.onPressed: (event) => { + if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { + if (event.key === Qt.Key_PageDown) { + currentTab = Math.min(currentTab + 1, root.tabButtonList.length - 1) + } else if (event.key === Qt.Key_PageUp) { + currentTab = Math.max(currentTab - 1, 0) + } + event.accepted = true; + } + // Open add dialog on "N" (any modifiers) + else if (event.key === Qt.Key_N) { + root.showAddDialog = true + event.accepted = true; + } + // Close dialog on Esc if open + else if (event.key === Qt.Key_Escape && root.showAddDialog) { + root.showAddDialog = false + event.accepted = true; + } + } ColumnLayout { anchors.fill: parent @@ -14,7 +42,6 @@ Item { TabBar { id: tabBar - property var tabButtonList: [{"icon": "checklist", "name": "Unfinished"}, {"name": "Done", "icon": "check_circle"}] Layout.fillWidth: true currentIndex: currentTab onCurrentIndexChanged: currentTab = currentIndex @@ -23,7 +50,7 @@ Item { WheelHandler { onWheel: (event) => { if (event.angleDelta.y < 0) - currentTab = Math.min(currentTab + 1, tabBar.tabButtonList.length - 1) + currentTab = Math.min(currentTab + 1, root.tabButtonList.length - 1) else if (event.angleDelta.y > 0) currentTab = Math.max(currentTab - 1, 0) } @@ -32,7 +59,7 @@ Item { } Repeater { - model: tabBar.tabButtonList + model: root.tabButtonList delegate: StyledTabButton { selected: (index == currentTab) buttonText: modelData.name @@ -50,8 +77,8 @@ Item { height: 3 radius: Appearance.rounding.full - width: tabBar.width / tabBar.tabButtonList.length - indicatorPadding * 2 - x: indicatorPadding + tabBar.width / tabBar.tabButtonList.length * currentTab + width: tabBar.width / root.tabButtonList.length - indicatorPadding * 2 + x: indicatorPadding + tabBar.width / root.tabButtonList.length * currentTab z: 2 Behavior on x { SmoothedAnimation { velocity: Appearance.animation.positionShift.velocity @@ -71,11 +98,17 @@ Item { // To Do tab TaskList { + listBottomPadding: root.fabSize + root.fabMargins * 2 + emptyPlaceholderIcon: "check_circle" + emptyPlaceholderText: "Nothing here!" taskList: Todo.list .map(function(item, i) { return Object.assign({}, item, {originalIndex: i}); }) .filter(function(item) { return !item.done; }) } TaskList { + listBottomPadding: root.fabSize + root.fabMargins * 2 + emptyPlaceholderIcon: "checklist" + emptyPlaceholderText: "Finished tasks will go here" taskList: Todo.list .map(function(item, i) { return Object.assign({}, item, {originalIndex: i}); }) .filter(function(item) { return item.done; }) @@ -83,4 +116,164 @@ Item { } } + + // + FAB + Button { + id: fabButton + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.rightMargin: root.fabMargins + anchors.bottomMargin: root.fabMargins + + width: root.fabSize + height: root.fabSize + + onClicked: root.showAddDialog = true + + background: Rectangle { + id: fabBackground + anchors.fill: parent + radius: Appearance.rounding.small + color: (fabButton.down) ? Appearance.colors.colPrimaryContainerActive : (fabButton.hovered ? Appearance.colors.colPrimaryContainerHover : Appearance.m3colors.m3primaryContainer) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } + + } + + DropShadow { + id: fabShadow + anchors.fill: fabBackground + source: fabBackground + horizontalOffset: 0 + verticalOffset: fabButton.hovered ? 4 : 2 + radius: fabButton.hovered ? Appearance.sizes.fabHoveredShadowRadius : Appearance.sizes.fabShadowRadius + samples: fabShadow.radius * 2 + 1 + color: Appearance.transparentize(Appearance.m3colors.m3shadow, 0.55) + z: fabBackground.z - 1 + + Behavior on verticalOffset { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } + } + + contentItem: MaterialSymbol { + text: "add" + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.huge + color: Appearance.m3colors.m3onPrimaryContainer + } + } + + Item { + anchors.fill: parent + visible: root.showAddDialog + z: 1000 + + onVisibleChanged: { + if (!visible) { + todoInput.text = "" + fabButton.focus = true + } + } + + Rectangle { // Scrim + anchors.fill: parent + radius: Appearance.rounding.small + color: "#80000000" + MouseArea { + hoverEnabled: true + anchors.fill: parent + preventStealing: true + propagateComposedEvents: false + } + } + + Rectangle { // The dialog + id: dialog + implicitWidth: parent.width - dialogMargins * 2 + implicitHeight: dialogColumnLayout.implicitHeight + anchors.centerIn: parent + color: Appearance.m3colors.m3surfaceContainerHigh + radius: Appearance.rounding.normal + + function addTask() { + if (todoInput.text.length > 0) { + Todo.addTask(todoInput.text) + todoInput.text = "" + root.showAddDialog = false + root.currentTab = 0 // Show unfinished tasks + } + } + + ColumnLayout { + anchors.fill: parent + id: dialogColumnLayout + spacing: 16 + + StyledText { + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.alignment: Qt.AlignLeft + color: Appearance.m3colors.m3onSurface + font.pixelSize: Appearance.font.pixelSize.larger + text: "Add task" + } + + TextField { + id: todoInput + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + padding: 10 + color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant + selectedTextColor: Appearance.m3colors.m3onSurface + placeholderText: "Task description" + focus: root.showAddDialog + onAccepted: dialog.addTask() + + background: Rectangle { + anchors.fill: parent + radius: 8 + border.width: 2 + border.color: todoInput.activeFocus ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline + color: "transparent" + } + + cursorDelegate: Rectangle { + width: 1 + color: todoInput.activeFocus ? Appearance.m3colors.m3primary : "transparent" + radius: 1 + } + } + + RowLayout { + Layout.bottomMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.alignment: Qt.AlignRight + spacing: 5 + + DialogButton { + buttonText: "Cancel" + onClicked: root.showAddDialog = false + } + DialogButton { + buttonText: "Add" + enabled: todoInput.text.length > 0 + onClicked: dialog.addTask() + } + } + } + } + } } \ No newline at end of file diff --git a/.config/quickshell/services/Todo.qml b/.config/quickshell/services/Todo.qml index 96b90de95..0dc062871 100644 --- a/.config/quickshell/services/Todo.qml +++ b/.config/quickshell/services/Todo.qml @@ -13,9 +13,19 @@ Singleton { function addItem(item) { list.push(item) + // Reassign to trigger onListChanged + root.list = list.slice(0) todoFileView.setText(JSON.stringify(root.list)) } + function addTask(desc) { + const item = { + "content": desc, + "done": false, + } + addItem(item) + } + function markDone(index) { if (index >= 0 && index < list.length) { list[index].done = true From 873cb246427c79c7c0c15af810be40bd16992839 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 17 Apr 2025 16:28:13 +0200 Subject: [PATCH 036/824] fix calendar scroll --- .../modules/sidebarRight/calendar/CalendarWidget.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml index 13eb59182..51b505e5c 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml @@ -28,9 +28,9 @@ Item { MouseArea { anchors.fill: parent onWheel: (event) => { - if (wheel.angleDelta.y > 0) { + if (event.angleDelta.y > 0) { monthShift--; - } else if (wheel.angleDelta.y < 0) { + } else if (event.angleDelta.y < 0) { monthShift++; } } From f798b912a6eac2133fd1555ba2a1eb09d67e7536 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 17 Apr 2025 16:39:23 +0200 Subject: [PATCH 037/824] make cursor shape pointing hand on button hover --- .config/quickshell/modules/common/widgets/DialogButton.qml | 4 +++- .../quickshell/modules/common/widgets/NavRailButton.qml | 3 ++- .../modules/common/widgets/PointingHandInteraction.qml | 7 +++++++ .../modules/common/widgets/SmallCircleButton.qml | 2 ++ .../quickshell/modules/common/widgets/StyledTabButton.qml | 2 ++ .../modules/sidebarRight/calendar/CalendarHeaderButton.qml | 3 ++- .../sidebarRight/{ => quickToggles}/QuickToggleButton.qml | 2 ++ .../modules/sidebarRight/todo/TodoItemActionButton.qml | 2 ++ .../quickshell/modules/sidebarRight/todo/TodoWidget.qml | 4 ++-- 9 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/PointingHandInteraction.qml rename .config/quickshell/modules/sidebarRight/{ => quickToggles}/QuickToggleButton.qml (97%) diff --git a/.config/quickshell/modules/common/widgets/DialogButton.qml b/.config/quickshell/modules/common/widgets/DialogButton.qml index 46dfa16d8..cc8ea6174 100644 --- a/.config/quickshell/modules/common/widgets/DialogButton.qml +++ b/.config/quickshell/modules/common/widgets/DialogButton.qml @@ -12,6 +12,8 @@ Button { implicitHeight: 30 implicitWidth: buttonTextWidget.implicitWidth + 15 * 2 + PointingHandInteraction {} + background: Rectangle { anchors.fill: parent radius: Appearance.rounding.full @@ -34,7 +36,7 @@ Button { anchors.rightMargin: 15 text: buttonText horizontalAlignment: Text.AlignHCenter - font.pixelSize: Appearance.font.pixelSize.normal + font.pixelSize: Appearance.font.pixelSize.small color: button.enabled ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline Behavior on color { diff --git a/.config/quickshell/modules/common/widgets/NavRailButton.qml b/.config/quickshell/modules/common/widgets/NavRailButton.qml index 935d98063..2ccbe5529 100644 --- a/.config/quickshell/modules/common/widgets/NavRailButton.qml +++ b/.config/quickshell/modules/common/widgets/NavRailButton.qml @@ -16,7 +16,8 @@ Button { implicitHeight: columnLayout.implicitHeight implicitWidth: columnLayout.implicitWidth - background: Item{} // No ugly bg + background: Item {} + PointingHandInteraction {} // Real stuff ColumnLayout { diff --git a/.config/quickshell/modules/common/widgets/PointingHandInteraction.qml b/.config/quickshell/modules/common/widgets/PointingHandInteraction.qml new file mode 100644 index 000000000..202cd6f3c --- /dev/null +++ b/.config/quickshell/modules/common/widgets/PointingHandInteraction.qml @@ -0,0 +1,7 @@ +import QtQuick + +MouseArea { + anchors.fill: parent + onPressed: mouse.accepted = false + cursorShape: Qt.PointingHandCursor +} \ No newline at end of file diff --git a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml b/.config/quickshell/modules/common/widgets/SmallCircleButton.qml index 14d7c19e3..091e37200 100644 --- a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml +++ b/.config/quickshell/modules/common/widgets/SmallCircleButton.qml @@ -11,6 +11,8 @@ Button { required default property Item content property bool extraActiveCondition: false + PointingHandInteraction{} + implicitHeight: Math.max(content.implicitHeight, 26, content.implicitHeight) implicitWidth: Math.max(content.implicitHeight, 26, content.implicitWidth) contentItem: content diff --git a/.config/quickshell/modules/common/widgets/StyledTabButton.qml b/.config/quickshell/modules/common/widgets/StyledTabButton.qml index 47d447b78..3e83712e6 100644 --- a/.config/quickshell/modules/common/widgets/StyledTabButton.qml +++ b/.config/quickshell/modules/common/widgets/StyledTabButton.qml @@ -13,6 +13,8 @@ TabButton { property bool selected: false height: buttonBackground.height + PointingHandInteraction {} + background: Rectangle { id: buttonBackground radius: Appearance.rounding.small diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml index babe3a26f..342652837 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml @@ -12,13 +12,14 @@ Button { implicitHeight: 30 implicitWidth: forceCircle ? implicitHeight : (contentItem.implicitWidth + 10 * 2) - Behavior on implicitWidth { SmoothedAnimation { velocity: Appearance.animation.elementDecel.velocity } } + PointingHandInteraction {} + background: Rectangle { anchors.fill: parent radius: Appearance.rounding.full diff --git a/.config/quickshell/modules/sidebarRight/QuickToggleButton.qml b/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml similarity index 97% rename from .config/quickshell/modules/sidebarRight/QuickToggleButton.qml rename to .config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml index 012784201..07c02f46d 100644 --- a/.config/quickshell/modules/sidebarRight/QuickToggleButton.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml @@ -13,6 +13,8 @@ Button { implicitWidth: 40 implicitHeight: 40 + PointingHandInteraction {} + background: Rectangle { anchors.fill: parent radius: Appearance.rounding.full diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml b/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml index 4bfdf61fe..43c8626a0 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml @@ -12,6 +12,8 @@ Button { implicitHeight: 30 implicitWidth: implicitHeight + PointingHandInteraction {} + Behavior on implicitWidth { SmoothedAnimation { velocity: Appearance.animation.elementDecel.velocity diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index 539db74eb..b84368634 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -11,7 +11,7 @@ Item { property int currentTab: 0 property var tabButtonList: [{"icon": "checklist", "name": "Unfinished"}, {"name": "Done", "icon": "check_circle"}] property bool showAddDialog: false - property int dialogMargins: 25 + property int dialogMargins: 20 property int fabSize: 48 property int fabMargins: 14 @@ -124,9 +124,9 @@ Item { anchors.bottom: parent.bottom anchors.rightMargin: root.fabMargins anchors.bottomMargin: root.fabMargins - width: root.fabSize height: root.fabSize + PointingHandInteraction {} onClicked: root.showAddDialog = true From 7f779476ccb11c459c5d19cdde7f8baffc5ce66e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 17 Apr 2025 21:54:12 +0200 Subject: [PATCH 038/824] Update rules.conf --- .config/hypr/hyprland/rules.conf | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index 701d5ebd6..5b732d992 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -1,9 +1,5 @@ # ######## Window rules ######## -# General rule – disable blur for all windows. -# (Using "class:.*" as a catch-all field since at least one field is required) -windowrulev2 = noblur, class:.* - # Uncomment to apply global transparency to all windows: # windowrulev2 = opacity 0.89 override 0.89 override, class:.* @@ -106,4 +102,7 @@ layerrule = ignorealpha 0.6, osk[0-9]* # Quickshell layerrule = animation fade, quickshell:screenCorners +layerrule = animation slide right, quickshell:sidebarRight +layerrule = animation slide left, quickshell:sidebarLeft + From 8bf8c200d7d5387dad469794446e8f02a4019b89 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 17 Apr 2025 22:15:27 +0200 Subject: [PATCH 039/824] sidebar todo dialog add fade anim --- .config/quickshell/modules/common/Appearance.qml | 1 + .../modules/sidebarRight/todo/TodoWidget.qml | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index e9635fbeb..a1bb15f61 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -130,6 +130,7 @@ Singleton { property color colSecondaryContainerActive: mix(m3colors.m3secondaryContainer, colLayer1Active, 0.6) property color colTooltip: m3colors.m3inverseSurface property color colOnTooltip: m3colors.m3inverseOnSurface + property color colScrim: transparentize(m3colors.m3scrim, 0.5) } rounding: QtObject { diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index b84368634..2554aaa07 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -175,9 +175,20 @@ Item { Item { anchors.fill: parent - visible: root.showAddDialog + visible: false z: 1000 + opacity: root.showAddDialog ? 1 : 0 + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } + onOpacityChanged: { + visible = opacity > 0 + } + onVisibleChanged: { if (!visible) { todoInput.text = "" @@ -188,7 +199,7 @@ Item { Rectangle { // Scrim anchors.fill: parent radius: Appearance.rounding.small - color: "#80000000" + color: Appearance.colors.colScrim MouseArea { hoverEnabled: true anchors.fill: parent From 2d540c16bc511d1296de1b85222b75d2d9584a8d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 17 Apr 2025 22:17:58 +0200 Subject: [PATCH 040/824] cursor shape for network and bluetooth buttons --- .../modules/sidebarRight/quickToggles/BluetoothToggle.qml | 1 + .../modules/sidebarRight/quickToggles/NetworkToggle.qml | 1 + 2 files changed, 2 insertions(+) diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml index a02caea38..03eda8846 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml @@ -22,6 +22,7 @@ QuickToggleButton { } hoverEnabled: false propagateComposedEvents: true + cursorShape: Qt.PointingHandCursor } Process { id: configureBluetooth diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml index 4a15071eb..cab9aa223 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml @@ -28,6 +28,7 @@ QuickToggleButton { } hoverEnabled: false propagateComposedEvents: true + cursorShape: Qt.PointingHandCursor } Process { id: configureNetwork From 75bf5028fd9af64bd6d57c9d24f0e34e03e95e11 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 17 Apr 2025 23:01:48 +0200 Subject: [PATCH 041/824] sync --- .config/quickshell/ReloadPopup.qml | 4 +++- .../common/widgets/PointingHandInteraction.qml | 2 +- .../modules/common/widgets/StyledTabButton.qml | 3 +++ .../modules/sidebarRight/todo/TodoWidget.qml | 14 +++++++------- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.config/quickshell/ReloadPopup.qml b/.config/quickshell/ReloadPopup.qml index eb19cbd91..637141912 100644 --- a/.config/quickshell/ReloadPopup.qml +++ b/.config/quickshell/ReloadPopup.qml @@ -58,7 +58,9 @@ Scope { MouseArea { id: mouseArea anchors.fill: parent - onClicked: popupLoader.active = false + onClicked: { + popupLoader.active = false + } // makes the mouse area track mouse hovering, so the hide animation // can be paused when hovering. diff --git a/.config/quickshell/modules/common/widgets/PointingHandInteraction.qml b/.config/quickshell/modules/common/widgets/PointingHandInteraction.qml index 202cd6f3c..cf8b065f7 100644 --- a/.config/quickshell/modules/common/widgets/PointingHandInteraction.qml +++ b/.config/quickshell/modules/common/widgets/PointingHandInteraction.qml @@ -2,6 +2,6 @@ import QtQuick MouseArea { anchors.fill: parent - onPressed: mouse.accepted = false + onPressed: (mouse) => mouse.accepted = false cursorShape: Qt.PointingHandCursor } \ No newline at end of file diff --git a/.config/quickshell/modules/common/widgets/StyledTabButton.qml b/.config/quickshell/modules/common/widgets/StyledTabButton.qml index 3e83712e6..704aae643 100644 --- a/.config/quickshell/modules/common/widgets/StyledTabButton.qml +++ b/.config/quickshell/modules/common/widgets/StyledTabButton.qml @@ -27,6 +27,9 @@ TabButton { easing.type: Appearance.animation.elementDecel.type } } + + border.color: button.activeFocus ? Appearance.m3colors.m3secondary : Appearance.transparentize(Appearance.m3colors.m3secondary, 1) + border.width: button.activeFocus ? 2 : 0 } contentItem: Item { anchors.centerIn: buttonBackground diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index 2554aaa07..92b33c6f3 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -252,13 +252,13 @@ Item { focus: root.showAddDialog onAccepted: dialog.addTask() - background: Rectangle { - anchors.fill: parent - radius: 8 - border.width: 2 - border.color: todoInput.activeFocus ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline - color: "transparent" - } + // background: Rectangle { + // anchors.fill: parent + // radius: Appearance.rounding.verysmall + // border.width: 2 + // border.color: todoInput.activeFocus ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline + // color: "transparent" + // } cursorDelegate: Rectangle { width: 1 From bef66ac40a980358c4c794ea350d1f2016f21823 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 18 Apr 2025 09:27:58 +0200 Subject: [PATCH 042/824] fix ugly dialog text field --- .../modules/common/widgets/StyledTabButton.qml | 4 ++-- .../modules/sidebarRight/todo/TodoWidget.qml | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/StyledTabButton.qml b/.config/quickshell/modules/common/widgets/StyledTabButton.qml index 704aae643..40d89c42f 100644 --- a/.config/quickshell/modules/common/widgets/StyledTabButton.qml +++ b/.config/quickshell/modules/common/widgets/StyledTabButton.qml @@ -28,8 +28,8 @@ TabButton { } } - border.color: button.activeFocus ? Appearance.m3colors.m3secondary : Appearance.transparentize(Appearance.m3colors.m3secondary, 1) - border.width: button.activeFocus ? 2 : 0 + // border.color: button.activeFocus ? Appearance.m3colors.m3secondary : Appearance.transparentize(Appearance.m3colors.m3secondary, 1) + // border.width: button.activeFocus ? 2 : 0 } contentItem: Item { anchors.centerIn: buttonBackground diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index 92b33c6f3..bf46fb7ee 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -252,13 +252,13 @@ Item { focus: root.showAddDialog onAccepted: dialog.addTask() - // background: Rectangle { - // anchors.fill: parent - // radius: Appearance.rounding.verysmall - // border.width: 2 - // border.color: todoInput.activeFocus ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline - // color: "transparent" - // } + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.verysmall + border.width: 2 + border.color: todoInput.activeFocus ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline + color: "transparent" + } cursorDelegate: Rectangle { width: 1 From d8d812cf47522f9294f551dbe041c39fbf488a2b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 18 Apr 2025 10:38:52 +0200 Subject: [PATCH 043/824] sidebarright bottom widget group collapse --- .../sidebarRight/BottomWidgetGroup.qml | 133 +++++++++++++++--- .../modules/sidebarRight/SidebarRight.qml | 15 +- .../modules/sidebarRight/todo/TodoWidget.qml | 2 +- .config/quickshell/services/DateTime.qml | 1 + 4 files changed, 128 insertions(+), 23 deletions(-) diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index 8479c2f49..a7d80202d 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -10,18 +10,46 @@ import Quickshell Rectangle { id: root - Layout.alignment: Qt.AlignHCenter - Layout.fillHeight: false - Layout.fillWidth: true radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 - height: bottomWidgetGroupRow.height + // height: collapsed ? collapsedBottomWidgetGroupRow.height : bottomWidgetGroupRow.height + implicitHeight: collapsed ? collapsedBottomWidgetGroupRow.implicitHeight : bottomWidgetGroupRow.implicitHeight property int selectedTab: 0 + property bool collapsed: false property var tabs: [ {"type": "calendar", "name": "Calendar", "icon": "calendar_month", "widget": calendarWidget}, - {"type": "todo", "name": "To Do", "icon": "done_outline", "widget": todoWidget} + {"type": "todo", "name": "To Do", "icon": "done_outline", "widget": todoWidget} ] + Behavior on implicitHeight { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + + Component.onCompleted: { + bottomWidgetGroupRow.opacity = !collapsed + collapsedBottomWidgetGroupRow.opacity = collapsed + } + + function setCollapsed(state) { + collapsed = state + if (collapsed) bottomWidgetGroupRow.opacity = 0 + else collapsedBottomWidgetGroupRow.opacity = 0 + collapseCleanFadeTimer.start() + } + + Timer { + id: collapseCleanFadeTimer + interval: Appearance.animation.elementDecel.duration / 2 + repeat: false + onTriggered: { + if(collapsed) collapsedBottomWidgetGroupRow.opacity = 1 + else bottomWidgetGroupRow.opacity = 1 + } + } + Keys.onPressed: (event) => { if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.ControlModifier) { @@ -34,30 +62,101 @@ Rectangle { } } + // The thing when collapsed + RowLayout { + id: collapsedBottomWidgetGroupRow + visible: opacity != 0 + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration / 2 + easing.type: Appearance.animation.elementDecel.type + } + } + + spacing: 15 + + CalendarHeaderButton { + Layout.margins: 10 + Layout.rightMargin: 0 + forceCircle: true + onClicked: { + root.setCollapsed(false) + } + contentItem: MaterialSymbol { + text: "keyboard_arrow_up" + font.pixelSize: Appearance.font.pixelSize.larger + horizontalAlignment: Text.AlignHCenter + color: Appearance.colors.colOnLayer1 + } + } + + StyledText { + property int remainingTasks: Todo.list.filter(task => !task.done).length; + Layout.margins: 10 + Layout.leftMargin: 0 + text: `${DateTime.day} ${DateTime.month} ${DateTime.year} • ${remainingTasks} task${remainingTasks > 1 ? "s" : ""}` + font.pixelSize: Appearance.font.pixelSize.large + color: Appearance.colors.colOnLayer1 + } + } + + // The thing when expanded RowLayout { id: bottomWidgetGroupRow + + visible: opacity != 0 + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration / 2 + easing.type: Appearance.animation.elementDecel.type + } + } + anchors.fill: parent height: tabStack.height spacing: 10 // Navigation rail - ColumnLayout { - id: tabBar + Item { Layout.fillHeight: true Layout.fillWidth: false - Layout.leftMargin: 15 - spacing: 15 - Repeater { - model: root.tabs - NavRailButton { - toggled: root.selectedTab == index - buttonText: modelData.name - buttonIcon: modelData.icon - onClicked: { - root.selectedTab = index + Layout.leftMargin: 10 + Layout.topMargin: 10 + width: tabBar.width + // Navigation rail buttons + ColumnLayout { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 5 + id: tabBar + spacing: 15 + Repeater { + model: root.tabs + NavRailButton { + toggled: root.selectedTab == index + buttonText: modelData.name + buttonIcon: modelData.icon + onClicked: { + root.selectedTab = index + } } } } + // Collapse button + CalendarHeaderButton { + anchors.left: parent.left + anchors.top: parent.top + forceCircle: true + onClicked: { + root.setCollapsed(true) + } + contentItem: MaterialSymbol { + text: "keyboard_arrow_down" + font.pixelSize: Appearance.font.pixelSize.larger + horizontalAlignment: Text.AlignHCenter + color: Appearance.colors.colOnLayer1 + } + } } // Content area diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 086dfb33e..717a2923a 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -84,9 +84,9 @@ Scope { } ColumnLayout { - anchors.centerIn: parent - height: parent.height - sidebarPadding * 2 - width: parent.width - sidebarPadding * 2 + anchors.fill: parent + anchors.margins: sidebarPadding + spacing: sidebarPadding RowLayout { @@ -139,6 +139,7 @@ Scope { } } + // Center widget group Rectangle { Layout.alignment: Qt.AlignHCenter Layout.fillHeight: true @@ -147,8 +148,12 @@ Scope { color: Appearance.colors.colLayer1 } - // Calendar - BottomWidgetGroup {} + BottomWidgetGroup { + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: false + Layout.fillWidth: true + Layout.preferredHeight: implicitHeight + } } } diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index bf46fb7ee..88336d6a7 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -287,4 +287,4 @@ Item { } } } -} \ No newline at end of file +} diff --git a/.config/quickshell/services/DateTime.qml b/.config/quickshell/services/DateTime.qml index fc1c501be..e3c1c08c3 100644 --- a/.config/quickshell/services/DateTime.qml +++ b/.config/quickshell/services/DateTime.qml @@ -7,6 +7,7 @@ pragma Singleton Singleton { property string time: Qt.formatDateTime(clock.date, "hh:mm") property string date: Qt.formatDateTime(clock.date, "dddd, dd/MM") + property string day: Qt.formatDateTime(clock.date, "dd") property string month: Qt.formatDateTime(clock.date, "MMMM") property string year: Qt.formatDateTime(clock.date, "yyyy") property string uptime: "0h, 0m" From 02151a93f6b518ef9e443e3e081d76356409bb23 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 18 Apr 2025 12:43:39 +0200 Subject: [PATCH 044/824] fancier tabs --- .../common/widgets/PrimaryTabButton.qml | 67 +++++++++++ ...edTabButton.qml => SecondaryTabButton.qml} | 13 ++- .../sidebarRight/BottomWidgetGroup.qml | 2 +- .../sidebarRight/CenterWidgetGroup.qml | 110 ++++++++++++++++++ .../modules/sidebarRight/SidebarRight.qml | 4 +- .../modules/sidebarRight/todo/TodoWidget.qml | 52 ++++++--- 6 files changed, 225 insertions(+), 23 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/PrimaryTabButton.qml rename .config/quickshell/modules/common/widgets/{StyledTabButton.qml => SecondaryTabButton.qml} (84%) create mode 100644 .config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml new file mode 100644 index 000000000..3a7565281 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml @@ -0,0 +1,67 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io +import Quickshell.Widgets + +TabButton { + id: button + property string buttonText + property string buttonIcon + property bool selected: false + property int tabContentWidth: contentItem.children[0].implicitWidth + height: buttonBackground.height + + PointingHandInteraction {} + + background: Rectangle { + id: buttonBackground + radius: Appearance.rounding.small + implicitHeight: 50 + color: (button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.colors.colLayer1Hover, 1)) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } + contentItem: Item { + anchors.centerIn: buttonBackground + ColumnLayout { + anchors.centerIn: parent + spacing: 0 + MaterialSymbol { + visible: buttonIcon?.length > 0 + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + text: buttonIcon + font.pixelSize: 24 + color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } + StyledText { + id: buttonTextWidget + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.small + color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 + text: buttonText + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/common/widgets/StyledTabButton.qml b/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml similarity index 84% rename from .config/quickshell/modules/common/widgets/StyledTabButton.qml rename to .config/quickshell/modules/common/widgets/SecondaryTabButton.qml index 40d89c42f..9a8f7e16a 100644 --- a/.config/quickshell/modules/common/widgets/StyledTabButton.qml +++ b/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml @@ -12,6 +12,7 @@ TabButton { property string buttonIcon property bool selected: false height: buttonBackground.height + property int tabContentWidth: buttonBackground.width - buttonBackground.radius*2 PointingHandInteraction {} @@ -27,9 +28,6 @@ TabButton { easing.type: Appearance.animation.elementDecel.type } } - - // border.color: button.activeFocus ? Appearance.m3colors.m3secondary : Appearance.transparentize(Appearance.m3colors.m3secondary, 1) - // border.width: button.activeFocus ? 2 : 0 } contentItem: Item { anchors.centerIn: buttonBackground @@ -37,9 +35,11 @@ TabButton { anchors.centerIn: parent spacing: 0 MaterialSymbol { + visible: buttonIcon?.length > 0 Layout.rightMargin: 5 + verticalAlignment: Text.AlignVCenter text: buttonIcon - font.pixelSize: Appearance.font.pixelSize.larger + font.pixelSize: Appearance.font.pixelSize.huge color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 Behavior on color { ColorAnimation { @@ -50,9 +50,10 @@ TabButton { } StyledText { id: buttonTextWidget - horizontalAlignment: Text.AlignHCenter - text: buttonText + verticalAlignment: Text.AlignVCenter + font.pixelSize: Appearance.font.pixelSize.small color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 + text: buttonText Behavior on color { ColorAnimation { duration: Appearance.animation.elementDecel.duration diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index a7d80202d..0c452829a 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -94,7 +94,7 @@ Rectangle { property int remainingTasks: Todo.list.filter(task => !task.done).length; Layout.margins: 10 Layout.leftMargin: 0 - text: `${DateTime.day} ${DateTime.month} ${DateTime.year} • ${remainingTasks} task${remainingTasks > 1 ? "s" : ""}` + text: `${DateTime.day} ${DateTime.month} ${DateTime.year} • ${remainingTasks} task${remainingTasks > 1 ? "s" : ""}` font.pixelSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer1 } diff --git a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml new file mode 100644 index 000000000..fcc4ddf47 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml @@ -0,0 +1,110 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import "./calendar" +import "./todo" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell + +Rectangle { + id: root + radius: Appearance.rounding.normal + color: Appearance.colors.colLayer1 + + property int currentTab: 0 + property var tabButtonList: [{"icon": "notifications", "name": "Notifications"}, {"icon": "volume_up", "name": "Volume mixer"}] + + ColumnLayout { + anchors.margins: 5 + anchors.fill: parent + spacing: 0 + + TabBar { + id: tabBar + Layout.fillWidth: true + currentIndex: currentTab + onCurrentIndexChanged: currentTab = currentIndex + + background: Item { + WheelHandler { + onWheel: (event) => { + if (event.angleDelta.y < 0) + tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1) + else if (event.angleDelta.y > 0) + tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0) + } + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } + } + + Repeater { + model: root.tabButtonList + delegate: PrimaryTabButton { + selected: (index == currentTab) + buttonText: modelData.name + buttonIcon: modelData.icon + } + } + } + + Item { // Tab indicator + id: tabIndicator + Layout.fillWidth: true + height: 3 + property bool enableIndicatorAnimation: false + Connections { + target: root + function onCurrentTabChanged() { + tabIndicator.enableIndicatorAnimation = true + } + } + Rectangle { + color: Appearance.m3colors.m3primary + radius: Appearance.rounding.full + z: 2 + + anchors.fill: parent + anchors.leftMargin: { + const tabCount = root.tabButtonList.length + const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth + const fullTabSize = tabBar.width / tabCount; + return fullTabSize * currentTab + (fullTabSize - targetWidth) / 2; + } + anchors.rightMargin: { + const tabCount = root.tabButtonList.length + const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth + const fullTabSize = tabBar.width / tabCount; + return fullTabSize * (tabCount - currentTab - 1) + (fullTabSize - targetWidth) / 2; + } + Behavior on anchors.leftMargin { + enabled: tabIndicator.enableIndicatorAnimation + SmoothedAnimation { + velocity: Appearance.animation.positionShift.velocity + } + } + Behavior on anchors.rightMargin { + enabled: tabIndicator.enableIndicatorAnimation + SmoothedAnimation { + velocity: Appearance.animation.positionShift.velocity + } + } + + } + } + + SwipeView { + id: swipeView + Layout.topMargin: 10 + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + currentIndex: currentTab + onCurrentIndexChanged: currentTab = currentIndex + + Item{} + Item{} + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 717a2923a..ca93bef4f 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -140,12 +140,10 @@ Scope { } // Center widget group - Rectangle { + CenterWidgetGroup { Layout.alignment: Qt.AlignHCenter Layout.fillHeight: true Layout.fillWidth: true - radius: Appearance.rounding.normal - color: Appearance.colors.colLayer1 } BottomWidgetGroup { diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index 88336d6a7..0fbc927a2 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -50,9 +50,9 @@ Item { WheelHandler { onWheel: (event) => { if (event.angleDelta.y < 0) - currentTab = Math.min(currentTab + 1, root.tabButtonList.length - 1) + tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1) else if (event.angleDelta.y > 0) - currentTab = Math.max(currentTab - 1, 0) + tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0) } acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad } @@ -60,7 +60,7 @@ Item { Repeater { model: root.tabButtonList - delegate: StyledTabButton { + delegate: SecondaryTabButton { selected: (index == currentTab) buttonText: modelData.name buttonIcon: modelData.icon @@ -68,21 +68,47 @@ Item { } } - Item { + Item { // Tab indicator + id: tabIndicator Layout.fillWidth: true + height: 3 + property bool enableIndicatorAnimation: false + Connections { + target: root + function onCurrentTabChanged() { + tabIndicator.enableIndicatorAnimation = true + } + } Rectangle { - property int indicatorPadding: 15 - id: indicator color: Appearance.m3colors.m3primary - height: 3 radius: Appearance.rounding.full - - width: tabBar.width / root.tabButtonList.length - indicatorPadding * 2 - x: indicatorPadding + tabBar.width / root.tabButtonList.length * currentTab z: 2 - Behavior on x { SmoothedAnimation { - velocity: Appearance.animation.positionShift.velocity - } } + + anchors.fill: parent + anchors.leftMargin: { + const tabCount = root.tabButtonList.length + const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth + const fullTabSize = tabBar.width / tabCount; + return fullTabSize * currentTab + (fullTabSize - targetWidth) / 2; + } + anchors.rightMargin: { + const tabCount = root.tabButtonList.length + const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth + const fullTabSize = tabBar.width / tabCount; + return fullTabSize * (tabCount - currentTab - 1) + (fullTabSize - targetWidth) / 2; + } + Behavior on anchors.leftMargin { + enabled: tabIndicator.enableIndicatorAnimation + SmoothedAnimation { + velocity: Appearance.animation.positionShift.velocity + } + } + Behavior on anchors.rightMargin { + enabled: tabIndicator.enableIndicatorAnimation + SmoothedAnimation { + velocity: Appearance.animation.positionShift.velocity + } + } } } From 6b457c7780c5ec93cfc02fa938fa5d359470e418 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Apr 2025 00:09:51 +0200 Subject: [PATCH 045/824] notification list --- .../quickshell/modules/common/Appearance.qml | 1 + .../modules/common/ConfigOptions.qml | 1 + .../common/widgets/NotificationWidget.qml | 138 ++++++++++++++++++ .../common/widgets/notification_utils.js | 62 ++++++++ .../sidebarRight/BottomWidgetGroup.qml | 2 +- .../sidebarRight/CenterWidgetGroup.qml | 3 +- .../modules/sidebarRight/SidebarRight.qml | 2 +- .../sidebarRight/calendar/CalendarWidget.qml | 1 + .../notifications/NotificationList.qml | 33 +++++ .../quickToggles/BluetoothToggle.qml | 2 +- .../quickToggles/NetworkToggle.qml | 2 +- .../modules/sidebarRight/todo/TaskList.qml | 2 +- .config/quickshell/services/Audio.qml | 1 + .config/quickshell/services/Bluetooth.qml | 1 + .config/quickshell/services/Brightness.qml | 1 + .config/quickshell/services/DateTime.qml | 2 +- .config/quickshell/services/Network.qml | 1 + .config/quickshell/services/Notifications.qml | 32 ++++ .config/quickshell/services/ResourceUsage.qml | 4 +- .config/quickshell/services/SystemInfo.qml | 4 +- .config/quickshell/services/Todo.qml | 1 + 21 files changed, 287 insertions(+), 9 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/NotificationWidget.qml create mode 100644 .config/quickshell/modules/common/widgets/notification_utils.js create mode 100644 .config/quickshell/modules/sidebarRight/notifications/NotificationList.qml create mode 100644 .config/quickshell/services/Notifications.qml diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index a1bb15f61..dde99a649 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -1,6 +1,7 @@ import QtQuick import Quickshell pragma Singleton +pragma ComponentBehavior: Bound Singleton { property QtObject m3colors diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index f225d9737..57b821749 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -1,6 +1,7 @@ import QtQuick import Quickshell pragma Singleton +pragma ComponentBehavior: Bound Singleton { property QtObject appearance: QtObject { diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml new file mode 100644 index 000000000..096125feb --- /dev/null +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -0,0 +1,138 @@ +import "root:/modules/common" +import "root:/services" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import Quickshell.Services.Notifications +import "./notification_utils.js" as NotificationUtils + +WrapperRectangle { + id: root + property var notificationObject + property bool expanded: true + + Layout.fillWidth: true + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.m3colors.m3secondaryContainer : Appearance.colors.colLayer2 + radius: Appearance.rounding.normal + RowLayout { + anchors.fill: parent + Rectangle { + id: iconRectangle + implicitWidth: 47 + implicitHeight: 47 + Layout.leftMargin: 10 + Layout.topMargin: 10 + Layout.bottomMargin: 10 + Layout.alignment: Qt.AlignTop + radius: Appearance.rounding.full + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.m3colors.m3secondary : Appearance.m3colors.m3secondaryContainer + MaterialSymbol { + visible: notificationObject.appIcon == "" + text: NotificationUtils.guessMessageType(notificationObject.summary) + anchors.fill: parent + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.m3colors.m3onSecondary : Appearance.m3colors.m3onSecondaryContainer + font.pixelSize: 27 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + IconImage { + visible: notificationObject.appIcon != "" + anchors.centerIn: parent + implicitSize: 33 + asynchronous: true + source: Quickshell.iconPath(notificationObject.appIcon) + } + } + ColumnLayout { + spacing: 0 + RowLayout { + Layout.topMargin: 10 + Layout.leftMargin: 10 + Layout.rightMargin: 10 + Layout.fillWidth: true + StyledText { + Layout.fillWidth: true + horizontalAlignment: Text.AlignLeft + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colOnLayer2 + text: notificationObject.summary + wrapMode: expanded ? Text.Wrap : Text.NoWrap + elide: Text.ElideRight + } + Item { Layout.fillWidth: true } + StyledText { + id: notificationTimeText + Layout.fillWidth: false + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignLeft + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.m3colors.m3outline + text: NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) + + Connections { + target: DateTime + function onTimeChanged() { + notificationTimeText.text = NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) + } + } + } + Button { + Layout.alignment: Qt.AlignVCenter + id: expandButton + implicitWidth: 22 + implicitHeight: 22 + + onClicked: { + root.expanded = !root.expanded + } + PointingHandInteraction{} + + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.full + color: (expandButton.down) ? Appearance.colors.colLayer2Active : (expandButton.hovered ? Appearance.colors.colLayer2Hover : Appearance.transparentize(Appearance.colors.colLayer2, 1)) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } + + } + contentItem: MaterialSymbol { + anchors.centerIn: parent + text: expanded ? "keyboard_arrow_up" : "keyboard_arrow_down" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colOnLayer2 + } + + } + + } + RowLayout { + StyledText { + Layout.fillWidth: true + Layout.bottomMargin: 10 + Layout.leftMargin: 10 + Layout.rightMargin: 10 + wrapMode: expanded ? Text.Wrap : Text.NoWrap + elide: Text.ElideRight + font.pixelSize: Appearance.font.pixelSize.small + horizontalAlignment: Text.AlignLeft + color: Appearance.m3colors.m3outline + textFormat: Text.MarkdownText + text: notificationObject.body + } + } + } + } +} diff --git a/.config/quickshell/modules/common/widgets/notification_utils.js b/.config/quickshell/modules/common/widgets/notification_utils.js new file mode 100644 index 000000000..52cc9d5bb --- /dev/null +++ b/.config/quickshell/modules/common/widgets/notification_utils.js @@ -0,0 +1,62 @@ +function guessMessageType(summary) { + const keywordsToTypes = { + 'reboot': 'restart_alt', + 'recording': 'screen_record', + 'battery': 'power', + 'power': 'power', + 'screenshot': 'screenshot_monitor', + 'welcome': 'waving_hand', + 'time': 'scheduleb', + 'installed': 'download', + 'update': 'update', + 'ai response': 'neurology', + 'startswith:file': 'folder_copy', // Declarative startsWith check + }; + + const lowerSummary = summary.toLowerCase(); + + for (const [keyword, type] of Object.entries(keywordsToTypes)) { + if (keyword.startsWith('startswith:')) { + const startsWithKeyword = keyword.replace('startswith:', ''); + if (lowerSummary.startsWith(startsWithKeyword)) { + return type; + } + } else if (lowerSummary.includes(keyword)) { + return type; + } + } + + return 'chat'; +} + +// const getFriendlyNotifTimeString = (timeObject) => { +// const messageTime = GLib.DateTime.new_from_unix_local(timeObject); +// const oneMinuteAgo = GLib.DateTime.new_now_local().add_seconds(-60); +// if (messageTime.compare(oneMinuteAgo) > 0) +// return getString('Now'); +// else if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year()) +// return messageTime.format(userOptions.time.format); +// else if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year() - 1) +// return getString('Yesterday'); +// else +// return messageTime.format(userOptions.time.dateFormat); +// } + +const getFriendlyNotifTimeString = (timeObject) => { + const messageTime = new Date(timeObject * 1000); + const now = new Date(); + const oneMinuteAgo = new Date(now.getTime() - 60000); + + if (messageTime > oneMinuteAgo) { + return 'Now'; + } + else if (messageTime.toDateString() === now.toDateString()) { + return messageTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + } + else if (messageTime.toDateString() === new Date(now.getTime() - 86400000).toDateString()) { + return 'Yesterday'; + } + else { + return messageTime.toLocaleDateString(); + } +}; diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index 0c452829a..53295916f 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -12,7 +12,7 @@ Rectangle { id: root radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 - // height: collapsed ? collapsedBottomWidgetGroupRow.height : bottomWidgetGroupRow.height + clip: true implicitHeight: collapsed ? collapsedBottomWidgetGroupRow.implicitHeight : bottomWidgetGroupRow.implicitHeight property int selectedTab: 0 property bool collapsed: false diff --git a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml index fcc4ddf47..ca96c34b0 100644 --- a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml @@ -3,6 +3,7 @@ import "root:/modules/common/widgets" import "root:/services" import "./calendar" import "./todo" +import "./notifications" import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -103,7 +104,7 @@ Rectangle { currentIndex: currentTab onCurrentIndexChanged: currentTab = currentIndex - Item{} + NotificationList {} Item{} } } diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index ca93bef4f..01c835e57 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -93,7 +93,7 @@ Scope { Layout.fillHeight: false spacing: 10 Layout.margins: 10 - Layout.bottomMargin: 5 + Layout.bottomMargin: 0 CustomIcon { width: 25 diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml index 51b505e5c..a1925f643 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml @@ -46,6 +46,7 @@ Item { Layout.fillWidth: true spacing: 5 CalendarHeaderButton { + clip: true buttonText: `${monthShift != 0 ? "• " : ""}${viewingDate.toLocaleDateString(Qt.locale(), "MMMM yyyy")}` tooltipText: (monthShift === 0) ? "" : "Jump to current month" onClicked: { diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml new file mode 100644 index 000000000..e921c9316 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -0,0 +1,33 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Widgets + +Item { + Flickable { // Scrollable window + id: flickable + anchors.fill: parent + contentHeight: columnLayout.height + clip: true + + ColumnLayout { // Scrollable window content + anchors.left: parent.left + anchors.right: parent.right + id: columnLayout + + Repeater { + model: Notifications.list + + delegate: NotificationWidget { + notificationObject: modelData + } + + } + + } + + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml index 03eda8846..16ef56e42 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml @@ -12,7 +12,7 @@ QuickToggleButton { MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton | Qt.LeftButton - onClicked: { + onClicked: (mouse) => { if (mouse.button === Qt.LeftButton) { toggleBluetooth.running = true } diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml index cab9aa223..8c916aac9 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml @@ -18,7 +18,7 @@ QuickToggleButton { MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton | Qt.LeftButton - onClicked: { + onClicked: (mouse) =>{ if (mouse.button === Qt.LeftButton) { toggleNetwork.running = true } diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml index 16e773465..89f92dee5 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -14,7 +14,7 @@ Item { property int todoListItemPadding: 8 property int listBottomPadding: 80 - Flickable { // Scrolled window + Flickable { id: flickable anchors.fill: parent contentHeight: columnLayout.height diff --git a/.config/quickshell/services/Audio.qml b/.config/quickshell/services/Audio.qml index 292efbf43..4bfe0e881 100644 --- a/.config/quickshell/services/Audio.qml +++ b/.config/quickshell/services/Audio.qml @@ -2,6 +2,7 @@ import QtQuick import Quickshell import Quickshell.Services.Pipewire pragma Singleton +pragma ComponentBehavior: Bound Singleton { id: root diff --git a/.config/quickshell/services/Bluetooth.qml b/.config/quickshell/services/Bluetooth.qml index fb654c417..fed333bb5 100644 --- a/.config/quickshell/services/Bluetooth.qml +++ b/.config/quickshell/services/Bluetooth.qml @@ -1,4 +1,5 @@ pragma Singleton +pragma ComponentBehavior: Bound import Quickshell; import Quickshell.Io; diff --git a/.config/quickshell/services/Brightness.qml b/.config/quickshell/services/Brightness.qml index 57ad389d3..a11c1fa4a 100644 --- a/.config/quickshell/services/Brightness.qml +++ b/.config/quickshell/services/Brightness.qml @@ -1,6 +1,7 @@ import Quickshell import Quickshell.Io pragma Singleton +pragma ComponentBehavior: Bound Singleton { id: root diff --git a/.config/quickshell/services/DateTime.qml b/.config/quickshell/services/DateTime.qml index e3c1c08c3..32b89ff9c 100644 --- a/.config/quickshell/services/DateTime.qml +++ b/.config/quickshell/services/DateTime.qml @@ -3,6 +3,7 @@ import QtQuick import Quickshell import Quickshell.Io pragma Singleton +pragma ComponentBehavior: Bound Singleton { property string time: Qt.formatDateTime(clock.date, "hh:mm") @@ -14,7 +15,6 @@ Singleton { SystemClock { id: clock - precision: SystemClock.Minutes } diff --git a/.config/quickshell/services/Network.qml b/.config/quickshell/services/Network.qml index 9a3a30345..1a8402586 100644 --- a/.config/quickshell/services/Network.qml +++ b/.config/quickshell/services/Network.qml @@ -1,4 +1,5 @@ pragma Singleton +pragma ComponentBehavior: Bound import Quickshell; import Quickshell.Io; diff --git a/.config/quickshell/services/Notifications.qml b/.config/quickshell/services/Notifications.qml new file mode 100644 index 000000000..db2eadc5a --- /dev/null +++ b/.config/quickshell/services/Notifications.qml @@ -0,0 +1,32 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import Quickshell.Services.Notifications + +Singleton { + id: root + property alias list: notifServer.trackedNotifications + + NotificationServer { + id: notifServer + actionIconsSupported: true + actionsSupported: true + bodyHyperlinksSupported: true + bodyImagesSupported: true + bodyMarkupSupported: true + bodySupported: true + imageSupported: true + keepOnReload: true + persistenceSupported: true + + onNotification: (notification) => { + notification.tracked = true; + if(!notification.time) { + notification.time = new Date(); + } + // root.list = [...root.list, notification]; + } + } +} diff --git a/.config/quickshell/services/ResourceUsage.qml b/.config/quickshell/services/ResourceUsage.qml index 239217a6f..e30b36c84 100644 --- a/.config/quickshell/services/ResourceUsage.qml +++ b/.config/quickshell/services/ResourceUsage.qml @@ -1,5 +1,7 @@ -import "root:/modules/common" pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common" import QtQuick import Quickshell import Quickshell.Io diff --git a/.config/quickshell/services/SystemInfo.qml b/.config/quickshell/services/SystemInfo.qml index 3db1237c8..2572b42dc 100644 --- a/.config/quickshell/services/SystemInfo.qml +++ b/.config/quickshell/services/SystemInfo.qml @@ -1,7 +1,9 @@ +pragma Singleton +pragma ComponentBehavior: Bound + import QtQuick import Quickshell import Quickshell.Io -pragma Singleton Singleton { property string distroName: "Unknown" diff --git a/.config/quickshell/services/Todo.qml b/.config/quickshell/services/Todo.qml index 0dc062871..6267768ce 100644 --- a/.config/quickshell/services/Todo.qml +++ b/.config/quickshell/services/Todo.qml @@ -1,4 +1,5 @@ pragma Singleton +pragma ComponentBehavior: Bound import Quickshell; import Quickshell.Io; From 63e29d18fb5c65664e0b63263711704cd36c14a2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Apr 2025 00:25:39 +0200 Subject: [PATCH 046/824] fix notif time --- .../common/widgets/notification_utils.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/notification_utils.js b/.config/quickshell/modules/common/widgets/notification_utils.js index 52cc9d5bb..cb223c83d 100644 --- a/.config/quickshell/modules/common/widgets/notification_utils.js +++ b/.config/quickshell/modules/common/widgets/notification_utils.js @@ -43,20 +43,15 @@ function guessMessageType(summary) { // } const getFriendlyNotifTimeString = (timeObject) => { - const messageTime = new Date(timeObject * 1000); + const messageTime = timeObject; const now = new Date(); const oneMinuteAgo = new Date(now.getTime() - 60000); - if (messageTime > oneMinuteAgo) { + if (messageTime > oneMinuteAgo) return 'Now'; - } - else if (messageTime.toDateString() === now.toDateString()) { - return messageTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - } - else if (messageTime.toDateString() === new Date(now.getTime() - 86400000).toDateString()) { + if (messageTime.toDateString() === now.toDateString()) + return Qt.formatDateTime(messageTime, "hh:mm"); + if (messageTime.toDateString() === new Date(now.getTime() - 86400000).toDateString()) return 'Yesterday'; - } - else { - return messageTime.toLocaleDateString(); - } + return Qt.formatDateTime(messageTime, "MMMM dd"); }; From 3b2628fbd7220c32cfe93955d202518fd584ab61 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Apr 2025 20:07:44 +0200 Subject: [PATCH 047/824] notifications properly working --- .../common/widgets/NotificationWidget.qml | 260 +++++++++++------- .../common/widgets/notification_utils.js | 4 +- .../notifications/NotificationList.qml | 58 +++- .../modules/sidebarRight/todo/TaskList.qml | 1 - .config/quickshell/services/Notifications.qml | 82 +++++- .config/quickshell/services/Todo.qml | 2 +- 6 files changed, 288 insertions(+), 119 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 096125feb..160501410 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -8,129 +8,201 @@ import Quickshell.Widgets import Quickshell.Services.Notifications import "./notification_utils.js" as NotificationUtils -WrapperRectangle { +Item { id: root property var notificationObject - property bool expanded: true + property bool expanded: false + property bool enableAnimation: true + property int notificationListSpacing: 5 + property bool ready: false Layout.fillWidth: true - color: (notificationObject.urgency == NotificationUrgency.Critical) ? - Appearance.m3colors.m3secondaryContainer : Appearance.colors.colLayer2 - radius: Appearance.rounding.normal - RowLayout { + clip: true + + // implicitHeight: notificationRowLayout.implicitHeight + implicitHeight: ready ? notificationRowLayout.implicitHeight + notificationListSpacing : 0 + Behavior on implicitHeight { + enabled: enableAnimation + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + + Component.onCompleted: { + root.ready = true + } + + function fancyDestroy() { + implicitHeight = 0 + notificationRowWrapper.anchors.top = undefined + notificationRowWrapper.anchors.bottom = root.bottom + destroyTimer.start() + } + + Timer { + id: destroyTimer + interval: Appearance.animation.elementDecel.duration + repeat: false + onTriggered: { + root.destroy() + } + } + + MouseArea { // Middle click to close anchors.fill: parent - Rectangle { - id: iconRectangle - implicitWidth: 47 - implicitHeight: 47 - Layout.leftMargin: 10 - Layout.topMargin: 10 - Layout.bottomMargin: 10 - Layout.alignment: Qt.AlignTop - radius: Appearance.rounding.full - color: (notificationObject.urgency == NotificationUrgency.Critical) ? - Appearance.m3colors.m3secondary : Appearance.m3colors.m3secondaryContainer - MaterialSymbol { - visible: notificationObject.appIcon == "" - text: NotificationUtils.guessMessageType(notificationObject.summary) - anchors.fill: parent - color: (notificationObject.urgency == NotificationUrgency.Critical) ? - Appearance.m3colors.m3onSecondary : Appearance.m3colors.m3onSecondaryContainer - font.pixelSize: 27 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - IconImage { - visible: notificationObject.appIcon != "" - anchors.centerIn: parent - implicitSize: 33 - asynchronous: true - source: Quickshell.iconPath(notificationObject.appIcon) + acceptedButtons: Qt.MiddleButton + onClicked: (mouse) => { + if (mouse.button == Qt.MiddleButton) { + Notifications.discardNotification(notificationObject.id) } } - ColumnLayout { - spacing: 0 - RowLayout { - Layout.topMargin: 10 - Layout.leftMargin: 10 - Layout.rightMargin: 10 - Layout.fillWidth: true - StyledText { - Layout.fillWidth: true - horizontalAlignment: Text.AlignLeft - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.colors.colOnLayer2 - text: notificationObject.summary - wrapMode: expanded ? Text.Wrap : Text.NoWrap - elide: Text.ElideRight - } - Item { Layout.fillWidth: true } - StyledText { - id: notificationTimeText - Layout.fillWidth: false - wrapMode: Text.Wrap - horizontalAlignment: Text.AlignLeft - font.pixelSize: Appearance.font.pixelSize.small - color: Appearance.m3colors.m3outline - text: NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) + } - Connections { - target: DateTime - function onTimeChanged() { - notificationTimeText.text = NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) + // Background + Rectangle { + anchors.fill: parent + anchors.topMargin: notificationListSpacing + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.m3colors.m3secondaryContainer : Appearance.colors.colLayer2 + radius: Appearance.rounding.normal + } + + + Item { + id: notificationRowWrapper + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + implicitHeight: notificationRowLayout.implicitHeight + notificationListSpacing + RowLayout { + id: notificationRowLayout + + anchors.left: parent.left + anchors.right: parent.right + // anchors.top: parent.top + anchors.bottom: parent.bottom + Rectangle { // App icon + id: iconRectangle + implicitWidth: 47 + implicitHeight: 47 + Layout.leftMargin: 10 + Layout.topMargin: 10 + Layout.bottomMargin: 10 + Layout.alignment: Qt.AlignTop + Layout.fillWidth: false + radius: Appearance.rounding.full + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.m3colors.m3secondary : Appearance.m3colors.m3secondaryContainer + MaterialSymbol { + visible: notificationObject.appIcon == "" + text: NotificationUtils.guessMessageType(notificationObject.summary) + anchors.fill: parent + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.m3colors.m3onSecondary : Appearance.m3colors.m3onSecondaryContainer + font.pixelSize: 27 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + IconImage { + visible: notificationObject.appIcon != "" + anchors.centerIn: parent + implicitSize: 33 + asynchronous: true + source: Quickshell.iconPath(notificationObject.appIcon) + } + } + ColumnLayout { // Notification content + spacing: 0 + Layout.fillWidth: true + + RowLayout { // Row of summary, time and expand button + Layout.topMargin: 10 + Layout.leftMargin: 10 + Layout.rightMargin: 10 + Layout.fillWidth: true + + StyledText { // Summary + Layout.fillWidth: true + horizontalAlignment: Text.AlignLeft + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colOnLayer2 + text: notificationObject.summary + wrapMode: expanded ? Text.Wrap : Text.NoWrap + elide: Text.ElideRight + } + + Item { Layout.fillWidth: true } + + StyledText { // Time + id: notificationTimeText + Layout.fillWidth: false + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignLeft + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.m3colors.m3outline + text: NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) + + Connections { + target: DateTime + function onTimeChanged() { + notificationTimeText.text = NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) + } } } - } - Button { - Layout.alignment: Qt.AlignVCenter - id: expandButton - implicitWidth: 22 - implicitHeight: 22 - onClicked: { - root.expanded = !root.expanded - } - PointingHandInteraction{} + Button { // Expand button + Layout.alignment: Qt.AlignVCenter + id: expandButton + implicitWidth: 22 + implicitHeight: 22 - background: Rectangle { - anchors.fill: parent - radius: Appearance.rounding.full - color: (expandButton.down) ? Appearance.colors.colLayer2Active : (expandButton.hovered ? Appearance.colors.colLayer2Hover : Appearance.transparentize(Appearance.colors.colLayer2, 1)) + PointingHandInteraction{} + onClicked: { + root.enableAnimation = true + root.expanded = !root.expanded + } + + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.full + color: (expandButton.down) ? Appearance.colors.colLayer2Active : (expandButton.hovered ? Appearance.colors.colLayer2Hover : Appearance.transparentize(Appearance.colors.colLayer2, 1)) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } - Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type } } - } - contentItem: MaterialSymbol { - anchors.centerIn: parent - text: expanded ? "keyboard_arrow_up" : "keyboard_arrow_down" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.colors.colOnLayer2 - } + contentItem: MaterialSymbol { + anchors.centerIn: parent + text: expanded ? "keyboard_arrow_up" : "keyboard_arrow_down" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colOnLayer2 + } + } } - } - RowLayout { - StyledText { + StyledText { // Notification body Layout.fillWidth: true Layout.bottomMargin: 10 Layout.leftMargin: 10 Layout.rightMargin: 10 + clip: true wrapMode: expanded ? Text.Wrap : Text.NoWrap elide: Text.ElideRight font.pixelSize: Appearance.font.pixelSize.small horizontalAlignment: Text.AlignLeft color: Appearance.m3colors.m3outline - textFormat: Text.MarkdownText - text: notificationObject.body + // textFormat: Text.MarkdownText + text: notificationObject.body } } } diff --git a/.config/quickshell/modules/common/widgets/notification_utils.js b/.config/quickshell/modules/common/widgets/notification_utils.js index cb223c83d..e7b82cc8e 100644 --- a/.config/quickshell/modules/common/widgets/notification_utils.js +++ b/.config/quickshell/modules/common/widgets/notification_utils.js @@ -42,8 +42,8 @@ function guessMessageType(summary) { // return messageTime.format(userOptions.time.dateFormat); // } -const getFriendlyNotifTimeString = (timeObject) => { - const messageTime = timeObject; +const getFriendlyNotifTimeString = (timestamp) => { + const messageTime = new Date(timestamp); const now = new Date(); const oneMinuteAgo = new Date(now.getTime() - 60000); diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml index e921c9316..1fb3657ce 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -7,6 +7,50 @@ import QtQuick.Layouts import Quickshell.Widgets Item { + id: root + property Component notifComponent: NotificationWidget {} + property list notificationWidgetList: [] + + // Signal handlers to add/remove notifications + Connections { + target: Notifications + function onInitDone() { + // notificationRepeater.model = Notifications.list.slice().reverse() + Notifications.list.slice().reverse().forEach((notification) => { + const notif = root.notifComponent.createObject(columnLayout, { notificationObject: notification }); + notificationWidgetList.push(notif) + }) + } + function onNotify(notification) { + // notificationRepeater.model = [notification, ...notificationRepeater.model] + const notif = root.notifComponent.createObject(columnLayout, { notificationObject: notification }); + notificationWidgetList.unshift(notif) + + // Remove stuff from t he column, add back + for (let i = 0; i < notificationWidgetList.length; i++) { + if (notificationWidgetList[i].parent === columnLayout) { + notificationWidgetList[i].parent = null; + } + } + + // Add notification widgets to the column + for (let i = 0; i < notificationWidgetList.length; i++) { + if (notificationWidgetList[i].parent === null) { + notificationWidgetList[i].parent = columnLayout; + } + } + } + function onDiscard(id) { + for (let i = notificationWidgetList.length - 1; i >= 0; i--) { + const widget = notificationWidgetList[i]; + if (widget && widget.notificationObject && widget.notificationObject.id === id) { + widget.fancyDestroy(); + notificationWidgetList.splice(i, 1); + } + } + } + } + Flickable { // Scrollable window id: flickable anchors.fill: parent @@ -14,20 +58,12 @@ Item { clip: true ColumnLayout { // Scrollable window content + id: columnLayout anchors.left: parent.left anchors.right: parent.right - id: columnLayout - - Repeater { - model: Notifications.list - - delegate: NotificationWidget { - notificationObject: modelData - } - - } + spacing: 0 // The widgets themselves have margins for spacing + // Notifications are added by the above signal handlers } - } } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml index 89f92dee5..2b920d432 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -94,7 +94,6 @@ Item { Item { Layout.fillWidth: true } - // layoutDirection: Qt.RightToLeft TodoItemActionButton { Layout.fillWidth: false onClicked: { diff --git a/.config/quickshell/services/Notifications.qml b/.config/quickshell/services/Notifications.qml index db2eadc5a..3d0cf1dd8 100644 --- a/.config/quickshell/services/Notifications.qml +++ b/.config/quickshell/services/Notifications.qml @@ -3,30 +3,92 @@ pragma ComponentBehavior: Bound import QtQuick import Quickshell +import Quickshell.Io import Quickshell.Services.Notifications +import Qt.labs.platform Singleton { id: root - property alias list: notifServer.trackedNotifications + property var filePath: `${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/notifications/notifications.json` + property var list: [] + + signal initDone(); + signal notify(notification: var); + signal discard(id: var); NotificationServer { id: notifServer - actionIconsSupported: true - actionsSupported: true + // actionIconsSupported: true + // actionsSupported: true bodyHyperlinksSupported: true - bodyImagesSupported: true + // bodyImagesSupported: true bodyMarkupSupported: true bodySupported: true - imageSupported: true - keepOnReload: true + // imageSupported: true + keepOnReload: false // I can't figure out RetainableLock, using a custom solution with a json file instead persistenceSupported: true onNotification: (notification) => { - notification.tracked = true; - if(!notification.time) { - notification.time = new Date(); + notification.tracked = true + const newNotifObject = { + "id": notification.id, + "actions": [], + "appIcon": notification.appIcon, + "appName": notification.appName, + "body": notification.body, + "summary": notification.summary, + "time": Date.now(), + "urgency": notification.urgency.toString(), + } + root.list = [...root.list, newNotifObject]; + root.notify(newNotifObject); + notifFileView.setText(JSON.stringify(root.list, null, 2)) + } + } + + function discardNotification(id) { + const index = root.list.findIndex((notif) => notif.id === id); + const notifServerIndex = notifServer.trackedNotifications.values.findIndex((notif) => notif.id === id); + if (index !== -1) { + root.list.splice(index, 1); + notifFileView.setText(JSON.stringify(root.list, null, 2)) + triggerListChange() + } + if (notifServerIndex !== -1) { + notifServer.trackedNotifications.values[notifServerIndex].dismiss() + } + root.discard(id); + } + + function triggerListChange() { + root.list = root.list.slice(0) + } + + function refresh() { + notifFileView.reload() + } + + Component.onCompleted: { + refresh() + } + + FileView { + id: notifFileView + path: filePath + onLoaded: { + const fileContents = notifFileView.text() + root.list = JSON.parse(fileContents) + console.log("[Notifications] File loaded") + root.initDone() + } + onLoadFailed: (error) => { + if(error == FileViewError.FileNotFound) { + console.log("[Notifications] File not found, creating new file.") + root.list = [] + notifFileView.setText(JSON.stringify(root.list)) + } else { + console.log("[Notifications] Error loading file: " + error) } - // root.list = [...root.list, notification]; } } } diff --git a/.config/quickshell/services/Todo.qml b/.config/quickshell/services/Todo.qml index 6267768ce..72f8055d1 100644 --- a/.config/quickshell/services/Todo.qml +++ b/.config/quickshell/services/Todo.qml @@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound import Quickshell; import Quickshell.Io; -import Quickshell.Services.Pipewire; import Qt.labs.platform import QtQuick; @@ -68,6 +67,7 @@ Singleton { onLoaded: { const fileContents = todoFileView.text() root.list = JSON.parse(fileContents) + console.log("[To Do] File loaded") } onLoadFailed: (error) => { if(error == FileViewError.FileNotFound) { From 3677873a05069d398e5b2bd2325f02de75d94ad3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Apr 2025 21:38:33 +0200 Subject: [PATCH 048/824] notif actions --- .../quickshell/modules/common/Appearance.qml | 4 +- .../widgets/NotificationActionButton.qml | 34 +++++++++ .../common/widgets/NotificationWidget.qml | 69 +++++++++++++++---- .config/quickshell/services/Notifications.qml | 19 ++++- 4 files changed, 109 insertions(+), 17 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/NotificationActionButton.qml diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index dde99a649..05c3f059c 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -127,8 +127,10 @@ Singleton { property color colPrimaryContainerActive: mix(m3colors.m3primaryContainer, colLayer1Active, 0.6) property color colSecondaryHover: mix(m3colors.m3secondary, colLayer1Hover, 0.85) property color colSecondaryActive: mix(m3colors.m3secondary, colLayer1Active, 0.4) - property color colSecondaryContainerHover: mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.8) + property color colSecondaryContainerHover: mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.67) property color colSecondaryContainerActive: mix(m3colors.m3secondaryContainer, colLayer1Active, 0.6) + property color colSurfaceContainerHighestHover: mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.9) + property color colSurfaceContainerHighestActive: mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.82) property color colTooltip: m3colors.m3inverseSurface property color colOnTooltip: m3colors.m3inverseOnSurface property color colScrim: transparentize(m3colors.m3scrim, 0.5) diff --git a/.config/quickshell/modules/common/widgets/NotificationActionButton.qml b/.config/quickshell/modules/common/widgets/NotificationActionButton.qml new file mode 100644 index 000000000..16bdf0a94 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/NotificationActionButton.qml @@ -0,0 +1,34 @@ +import "root:/modules/common" +import "root:/services" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.Notifications + +Button { + id: button + property string buttonText + property string urgency + + implicitHeight: 30 + leftPadding: 10 + rightPadding: 10 + + background: Rectangle { + radius: Appearance.rounding.small + color: (urgency == NotificationUrgency.Critical) ? + (button.down ? Appearance.colors.colSecondaryContainerActive : + button.hovered ? Appearance.colors.colSecondaryContainerHover : + Appearance.m3colors.m3secondaryContainer) : + (button.down ? Appearance.colors.colSurfaceContainerHighestActive : + button.hovered ? Appearance.colors.colSurfaceContainerHighestHover : + Appearance.m3colors.m3surfaceContainerHighest) + } + + contentItem: StyledText { + horizontalAlignment: Text.AlignHCenter + text: buttonText + color: (urgency == NotificationUrgency.Critical) ? Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 160501410..aeb637356 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -24,8 +24,8 @@ Item { Behavior on implicitHeight { enabled: enableAnimation NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type } } @@ -42,7 +42,7 @@ Item { Timer { id: destroyTimer - interval: Appearance.animation.elementDecel.duration + interval: Appearance.animation.elementDecelFast.duration repeat: false onTriggered: { root.destroy() @@ -51,11 +51,12 @@ Item { MouseArea { // Middle click to close anchors.fill: parent - acceptedButtons: Qt.MiddleButton + acceptedButtons: Qt.MiddleButton | Qt.RightButton onClicked: (mouse) => { - if (mouse.button == Qt.MiddleButton) { - Notifications.discardNotification(notificationObject.id) - } + if (mouse.button == Qt.MiddleButton) + Notifications.discardNotification(notificationObject.id); + else if (mouse.button == Qt.RightButton) + root.expanded = !root.expanded; } } @@ -64,7 +65,7 @@ Item { anchors.fill: parent anchors.topMargin: notificationListSpacing color: (notificationObject.urgency == NotificationUrgency.Critical) ? - Appearance.m3colors.m3secondaryContainer : Appearance.colors.colLayer2 + Appearance.mix(Appearance.m3colors.m3secondaryContainer, Appearance.colors.colLayer2, 0.35) : Appearance.colors.colLayer2 radius: Appearance.rounding.normal } @@ -92,14 +93,15 @@ Item { Layout.alignment: Qt.AlignTop Layout.fillWidth: false radius: Appearance.rounding.full - color: (notificationObject.urgency == NotificationUrgency.Critical) ? - Appearance.m3colors.m3secondary : Appearance.m3colors.m3secondaryContainer + color: Appearance.m3colors.m3secondaryContainer MaterialSymbol { visible: notificationObject.appIcon == "" - text: NotificationUtils.guessMessageType(notificationObject.summary) + text: (notificationObject.urgency == NotificationUrgency.Critical) ? "release_alert" : + NotificationUtils.guessMessageType(notificationObject.summary) anchors.fill: parent color: (notificationObject.urgency == NotificationUrgency.Critical) ? - Appearance.m3colors.m3onSecondary : Appearance.m3colors.m3onSecondaryContainer + Appearance.mix(Appearance.m3colors.m3onSecondary, Appearance.m3colors.m3onSecondaryContainer, 0.1) : + Appearance.m3colors.m3onSecondaryContainer font.pixelSize: 27 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -192,17 +194,56 @@ Item { StyledText { // Notification body Layout.fillWidth: true - Layout.bottomMargin: 10 Layout.leftMargin: 10 Layout.rightMargin: 10 + Layout.bottomMargin: 10 clip: true + wrapMode: expanded ? Text.Wrap : Text.NoWrap elide: Text.ElideRight font.pixelSize: Appearance.font.pixelSize.small horizontalAlignment: Text.AlignLeft color: Appearance.m3colors.m3outline // textFormat: Text.MarkdownText - text: notificationObject.body + text: notificationObject.body + } + + Flickable { + Layout.fillWidth: true + Layout.topMargin: -5 + Layout.leftMargin: 10 + Layout.rightMargin: 10 + Layout.bottomMargin: 10 + visible: expanded + implicitHeight: actionRowLayout.implicitHeight + contentWidth: actionRowLayout.implicitWidth + + RowLayout { // Actions + id: actionRowLayout + + Repeater { + id: actionRepeater + model: notificationObject.actions + NotificationActionButton { + Layout.fillWidth: true + buttonText: modelData.text + urgency: notificationObject.urgency + onClicked: { + Notifications.attemptInvokeAction(notificationObject.id, modelData.identifier); + } + } + } + + NotificationActionButton { + Layout.fillWidth: true + buttonText: "Close" + urgency: notificationObject.urgency + onClicked: { + Notifications.discardNotification(notificationObject.id); + } + } + + } } } } diff --git a/.config/quickshell/services/Notifications.qml b/.config/quickshell/services/Notifications.qml index 3d0cf1dd8..5c315d69a 100644 --- a/.config/quickshell/services/Notifications.qml +++ b/.config/quickshell/services/Notifications.qml @@ -19,7 +19,7 @@ Singleton { NotificationServer { id: notifServer // actionIconsSupported: true - // actionsSupported: true + actionsSupported: true bodyHyperlinksSupported: true // bodyImagesSupported: true bodyMarkupSupported: true @@ -32,7 +32,12 @@ Singleton { notification.tracked = true const newNotifObject = { "id": notification.id, - "actions": [], + "actions": notification.actions.map((action) => { + return { + "identifier": action.identifier, + "text": action.text, + } + }), "appIcon": notification.appIcon, "appName": notification.appName, "body": notification.body, @@ -60,6 +65,16 @@ Singleton { root.discard(id); } + function attemptInvokeAction(id, notifIdentifier) { + const notifServerIndex = notifServer.trackedNotifications.values.findIndex((notif) => notif.id === id); + if (notifServerIndex !== -1) { + const notifServerNotif = notifServer.trackedNotifications.values[notifServerIndex]; + const action = notifServerNotif.actions.find((action) => action.identifier === notifIdentifier); + action.invoke() + } else console.log("Notification not found in server: " + id) + root.discard(id); + } + function triggerListChange() { root.list = root.list.slice(0) } From 17289cef297972efc5bfb6fb2c96312ea61f137e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Apr 2025 22:37:14 +0200 Subject: [PATCH 049/824] notif actions --- .../common/widgets/NotificationWidget.qml | 300 +++++++++--------- 1 file changed, 155 insertions(+), 145 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index aeb637356..31bb66ab3 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -19,8 +19,7 @@ Item { Layout.fillWidth: true clip: true - // implicitHeight: notificationRowLayout.implicitHeight - implicitHeight: ready ? notificationRowLayout.implicitHeight + notificationListSpacing : 0 + implicitHeight: ready ? notificationColumnLayout.implicitHeight + notificationListSpacing : 0 Behavior on implicitHeight { enabled: enableAnimation NumberAnimation { @@ -75,175 +74,186 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top - implicitHeight: notificationRowLayout.implicitHeight + notificationListSpacing - RowLayout { - id: notificationRowLayout - + implicitHeight: notificationColumnLayout.implicitHeight + notificationListSpacing + ColumnLayout { + id: notificationColumnLayout anchors.left: parent.left anchors.right: parent.right - // anchors.top: parent.top anchors.bottom: parent.bottom - Rectangle { // App icon - id: iconRectangle - implicitWidth: 47 - implicitHeight: 47 - Layout.leftMargin: 10 - Layout.topMargin: 10 - Layout.bottomMargin: 10 - Layout.alignment: Qt.AlignTop - Layout.fillWidth: false - radius: Appearance.rounding.full - color: Appearance.m3colors.m3secondaryContainer - MaterialSymbol { - visible: notificationObject.appIcon == "" - text: (notificationObject.urgency == NotificationUrgency.Critical) ? "release_alert" : - NotificationUtils.guessMessageType(notificationObject.summary) - anchors.fill: parent - color: (notificationObject.urgency == NotificationUrgency.Critical) ? - Appearance.mix(Appearance.m3colors.m3onSecondary, Appearance.m3colors.m3onSecondaryContainer, 0.1) : - Appearance.m3colors.m3onSecondaryContainer - font.pixelSize: 27 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - IconImage { - visible: notificationObject.appIcon != "" - anchors.centerIn: parent - implicitSize: 33 - asynchronous: true - source: Quickshell.iconPath(notificationObject.appIcon) - } - } - ColumnLayout { // Notification content - spacing: 0 - Layout.fillWidth: true + RowLayout { + id: notificationRowLayout - RowLayout { // Row of summary, time and expand button - Layout.topMargin: 10 + Layout.fillWidth: true + + Rectangle { // App icon + id: iconRectangle + implicitWidth: 47 + implicitHeight: 47 Layout.leftMargin: 10 - Layout.rightMargin: 10 + Layout.topMargin: 10 + Layout.bottomMargin: 10 + Layout.alignment: Qt.AlignTop + Layout.fillWidth: false + radius: Appearance.rounding.full + color: Appearance.m3colors.m3secondaryContainer + MaterialSymbol { + visible: notificationObject.appIcon == "" + text: (notificationObject.urgency == NotificationUrgency.Critical) ? "release_alert" : + NotificationUtils.guessMessageType(notificationObject.summary) + anchors.fill: parent + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.mix(Appearance.m3colors.m3onSecondary, Appearance.m3colors.m3onSecondaryContainer, 0.1) : + Appearance.m3colors.m3onSecondaryContainer + font.pixelSize: 27 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + IconImage { + visible: notificationObject.appIcon != "" + anchors.centerIn: parent + implicitSize: 33 + asynchronous: true + source: Quickshell.iconPath(notificationObject.appIcon) + } + } + ColumnLayout { // Notification content + spacing: 0 Layout.fillWidth: true - StyledText { // Summary + RowLayout { // Row of summary, time and expand button + Layout.topMargin: 10 + Layout.leftMargin: 10 + Layout.rightMargin: 10 Layout.fillWidth: true - horizontalAlignment: Text.AlignLeft - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.colors.colOnLayer2 - text: notificationObject.summary - wrapMode: expanded ? Text.Wrap : Text.NoWrap - elide: Text.ElideRight - } - Item { Layout.fillWidth: true } - - StyledText { // Time - id: notificationTimeText - Layout.fillWidth: false - wrapMode: Text.Wrap - horizontalAlignment: Text.AlignLeft - font.pixelSize: Appearance.font.pixelSize.smaller - color: Appearance.m3colors.m3outline - text: NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) - - Connections { - target: DateTime - function onTimeChanged() { - notificationTimeText.text = NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) - } - } - } - - Button { // Expand button - Layout.alignment: Qt.AlignVCenter - id: expandButton - implicitWidth: 22 - implicitHeight: 22 - - PointingHandInteraction{} - onClicked: { - root.enableAnimation = true - root.expanded = !root.expanded - } - - background: Rectangle { - anchors.fill: parent - radius: Appearance.rounding.full - color: (expandButton.down) ? Appearance.colors.colLayer2Active : (expandButton.hovered ? Appearance.colors.colLayer2Hover : Appearance.transparentize(Appearance.colors.colLayer2, 1)) - - Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type - } - - } - - } - - contentItem: MaterialSymbol { - anchors.centerIn: parent - text: expanded ? "keyboard_arrow_up" : "keyboard_arrow_down" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + StyledText { // Summary + Layout.fillWidth: true + horizontalAlignment: Text.AlignLeft font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.colors.colOnLayer2 + text: notificationObject.summary + wrapMode: expanded ? Text.Wrap : Text.NoWrap + elide: Text.ElideRight } - } - } + Item { Layout.fillWidth: true } - StyledText { // Notification body - Layout.fillWidth: true - Layout.leftMargin: 10 - Layout.rightMargin: 10 - Layout.bottomMargin: 10 - clip: true + StyledText { // Time + id: notificationTimeText + Layout.fillWidth: false + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignLeft + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.m3colors.m3outline + text: NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) - wrapMode: expanded ? Text.Wrap : Text.NoWrap - elide: Text.ElideRight - font.pixelSize: Appearance.font.pixelSize.small - horizontalAlignment: Text.AlignLeft - color: Appearance.m3colors.m3outline - // textFormat: Text.MarkdownText - text: notificationObject.body - } - - Flickable { - Layout.fillWidth: true - Layout.topMargin: -5 - Layout.leftMargin: 10 - Layout.rightMargin: 10 - Layout.bottomMargin: 10 - visible: expanded - implicitHeight: actionRowLayout.implicitHeight - contentWidth: actionRowLayout.implicitWidth - - RowLayout { // Actions - id: actionRowLayout - - Repeater { - id: actionRepeater - model: notificationObject.actions - NotificationActionButton { - Layout.fillWidth: true - buttonText: modelData.text - urgency: notificationObject.urgency - onClicked: { - Notifications.attemptInvokeAction(notificationObject.id, modelData.identifier); + Connections { + target: DateTime + function onTimeChanged() { + notificationTimeText.text = NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) } } } + Button { // Expand button + Layout.alignment: Qt.AlignVCenter + id: expandButton + implicitWidth: 22 + implicitHeight: 22 + + PointingHandInteraction{} + onClicked: { + root.enableAnimation = true + root.expanded = !root.expanded + } + + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.full + color: (expandButton.down) ? Appearance.colors.colLayer2Active : (expandButton.hovered ? Appearance.colors.colLayer2Hover : Appearance.transparentize(Appearance.colors.colLayer2, 1)) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } + + } + + contentItem: MaterialSymbol { + anchors.centerIn: parent + text: expanded ? "keyboard_arrow_up" : "keyboard_arrow_down" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colOnLayer2 + } + + } + } + + StyledText { // Notification body + Layout.fillWidth: true + Layout.leftMargin: 10 + Layout.rightMargin: 10 + Layout.bottomMargin: 10 + clip: true + + wrapMode: expanded ? Text.Wrap : Text.NoWrap + elide: Text.ElideRight + font.pixelSize: Appearance.font.pixelSize.small + horizontalAlignment: Text.AlignLeft + color: Appearance.m3colors.m3outline + // textFormat: Text.MarkdownText + text: notificationObject.body + } + } + } + + + // Actions + Flickable { + id: actionsFlickable + Layout.fillWidth: true + Layout.topMargin: -5 + Layout.leftMargin: 10 + Layout.rightMargin: 10 + Layout.bottomMargin: 10 + implicitHeight: actionRowLayout.implicitHeight + contentWidth: actionRowLayout.implicitWidth + clip: true + + visible: expanded + + RowLayout { + id: actionRowLayout + + Repeater { + id: actionRepeater + model: notificationObject.actions NotificationActionButton { Layout.fillWidth: true - buttonText: "Close" + buttonText: modelData.text urgency: notificationObject.urgency onClicked: { - Notifications.discardNotification(notificationObject.id); + Notifications.attemptInvokeAction(notificationObject.id, modelData.identifier); } } - } + + NotificationActionButton { + Layout.fillWidth: true + buttonText: "Close" + urgency: notificationObject.urgency + implicitWidth: (notificationObject.actions.length == 0) ? (actionsFlickable.width) : + (contentItem.implicitWidth + leftPadding + rightPadding) + onClicked: { + Notifications.discardNotification(notificationObject.id); + } + } + } } } From 6d2469fe4ce36bec37863a223773261af1cf88e6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Apr 2025 23:11:10 +0200 Subject: [PATCH 050/824] notifications: handle images --- .../common/widgets/NotificationWidget.qml | 42 ++++++++++++++++++- .config/quickshell/services/Notifications.qml | 5 ++- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 31bb66ab3..687df16ae 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -1,5 +1,6 @@ import "root:/modules/common" import "root:/services" +import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -109,12 +110,50 @@ Item { verticalAlignment: Text.AlignVCenter } IconImage { - visible: notificationObject.appIcon != "" + visible: notificationObject.image == "" && notificationObject.appIcon != "" anchors.centerIn: parent implicitSize: 33 asynchronous: true source: Quickshell.iconPath(notificationObject.appIcon) } + Item { + anchors.fill: parent + visible: notificationObject.image != "" + Image { + id: notifImage + + anchors.fill: parent + readonly property int size: parent.width + + source: notificationObject?.image + fillMode: Image.PreserveAspectCrop + cache: false + antialiasing: true + asynchronous: true + + width: size + height: size + sourceSize.width: size + sourceSize.height: size + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: notifImage.size + height: notifImage.size + radius: Appearance.rounding.full + } + } + } + IconImage { + visible: notificationObject.appIcon != "" + anchors.bottom: parent.bottom + anchors.right: parent.right + implicitSize: 23 + asynchronous: true + source: Quickshell.iconPath(notificationObject.appIcon) + } + } } ColumnLayout { // Notification content spacing: 0 @@ -212,7 +251,6 @@ Item { } } - // Actions Flickable { id: actionsFlickable diff --git a/.config/quickshell/services/Notifications.qml b/.config/quickshell/services/Notifications.qml index 5c315d69a..98a3212d5 100644 --- a/.config/quickshell/services/Notifications.qml +++ b/.config/quickshell/services/Notifications.qml @@ -24,8 +24,8 @@ Singleton { // bodyImagesSupported: true bodyMarkupSupported: true bodySupported: true - // imageSupported: true - keepOnReload: false // I can't figure out RetainableLock, using a custom solution with a json file instead + imageSupported: true + keepOnReload: false persistenceSupported: true onNotification: (notification) => { @@ -41,6 +41,7 @@ Singleton { "appIcon": notification.appIcon, "appName": notification.appName, "body": notification.body, + "image": notification.image, "summary": notification.summary, "time": Date.now(), "urgency": notification.urgency.toString(), From d44a1dce8d49ad424f5c3c56bb3cea2638ce7398 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Apr 2025 23:12:59 +0200 Subject: [PATCH 051/824] keybinds: update notif test --- .config/hypr/hyprland/keybinds.conf | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index bfa6578b6..00d5ca299 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -157,7 +157,7 @@ bind = Alt, Tab, bringactivetotop, # [hidden] bring it to the top #! ##! Widgets -bindr = Ctrl+Super, R, exec, killall ags agsv1 ydotool; agsv1 & # Restart widgets +bindr = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool; agsv1 & # Restart widgets bindr = Ctrl+Super+Alt, R, exec, hyprctl reload; killall agsv1 ydotool; agsv1 & # [hidden] bind = Ctrl+Alt, Slash, exec, agsv1 run-js 'cycleMode();' # Cycle bar mode (normal, focus) bindir = Super, Super_L, exec, agsv1 -t 'overview' # Toggle overview/launcher @@ -178,9 +178,7 @@ bindl = , XF86AudioMute, exec, agsv1 run-js 'indicator.popup(1);' # [hidden] bindl = Super+Shift,M, exec, agsv1 run-js 'indicator.popup(1);' # [hidden] # Testing -# bind = SuperAlt, f12, exec, notify-send "Hyprland version: $(hyprctl version | head -2 | tail -1 | cut -f2 -d ' ')" "owo" -a 'Hyprland keybind' -# bind = Super+Alt, f12, exec, notify-send "Millis since epoch" "$(date +%s%N | cut -b1-13)" -a 'Hyprland keybind' -bind = Super+Alt, f12, exec, notify-send 'Test notification' "Here's a really long message to test truncation and wrapping\nYou can middle click or flick this notification to dismiss it!" -a 'Shell' -A "Test1=I got it!" -A "Test2=Another action" -t 5000 # [hidden] +bind = Super+Alt, f12, exec, bash -c 'ACTION=$(notify-send "Test notification" "The quick shill fox jumps over the crazy dog. Anyway, this notification should contain your profile image (like on the login screen) (can be created with GNOME Control center)" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 3000 -i "discord" -A "openImage=Open Image" -A "action2=Intentionally useless button" -A "action3=yes its intentional"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"' # [hidden] bind = Super+Alt, Equal, exec, notify-send "Urgent notification" "Ah hell no" -u critical -a 'Hyprland keybind' # [hidden] ##! Media From 6ae0e5f84dce976dfe217c589b2e0c6944287cc5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Apr 2025 23:13:15 +0200 Subject: [PATCH 052/824] update layer rules --- .config/hypr/hyprland/rules.conf | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index 5b732d992..712ae8df5 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -3,6 +3,9 @@ # Uncomment to apply global transparency to all windows: # windowrulev2 = opacity 0.89 override 0.89 override, class:.* +# Disable blur for XWayland windows (or context menus with shadow would look weird) +windowrulev2 = noblur, xwayland:1 + # Floating windowrulev2 = float, class:^(blueberry\.py)$ windowrulev2 = float, class:^(steam)$ @@ -101,8 +104,14 @@ layerrule = blur, osk[0-9]* layerrule = ignorealpha 0.6, osk[0-9]* # Quickshell +## My stuff layerrule = animation fade, quickshell:screenCorners layerrule = animation slide right, quickshell:sidebarRight layerrule = animation slide left, quickshell:sidebarLeft +## outfoxxed's stuff +layerrule = blur, shell:bar +layerrule = ignorezero, shell:bar +layerrule = blur, shell:notifications +layerrule = ignorealpha 0.1, shell:notifications From 9a82d40ddc7ae07da52c86f4d5daeca799a25eb8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Apr 2025 23:46:47 +0200 Subject: [PATCH 053/824] notifications: better destroy animation --- .../common/widgets/NotificationWidget.qml | 70 +++++++++++++++---- .../notifications/NotificationList.qml | 22 +++++- 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 687df16ae..94de0521d 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -24,8 +24,8 @@ Item { Behavior on implicitHeight { enabled: enableAnimation NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type } } @@ -33,16 +33,33 @@ Item { root.ready = true } - function fancyDestroy() { - implicitHeight = 0 - notificationRowWrapper.anchors.top = undefined - notificationRowWrapper.anchors.bottom = root.bottom - destroyTimer.start() + function destroyWithAnimation() { + notificationRowWrapper.anchors.left = undefined + notificationRowWrapper.anchors.right = undefined + notificationRowWrapper.anchors.fill = undefined + notificationBackground.anchors.left = undefined + notificationBackground.anchors.right = undefined + notificationBackground.anchors.fill = undefined + notificationRowWrapper.x = width + notificationBackground.x = width + destroyTimer1.start() } Timer { - id: destroyTimer - interval: Appearance.animation.elementDecelFast.duration + id: destroyTimer1 + interval: Appearance.animation.elementDecel.duration / 2 + repeat: false + onTriggered: { + notificationRowWrapper.anchors.top = undefined + notificationRowWrapper.anchors.bottom = root.bottom + implicitHeight = 0 + destroyTimer2.start() + } + } + + Timer { + id: destroyTimer2 + interval: Appearance.animation.elementDecel.duration repeat: false onTriggered: { root.destroy() @@ -61,12 +78,30 @@ Item { } // Background - Rectangle { + Item { + id: notificationBackgroundWrapper + anchors.fill: parent + implicitHeight: notificationColumnLayout.implicitHeight anchors.topMargin: notificationListSpacing - color: (notificationObject.urgency == NotificationUrgency.Critical) ? - Appearance.mix(Appearance.m3colors.m3secondaryContainer, Appearance.colors.colLayer2, 0.35) : Appearance.colors.colLayer2 - radius: Appearance.rounding.normal + + Rectangle { + id: notificationBackground + anchors.fill: parent + anchors.bottom: parent.bottom + + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.mix(Appearance.m3colors.m3secondaryContainer, Appearance.colors.colLayer2, 0.35) : Appearance.colors.colLayer2 + radius: Appearance.rounding.normal + + Behavior on x { + enabled: enableAnimation + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } } @@ -76,6 +111,15 @@ Item { anchors.right: parent.right anchors.top: parent.top implicitHeight: notificationColumnLayout.implicitHeight + notificationListSpacing + + Behavior on x { + enabled: enableAnimation + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + ColumnLayout { id: notificationColumnLayout anchors.left: parent.left diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml index 1fb3657ce..fa84089d9 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -44,7 +44,7 @@ Item { for (let i = notificationWidgetList.length - 1; i >= 0; i--) { const widget = notificationWidgetList[i]; if (widget && widget.notificationObject && widget.notificationObject.id === id) { - widget.fancyDestroy(); + widget.destroyWithAnimation(); notificationWidgetList.splice(i, 1); } } @@ -53,7 +53,10 @@ Item { Flickable { // Scrollable window id: flickable - anchors.fill: parent + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: statusRow.top contentHeight: columnLayout.height clip: true @@ -66,4 +69,19 @@ Item { // Notifications are added by the above signal handlers } } + + RowLayout { + id: statusRow + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + StyledText { + Layout.margins: 10 + Layout.bottomMargin: 5 + Layout.alignment: Qt.AlignVCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: `${notificationWidgetList.length} Notifications` + } + } } \ No newline at end of file From b879489d43e0999708b67b7945be766a9b9d7f4d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Apr 2025 23:47:30 +0200 Subject: [PATCH 054/824] pointing hand interaction --- .../modules/common/widgets/NotificationActionButton.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.config/quickshell/modules/common/widgets/NotificationActionButton.qml b/.config/quickshell/modules/common/widgets/NotificationActionButton.qml index 16bdf0a94..07bb9b33d 100644 --- a/.config/quickshell/modules/common/widgets/NotificationActionButton.qml +++ b/.config/quickshell/modules/common/widgets/NotificationActionButton.qml @@ -15,6 +15,8 @@ Button { leftPadding: 10 rightPadding: 10 + PointingHandInteraction {} + background: Rectangle { radius: Appearance.rounding.small color: (urgency == NotificationUrgency.Critical) ? From 905d26570f1234aa0f9a00738a9f3ded68a3af93 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Apr 2025 23:57:50 +0200 Subject: [PATCH 055/824] fix animations --- .config/quickshell/modules/bar/Battery.qml | 2 +- .../modules/common/widgets/NotificationWidget.qml | 13 +++++++++---- .../modules/sidebarRight/BottomWidgetGroup.qml | 4 ++-- .../sidebarRight/notifications/NotificationList.qml | 11 ++++++++++- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/.config/quickshell/modules/bar/Battery.qml b/.config/quickshell/modules/bar/Battery.qml index 5e5e731b3..8e22532af 100644 --- a/.config/quickshell/modules/bar/Battery.qml +++ b/.config/quickshell/modules/bar/Battery.qml @@ -71,7 +71,7 @@ Rectangle { text: "bolt" font.pixelSize: Appearance.font.pixelSize.large color: Appearance.m3colors.m3onSecondaryContainer - visible: opacity !== 0 // Only show when charging + visible: opacity > 0 // Only show when charging opacity: isCharging ? 1 : 0 // Keep opacity for visibility Behavior on opacity { diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 94de0521d..e65346e50 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -81,14 +81,19 @@ Item { Item { id: notificationBackgroundWrapper - anchors.fill: parent - implicitHeight: notificationColumnLayout.implicitHeight + // anchors.fill: parent + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom anchors.topMargin: notificationListSpacing + implicitHeight: notificationColumnLayout.implicitHeight + notificationListSpacing Rectangle { id: notificationBackground - anchors.fill: parent + anchors.left: parent.left + anchors.right: parent.right anchors.bottom: parent.bottom + height: notificationColumnLayout.implicitHeight color: (notificationObject.urgency == NotificationUrgency.Critical) ? Appearance.mix(Appearance.m3colors.m3secondaryContainer, Appearance.colors.colLayer2, 0.35) : Appearance.colors.colLayer2 @@ -109,7 +114,7 @@ Item { id: notificationRowWrapper anchors.left: parent.left anchors.right: parent.right - anchors.top: parent.top + anchors.bottom: parent.bottom implicitHeight: notificationColumnLayout.implicitHeight + notificationListSpacing Behavior on x { diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index 53295916f..bb8fc9036 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -65,7 +65,7 @@ Rectangle { // The thing when collapsed RowLayout { id: collapsedBottomWidgetGroupRow - visible: opacity != 0 + visible: opacity > 0 Behavior on opacity { NumberAnimation { duration: Appearance.animation.elementDecel.duration / 2 @@ -104,7 +104,7 @@ Rectangle { RowLayout { id: bottomWidgetGroupRow - visible: opacity != 0 + visible: opacity > 0 Behavior on opacity { NumberAnimation { duration: Appearance.animation.elementDecel.duration / 2 diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml index fa84089d9..69fcffbb1 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -81,7 +81,16 @@ Item { Layout.alignment: Qt.AlignVCenter horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - text: `${notificationWidgetList.length} Notifications` + text: `${notificationWidgetList.length} notification${notificationWidgetList.length > 1 ? "s" : ""}` + + opacity: notificationWidgetList.length > 0 ? 1 : 0 + visible: opacity > 0 + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } } } } \ No newline at end of file From 3096107d6bed1d9e5a7327423ea1c944901c5a54 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 20 Apr 2025 00:48:43 +0200 Subject: [PATCH 056/824] notif animations --- .../common/widgets/NotificationWidget.qml | 40 +++++++++----- .../notifications/NotificationList.qml | 52 ++++++++++++++++++ .../NotificationStatusButton.qml | 55 +++++++++++++++++++ .../modules/sidebarRight/todo/TaskList.qml | 4 +- .../quickshell/services/MprisController.qml | 2 +- .config/quickshell/services/Notifications.qml | 9 +++ 6 files changed, 144 insertions(+), 18 deletions(-) create mode 100644 .config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index e65346e50..4d5ed7fcc 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -24,7 +24,7 @@ Item { Behavior on implicitHeight { enabled: enableAnimation NumberAnimation { - duration: Appearance.animation.elementDecel.duration + duration: Appearance.animation.elementDecelFast.duration easing.type: Appearance.animation.elementDecel.type } } @@ -33,21 +33,31 @@ Item { root.ready = true } - function destroyWithAnimation() { - notificationRowWrapper.anchors.left = undefined - notificationRowWrapper.anchors.right = undefined - notificationRowWrapper.anchors.fill = undefined - notificationBackground.anchors.left = undefined - notificationBackground.anchors.right = undefined - notificationBackground.anchors.fill = undefined - notificationRowWrapper.x = width - notificationBackground.x = width - destroyTimer1.start() + function destroyWithAnimation(delay = 0) { + destroyTimer0.interval = delay + destroyTimer0.start() + } + + Timer { + id: destroyTimer0 + interval: 0 + repeat: false + onTriggered: { + notificationRowWrapper.anchors.left = undefined + notificationRowWrapper.anchors.right = undefined + notificationRowWrapper.anchors.fill = undefined + notificationBackground.anchors.left = undefined + notificationBackground.anchors.right = undefined + notificationBackground.anchors.fill = undefined + notificationRowWrapper.x = width + notificationBackground.x = width + destroyTimer1.start() + } } Timer { id: destroyTimer1 - interval: Appearance.animation.elementDecel.duration / 2 + interval: Appearance.animation.elementDecelFast.duration repeat: false onTriggered: { notificationRowWrapper.anchors.top = undefined @@ -59,7 +69,7 @@ Item { Timer { id: destroyTimer2 - interval: Appearance.animation.elementDecel.duration + interval: Appearance.animation.elementDecelFast.duration repeat: false onTriggered: { root.destroy() @@ -102,7 +112,7 @@ Item { Behavior on x { enabled: enableAnimation NumberAnimation { - duration: Appearance.animation.elementDecel.duration + duration: Appearance.animation.elementDecelFast.duration easing.type: Appearance.animation.elementDecel.type } } @@ -120,7 +130,7 @@ Item { Behavior on x { enabled: enableAnimation NumberAnimation { - duration: Appearance.animation.elementDecel.duration + duration: Appearance.animation.elementDecelFast.duration easing.type: Appearance.animation.elementDecel.type } } diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml index 69fcffbb1..4af329448 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -70,6 +70,40 @@ Item { } } + // Placeholder when list is empty + Item { + anchors.fill: flickable + + visible: opacity > 0 + opacity: (root.notificationWidgetList.length === 0) ? 1 : 0 + + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + + ColumnLayout { + anchors.centerIn: parent + spacing: 5 + + MaterialSymbol { + Layout.alignment: Qt.AlignHCenter + font.pixelSize: 55 + color: Appearance.m3colors.m3outline + text: "notifications_active" + } + StyledText { + Layout.alignment: Qt.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3outline + horizontalAlignment: Text.AlignHCenter + text: "No notifications" + } + } + } + RowLayout { id: statusRow anchors.left: parent.left @@ -92,5 +126,23 @@ Item { } } } + + Item { Layout.fillWidth: true } + + NotificationStatusButton { + buttonIcon: "clear_all" + buttonText: "Clear" + onClicked: () => { + for (let i = notificationWidgetList.length - 1; i >= 0; i--) { + // for (let i = 0; i < notificationWidgetList.length; i++) { + const widget = notificationWidgetList[i]; + if (widget && widget.notificationObject) { + widget.destroyWithAnimation(); + } + } + notificationWidgetList = []; + Notifications.discardAll() + } + } } } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml new file mode 100644 index 000000000..39a1f4fe9 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml @@ -0,0 +1,55 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Button { + id: button + property string buttonText: "" + property string buttonIcon: "" + + implicitHeight: 30 + implicitWidth: contentRowLayout.implicitWidth + 10 * 2 + Behavior on implicitWidth { + SmoothedAnimation { + velocity: Appearance.animation.elementDecel.velocity + } + } + + PointingHandInteraction {} + + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.full + color: (button.down) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } + + } + contentItem: RowLayout { + id: contentRowLayout + // anchors.centerIn: parent + anchors.right: parent.right + spacing: 0 + MaterialSymbol { + text: buttonIcon + Layout.fillWidth: false + font.pixelSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer1 + } + StyledText { + text: buttonText + Layout.fillWidth: false + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.colors.colOnLayer1 + } + } + +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml index 2b920d432..5da182e09 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -145,16 +145,16 @@ Item { MaterialSymbol { Layout.alignment: Qt.AlignHCenter - text: emptyPlaceholderIcon font.pixelSize: 55 color: Appearance.m3colors.m3outline + text: emptyPlaceholderIcon } StyledText { Layout.alignment: Qt.AlignHCenter - text: emptyPlaceholderText font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3outline horizontalAlignment: Text.AlignHCenter + text: emptyPlaceholderText } } } diff --git a/.config/quickshell/services/MprisController.qml b/.config/quickshell/services/MprisController.qml index be0c1939f..aa9f6ac7a 100644 --- a/.config/quickshell/services/MprisController.qml +++ b/.config/quickshell/services/MprisController.qml @@ -130,7 +130,7 @@ Singleton { function setActivePlayer(player: MprisPlayer) { const targetPlayer = player ?? Mpris.players[0]; - console.log(`setactive: ${targetPlayer} from ${activePlayer}`) + console.log(`[Mpris] Active player ${targetPlayer} << ${activePlayer}`) if (targetPlayer && this.activePlayer) { this.__reverse = Mpris.players.indexOf(targetPlayer) < Mpris.players.indexOf(this.activePlayer); diff --git a/.config/quickshell/services/Notifications.qml b/.config/quickshell/services/Notifications.qml index 98a3212d5..6b8967b88 100644 --- a/.config/quickshell/services/Notifications.qml +++ b/.config/quickshell/services/Notifications.qml @@ -66,6 +66,15 @@ Singleton { root.discard(id); } + function discardAll() { + root.list = [] + triggerListChange() + notifFileView.setText(JSON.stringify(root.list, null, 2)) + notifServer.trackedNotifications.values.forEach((notif) => { + notif.dismiss() + }) + } + function attemptInvokeAction(id, notifIdentifier) { const notifServerIndex = notifServer.trackedNotifications.values.findIndex((notif) => notif.id === id); if (notifServerIndex !== -1) { From b5a9e01455b51f20beb3a33fe73cbda735347278 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 20 Apr 2025 08:44:06 +0200 Subject: [PATCH 057/824] notifications: avoid id collisions across sessions --- .../widgets/NotificationActionButton.qml | 2 +- .config/quickshell/services/Notifications.qml | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationActionButton.qml b/.config/quickshell/modules/common/widgets/NotificationActionButton.qml index 07bb9b33d..28088d744 100644 --- a/.config/quickshell/modules/common/widgets/NotificationActionButton.qml +++ b/.config/quickshell/modules/common/widgets/NotificationActionButton.qml @@ -15,7 +15,7 @@ Button { leftPadding: 10 rightPadding: 10 - PointingHandInteraction {} + // PointingHandInteraction {} background: Rectangle { radius: Appearance.rounding.small diff --git a/.config/quickshell/services/Notifications.qml b/.config/quickshell/services/Notifications.qml index 6b8967b88..12c263b52 100644 --- a/.config/quickshell/services/Notifications.qml +++ b/.config/quickshell/services/Notifications.qml @@ -11,6 +11,9 @@ Singleton { id: root property var filePath: `${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/notifications/notifications.json` property var list: [] + // Quickshell's notification IDs starts at 1 on each run, while saved notifications + // can already contain higher IDs. This is a workaround to avoid id collisions + property int idOffset signal initDone(); signal notify(notification: var); @@ -31,7 +34,7 @@ Singleton { onNotification: (notification) => { notification.tracked = true const newNotifObject = { - "id": notification.id, + "id": notification.id + root.idOffset, "actions": notification.actions.map((action) => { return { "identifier": action.identifier, @@ -54,7 +57,7 @@ Singleton { function discardNotification(id) { const index = root.list.findIndex((notif) => notif.id === id); - const notifServerIndex = notifServer.trackedNotifications.values.findIndex((notif) => notif.id === id); + const notifServerIndex = notifServer.trackedNotifications.values.findIndex((notif) => notif.id + root.idOffset === id); if (index !== -1) { root.list.splice(index, 1); notifFileView.setText(JSON.stringify(root.list, null, 2)) @@ -76,12 +79,13 @@ Singleton { } function attemptInvokeAction(id, notifIdentifier) { - const notifServerIndex = notifServer.trackedNotifications.values.findIndex((notif) => notif.id === id); + const notifServerIndex = notifServer.trackedNotifications.values.findIndex((notif) => notif.id + root.idOffset === id); if (notifServerIndex !== -1) { const notifServerNotif = notifServer.trackedNotifications.values[notifServerIndex]; const action = notifServerNotif.actions.find((action) => action.identifier === notifIdentifier); action.invoke() - } else console.log("Notification not found in server: " + id) + } + // else console.log("Notification not found in server: " + id) root.discard(id); } @@ -103,7 +107,14 @@ Singleton { onLoaded: { const fileContents = notifFileView.text() root.list = JSON.parse(fileContents) + // Find largest id + let maxId = 0 + root.list.forEach((notif) => { + maxId = Math.max(maxId, notif.id) + }) + console.log("[Notifications] File loaded") + root.idOffset = maxId root.initDone() } onLoadFailed: (error) => { From 11ff4bbfaf539ea33a82a38b6c4cc4d45e74c7d8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 20 Apr 2025 09:46:13 +0200 Subject: [PATCH 058/824] MORE ANIMATION FIX AAAAAA --- .../common/widgets/NotificationWidget.qml | 335 ++++++++++-------- .../sidebarRight/CenterWidgetGroup.qml | 11 + .../modules/sidebarRight/SidebarRight.qml | 2 +- .../notifications/NotificationList.qml | 6 +- 4 files changed, 209 insertions(+), 145 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 4d5ed7fcc..b66dc705e 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -38,6 +38,12 @@ Item { destroyTimer0.start() } + function toggleExpanded() { + root.enableAnimation = true + notificationRowWrapper.anchors.bottom = undefined + root.expanded = !root.expanded + } + Timer { id: destroyTimer0 interval: 0 @@ -83,7 +89,7 @@ Item { if (mouse.button == Qt.MiddleButton) Notifications.discardNotification(notificationObject.id); else if (mouse.button == Qt.RightButton) - root.expanded = !root.expanded; + root.toggleExpanded() } } @@ -95,6 +101,7 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom + // anchors.top: parent.top anchors.topMargin: notificationListSpacing implicitHeight: notificationColumnLayout.implicitHeight + notificationListSpacing @@ -103,6 +110,7 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom + // anchors.top: parent.top height: notificationColumnLayout.implicitHeight color: (notificationObject.urgency == NotificationUrgency.Critical) ? @@ -116,6 +124,13 @@ Item { easing.type: Appearance.animation.elementDecel.type } } + Behavior on height { + enabled: enableAnimation + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecel.type + } + } } } @@ -125,6 +140,7 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom + // anchors.top: parent.top implicitHeight: notificationColumnLayout.implicitHeight + notificationListSpacing Behavior on x { @@ -140,172 +156,186 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom - RowLayout { - id: notificationRowLayout - - Layout.fillWidth: true - - Rectangle { // App icon - id: iconRectangle - implicitWidth: 47 - implicitHeight: 47 - Layout.leftMargin: 10 - Layout.topMargin: 10 - Layout.bottomMargin: 10 - Layout.alignment: Qt.AlignTop - Layout.fillWidth: false - radius: Appearance.rounding.full - color: Appearance.m3colors.m3secondaryContainer - MaterialSymbol { - visible: notificationObject.appIcon == "" - text: (notificationObject.urgency == NotificationUrgency.Critical) ? "release_alert" : - NotificationUtils.guessMessageType(notificationObject.summary) - anchors.fill: parent - color: (notificationObject.urgency == NotificationUrgency.Critical) ? - Appearance.mix(Appearance.m3colors.m3onSecondary, Appearance.m3colors.m3onSecondaryContainer, 0.1) : - Appearance.m3colors.m3onSecondaryContainer - font.pixelSize: 27 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + spacing: 0 + Item { + Layout.fillWidth: true + implicitHeight: notificationRowLayout.implicitHeight + Behavior on implicitHeight { + enabled: enableAnimation + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type } - IconImage { - visible: notificationObject.image == "" && notificationObject.appIcon != "" - anchors.centerIn: parent - implicitSize: 33 - asynchronous: true - source: Quickshell.iconPath(notificationObject.appIcon) - } - Item { - anchors.fill: parent - visible: notificationObject.image != "" - Image { - id: notifImage + } + RowLayout { + id: notificationRowLayout + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Rectangle { // App icon + id: iconRectangle + implicitWidth: 47 + implicitHeight: 47 + Layout.leftMargin: 10 + Layout.topMargin: 10 + Layout.bottomMargin: 10 + Layout.alignment: Qt.AlignTop + Layout.fillWidth: false + radius: Appearance.rounding.full + color: Appearance.m3colors.m3secondaryContainer + MaterialSymbol { + visible: notificationObject.appIcon == "" + text: (notificationObject.urgency == NotificationUrgency.Critical) ? "release_alert" : + NotificationUtils.guessMessageType(notificationObject.summary) anchors.fill: parent - readonly property int size: parent.width - - source: notificationObject?.image - fillMode: Image.PreserveAspectCrop - cache: false - antialiasing: true - asynchronous: true - - width: size - height: size - sourceSize.width: size - sourceSize.height: size - - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: notifImage.size - height: notifImage.size - radius: Appearance.rounding.full - } - } + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.mix(Appearance.m3colors.m3onSecondary, Appearance.m3colors.m3onSecondaryContainer, 0.1) : + Appearance.m3colors.m3onSecondaryContainer + font.pixelSize: 27 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter } IconImage { - visible: notificationObject.appIcon != "" - anchors.bottom: parent.bottom - anchors.right: parent.right - implicitSize: 23 + visible: notificationObject.image == "" && notificationObject.appIcon != "" + anchors.centerIn: parent + implicitSize: 33 asynchronous: true source: Quickshell.iconPath(notificationObject.appIcon) } - } - } - ColumnLayout { // Notification content - spacing: 0 - Layout.fillWidth: true + Item { + anchors.fill: parent + visible: notificationObject.image != "" + Image { + id: notifImage - RowLayout { // Row of summary, time and expand button - Layout.topMargin: 10 - Layout.leftMargin: 10 - Layout.rightMargin: 10 - Layout.fillWidth: true + anchors.fill: parent + readonly property int size: parent.width - StyledText { // Summary - Layout.fillWidth: true - horizontalAlignment: Text.AlignLeft - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.colors.colOnLayer2 - text: notificationObject.summary - wrapMode: expanded ? Text.Wrap : Text.NoWrap - elide: Text.ElideRight - } + source: notificationObject?.image + fillMode: Image.PreserveAspectCrop + cache: false + antialiasing: true + asynchronous: true - Item { Layout.fillWidth: true } + width: size + height: size + sourceSize.width: size + sourceSize.height: size - StyledText { // Time - id: notificationTimeText - Layout.fillWidth: false - wrapMode: Text.Wrap - horizontalAlignment: Text.AlignLeft - font.pixelSize: Appearance.font.pixelSize.smaller - color: Appearance.m3colors.m3outline - text: NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) - - Connections { - target: DateTime - function onTimeChanged() { - notificationTimeText.text = NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: notifImage.size + height: notifImage.size + radius: Appearance.rounding.full + } } } + IconImage { + visible: notificationObject.appIcon != "" + anchors.bottom: parent.bottom + anchors.right: parent.right + implicitSize: 23 + asynchronous: true + source: Quickshell.iconPath(notificationObject.appIcon) + } } + } - Button { // Expand button - Layout.alignment: Qt.AlignVCenter - id: expandButton - implicitWidth: 22 - implicitHeight: 22 + ColumnLayout { // Notification content + spacing: 0 + Layout.fillWidth: true - PointingHandInteraction{} - onClicked: { - root.enableAnimation = true - root.expanded = !root.expanded + RowLayout { // Row of summary, time and expand button + Layout.topMargin: 10 + Layout.leftMargin: 10 + Layout.rightMargin: 10 + Layout.fillWidth: true + + StyledText { // Summary + Layout.fillWidth: true + horizontalAlignment: Text.AlignLeft + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colOnLayer2 + text: notificationObject.summary + wrapMode: expanded ? Text.Wrap : Text.NoWrap + elide: Text.ElideRight } - background: Rectangle { - anchors.fill: parent - radius: Appearance.rounding.full - color: (expandButton.down) ? Appearance.colors.colLayer2Active : (expandButton.hovered ? Appearance.colors.colLayer2Hover : Appearance.transparentize(Appearance.colors.colLayer2, 1)) + Item { Layout.fillWidth: true } + + StyledText { // Time + id: notificationTimeText + Layout.fillWidth: false + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignLeft + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.m3colors.m3outline + text: NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) + + Connections { + target: DateTime + function onTimeChanged() { + notificationTimeText.text = NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) + } + } + } + + Button { // Expand button + Layout.alignment: Qt.AlignVCenter + id: expandButton + implicitWidth: 22 + implicitHeight: 22 + + PointingHandInteraction{} + onClicked: { + root.toggleExpanded() + } + + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.full + color: (expandButton.down) ? Appearance.colors.colLayer2Active : (expandButton.hovered ? Appearance.colors.colLayer2Hover : Appearance.transparentize(Appearance.colors.colLayer2, 1)) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } - Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type } } - } + contentItem: MaterialSymbol { + anchors.centerIn: parent + text: expanded ? "keyboard_arrow_up" : "keyboard_arrow_down" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colOnLayer2 + } - contentItem: MaterialSymbol { - anchors.centerIn: parent - text: expanded ? "keyboard_arrow_up" : "keyboard_arrow_down" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.colors.colOnLayer2 } - } - } - StyledText { // Notification body - Layout.fillWidth: true - Layout.leftMargin: 10 - Layout.rightMargin: 10 - Layout.bottomMargin: 10 - clip: true + StyledText { // Notification body + Layout.fillWidth: true + Layout.leftMargin: 10 + Layout.rightMargin: 10 + Layout.bottomMargin: 10 + clip: true - wrapMode: expanded ? Text.Wrap : Text.NoWrap - elide: Text.ElideRight - font.pixelSize: Appearance.font.pixelSize.small - horizontalAlignment: Text.AlignLeft - color: Appearance.m3colors.m3outline - // textFormat: Text.MarkdownText - text: notificationObject.body + wrapMode: expanded ? Text.Wrap : Text.NoWrap + elide: Text.ElideRight + font.pixelSize: Appearance.font.pixelSize.small + horizontalAlignment: Text.AlignLeft + color: Appearance.m3colors.m3outline + // textFormat: Text.MarkdownText + text: expanded ? notificationObject.body : notificationObject.body.split("\n")[0] + } } } } @@ -314,18 +344,39 @@ Item { Flickable { id: actionsFlickable Layout.fillWidth: true - Layout.topMargin: -5 + // Layout.topMargin: -5 Layout.leftMargin: 10 Layout.rightMargin: 10 - Layout.bottomMargin: 10 - implicitHeight: actionRowLayout.implicitHeight + Layout.bottomMargin: expanded ? 10 : 0 + implicitHeight: expanded ? actionRowLayout.implicitHeight : 0 + height: expanded ? actionRowLayout.implicitHeight : 0 contentWidth: actionRowLayout.implicitWidth clip: true - visible: expanded + opacity: expanded ? 1 : 0 + visible: opacity > 0 + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + Behavior on height { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + Behavior on implicitHeight { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } RowLayout { id: actionRowLayout + Layout.alignment: Qt.AlignBottom Repeater { id: actionRepeater diff --git a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml index ca96c34b0..bb0b66151 100644 --- a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml @@ -17,6 +17,17 @@ Rectangle { property int currentTab: 0 property var tabButtonList: [{"icon": "notifications", "name": "Notifications"}, {"icon": "volume_up", "name": "Volume mixer"}] + Keys.onPressed: (event) => { + if (event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) { + if (event.key === Qt.Key_PageDown) { + root.currentTab = Math.min(root.currentTab + 1, root.tabButtonList.length - 1) + } else if (event.key === Qt.Key_PageUp) { + root.currentTab = Math.max(root.currentTab - 1, 0) + } + event.accepted = true; + } + } + ColumnLayout { anchors.margins: 5 anchors.fill: parent diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 01c835e57..572015fb0 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -75,7 +75,6 @@ Scope { color: Appearance.colors.colLayer0 radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 - focus: true Keys.onPressed: (event) => { if (event.key === Qt.Key_Escape) { sidebarRoot.visible = false; @@ -141,6 +140,7 @@ Scope { // Center widget group CenterWidgetGroup { + focus: sidebarRoot.visible Layout.alignment: Qt.AlignHCenter Layout.fillHeight: true Layout.fillWidth: true diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml index 4af329448..1fbfcc349 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -79,8 +79,8 @@ Item { Behavior on opacity { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.menuDecel.duration + easing.type: Appearance.animation.menuDecel.type } } @@ -130,6 +130,8 @@ Item { Item { Layout.fillWidth: true } NotificationStatusButton { + Layout.alignment: Qt.AlignVCenter + Layout.topMargin: 5 buttonIcon: "clear_all" buttonText: "Clear" onClicked: () => { From 74fe9f44dd10b1823198fdb6d91df2febdf5eef2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:52:14 +0200 Subject: [PATCH 059/824] notifications: drag right to dismiss --- .../common/widgets/NotificationWidget.qml | 76 +++++++++++++++++-- .../modules/sidebarRight/todo/TaskList.qml | 5 +- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index b66dc705e..5a02cbe1d 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -82,15 +82,60 @@ Item { } } - MouseArea { // Middle click to close + MouseArea { + // Middle click to close anchors.fill: parent - acceptedButtons: Qt.MiddleButton | Qt.RightButton + acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton onClicked: (mouse) => { if (mouse.button == Qt.MiddleButton) Notifications.discardNotification(notificationObject.id); else if (mouse.button == Qt.RightButton) root.toggleExpanded() } + + // Flick right to dismiss + property real startX: 0 + property real dragStartThreshold: 10 + property real dragConfirmThresholdRatio: 0.2 + property bool dragStarted: false + + onPressed: (mouse) => { + if (mouse.button === Qt.LeftButton) { + startX = mouse.x + } + } + onDragStartedChanged: () => { + // Prevent drag focus being shifted to parent flickable + root.parent.parent.parent.interactive = !dragStarted + } + onReleased: (mouse) => { + dragStarted = false + if (mouse.button === Qt.LeftButton) { + if (notificationRowWrapper.x > width * dragConfirmThresholdRatio) { + Notifications.discardNotification(notificationObject.id); + } else { + // Animate back if not far enough + notificationRowWrapper.x = 0 + notificationBackground.x = 0 + } + } + } + onPositionChanged: (mouse) => { + if (mouse.buttons & Qt.LeftButton) { + let dx = mouse.x - startX + if (dragStarted || dx > dragStartThreshold) { + dragStarted = true + notificationRowWrapper.anchors.left = undefined + notificationRowWrapper.anchors.right = undefined + notificationRowWrapper.anchors.fill = undefined + notificationBackground.anchors.left = undefined + notificationBackground.anchors.right = undefined + notificationBackground.anchors.fill = undefined + notificationRowWrapper.x = Math.max(0, dx) + notificationBackground.x = Math.max(0, dx) + } + } + } } // Background @@ -257,6 +302,7 @@ Item { StyledText { // Summary Layout.fillWidth: true horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignBottom font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.colors.colOnLayer2 text: notificationObject.summary @@ -264,11 +310,21 @@ Item { elide: Text.ElideRight } - Item { Layout.fillWidth: true } + // StyledText { // dot + // id: notificationDot + // Layout.fillWidth: false + // wrapMode: Text.Wrap + // horizontalAlignment: Text.AlignLeft + // font.pixelSize: Appearance.font.pixelSize.smaller + // color: Appearance.m3colors.m3outline + // text: NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) + // } StyledText { // Time id: notificationTimeText Layout.fillWidth: false + Layout.alignment: Qt.AlignTop + Layout.topMargin: 3 wrapMode: Text.Wrap horizontalAlignment: Text.AlignLeft font.pixelSize: Appearance.font.pixelSize.smaller @@ -283,8 +339,10 @@ Item { } } + Item { Layout.fillWidth: true } + Button { // Expand button - Layout.alignment: Qt.AlignVCenter + Layout.alignment: Qt.AlignTop id: expandButton implicitWidth: 22 implicitHeight: 22 @@ -311,11 +369,19 @@ Item { contentItem: MaterialSymbol { anchors.centerIn: parent - text: expanded ? "keyboard_arrow_up" : "keyboard_arrow_down" + // text: expanded ? "keyboard_arrow_up" : "keyboard_arrow_down" + text: "keyboard_arrow_down" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.colors.colOnLayer2 + rotation: expanded ? 180 : 0 + Behavior on rotation { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } } } diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml index 5da182e09..aca35af3a 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -4,6 +4,7 @@ import "root:/services" import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Quickshell Item { id: root @@ -25,7 +26,9 @@ Item { width: parent.width spacing: 0 Repeater { - model: taskList + model: ScriptModel { + values: taskList + } delegate: Item { id: todoItem property bool pendingDoneToggle: false From 9d7262382f65c23889af6bd6945de46ed9f9864b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:53:48 +0200 Subject: [PATCH 060/824] notifications remove gap between time and arrow --- .../modules/common/widgets/NotificationWidget.qml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 5a02cbe1d..e22544d27 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -310,16 +310,6 @@ Item { elide: Text.ElideRight } - // StyledText { // dot - // id: notificationDot - // Layout.fillWidth: false - // wrapMode: Text.Wrap - // horizontalAlignment: Text.AlignLeft - // font.pixelSize: Appearance.font.pixelSize.smaller - // color: Appearance.m3colors.m3outline - // text: NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) - // } - StyledText { // Time id: notificationTimeText Layout.fillWidth: false @@ -339,8 +329,6 @@ Item { } } - Item { Layout.fillWidth: true } - Button { // Expand button Layout.alignment: Qt.AlignTop id: expandButton From bc6d963800670c55675f80957868526ff565a239 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 20 Apr 2025 12:14:44 +0200 Subject: [PATCH 061/824] notifications: handle body images --- .../common/widgets/NotificationWidget.qml | 28 ++++++++++++++++--- .config/quickshell/services/Notifications.qml | 2 +- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index e22544d27..01c46072d 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -5,6 +5,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell +import Quickshell.Io import Quickshell.Widgets import Quickshell.Services.Notifications import "./notification_utils.js" as NotificationUtils @@ -20,6 +21,11 @@ Item { Layout.fillWidth: true clip: true + Process { + id: closeSidebarProcess + command: ["bash", "-c", `qs ipc call sidebarRight close`] + } + implicitHeight: ready ? notificationColumnLayout.implicitHeight + notificationListSpacing : 0 Behavior on implicitHeight { enabled: enableAnimation @@ -96,7 +102,7 @@ Item { // Flick right to dismiss property real startX: 0 property real dragStartThreshold: 10 - property real dragConfirmThresholdRatio: 0.2 + property real dragConfirmThreshold: 70 property bool dragStarted: false onPressed: (mouse) => { @@ -111,7 +117,7 @@ Item { onReleased: (mouse) => { dragStarted = false if (mouse.button === Qt.LeftButton) { - if (notificationRowWrapper.x > width * dragConfirmThresholdRatio) { + if (notificationRowWrapper.x > dragConfirmThreshold) { Notifications.discardNotification(notificationObject.id); } else { // Animate back if not far enough @@ -376,6 +382,7 @@ Item { } StyledText { // Notification body + id: notificationBodyText Layout.fillWidth: true Layout.leftMargin: 10 Layout.rightMargin: 10 @@ -387,8 +394,21 @@ Item { font.pixelSize: Appearance.font.pixelSize.small horizontalAlignment: Text.AlignLeft color: Appearance.m3colors.m3outline - // textFormat: Text.MarkdownText - text: expanded ? notificationObject.body : notificationObject.body.split("\n")[0] + textFormat: expanded ? Text.RichText : Text.StyledText + text: expanded + ? `` + + `${notificationObject.body.replace(/\n/g, "
")}` + : notificationObject.body.replace(/ Date: Sun, 20 Apr 2025 12:32:04 +0200 Subject: [PATCH 062/824] clipping rounding for scrollables --- .../modules/common/widgets/NotificationWidget.qml | 10 +++++++++- .../modules/sidebarRight/CenterWidgetGroup.qml | 11 ++++++++++- .../sidebarRight/notifications/NotificationList.qml | 11 ++++++++++- .../quickshell/modules/sidebarRight/todo/TaskList.qml | 11 ++++++++++- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 01c46072d..204208df0 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -425,7 +425,15 @@ Item { implicitHeight: expanded ? actionRowLayout.implicitHeight : 0 height: expanded ? actionRowLayout.implicitHeight : 0 contentWidth: actionRowLayout.implicitWidth - clip: true + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: actionsFlickable.width + height: actionsFlickable.height + radius: Appearance.rounding.small + } + } opacity: expanded ? 1 : 0 visible: opacity > 0 diff --git a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml index bb0b66151..4230a6064 100644 --- a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml @@ -4,6 +4,7 @@ import "root:/services" import "./calendar" import "./todo" import "./notifications" +import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -111,10 +112,18 @@ Rectangle { Layout.topMargin: 10 Layout.fillWidth: true Layout.fillHeight: true - clip: true currentIndex: currentTab onCurrentIndexChanged: currentTab = currentIndex + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: swipeView.width + height: swipeView.height + radius: Appearance.rounding.normal + } + } + NotificationList {} Item{} } diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml index 1fbfcc349..e5b6465ea 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -1,6 +1,7 @@ import "root:/modules/common" import "root:/modules/common/widgets" import "root:/services" +import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -58,7 +59,15 @@ Item { anchors.top: parent.top anchors.bottom: statusRow.top contentHeight: columnLayout.height - clip: true + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: flickable.width + height: flickable.height + radius: Appearance.rounding.normal + } + } ColumnLayout { // Scrollable window content id: columnLayout diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml index aca35af3a..f68dadaec 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -1,6 +1,7 @@ import "root:/modules/common" import "root:/modules/common/widgets" import "root:/services" +import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -19,7 +20,15 @@ Item { id: flickable anchors.fill: parent contentHeight: columnLayout.height - clip: true + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: flickable.width + height: flickable.height + radius: Appearance.rounding.small + } + } ColumnLayout { id: columnLayout From ab334c7a944e3f4292eee42fc5e479c8784be246 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 20 Apr 2025 12:32:50 +0200 Subject: [PATCH 063/824] Update keybinds.conf --- .config/hypr/hyprland/keybinds.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 00d5ca299..27fa950ff 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -178,7 +178,8 @@ bindl = , XF86AudioMute, exec, agsv1 run-js 'indicator.popup(1);' # [hidden] bindl = Super+Shift,M, exec, agsv1 run-js 'indicator.popup(1);' # [hidden] # Testing -bind = Super+Alt, f12, exec, bash -c 'ACTION=$(notify-send "Test notification" "The quick shill fox jumps over the crazy dog. Anyway, this notification should contain your profile image (like on the login screen) (can be created with GNOME Control center)" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 3000 -i "discord" -A "openImage=Open Image" -A "action2=Intentionally useless button" -A "action3=yes its intentional"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"' # [hidden] +bind = Super+Alt, f11, exec, bash -c 'RANDOM_IMAGE=$(find ~/Pictures -type f | grep -v -i "nipple" | grep -v -i "pussy" | shuf -n 1); ACTION=$(notify-send "Fancy test notification" "This notification should contain your profile image and
Discord icon. Oh and here is a random image in your Pictures folder: \"Testing" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Open the random image" -A "action3=Useless button"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] +bind = Super+Alt, f12, exec, bash -c 'notify-send "Test notification" "A simple rich text test notification" -t 3000' # [hidden] bind = Super+Alt, Equal, exec, notify-send "Urgent notification" "Ah hell no" -u critical -a 'Hyprland keybind' # [hidden] ##! Media From e66c942790e14f5aa238ebb8979d8adc49dac7e6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 20 Apr 2025 17:53:11 +0200 Subject: [PATCH 064/824] notifications: copy button --- .../quickshell/modules/common/Appearance.qml | 8 +-- .../common/widgets/NotificationWidget.qml | 56 ++++++++++++++++++- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 05c3f059c..70eb0e726 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -127,10 +127,10 @@ Singleton { property color colPrimaryContainerActive: mix(m3colors.m3primaryContainer, colLayer1Active, 0.6) property color colSecondaryHover: mix(m3colors.m3secondary, colLayer1Hover, 0.85) property color colSecondaryActive: mix(m3colors.m3secondary, colLayer1Active, 0.4) - property color colSecondaryContainerHover: mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.67) - property color colSecondaryContainerActive: mix(m3colors.m3secondaryContainer, colLayer1Active, 0.6) - property color colSurfaceContainerHighestHover: mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.9) - property color colSurfaceContainerHighestActive: mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.82) + property color colSecondaryContainerHover: mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.6) + property color colSecondaryContainerActive: mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54) + property color colSurfaceContainerHighestHover: mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.95) + property color colSurfaceContainerHighestActive: mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.85) property color colTooltip: m3colors.m3inverseSurface property color colOnTooltip: m3colors.m3inverseOnSurface property color colScrim: transparentize(m3colors.m3scrim, 0.5) diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 204208df0..d52bec6d0 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -26,6 +26,11 @@ Item { command: ["bash", "-c", `qs ipc call sidebarRight close`] } + Process { + id: copyNotificationBody + command: ["bash", "-c", `wl-copy "${notificationObject.body}"`] + } + implicitHeight: ready ? notificationColumnLayout.implicitHeight + notificationListSpacing : 0 Behavior on implicitHeight { enabled: enableAnimation @@ -110,6 +115,13 @@ Item { startX = mouse.x } } + onPressAndHold: (mouse) => { + if (mouse.button === Qt.LeftButton) { + copyNotificationBody.running = true + notificationSummaryText.text = `${notificationObject.summary} (copied)` + console.log(notificationSummaryText.text) + } + } onDragStartedChanged: () => { // Prevent drag focus being shifted to parent flickable root.parent.parent.parent.interactive = !dragStarted @@ -306,6 +318,7 @@ Item { Layout.fillWidth: true StyledText { // Summary + id: notificationSummaryText Layout.fillWidth: true horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignBottom @@ -473,15 +486,56 @@ Item { } } + NotificationActionButton { + Layout.fillWidth: true + urgency: notificationObject.urgency + implicitWidth: (notificationObject.actions.length == 0) ? (actionsFlickable.width / 2) : + (contentItem.implicitWidth + leftPadding + rightPadding) + + onClicked: { + copyNotificationBody.running = true + copyIcon.text = "inventory" + copyIconTimer.stop() + copyIconTimer.start() + } + + Timer { + id: copyIconTimer + interval: 1500 + repeat: false + onTriggered: { + copyIcon.text = "content_copy" + } + } + + contentItem: MaterialSymbol { + id: copyIcon + font.pixelSize: Appearance.font.pixelSize.large + horizontalAlignment: Text.AlignHCenter + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface + text: "content_copy" + } + } + NotificationActionButton { Layout.fillWidth: true buttonText: "Close" urgency: notificationObject.urgency - implicitWidth: (notificationObject.actions.length == 0) ? (actionsFlickable.width) : + implicitWidth: (notificationObject.actions.length == 0) ? (actionsFlickable.width / 2) : (contentItem.implicitWidth + leftPadding + rightPadding) + onClicked: { Notifications.discardNotification(notificationObject.id); } + + contentItem: MaterialSymbol { + font.pixelSize: Appearance.font.pixelSize.large + horizontalAlignment: Text.AlignHCenter + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface + text: "close" + } } } From 19ba8329386d87f3b1f26d38a4adf13252c469f2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 20 Apr 2025 17:53:17 +0200 Subject: [PATCH 065/824] volume mixer --- .../modules/common/widgets/StyledToolTip.qml | 2 +- .../sidebarRight/CenterWidgetGroup.qml | 5 +- .../sidebarRight/volumeMixer/VolumeMixer.qml | 42 +++++ .../volumeMixer/VolumeMixerEntry.qml | 145 ++++++++++++++++++ 4 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 .config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml create mode 100644 .config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index 388a4bad7..13959fd8f 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -9,7 +9,7 @@ ToolTip { property bool extraVisibleCondition: true padding: 7 - visible: (extraVisibleCondition && parent.hovered) + visible: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) background: Rectangle { color: Appearance.colors.colTooltip diff --git a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml index 4230a6064..2322a505d 100644 --- a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml @@ -2,8 +2,9 @@ import "root:/modules/common" import "root:/modules/common/widgets" import "root:/services" import "./calendar" -import "./todo" import "./notifications" +import "./todo" +import "./volumeMixer" import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls @@ -125,7 +126,7 @@ Rectangle { } NotificationList {} - Item{} + VolumeMixer {} } } } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml new file mode 100644 index 000000000..998cdd480 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml @@ -0,0 +1,42 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Widgets +import Quickshell.Services.Pipewire + + +Item { + id: root + Flickable { + id: flickable + anchors.fill: parent + contentHeight: volumeMixerColumnLayout.height + ColumnLayout { + id: volumeMixerColumnLayout + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 10 + spacing: 10 + + // get a list of nodes that output to the default sink + PwNodeLinkTracker { + id: linkTracker + node: Pipewire.defaultAudioSink + } + + Repeater { + model: linkTracker.linkGroups + + VolumeMixerEntry { + required property PwLinkGroup modelData + node: modelData.source // target = default sink, source = what we need + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml new file mode 100644 index 000000000..a3898df95 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml @@ -0,0 +1,145 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Widgets +import Quickshell.Services.Pipewire + + +RowLayout { + id: root + required property PwNode node; + PwObjectTracker { objects: [ node ] } + + spacing: 10 + + Image { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + visible: source != "" + sourceSize.width: 50 + sourceSize.height: 50 + source: { + const icon = node.properties["application.icon-name"] ?? "audio-volume-high-symbolic"; + return `image://icon/${icon}`; + } + } + + ColumnLayout { + Layout.fillWidth: true + RowLayout { + StyledText { + Layout.fillWidth: true + font.pixelSize: Appearance.font.pixelSize.normal + elide: Text.ElideRight + text: { + // application.name -> description -> name + const app = node.properties["application.name"] ?? (node.description != "" ? node.description : node.name); + const media = node.properties["media.name"]; + return media != undefined ? `${app} • ${media}` : app; + } + } + } + + RowLayout { + Slider { + id: slider + property real backgroundDotSize: 4 + property real backgroundDotMargins: 4 + property real handleMargins: slider.pressed ? 3 : 6 + property real handleWidth: slider.pressed ? 3 : 5 + property real handleHeight: 44 + property real handleLimit: slider.backgroundDotMargins + property real limitedHandleWidth: (slider.availableWidth - handleWidth - slider.handleLimit * 2) + Layout.fillWidth: true + value: node.audio.volume + onValueChanged: node.audio.volume = value + from: 0 + to: 1 + + Behavior on value { // This makes the volume shift smoothly + SmoothedAnimation { + velocity: Appearance.animation.elementDecel.velocity + } + } + + Behavior on handleMargins { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + + MouseArea { + anchors.fill: parent + onPressed: (mouse) => mouse.accepted = false + cursorShape: slider.pressed ? Qt.ClosedHandCursor : Qt.PointingHandCursor + } + + background: Item { + anchors.verticalCenter: parent.verticalCenter + implicitHeight: 16 + + // Fill left + Rectangle { + anchors.left: parent.left + width: slider.handleLimit + slider.visualPosition * slider.limitedHandleWidth - (slider.handleMargins + slider.handleWidth / 2) + height: parent.height + color: Appearance.m3colors.m3primary + topLeftRadius: Appearance.rounding.full + bottomLeftRadius: Appearance.rounding.full + topRightRadius: Appearance.rounding.unsharpen + bottomRightRadius: Appearance.rounding.unsharpen + } + + // Fill right + Rectangle { + anchors.right: parent.right + width: slider.handleLimit + (1 - slider.visualPosition) * slider.limitedHandleWidth - (slider.handleMargins + slider.handleWidth / 2) + height: parent.height + color: Appearance.m3colors.m3secondaryContainer + topLeftRadius: Appearance.rounding.unsharpen + bottomLeftRadius: Appearance.rounding.unsharpen + topRightRadius: Appearance.rounding.full + bottomRightRadius: Appearance.rounding.full + } + + // Dot at the end + Rectangle { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: slider.backgroundDotMargins + width: slider.backgroundDotSize + height: slider.backgroundDotSize + radius: Appearance.rounding.full + color: Appearance.m3colors.m3onSecondaryContainer + } + } + + handle: Rectangle { + id: handle + x: slider.leftPadding + slider.handleLimit + slider.visualPosition * slider.limitedHandleWidth + y: slider.topPadding + slider.availableHeight / 2 - height / 2 + implicitWidth: slider.handleWidth + implicitHeight: slider.handleHeight + radius: Appearance.rounding.full + color: Appearance.m3colors.m3onSecondaryContainer + + Behavior on implicitWidth { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + + StyledToolTip { + extraVisibleCondition: slider.pressed + content: `${Math.round(slider.value * 100)}%` + } + } + } + } + } +} \ No newline at end of file From 5b84100c528a2a8d38ef974d08e0057197123eb2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 20 Apr 2025 18:28:28 +0200 Subject: [PATCH 066/824] refractor --- .../modules/common/widgets/StyledSlider.qml | 105 ++++++++++++++++++ .../sidebarRight/volumeMixer/VolumeMixer.qml | 10 ++ .../volumeMixer/VolumeMixerEntry.qml | 94 +--------------- 3 files changed, 116 insertions(+), 93 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/StyledSlider.qml diff --git a/.config/quickshell/modules/common/widgets/StyledSlider.qml b/.config/quickshell/modules/common/widgets/StyledSlider.qml new file mode 100644 index 000000000..17433c591 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/StyledSlider.qml @@ -0,0 +1,105 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Widgets + +// Material 3 slider. See https://m3.material.io/components/sliders/overview +Slider { + id: slider + property real scale: 0.85 + property real backgroundDotSize: 4 * scale + property real backgroundDotMargins: 4 * scale + property real handleMargins: (slider.pressed ? 3 : 6) * scale + property real handleWidth: (slider.pressed ? 3 : 5) * scale + property real handleHeight: 44 * scale + property real handleLimit: slider.backgroundDotMargins * scale + + property real limitedHandleRangeWidth: (slider.availableWidth - handleWidth - slider.handleLimit * 2) + Layout.fillWidth: true + from: 0 + to: 1 + + Behavior on value { // This makes the adjusted value (like volume) shift smoothly + SmoothedAnimation { + velocity: Appearance.animation.elementDecel.velocity + } + } + + Behavior on handleMargins { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + + MouseArea { + anchors.fill: parent + onPressed: (mouse) => mouse.accepted = false + cursorShape: slider.pressed ? Qt.ClosedHandCursor : Qt.PointingHandCursor + } + + background: Item { + anchors.verticalCenter: parent.verticalCenter + implicitHeight: 12 // Somehow binding this makes it fill height. Must be set with a constant like this + + // Fill left + Rectangle { + anchors.left: parent.left + width: slider.handleLimit + slider.visualPosition * slider.limitedHandleRangeWidth - (slider.handleMargins + slider.handleWidth / 2) + height: parent.height + color: Appearance.m3colors.m3primary + topLeftRadius: Appearance.rounding.full + bottomLeftRadius: Appearance.rounding.full + topRightRadius: Appearance.rounding.unsharpen + bottomRightRadius: Appearance.rounding.unsharpen + } + + // Fill right + Rectangle { + anchors.right: parent.right + width: slider.handleLimit + (1 - slider.visualPosition) * slider.limitedHandleRangeWidth - (slider.handleMargins + slider.handleWidth / 2) + height: parent.height + color: Appearance.m3colors.m3secondaryContainer + topLeftRadius: Appearance.rounding.unsharpen + bottomLeftRadius: Appearance.rounding.unsharpen + topRightRadius: Appearance.rounding.full + bottomRightRadius: Appearance.rounding.full + } + + // Dot at the end + Rectangle { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: slider.backgroundDotMargins + width: slider.backgroundDotSize + height: slider.backgroundDotSize + radius: Appearance.rounding.full + color: Appearance.m3colors.m3onSecondaryContainer + } + } + + handle: Rectangle { + id: handle + x: slider.leftPadding + slider.handleLimit + slider.visualPosition * slider.limitedHandleRangeWidth + y: slider.topPadding + slider.availableHeight / 2 - height / 2 + implicitWidth: slider.handleWidth + implicitHeight: slider.handleHeight + radius: Appearance.rounding.full + color: Appearance.m3colors.m3onSecondaryContainer + + Behavior on implicitWidth { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + + StyledToolTip { + extraVisibleCondition: slider.pressed + content: `${Math.round(slider.value * 100)}%` + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml index 998cdd480..68b96dea3 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml @@ -15,6 +15,16 @@ Item { id: flickable anchors.fill: parent contentHeight: volumeMixerColumnLayout.height + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: flickable.width + height: flickable.height + radius: Appearance.rounding.normal + } + } + ColumnLayout { id: volumeMixerColumnLayout anchors.top: parent.top diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml index a3898df95..79975135e 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml @@ -44,101 +44,9 @@ RowLayout { } RowLayout { - Slider { - id: slider - property real backgroundDotSize: 4 - property real backgroundDotMargins: 4 - property real handleMargins: slider.pressed ? 3 : 6 - property real handleWidth: slider.pressed ? 3 : 5 - property real handleHeight: 44 - property real handleLimit: slider.backgroundDotMargins - property real limitedHandleWidth: (slider.availableWidth - handleWidth - slider.handleLimit * 2) - Layout.fillWidth: true + StyledSlider { value: node.audio.volume onValueChanged: node.audio.volume = value - from: 0 - to: 1 - - Behavior on value { // This makes the volume shift smoothly - SmoothedAnimation { - velocity: Appearance.animation.elementDecel.velocity - } - } - - Behavior on handleMargins { - NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type - } - } - - MouseArea { - anchors.fill: parent - onPressed: (mouse) => mouse.accepted = false - cursorShape: slider.pressed ? Qt.ClosedHandCursor : Qt.PointingHandCursor - } - - background: Item { - anchors.verticalCenter: parent.verticalCenter - implicitHeight: 16 - - // Fill left - Rectangle { - anchors.left: parent.left - width: slider.handleLimit + slider.visualPosition * slider.limitedHandleWidth - (slider.handleMargins + slider.handleWidth / 2) - height: parent.height - color: Appearance.m3colors.m3primary - topLeftRadius: Appearance.rounding.full - bottomLeftRadius: Appearance.rounding.full - topRightRadius: Appearance.rounding.unsharpen - bottomRightRadius: Appearance.rounding.unsharpen - } - - // Fill right - Rectangle { - anchors.right: parent.right - width: slider.handleLimit + (1 - slider.visualPosition) * slider.limitedHandleWidth - (slider.handleMargins + slider.handleWidth / 2) - height: parent.height - color: Appearance.m3colors.m3secondaryContainer - topLeftRadius: Appearance.rounding.unsharpen - bottomLeftRadius: Appearance.rounding.unsharpen - topRightRadius: Appearance.rounding.full - bottomRightRadius: Appearance.rounding.full - } - - // Dot at the end - Rectangle { - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - anchors.rightMargin: slider.backgroundDotMargins - width: slider.backgroundDotSize - height: slider.backgroundDotSize - radius: Appearance.rounding.full - color: Appearance.m3colors.m3onSecondaryContainer - } - } - - handle: Rectangle { - id: handle - x: slider.leftPadding + slider.handleLimit + slider.visualPosition * slider.limitedHandleWidth - y: slider.topPadding + slider.availableHeight / 2 - height / 2 - implicitWidth: slider.handleWidth - implicitHeight: slider.handleHeight - radius: Appearance.rounding.full - color: Appearance.m3colors.m3onSecondaryContainer - - Behavior on implicitWidth { - NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type - } - } - - StyledToolTip { - extraVisibleCondition: slider.pressed - content: `${Math.round(slider.value * 100)}%` - } - } } } } From 9c6aff249fe80fe04df9c4301bc120ab5ff1119e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 20 Apr 2025 18:37:14 +0200 Subject: [PATCH 067/824] volume mixer list placeholder --- .../sidebarRight/volumeMixer/VolumeMixer.qml | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml index 68b96dea3..0c24fed9f 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml @@ -15,7 +15,7 @@ Item { id: flickable anchors.fill: parent contentHeight: volumeMixerColumnLayout.height - + layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { @@ -49,4 +49,38 @@ Item { } } } + + // Placeholder when list is empty + Item { + anchors.fill: flickable + + visible: opacity > 0 + opacity: (linkTracker.linkGroups.length === 0) ? 1 : 0 + + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.menuDecel.duration + easing.type: Appearance.animation.menuDecel.type + } + } + + ColumnLayout { + anchors.centerIn: parent + spacing: 5 + + MaterialSymbol { + Layout.alignment: Qt.AlignHCenter + font.pixelSize: 55 + color: Appearance.m3colors.m3outline + text: "brand_awareness" + } + StyledText { + Layout.alignment: Qt.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3outline + horizontalAlignment: Text.AlignHCenter + text: "No audio source" + } + } + } } \ No newline at end of file From f9a8138264e5043e41ebbf8dbba2029a2b1e11f8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 20 Apr 2025 22:14:34 +0200 Subject: [PATCH 068/824] notification: make dragging not lag --- .config/quickshell/modules/common/widgets/NotificationWidget.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index d52bec6d0..e3425efc7 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -125,6 +125,7 @@ Item { onDragStartedChanged: () => { // Prevent drag focus being shifted to parent flickable root.parent.parent.parent.interactive = !dragStarted + root.enableAnimation = !dragStarted } onReleased: (mouse) => { dragStarted = false From 9164ad2471905b4380a33bd6d990b3bc2b6abcb3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 20 Apr 2025 22:15:10 +0200 Subject: [PATCH 069/824] swap usage: don't show when zero and there's music --- .config/quickshell/modules/bar/Resources.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/bar/Resources.qml b/.config/quickshell/modules/bar/Resources.qml index 8de5dcfc1..db7741b2c 100644 --- a/.config/quickshell/modules/bar/Resources.qml +++ b/.config/quickshell/modules/bar/Resources.qml @@ -29,7 +29,7 @@ Rectangle { Resource { iconName: "swap_horiz" percentage: ResourceUsage.swapUsedPercentage - shown: ConfigOptions.bar.resources.alwaysShowSwap || (MprisController.activePlayer?.trackTitle == null) + shown: (ConfigOptions.bar.resources.alwaysShowSwap && percentage > 0) || (MprisController.activePlayer?.trackTitle == null) Layout.leftMargin: shown ? 4 : 0 } From eca98598cf29acf32a532189a64ce8d098a74967 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 21 Apr 2025 00:47:34 +0200 Subject: [PATCH 070/824] mixer: add audio device selector --- .../sidebarRight/CenterWidgetGroup.qml | 2 +- .../notifications/NotificationList.qml | 3 +- .../modules/sidebarRight/todo/TodoWidget.qml | 16 +- .../volumeMixer/AudioDeviceSelectorButton.qml | 64 ++++ .../sidebarRight/volumeMixer/VolumeMixer.qml | 336 +++++++++++++++--- .../volumeMixer/VolumeMixerEntry.qml | 66 ++-- 6 files changed, 389 insertions(+), 98 deletions(-) create mode 100644 .config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml diff --git a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml index 2322a505d..7fe0fd29d 100644 --- a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml @@ -121,7 +121,7 @@ Rectangle { maskSource: Rectangle { width: swipeView.width height: swipeView.height - radius: Appearance.rounding.normal + radius: Appearance.rounding.small } } diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml index e5b6465ea..c04015b0e 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -140,7 +140,8 @@ Item { NotificationStatusButton { Layout.alignment: Qt.AlignVCenter - Layout.topMargin: 5 + Layout.margins: 5 + Layout.topMargin: 10 buttonIcon: "clear_all" buttonText: "Clear" onClicked: () => { diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index 0fbc927a2..d473eda2d 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -201,9 +201,9 @@ Item { Item { anchors.fill: parent - visible: false - z: 1000 + z: 9999 + visible: opacity > 0 opacity: root.showAddDialog ? 1 : 0 Behavior on opacity { NumberAnimation { @@ -211,9 +211,6 @@ Item { easing.type: Appearance.animation.elementDecelFast.type } } - onOpacityChanged: { - visible = opacity > 0 - } onVisibleChanged: { if (!visible) { @@ -236,9 +233,12 @@ Item { Rectangle { // The dialog id: dialog - implicitWidth: parent.width - dialogMargins * 2 + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: root.dialogMargins implicitHeight: dialogColumnLayout.implicitHeight - anchors.centerIn: parent + color: Appearance.m3colors.m3surfaceContainerHigh radius: Appearance.rounding.normal @@ -252,8 +252,8 @@ Item { } ColumnLayout { - anchors.fill: parent id: dialogColumnLayout + anchors.fill: parent spacing: 16 StyledText { diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml new file mode 100644 index 000000000..fc2dd17e7 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml @@ -0,0 +1,64 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Widgets +import Quickshell.Services.Pipewire + +Button { + id: button + required property bool input + + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.small + color: (button.down) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } + } + + PointingHandInteraction {} + + contentItem: RowLayout { + anchors.fill: parent + anchors.margins: 5 + spacing: 5 + + MaterialSymbol { + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: false + Layout.leftMargin: 5 + font.pixelSize: Appearance.font.pixelSize.hugeass + text: input ? "mic_external_on" : "media_output" + } + + ColumnLayout { + Layout.fillWidth: true + Layout.rightMargin: 5 + spacing: 0 + StyledText { + Layout.fillWidth: true + elide: Text.ElideRight + font.pixelSize: Appearance.font.pixelSize.normal + text: input ? "Input" : "Output" + color: Appearance.colors.colOnLayer2 + } + StyledText { + Layout.fillWidth: true + elide: Text.ElideRight + font.pixelSize: Appearance.font.pixelSize.smaller + text: input ? Pipewire.defaultAudioSource?.description : Pipewire.defaultAudioSink?.description + color: Appearance.m3colors.m3outline + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml index 0c24fed9f..447422e43 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml @@ -11,76 +11,296 @@ import Quickshell.Services.Pipewire Item { id: root - Flickable { - id: flickable - anchors.fill: parent - contentHeight: volumeMixerColumnLayout.height + property bool showDeviceSelector: false + property bool deviceSelectorInput + property int dialogMargins: 16 + property PwNode selectedDevice - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: flickable.width - height: flickable.height - radius: Appearance.rounding.normal + function showDeviceSelectorDialog(input) { + root.selectedDevice = null + root.showDeviceSelector = true + root.deviceSelectorInput = input + } + + Keys.onPressed: (event) => { + // Close dialog on pressing Esc if open + if (event.key === Qt.Key_Escape && root.showDeviceSelector) { + root.showDeviceSelector = false + event.accepted = true; + } + } + + ColumnLayout { + anchors.fill: parent + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Flickable { + id: flickable + anchors.fill: parent + contentHeight: volumeMixerColumnLayout.height + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: flickable.width + height: flickable.height + radius: Appearance.rounding.normal + } + } + + ColumnLayout { + id: volumeMixerColumnLayout + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 10 + spacing: 10 + + // Get a list of nodes that output to the default sink + PwNodeLinkTracker { + id: linkTracker + node: Pipewire.defaultAudioSink + } + + Repeater { + model: linkTracker.linkGroups + + VolumeMixerEntry { + Layout.fillWidth: true + // Get links to the default sinnk + required property PwLinkGroup modelData + // Consider sources that output to the default sink + node: modelData.source + } + } + } + } + + // Placeholder when list is empty + Item { + anchors.fill: flickable + + visible: opacity > 0 + opacity: (linkTracker.linkGroups.length === 0) ? 1 : 0 + + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.menuDecel.duration + easing.type: Appearance.animation.menuDecel.type + } + } + + ColumnLayout { + anchors.centerIn: parent + spacing: 5 + + MaterialSymbol { + Layout.alignment: Qt.AlignHCenter + font.pixelSize: 55 + color: Appearance.m3colors.m3outline + text: "brand_awareness" + } + StyledText { + Layout.alignment: Qt.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3outline + horizontalAlignment: Text.AlignHCenter + text: "No audio source" + } + } + } + } + // Device selector + RowLayout { + id: deviceSelectorRowLayout + Layout.fillWidth: true + Layout.fillHeight: false + AudioDeviceSelectorButton { + Layout.fillWidth: true + input: false + onClicked: root.showDeviceSelectorDialog(input) + } + AudioDeviceSelectorButton { + Layout.fillWidth: true + input: true + onClicked: root.showDeviceSelectorDialog(input) + } + } + } + + // Device selector dialog + Item { + anchors.fill: parent + z: 9999 + + visible: opacity > 0 + opacity: root.showDeviceSelector ? 1 : 0 + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type } } - ColumnLayout { - id: volumeMixerColumnLayout - anchors.top: parent.top + Rectangle { // Scrim + id: scrimOverlay + anchors.fill: parent + radius: Appearance.rounding.small + color: Appearance.colors.colScrim + MouseArea { + hoverEnabled: true + anchors.fill: parent + preventStealing: true + propagateComposedEvents: false + } + } + + Rectangle { // The dialog + id: dialog + color: Appearance.m3colors.m3surfaceContainerHigh + radius: Appearance.rounding.normal anchors.left: parent.left anchors.right: parent.right - anchors.margins: 10 - spacing: 10 + anchors.verticalCenter: parent.verticalCenter + anchors.margins: 30 + implicitHeight: dialogColumnLayout.implicitHeight + + ColumnLayout { + id: dialogColumnLayout + anchors.fill: parent + spacing: 16 - // get a list of nodes that output to the default sink - PwNodeLinkTracker { - id: linkTracker - node: Pipewire.defaultAudioSink - } + StyledText { + id: dialogTitle + Layout.topMargin: dialogMargins + Layout.leftMargin: dialogMargins + Layout.rightMargin: dialogMargins + Layout.alignment: Qt.AlignLeft + color: Appearance.m3colors.m3onSurface + font.pixelSize: Appearance.font.pixelSize.larger + text: `Select ${root.deviceSelectorInput ? "input" : "output"} device` + } - Repeater { - model: linkTracker.linkGroups + Rectangle { + color: Appearance.m3colors.m3outline + implicitHeight: 1 + Layout.fillWidth: true + Layout.leftMargin: dialogMargins + Layout.rightMargin: dialogMargins + } - VolumeMixerEntry { - required property PwLinkGroup modelData - node: modelData.source // target = default sink, source = what we need + Flickable { + id: dialogFlickable + Layout.fillWidth: true + clip: true + implicitHeight: Math.min(scrimOverlay.height - dialogMargins * 8 - dialogTitle.height - dialogButtonsRowLayout.height, devicesColumnLayout.implicitHeight) + + contentHeight: devicesColumnLayout.implicitHeight + + ColumnLayout { + id: devicesColumnLayout + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + Layout.fillWidth: true + + Repeater { + model: Pipewire.nodes.values.filter(node => { + return !node.isStream && node.isSink !== root.deviceSelectorInput && node.audio + }) + + delegate: RadioButton { + Layout.leftMargin: root.dialogMargins + Layout.rightMargin: root.dialogMargins + Layout.fillWidth: true + leftInset: 4 + rightInset: 4 + topInset: 4 + bottomInset: 4 + checked: modelData.id === Pipewire.defaultAudioSink.id + + onCheckedChanged: { + if (checked) { + root.selectedDevice = modelData + } + } + + indicator: Item{} + + contentItem: RowLayout { + Layout.fillWidth: true + spacing: 8 + Rectangle { + id: radio + Layout.fillWidth: false + Layout.alignment: Qt.AlignVCenter + width: 20 + height: 20 + radius: 10 + border.color: checked ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline + border.width: 2 + color: "transparent" + + Rectangle { + anchors.centerIn: parent + width: 10 + height: 10 + radius: 5 + color: checked ? Appearance.m3colors.m3primary : "transparent" + visible: checked + } + } + StyledText { + text: modelData.description + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + wrapMode: Text.Wrap + color: Appearance.m3colors.m3onSurface + } + } + } + } + } + } + + Rectangle { + color: Appearance.m3colors.m3outline + implicitHeight: 1 + Layout.fillWidth: true + Layout.leftMargin: dialogMargins + Layout.rightMargin: dialogMargins + } + + RowLayout { + id: dialogButtonsRowLayout + Layout.bottomMargin: dialogMargins + Layout.leftMargin: dialogMargins + Layout.rightMargin: dialogMargins + Layout.alignment: Qt.AlignRight + + DialogButton { + buttonText: "Cancel" + onClicked: { + root.showDeviceSelector = false + } + } + DialogButton { + buttonText: "OK" + onClicked: { + root.showDeviceSelector = false + if (root.selectedDevice) { + if (root.deviceSelectorInput) { + Pipewire.preferredDefaultAudioSource = root.selectedDevice + } else { + Pipewire.preferredDefaultAudioSink = root.selectedDevice + } + } + } + } } } } } - // Placeholder when list is empty - Item { - anchors.fill: flickable - - visible: opacity > 0 - opacity: (linkTracker.linkGroups.length === 0) ? 1 : 0 - - Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.menuDecel.duration - easing.type: Appearance.animation.menuDecel.type - } - } - - ColumnLayout { - anchors.centerIn: parent - spacing: 5 - - MaterialSymbol { - Layout.alignment: Qt.AlignHCenter - font.pixelSize: 55 - color: Appearance.m3colors.m3outline - text: "brand_awareness" - } - StyledText { - Layout.alignment: Qt.AlignHCenter - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.m3colors.m3outline - horizontalAlignment: Text.AlignHCenter - text: "No audio source" - } - } - } } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml index 79975135e..ec4e6f6c2 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml @@ -8,45 +8,51 @@ import QtQuick.Layouts import Quickshell.Widgets import Quickshell.Services.Pipewire - -RowLayout { +Item { id: root required property PwNode node; PwObjectTracker { objects: [ node ] } - spacing: 10 + implicitHeight: rowLayout.implicitHeight - Image { - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - visible: source != "" - sourceSize.width: 50 - sourceSize.height: 50 - source: { - const icon = node.properties["application.icon-name"] ?? "audio-volume-high-symbolic"; - return `image://icon/${icon}`; - } - } + RowLayout { + id: rowLayout + anchors.fill: parent + spacing: 10 - ColumnLayout { - Layout.fillWidth: true - RowLayout { - StyledText { - Layout.fillWidth: true - font.pixelSize: Appearance.font.pixelSize.normal - elide: Text.ElideRight - text: { - // application.name -> description -> name - const app = node.properties["application.name"] ?? (node.description != "" ? node.description : node.name); - const media = node.properties["media.name"]; - return media != undefined ? `${app} • ${media}` : app; - } + Image { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + visible: source != "" + sourceSize.width: 50 + sourceSize.height: 50 + source: { + const icon = root.node.properties["application.icon-name"] ?? "audio-volume-high-symbolic"; + return `image://icon/${icon}`; } } - RowLayout { - StyledSlider { - value: node.audio.volume - onValueChanged: node.audio.volume = value + ColumnLayout { + Layout.fillWidth: true + RowLayout { + StyledText { + Layout.fillWidth: true + font.pixelSize: Appearance.font.pixelSize.normal + elide: Text.ElideRight + text: { + // application.name -> description -> name + const app = root.node.properties["application.name"] ?? (root.node.description != "" ? root.node.description : root.node.name); + const media = root.node.properties["media.name"]; + return media != undefined ? `${app} • ${media}` : app; + } + } + } + + RowLayout { + StyledSlider { + id: slider + value: root.node.audio.volume + onValueChanged: root.node.audio.volume = value + } } } } From 073e35381ca481ae957d263a95703a2e184122d8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 21 Apr 2025 11:05:18 +0200 Subject: [PATCH 071/824] fancier radiobuttons --- .../widgets/NotificationActionButton.qml | 1 + .../common/widgets/NotificationWidget.qml | 8 +- .../common/widgets/notification_utils.js | 9 +- .../volumeMixer/AudioDeviceSelectorButton.qml | 2 +- .../sidebarRight/volumeMixer/VolumeMixer.qml | 93 +++++++++++++++---- 5 files changed, 92 insertions(+), 21 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationActionButton.qml b/.config/quickshell/modules/common/widgets/NotificationActionButton.qml index 28088d744..a907f3b31 100644 --- a/.config/quickshell/modules/common/widgets/NotificationActionButton.qml +++ b/.config/quickshell/modules/common/widgets/NotificationActionButton.qml @@ -26,6 +26,7 @@ Button { (button.down ? Appearance.colors.colSurfaceContainerHighestActive : button.hovered ? Appearance.colors.colSurfaceContainerHighestHover : Appearance.m3colors.m3surfaceContainerHighest) + } contentItem: StyledText { diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index e3425efc7..5e2763c3f 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -251,8 +251,12 @@ Item { color: Appearance.m3colors.m3secondaryContainer MaterialSymbol { visible: notificationObject.appIcon == "" - text: (notificationObject.urgency == NotificationUrgency.Critical) ? "release_alert" : - NotificationUtils.guessMessageType(notificationObject.summary) + text: { + const defaultIcon = NotificationUtils.findSuitableMaterialSymbol("") + const guessedIcon = NotificationUtils.findSuitableMaterialSymbol(notificationObject.summary) + return (notificationObject.urgency == NotificationUrgency.Critical && guessedIcon === defaultIcon) ? + "release_alert" : guessedIcon + } anchors.fill: parent color: (notificationObject.urgency == NotificationUrgency.Critical) ? Appearance.mix(Appearance.m3colors.m3onSecondary, Appearance.m3colors.m3onSecondaryContainer, 0.1) : diff --git a/.config/quickshell/modules/common/widgets/notification_utils.js b/.config/quickshell/modules/common/widgets/notification_utils.js index e7b82cc8e..af97229c1 100644 --- a/.config/quickshell/modules/common/widgets/notification_utils.js +++ b/.config/quickshell/modules/common/widgets/notification_utils.js @@ -1,4 +1,9 @@ -function guessMessageType(summary) { + + +function findSuitableMaterialSymbol(summary = "") { + const defaultType = 'chat'; + if(summary.length === 0) return defaultType; + const keywordsToTypes = { 'reboot': 'restart_alt', 'recording': 'screen_record', @@ -26,7 +31,7 @@ function guessMessageType(summary) { } } - return 'chat'; + return defaultType; } // const getFriendlyNotifTimeString = (timeObject) => { diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml index fc2dd17e7..4b28980f8 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml @@ -56,7 +56,7 @@ Button { Layout.fillWidth: true elide: Text.ElideRight font.pixelSize: Appearance.font.pixelSize.smaller - text: input ? Pipewire.defaultAudioSource?.description : Pipewire.defaultAudioSink?.description + text: (input ? Pipewire.defaultAudioSource?.description : Pipewire.defaultAudioSink?.description) ?? "Unknown" color: Appearance.m3colors.m3outline } } diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml index 447422e43..5656cc908 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml @@ -200,25 +200,33 @@ Item { ColumnLayout { id: devicesColumnLayout - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + anchors.fill: parent Layout.fillWidth: true + spacing: 0 Repeater { model: Pipewire.nodes.values.filter(node => { return !node.isStream && node.isSink !== root.deviceSelectorInput && node.audio }) + // This could and should be refractored, but all data becomes null when passed wtf delegate: RadioButton { + id: radioButton Layout.leftMargin: root.dialogMargins Layout.rightMargin: root.dialogMargins Layout.fillWidth: true - leftInset: 4 - rightInset: 4 - topInset: 4 - bottomInset: 4 - checked: modelData.id === Pipewire.defaultAudioSink.id + implicitHeight: 40 + checked: modelData.id === Pipewire.defaultAudioSink?.id + + Connections { + target: root + function onShowDeviceSelectorChanged() { + if(!root.showDeviceSelector) return; + radioButton.checked = (modelData.id === Pipewire.defaultAudioSink?.id) + } + } + + PointingHandInteraction {} onCheckedChanged: { if (checked) { @@ -230,25 +238,75 @@ Item { contentItem: RowLayout { Layout.fillWidth: true - spacing: 8 + spacing: 12 Rectangle { id: radio Layout.fillWidth: false Layout.alignment: Qt.AlignVCenter width: 20 height: 20 - radius: 10 - border.color: checked ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline + radius: Appearance.rounding.full + border.color: checked ? Appearance.m3colors.m3primary : Appearance.m3colors.m3onSurfaceVariant border.width: 2 color: "transparent" + // Checked indicator Rectangle { anchors.centerIn: parent - width: 10 - height: 10 - radius: 5 - color: checked ? Appearance.m3colors.m3primary : "transparent" - visible: checked + width: checked ? 10 : 4 + height: checked ? 10 : 4 + radius: Appearance.rounding.full + color: Appearance.m3colors.m3primary + opacity: checked ? 1 : 0 + + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } + Behavior on width { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } + Behavior on height { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } + + } + + // Hover + Rectangle { + anchors.centerIn: parent + width: radioButton.hovered ? 40 : 20 + height: radioButton.hovered ? 40 : 20 + radius: Appearance.rounding.full + color: Appearance.m3colors.m3onSurface + opacity: radioButton.hovered ? 0.1 : 0 + + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } + Behavior on width { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } + Behavior on height { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } } } StyledText { @@ -261,6 +319,9 @@ Item { } } } + Item { + implicitHeight: dialogMargins + } } } From 967875115679cb5cbc7dcec252d53c41c56b0634 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 21 Apr 2025 15:29:21 +0200 Subject: [PATCH 072/824] circular progress more like m3 --- .../modules/common/widgets/CircularProgress.qml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/CircularProgress.qml b/.config/quickshell/modules/common/widgets/CircularProgress.qml index 73ab50f4b..2c6b1b9a0 100644 --- a/.config/quickshell/modules/common/widgets/CircularProgress.qml +++ b/.config/quickshell/modules/common/widgets/CircularProgress.qml @@ -1,5 +1,6 @@ // From https://github.com/rafzby/circular-progressbar // License: LGPL-3.0 - A copy can be found in `licenses` folder of repo +// Modified import QtQuick 2.9 Item { @@ -10,6 +11,7 @@ Item { property real value: 0 property color primaryColor: "#70585D" property color secondaryColor: "#FFF8F7" + property real gapAngle: Math.PI / 10 property bool fill: false property int fillOverflow: 2 property int animationDuration: 1000 @@ -46,6 +48,8 @@ Item { var startAngle = (Math.PI / 180) * 270; var fullAngle = (Math.PI / 180) * (270 + 360); var progressAngle = (Math.PI / 180) * (270 + degree); + var epsilon = 0.01; // Small angle in radians + ctx.reset(); if (root.fill) { ctx.fillStyle = root.secondaryColor; @@ -55,12 +59,17 @@ Item { } ctx.lineCap = 'round'; ctx.lineWidth = root.lineWidth; + + // Secondary ctx.beginPath(); - ctx.arc(x, y, radius, startAngle, fullAngle); + ctx.arc(x, y, radius, progressAngle + gapAngle, startAngle - gapAngle); ctx.strokeStyle = root.secondaryColor; ctx.stroke(); + + // Primary (value indication) + var endAngle = (progressAngle === startAngle) ? startAngle + epsilon : progressAngle; ctx.beginPath(); - ctx.arc(x, y, radius, startAngle, progressAngle); + ctx.arc(x, y, radius, startAngle, endAngle); ctx.strokeStyle = root.primaryColor; ctx.stroke(); } From 0faf9287ba4a9f0846b71656bd7996b22645f100 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 21 Apr 2025 17:51:28 +0200 Subject: [PATCH 073/824] osd --- .config/quickshell/modules/bar/Bar.qml | 84 ++++++++++--- .../quickshell/modules/common/Appearance.qml | 1 + .../modules/common/ConfigOptions.qml | 6 +- .../common/widgets/CircularProgress.qml | 2 +- .../common/widgets/StyledProgressBar.qml | 57 +++++++++ .../onScreenDisplay/OnScreenDisplay.qml | 118 ++++++++++++++++++ .../onScreenDisplay/OsdValueIndicator.qml | 86 +++++++++++++ .../modules/onScreenDisplay/OsdValues.qml | 24 ++++ .../modules/screenCorners/ScreenCorners.qml | 1 - .../modules/sidebarRight/SidebarRight.qml | 2 +- .config/quickshell/services/Audio.qml | 1 + .config/quickshell/services/Brightness.qml | 36 ++++-- .config/quickshell/shell.qml | 2 + 13 files changed, 392 insertions(+), 28 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/StyledProgressBar.qml create mode 100644 .config/quickshell/modules/onScreenDisplay/OnScreenDisplay.qml create mode 100644 .config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml create mode 100644 .config/quickshell/modules/onScreenDisplay/OsdValues.qml diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index cbaaad833..6a5971459 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -12,6 +12,7 @@ Scope { readonly property int barHeight: Appearance.sizes.barHeight readonly property int barCenterSideModuleWidth: Appearance.sizes.barCenterSideModuleWidth + readonly property int osdHideMouseMoveThreshold: 20 Process { id: openSidebarRight @@ -21,6 +22,10 @@ Scope { id: openSidebarLeft command: ["qs", "ipc", "call", "sidebarLeft", "open"] } + Process { + id: hideOsd + command: ["qs", "ipc", "call", "osd", "hide"] + } Variants { model: Quickshell.screens @@ -60,18 +65,6 @@ Scope { ActiveWindow { bar: barRoot } - - // Scroll to change brightness - WheelHandler { - onWheel: (event) => { - if (event.angleDelta.y < 0) - Brightness.value = -1; - else if (event.angleDelta.y > 0) - Brightness.value = 1; - } - acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad - } - } // Middle section @@ -179,9 +172,57 @@ Scope { } - MouseArea { + + // Interactions + MouseArea { // Left side: scroll to change brightness + id: barLeftSideMouseArea + property bool hovered: false + property real lastScrollX: 0 + property real lastScrollY: 0 + property bool trackingScroll: false + anchors.fill: leftSection + acceptedButtons: Qt.LeftButton + hoverEnabled: true + propagateComposedEvents: true + onEntered: (event) => { + barLeftSideMouseArea.hovered = true + } + onExited: (event) => { + barLeftSideMouseArea.hovered = false + barLeftSideMouseArea.trackingScroll = false + } + // Scroll to change brightness + WheelHandler { + onWheel: (event) => { + if (event.angleDelta.y < 0) + Brightness.increment = -1; + else if (event.angleDelta.y > 0) + Brightness.increment = 1; + // Store the mouse position and start tracking + barLeftSideMouseArea.lastScrollX = event.x; + barLeftSideMouseArea.lastScrollY = event.y; + barLeftSideMouseArea.trackingScroll = true; + } + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } + onPositionChanged: (mouse) => { + if (barLeftSideMouseArea.trackingScroll) { + const dx = mouse.x - barLeftSideMouseArea.lastScrollX; + const dy = mouse.y - barLeftSideMouseArea.lastScrollY; + if (Math.sqrt(dx*dx + dy*dy) > osdHideMouseMoveThreshold) { + hideOsd.running = true; + barLeftSideMouseArea.trackingScroll = false; + } + } + } + } + + MouseArea { // Right side: scroll to change volume id: barRightSideMouseArea property bool hovered: false + property real lastScrollX: 0 + property real lastScrollY: 0 + property bool trackingScroll: false anchors.fill: rightSection acceptedButtons: Qt.LeftButton hoverEnabled: true @@ -191,6 +232,7 @@ Scope { } onExited: (event) => { barRightSideMouseArea.hovered = false + barRightSideMouseArea.trackingScroll = false } onPressed: (event) => { if (event.button === Qt.LeftButton) { @@ -200,15 +242,29 @@ Scope { // Scroll to change volume WheelHandler { onWheel: (event) => { - const currentVolume = Audio.sink?.audio.volume; + const currentVolume = Audio.value; const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2; if (event.angleDelta.y < 0) Audio.sink.audio.volume -= step; else if (event.angleDelta.y > 0) Audio.sink.audio.volume += step; + // Store the mouse position and start tracking + barRightSideMouseArea.lastScrollX = event.x; + barRightSideMouseArea.lastScrollY = event.y; + barRightSideMouseArea.trackingScroll = true; } acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad } + onPositionChanged: (mouse) => { + if (barRightSideMouseArea.trackingScroll) { + const dx = mouse.x - barRightSideMouseArea.lastScrollX; + const dy = mouse.y - barRightSideMouseArea.lastScrollY; + if (Math.sqrt(dx*dx + dy*dy) > osdHideMouseMoveThreshold) { + hideOsd.running = true; + barRightSideMouseArea.trackingScroll = false; + } + } + } } } diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 70eb0e726..0feb39f7e 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -134,6 +134,7 @@ Singleton { property color colTooltip: m3colors.m3inverseSurface property color colOnTooltip: m3colors.m3inverseOnSurface property color colScrim: transparentize(m3colors.m3scrim, 0.5) + property color colShadow: transparentize(m3colors.m3shadow, 0.75) } rounding: QtObject { diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 57b821749..d30be6078 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -26,12 +26,16 @@ Singleton { } } + property QtObject osd: QtObject { + property int timeout: 1000 + } + property QtObject resources: QtObject { property int updateInterval: 3000 } property QtObject hacks: QtObject { - property int arbitraryRaceConditionDelay: 10 + property int arbitraryRaceConditionDelay: 10 // milliseconds } } diff --git a/.config/quickshell/modules/common/widgets/CircularProgress.qml b/.config/quickshell/modules/common/widgets/CircularProgress.qml index 2c6b1b9a0..8fc6a83b5 100644 --- a/.config/quickshell/modules/common/widgets/CircularProgress.qml +++ b/.config/quickshell/modules/common/widgets/CircularProgress.qml @@ -1,6 +1,6 @@ // From https://github.com/rafzby/circular-progressbar // License: LGPL-3.0 - A copy can be found in `licenses` folder of repo -// Modified +// Modified so it looks like in Material 3: https://m3.material.io/components/progress-indicators/specs import QtQuick 2.9 Item { diff --git a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml new file mode 100644 index 000000000..078570ffe --- /dev/null +++ b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml @@ -0,0 +1,57 @@ +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import Qt5Compat.GraphicalEffects + +ProgressBar { + id: root + property real valueBarWidth: 120 + property real valueBarHeight: 4 + property real valueBarGap: 4 + + Behavior on value { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + + background: Rectangle { + anchors.fill: parent + color: "transparent" + radius: Appearance.rounding.full + implicitHeight: valueBarHeight + implicitWidth: valueBarWidth + } + + contentItem: Item { + implicitWidth: parent.width + implicitHeight: parent.height + + Rectangle { // Left progress fill + width: root.visualPosition * parent.width + height: parent.height + radius: Appearance.rounding.full + color: Appearance.m3colors.m3primary + } + Rectangle { // Right remaining part fill + anchors.right: parent.right + width: (1 - root.visualPosition) * parent.width - valueBarGap + height: parent.height + radius: Appearance.rounding.full + color: Appearance.m3colors.m3secondaryContainer + } + Rectangle { // Stop point + anchors.right: parent.right + width: valueBarGap + height: valueBarGap + radius: Appearance.rounding.full + color: Appearance.m3colors.m3primary + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplay.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplay.qml new file mode 100644 index 000000000..f5fe97d35 --- /dev/null +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplay.qml @@ -0,0 +1,118 @@ +import "root:/services/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland + +Scope { + id: root + property bool showOsdValues: false + + function triggerOsd() { + showOsdValues = true + osdTimeout.restart() + } + + Timer { + id: osdTimeout + interval: ConfigOptions.osd.timeout + repeat: false + running: false + onTriggered: { + showOsdValues = false + } + } + + Connections { + target: Brightness + function onValueChanged() { + if (!Brightness.ready) return + root.triggerOsd() + } + } + + Connections { + target: Audio.sink.audio + function onVolumeChanged() { + if (!Audio.ready) return + root.triggerOsd() + } + } + + Variants { + model: Quickshell.screens + + PanelWindow { + property var modelData + + screen: modelData + exclusionMode: ExclusionMode.Normal + WlrLayershell.namespace: "quickshell:onScreenDisplay" + WlrLayershell.layer: WlrLayer.Overlay + color: "transparent" + + anchors { + top: true + left: true + right: true + } + mask: Region { + item: columnLayout + } + + width: columnLayout.implicitWidth + height: columnLayout.implicitHeight + visible: showOsdValues + + ColumnLayout { + id: columnLayout + anchors.horizontalCenter: parent.horizontalCenter + Item { + height: 1 // Prevent Wayland protocol error + } + Item { + implicitHeight: true ? osdValues.implicitHeight : 0 + implicitWidth: osdValues.implicitWidth + clip: true + + Behavior on implicitHeight { + NumberAnimation { + duration: Appearance.animation.menuDecel.duration + easing.type: Appearance.animation.menuDecel.type + } + } + + OsdValues { + id: osdValues + anchors.bottom: parent.bottom + // height: showOsdValues ? implicitHeight : 0 + // implicitHeight: 0 + } + } + } + + } + + } + + IpcHandler { + target: "osd" + + function trigger() { + root.triggerOsd() + } + + function hide() { + showOsdValues = false + } + + function toggle() { + showOsdValues = !showOsdValues + } + } + +} diff --git a/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml b/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml new file mode 100644 index 000000000..5437c09ee --- /dev/null +++ b/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml @@ -0,0 +1,86 @@ +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import Qt5Compat.GraphicalEffects + +Item { + id: root + required property real value + required property string icon + required property string name + + property real valueIndicatorVerticalPadding: 5 + property real valueIndicatorLeftPadding: 10 + property real valueIndicatorRightPadding: 20 // An icon is circle ish, a column isn't, hence the extra padding + + Layout.margins: Appearance.sizes.elevationMargin + implicitWidth: valueIndicator.implicitWidth + implicitHeight: valueIndicator.implicitHeight + + WrapperRectangle { + id: valueIndicator + radius: Appearance.rounding.full + color: Appearance.colors.colLayer0 + implicitWidth: valueRow.implicitWidth + + RowLayout { // Icon on the left, stuff on the right + id: valueRow + spacing: 5 + Layout.margins: 10 + + MaterialSymbol { // Icon + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: valueIndicatorLeftPadding + Layout.topMargin: valueIndicatorVerticalPadding + Layout.bottomMargin: valueIndicatorVerticalPadding + text: root.icon + font.pixelSize: 30 + } + ColumnLayout { // Stuff + Layout.alignment: Qt.AlignVCenter + Layout.rightMargin: valueIndicatorRightPadding + spacing: 5 + + RowLayout { // Name fill left, value on the right end + Layout.leftMargin: valueBarHeight / 2 // Align text with progressbar radius curve's left end + Layout.rightMargin: valueBarHeight / 2 // Align text with progressbar radius curve's left end + + StyledText { + color: Appearance.colors.colOnLayer0 + font.pixelSize: Appearance.font.pixelSize.small + Layout.fillWidth: true + text: root.name + } + + StyledText { + color: Appearance.colors.colOnLayer0 + font.pixelSize: Appearance.font.pixelSize.small + Layout.fillWidth: false + text: Math.round(root.value * 100) + } + } + + StyledProgressBar { + id: valueProgressBar + value: root.value + } + } + } + } + + DropShadow { + id: valueShadow + anchors.fill: valueIndicator + source: valueIndicator + radius: Appearance.sizes.elevationMargin + samples: radius * 2 + 1 + color: Appearance.colors.colShadow + verticalOffset: 2 + horizontalOffset: 0 + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/onScreenDisplay/OsdValues.qml b/.config/quickshell/modules/onScreenDisplay/OsdValues.qml new file mode 100644 index 000000000..ccb23e386 --- /dev/null +++ b/.config/quickshell/modules/onScreenDisplay/OsdValues.qml @@ -0,0 +1,24 @@ +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import Qt5Compat.GraphicalEffects + +RowLayout { + spacing: -5 + + OsdValueIndicator { + value: Brightness.value + icon: "light_mode" + name: "Brightness" + } + OsdValueIndicator { + value: Audio.sink.audio.volume + icon: "volume_up" + name: "Volume" + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/screenCorners/ScreenCorners.qml b/.config/quickshell/modules/screenCorners/ScreenCorners.qml index f7075305f..32708dc60 100644 --- a/.config/quickshell/modules/screenCorners/ScreenCorners.qml +++ b/.config/quickshell/modules/screenCorners/ScreenCorners.qml @@ -14,7 +14,6 @@ Scope { model: Quickshell.screens PanelWindow { - id: barRoot visible: (ConfigOptions.appearance.fakeScreenRounding === 1 || (ConfigOptions.appearance.fakeScreenRounding === 2 && !activeWindow?.fullscreen)) property var modelData diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 572015fb0..b2e9404b1 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -162,7 +162,7 @@ Scope { verticalOffset: 2 radius: Appearance.sizes.elevationMargin samples: Appearance.sizes.elevationMargin * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs - color: Appearance.transparentize(Appearance.m3colors.m3shadow, 0.55) + color: Appearance.colors.colShadow source: sidebarRightBackground } diff --git a/.config/quickshell/services/Audio.qml b/.config/quickshell/services/Audio.qml index 4bfe0e881..80c59f164 100644 --- a/.config/quickshell/services/Audio.qml +++ b/.config/quickshell/services/Audio.qml @@ -7,6 +7,7 @@ pragma ComponentBehavior: Bound Singleton { id: root + property bool ready: Pipewire.defaultAudioSink.ready property var sink: Pipewire.defaultAudioSink property var source: Pipewire.defaultAudioSource diff --git a/.config/quickshell/services/Brightness.qml b/.config/quickshell/services/Brightness.qml index a11c1fa4a..e8d697a1f 100644 --- a/.config/quickshell/services/Brightness.qml +++ b/.config/quickshell/services/Brightness.qml @@ -6,22 +6,22 @@ pragma ComponentBehavior: Bound Singleton { id: root - property string brightness - property int value: 0 + property bool ready: false + property real value + property int increment: 0 function refresh() { getBrightness.running = true; } - onValueChanged: () => { - if (value > 0) { + onIncrementChanged: () => { + if (increment > 0) { increaseBrightness.running = true; - root.value = 0; - } else if (value < 0) { + root.increment = 0; + } else if (increment < 0) { decreaseBrightness.running = true; - root.value = 0; + root.increment = 0; } - getBrightness.running = true; } Process { @@ -30,12 +30,15 @@ Singleton { command: ["sh", "-c", "brightnessctl -m i | cut -d, -f4"] running: true onExited: { - running = false; + if (!ready) ready = true } stdout: SplitParser { onRead: (data) => { - root.brightness = data; + root.value = parseFloat(data.replace("%", "")) / 100; + if (root.value < 0.01) { + preventPitchBlack.running = true; + } } } @@ -48,6 +51,7 @@ Singleton { running: false onExited: { running = false; + getBrightness.running = true; } } @@ -58,6 +62,18 @@ Singleton { running: false onExited: { running = false; + getBrightness.running = true; + } + } + + Process { + id: preventPitchBlack + + command: ["brightnessctl", "set", "1%+"] + running: false + onExited: { + running = false; + getBrightness.running = true; } } diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index cda323564..ab43b8243 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -1,6 +1,7 @@ //@ pragma UseQApplication import "./modules/bar/" +import "./modules/onScreenDisplay/" import "./modules/screenCorners/" import "./modules/sidebarRight/" import QtQuick @@ -14,5 +15,6 @@ ShellRoot { SidebarRight {} ScreenCorners {} ReloadPopup {} + OnScreenDisplay {} } From 0728557b048e15453caf04102e346b44a3131837 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 21 Apr 2025 18:15:01 +0200 Subject: [PATCH 074/824] osd: separate, make keybinds prefer qs over ags --- .config/hypr/hyprland/keybinds.conf | 14 +- .config/quickshell/modules/bar/Bar.qml | 25 ++-- .../OnScreenDisplayBrightness.qml | 127 ++++++++++++++++++ ...nDisplay.qml => OnScreenDisplayVolume.qml} | 39 +++--- .../modules/onScreenDisplay/OsdValues.qml | 24 ---- .config/quickshell/services/Brightness.qml | 12 ++ .config/quickshell/shell.qml | 3 +- 7 files changed, 185 insertions(+), 59 deletions(-) create mode 100644 .config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml rename .config/quickshell/modules/onScreenDisplay/{OnScreenDisplay.qml => OnScreenDisplayVolume.qml} (74%) delete mode 100644 .config/quickshell/modules/onScreenDisplay/OsdValues.qml diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 27fa950ff..8aac511b2 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -5,8 +5,8 @@ bindl = Alt ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # [hidd bindl = Super ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # [hidden] bindl = ,XF86AudioMute, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 0% # [hidden] bindl = Super+Shift,M, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 0% # [hidden] -bindle=, XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+ # [hidden] -bindle=, XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- # [hidden] +bindle=, XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 2%+ # [hidden] +bindle=, XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%- # [hidden] # Uncomment these if you can't get AGS to work #bindle=, XF86MonBrightnessUp, exec, brightnessctl set '12.75+' @@ -172,14 +172,12 @@ bind = Super, Comma, exec, agsv1 run-js 'openColorScheme.value = true; Utils.tim bind = Super, K, exec, for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "osk""$i"; done # Toggle on-screen keyboard bind = Ctrl+Alt, Delete, exec, for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "session""$i"; done # Toggle power menu bind = Ctrl+Super, G, exec, for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "crosshair""$i"; done # Toggle crosshair -bindle=, XF86MonBrightnessUp, exec, agsv1 run-js 'brightness.screen_value += 0.05; indicator.popup(1);' # [hidden] -bindle=, XF86MonBrightnessDown, exec, agsv1 run-js 'brightness.screen_value -= 0.05; indicator.popup(1);' # [hidden] -bindl = , XF86AudioMute, exec, agsv1 run-js 'indicator.popup(1);' # [hidden] -bindl = Super+Shift,M, exec, agsv1 run-js 'indicator.popup(1);' # [hidden] +bindle=, XF86MonBrightnessUp, exec, qs ipc call brightness increment || agsv1 run-js 'brightness.screen_value += 0.05; indicator.popup(1);' # [hidden] +bindle=, XF86MonBrightnessDown, exec, qs ipc call brightness decrement || agsv1 run-js 'brightness.screen_value -= 0.05; indicator.popup(1);' # [hidden] # Testing -bind = Super+Alt, f11, exec, bash -c 'RANDOM_IMAGE=$(find ~/Pictures -type f | grep -v -i "nipple" | grep -v -i "pussy" | shuf -n 1); ACTION=$(notify-send "Fancy test notification" "This notification should contain your profile image and Discord icon. Oh and here is a random image in your Pictures folder: \"Testing" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Open the random image" -A "action3=Useless button"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] -bind = Super+Alt, f12, exec, bash -c 'notify-send "Test notification" "A simple rich text test notification" -t 3000' # [hidden] +bind = Super+Alt, f11, exec, bash -c 'RANDOM_IMAGE=$(find ~/Pictures -type f | grep -v -i "nipple" | grep -v -i "pussy" | shuf -n 1); ACTION=$(notify-send "Test notification with body image" "This notification should contain your user account image and Discord icon. Oh and here is a random image in your Pictures folder: \"Testing" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Open the random image" -A "action3=Useless button"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] +bind = Super+Alt, f12, exec, bash -c 'ACTION=$(notify-send "Test notification" "This notification should contain your user account image and Discord icon." -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Useless button" -A "action3=Cry more"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] bind = Super+Alt, Equal, exec, notify-send "Urgent notification" "Ah hell no" -u critical -a 'Hyprland keybind' # [hidden] ##! Media diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 6a5971459..d6dc54595 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -23,14 +23,18 @@ Scope { command: ["qs", "ipc", "call", "sidebarLeft", "open"] } Process { - id: hideOsd - command: ["qs", "ipc", "call", "osd", "hide"] + id: hideOsdBrightness + command: ["qs", "ipc", "call", "osdBrightness", "hide"] + } + Process { + id: hideOsdVolume + command: ["qs", "ipc", "call", "osdVolume", "hide"] } - Variants { + Variants { // For each monitor model: Quickshell.screens - PanelWindow { + PanelWindow { // Bar window id: barRoot property var modelData @@ -49,15 +53,15 @@ Scope { right: true } - Rectangle { + Rectangle { // Bar background id: barContent anchors.right: parent.right anchors.left: parent.left anchors.top: parent.top color: Appearance.colors.colLayer0 height: barHeight - // Left section - RowLayout { + + RowLayout { // Left section id: leftSection anchors.left: parent.left implicitHeight: barHeight @@ -67,8 +71,7 @@ Scope { } } - // Middle section - RowLayout { + RowLayout { // Middle section id: middleSection anchors.centerIn: parent spacing: 8 @@ -210,7 +213,7 @@ Scope { const dx = mouse.x - barLeftSideMouseArea.lastScrollX; const dy = mouse.y - barLeftSideMouseArea.lastScrollY; if (Math.sqrt(dx*dx + dy*dy) > osdHideMouseMoveThreshold) { - hideOsd.running = true; + hideOsdBrightness.running = true; barLeftSideMouseArea.trackingScroll = false; } } @@ -260,7 +263,7 @@ Scope { const dx = mouse.x - barRightSideMouseArea.lastScrollX; const dy = mouse.y - barRightSideMouseArea.lastScrollY; if (Math.sqrt(dx*dx + dy*dy) > osdHideMouseMoveThreshold) { - hideOsd.running = true; + hideOsdVolume.running = true; barRightSideMouseArea.trackingScroll = false; } } diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml new file mode 100644 index 000000000..961ea6d4c --- /dev/null +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml @@ -0,0 +1,127 @@ +import "root:/services/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland + +Scope { + id: root + property bool showOsdValues: false + + function triggerOsd() { + showOsdValues = true + osdTimeout.restart() + } + + Timer { + id: osdTimeout + interval: ConfigOptions.osd.timeout + repeat: false + running: false + onTriggered: { + showOsdValues = false + } + } + + Connections { + target: Brightness + function onValueChanged() { + if (!Brightness.ready) return + root.triggerOsd() + } + } + + Connections { + target: Audio.sink.audio + function onVolumeChanged() { + if (!Audio.ready) return + root.showOsdValues = false + } + } + + Variants { + model: Quickshell.screens + + PanelWindow { + property var modelData + + screen: modelData + exclusionMode: ExclusionMode.Normal + WlrLayershell.namespace: "quickshell:onScreenDisplay" + WlrLayershell.layer: WlrLayer.Overlay + color: "transparent" + + anchors { + top: true + left: true + right: true + } + mask: Region { + item: osdValuesWrapper + } + + width: columnLayout.implicitWidth + height: columnLayout.implicitHeight + visible: showOsdValues + + ColumnLayout { + id: columnLayout + anchors.horizontalCenter: parent.horizontalCenter + Item { + height: 1 // Prevent Wayland protocol error + } + Item { + id: osdValuesWrapper + // Extra space for shadow + implicitHeight: true ? (osdValues.implicitHeight + Appearance.sizes.elevationMargin * 2) : 0 + implicitWidth: osdValues.implicitWidth + Appearance.sizes.elevationMargin * 2 + clip: true + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: root.showOsdValues = false + } + + Behavior on implicitHeight { + NumberAnimation { + duration: Appearance.animation.menuDecel.duration + easing.type: Appearance.animation.menuDecel.type + } + } + + OsdValueIndicator { + id: osdValues + anchors.centerIn: parent + value: Brightness.value + icon: "light_mode" + name: "Brightness" + } + } + } + + } + + } + + IpcHandler { + target: "osdBrightness" + + function trigger() { + root.triggerOsd() + } + + function hide() { + showOsdValues = false + } + + function toggle() { + showOsdValues = !showOsdValues + } + } + +} diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplay.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml similarity index 74% rename from .config/quickshell/modules/onScreenDisplay/OnScreenDisplay.qml rename to .config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml index f5fe97d35..c430e935c 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplay.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml @@ -28,18 +28,18 @@ Scope { } Connections { - target: Brightness - function onValueChanged() { - if (!Brightness.ready) return + target: Audio.sink.audio + function onVolumeChanged() { + if (!Audio.ready) return root.triggerOsd() } } Connections { - target: Audio.sink.audio - function onVolumeChanged() { - if (!Audio.ready) return - root.triggerOsd() + target: Brightness + function onValueChanged() { + if (!Brightness.ready) return + root.showOsdValues = false } } @@ -61,7 +61,7 @@ Scope { right: true } mask: Region { - item: columnLayout + item: osdValuesWrapper } width: columnLayout.implicitWidth @@ -75,10 +75,18 @@ Scope { height: 1 // Prevent Wayland protocol error } Item { - implicitHeight: true ? osdValues.implicitHeight : 0 - implicitWidth: osdValues.implicitWidth + id: osdValuesWrapper + // Extra space for shadow + implicitHeight: true ? (osdValues.implicitHeight + Appearance.sizes.elevationMargin * 2) : 0 + implicitWidth: osdValues.implicitWidth + Appearance.sizes.elevationMargin * 2 clip: true + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: root.showOsdValues = false + } + Behavior on implicitHeight { NumberAnimation { duration: Appearance.animation.menuDecel.duration @@ -86,11 +94,12 @@ Scope { } } - OsdValues { + OsdValueIndicator { id: osdValues - anchors.bottom: parent.bottom - // height: showOsdValues ? implicitHeight : 0 - // implicitHeight: 0 + anchors.centerIn: parent + value: Audio.sink.audio.volume + icon: "volume_up" + name: "Volume" } } } @@ -100,7 +109,7 @@ Scope { } IpcHandler { - target: "osd" + target: "osdVolume" function trigger() { root.triggerOsd() diff --git a/.config/quickshell/modules/onScreenDisplay/OsdValues.qml b/.config/quickshell/modules/onScreenDisplay/OsdValues.qml deleted file mode 100644 index ccb23e386..000000000 --- a/.config/quickshell/modules/onScreenDisplay/OsdValues.qml +++ /dev/null @@ -1,24 +0,0 @@ -import "root:/services" -import "root:/modules/common" -import "root:/modules/common/widgets" -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Quickshell -import Quickshell.Widgets -import Qt5Compat.GraphicalEffects - -RowLayout { - spacing: -5 - - OsdValueIndicator { - value: Brightness.value - icon: "light_mode" - name: "Brightness" - } - OsdValueIndicator { - value: Audio.sink.audio.volume - icon: "volume_up" - name: "Volume" - } -} \ No newline at end of file diff --git a/.config/quickshell/services/Brightness.qml b/.config/quickshell/services/Brightness.qml index e8d697a1f..742b5c5a2 100644 --- a/.config/quickshell/services/Brightness.qml +++ b/.config/quickshell/services/Brightness.qml @@ -77,4 +77,16 @@ Singleton { } } + IpcHandler { + target: "brightness" + + function increment() { + root.increment = 1 + } + + function decrement() { + root.increment = -1 + } + } + } diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index ab43b8243..9753bfbb3 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -15,6 +15,7 @@ ShellRoot { SidebarRight {} ScreenCorners {} ReloadPopup {} - OnScreenDisplay {} + OnScreenDisplayBrightness {} + OnScreenDisplayVolume {} } From dfc0a53a5fc5d7b102859ca1418cf0aea4240656 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 21 Apr 2025 18:17:49 +0200 Subject: [PATCH 075/824] update hyprland layer rule for osd --- .config/hypr/hyprland/rules.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index 712ae8df5..ae754008e 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -108,10 +108,10 @@ layerrule = ignorealpha 0.6, osk[0-9]* layerrule = animation fade, quickshell:screenCorners layerrule = animation slide right, quickshell:sidebarRight layerrule = animation slide left, quickshell:sidebarLeft +layerrule = animation slide top, quickshell:onScreenDisplay ## outfoxxed's stuff layerrule = blur, shell:bar layerrule = ignorezero, shell:bar layerrule = blur, shell:notifications layerrule = ignorealpha 0.1, shell:notifications - From 99b9de9d5c8ce23bbb3ab01e4a28de44e109d6ef Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 21 Apr 2025 21:14:01 +0200 Subject: [PATCH 076/824] session menu --- .../widgets => bar}/SmallCircleButton.qml | 1 + .../quickshell/modules/common/Appearance.qml | 3 +- .../onScreenDisplay/OsdValueIndicator.qml | 6 +- .../quickshell/modules/session/Session.qml | 290 ++++++++++++++++++ .../modules/session/SessionActionButton.qml | 65 ++++ .../modules/sidebarRight/SidebarRight.qml | 18 +- .config/quickshell/shell.qml | 8 +- 7 files changed, 381 insertions(+), 10 deletions(-) rename .config/quickshell/modules/{common/widgets => bar}/SmallCircleButton.qml (96%) create mode 100644 .config/quickshell/modules/session/Session.qml create mode 100644 .config/quickshell/modules/session/SessionActionButton.qml diff --git a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml b/.config/quickshell/modules/bar/SmallCircleButton.qml similarity index 96% rename from .config/quickshell/modules/common/widgets/SmallCircleButton.qml rename to .config/quickshell/modules/bar/SmallCircleButton.qml index 091e37200..97cb93079 100644 --- a/.config/quickshell/modules/common/widgets/SmallCircleButton.qml +++ b/.config/quickshell/modules/bar/SmallCircleButton.qml @@ -1,4 +1,5 @@ import "root:/modules/common" +import "root:/modules/common/widgets/" import QtQuick import QtQuick.Controls import QtQuick.Layouts diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 0feb39f7e..048fbbf25 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -164,6 +164,7 @@ Singleton { property int larger: 19 property int huge: 22 property int hugeass: 23 + property int title: 28 } } @@ -176,7 +177,7 @@ Singleton { property QtObject elementDecelFast: QtObject { property int duration: 140 property int type: Easing.OutCirc - property int velocity: 750 + property int velocity: 850 } property QtObject menuDecel: QtObject { property int duration: 350 diff --git a/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml b/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml index 5437c09ee..d24074879 100644 --- a/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml +++ b/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml @@ -30,8 +30,8 @@ Item { RowLayout { // Icon on the left, stuff on the right id: valueRow - spacing: 5 Layout.margins: 10 + spacing: 10 MaterialSymbol { // Icon Layout.alignment: Qt.AlignVCenter @@ -47,8 +47,8 @@ Item { spacing: 5 RowLayout { // Name fill left, value on the right end - Layout.leftMargin: valueBarHeight / 2 // Align text with progressbar radius curve's left end - Layout.rightMargin: valueBarHeight / 2 // Align text with progressbar radius curve's left end + Layout.leftMargin: valueProgressBar.height / 2 // Align text with progressbar radius curve's left end + Layout.rightMargin: valueProgressBar.height / 2 // Align text with progressbar radius curve's left end StyledText { color: Appearance.colors.colOnLayer0 diff --git a/.config/quickshell/modules/session/Session.qml b/.config/quickshell/modules/session/Session.qml new file mode 100644 index 000000000..620ee8353 --- /dev/null +++ b/.config/quickshell/modules/session/Session.qml @@ -0,0 +1,290 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Widgets + +Scope { + id: root + readonly property Toplevel activeWindow: ToplevelManager.activeToplevel + + Variants { + id: sessionVariants + model: Quickshell.screens + + PanelWindow { // Session menu + id: sessionRoot + visible: false + + property var modelData + property string subtitle + + screen: modelData + exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "quickshell:session" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + color: Appearance.transparentize(Appearance.m3colors.m3background, 0.4) + + anchors { + top: true + left: true + right: true + } + + width: modelData.width + height: modelData.height + + HyprlandFocusGrab { + id: grab + windows: [ sessionRoot ] + active: false + onCleared: () => { + if (!active) sessionRoot.visible = false + } + } + + Connections { + target: sessionRoot + function onVisibleChanged() { + delayedGrabTimer.start() + } + } + + Timer { + id: delayedGrabTimer + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + onTriggered: { + grab.active = sessionRoot.visible + } + } + + MouseArea { + id: sessionMouseArea + anchors.fill: parent + onClicked: { + sessionRoot.visible = false + } + } + + ColumnLayout { // Content column + anchors.centerIn: parent + spacing: 15 + + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Escape) { + sessionRoot.visible = false; + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 0 + StyledText { // Title + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + font.family: Appearance.font.family.title + font.pixelSize: Appearance.font.pixelSize.title + font.weight: Font.DemiBold + text: "Session" + } + + StyledText { // Small instruction + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + font.family: Appearance.font.family.title + font.pixelSize: Appearance.font.pixelSize.normal + text: "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel" + } + } + + RowLayout { // First row of buttons + spacing: 15 + SessionActionButton { + id: sessionLock + focus: sessionRoot.visible + buttonIcon: "lock" + buttonText: "Lock" + onClicked: { lock.running = true; sessionRoot.visible = false } + onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } + KeyNavigation.right: sessionSleep + KeyNavigation.down: sessionHibernate + } + SessionActionButton { + id: sessionSleep + focus: sessionRoot.visible + buttonIcon: "dark_mode" + buttonText: "Sleep" + onClicked: { sleep.running = true; sessionRoot.visible = false } + onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } + KeyNavigation.left: sessionLock + KeyNavigation.right: sessionLogout + KeyNavigation.down: sessionShutdown + } + SessionActionButton { + id: sessionLogout + focus: sessionRoot.visible + buttonIcon: "logout" + buttonText: "Logout" + onClicked: { logout.running = true; sessionRoot.visible = false } + onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } + KeyNavigation.left: sessionSleep + KeyNavigation.right: sessionTaskManager + KeyNavigation.down: sessionReboot + } + SessionActionButton { + id: sessionTaskManager + focus: sessionRoot.visible + buttonIcon: "browse_activity" + buttonText: "Task Manager" + onClicked: { taskManager.running = true; sessionRoot.visible = false } + onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } + KeyNavigation.left: sessionLogout + KeyNavigation.down: sessionFirmwareReboot + } + } + + RowLayout { // Second row of buttons + spacing: 15 + SessionActionButton { + id: sessionHibernate + focus: sessionRoot.visible + buttonIcon: "downloading" + buttonText: "Hibernate" + onClicked: { hibernate.running = true; sessionRoot.visible = false } + onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } + KeyNavigation.up: sessionLock + KeyNavigation.right: sessionShutdown + } + SessionActionButton { + id: sessionShutdown + focus: sessionRoot.visible + buttonIcon: "power_settings_new" + buttonText: "Shutdown" + onClicked: { shutdown.running = true; sessionRoot.visible = false } + onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } + KeyNavigation.left: sessionHibernate + KeyNavigation.right: sessionReboot + KeyNavigation.up: sessionSleep + } + SessionActionButton { + id: sessionReboot + focus: sessionRoot.visible + buttonIcon: "restart_alt" + buttonText: "Reboot" + onClicked: { reboot.running = true; sessionRoot.visible = false } + onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } + KeyNavigation.left: sessionShutdown + KeyNavigation.right: sessionFirmwareReboot + KeyNavigation.up: sessionLogout + } + SessionActionButton { + id: sessionFirmwareReboot + focus: sessionRoot.visible + buttonIcon: "reset_wrench" + buttonText: "Reboot to firmware settings" + onClicked: { firmwareReboot.running = true; sessionRoot.visible = false } + onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } + KeyNavigation.up: sessionTaskManager + KeyNavigation.left: sessionReboot + } + } + + Rectangle { + Layout.alignment: Qt.AlignHCenter + radius: Appearance.rounding.normal + implicitHeight: sessionSubtitle.implicitHeight + 10 * 2 + implicitWidth: sessionSubtitle.implicitWidth + 10 * 2 + color: Appearance.colors.colTooltip + clip: true + + Behavior on implicitWidth { + SmoothedAnimation { + velocity: Appearance.animation.elementDecelFast.velocity + } + } + + StyledText { + id: sessionSubtitle + anchors.centerIn: parent + color: Appearance.colors.colOnTooltip + text: sessionRoot.subtitle + } + } + } + + } + + } + + Process { + id: lock + command: ["bash", "-c", "loginctl lock-session"] + } + Process { + id: sleep + command: ["bash", "-c", "systemctl suspend || loginctl suspend"] + } + Process { + id: logout + command: ["bash", "-c", "loginctl terminate-session $XDG_SESSION_ID"] + } + Process { + id: hibernate + command: ["bash", "-c", "systemctl hibernate || loginctl hibernate"] + } + Process { + id: shutdown + command: ["bash", "-c", "systemctl poweroff || loginctl poweroff"] + } + Process { + id: reboot + command: ["bash", "-c", "systemctl reboot || loginctl reboot"] + } + Process { + id: firmwareReboot + command: ["bash", "-c", "systemctl reboot --firmware-setup || loginctl reboot --firmware-setup"] + } + Process { + id: taskManager + command: ["bash", "-c", "gnome-system-monitor & disown"] + } + + IpcHandler { + target: "session" + + function toggle(): void { + for (let i = 0; i < sessionVariants.instances.length; i++) { + let panelWindow = sessionVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = !panelWindow.visible; + } + } + } + + function close(): void { + for (let i = 0; i < sessionVariants.instances.length; i++) { + let panelWindow = sessionVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = false; + } + } + } + + function open(): void { + for (let i = 0; i < sessionVariants.instances.length; i++) { + let panelWindow = sessionVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = true; + } + } + } + } + +} diff --git a/.config/quickshell/modules/session/SessionActionButton.qml b/.config/quickshell/modules/session/SessionActionButton.qml new file mode 100644 index 000000000..c27b0b268 --- /dev/null +++ b/.config/quickshell/modules/session/SessionActionButton.qml @@ -0,0 +1,65 @@ +import "root:/modules/common" +import "root:/modules/common/widgets/" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io + +Button { + id: button + + property string buttonIcon + property string buttonText + property bool keyboardDown: false + + implicitHeight: 120 + implicitWidth: 120 + + PointingHandInteraction {} + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + keyboardDown = true + button.clicked() + event.accepted = true; + } + } + Keys.onReleased: (event) => { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + keyboardDown = false + event.accepted = true; + } + } + + onClicked: { + console.log("Button clicked:", buttonText) + } + + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.full + color: (button.down || button.keyboardDown) ? Appearance.colors.colLayer2Active : ((button.hovered || button.focus) ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } + + } + + contentItem: MaterialSymbol { + id: icon + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + text: buttonIcon + font.pixelSize: 40 + } + + StyledToolTip { + content: buttonText + } + +} diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index b2e9404b1..2d9037923 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -78,7 +78,6 @@ Scope { Keys.onPressed: (event) => { if (event.key === Qt.Key_Escape) { sidebarRoot.visible = false; - event.accepted = true; // Prevent further propagation of the event } } @@ -108,10 +107,23 @@ Scope { } Item { - Layout.fillHeight: true + Layout.fillWidth: true } - + QuickToggleButton { + toggled: false + buttonIcon: "power_settings_new" + onClicked: { + openSessionMenu.running = true + } + Process { + id: openSessionMenu + command: ["qs", "ipc", "call", "session", "open"] + } + StyledToolTip { + content: "Session" + } + } } Rectangle { diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 9753bfbb3..68f1f047e 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -3,6 +3,7 @@ import "./modules/bar/" import "./modules/onScreenDisplay/" import "./modules/screenCorners/" +import "./modules/session/" import "./modules/sidebarRight/" import QtQuick import QtQuick.Controls @@ -12,10 +13,11 @@ import Quickshell ShellRoot { Bar {} - SidebarRight {} - ScreenCorners {} - ReloadPopup {} OnScreenDisplayBrightness {} OnScreenDisplayVolume {} + ReloadPopup {} + ScreenCorners {} + Session {} + SidebarRight {} } From 5dc0dc133dec95bd24219190d1e332b7bd185673 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 21 Apr 2025 21:31:33 +0200 Subject: [PATCH 077/824] safer property access + style adjustments --- .config/quickshell/ReloadPopup.qml | 1 - .config/quickshell/modules/common/widgets/MaterialSymbol.qml | 4 ++-- .../quickshell/modules/common/widgets/NotificationWidget.qml | 1 - .../modules/onScreenDisplay/OnScreenDisplayBrightness.qml | 2 +- .../modules/onScreenDisplay/OnScreenDisplayVolume.qml | 4 ++-- .../quickshell/modules/onScreenDisplay/OsdValueIndicator.qml | 1 + .config/quickshell/modules/session/Session.qml | 2 +- .config/quickshell/modules/session/SessionActionButton.qml | 1 + .config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml | 2 +- .config/quickshell/modules/sidebarRight/SidebarRight.qml | 1 + .../sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml | 1 + .config/quickshell/services/Audio.qml | 2 +- 12 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.config/quickshell/ReloadPopup.qml b/.config/quickshell/ReloadPopup.qml index 637141912..a0927d5b4 100644 --- a/.config/quickshell/ReloadPopup.qml +++ b/.config/quickshell/ReloadPopup.qml @@ -80,7 +80,6 @@ Scope { renderType: Text.NativeRendering font.family: "Rubik" font.pointSize: 14 - // color: Appearance.colors.colOnBackground text: root.failed ? "Quickshell: Reload failed" : "Quickshell reloaded" color: failed ? "#ff93000A" : "#ff0C1F13" } diff --git a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml index 7891da690..324d77208 100644 --- a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml +++ b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml @@ -1,4 +1,4 @@ -import "root:/modules/common" +import "root:/modules/common/" import QtQuick import QtQuick.Layouts @@ -7,5 +7,5 @@ Text { verticalAlignment: Text.AlignVCenter font.family: Appearance.font.family.iconMaterial font.pixelSize: Appearance.font.pixelSize.small - color: Appearance.colors.colOnBackground + color: Appearance.m3colors.m3onBackground } diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 5e2763c3f..4ae3ba9c6 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -381,7 +381,6 @@ Item { contentItem: MaterialSymbol { anchors.centerIn: parent - // text: expanded ? "keyboard_arrow_up" : "keyboard_arrow_down" text: "keyboard_arrow_down" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml index 961ea6d4c..1c692264c 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml @@ -36,7 +36,7 @@ Scope { } Connections { - target: Audio.sink.audio + target: Audio.sink?.audio function onVolumeChanged() { if (!Audio.ready) return root.showOsdValues = false diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml index c430e935c..f15f8acca 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml @@ -28,7 +28,7 @@ Scope { } Connections { - target: Audio.sink.audio + target: Audio.sink?.audio function onVolumeChanged() { if (!Audio.ready) return root.triggerOsd() @@ -97,7 +97,7 @@ Scope { OsdValueIndicator { id: osdValues anchors.centerIn: parent - value: Audio.sink.audio.volume + value: Audio.sink?.audio.volume icon: "volume_up" name: "Volume" } diff --git a/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml b/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml index d24074879..d79dd1e56 100644 --- a/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml +++ b/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml @@ -38,6 +38,7 @@ Item { Layout.leftMargin: valueIndicatorLeftPadding Layout.topMargin: valueIndicatorVerticalPadding Layout.bottomMargin: valueIndicatorVerticalPadding + color: Appearance.colors.colOnLayer0 text: root.icon font.pixelSize: 30 } diff --git a/.config/quickshell/modules/session/Session.qml b/.config/quickshell/modules/session/Session.qml index 620ee8353..f2d666cf7 100644 --- a/.config/quickshell/modules/session/Session.qml +++ b/.config/quickshell/modules/session/Session.qml @@ -200,7 +200,7 @@ Scope { Layout.alignment: Qt.AlignHCenter radius: Appearance.rounding.normal implicitHeight: sessionSubtitle.implicitHeight + 10 * 2 - implicitWidth: sessionSubtitle.implicitWidth + 10 * 2 + implicitWidth: sessionSubtitle.implicitWidth + 15 * 2 color: Appearance.colors.colTooltip clip: true diff --git a/.config/quickshell/modules/session/SessionActionButton.qml b/.config/quickshell/modules/session/SessionActionButton.qml index c27b0b268..545cfab54 100644 --- a/.config/quickshell/modules/session/SessionActionButton.qml +++ b/.config/quickshell/modules/session/SessionActionButton.qml @@ -53,6 +53,7 @@ Button { contentItem: MaterialSymbol { id: icon anchors.fill: parent + color: Appearance.colors.colOnLayer2 horizontalAlignment: Text.AlignHCenter text: buttonIcon font.pixelSize: 40 diff --git a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml index 7fe0fd29d..1cd649d68 100644 --- a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml @@ -110,7 +110,7 @@ Rectangle { SwipeView { id: swipeView - Layout.topMargin: 10 + Layout.topMargin: 5 Layout.fillWidth: true Layout.fillHeight: true currentIndex: currentTab diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 2d9037923..576015ab9 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -91,6 +91,7 @@ Scope { Layout.fillHeight: false spacing: 10 Layout.margins: 10 + Layout.topMargin: 5 Layout.bottomMargin: 0 CustomIcon { diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml index 4b28980f8..1e235adea 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml @@ -37,6 +37,7 @@ Button { Layout.alignment: Qt.AlignVCenter Layout.fillWidth: false Layout.leftMargin: 5 + color: Appearance.colors.colOnLayer2 font.pixelSize: Appearance.font.pixelSize.hugeass text: input ? "mic_external_on" : "media_output" } diff --git a/.config/quickshell/services/Audio.qml b/.config/quickshell/services/Audio.qml index 80c59f164..5d97c3a3d 100644 --- a/.config/quickshell/services/Audio.qml +++ b/.config/quickshell/services/Audio.qml @@ -7,7 +7,7 @@ pragma ComponentBehavior: Bound Singleton { id: root - property bool ready: Pipewire.defaultAudioSink.ready + property bool ready: Pipewire.defaultAudioSink?.ready property var sink: Pipewire.defaultAudioSink property var source: Pipewire.defaultAudioSource From 54fdf043c94fef17f2ded993603e5a4523f654ce Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 21 Apr 2025 23:29:31 +0200 Subject: [PATCH 078/824] notification popups --- .config/quickshell/GlobalStates.qml | 8 ++ .config/quickshell/modules/bar/Workspaces.qml | 43 +++---- .../quickshell/modules/common/Appearance.qml | 1 + .../common/widgets/CircularProgress.qml | 17 ++- .../common/widgets/NotificationWidget.qml | 45 ++++++- .../notificationPopup/NotificationPopup.qml | 120 ++++++++++++++++++ .../OnScreenDisplayBrightness.qml | 2 - .../onScreenDisplay/OnScreenDisplayVolume.qml | 2 - .../modules/sidebarRight/SidebarRight.qml | 9 +- .../notifications/NotificationList.qml | 22 ++-- .config/quickshell/services/Notifications.qml | 15 ++- .config/quickshell/shell.qml | 2 + 12 files changed, 240 insertions(+), 46 deletions(-) create mode 100644 .config/quickshell/GlobalStates.qml create mode 100644 .config/quickshell/modules/notificationPopup/NotificationPopup.qml diff --git a/.config/quickshell/GlobalStates.qml b/.config/quickshell/GlobalStates.qml new file mode 100644 index 000000000..e50f7b7bb --- /dev/null +++ b/.config/quickshell/GlobalStates.qml @@ -0,0 +1,8 @@ +import QtQuick +import Quickshell +pragma Singleton +pragma ComponentBehavior: Bound + +Singleton { + property int sidebarRightOpenCount: 0 +} \ No newline at end of file diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index ef4356ef9..8e69b94f2 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -163,32 +163,31 @@ Item { Layout.fillHeight: true onPressed: Hyprland.dispatch(`workspace ${index+1}`) width: workspaceButtonWidth - - contentItem: StyledText { - z: 3 - property int workspaceValue: workspaceGroup * ConfigOptions.bar.workspacesShown + index + 1 - - anchors.fill: parent - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.pixelSize: Appearance.font.pixelSize.small - text: `${workspaceValue}` - elide: Text.ElideRight - color: (monitor.activeWorkspace?.id == workspaceValue) ? Appearance.m3colors.m3onPrimary : (workspaceOccupied[index] ? Appearance.colors.colOnLayer1 : Appearance.colors.colOnLayer1Inactive) - - Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type - } - - } - - } background: Item { implicitWidth: workspaceButtonWidth implicitHeight: workspaceButtonWidth + StyledText { + z: 3 + property int workspaceValue: workspaceGroup * ConfigOptions.bar.workspacesShown + index + 1 + + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== "10")) + text: `${workspaceValue}` + elide: Text.ElideRight + color: (monitor.activeWorkspace?.id == workspaceValue) ? Appearance.m3colors.m3onPrimary : (workspaceOccupied[index] ? Appearance.colors.colOnLayer1 : Appearance.colors.colOnLayer1Inactive) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } + + } } diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 048fbbf25..5a0f521df 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -195,6 +195,7 @@ Singleton { property int barCenterSideModuleWidth: 360 property int barPreferredSideSectionWidth: 400 property int sidebarWidth: 450 + property int hyprlandGapsOut: 5 property int elevationMargin: 7 property int fabShadowRadius: 5 diff --git a/.config/quickshell/modules/common/widgets/CircularProgress.qml b/.config/quickshell/modules/common/widgets/CircularProgress.qml index 8fc6a83b5..480098db1 100644 --- a/.config/quickshell/modules/common/widgets/CircularProgress.qml +++ b/.config/quickshell/modules/common/widgets/CircularProgress.qml @@ -1,7 +1,8 @@ // From https://github.com/rafzby/circular-progressbar // License: LGPL-3.0 - A copy can be found in `licenses` folder of repo // Modified so it looks like in Material 3: https://m3.material.io/components/progress-indicators/specs -import QtQuick 2.9 +import QtQuick +import "root:/modules/common" Item { id: root @@ -9,15 +10,19 @@ Item { property int size: 30 property int lineWidth: 2 property real value: 0 - property color primaryColor: "#70585D" - property color secondaryColor: "#FFF8F7" + property color primaryColor: Appearance.m3colors.m3onSecondaryContainer + property color secondaryColor: Appearance.m3colors.m3secondaryContainer property real gapAngle: Math.PI / 10 property bool fill: false property int fillOverflow: 2 property int animationDuration: 1000 + property var easingType: Easing.OutCubic width: size height: size + + signal animationFinished(); + onValueChanged: { canvas.degree = value * 360; } @@ -62,12 +67,12 @@ Item { // Secondary ctx.beginPath(); - ctx.arc(x, y, radius, progressAngle + gapAngle, startAngle - gapAngle); + ctx.arc(x, y, radius, progressAngle + gapAngle, fullAngle - gapAngle); ctx.strokeStyle = root.secondaryColor; ctx.stroke(); // Primary (value indication) - var endAngle = (progressAngle === startAngle) ? startAngle + epsilon : progressAngle; + var endAngle = progressAngle + (value > 0 ? 0 : epsilon); ctx.beginPath(); ctx.arc(x, y, radius, startAngle, endAngle); ctx.strokeStyle = root.primaryColor; @@ -77,7 +82,7 @@ Item { Behavior on degree { NumberAnimation { duration: root.animationDuration - easing.type: Easing.OutCubic + easing.type: root.easingType } } diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 4ae3ba9c6..bef591a93 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -13,13 +13,15 @@ import "./notification_utils.js" as NotificationUtils Item { id: root property var notificationObject + property bool popup: false property bool expanded: false property bool enableAnimation: true property int notificationListSpacing: 5 property bool ready: false + property int defaultTimeoutValue: 5000 Layout.fillWidth: true - clip: true + clip: !popup Process { id: closeSidebarProcess @@ -42,6 +44,16 @@ Item { Component.onCompleted: { root.ready = true + if (popup) timeoutTimer.start() + } + + Timer { + id: timeoutTimer + interval: notificationObject.expireTimeout ?? root.defaultTimeoutValue + repeat: false + onTriggered: { + Notifications.timeoutNotification(notificationObject.id); + } } function destroyWithAnimation(delay = 0) { @@ -104,7 +116,7 @@ Item { root.toggleExpanded() } - // Flick right to dismiss + // Flick right to dismiss/discard property real startX: 0 property real dragStartThreshold: 10 property real dragConfirmThreshold: 70 @@ -124,7 +136,7 @@ Item { } onDragStartedChanged: () => { // Prevent drag focus being shifted to parent flickable - root.parent.parent.parent.interactive = !dragStarted + if (root.parent.parent.parent.interactive !== undefined) root.parent.parent.parent.interactive = !dragStarted root.enableAnimation = !dragStarted } onReleased: (mouse) => { @@ -196,6 +208,18 @@ Item { } } } + + DropShadow { + visible: popup + id: notificationShadow + anchors.fill: notificationBackground + source: notificationBackground + radius: 5 + samples: radius * 2 + 1 + color: Appearance.colors.colShadow + verticalOffset: 2 + horizontalOffset: 0 + } } @@ -334,6 +358,21 @@ Item { elide: Text.ElideRight } + CircularProgress { + id: notificationProgress + visible: popup + Layout.alignment: Qt.AlignVCenter + lineWidth: 2 + value: popup ? 1 : 0 + size: 20 + animationDuration: notificationObject.expireTimeout ?? root.defaultTimeoutValue + easingType: Easing.Linear + + Component.onCompleted: { + value = 0 + } + } + StyledText { // Time id: notificationTimeText Layout.fillWidth: false diff --git a/.config/quickshell/modules/notificationPopup/NotificationPopup.qml b/.config/quickshell/modules/notificationPopup/NotificationPopup.qml new file mode 100644 index 000000000..cfd5537d9 --- /dev/null +++ b/.config/quickshell/modules/notificationPopup/NotificationPopup.qml @@ -0,0 +1,120 @@ +import "root:/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland + +Scope { + id: screenCorners + readonly property Toplevel activeWindow: ToplevelManager.activeToplevel + + Variants { + model: Quickshell.screens + + PanelWindow { + id: root + visible: true + + property var modelData + property Component notifComponent: NotificationWidget {} + property list notificationWidgetList: [] + + screen: modelData + WlrLayershell.namespace: "quickshell:notificationPopup" + WlrLayershell.layer: WlrLayer.Overlay + exclusiveZone: 0 + + anchors { + top: true + right: true + bottom: true + } + + mask: Region { + item: columnLayout + } + + color: "transparent" + width: Appearance.sizes.notificationPopupWidth + height: columnLayout.implicitHeight + + // Signal handlers to add/remove notifications + Connections { + target: Notifications + function onNotify(notification) { + if (GlobalStates.sidebarRightOpenCount > 0) { + return + } + // notificationRepeater.model = [notification, ...notificationRepeater.model] + const notif = root.notifComponent.createObject(columnLayout, { + notificationObject: notification, + popup: true + }); + notificationWidgetList.unshift(notif) + + // Remove stuff from t he column, add back + for (let i = 0; i < notificationWidgetList.length; i++) { + if (notificationWidgetList[i].parent === columnLayout) { + notificationWidgetList[i].parent = null; + } + } + + // Add notification widgets to the column + for (let i = 0; i < notificationWidgetList.length; i++) { + if (notificationWidgetList[i].parent === null) { + notificationWidgetList[i].parent = columnLayout; + } + } + } + function onDiscard(id) { + for (let i = notificationWidgetList.length - 1; i >= 0; i--) { + const widget = notificationWidgetList[i]; + if (widget && widget.notificationObject && widget.notificationObject.id === id) { + widget.destroyWithAnimation(); + notificationWidgetList.splice(i, 1); + } + } + } + function onTimeout(id) { + for (let i = notificationWidgetList.length - 1; i >= 0; i--) { + const widget = notificationWidgetList[i]; + if (widget && widget.notificationObject && widget.notificationObject.id === id) { + widget.destroyWithAnimation(); + notificationWidgetList.splice(i, 1); + } + } + } + function onDiscardAll() { + for (let i = notificationWidgetList.length - 1; i >= 0; i--) { + const widget = notificationWidgetList[i]; + if (widget && widget.notificationObject) { + widget.destroyWithAnimation(); + } + } + notificationWidgetList = []; + } + } + + ColumnLayout { // Scrollable window content + id: columnLayout + anchors.horizontalCenter: parent.horizontalCenter + width: parent.width - Appearance.sizes.hyprlandGapsOut * 2 + spacing: 0 // The widgets themselves have margins for spacing + + Item { + implicitHeight: 1 + implicitWidth: 1 + } + + // Notifications are added by the above signal handlers + } + + } + + } + +} diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml index 1c692264c..fcdb778f5 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml @@ -57,8 +57,6 @@ Scope { anchors { top: true - left: true - right: true } mask: Region { item: osdValuesWrapper diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml index f15f8acca..dd7d237d7 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml @@ -57,8 +57,6 @@ Scope { anchors { top: true - left: true - right: true } mask: Region { item: osdValuesWrapper diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 576015ab9..50cd24bb2 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -1,6 +1,7 @@ +import "root:/" +import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" -import "root:/services" import "./quickToggles/" import QtQuick import QtQuick.Controls @@ -25,6 +26,10 @@ Scope { visible: false focusable: true + onVisibleChanged: { + GlobalStates.sidebarRightOpenCount += visible ? 1 : -1 + } + property var modelData screen: modelData @@ -191,6 +196,7 @@ Scope { let panelWindow = sidebarVariants.instances[i]; if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { panelWindow.visible = !panelWindow.visible; + if(panelWindow.visible) Notifications.timeoutAll(); } } } @@ -209,6 +215,7 @@ Scope { let panelWindow = sidebarVariants.instances[i]; if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { panelWindow.visible = true; + if(panelWindow.visible) Notifications.timeoutAll(); } } } diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml index c04015b0e..a0e1bfb96 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -22,6 +22,7 @@ Item { notificationWidgetList.push(notif) }) } + function onNotify(notification) { // notificationRepeater.model = [notification, ...notificationRepeater.model] const notif = root.notifComponent.createObject(columnLayout, { notificationObject: notification }); @@ -41,6 +42,7 @@ Item { } } } + function onDiscard(id) { for (let i = notificationWidgetList.length - 1; i >= 0; i--) { const widget = notificationWidgetList[i]; @@ -50,6 +52,16 @@ Item { } } } + + function onDiscardAll() { + for (let i = notificationWidgetList.length - 1; i >= 0; i--) { + const widget = notificationWidgetList[i]; + if (widget && widget.notificationObject) { + widget.destroyWithAnimation(); + } + } + notificationWidgetList = []; + } } Flickable { // Scrollable window @@ -145,15 +157,7 @@ Item { buttonIcon: "clear_all" buttonText: "Clear" onClicked: () => { - for (let i = notificationWidgetList.length - 1; i >= 0; i--) { - // for (let i = 0; i < notificationWidgetList.length; i++) { - const widget = notificationWidgetList[i]; - if (widget && widget.notificationObject) { - widget.destroyWithAnimation(); - } - } - notificationWidgetList = []; - Notifications.discardAll() + Notifications.discardAllNotifications() } } } diff --git a/.config/quickshell/services/Notifications.qml b/.config/quickshell/services/Notifications.qml index 2aa17c2bf..d04233b89 100644 --- a/.config/quickshell/services/Notifications.qml +++ b/.config/quickshell/services/Notifications.qml @@ -18,6 +18,8 @@ Singleton { signal initDone(); signal notify(notification: var); signal discard(id: var); + signal discardAll(); + signal timeout(id: var); NotificationServer { id: notifServer @@ -69,13 +71,24 @@ Singleton { root.discard(id); } - function discardAll() { + function discardAllNotifications() { root.list = [] triggerListChange() notifFileView.setText(JSON.stringify(root.list, null, 2)) notifServer.trackedNotifications.values.forEach((notif) => { notif.dismiss() }) + root.discardAll(); + } + + function timeoutNotification(id) { + root.timeout(id); + } + + function timeoutAll() { + root.list.forEach((notif) => { + root.timeout(notif.id); + }) } function attemptInvokeAction(id, notifIdentifier) { diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 68f1f047e..002dad7df 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -1,6 +1,7 @@ //@ pragma UseQApplication import "./modules/bar/" +import "./modules/notificationPopup/" import "./modules/onScreenDisplay/" import "./modules/screenCorners/" import "./modules/session/" @@ -13,6 +14,7 @@ import Quickshell ShellRoot { Bar {} + NotificationPopup {} OnScreenDisplayBrightness {} OnScreenDisplayVolume {} ReloadPopup {} From 2459bf24640e091677da39b4f4fea8118cfec821 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 21 Apr 2025 23:40:20 +0200 Subject: [PATCH 079/824] fix weird notif width stupid copilot bug --- .config/quickshell/modules/common/Appearance.qml | 2 +- .../quickshell/modules/notificationPopup/NotificationPopup.qml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 5a0f521df..82915bfce 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -195,7 +195,7 @@ Singleton { property int barCenterSideModuleWidth: 360 property int barPreferredSideSectionWidth: 400 property int sidebarWidth: 450 - + property int notificationPopupWidth: 410 property int hyprlandGapsOut: 5 property int elevationMargin: 7 property int fabShadowRadius: 5 diff --git a/.config/quickshell/modules/notificationPopup/NotificationPopup.qml b/.config/quickshell/modules/notificationPopup/NotificationPopup.qml index cfd5537d9..e21f5ed0e 100644 --- a/.config/quickshell/modules/notificationPopup/NotificationPopup.qml +++ b/.config/quickshell/modules/notificationPopup/NotificationPopup.qml @@ -1,5 +1,5 @@ import "root:/" -import "root:/modules/common" +import "root:/modules/common/" import "root:/modules/common/widgets" import "root:/services" import QtQuick @@ -40,7 +40,6 @@ Scope { color: "transparent" width: Appearance.sizes.notificationPopupWidth - height: columnLayout.implicitHeight // Signal handlers to add/remove notifications Connections { From 8e9f8bf173dbf2c929beb731a677148211cf601c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 22 Apr 2025 08:04:12 +0200 Subject: [PATCH 080/824] use dolphin; update session keybind --- .config/hypr/hyprland/keybinds.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 8aac511b2..4c85c62bc 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -170,14 +170,14 @@ bind = Super, N, exec, qs ipc call sidebarRight toggle || agsv1 -t 'sideright' # bind = Super, M, exec, agsv1 run-js 'openMusicControls.value = (!mpris.getPlayer() ? false : !openMusicControls.value);' # Toggle music controls bind = Super, Comma, exec, agsv1 run-js 'openColorScheme.value = true; Utils.timeout(2000, () => openColorScheme.value = false);' # View color scheme and options bind = Super, K, exec, for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "osk""$i"; done # Toggle on-screen keyboard -bind = Ctrl+Alt, Delete, exec, for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "session""$i"; done # Toggle power menu +bind = Ctrl+Alt, Delete, exec, qs ipc call session toggle || for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "session""$i"; done # Toggle power menu bind = Ctrl+Super, G, exec, for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "crosshair""$i"; done # Toggle crosshair bindle=, XF86MonBrightnessUp, exec, qs ipc call brightness increment || agsv1 run-js 'brightness.screen_value += 0.05; indicator.popup(1);' # [hidden] bindle=, XF86MonBrightnessDown, exec, qs ipc call brightness decrement || agsv1 run-js 'brightness.screen_value -= 0.05; indicator.popup(1);' # [hidden] # Testing bind = Super+Alt, f11, exec, bash -c 'RANDOM_IMAGE=$(find ~/Pictures -type f | grep -v -i "nipple" | grep -v -i "pussy" | shuf -n 1); ACTION=$(notify-send "Test notification with body image" "This notification should contain your user account image and Discord icon. Oh and here is a random image in your Pictures folder: \"Testing" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Open the random image" -A "action3=Useless button"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] -bind = Super+Alt, f12, exec, bash -c 'ACTION=$(notify-send "Test notification" "This notification should contain your user account image and Discord icon." -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Useless button" -A "action3=Cry more"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] +bind = Super+Alt, f12, exec, bash -c 'ACTION=$(notify-send "Test notification" "This notification should contain your user account image and Discord icon.\nFlick right to dismiss!" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Useless button" -A "action3=Cry more"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] bind = Super+Alt, Equal, exec, notify-send "Urgent notification" "Ah hell no" -u critical -a 'Hyprland keybind' # [hidden] ##! Media @@ -196,9 +196,9 @@ bindl= ,XF86AudioPause, exec, playerctl play-pause # [hidden] #! ##! Apps bind = Super, T, exec, # Launch foot (terminal) +bind = Super, E, exec, dolphin --new-window || nautilus --new-window # Launch file manager (Dolphin/Nautilus) bind = Super, Z, exec, Zed # Launch Zed (editor) bind = Super, C, exec, code # Launch VSCode (editor) -bind = Super, E, exec, nautilus --new-window # Launch Nautilus (file manager) bind = Super+Alt, E, exec, thunar # [hidden] bind = Super, W, exec, google-chrome-stable || firefox # [hidden] Let's not give people (more) reason to shit on my rice bind = Ctrl+Super, W, exec, firefox # Launch Firefox (browser) From 13f68dacfb3dcc35c1875f26d12a8fdbcd811215 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 22 Apr 2025 08:04:21 +0200 Subject: [PATCH 081/824] update hyprland layer rules --- .config/hypr/hyprland/rules.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index ae754008e..b789c0d5d 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -109,6 +109,8 @@ layerrule = animation fade, quickshell:screenCorners layerrule = animation slide right, quickshell:sidebarRight layerrule = animation slide left, quickshell:sidebarLeft layerrule = animation slide top, quickshell:onScreenDisplay +layerrule = animation fade, quickshell:session +layerrule = blur, quickshell:session ## outfoxxed's stuff layerrule = blur, shell:bar layerrule = ignorezero, shell:bar From 806230ff5254e3a87ba784b13df824f1c1a5dedf Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 22 Apr 2025 08:10:54 +0200 Subject: [PATCH 082/824] notif popup dismiss animation fix --- .../quickshell/modules/common/widgets/NotificationWidget.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index bef591a93..85da673f0 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -78,8 +78,8 @@ Item { notificationBackground.anchors.left = undefined notificationBackground.anchors.right = undefined notificationBackground.anchors.fill = undefined - notificationRowWrapper.x = width - notificationBackground.x = width + notificationRowWrapper.x = width + 5 * 2 // Account for shadow + notificationBackground.x = width + 5 * 2 // Account for shadow destroyTimer1.start() } } From 41ffd0ac805bd1d36ad0edf52678add1f90fc342 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 23 Apr 2025 20:40:29 +0200 Subject: [PATCH 083/824] overview --- .config/quickshell/GlobalStates.qml | 1 + .config/quickshell/modules/bar/Bar.qml | 19 ++- .../quickshell/modules/common/Appearance.qml | 1 + .../modules/common/ConfigOptions.qml | 6 + .../common/widgets/NotificationWidget.qml | 1 - .../quickshell/modules/overview/Overview.qml | 105 ++++++++++++++++ .../modules/overview/OverviewWidget.qml | 114 ++++++++++++++++++ .../modules/overview/OverviewWindow.qml | 107 ++++++++++++++++ .config/quickshell/modules/overview/icons.js | 71 +++++++++++ .../quickshell/modules/session/Session.qml | 2 +- .../modules/session/SessionActionButton.qml | 4 - .config/quickshell/services/HyprlandData.qml | 65 ++++++++++ .config/quickshell/shell.qml | 2 + 13 files changed, 491 insertions(+), 7 deletions(-) create mode 100644 .config/quickshell/modules/overview/Overview.qml create mode 100644 .config/quickshell/modules/overview/OverviewWidget.qml create mode 100644 .config/quickshell/modules/overview/OverviewWindow.qml create mode 100644 .config/quickshell/modules/overview/icons.js create mode 100644 .config/quickshell/services/HyprlandData.qml diff --git a/.config/quickshell/GlobalStates.qml b/.config/quickshell/GlobalStates.qml index e50f7b7bb..674d586c3 100644 --- a/.config/quickshell/GlobalStates.qml +++ b/.config/quickshell/GlobalStates.qml @@ -5,4 +5,5 @@ pragma ComponentBehavior: Bound Singleton { property int sidebarRightOpenCount: 0 + property bool overviewOpen: false } \ No newline at end of file diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index d6dc54595..5ab29e8ec 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -30,6 +30,10 @@ Scope { id: hideOsdVolume command: ["qs", "ipc", "call", "osdVolume", "hide"] } + Process { + id: toggleOverview + command: ["qs", "ipc", "call", "overview", "toggle"] + } Variants { // For each monitor model: Quickshell.screens @@ -220,6 +224,19 @@ Scope { } } + MouseArea { // Middle: right-click to toggle overview + id: barMiddleMouseArea + anchors.fill: middleSection + acceptedButtons: Qt.RightButton + + onPressed: (event) => { + if (event.button === Qt.RightButton) { + toggleOverview.running = true; + } + } + + } + MouseArea { // Right side: scroll to change volume id: barRightSideMouseArea property bool hovered: false @@ -250,7 +267,7 @@ Scope { if (event.angleDelta.y < 0) Audio.sink.audio.volume -= step; else if (event.angleDelta.y > 0) - Audio.sink.audio.volume += step; + Audio.sink.audio.volume = Math.min(1, Audio.sink.audio.volume + step); // Store the mouse position and start tracking barRightSideMouseArea.lastScrollX = event.x; barRightSideMouseArea.lastScrollY = event.y; diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 82915bfce..284aca6de 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -145,6 +145,7 @@ Singleton { property int large: 25 property int full: 9999 property int screenRounding: large + property int windowRounding: 20 } font: QtObject { diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index d30be6078..cddaf8f4b 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -30,6 +30,12 @@ Singleton { property int timeout: 1000 } + property QtObject overview: QtObject { + property real scale: 0.18 // Relative to screen size + property real numOfRows: 2 + property real numOfCols: 5 + } + property QtObject resources: QtObject { property int updateInterval: 3000 } diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 85da673f0..bd53316a0 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -131,7 +131,6 @@ Item { if (mouse.button === Qt.LeftButton) { copyNotificationBody.running = true notificationSummaryText.text = `${notificationObject.summary} (copied)` - console.log(notificationSummaryText.text) } } onDragStartedChanged: () => { diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml new file mode 100644 index 000000000..4ff3d11f7 --- /dev/null +++ b/.config/quickshell/modules/overview/Overview.qml @@ -0,0 +1,105 @@ +import "root:/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland + +Scope { + id: overview + + Variants { + model: Quickshell.screens + + PanelWindow { + id: root + property var modelData + screen: modelData + visible: GlobalStates.overviewOpen + + WlrLayershell.namespace: "quickshell:overview" + WlrLayershell.layer: WlrLayer.Overlay + color: "transparent" + + mask: Region { + item: columnLayout + } + + anchors { + top: true + left: true + right: true + bottom: true + } + + HyprlandFocusGrab { + id: grab + windows: [ root ] + active: false + onCleared: () => { + if (!active) GlobalStates.overviewOpen = false + } + } + + Connections { + target: root + function onVisibleChanged() { + delayedGrabTimer.start() + } + } + + Timer { + id: delayedGrabTimer + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + onTriggered: { + grab.active = root.visible + } + } + + width: columnLayout.width + height: columnLayout.height + + ColumnLayout { + id: columnLayout + anchors.horizontalCenter: parent.horizontalCenter + + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Escape) { + sessionRoot.visible = false; + } + } + + Item { + height: 1 // Prevent Wayland protocol error + width: 1 // Prevent Wayland protocol error + } + + OverviewWidget { + bar: root + } + } + + } + + } + + IpcHandler { + target: "overview" + + function toggle() { + GlobalStates.overviewOpen = !GlobalStates.overviewOpen + } + function close() { + GlobalStates.overviewOpen = false + } + function open() { + GlobalStates.overviewOpen = true + } + } + +} diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml new file mode 100644 index 000000000..db63369f6 --- /dev/null +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -0,0 +1,114 @@ +import "root:/services/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import Quickshell.Wayland +import Quickshell.Hyprland +import "./icons.js" as Icons + +Item { + id: root + required property var bar + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) + readonly property var toplevels: ToplevelManager.toplevels + readonly property int workspacesShown: ConfigOptions.overview.numOfRows * ConfigOptions.overview.numOfCols + readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / workspacesShown) + property var windows: HyprlandData.windowList + property var windowByAddress: HyprlandData.windowByAddress + property var windowAddresses: HyprlandData.addresses + property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id) + property real scale: ConfigOptions.overview.scale + + property real workspaceNumberMargin: 80 + property real workspaceNumberSize: 80 + + implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 + implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 + + property Component windowComponent: OverviewWindow {} + property list windowWidgets: [] + + // onWindowsChanged: { + // console.log("Windows changed") + // } + + Rectangle { + id: overviewBackground + + anchors.fill: parent + + implicitWidth: columnLayout.implicitWidth + 5 * 2 + implicitHeight: columnLayout.implicitHeight + 5 * 2 + color: Appearance.colors.colLayer0 + radius: Appearance.rounding.screenRounding * root.scale + 5 * 2 + + ColumnLayout { + id: columnLayout + anchors.centerIn: parent + spacing: 5 + + Repeater { + model: ConfigOptions.overview.numOfRows + delegate: RowLayout { + id: row + property int rowIndex: index + + Repeater { // Workspace repeater + model: ConfigOptions.overview.numOfCols + Rectangle { // Workspace + id: workspace + property int colIndex: index + property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * ConfigOptions.overview.numOfCols + colIndex + 1 + + implicitWidth: (monitor.width - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale + implicitHeight: (monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale + color: Appearance.colors.colLayer1 // TODO: reconsider this color for a cleaner look + radius: Appearance.rounding.screenRounding * root.scale + + StyledText { + z: 9999 + anchors.left: parent.left + anchors.top: parent.top + anchors.leftMargin: root.workspaceNumberMargin * root.scale + anchors.topMargin: root.workspaceNumberMargin * root.scale + font.pixelSize: root.workspaceNumberSize * root.scale + color: Appearance.colors.colSubtext + text: workspaceValue + } + + Repeater { // Window repeater + model: ScriptModel { + values: windowAddresses.filter((address) => { + var win = windowByAddress[address] + return (win?.workspace?.id === workspace.workspaceValue) + }) + } + delegate: OverviewWindow { + windowData: windowByAddress[modelData] + monitorData: root.monitorData + scale: root.scale + availableWorkspaceWidth: workspace.implicitWidth + availableWorkspaceHeight: workspace.implicitHeight + } + } + } + } + } + } + } + } + + DropShadow { + anchors.fill: overviewBackground + horizontalOffset: 0 + verticalOffset: 2 + radius: Appearance.sizes.elevationMargin + samples: radius * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs + color: Appearance.colors.colShadow + source: overviewBackground + } +} diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml new file mode 100644 index 000000000..34a062eb6 --- /dev/null +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -0,0 +1,107 @@ +import "root:/services/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import Quickshell.Io +import Quickshell.Hyprland +import "./icons.js" as Icons + +Rectangle { // Window + id: root + + property var windowData + property var monitorData + property var scale + property var availableWorkspaceWidth + property var availableWorkspaceHeight + + property var iconToWindowRatio: 0.35 + property var iconToWindowRatioCompact: 0.6 + property var iconPath: Quickshell.iconPath(Icons.noKnowledgeIconGuess(windowData?.class)) + property bool compactMode: Appearance.font.pixelSize.smaller * 4 > root.height || Appearance.font.pixelSize.smaller * 4 > root.width + + z: 1 + x: Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + y: Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + width: Math.min(windowData?.size[0] * root.scale, availableWorkspaceWidth - x) + height: Math.min(windowData?.size[1] * root.scale, availableWorkspaceHeight - y) + + radius: Appearance.rounding.windowRounding * root.scale + color: Appearance.colors.colLayer2 + border.color : Appearance.transparentize(Appearance.m3colors.m3outline, 0.9) + border.pixelAligned : false + border.width : 1 + + Behavior on x { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + Behavior on y { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + Behavior on width { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + Behavior on height { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + + Process { + id: closeOverview + command: ["bash", "-c", "qs ipc call overview close &"] // Somehow has to by async to work? + } + + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: { + if (windowData) { + closeOverview.running = true + Hyprland.dispatch(`focuswindow address:${windowData.address}`) + } + } + } + + ColumnLayout { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.font.pixelSize.smaller * 0.5 + + IconImage { + id: windowIcon + Layout.alignment: Qt.AlignHCenter + source: root.iconPath + width: root.width * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) + height: root.height * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) + } + + StyledText { + Layout.leftMargin: 10 + Layout.rightMargin: 10 + visible: !compactMode + Layout.fillWidth: true + Layout.fillHeight: true + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.smaller + elide: Text.ElideRight + // wrapMode: Text.Wrap + text: windowData?.title ?? "" + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/overview/icons.js b/.config/quickshell/modules/overview/icons.js new file mode 100644 index 000000000..3eeeab2b9 --- /dev/null +++ b/.config/quickshell/modules/overview/icons.js @@ -0,0 +1,71 @@ +const substitutions = { + "code-url-handler": "visual-studio-code", + "Code": "visual-studio-code", + "GitHub Desktop": "github-desktop", + "Minecraft* 1.20.1": "minecraft", + "gnome-tweaks": "org.gnome.tweaks", + "pavucontrol-qt": "pavucontrol", + "wps": "wps-office2019-kprometheus", + "wpsoffice": "wps-office2019-kprometheus", + "footclient": "foot", + "": "image-missing" +} +const regexSubstitutions = [ + { + "regex": "/^steam_app_(\\d+)$/", + "replace": "steam_icon_$1" + } +] + + +function iconExists(iconName) { + return false; // TODO: Make this work without Gtk +} + +function substitute(str) { + // Normal substitutions + if (substitutions[str]) + return substitutions[str]; + + // Regex substitutions + for (let i = 0; i < regexSubstitutions.length; i++) { + const substitution = regexSubstitutions[i]; + const replacedName = str.replace( + substitution.regex, + substitution.replace, + ); + if (replacedName != str) return replacedName; + } + + // Guess: convert to kebab case + if (!iconExists(str)) str = str.toLowerCase().replace(/\s+/g, "-"); + + // Original string + return str; +} + +function noKnowledgeIconGuess(str) { + if (!str) return "image-missing"; + + // Normal substitutions + if (substitutions[str]) + return substitutions[str]; + + // Regex substitutions + for (let i = 0; i < regexSubstitutions.length; i++) { + const substitution = regexSubstitutions[i]; + const replacedName = str.replace( + substitution.regex, + substitution.replace, + ); + if (replacedName != str) return replacedName; + } + + // Guess: convert to kebab case if it's not reverse domain name notation + if (!str.includes('.')) { + str = str.toLowerCase().replace(/\s+/g, "-"); + } + + // Original string + return str; +} \ No newline at end of file diff --git a/.config/quickshell/modules/session/Session.qml b/.config/quickshell/modules/session/Session.qml index f2d666cf7..50389c82f 100644 --- a/.config/quickshell/modules/session/Session.qml +++ b/.config/quickshell/modules/session/Session.qml @@ -187,7 +187,7 @@ Scope { SessionActionButton { id: sessionFirmwareReboot focus: sessionRoot.visible - buttonIcon: "reset_wrench" + buttonIcon: "settings_applications" buttonText: "Reboot to firmware settings" onClicked: { firmwareReboot.running = true; sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } diff --git a/.config/quickshell/modules/session/SessionActionButton.qml b/.config/quickshell/modules/session/SessionActionButton.qml index 545cfab54..c0ff0a95e 100644 --- a/.config/quickshell/modules/session/SessionActionButton.qml +++ b/.config/quickshell/modules/session/SessionActionButton.qml @@ -31,10 +31,6 @@ Button { } } - onClicked: { - console.log("Button clicked:", buttonText) - } - background: Rectangle { anchors.fill: parent radius: Appearance.rounding.full diff --git a/.config/quickshell/services/HyprlandData.qml b/.config/quickshell/services/HyprlandData.qml new file mode 100644 index 000000000..bc129465d --- /dev/null +++ b/.config/quickshell/services/HyprlandData.qml @@ -0,0 +1,65 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland + +Singleton { + id: root + property var windowList: [] + property var addresses: [] + property var windowByAddress: {} + property var monitors: [] + + function updateWindowList() { + getClients.running = true + getMonitors.running = true + } + + Component.onCompleted: { + updateWindowList() + } + + Connections { + target: Hyprland + + function onRawEvent(event) { + // Filter out redundant old v1 events for the same thing + if(event in [ + "activewindow", "focusedmon", "monitoradded", + "createworkspace", "destroyworkspace", "moveworkspace", + "activespecial", "movewindow", "windowtitle" + ]) return ; + updateWindowList() + } + } + + Process { + id: getClients + command: ["bash", "-c", "hyprctl clients -j | jq -c"] + stdout: SplitParser { + onRead: (data) => { + root.windowList = JSON.parse(data) + root.windowByAddress = {} + for (var i = 0; i < root.windowList.length; ++i) { + var win = root.windowList[i] + root.windowByAddress[win.address] = win + } + root.addresses = root.windowList.map((win) => win.address) + } + } + } + Process { + id: getMonitors + command: ["bash", "-c", "hyprctl monitors -j | jq -c"] + stdout: SplitParser { + onRead: (data) => { + root.monitors = JSON.parse(data) + } + } + } +} + diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 002dad7df..fdc1f8b15 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -3,6 +3,7 @@ import "./modules/bar/" import "./modules/notificationPopup/" import "./modules/onScreenDisplay/" +import "./modules/overview/" import "./modules/screenCorners/" import "./modules/session/" import "./modules/sidebarRight/" @@ -17,6 +18,7 @@ ShellRoot { NotificationPopup {} OnScreenDisplayBrightness {} OnScreenDisplayVolume {} + Overview {} ReloadPopup {} ScreenCorners {} Session {} From 042c8dd46127f63a4f8d7aae28f868b47e85e7e0 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 23 Apr 2025 22:16:18 +0200 Subject: [PATCH 084/824] overview windows: interaction --- .../modules/overview/OverviewWindow.qml | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index 34a062eb6..cbb00ec68 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -18,11 +18,16 @@ Rectangle { // Window property var scale property var availableWorkspaceWidth property var availableWorkspaceHeight + + property var targetWindowWidth: windowData?.size[0] * scale + property var targetWindowHeight: windowData?.size[1] * scale + property bool hovered: false + property bool pressed: false property var iconToWindowRatio: 0.35 property var iconToWindowRatioCompact: 0.6 property var iconPath: Quickshell.iconPath(Icons.noKnowledgeIconGuess(windowData?.class)) - property bool compactMode: Appearance.font.pixelSize.smaller * 4 > root.height || Appearance.font.pixelSize.smaller * 4 > root.width + property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth z: 1 x: Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) @@ -31,7 +36,7 @@ Rectangle { // Window height: Math.min(windowData?.size[1] * root.scale, availableWorkspaceHeight - y) radius: Appearance.rounding.windowRounding * root.scale - color: Appearance.colors.colLayer2 + color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 border.color : Appearance.transparentize(Appearance.m3colors.m3outline, 0.9) border.pixelAligned : false border.width : 1 @@ -69,6 +74,11 @@ Rectangle { // Window MouseArea { id: mouseArea anchors.fill: parent + hoverEnabled: true + onEntered: root.hovered = true + onExited: root.hovered = false + onPressed: root.pressed = true + onReleased: root.pressed = false onClicked: { if (windowData) { closeOverview.running = true @@ -87,8 +97,14 @@ Rectangle { // Window id: windowIcon Layout.alignment: Qt.AlignHCenter source: root.iconPath - width: root.width * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) - height: root.height * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) + implicitSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) + + Behavior on implicitSize { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } } StyledText { From 3c38bb3a72ec86c5a0e27ef388464059b1a9cb2d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 24 Apr 2025 09:36:58 +0200 Subject: [PATCH 085/824] bar: right click right side for next track --- .config/quickshell/modules/bar/Bar.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 5ab29e8ec..942e2b0b3 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -6,6 +6,7 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io +import Quickshell.Services.Mpris Scope { id: bar @@ -258,6 +259,9 @@ Scope { if (event.button === Qt.LeftButton) { openSidebarRight.running = true } + else if (event.button === Qt.RightButton) { + MprisController.activePlayer.next() + } } // Scroll to change volume WheelHandler { From 6513ee82da760d5c5171e733d1ae5b0473faf327 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 24 Apr 2025 09:37:08 +0200 Subject: [PATCH 086/824] overview: xwayland indicator --- .config/quickshell/modules/overview/OverviewWindow.qml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index cbb00ec68..da65237e6 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -105,6 +105,15 @@ Rectangle { // Window easing.type: Appearance.animation.elementDecel.type } } + + IconImage { + id: xwaylandIndicator + visible: windowData?.xwayland + anchors.right: parent.right + anchors.bottom: parent.bottom + source: Quickshell.iconPath("xorg") + implicitSize: windowIcon.implicitSize * 0.35 + } } StyledText { From e612abad23aaafcc0bf24fa6ff9d998d94710647 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 24 Apr 2025 09:44:32 +0200 Subject: [PATCH 087/824] make xwayland indicator configurable --- .config/quickshell/modules/common/ConfigOptions.qml | 1 + .config/quickshell/modules/overview/OverviewWindow.qml | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index cddaf8f4b..b75bb7a0c 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -34,6 +34,7 @@ Singleton { property real scale: 0.18 // Relative to screen size property real numOfRows: 2 property real numOfCols: 5 + property bool showXwaylandIndicator: true } property QtObject resources: QtObject { diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index da65237e6..9f255d7a0 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -25,6 +25,7 @@ Rectangle { // Window property bool pressed: false property var iconToWindowRatio: 0.35 + property var xwaylandIndicatorToIconRatio: 0.35 property var iconToWindowRatioCompact: 0.6 property var iconPath: Quickshell.iconPath(Icons.noKnowledgeIconGuess(windowData?.class)) property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth @@ -108,11 +109,11 @@ Rectangle { // Window IconImage { id: xwaylandIndicator - visible: windowData?.xwayland + visible: ConfigOptions.overview.showXwaylandIndicator && windowData?.xwayland anchors.right: parent.right anchors.bottom: parent.bottom source: Quickshell.iconPath("xorg") - implicitSize: windowIcon.implicitSize * 0.35 + implicitSize: windowIcon.implicitSize * xwaylandIndicatorToIconRatio } } From 700b126a9e81b9ae9b546ca22fbb3f9a1d406f85 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 24 Apr 2025 10:01:26 +0200 Subject: [PATCH 088/824] overview: no cursor warp for click-to-focus, add ws focus --- .../modules/overview/OverviewWidget.qml | 17 ++++++++++++++--- .../modules/overview/OverviewWindow.qml | 5 ++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index db63369f6..17068522a 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -5,6 +5,7 @@ import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Layouts import Quickshell +import Quickshell.Io import Quickshell.Widgets import Quickshell.Wayland import Quickshell.Hyprland @@ -32,9 +33,10 @@ Item { property Component windowComponent: OverviewWindow {} property list windowWidgets: [] - // onWindowsChanged: { - // console.log("Windows changed") - // } + Process { + id: closeOverview + command: ["bash", "-c", "qs ipc call overview close &"] // Somehow has to by async to work? + } Rectangle { id: overviewBackground @@ -69,6 +71,15 @@ Item { color: Appearance.colors.colLayer1 // TODO: reconsider this color for a cleaner look radius: Appearance.rounding.screenRounding * root.scale + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: (event) => { + closeOverview.running = true + Hyprland.dispatch(`workspace ${workspace.workspaceValue}`) + } + } + StyledText { z: 9999 anchors.left: parent.left diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index 9f255d7a0..ce85e0408 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -80,10 +80,13 @@ Rectangle { // Window onExited: root.hovered = false onPressed: root.pressed = true onReleased: root.pressed = false - onClicked: { + onClicked: (event) => { if (windowData) { closeOverview.running = true + Hyprland.dispatch(`keyword cursor:no_warps true`) Hyprland.dispatch(`focuswindow address:${windowData.address}`) + Hyprland.dispatch(`keyword cursor:no_warps false`) + event.accepted = true } } } From 49b3107adbf273deb6be9e95c6bc93e176eb2748 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 24 Apr 2025 19:56:12 +0200 Subject: [PATCH 089/824] typo --- .config/quickshell/modules/overview/OverviewWidget.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index 17068522a..4f5695ae1 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -35,7 +35,7 @@ Item { Process { id: closeOverview - command: ["bash", "-c", "qs ipc call overview close &"] // Somehow has to by async to work? + command: ["bash", "-c", "qs ipc call overview close &"] // Somehow has to be async to work? } Rectangle { From 84f031573e107abcb4cc9af6739664a96fef3d68 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 24 Apr 2025 20:26:58 +0200 Subject: [PATCH 090/824] overview: middle click to close --- .../quickshell/modules/overview/Overview.qml | 37 ++++++++++++++++++- .../modules/overview/OverviewWidget.qml | 10 ++--- .../modules/overview/OverviewWindow.qml | 12 ++++-- .../modules/sidebarRight/todo/TodoWidget.qml | 1 + 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 4ff3d11f7..6bdf17430 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -18,6 +18,7 @@ Scope { PanelWindow { id: root property var modelData + property string searchingText: "" screen: modelData visible: GlobalStates.overviewOpen @@ -70,7 +71,7 @@ Scope { Keys.onPressed: (event) => { if (event.key === Qt.Key_Escape) { - sessionRoot.visible = false; + GlobalStates.overviewOpen = false; } } @@ -79,7 +80,41 @@ Scope { width: 1 // Prevent Wayland protocol error } + TextField { + id: searchInput + + Layout.alignment: Qt.AlignHCenter + padding: 15 + color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant + selectedTextColor: Appearance.m3colors.m3onSurface + placeholderText: "Search, calculate or run" + placeholderTextColor: Appearance.m3colors.m3outline + focus: root.visible + + onTextChanged: root.searchingText = text + Connections { + target: root + function onVisibleChanged() { + searchInput.selectAll() + root.searchingText = "" + } + } + + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.normal + color: Appearance.colors.colLayer0 + } + + cursorDelegate: Rectangle { + width: 1 + color: searchInput.activeFocus ? Appearance.m3colors.m3primary : "transparent" + radius: 1 + } + } + OverviewWidget { + visible: (root.searchingText == "") bar: root } } diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index 4f5695ae1..829d7d653 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -92,12 +92,10 @@ Item { } Repeater { // Window repeater - model: ScriptModel { - values: windowAddresses.filter((address) => { - var win = windowByAddress[address] - return (win?.workspace?.id === workspace.workspaceValue) - }) - } + model: windowAddresses.filter((address) => { + var win = windowByAddress[address] + return (win?.workspace?.id === workspace.workspaceValue) + }) delegate: OverviewWindow { windowData: windowByAddress[modelData] monitorData: root.monitorData diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index ce85e0408..f492ee155 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -80,12 +80,16 @@ Rectangle { // Window onExited: root.hovered = false onPressed: root.pressed = true onReleased: root.pressed = false + acceptedButtons: Qt.LeftButton | Qt.MiddleButton onClicked: (event) => { - if (windowData) { + if (!windowData) return; + + if (event.button === Qt.LeftButton) { closeOverview.running = true - Hyprland.dispatch(`keyword cursor:no_warps true`) - Hyprland.dispatch(`focuswindow address:${windowData.address}`) - Hyprland.dispatch(`keyword cursor:no_warps false`) + Hyprland.dispatch(`workspace ${windowData.workspace.id}`) + event.accepted = true + } else if (event.button === Qt.MiddleButton) { + Hyprland.dispatch(`closewindow address:${windowData.address}`) event.accepted = true } } diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index d473eda2d..ab4e54305 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -275,6 +275,7 @@ Item { color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant selectedTextColor: Appearance.m3colors.m3onSurface placeholderText: "Task description" + placeholderTextColor: Appearance.m3colors.m3outline focus: root.showAddDialog onAccepted: dialog.addTask() From 8dd82baf264ac582df1242a3e67f0e9dafffa07f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 24 Apr 2025 20:28:22 +0200 Subject: [PATCH 091/824] use qsTr (for translations later) --- .../quickshell/modules/bar/ActiveWindow.qml | 4 ++-- .config/quickshell/modules/bar/Media.qml | 2 +- .../common/widgets/NotificationWidget.qml | 2 +- .../OnScreenDisplayBrightness.qml | 2 +- .../onScreenDisplay/OnScreenDisplayVolume.qml | 2 +- .../quickshell/modules/overview/Overview.qml | 2 +- .../quickshell/modules/session/Session.qml | 20 +++++++++---------- .../sidebarRight/CenterWidgetGroup.qml | 2 +- .../modules/sidebarRight/SidebarRight.qml | 2 +- .../sidebarRight/calendar/CalendarWidget.qml | 2 +- .../notifications/NotificationList.qml | 4 ++-- .../quickToggles/BluetoothToggle.qml | 2 +- .../sidebarRight/quickToggles/GameMode.qml | 2 +- .../quickToggles/IdleInhibitor.qml | 2 +- .../sidebarRight/quickToggles/NightLight.qml | 2 +- .../modules/sidebarRight/todo/TodoWidget.qml | 14 ++++++------- .../volumeMixer/AudioDeviceSelectorButton.qml | 4 ++-- .../sidebarRight/volumeMixer/VolumeMixer.qml | 6 +++--- .../quickshell/services/MprisController.qml | 6 +++--- 19 files changed, 41 insertions(+), 41 deletions(-) diff --git a/.config/quickshell/modules/bar/ActiveWindow.qml b/.config/quickshell/modules/bar/ActiveWindow.qml index 9d5ecb89e..a675240a1 100644 --- a/.config/quickshell/modules/bar/ActiveWindow.qml +++ b/.config/quickshell/modules/bar/ActiveWindow.qml @@ -27,7 +27,7 @@ Item { color: Appearance.colors.colSubtext Layout.preferredWidth: preferredWidth elide: Text.ElideRight - text: activeWindow?.activated ? activeWindow?.appId : "Desktop" + text: activeWindow?.activated ? activeWindow?.appId : qsTr("Desktop") } StyledText { @@ -35,7 +35,7 @@ Item { color: Appearance.colors.colOnLayer0 Layout.preferredWidth: preferredWidth elide: Text.ElideRight - text: activeWindow?.activated ? activeWindow?.title : `Workspace ${monitor.activeWorkspace?.id}` + text: activeWindow?.activated ? activeWindow?.title : `${qsTr("Workspace")} ${monitor.activeWorkspace?.id}` } } diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml index 434a8a24d..910bf4c6b 100644 --- a/.config/quickshell/modules/bar/Media.qml +++ b/.config/quickshell/modules/bar/Media.qml @@ -9,7 +9,7 @@ import Quickshell.Services.Mpris Item { readonly property MprisPlayer activePlayer: MprisController.activePlayer - readonly property string cleanedTitle: activePlayer?.trackTitle.replace(/【[^】]*】/, "") || "No media" + readonly property string cleanedTitle: activePlayer?.trackTitle.replace(/【[^】]*】/, "") || qsTr("No media") Layout.fillHeight: true implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index bd53316a0..6ce566051 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -562,7 +562,7 @@ Item { NotificationActionButton { Layout.fillWidth: true - buttonText: "Close" + buttonText: qsTr("Close") urgency: notificationObject.urgency implicitWidth: (notificationObject.actions.length == 0) ? (actionsFlickable.width / 2) : (contentItem.implicitWidth + leftPadding + rightPadding) diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml index fcdb778f5..550cb2dc6 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml @@ -97,7 +97,7 @@ Scope { anchors.centerIn: parent value: Brightness.value icon: "light_mode" - name: "Brightness" + name: qsTr("Brightness") } } } diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml index dd7d237d7..c01b6209a 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml @@ -97,7 +97,7 @@ Scope { anchors.centerIn: parent value: Audio.sink?.audio.volume icon: "volume_up" - name: "Volume" + name: qsTr("Volume") } } } diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 6bdf17430..31f48b468 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -87,7 +87,7 @@ Scope { padding: 15 color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant selectedTextColor: Appearance.m3colors.m3onSurface - placeholderText: "Search, calculate or run" + placeholderText: qsTr("Search") placeholderTextColor: Appearance.m3colors.m3outline focus: root.visible diff --git a/.config/quickshell/modules/session/Session.qml b/.config/quickshell/modules/session/Session.qml index 50389c82f..a2be293da 100644 --- a/.config/quickshell/modules/session/Session.qml +++ b/.config/quickshell/modules/session/Session.qml @@ -92,7 +92,7 @@ Scope { font.family: Appearance.font.family.title font.pixelSize: Appearance.font.pixelSize.title font.weight: Font.DemiBold - text: "Session" + text: qsTr("Session") } StyledText { // Small instruction @@ -100,7 +100,7 @@ Scope { horizontalAlignment: Text.AlignHCenter font.family: Appearance.font.family.title font.pixelSize: Appearance.font.pixelSize.normal - text: "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel" + text: qsTr("Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel") } } @@ -110,7 +110,7 @@ Scope { id: sessionLock focus: sessionRoot.visible buttonIcon: "lock" - buttonText: "Lock" + buttonText: qsTr("Lock") onClicked: { lock.running = true; sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.right: sessionSleep @@ -120,7 +120,7 @@ Scope { id: sessionSleep focus: sessionRoot.visible buttonIcon: "dark_mode" - buttonText: "Sleep" + buttonText: qsTr("Sleep") onClicked: { sleep.running = true; sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.left: sessionLock @@ -131,7 +131,7 @@ Scope { id: sessionLogout focus: sessionRoot.visible buttonIcon: "logout" - buttonText: "Logout" + buttonText: qsTr("Logout") onClicked: { logout.running = true; sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.left: sessionSleep @@ -142,7 +142,7 @@ Scope { id: sessionTaskManager focus: sessionRoot.visible buttonIcon: "browse_activity" - buttonText: "Task Manager" + buttonText: qsTr("Task Manager") onClicked: { taskManager.running = true; sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.left: sessionLogout @@ -156,7 +156,7 @@ Scope { id: sessionHibernate focus: sessionRoot.visible buttonIcon: "downloading" - buttonText: "Hibernate" + buttonText: qsTr("Hibernate") onClicked: { hibernate.running = true; sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.up: sessionLock @@ -166,7 +166,7 @@ Scope { id: sessionShutdown focus: sessionRoot.visible buttonIcon: "power_settings_new" - buttonText: "Shutdown" + buttonText: qsTr("Shutdown") onClicked: { shutdown.running = true; sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.left: sessionHibernate @@ -177,7 +177,7 @@ Scope { id: sessionReboot focus: sessionRoot.visible buttonIcon: "restart_alt" - buttonText: "Reboot" + buttonText: qsTr("Reboot") onClicked: { reboot.running = true; sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.left: sessionShutdown @@ -188,7 +188,7 @@ Scope { id: sessionFirmwareReboot focus: sessionRoot.visible buttonIcon: "settings_applications" - buttonText: "Reboot to firmware settings" + buttonText: qsTr("Reboot to firmware settings") onClicked: { firmwareReboot.running = true; sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.up: sessionTaskManager diff --git a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml index 1cd649d68..be69cd5b1 100644 --- a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml @@ -17,7 +17,7 @@ Rectangle { color: Appearance.colors.colLayer1 property int currentTab: 0 - property var tabButtonList: [{"icon": "notifications", "name": "Notifications"}, {"icon": "volume_up", "name": "Volume mixer"}] + property var tabButtonList: [{"icon": "notifications", "name": qsTr("Notifications")}, {"icon": "volume_up", "name": qsTr("Volume mixer")}] Keys.onPressed: (event) => { if (event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) { diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 50cd24bb2..4491d3d09 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -127,7 +127,7 @@ Scope { command: ["qs", "ipc", "call", "session", "open"] } StyledToolTip { - content: "Session" + content: qsTr("Session") } } } diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml index a1925f643..eb4f62f58 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml @@ -48,7 +48,7 @@ Item { CalendarHeaderButton { clip: true buttonText: `${monthShift != 0 ? "• " : ""}${viewingDate.toLocaleDateString(Qt.locale(), "MMMM yyyy")}` - tooltipText: (monthShift === 0) ? "" : "Jump to current month" + tooltipText: (monthShift === 0) ? "" : qsTr("Jump to current month") onClicked: { monthShift = 0; } diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml index a0e1bfb96..87c62b554 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -120,7 +120,7 @@ Item { font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3outline horizontalAlignment: Text.AlignHCenter - text: "No notifications" + text: qsTr("No notifications") } } } @@ -155,7 +155,7 @@ Item { Layout.margins: 5 Layout.topMargin: 10 buttonIcon: "clear_all" - buttonText: "Clear" + buttonText: qsTr("Clear") onClicked: () => { Notifications.discardAllNotifications() } diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml index 16ef56e42..c7a24fc70 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml @@ -39,7 +39,7 @@ QuickToggleButton { } StyledToolTip { content: `${(Bluetooth.bluetoothEnabled && Bluetooth.bluetoothDeviceName.length > 0) ? - Bluetooth.bluetoothDeviceName : "Bluetooth"} | Right-click to configure` + Bluetooth.bluetoothDeviceName : "Bluetooth"} | ${qsTr("Right-click to configure")}` } } diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml b/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml index 94f8ebb6c..4818f0c42 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml @@ -25,6 +25,6 @@ QuickToggleButton { command: ['bash', '-c', `hyprctl reload`] } StyledToolTip { - content: "Game mode" + content: qsTr("Game mode") } } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml b/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml index 086836975..4ac63d220 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml @@ -15,6 +15,6 @@ QuickToggleButton { command: ["bash", "-c", "${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/scripts/wayland-idle-inhibitor.py"] } StyledToolTip { - content: "Keep system awake" + content: qsTr("Keep system awake") } } diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/NightLight.qml b/.config/quickshell/modules/sidebarRight/quickToggles/NightLight.qml index e69a81c04..260b508a6 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/NightLight.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/NightLight.qml @@ -37,6 +37,6 @@ QuickToggleButton { } } StyledToolTip { - content: "Night Light" + content: qsTr("Night Light") } } diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index ab4e54305..7464e9884 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -9,7 +9,7 @@ import Qt5Compat.GraphicalEffects Item { id: root property int currentTab: 0 - property var tabButtonList: [{"icon": "checklist", "name": "Unfinished"}, {"name": "Done", "icon": "check_circle"}] + property var tabButtonList: [{"icon": "checklist", "name": qsTr("Unfinished")}, {"name": qsTr("Done"), "icon": "check_circle"}] property bool showAddDialog: false property int dialogMargins: 20 property int fabSize: 48 @@ -126,7 +126,7 @@ Item { TaskList { listBottomPadding: root.fabSize + root.fabMargins * 2 emptyPlaceholderIcon: "check_circle" - emptyPlaceholderText: "Nothing here!" + emptyPlaceholderText: qsTr("Nothing here!") taskList: Todo.list .map(function(item, i) { return Object.assign({}, item, {originalIndex: i}); }) .filter(function(item) { return !item.done; }) @@ -134,7 +134,7 @@ Item { TaskList { listBottomPadding: root.fabSize + root.fabMargins * 2 emptyPlaceholderIcon: "checklist" - emptyPlaceholderText: "Finished tasks will go here" + emptyPlaceholderText: qsTr("Finished tasks will go here") taskList: Todo.list .map(function(item, i) { return Object.assign({}, item, {originalIndex: i}); }) .filter(function(item) { return item.done; }) @@ -263,7 +263,7 @@ Item { Layout.alignment: Qt.AlignLeft color: Appearance.m3colors.m3onSurface font.pixelSize: Appearance.font.pixelSize.larger - text: "Add task" + text: qsTr("Add task") } TextField { @@ -274,7 +274,7 @@ Item { padding: 10 color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant selectedTextColor: Appearance.m3colors.m3onSurface - placeholderText: "Task description" + placeholderText: qsTr("Task description") placeholderTextColor: Appearance.m3colors.m3outline focus: root.showAddDialog onAccepted: dialog.addTask() @@ -302,11 +302,11 @@ Item { spacing: 5 DialogButton { - buttonText: "Cancel" + buttonText: qsTr("Cancel") onClicked: root.showAddDialog = false } DialogButton { - buttonText: "Add" + buttonText: qsTr("Add") enabled: todoInput.text.length > 0 onClicked: dialog.addTask() } diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml index 1e235adea..cdb89ed27 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml @@ -50,14 +50,14 @@ Button { Layout.fillWidth: true elide: Text.ElideRight font.pixelSize: Appearance.font.pixelSize.normal - text: input ? "Input" : "Output" + text: input ? qsTr("Input") : qsTr("Output") color: Appearance.colors.colOnLayer2 } StyledText { Layout.fillWidth: true elide: Text.ElideRight font.pixelSize: Appearance.font.pixelSize.smaller - text: (input ? Pipewire.defaultAudioSource?.description : Pipewire.defaultAudioSink?.description) ?? "Unknown" + text: (input ? Pipewire.defaultAudioSource?.description : Pipewire.defaultAudioSink?.description) ?? qsTr("Unknown") color: Appearance.m3colors.m3outline } } diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml index 5656cc908..cc5a6a4a9 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml @@ -106,7 +106,7 @@ Item { font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3outline horizontalAlignment: Text.AlignHCenter - text: "No audio source" + text: qsTr("No audio source") } } } @@ -341,13 +341,13 @@ Item { Layout.alignment: Qt.AlignRight DialogButton { - buttonText: "Cancel" + buttonText: qsTr("Cancel") onClicked: { root.showDeviceSelector = false } } DialogButton { - buttonText: "OK" + buttonText: qsTr("OK") onClicked: { root.showDeviceSelector = false if (root.selectedDevice) { diff --git a/.config/quickshell/services/MprisController.qml b/.config/quickshell/services/MprisController.qml index aa9f6ac7a..00ef982fc 100644 --- a/.config/quickshell/services/MprisController.qml +++ b/.config/quickshell/services/MprisController.qml @@ -79,9 +79,9 @@ Singleton { this.activeTrack = { uniqueId: this.activePlayer?.uniqueId ?? 0, artUrl: this.activePlayer?.trackArtUrl ?? "", - title: this.activePlayer?.trackTitle || "Unknown Title", - artist: this.activePlayer?.trackArtist || "Unknown Artist", - album: this.activePlayer?.trackAlbum || "Unknown Album", + title: this.activePlayer?.trackTitle || qsTr("Unknown Title"), + artist: this.activePlayer?.trackArtist || qsTr("Unknown Artist"), + album: this.activePlayer?.trackAlbum || qsTr("Unknown Album"), }; this.trackChanged(__reverse); From 72ccce04c664202b820287f97260b89e2cf879cb Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 24 Apr 2025 22:36:47 +0200 Subject: [PATCH 092/824] overview: app search --- .../quickshell/modules/common/Appearance.qml | 1 + .../quickshell/modules/overview/Overview.qml | 38 +---- .../modules/overview/OverviewWidget.qml | 4 +- .../modules/overview/SearchItem.qml | 93 ++++++++++++ .../modules/overview/SearchWidget.qml | 135 ++++++++++++++++++ 5 files changed, 238 insertions(+), 33 deletions(-) create mode 100644 .config/quickshell/modules/overview/SearchItem.qml create mode 100644 .config/quickshell/modules/overview/SearchWidget.qml diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 284aca6de..32133ed7a 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -197,6 +197,7 @@ Singleton { property int barPreferredSideSectionWidth: 400 property int sidebarWidth: 450 property int notificationPopupWidth: 410 + property int searchWidth: 450 property int hyprlandGapsOut: 5 property int elevationMargin: 7 property int fabShadowRadius: 5 diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 31f48b468..7a7599166 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -24,6 +24,7 @@ Scope { WlrLayershell.namespace: "quickshell:overview" WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive color: "transparent" mask: Region { @@ -40,7 +41,7 @@ Scope { HyprlandFocusGrab { id: grab windows: [ root ] - active: false + active: GlobalStates.overviewOpen onCleared: () => { if (!active) GlobalStates.overviewOpen = false } @@ -80,42 +81,17 @@ Scope { width: 1 // Prevent Wayland protocol error } - TextField { - id: searchInput - + SearchWidget { + panelWindow: root Layout.alignment: Qt.AlignHCenter - padding: 15 - color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant - selectedTextColor: Appearance.m3colors.m3onSurface - placeholderText: qsTr("Search") - placeholderTextColor: Appearance.m3colors.m3outline - focus: root.visible - - onTextChanged: root.searchingText = text - Connections { - target: root - function onVisibleChanged() { - searchInput.selectAll() - root.searchingText = "" - } - } - - background: Rectangle { - anchors.fill: parent - radius: Appearance.rounding.normal - color: Appearance.colors.colLayer0 - } - - cursorDelegate: Rectangle { - width: 1 - color: searchInput.activeFocus ? Appearance.m3colors.m3primary : "transparent" - radius: 1 + onSearchingTextChanged: (text) => { + root.searchingText = searchingText } } OverviewWidget { + panelWindow: root visible: (root.searchingText == "") - bar: root } } diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index 829d7d653..f586af2e1 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -13,8 +13,8 @@ import "./icons.js" as Icons Item { id: root - required property var bar - readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) + required property var panelWindow + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen) readonly property var toplevels: ToplevelManager.toplevels readonly property int workspacesShown: ConfigOptions.overview.numOfRows * ConfigOptions.overview.numOfCols readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / workspacesShown) diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml new file mode 100644 index 000000000..1e67290a8 --- /dev/null +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -0,0 +1,93 @@ +// pragma NativeMethodBehavior: AcceptThisObject +import "root:/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Widgets + +Button { + id: root + property DesktopEntry desktopEntry + property string itemName: desktopEntry?.name + property string itemIcon: desktopEntry?.icon + property var itemExecute: desktopEntry?.execute + property string itemClickActionName: desktopEntry?.clickActionName + + property int horizontalMargin: 10 + property int buttonHorizontalPadding: 10 + property int buttonVerticalPadding: 5 + property bool keyboardDown: false + + anchors.left: parent?.left + anchors.right: parent?.right + implicitHeight: rowLayout.implicitHeight + root.buttonVerticalPadding * 2 + implicitWidth: rowLayout.implicitWidth + root.buttonHorizontalPadding * 2 + + PointingHandInteraction {} + onClicked: { + root.itemExecute() + closeOverview.running = true + } + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + root.keyboardDown = true + root.clicked() + event.accepted = true; + } + } + Keys.onReleased: (event) => { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + root.keyboardDown = false + event.accepted = true; + } + } + + background: Rectangle { + anchors.fill: parent + anchors.leftMargin: root.horizontalMargin + anchors.rightMargin: root.horizontalMargin + radius: Appearance.rounding.small + color: (root.down || root.keyboardDown) ? Appearance.colors.colLayer1Active : ((root.hovered || root.focus) ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.m3colors.m3surfaceContainerHigh, 1)) + } + + RowLayout { + id: rowLayout + spacing: 10 + anchors.fill: parent + anchors.leftMargin: root.horizontalMargin + root.buttonHorizontalPadding + anchors.rightMargin: root.horizontalMargin + root.buttonHorizontalPadding + + IconImage { + source: Quickshell.iconPath(root.itemIcon); + width: 35 + height: 35 + } + StyledText { + Layout.fillWidth: true + id: nameText + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3onSurface + horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight + text: root.itemName + } + StyledText { + Layout.fillWidth: false + visible: (root.hovered || root.focus) + id: clickAction + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colSubtext + horizontalAlignment: Text.AlignRight + text: root.itemClickActionName + } + } + + Process { + id: closeOverview + command: ["bash", "-c", "qs ipc call overview close &"] // Somehow has to be async to work? + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml new file mode 100644 index 000000000..a3cf41d95 --- /dev/null +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -0,0 +1,135 @@ +import "root:/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io + +Item { // Wrapper + id: root + required property var panelWindow + property string searchingText: "" + property bool showResults: searchingText != "" + property real searchBarHeight: searchBar.height + Appearance.sizes.elevationMargin * 2 + implicitWidth: searchWidgetContent.implicitWidth + Appearance.sizes.elevationMargin * 2 + implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2 + + Rectangle { // Background + id: searchWidgetContent + anchors.centerIn: parent + implicitWidth: columnLayout.implicitWidth + implicitHeight: columnLayout.implicitHeight + radius: Appearance.rounding.large + color: Appearance.colors.colLayer0 + + ColumnLayout { + id: columnLayout + anchors.centerIn: parent + spacing: 0 + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: searchWidgetContent.width + height: searchWidgetContent.width + radius: searchWidgetContent.radius + } + } + + RowLayout { + id: searchBar + spacing: 5 + KeyNavigation.down: appResults + MaterialSymbol { + id: searchIcon + Layout.leftMargin: 15 + font.pixelSize: Appearance.font.pixelSize.huge + color: Appearance.m3colors.m3onSurface + text: "search" + } + TextField { // Search box + id: searchInput + + padding: 15 + Layout.rightMargin: 15 + color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant + selectedTextColor: Appearance.m3colors.m3onSurface + placeholderText: qsTr("Search") + placeholderTextColor: Appearance.m3colors.m3outline + focus: root.panelWindow.visible || GlobalStates.overviewOpen + + implicitWidth: Appearance.sizes.searchWidth + + onTextChanged: root.searchingText = text + Connections { + target: root + function onVisibleChanged() { + searchInput.selectAll() + root.searchingText = "" + } + } + + background: Item {} + + cursorDelegate: Rectangle { + width: 1 + color: searchInput.activeFocus ? Appearance.m3colors.m3primary : "transparent" + radius: 1 + } + } + } + + Rectangle { // Separator + visible: root.showResults + Layout.fillWidth: true + height: 1 + color: Appearance.m3colors.m3outline + } + + ListView { // App results + id: appResults + visible: root.showResults + Layout.fillWidth: true + implicitHeight: 600 + clip: true + topMargin: 10 + bottomMargin: 10 + spacing: 0 + KeyNavigation.up: searchBar + + model: ScriptModel { + id: model; + values: DesktopEntries.applications.values + .filter((entry) => { + if (root.searchingText == "") return false + return entry.name.toLowerCase().includes(root.searchingText.toLowerCase()) + }) + .map((entry) => { + entry.clickActionName = "Launch"; + return entry; + }) + } + delegate: SearchItem { + desktopEntry: modelData + // itemName: modelData.name + // itemIcon: modelData.icon + } + } + + } + } + + DropShadow { + id: searchWidgetShadow + anchors.fill: searchWidgetContent + source: searchWidgetContent + radius: Appearance.sizes.elevationMargin + samples: radius * 2 + 1 + color: Appearance.colors.colShadow + verticalOffset: 2 + horizontalOffset: 0 + } +} \ No newline at end of file From ebb831d3455bd40f6875e7b1e00d4f95af193ddb Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 24 Apr 2025 23:02:05 +0200 Subject: [PATCH 093/824] app search qol: enter first item, entry always respond to char keys --- .../modules/overview/SearchItem.qml | 2 +- .../modules/overview/SearchWidget.qml | 35 ++++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index 1e67290a8..e024fae9e 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -50,7 +50,7 @@ Button { anchors.fill: parent anchors.leftMargin: root.horizontalMargin anchors.rightMargin: root.horizontalMargin - radius: Appearance.rounding.small + radius: Appearance.rounding.normal color: (root.down || root.keyboardDown) ? Appearance.colors.colLayer1Active : ((root.hovered || root.focus) ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.m3colors.m3surfaceContainerHigh, 1)) } diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index a3cf41d95..d7d7c25a9 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -17,6 +17,21 @@ Item { // Wrapper implicitWidth: searchWidgetContent.implicitWidth + Appearance.sizes.elevationMargin * 2 implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2 + Keys.onPressed: { + // Only handle printable characters (ignore modifiers, arrows, etc.) + if (event.text && event.text.length === 1 && event.key !== Qt.Key_Enter && event.key !== Qt.Key_Return) { + if (!searchInput.activeFocus) { + searchInput.forceActiveFocus(); + // Insert the character at the cursor position + searchInput.text = searchInput.text.slice(0, searchInput.cursorPosition) + + event.text + + searchInput.text.slice(searchInput.cursorPosition); + searchInput.cursorPosition += 1; + event.accepted = true; + } + } + } + Rectangle { // Background id: searchWidgetContent anchors.centerIn: parent @@ -53,16 +68,16 @@ Item { // Wrapper TextField { // Search box id: searchInput - padding: 15 + focus: root.panelWindow.visible || GlobalStates.overviewOpen Layout.rightMargin: 15 + padding: 15 color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant selectedTextColor: Appearance.m3colors.m3onSurface placeholderText: qsTr("Search") placeholderTextColor: Appearance.m3colors.m3outline - focus: root.panelWindow.visible || GlobalStates.overviewOpen - implicitWidth: Appearance.sizes.searchWidth + onTextChanged: root.searchingText = text Connections { target: root @@ -72,6 +87,16 @@ Item { // Wrapper } } + onAccepted: { + if (appResults.count > 0) { + // Get the first visible delegate and trigger its click + let firstItem = appResults.itemAtIndex(0); + if (firstItem && firstItem.clicked) { + firstItem.clicked(); + } + } + } + background: Item {} cursorDelegate: Rectangle { @@ -93,7 +118,7 @@ Item { // Wrapper id: appResults visible: root.showResults Layout.fillWidth: true - implicitHeight: 600 + implicitHeight: Math.min(600, appResults.contentHeight + topMargin + bottomMargin) clip: true topMargin: 10 bottomMargin: 10 @@ -101,7 +126,7 @@ Item { // Wrapper KeyNavigation.up: searchBar model: ScriptModel { - id: model; + id: model values: DesktopEntries.applications.values .filter((entry) => { if (root.searchingText == "") return false From 3c10a074dae0970aef0ea76a9d0b91c998a0cb6f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 24 Apr 2025 23:17:26 +0200 Subject: [PATCH 094/824] Update README.md --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index af4a74e69..f5cd66bbb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,14 @@ -# QUICKSHELL, WIP, DO NOT USE +# Quickshell illogical-impulse +## Feel free to try it - here are the steps + +- Assumption: You are already using the AGS illogical-impulse +- Install Qt packages (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-graphicaleffects qt5-imageformats qt5-quickcontrols qt5-quickcontrols2 qt5-svg qt5-translations qt5-wayland qt5-x11extras qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland qt6-webchannel qt6-webengine qt6-websockets qt6-webview` +- Then install quickshell: `yay -S quickshell` +- Copy `.config/quickshell` folder and hyprland keybind file (or just manually copy over the lines containing `qs ipc call`) (backing up is your responsibility) +- Run quickshell with `qs` and enjoy... + - We currently have bar, right sidebar, search/overview + - Tips: scrolled windows are flickable -i hope ill actually properly learn quickshell this time

【 end_4's Hyprland dotfiles 】

From e34a2494dd6d9955369d4726990a953bb5304925 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 25 Apr 2025 16:07:50 +0200 Subject: [PATCH 095/824] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f5cd66bbb..833662708 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# Quickshell illogical-impulse -## Feel free to try it - here are the steps +# Quickshell-powered illogical-impulse +## Not ready, but feel free to try it. It's simple: -- Assumption: You are already using the AGS illogical-impulse -- Install Qt packages (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-graphicaleffects qt5-imageformats qt5-quickcontrols qt5-quickcontrols2 qt5-svg qt5-translations qt5-wayland qt5-x11extras qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland qt6-webchannel qt6-webengine qt6-websockets qt6-webview` -- Then install quickshell: `yay -S quickshell` -- Copy `.config/quickshell` folder and hyprland keybind file (or just manually copy over the lines containing `qs ipc call`) (backing up is your responsibility) -- Run quickshell with `qs` and enjoy... +- **Assumption**: You are already using the AGS illogical-impulse +- **Install Qt packages** (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-graphicaleffects qt5-imageformats qt5-quickcontrols qt5-quickcontrols2 qt5-svg qt5-translations qt5-wayland qt5-x11extras qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland qt6-webchannel qt6-webengine qt6-websockets qt6-webview` +- **Install quickshell**: `yay -S quickshell` +- **Copy** `.config/quickshell` folder and hyprland config files in `.config/hypr/hyprland/` (the files are still AGS-friendly and shouldn't affect your use of the normal illogical-impulse) (still, backing up is your responsibility) +- **Run quickshell** with `qs` and see how things are - it's not finished for daily use, but **feedback is very welcome** - We currently have bar, right sidebar, search/overview - Tips: scrolled windows are flickable From e1359116b8e0f491a65f73b628431453d0c90d10 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:02:40 +0200 Subject: [PATCH 096/824] better support for workspace 10000 --- .config/quickshell/modules/bar/Workspaces.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 8e69b94f2..f1311ec79 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -174,7 +174,7 @@ Item { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - font.pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== "10")) + font.pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== "10") * 2) text: `${workspaceValue}` elide: Text.ElideRight color: (monitor.activeWorkspace?.id == workspaceValue) ? Appearance.m3colors.m3onPrimary : (workspaceOccupied[index] ? Appearance.colors.colOnLayer1 : Appearance.colors.colOnLayer1Inactive) From 63c844cdebe06d444e63239befc880ba21677bbc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 25 Apr 2025 20:35:37 +0200 Subject: [PATCH 097/824] overview: search --- .../quickshell/modules/common/Appearance.qml | 1 + .../modules/common/ConfigOptions.qml | 6 + .../modules/overview/SearchItem.qml | 55 ++++-- .../modules/overview/SearchWidget.qml | 181 ++++++++++++++++-- 4 files changed, 214 insertions(+), 29 deletions(-) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 32133ed7a..fecdfb8e7 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -197,6 +197,7 @@ Singleton { property int barPreferredSideSectionWidth: 400 property int sidebarWidth: 450 property int notificationPopupWidth: 410 + property int searchWidthCollapsed: 260 property int searchWidth: 450 property int hyprlandGapsOut: 5 property int elevationMargin: 7 diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index b75bb7a0c..858d211f7 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -41,6 +41,12 @@ Singleton { property int updateInterval: 3000 } + property QtObject search: QtObject { + property int nonAppResultDelay: 30 // This prevents lagging when typing + property string engineBaseUrl: "https://www.google.com/search?q=" + property list excludedSites: [ "quora.com" ] + } + property QtObject hacks: QtObject { property int arbitraryRaceConditionDelay: 10 // milliseconds } diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index e024fae9e..1f874752b 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -11,12 +11,17 @@ import Quickshell.Widgets Button { id: root - property DesktopEntry desktopEntry - property string itemName: desktopEntry?.name - property string itemIcon: desktopEntry?.icon - property var itemExecute: desktopEntry?.execute - property string itemClickActionName: desktopEntry?.clickActionName + property var entry + property bool entryShown: entry?.shown ?? true + property string itemType: entry?.type + property string itemName: entry?.name + property string itemIcon: entry?.icon ?? "" + property var itemExecute: entry?.execute + property string fontType: entry?.fontType ?? "main" + property string itemClickActionName: entry?.clickActionName + property string materialSymbol: entry?.materialSymbol ?? "" + visible: root.entryShown property int horizontalMargin: 10 property int buttonHorizontalPadding: 10 property int buttonVerticalPadding: 5 @@ -61,20 +66,46 @@ Button { anchors.leftMargin: root.horizontalMargin + root.buttonHorizontalPadding anchors.rightMargin: root.horizontalMargin + root.buttonHorizontalPadding + // Icon IconImage { + visible: root.materialSymbol == "" source: Quickshell.iconPath(root.itemIcon); width: 35 height: 35 } - StyledText { - Layout.fillWidth: true - id: nameText - font.pixelSize: Appearance.font.pixelSize.normal + MaterialSymbol { + visible: root.materialSymbol != "" + text: root.materialSymbol + font.pixelSize: 30 color: Appearance.m3colors.m3onSurface - horizontalAlignment: Text.AlignLeft - elide: Text.ElideRight - text: root.itemName + // width: 35 + // height: 35 } + + // Main text + ColumnLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + spacing: 0 + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.colors.colSubtext + visible: root.itemType && root.itemType != "App" + text: root.itemType + } + StyledText { + Layout.fillWidth: true + id: nameText + font.pixelSize: Appearance.font.pixelSize.normal + font.family: Appearance.font.family[root.fontType] + color: Appearance.m3colors.m3onSurface + horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight + text: root.itemName + } + } + + // Action text StyledText { Layout.fillWidth: false visible: (root.hovered || root.focus) diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index d7d7c25a9..801a9208e 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -17,15 +17,106 @@ Item { // Wrapper implicitWidth: searchWidgetContent.implicitWidth + Appearance.sizes.elevationMargin * 2 implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2 - Keys.onPressed: { - // Only handle printable characters (ignore modifiers, arrows, etc.) - if (event.text && event.text.length === 1 && event.key !== Qt.Key_Enter && event.key !== Qt.Key_Return) { + property string mathResult: "" + + Timer { + id: nonAppResultsTimer + interval: ConfigOptions.search.nonAppResultDelay + onTriggered: { + mathProcess.calculateExpression(root.searchingText); + } + } + + Process { + id: mathProcess + property list baseCommand: ["qalc", "-t"] + function calculateExpression(expression) { + // mathProcess.running = false + mathProcess.command = baseCommand.concat(expression) + mathProcess.running = true + } + stdout: SplitParser { + onRead: data => { + root.mathResult = data + if (searchInput.focus) appResults.currentIndex = 0; // Focus the first item + } + } + } + + Process { + id: copyText + property list baseCommand: ["wl-copy"] + function copyTextToClipboard(text) { + copyText.running = false + copyText.command = baseCommand.concat(text) + copyText.running = true + } + } + + Process { + id: webSearch + property list baseCommand: ["xdg-open"] + function search(query) { + webSearch.running = false + let url = ConfigOptions.search.engineBaseUrl + query + for (let site of ConfigOptions.search.excludedSites) { + url += ` -site:${site}`; + } + webSearch.command = baseCommand.concat(url) + webSearch.running = true + } + } + + Keys.onPressed: (event) => { + // Prevent Esc and Backspace from registering + if (event.key === Qt.Key_Escape) return; + + // Handle Backspace: focus and delete character if not focused + if (event.key === Qt.Key_Backspace) { + if (!searchInput.activeFocus) { + searchInput.forceActiveFocus(); + if (event.modifiers & Qt.ControlModifier) { + // Delete word before cursor + let text = searchInput.text; + let pos = searchInput.cursorPosition; + if (pos > 0) { + // Find the start of the previous word + let left = text.slice(0, pos); + let match = left.match(/(\s*\S+)\s*$/); + let deleteLen = match ? match[0].length : 1; + searchInput.text = text.slice(0, pos - deleteLen) + text.slice(pos); + searchInput.cursorPosition = pos - deleteLen; + } + } else { + // Delete character before cursor if any + if (searchInput.cursorPosition > 0) { + searchInput.text = searchInput.text.slice(0, searchInput.cursorPosition - 1) + + searchInput.text.slice(searchInput.cursorPosition); + searchInput.cursorPosition -= 1; + } + } + // Always move cursor to end after programmatic edit + searchInput.cursorPosition = searchInput.text.length; + event.accepted = true; + } + // If already focused, let TextField handle it + return; + } + + // Only handle visible printable characters (ignore control chars, arrows, etc.) + if ( + event.text && + event.text.length === 1 && + event.key !== Qt.Key_Enter && + event.key !== Qt.Key_Return && + event.text.charCodeAt(0) >= 0x20 // ignore control chars like Backspace, Tab, etc. + ) { if (!searchInput.activeFocus) { searchInput.forceActiveFocus(); // Insert the character at the cursor position searchInput.text = searchInput.text.slice(0, searchInput.cursorPosition) + - event.text + - searchInput.text.slice(searchInput.cursorPosition); + event.text + + searchInput.text.slice(searchInput.cursorPosition); searchInput.cursorPosition += 1; event.accepted = true; } @@ -57,7 +148,12 @@ Item { // Wrapper RowLayout { id: searchBar spacing: 5 - KeyNavigation.down: appResults + KeyNavigation.down: { + if (appResults.count > 1) { + appResults.currentIndex = 1; + appResults.forceActiveFocus(); + } + } MaterialSymbol { id: searchIcon Layout.leftMargin: 15 @@ -75,8 +171,14 @@ Item { // Wrapper selectedTextColor: Appearance.m3colors.m3onSurface placeholderText: qsTr("Search") placeholderTextColor: Appearance.m3colors.m3outline - implicitWidth: Appearance.sizes.searchWidth + implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth + Behavior on implicitWidth { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } onTextChanged: root.searchingText = text Connections { @@ -125,20 +227,65 @@ Item { // Wrapper spacing: 0 KeyNavigation.up: searchBar + Connections { + target: root + function onSearchingTextChanged() { + if (appResults.count > 0) + appResults.currentIndex = 0; + } + } + model: ScriptModel { id: model - values: DesktopEntries.applications.values - .filter((entry) => { - if (root.searchingText == "") return false - return entry.name.toLowerCase().includes(root.searchingText.toLowerCase()) - }) - .map((entry) => { - entry.clickActionName = "Launch"; - return entry; - }) + values: { + if(root.searchingText == "") return []; + + // Start math and other non-app stuff + nonAppResultsTimer.restart(); + + // Init result array + let result = []; + + // Add filtered application entries + result = result.concat( + DesktopEntries.applications.values + .filter((entry) => { + if (root.searchingText == "") return false + return entry.name.toLowerCase().includes(root.searchingText.toLowerCase()) + }) + .map((entry) => { + entry.clickActionName = "Launch"; + entry.type = "App" + return entry; + }) + ); + + // Add non-app results + result.push({ + name: root.mathResult, + clickActionName: "Copy", + type: qsTr("Math result"), + fontType: "monospace", + materialSymbol: 'calculate', + execute: () => { + copyText.copyTextToClipboard(root.mathResult); + } + }); + result.push({ + name: root.searchingText, + clickActionName: "Search", + type: "Search the web", + materialSymbol: 'travel_explore', + execute: () => { + webSearch.search(root.searchingText); + } + }); + + return result; + } } delegate: SearchItem { - desktopEntry: modelData + entry: modelData // itemName: modelData.name // itemIcon: modelData.icon } From e691fdcc59747bee3886e386a7d13aa6fe3b8e15 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 25 Apr 2025 22:01:19 +0200 Subject: [PATCH 098/824] search: run command --- .../modules/overview/SearchWidget.qml | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 801a9208e..19cf5f459 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -19,6 +19,10 @@ Item { // Wrapper property string mathResult: "" + function focusFirstItemIfNeeded() { + if (searchInput.focus) appResults.currentIndex = 0; // Focus the first item + } + Timer { id: nonAppResultsTimer interval: ConfigOptions.search.nonAppResultDelay @@ -38,7 +42,7 @@ Item { // Wrapper stdout: SplitParser { onRead: data => { root.mathResult = data - if (searchInput.focus) appResults.currentIndex = 0; // Focus the first item + root.focusFirstItemIfNeeded() } } } @@ -67,6 +71,18 @@ Item { // Wrapper } } + Process { + id: executor + property list baseCommand: ["bash", "-c"] + function executeCommand(command) { + executor.running = false + executor.command = baseCommand.concat( + `${command} || ${ConfigOptions.apps.terminal} fish -C 'echo "${qsTr("Searching for package with that command")}..." && pacman -F ${command}'` + ) + executor.running = true + } + } + Keys.onPressed: (event) => { // Prevent Esc and Backspace from registering if (event.key === Qt.Key_Escape) return; @@ -148,12 +164,6 @@ Item { // Wrapper RowLayout { id: searchBar spacing: 5 - KeyNavigation.down: { - if (appResults.count > 1) { - appResults.currentIndex = 1; - appResults.forceActiveFocus(); - } - } MaterialSymbol { id: searchIcon Layout.leftMargin: 15 @@ -169,10 +179,17 @@ Item { // Wrapper padding: 15 color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant selectedTextColor: Appearance.m3colors.m3onSurface - placeholderText: qsTr("Search") + placeholderText: qsTr("Search, calculate or run") placeholderTextColor: Appearance.m3colors.m3outline implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth + KeyNavigation.down: { + if (appResults.count > 1) { + appResults.focus = true; + appResults.currentIndex = 1; + } + } + Behavior on implicitWidth { NumberAnimation { duration: Appearance.animation.elementDecelFast.duration @@ -271,6 +288,16 @@ Item { // Wrapper copyText.copyTextToClipboard(root.mathResult); } }); + result.push({ + name: searchingText, + clickActionName: "Run", + type: qsTr("Run command"), + fontType: "monospace", + materialSymbol: 'terminal', + execute: () => { + executor.executeCommand(searchingText.startsWith('sudo') ? `${ConfigOptions.apps.terminal} fish -C '${root.searchingText}'` : root.searchingText); + } + }); result.push({ name: root.searchingText, clickActionName: "Search", From 98547ba83705a5c0a0126ffff57e942ec41f85fa Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 25 Apr 2025 22:10:16 +0200 Subject: [PATCH 099/824] search: fix space confirming choice --- .config/quickshell/modules/overview/SearchWidget.qml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 19cf5f459..b566f1b32 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -183,13 +183,6 @@ Item { // Wrapper placeholderTextColor: Appearance.m3colors.m3outline implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth - KeyNavigation.down: { - if (appResults.count > 1) { - appResults.focus = true; - appResults.currentIndex = 1; - } - } - Behavior on implicitWidth { NumberAnimation { duration: Appearance.animation.elementDecelFast.duration @@ -244,6 +237,10 @@ Item { // Wrapper spacing: 0 KeyNavigation.up: searchBar + onFocusChanged: { + if(focus) appResults.currentIndex = 1; + } + Connections { target: root function onSearchingTextChanged() { From 5e9a6bf96533d4cace9100359a1cd2bb67433296 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 26 Apr 2025 09:22:28 +0200 Subject: [PATCH 100/824] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 833662708..2e9c2d4d5 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,16 @@ - **Assumption**: You are already using the AGS illogical-impulse - **Install Qt packages** (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-graphicaleffects qt5-imageformats qt5-quickcontrols qt5-quickcontrols2 qt5-svg qt5-translations qt5-wayland qt5-x11extras qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland qt6-webchannel qt6-webengine qt6-websockets qt6-webview` -- **Install quickshell**: `yay -S quickshell` +- **Install quickshell and more stuff**: `yay -S quickshell matugen-bin` - **Copy** `.config/quickshell` folder and hyprland config files in `.config/hypr/hyprland/` (the files are still AGS-friendly and shouldn't affect your use of the normal illogical-impulse) (still, backing up is your responsibility) - **Run quickshell** with `qs` and see how things are - it's not finished for daily use, but **feedback is very welcome** - We currently have bar, right sidebar, search/overview - Tips: scrolled windows are flickable +## Notes +- Gradience will no longer be needed +

【 end_4's Hyprland dotfiles 】

From a9c40bc86d33137bf8a98d8d815c3cdcdeaebc46 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 26 Apr 2025 14:17:13 +0200 Subject: [PATCH 101/824] color generation --- .config/matugen/config.toml | 31 +++ .config/matugen/templates/colors.json | 51 +++++ .config/matugen/templates/fuzzel/fuzzel.ini | 21 ++ .config/matugen/templates/gtk/gtk-colors.css | 22 +++ .../matugen/templates/hyprland/colors.conf | 32 ++++ .../matugen/templates/hyprland/hyprlock.conf | 83 ++++++++ .../modules/common/ConfigOptions.qml | 3 + .../modules/common/widgets/RoundCorner.qml | 4 + .../modules/overview/SearchWidget.qml | 59 +++++- .../modules/sidebarRight/SidebarRight.qml | 10 +- .../modules/sidebarRight/todo/TodoWidget.qml | 3 +- .config/quickshell/scripts/applycolor.sh | 58 ++++++ .../scripts/generate_colors_material.py | 181 ++++++++++++++++++ .config/quickshell/scripts/kvantum/adwsvg.py | 79 ++++++++ .../quickshell/scripts/kvantum/adwsvgDark.py | 87 +++++++++ .../scripts/kvantum/changeAdwColors.py | 71 +++++++ .../quickshell/scripts/kvantum/materialQT.sh | 42 ++++ .config/quickshell/scripts/switchwall.sh | 137 +++++++++++++ .../scripts/terminal/scheme-base.json | 38 ++++ .../quickshell/scripts/terminal/sequences.txt | 1 + .config/quickshell/services/MaterialTheme.qml | 53 +++++ .config/quickshell/shell.qml | 6 + 22 files changed, 1065 insertions(+), 7 deletions(-) create mode 100644 .config/matugen/config.toml create mode 100644 .config/matugen/templates/colors.json create mode 100644 .config/matugen/templates/fuzzel/fuzzel.ini create mode 100644 .config/matugen/templates/gtk/gtk-colors.css create mode 100644 .config/matugen/templates/hyprland/colors.conf create mode 100644 .config/matugen/templates/hyprland/hyprlock.conf create mode 100755 .config/quickshell/scripts/applycolor.sh create mode 100755 .config/quickshell/scripts/generate_colors_material.py create mode 100644 .config/quickshell/scripts/kvantum/adwsvg.py create mode 100644 .config/quickshell/scripts/kvantum/adwsvgDark.py create mode 100644 .config/quickshell/scripts/kvantum/changeAdwColors.py create mode 100755 .config/quickshell/scripts/kvantum/materialQT.sh create mode 100755 .config/quickshell/scripts/switchwall.sh create mode 100644 .config/quickshell/scripts/terminal/scheme-base.json create mode 100644 .config/quickshell/scripts/terminal/sequences.txt create mode 100644 .config/quickshell/services/MaterialTheme.qml diff --git a/.config/matugen/config.toml b/.config/matugen/config.toml new file mode 100644 index 000000000..f56c1a9aa --- /dev/null +++ b/.config/matugen/config.toml @@ -0,0 +1,31 @@ +[config] +version_check = false + +[config.wallpaper] +command = "swww" +arguments = ["img", "--transition-step", "100", "--transition-fps", "120", "--transition-type", "grow", "--transition-angle", "30", "--transition-duration", "1"] + +[templates.m3colors] +input_path = '~/.config/matugen/templates/colors.json' +output_path = '~/.local/state/quickshell/user/generated/colors.json' + +[templates.hyprland] +input_path = '~/.config/matugen/templates/hyprland/colors.conf' +output_path = '~/.config/hypr/hyprland/hyprland/colors.conf' + +[templates.hyprlock] +input_path = '~/.config/matugen/templates/hyprland/hyprlock.conf' +output_path = '~/.config/hypr/hyprlock.conf' + +[templates.fuzzel] +input_path = '~/.config/matugen/templates/fuzzel/fuzzel.ini' +output_path = '~/.config/fuzzel/fuzzel.ini' + +[templates.gtk3] +input_path = '~/.config/matugen/templates/gtk/gtk-colors.css' +output_path = '~/.config/gtk-3.0/gtk.css' + +[templates.gtk4] +input_path = '~/.config/matugen/templates/gtk/gtk-colors.css' +output_path = '~/.config/gtk-4.0/gtk.css' + diff --git a/.config/matugen/templates/colors.json b/.config/matugen/templates/colors.json new file mode 100644 index 000000000..25f80e836 --- /dev/null +++ b/.config/matugen/templates/colors.json @@ -0,0 +1,51 @@ +{ + "background": "{{colors.background.default.hex}}", + "error": "{{colors.error.default.hex}}", + "error_container": "{{colors.error_container.default.hex}}", + "inverse_on_surface": "{{colors.inverse_on_surface.default.hex}}", + "inverse_primary": "{{colors.inverse_primary.default.hex}}", + "inverse_surface": "{{colors.inverse_surface.default.hex}}", + "on_background": "{{colors.on_background.default.hex}}", + "on_error": "{{colors.on_error.default.hex}}", + "on_error_container": "{{colors.on_error_container.default.hex}}", + "on_primary": "{{colors.on_primary.default.hex}}", + "on_primary_container": "{{colors.on_primary_container.default.hex}}", + "on_primary_fixed": "{{colors.on_primary_fixed.default.hex}}", + "on_primary_fixed_variant": "{{colors.on_primary_fixed_variant.default.hex}}", + "on_secondary": "{{colors.on_secondary.default.hex}}", + "on_secondary_container": "{{colors.on_secondary_container.default.hex}}", + "on_secondary_fixed": "{{colors.on_secondary_fixed.default.hex}}", + "on_secondary_fixed_variant": "{{colors.on_secondary_fixed_variant.default.hex}}", + "on_surface": "{{colors.on_surface.default.hex}}", + "on_surface_variant": "{{colors.on_surface_variant.default.hex}}", + "on_tertiary": "{{colors.on_tertiary.default.hex}}", + "on_tertiary_container": "{{colors.on_tertiary_container.default.hex}}", + "on_tertiary_fixed": "{{colors.on_tertiary_fixed.default.hex}}", + "on_tertiary_fixed_variant": "{{colors.on_tertiary_fixed_variant.default.hex}}", + "outline": "{{colors.outline.default.hex}}", + "outline_variant": "{{colors.outline_variant.default.hex}}", + "primary": "{{colors.primary.default.hex}}", + "primary_container": "{{colors.primary_container.default.hex}}", + "primary_fixed": "{{colors.primary_fixed.default.hex}}", + "primary_fixed_dim": "{{colors.primary_fixed_dim.default.hex}}", + "scrim": "{{colors.scrim.default.hex}}", + "secondary": "{{colors.secondary.default.hex}}", + "secondary_container": "{{colors.secondary_container.default.hex}}", + "secondary_fixed": "{{colors.secondary_fixed.default.hex}}", + "secondary_fixed_dim": "{{colors.secondary_fixed_dim.default.hex}}", + "shadow": "{{colors.shadow.default.hex}}", + "surface": "{{colors.surface.default.hex}}", + "surface_bright": "{{colors.surface_bright.default.hex}}", + "surface_container": "{{colors.surface_container.default.hex}}", + "surface_container_high": "{{colors.surface_container_high.default.hex}}", + "surface_container_highest": "{{colors.surface_container_highest.default.hex}}", + "surface_container_low": "{{colors.surface_container_low.default.hex}}", + "surface_container_lowest": "{{colors.surface_container_lowest.default.hex}}", + "surface_dim": "{{colors.surface_dim.default.hex}}", + "surface_tint": "{{colors.surface_tint.default.hex}}", + "surface_variant": "{{colors.surface_variant.default.hex}}", + "tertiary": "{{colors.tertiary.default.hex}}", + "tertiary_container": "{{colors.tertiary_container.default.hex}}", + "tertiary_fixed": "{{colors.tertiary_fixed.default.hex}}", + "tertiary_fixed_dim": "{{colors.tertiary_fixed_dim.default.hex}}" +} diff --git a/.config/matugen/templates/fuzzel/fuzzel.ini b/.config/matugen/templates/fuzzel/fuzzel.ini new file mode 100644 index 000000000..138799dea --- /dev/null +++ b/.config/matugen/templates/fuzzel/fuzzel.ini @@ -0,0 +1,21 @@ +font=Gabarito +terminal=foot -e +prompt=">> " +layer=overlay + +[colors] +background={{colors.background.default.hex_stripped}}ff +text={{colors.on_background.default.hex_stripped}}ff +selection={{colors.surface_variant.default.hex_stripped}}ff +selection-text={{colors.on_surface_variant.default.hex_stripped}}ff +border={{colors.surface_variant.default.hex_stripped}}dd +match={{colors.primary.default.hex_stripped}}ff +selection-match={{colors.primary.default.hex_stripped}}ff + + +[border] +radius=17 +width=1 + +[dmenu] +exit-immediately-if-empty=yes diff --git a/.config/matugen/templates/gtk/gtk-colors.css b/.config/matugen/templates/gtk/gtk-colors.css new file mode 100644 index 000000000..c0054f864 --- /dev/null +++ b/.config/matugen/templates/gtk/gtk-colors.css @@ -0,0 +1,22 @@ +/* +* GTK Colors +* Generated with Matugen +*/ + +@define-color accent_color {{colors.primary.default.hex}}; +@define-color accent_fg_color {{colors.on_primary.default.hex}}; +@define-color accent_bg_color {{colors.primary.default.hex}}; +@define-color window_bg_color {{colors.background.default.hex}}; +@define-color window_fg_color {{colors.on_background.default.hex}}; +@define-color headerbar_bg_color {{colors.surface_dim.default.hex}}; +@define-color headerbar_fg_color {{colors.on_surface.default.hex}}; +@define-color popover_bg_color {{colors.surface_dim.default.hex}}; +@define-color popover_fg_color {{colors.on_surface.default.hex}}; +@define-color view_bg_color {{colors.surface.default.hex}}; +@define-color view_fg_color {{colors.on_surface.default.hex}}; +@define-color card_bg_color {{colors.surface.default.hex}}; +@define-color card_fg_color {{colors.on_surface.default.hex}}; +@define-color sidebar_bg_color @window_bg_color; +@define-color sidebar_fg_color @window_fg_color; +@define-color sidebar_border_color @window_bg_color; +@define-color sidebar_backdrop_color @window_bg_color; diff --git a/.config/matugen/templates/hyprland/colors.conf b/.config/matugen/templates/hyprland/colors.conf new file mode 100644 index 000000000..db99e72ff --- /dev/null +++ b/.config/matugen/templates/hyprland/colors.conf @@ -0,0 +1,32 @@ +general { + col.active_border = rgba({{colors.on_surface.default.hex_stripped}}39) + col.inactive_border = rgba({{colors.outline.default.hex_stripped}}30) +} + +misc { + background_color = rgba({{colors.surface.default.hex_stripped}}FF) +} + +plugin { + hyprbars { + # Honestly idk if it works like css, but well, why not + bar_text_font = Rubik, Geist, AR One Sans, Reddit Sans, Inter, Roboto, Ubuntu, Noto Sans, sans-serif + bar_height = 30 + bar_padding = 10 + bar_button_padding = 5 + bar_precedence_over_border = true + bar_part_of_window = true + + bar_color = rgba({{colors.background.default.hex_stripped}}FF) + col.text = rgba({{colors.on_background.default.hex_stripped}}FF) + + + # example buttons (R -> L) + # hyprbars-button = color, size, on-click + hyprbars-button = rgb({{colors.on_background.default.hex_stripped}}), 13, 󰖭, hyprctl dispatch killactive + hyprbars-button = rgb({{colors.on_background.default.hex_stripped}}), 13, 󰖯, hyprctl dispatch fullscreen 1 + hyprbars-button = rgb({{colors.on_background.default.hex_stripped}}), 13, 󰖰, hyprctl dispatch movetoworkspacesilent special + } +} + +windowrulev2 = bordercolor rgba({{colors.primary.default.hex_stripped}}AA) rgba({{colors.primary.default.hex_stripped}}77),pinned:1 diff --git a/.config/matugen/templates/hyprland/hyprlock.conf b/.config/matugen/templates/hyprland/hyprlock.conf new file mode 100644 index 000000000..9ee4114f8 --- /dev/null +++ b/.config/matugen/templates/hyprland/hyprlock.conf @@ -0,0 +1,83 @@ +$text_color = rgba({{colors.primary_fixed.default.hex_stripped}}FF) +$entry_background_color = rgba({{colors.on_primary_fixed.default.hex_stripped}}11) +$entry_border_color = rgba({{colors.outline.default.hex_stripped}}55) +$entry_color = rgba({{colors.primary_fixed.default.hex_stripped}}FF) +$font_family = Rubik Light +$font_family_clock = Rubik Light +$font_material_symbols = Material Symbols Rounded + +background { + color = rgba(181818FF) + # path = {{image}} + + # path = screenshot + # blur_size = 15 + # blur_passes = 4 +} +input-field { + monitor = + size = 250, 50 + outline_thickness = 2 + dots_size = 0.1 + dots_spacing = 0.3 + outer_color = $entry_border_color + inner_color = $entry_background_color + font_color = $entry_color + fade_on_empty = true + + position = 0, 20 + halign = center + valign = center +} + +label { # Clock + monitor = + text = $TIME + color = $text_color + font_size = 65 + font_family = $font_family_clock + + position = 0, 300 + halign = center + valign = center +} +label { # Date + monitor = + text = cmd[update:5000] date +"%A, %B %d" + color = $text_color + font_size = 17 + font_family = $font_family + + position = 0, 240 + halign = center + valign = center +} + +label { # User + monitor = + text =  $USER + color = $text_color + shadow_passes = 1 + shadow_boost = 0.35 + outline_thickness = 2 + dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8 + dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0 + dots_center = true + font_size = 20 + font_family = $font_family + position = 0, 50 + halign = center + valign = bottom +} + +label { # Status + monitor = + text = cmd[update:5000] ${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprlock/status.sh + color = $text_color + font_size = 14 + font_family = $font_family + + position = 30, -30 + halign = left + valign = top +} \ No newline at end of file diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 858d211f7..2dff5bf6c 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -45,6 +45,9 @@ Singleton { property int nonAppResultDelay: 30 // This prevents lagging when typing property string engineBaseUrl: "https://www.google.com/search?q=" property list excludedSites: [ "quora.com" ] + property QtObject prefix: QtObject { + property string action: "/" + } } property QtObject hacks: QtObject { diff --git a/.config/quickshell/modules/common/widgets/RoundCorner.qml b/.config/quickshell/modules/common/widgets/RoundCorner.qml index 6a9020bc0..59e9a0c01 100644 --- a/.config/quickshell/modules/common/widgets/RoundCorner.qml +++ b/.config/quickshell/modules/common/widgets/RoundCorner.qml @@ -6,6 +6,10 @@ Item { property int size: 25 property color color: "#000000" + onColorChanged: { + canvas.requestPaint(); + } + property QtObject cornerEnum: QtObject { property int topLeft: 0 property int topRight: 1 diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index b566f1b32..73a2d96d0 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -1,7 +1,9 @@ import "root:/" +import "root:/services/" import "root:/modules/common" import "root:/modules/common/widgets" import Qt5Compat.GraphicalEffects +import Qt.labs.platform import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -11,6 +13,7 @@ import Quickshell.Io Item { // Wrapper id: root required property var panelWindow + readonly property string xdgConfigHome: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0] property string searchingText: "" property bool showResults: searchingText != "" property real searchBarHeight: searchBar.height + Appearance.sizes.elevationMargin * 2 @@ -18,6 +21,41 @@ Item { // Wrapper implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2 property string mathResult: "" + property var searchActions: [ + { + action: "img", + execute: () => { + executor.executeCommand(`${xdgConfigHome}/quickshell/scripts/switchwall.sh`.replace(/file:\/\//, "")) + } + }, + { + action: "dark", + execute: () => { + executor.executeCommand(`${xdgConfigHome}/quickshell/scripts/switchwall.sh --mode dark --noswitch`.replace(/file:\/\//, "")) + } + }, + { + action: "light", + execute: () => { + executor.executeCommand(`${xdgConfigHome}/quickshell/scripts/switchwall.sh --mode light --noswitch`.replace(/file:\/\//, "")) + } + }, + { + action: "accentcolor", + execute: (args) => { + console.log(args) + executor.executeCommand( + `${xdgConfigHome}/quickshell/scripts/switchwall.sh --noswitch --color ${args != '' ? ("'"+args+"'") : ""}` + .replace(/file:\/\//, "")) + } + }, + { + action: "todo", + execute: (args) => { + Todo.addTask(args) + } + }, + ] function focusFirstItemIfNeeded() { if (searchInput.focus) appResults.currentIndex = 0; // Focus the first item @@ -178,7 +216,8 @@ Item { // Wrapper Layout.rightMargin: 15 padding: 15 color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant - selectedTextColor: Appearance.m3colors.m3onSurface + selectedTextColor: Appearance.m3colors.m3onPrimary + selectionColor: Appearance.m3colors.m3primary placeholderText: qsTr("Search, calculate or run") placeholderTextColor: Appearance.m3colors.m3outline implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth @@ -275,6 +314,22 @@ Item { // Wrapper ); // Add non-app results + // Launcher actions + for (let action of root.searchActions) { + const actionString = `${ConfigOptions.search.prefix.action}${action.action}` + if (actionString.startsWith(root.searchingText) || root.searchingText.startsWith(actionString)) { + result.push({ + name: root.searchingText.startsWith(actionString) ? root.searchingText : actionString, + clickActionName: "Run", + type: "Action", + materialSymbol: 'settings_suggest', + execute: () => { + action.execute(root.searchingText.split(" ").slice(1).join(" ")) + }, + }); + } + } + // Qalc math result result.push({ name: root.mathResult, clickActionName: "Copy", @@ -285,6 +340,7 @@ Item { // Wrapper copyText.copyTextToClipboard(root.mathResult); } }); + // Run command result.push({ name: searchingText, clickActionName: "Run", @@ -295,6 +351,7 @@ Item { // Wrapper executor.executeCommand(searchingText.startsWith('sudo') ? `${ConfigOptions.apps.terminal} fish -C '${root.searchingText}'` : root.searchingText); } }); + // Web search result.push({ name: root.searchingText, clickActionName: "Search", diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 4491d3d09..f5b6472e5 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -99,11 +99,11 @@ Scope { Layout.topMargin: 5 Layout.bottomMargin: 0 - CustomIcon { - width: 25 - height: 25 - source: SystemInfo.distroIcon - } + // CustomIcon { + // width: 25 + // height: 25 + // source: SystemInfo.distroIcon + // } StyledText { font.pixelSize: Appearance.font.pixelSize.normal diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index 7464e9884..7db2e1c50 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -273,7 +273,8 @@ Item { Layout.rightMargin: 16 padding: 10 color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant - selectedTextColor: Appearance.m3colors.m3onSurface + selectedTextColor: Appearance.m3colors.m3onPrimary + selectionColor: Appearance.m3colors.m3primary placeholderText: qsTr("Task description") placeholderTextColor: Appearance.m3colors.m3outline focus: root.showAddDialog diff --git a/.config/quickshell/scripts/applycolor.sh b/.config/quickshell/scripts/applycolor.sh new file mode 100755 index 000000000..3ba7862e2 --- /dev/null +++ b/.config/quickshell/scripts/applycolor.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" +XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" +XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}" +CONFIG_DIR="$XDG_CONFIG_HOME/quickshell" +CACHE_DIR="$XDG_CACHE_HOME/quickshell" +STATE_DIR="$XDG_STATE_HOME/quickshell" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +term_alpha=100 #Set this to < 100 make all your terminals transparent +# sleep 0 # idk i wanted some delay or colors dont get applied properly +if [ ! -d "$CACHE_DIR"/user/generated ]; then + mkdir -p "$CACHE_DIR"/user/generated +fi +cd "$CONFIG_DIR" || exit + +colornames='' +colorstrings='' +colorlist=() +colorvalues=() + +colornames=$(cat $STATE_DIR/user/generated/material_colors.scss | cut -d: -f1) +colorstrings=$(cat $STATE_DIR/user/generated/material_colors.scss | cut -d: -f2 | cut -d ' ' -f2 | cut -d ";" -f1) +IFS=$'\n' +colorlist=($colornames) # Array of color names +colorvalues=($colorstrings) # Array of color values + +apply_term() { + # Check if terminal escape sequence template exists + if [ ! -f "$SCRIPT_DIR"/terminal/sequences.txt ]; then + echo "Template file not found for Terminal. Skipping that." + return + fi + # Copy template + mkdir -p "$CACHE_DIR"/user/generated/terminal + cp "$SCRIPT_DIR"/terminal/sequences.txt "$CACHE_DIR"/user/generated/terminal/sequences.txt + # Apply colors + for i in "${!colorlist[@]}"; do + sed -i "s/${colorlist[$i]} #/${colorvalues[$i]#\#}/g" "$CACHE_DIR"/user/generated/terminal/sequences.txt + done + + sed -i "s/\$alpha/$term_alpha/g" "$CACHE_DIR/user/generated/terminal/sequences.txt" + + for file in /dev/pts/*; do + if [[ $file =~ ^/dev/pts/[0-9]+$ ]]; then + cat "$CACHE_DIR"/user/generated/terminal/sequences.txt >"$file" + fi + done +} + +apply_qt() { + sh "$CONFIG_DIR/scripts/kvantum/materialQT.sh" # generate kvantum theme + python "$CONFIG_DIR/scripts/kvantum/changeAdwColors.py" # apply config colors +} + +apply_qt & +apply_term & diff --git a/.config/quickshell/scripts/generate_colors_material.py b/.config/quickshell/scripts/generate_colors_material.py new file mode 100755 index 000000000..db6b1664b --- /dev/null +++ b/.config/quickshell/scripts/generate_colors_material.py @@ -0,0 +1,181 @@ +#!/usr/bin/env -S\_/bin/sh\_-c\_"source\_\$(eval\_echo\_\$ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate&&exec\_python\_-E\_"\$0"\_"\$@"" +import argparse +import math +import json +from PIL import Image +from materialyoucolor.quantize import QuantizeCelebi +from materialyoucolor.score.score import Score +from materialyoucolor.hct import Hct +from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors +from materialyoucolor.utils.color_utils import (rgba_from_argb, argb_from_rgb, argb_from_rgba) +from materialyoucolor.utils.math_utils import (sanitize_degrees_double, difference_degrees, rotation_direction) + +parser = argparse.ArgumentParser(description='Color generation script') +parser.add_argument('--path', type=str, default=None, help='generate colorscheme from image') +parser.add_argument('--size', type=int , default=128 , help='bitmap image size') +parser.add_argument('--color', type=str, default=None, help='generate colorscheme from color') +parser.add_argument('--mode', type=str, choices=['dark', 'light'], default='dark', help='dark or light mode') +parser.add_argument('--scheme', type=str, default='vibrant', help='material scheme to use') +parser.add_argument('--smart', action='store_true', default=False, help='decide scheme type based on image color') +parser.add_argument('--transparency', type=str, choices=['opaque', 'transparent'], default='opaque', help='enable transparency') +parser.add_argument('--termscheme', type=str, default=None, help='JSON file containg the terminal scheme for generating term colors') +parser.add_argument('--harmony', type=float , default=0.8, help='(0-1) Color hue shift towards accent') +parser.add_argument('--harmonize_threshold', type=float , default=100, help='(0-180) Max threshold angle to limit color hue shift') +parser.add_argument('--term_fg_boost', type=float , default=0.35, help='Make terminal foreground more different from the background') +parser.add_argument('--blend_bg_fg', action='store_true', default=False, help='Shift terminal background or foreground towards accent') +parser.add_argument('--cache', type=str, default=None, help='file path to store the generated color') +parser.add_argument('--debug', action='store_true', default=False, help='debug mode') +args = parser.parse_args() + +rgba_to_hex = lambda rgba: "#{:02X}{:02X}{:02X}".format(rgba[0], rgba[1], rgba[2]) +argb_to_hex = lambda argb: "#{:02X}{:02X}{:02X}".format(*map(round, rgba_from_argb(argb))) +hex_to_argb = lambda hex_code: argb_from_rgb(int(hex_code[1:3], 16), int(hex_code[3:5], 16), int(hex_code[5:], 16)) +display_color = lambda rgba : "\x1B[38;2;{};{};{}m{}\x1B[0m".format(rgba[0], rgba[1], rgba[2], "\x1b[7m \x1b[7m") + +def calculate_optimal_size (width: int, height: int, bitmap_size: int) -> (int, int): + image_area = width * height; + bitmap_area = bitmap_size ** 2 + scale = math.sqrt(bitmap_area/image_area) if image_area > bitmap_area else 1 + new_width = round(width * scale) + new_height = round(height * scale) + if new_width == 0: + new_width = 1 + if new_height == 0: + new_height = 1 + return new_width, new_height + +def harmonize (design_color: int, source_color: int, threshold: float = 35, harmony: float = 0.5) -> int: + from_hct = Hct.from_int(design_color) + to_hct = Hct.from_int(source_color) + difference_degrees_ = difference_degrees(from_hct.hue, to_hct.hue) + rotation_degrees = min(difference_degrees_ * harmony, threshold) + output_hue = sanitize_degrees_double( + from_hct.hue + rotation_degrees * rotation_direction(from_hct.hue, to_hct.hue) + ) + return Hct.from_hct(output_hue, from_hct.chroma, from_hct.tone).to_int() + +def boost_chroma_tone (argb: int, chroma: float = 1, tone: float = 1) -> int: + hct = Hct.from_int(argb) + return Hct.from_hct(hct.hue, hct.chroma * chroma, hct.tone * tone).to_int() + +darkmode = (args.mode == 'dark') +transparent = (args.transparency == 'transparent') + +if args.path is not None: + image = Image.open(args.path) + + if image.format == "GIF": + image.seek(1) + + if image.mode in ["L", "P"]: + image = image.convert('RGB') + wsize, hsize = image.size + wsize_new, hsize_new = calculate_optimal_size(wsize, hsize, args.size) + if wsize_new < wsize or hsize_new < hsize: + image = image.resize((wsize_new, hsize_new), Image.Resampling.BICUBIC) + colors = QuantizeCelebi(list(image.getdata()), 128) + argb = Score.score(colors)[0] + + if args.cache is not None: + with open(args.cache, 'w') as file: + file.write(argb_to_hex(argb)) + hct = Hct.from_int(argb) + if(args.smart): + if(hct.chroma < 20): + args.scheme = 'neutral' +elif args.color is not None: + argb = hex_to_argb(args.color) + hct = Hct.from_int(argb) + +if args.scheme == 'scheme-fruit-salad': + from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad as Scheme +elif args.scheme == 'scheme-expressive': + from materialyoucolor.scheme.scheme_expressive import SchemeExpressive as Scheme +elif args.scheme == 'scheme-monochrome': + from materialyoucolor.scheme.scheme_monochrome import SchemeMonochrome as Scheme +elif args.scheme == 'scheme-rainbow': + from materialyoucolor.scheme.scheme_rainbow import SchemeRainbow as Scheme +elif args.scheme == 'scheme-tonal-spot': + from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot as Scheme +elif args.scheme == 'scheme-neutral': + from materialyoucolor.scheme.scheme_neutral import SchemeNeutral as Scheme +elif args.scheme == 'scheme-fidelity': + from materialyoucolor.scheme.scheme_fidelity import SchemeFidelity as Scheme +elif args.scheme == 'scheme-content': + from materialyoucolor.scheme.scheme_content import SchemeContent as Scheme +elif args.scheme == 'scheme-vibrant': + from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant as Scheme +else: + from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot as Scheme +# Generate +scheme = Scheme(hct, darkmode, 0.0) + +material_colors = {} +term_colors = {} + +for color in vars(MaterialDynamicColors).keys(): + color_name = getattr(MaterialDynamicColors, color) + if hasattr(color_name, "get_hct"): + rgba = color_name.get_hct(scheme).to_rgba() + material_colors[color] = rgba_to_hex(rgba) + +# Extended material +if darkmode == True: + material_colors['success'] = '#B5CCBA' + material_colors['onSuccess'] = '#213528' + material_colors['successContainer'] = '#374B3E' + material_colors['onSuccessContainer'] = '#D1E9D6' +else: + material_colors['success'] = '#4F6354' + material_colors['onSuccess'] = '#FFFFFF' + material_colors['successContainer'] = '#D1E8D5' + material_colors['onSuccessContainer'] = '#0C1F13' + +# Terminal Colors +if args.termscheme is not None: + with open(args.termscheme, 'r') as f: + json_termscheme = f.read() + term_source_colors = json.loads(json_termscheme)['dark' if darkmode else 'light'] + + primary_color_argb = hex_to_argb(material_colors['primary_paletteKeyColor']) + for color, val in term_source_colors.items(): + if(args.scheme == 'monochrome') : + term_colors[color] = val + continue + if args.blend_bg_fg and color == "term0": + harmonized = boost_chroma_tone(hex_to_argb(material_colors['surfaceContainerLow']), 1.2, 0.95) + elif args.blend_bg_fg and color == "term15": + harmonized = boost_chroma_tone(hex_to_argb(material_colors['onSurface']), 3, 1) + else: + harmonized = harmonize(hex_to_argb(val), primary_color_argb, args.harmonize_threshold, args.harmony) + harmonized = boost_chroma_tone(harmonized, 1, 1 + (args.term_fg_boost * (1 if darkmode else -1))) + term_colors[color] = argb_to_hex(harmonized) + +if args.debug == False: + print(f"$darkmode: {darkmode};") + print(f"$transparent: {transparent};") + for color, code in material_colors.items(): + print(f"${color}: {code};") + for color, code in term_colors.items(): + print(f"${color}: {code};") +else: + if args.path is not None: + print('\n--------------Image properties-----------------') + print(f"Image size: {wsize} x {hsize}") + print(f"Resized image: {wsize_new} x {hsize_new}") + print('\n---------------Selected color------------------') + print(f"Dark mode: {darkmode}") + print(f"Scheme: {args.scheme}") + print(f"Accent color: {display_color(rgba_from_argb(argb))} {argb_to_hex(argb)}") + print(f"HCT: {hct.hue:.2f} {hct.chroma:.2f} {hct.tone:.2f}") + print('\n---------------Material colors-----------------') + for color, code in material_colors.items(): + rgba = rgba_from_argb(hex_to_argb(code)) + print(f"{color.ljust(32)} : {display_color(rgba)} {code}") + print('\n----------Harmonize terminal colors------------') + for color, code in term_colors.items(): + rgba = rgba_from_argb(hex_to_argb(code)) + code_source = term_source_colors[color] + rgba_source = rgba_from_argb(hex_to_argb(code_source)) + print(f"{color.ljust(6)} : {display_color(rgba_source)} {code_source} --> {display_color(rgba)} {code}") + print('-----------------------------------------------') diff --git a/.config/quickshell/scripts/kvantum/adwsvg.py b/.config/quickshell/scripts/kvantum/adwsvg.py new file mode 100644 index 000000000..10ce1d150 --- /dev/null +++ b/.config/quickshell/scripts/kvantum/adwsvg.py @@ -0,0 +1,79 @@ +import re +import os + +def read_scss(file_path): + """Reads an SCSS file and returns a dictionary of color variables.""" + colors = {} + with open(file_path, 'r') as file: + for line in file: + match = re.match(r'\$(\w+):\s*(#[0-9A-Fa-f]{6});', line.strip()) + if match: + variable_name, color = match.groups() + colors[variable_name] = color + return colors + +def update_svg_colors(svg_path, old_to_new_colors, output_path): + """ + Updates the colors in an SVG file based on the provided color map. + + :param svg_path: Path to the SVG file. + :param old_to_new_colors: Dictionary mapping old colors to new colors. + :param output_path: Path to save the updated SVG file. + """ + # Read the SVG content + with open(svg_path, 'r') as file: + svg_content = file.read() + + # Replace old colors with new colors + for old_color, new_color in old_to_new_colors.items(): + svg_content = re.sub(old_color, new_color, svg_content, flags=re.IGNORECASE) + + # Write the updated SVG content to the output file + with open(output_path, 'w') as file: + file.write(svg_content) + + print(f"SVG colors have been updated and saved to {output_path}!") + +def main(): + xdg_config_home = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")) + xdg_state_home = os.environ.get("XDG_STATE_HOME", os.path.expanduser("~/.local/state")) + + scss_file = os.path.join(xdg_state_home, "quickshell", "user", "generated", "material_colors.scss") + svg_path = os.path.join(xdg_config_home, "Kvantum", "Colloid", "Colloid.svg") + output_path = os.path.join(xdg_config_home, "Kvantum", "MaterialAdw", "MaterialAdw.svg") + + # Read colors from the SCSS file + color_data = read_scss(scss_file) + + # Specify the old colors and map them to new colors from the SCSS file + old_to_new_colors = { + #'#cccccc': color_data['surfaceDim'], # Map old SVG color to new SCSS color + #'#666666': color_data['surfaceDim'], + '#3c84f7': color_data['primary'], + #'#5a5a5a': color_data['neutral_paletteKeyColor'], + '#000000': color_data['shadow'], + '#f04a50': color_data['error'], + '#4285f4': color_data['primaryFixedDim'], + '#f2f2f2': color_data['background'], + #'#dfdfdf': color_data['surfaceContainerLow'], + '#ffffff': color_data['background'], + '#1e1e1e': color_data['onPrimaryFixed'], + #'#b6b6b6': color_data['surfaceContainer'], + '#333': color_data['inverseSurface'], + '#212121': color_data['onSecondaryFixed'], + '#5b9bf8': color_data['secondaryContainer'], + '#26272a': color_data['term7'], + #'#b3b3b3': color_data['surfaceBright'], + #'#b74aff': color_data['tertiary'], + #'#989898': color_data['surfaceContainerHighest'], + #'#c1c1c1': color_data['surfaceContainerHigh'], + '#444444': color_data['onBackground'], + '#333333': color_data['onPrimaryFixed'], + } + + # Update the SVG colors + update_svg_colors(svg_path, old_to_new_colors, output_path) + +if __name__ == "__main__": + main() + diff --git a/.config/quickshell/scripts/kvantum/adwsvgDark.py b/.config/quickshell/scripts/kvantum/adwsvgDark.py new file mode 100644 index 000000000..9fb097740 --- /dev/null +++ b/.config/quickshell/scripts/kvantum/adwsvgDark.py @@ -0,0 +1,87 @@ +import re +import os + +def read_scss(file_path): + """Reads an SCSS file and returns a dictionary of color variables.""" + colors = {} + with open(file_path, 'r') as file: + for line in file: + match = re.match(r'\$(\w+):\s*(#[0-9A-Fa-f]{6});', line.strip()) + if match: + variable_name, color = match.groups() + colors[variable_name] = color + return colors + +def update_svg_colors(svg_path, old_to_new_colors, output_path): + """ + Updates the colors in an SVG file based on the provided color map. + + :param svg_path: Path to the SVG file. + :param old_to_new_colors: Dictionary mapping old colors to new colors. + :param output_path: Path to save the updated SVG file. + """ + # Read the SVG content + with open(svg_path, 'r') as file: + svg_content = file.read() + + # Replace old colors with new colors + for old_color, new_color in old_to_new_colors.items(): + svg_content = re.sub(old_color, new_color, svg_content, flags=re.IGNORECASE) + + # Write the updated SVG content to the output file + with open(output_path, 'w') as file: + file.write(svg_content) + + print(f"SVG colors have been updated and saved to {output_path}!") + +def main(): + xdg_config_home = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")) + xdg_state_home = os.environ.get("XDG_STATE_HOME", os.path.expanduser("~/.local/state")) + + scss_file = os.path.join(xdg_state_home, "quickshell", "user", "generated", "material_colors.scss") + svg_path = os.path.join(xdg_config_home, "Kvantum", "Colloid", "ColloidDark.svg") + output_path = os.path.join(xdg_config_home, "Kvantum", "MaterialAdw", "MaterialAdw.svg") + + # Read colors from the SCSS file + color_data = read_scss(scss_file) + + # Specify the old colors and map them to new colors from the SCSS file + old_to_new_colors = { + #'#525252': color_data['surfaceDim'], # Map old SVG color to new SCSS color + #'#666666': color_data['surfaceDim'], + '#31363b': color_data['background'], + #'#eff0f1': color_data['neutral_paletteKeyColor'], + '#000000': color_data['shadow'], + '#5b9bf8': color_data['primary'], + '#93cee9': color_data['onSecondaryContainer'], + '#3daee9': color_data['secondary'], + #'#fff': color_data['term10'], + #'#5a5a5a': color_data['surfaceVariant'], + #'#acb1bc': color_data['onPrimaryFixed'], + '#ffffff': color_data['term11'], + '#5a616e': color_data['surfaceVariant'], + '#f04a50': color_data['error'], + '#4285f4': color_data['secondary'], + '#242424': color_data['background'], + '#2c2c2c': color_data['background'], + #'#dfdfdf': color_data['onSurfaceVariant'], + #'#646464': color_data['surfaceContainerHighest'], + #'#989898': color_data['surfaceContainerHigh'], + #'#c1c1c1': color_data['primaryFixedDim'], + '#1e1e1e': color_data['background'], + '#3c3c3c': color_data['background'], + '#26272a': color_data['surfaceBright'], + '#000000': color_data['shadow'], + '#b74aff': color_data['tertiary'], + #'#b6b6b6': color_data['onSurfaceVariant'], + '#1a1a1a': color_data['background'], + '#333': color_data['term0'], + '#212121': color_data['background'], + } + + # Update the SVG colors + update_svg_colors(svg_path, old_to_new_colors, output_path) + +if __name__ == "__main__": + main() + diff --git a/.config/quickshell/scripts/kvantum/changeAdwColors.py b/.config/quickshell/scripts/kvantum/changeAdwColors.py new file mode 100644 index 000000000..26d067ad5 --- /dev/null +++ b/.config/quickshell/scripts/kvantum/changeAdwColors.py @@ -0,0 +1,71 @@ +import re +import os + +def get_colors_from_scss(scss_file): + colors = {} + with open(scss_file, 'r') as file: + for line in file: + match = re.match(r'\$(\w+):\s*(#[0-9A-Fa-f]{6});', line) + if match: + colors[match.group(1)] = match.group(2) + return colors + +def update_config_colors(config_file, colors, mappings): + with open(config_file, 'r') as file: + config_content = file.read() + + for key, variable in mappings.items(): + if variable in colors: + color = colors[variable] + pattern = rf'({key}=)#?\w+\b' + new_line = f'\\1{color}' + if re.search(pattern, config_content): + config_content = re.sub(pattern, new_line, config_content) + else: + config_content += f"\n{key}={color}" + + with open(config_file, 'w') as file: + file.write(config_content) + +if __name__ == "__main__": + xdg_config_home = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")) + xdg_state_home = os.environ.get("XDG_STATE_HOME", os.path.expanduser("~/.local/state")) + + config_file = os.path.join(xdg_config_home, "Kvantum", "MaterialAdw", "MaterialAdw.kvconfig") + scss_file = os.path.join(xdg_state_home, "quickshell", "user", "generated", "material_colors.scss") + + # Define your mappings here + mappings = { + 'window.color': 'background', + 'base.color': 'background', + 'alt.base.color': 'background', + 'button.color': 'surfaceContainer', + 'light.color': 'surfaceContainerLow', + 'mid.light.color': 'surfaceContainer', + 'dark.color': 'surfaceContainerHighest', + 'mid.color': 'surfaceContainerHigh', + 'highlight.color': 'primary', + 'inactive.highlight.color': 'primary', + 'text.color': 'onBackground', + 'window.text.color': 'onBackground', + 'button.text.color': 'onBackground', + 'disabled.text.color': 'onBackground', + 'tooltip.text.color': 'onBackground', + 'highlight.text.color': 'onSurface', + 'link.color': 'tertiary', + 'link.visited.color': 'tertiaryFixed', + 'progress.indicator.text.color': 'onBackground', + 'text.normal.color': 'onBackground', + 'text.focus.color': 'onBackground', + 'text.press.color': 'onsecondarycontainer', + 'text.toggle.color': 'onsecondarycontainer', + 'text.disabled.color': 'surfaceDim', + + + # Add more mappings as needed + } + + colors = get_colors_from_scss(scss_file) + update_config_colors(config_file, colors, mappings) + print("Config colors updated successfully!") + diff --git a/.config/quickshell/scripts/kvantum/materialQT.sh b/.config/quickshell/scripts/kvantum/materialQT.sh new file mode 100755 index 000000000..3d1f8a7bf --- /dev/null +++ b/.config/quickshell/scripts/kvantum/materialQT.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" +XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" +XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}" +CONFIG_DIR="$XDG_CONFIG_HOME/quickshell" +CACHE_DIR="$XDG_CACHE_HOME/quickshell" +STATE_DIR="$XDG_STATE_HOME/quickshell" + +get_light_dark() { + current_mode=$(gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null | tr -d "'") + if [[ "$current_mode" == "prefer-dark" ]]; then + echo "dark" + else + echo "light" + fi +} + +apply_qt() { + # Check if the theme exists + FOLDER_PATH="$XDG_CONFIG_HOME/Kvantum/Colloid/" + + if [ ! -d "$FOLDER_PATH" ]; then + # Send a notification + notify-send "Colloid-kde theme required" " The folder '$FOLDER_PATH' does not exist." + exit 1 # Exit the function if the folder does not exist + fi + + lightdark=$(get_light_dark) + if [ "$lightdark" = "light" ]; then + # apply ligght colors + cp "$XDG_CONFIG_HOME/Kvantum/Colloid/Colloid.kvconfig" "$XDG_CONFIG_HOME/Kvantum/MaterialAdw/MaterialAdw.kvconfig" + python "$CONFIG_DIR/scripts/kvantum/adwsvg.py" + + else + #apply dark colors + cp "$XDG_CONFIG_HOME/Kvantum/Colloid/ColloidDark.kvconfig" "$XDG_CONFIG_HOME/Kvantum/MaterialAdw/MaterialAdw.kvconfig" + python "$CONFIG_DIR/scripts/kvantum/adwsvgDark.py" + fi +} + +apply_qt diff --git a/.config/quickshell/scripts/switchwall.sh b/.config/quickshell/scripts/switchwall.sh new file mode 100755 index 000000000..32fb7c8a2 --- /dev/null +++ b/.config/quickshell/scripts/switchwall.sh @@ -0,0 +1,137 @@ +#!/usr/bin/env bash + +XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" +XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" +XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}" +CONFIG_DIR="$XDG_CONFIG_HOME/quickshell" +CACHE_DIR="$XDG_CACHE_HOME/quickshell" +STATE_DIR="$XDG_STATE_HOME/quickshell" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +terminalscheme="$XDG_CONFIG_HOME/quickshell/scripts/terminal/scheme-base.json" + +pre_process() { + if [ ! -d "$CACHE_DIR"/user/generated ]; then + mkdir -p "$CACHE_DIR"/user/generated + fi +} + +post_process() { + local mode_flag="$1" + # Set GNOME color-scheme if mode_flag is dark or light + if [[ "$mode_flag" == "dark" ]]; then + gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' + gsettings set org.gnome.desktop.interface gtk-theme 'adw-gtk3-dark' + elif [[ "$mode_flag" == "light" ]]; then + gsettings set org.gnome.desktop.interface color-scheme 'prefer-light' + gsettings set org.gnome.desktop.interface gtk-theme 'adw-gtk3' + fi +} + +switch() { + imgpath="$1" + mode_flag="$2" + type_flag="$3" + color_flag="$4" + color="$5" + read scale screenx screeny screensizey < <(hyprctl monitors -j | jq '.[] | select(.focused) | .scale, .x, .y, .height' | xargs) + cursorposx=$(hyprctl cursorpos -j | jq '.x' 2>/dev/null) || cursorposx=960 + cursorposx=$(bc <<< "scale=0; ($cursorposx - $screenx) * $scale / 1") + cursorposy=$(hyprctl cursorpos -j | jq '.y' 2>/dev/null) || cursorposy=540 + cursorposy=$(bc <<< "scale=0; ($cursorposy - $screeny) * $scale / 1") + cursorposy_inverted=$((screensizey - cursorposy)) + + if [[ "$color_flag" == "1" ]]; then + matugen_args=(color hex "$color") + generate_colors_material_args=(--color "$color") + else + if [[ -z "$imgpath" ]]; then + echo 'Aborted' + exit 0 + fi + matugen_args=(image "$imgpath") + generate_colors_material_args=(--path "$imgpath") + fi + + # Determine mode if not set + if [[ -z "$mode_flag" ]]; then + current_mode=$(gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null | tr -d "'") + if [[ "$current_mode" == "prefer-dark" ]]; then + mode_flag="dark" + else + mode_flag="light" + fi + fi + + # Dark/light mode, material scheme + [[ -n "$mode_flag" ]] && matugen_args+=(--mode "$mode_flag") && generate_colors_material_args+=(--mode "$mode_flag") + [[ -n "$type_flag" ]] && matugen_args+=(--type "$type_flag") && generate_colors_material_args+=(--scheme "$type_flag") + # Terminal scheme + generate_colors_material_args+=(--termscheme "$terminalscheme" --blend_bg_fg) + generate_colors_material_args+=(--cache "$STATE_DIR/user/color.txt") + + pre_process + + # Generate with matugen + matugen "${matugen_args[@]}" + # Use custom script for mixing (matugen can't D:) + source "$(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate" + python "$SCRIPT_DIR/generate_colors_material.py" "${generate_colors_material_args[@]}" \ + > "$STATE_DIR"/user/generated/material_colors.scss + "$SCRIPT_DIR"/applycolor.sh + deactivate + + post_process "$mode_flag" +} + +main() { + imgpath="" + mode_flag="" + type_flag="" + color_flag="" + color="" + noswitch_flag="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --mode) + mode_flag="$2" + shift 2 + ;; + --type) + type_flag="$2" + shift 2 + ;; + --color) + color_flag="1" + if [[ "$2" =~ ^#?[A-Fa-f0-9]{6}$ ]]; then + color="$2" + shift 2 + else + color=$(hyprpicker --no-fancy) + shift + fi + ;; + --noswitch) + noswitch_flag="1" + imgpath=$(swww query | awk -F 'image: ' '{print $2}') + shift + ;; + *) + if [[ -z "$imgpath" ]]; then + imgpath="$1" + fi + shift + ;; + esac + done + + # Only prompt for wallpaper if not using --color and not using --noswitch and no imgpath set + if [[ -z "$imgpath" && -z "$color_flag" && -z "$noswitch_flag" ]]; then + cd "$(xdg-user-dir PICTURES)/Wallpapers" 2>/dev/null || cd "$(xdg-user-dir PICTURES)" || return 1 + imgpath="$(yad --width 1200 --height 800 --file --add-preview --large-preview --title='Choose wallpaper')" + fi + + switch "$imgpath" "$mode_flag" "$type_flag" "$color_flag" "$color" +} + +main "$@" \ No newline at end of file diff --git a/.config/quickshell/scripts/terminal/scheme-base.json b/.config/quickshell/scripts/terminal/scheme-base.json new file mode 100644 index 000000000..e4b78e7e7 --- /dev/null +++ b/.config/quickshell/scripts/terminal/scheme-base.json @@ -0,0 +1,38 @@ +{ + "dark": { + "term0" : "#282828", + "term1" : "#CC241D", + "term2" : "#98971A", + "term3" : "#D79921", + "term4" : "#458588", + "term5" : "#B16286", + "term6" : "#689D6A", + "term7" : "#A89984", + "term8" : "#928374", + "term9" : "#FB4934", + "term10" : "#B8BB26", + "term11" : "#FABD2F", + "term12" : "#83A598", + "term13" : "#D3869B", + "term14" : "#8EC07C", + "term15" : "#EBDBB2" + }, + "light": { + "term0" : "#FDF9F3", + "term1" : "#FF6188", + "term2" : "#A9DC76", + "term3" : "#FC9867", + "term4" : "#FFD866", + "term5" : "#F47FD4", + "term6" : "#78DCE8", + "term7" : "#333034", + "term8" : "#121212", + "term9" : "#FF6188", + "term10" : "#A9DC76", + "term11" : "#FC9867", + "term12" : "#FFD866", + "term13" : "#F47FD4", + "term14" : "#78DCE8", + "term15" : "#333034" + } +} diff --git a/.config/quickshell/scripts/terminal/sequences.txt b/.config/quickshell/scripts/terminal/sequences.txt new file mode 100644 index 000000000..0308e0b34 --- /dev/null +++ b/.config/quickshell/scripts/terminal/sequences.txt @@ -0,0 +1 @@ +]4;0;#$term0 #\]1;0;#$term0 #\]4;1;#$term1 #\]4;2;#$term2 #\]4;3;#$term3 #\]4;4;#$term4 #\]4;5;#$term5 #\]4;6;#$term6 #\]4;7;#$term7 #\]4;8;#$term8 #\]4;9;#$term9 #\]4;10;#$term10 #\]4;11;#$term11 #\]4;12;#$term12 #\]4;13;#$term13 #\]4;14;#$term14 #\]4;15;#$term15 #\]10;#$term7 #\]11;[100]#$term0 #\]12;#$term7 #\]13;#$term7 #\]17;#$term7 #\]19;#$term0 #\]4;232;#$term7 #\]4;256;#$term7 #\]708;[100]#$term0 #\]11;#$term0 #\ diff --git a/.config/quickshell/services/MaterialTheme.qml b/.config/quickshell/services/MaterialTheme.qml new file mode 100644 index 000000000..94b117b42 --- /dev/null +++ b/.config/quickshell/services/MaterialTheme.qml @@ -0,0 +1,53 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common" +import QtQuick +import Quickshell +import Quickshell.Io +import Qt.labs.platform + +Singleton { + id: root + property string filePath: `${StandardPaths.standardLocations(StandardPaths.StateLocation)[0]}/user/generated/colors.json` + + function reapplyTheme() { + themeFileView.reload() + } + + function applyColors(fileContent) { + const json = JSON.parse(fileContent) + for (const key in json) { + if (json.hasOwnProperty(key)) { + // Convert snake_case to CamelCase + const camelCaseKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase()) + const m3Key = `m3${camelCaseKey}` + Appearance.m3colors[m3Key] = json[key] + } + } + } + + Timer { + id: delayedFileRead + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + running: false + onTriggered: { + root.applyColors(themeFileView.text()) + } + } + + FileView { + id: themeFileView + path: root.filePath + watchChanges: true + onFileChanged: { + this.reload() + delayedFileRead.start() + } + onLoadedChanged: { + const fileContent = themeFileView.text() + root.applyColors(fileContent) + } + } +} diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index fdc1f8b15..e6ca21795 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -12,8 +12,14 @@ import QtQuick.Controls import QtQuick.Layouts import QtQuick.Window import Quickshell +import "./services/" ShellRoot { + Component.onCompleted: { + console.log("ShellRoot loaded") + MaterialTheme.reapplyTheme() + } + Bar {} NotificationPopup {} OnScreenDisplayBrightness {} From 35e4626fbcf490c103e4cc2518e3092168c0b198 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 26 Apr 2025 14:18:52 +0200 Subject: [PATCH 102/824] update hyprland config --- .config/hypr/hyprland/keybinds.conf | 10 +++++----- .config/hypr/hyprland/rules.conf | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 4c85c62bc..ee3f91ef3 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -18,7 +18,7 @@ bindle=, XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%- # bind = Super, T, exec, foot # Launch foot (terminal) bind = Super, Return, exec, foot # [hidden] # In case you're from i3 or its Wayland clone bind = , Super, exec, true # Open app launcher -bind = Ctrl+Super, T, exec, ~/.config/ags/scripts/color_generation/switchwall.sh # Change wallpaper +bind = Ctrl+Super, T, exec, ~/.config/quickshell/scripts/switchwall.sh || ~/.config/ags/scripts/color_generation/switchwall.sh # Change wallpaper ##! Actions # Screenshot, Record, OCR, Color picker, Clipboard history bind = Super, V, exec, pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # Clipboard history >> clipboard @@ -160,10 +160,10 @@ bind = Alt, Tab, bringactivetotop, # [hidden] bring it to the top bindr = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool; agsv1 & # Restart widgets bindr = Ctrl+Super+Alt, R, exec, hyprctl reload; killall agsv1 ydotool; agsv1 & # [hidden] bind = Ctrl+Alt, Slash, exec, agsv1 run-js 'cycleMode();' # Cycle bar mode (normal, focus) -bindir = Super, Super_L, exec, agsv1 -t 'overview' # Toggle overview/launcher -bind = Super, Tab, exec, agsv1 -t 'overview' # [hidden] +bindir = Super, Super_L, exec, qs ipc call overview toggle || agsv1 -t 'overview' # Toggle overview/launcher +bind = Super, Tab, exec, qs ipc call overview toggle || agsv1 -t 'overview' # [hidden] bind = Super, Slash, exec, for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "cheatsheet""$i"; done # Show cheatsheet -bind = Super, B, exec, agsv1 -t 'sideleft' # Toggle left sidebar +bind = Super, B, exec, qs ipc call sidebarLeft toggle || agsv1 -t 'sideleft' # Toggle left sidebar bind = Super, A, exec, qs ipc call sidebarLeft toggle || agsv1 -t 'sideleft' # [hidden] bind = Super, O, exec, qs ipc call sidebarLeft toggle || agsv1 -t 'sideleft' # [hidden] bind = Super, N, exec, qs ipc call sidebarRight toggle || agsv1 -t 'sideright' # Toggle right sidebar @@ -196,7 +196,7 @@ bindl= ,XF86AudioPause, exec, playerctl play-pause # [hidden] #! ##! Apps bind = Super, T, exec, # Launch foot (terminal) -bind = Super, E, exec, dolphin --new-window || nautilus --new-window # Launch file manager (Dolphin/Nautilus) +bind = Super, E, exec, nautilus --new-window # Launch file manager (Nautilus) bind = Super, Z, exec, Zed # Launch Zed (editor) bind = Super, C, exec, code # Launch VSCode (editor) bind = Super+Alt, E, exec, thunar # [hidden] diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index b789c0d5d..cf35e5d22 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -111,6 +111,7 @@ layerrule = animation slide left, quickshell:sidebarLeft layerrule = animation slide top, quickshell:onScreenDisplay layerrule = animation fade, quickshell:session layerrule = blur, quickshell:session +layerrule = noanim, quickshell:overview # Launcher needs to be FAST ## outfoxxed's stuff layerrule = blur, shell:bar layerrule = ignorezero, shell:bar From dc53953766c09391ab1466bd797617852eefad03 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 26 Apr 2025 14:25:52 +0200 Subject: [PATCH 103/824] venv: ags -> quickshell --- .config/hypr/hyprland/env.conf | 2 +- diagnose | 2 +- scriptdata/installers | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.config/hypr/hyprland/env.conf b/.config/hypr/hyprland/env.conf index 91e483807..d168b9310 100644 --- a/.config/hypr/hyprland/env.conf +++ b/.config/hypr/hyprland/env.conf @@ -19,7 +19,7 @@ env = QT_QPA_PLATFORMTHEME, qt6ct # ######## Virtual envrionment ######### env = XDG_STATE_HOME, $HOME/.local/state -env = ILLOGICAL_IMPULSE_VIRTUAL_ENV, $XDG_STATE_HOME/ags/.venv +env = ILLOGICAL_IMPULSE_VIRTUAL_ENV, $XDG_STATE_HOME/quickshell/.venv # ############ Others ############# diff --git a/diagnose b/diagnose index b5d607a02..c793d7998 100755 --- a/diagnose +++ b/diagnose @@ -62,7 +62,7 @@ x declare -p XDG_CACHE_HOME # ~/.cache x declare -p XDG_CONFIG_HOME # ~/.config x declare -p XDG_DATA_HOME # ~/.local/share x declare -p XDG_STATE_HOME # ~/.local/state -x declare -p ILLOGICAL_IMPULSE_VIRTUAL_ENV # $XDG_STATE_HOME/ags/.venv +x declare -p ILLOGICAL_IMPULSE_VIRTUAL_ENV # $XDG_STATE_HOME/quickshell/.venv e "Checking directories/files" x ls -l ~/.local/state/ags/.venv diff --git a/scriptdata/installers b/scriptdata/installers index 1aca57c15..bfb824c89 100644 --- a/scriptdata/installers +++ b/scriptdata/installers @@ -123,7 +123,7 @@ install-uv (){ # Both for Arch(based) and other distros. install-python-packages (){ UV_NO_MODIFY_PATH=1 - ILLOGICAL_IMPULSE_VIRTUAL_ENV=$XDG_STATE_HOME/ags/.venv + ILLOGICAL_IMPULSE_VIRTUAL_ENV=$XDG_STATE_HOME/quickshell/.venv x mkdir -p $ILLOGICAL_IMPULSE_VIRTUAL_ENV # we need python 3.12 https://github.com/python-pillow/Pillow/issues/8089 x uv venv --prompt .venv $ILLOGICAL_IMPULSE_VIRTUAL_ENV -p 3.12 From bbd472f4784943e20567254fd8dd89955484fec1 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 26 Apr 2025 14:42:50 +0200 Subject: [PATCH 104/824] terminal colors: cache -> state dir --- .config/quickshell/scripts/applycolor.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.config/quickshell/scripts/applycolor.sh b/.config/quickshell/scripts/applycolor.sh index 3ba7862e2..59ce862b7 100755 --- a/.config/quickshell/scripts/applycolor.sh +++ b/.config/quickshell/scripts/applycolor.sh @@ -10,8 +10,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" term_alpha=100 #Set this to < 100 make all your terminals transparent # sleep 0 # idk i wanted some delay or colors dont get applied properly -if [ ! -d "$CACHE_DIR"/user/generated ]; then - mkdir -p "$CACHE_DIR"/user/generated +if [ ! -d "$STATE_DIR"/user/generated ]; then + mkdir -p "$STATE_DIR"/user/generated fi cd "$CONFIG_DIR" || exit @@ -33,18 +33,18 @@ apply_term() { return fi # Copy template - mkdir -p "$CACHE_DIR"/user/generated/terminal - cp "$SCRIPT_DIR"/terminal/sequences.txt "$CACHE_DIR"/user/generated/terminal/sequences.txt + mkdir -p "$STATE_DIR"/user/generated/terminal + cp "$SCRIPT_DIR"/terminal/sequences.txt "$STATE_DIR"/user/generated/terminal/sequences.txt # Apply colors for i in "${!colorlist[@]}"; do - sed -i "s/${colorlist[$i]} #/${colorvalues[$i]#\#}/g" "$CACHE_DIR"/user/generated/terminal/sequences.txt + sed -i "s/${colorlist[$i]} #/${colorvalues[$i]#\#}/g" "$STATE_DIR"/user/generated/terminal/sequences.txt done - sed -i "s/\$alpha/$term_alpha/g" "$CACHE_DIR/user/generated/terminal/sequences.txt" + sed -i "s/\$alpha/$term_alpha/g" "$STATE_DIR/user/generated/terminal/sequences.txt" for file in /dev/pts/*; do if [[ $file =~ ^/dev/pts/[0-9]+$ ]]; then - cat "$CACHE_DIR"/user/generated/terminal/sequences.txt >"$file" + cat "$STATE_DIR"/user/generated/terminal/sequences.txt >"$file" fi done } From 6ab4c3e7865372eb0f77b70a814f24f9bd3362fc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 26 Apr 2025 14:43:24 +0200 Subject: [PATCH 105/824] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e9c2d4d5..c246d1d00 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ - **Assumption**: You are already using the AGS illogical-impulse - **Install Qt packages** (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-graphicaleffects qt5-imageformats qt5-quickcontrols qt5-quickcontrols2 qt5-svg qt5-translations qt5-wayland qt5-x11extras qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland qt6-webchannel qt6-webengine qt6-websockets qt6-webview` - **Install quickshell and more stuff**: `yay -S quickshell matugen-bin` -- **Copy** `.config/quickshell` folder and hyprland config files in `.config/hypr/hyprland/` (the files are still AGS-friendly and shouldn't affect your use of the normal illogical-impulse) (still, backing up is your responsibility) +- **Copy** `.config/quickshell` folder and hyprland config files in `.config/hypr/hyprland/` (backing up is your responsibility) - **Run quickshell** with `qs` and see how things are - it's not finished for daily use, but **feedback is very welcome** - We currently have bar, right sidebar, search/overview - Tips: scrolled windows are flickable From ffb7872a79b910bb882c618f8e676222513b689f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 26 Apr 2025 14:57:09 +0200 Subject: [PATCH 106/824] hyprland keybinds: move away from scripts in ags folder --- .config/hypr/hyprland/keybinds.conf | 44 +++++++++++------------ .config/hypr/hyprland/workspace_action.sh | 2 ++ README.md | 2 +- 3 files changed, 24 insertions(+), 24 deletions(-) create mode 100755 .config/hypr/hyprland/workspace_action.sh diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index ee3f91ef3..3bfe21045 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -24,7 +24,7 @@ bind = Ctrl+Super, T, exec, ~/.config/quickshell/scripts/switchwall.sh || ~/.con bind = Super, V, exec, pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # Clipboard history >> clipboard bind = Super, Period, exec, pkill fuzzel || ~/.local/bin/fuzzel-emoji # Pick emoji >> clipboard bind = Ctrl+Shift+Alt, Delete, exec, pkill wlogout || wlogout -p layer-shell # [hidden] -bind = Super+Shift, S, exec, ~/.config/ags/scripts/grimblast.sh --freeze copy area # Screen snip +bind = Super+Shift, S, exec, grimblast --freeze copy area # Screen snip bind = Super+Shift+Alt, S, exec, grim -g "$(slurp)" - | swappy -f - # Screen snip >> edit # OCR bind = Super+Shift,T,exec,grim -g "$(slurp $SLURP_ARGS)" "tmp.png" && tesseract -l eng "tmp.png" - | wl-copy && rm "tmp.png" # Screen snip to text >> clipboard @@ -85,16 +85,16 @@ bind = Super, D, fullscreen, 1 ##! Workspace navigation # Switching #/# bind = Super, Hash,, # Focus workspace # (1, 2, 3, 4, ...) -bind = Super, 1, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh workspace 1 # [hidden] -bind = Super, 2, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh workspace 2 # [hidden] -bind = Super, 3, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh workspace 3 # [hidden] -bind = Super, 4, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh workspace 4 # [hidden] -bind = Super, 5, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh workspace 5 # [hidden] -bind = Super, 6, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh workspace 6 # [hidden] -bind = Super, 7, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh workspace 7 # [hidden] -bind = Super, 8, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh workspace 8 # [hidden] -bind = Super, 9, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh workspace 9 # [hidden] -bind = Super, 0, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh workspace 10 # [hidden] +bind = Super, 1, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 1 # [hidden] +bind = Super, 2, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 2 # [hidden] +bind = Super, 3, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 3 # [hidden] +bind = Super, 4, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 4 # [hidden] +bind = Super, 5, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 5 # [hidden] +bind = Super, 6, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 6 # [hidden] +bind = Super, 7, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 7 # [hidden] +bind = Super, 8, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 8 # [hidden] +bind = Super, 9, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 9 # [hidden] +bind = Super, 0, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 10 # [hidden] #/# bind = Super, Scroll ↑/↓,, # Workspace: focus left/right bind = Super, mouse_up, workspace, +1 # [hidden] @@ -119,16 +119,16 @@ bind = Super, mouse:275, togglespecialworkspace, ##! Workspace management # Move window to workspace Super + Alt + [0-9] #/# bind = Super+Alt, Hash,, # Window: move to workspace # (1, 2, 3, 4, ...) -bind = Super+Alt, 1, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh movetoworkspacesilent 1 # [hidden] -bind = Super+Alt, 2, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh movetoworkspacesilent 2 # [hidden] -bind = Super+Alt, 3, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh movetoworkspacesilent 3 # [hidden] -bind = Super+Alt, 4, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh movetoworkspacesilent 4 # [hidden] -bind = Super+Alt, 5, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh movetoworkspacesilent 5 # [hidden] -bind = Super+Alt, 6, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh movetoworkspacesilent 6 # [hidden] -bind = Super+Alt, 7, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh movetoworkspacesilent 7 # [hidden] -bind = Super+Alt, 8, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh movetoworkspacesilent 8 # [hidden] -bind = Super+Alt, 9, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh movetoworkspacesilent 9 # [hidden] -bind = Super+Alt, 0, exec, ~/.config/ags/scripts/hyprland/workspace_action.sh movetoworkspacesilent 10 # [hidden] +bind = Super+Alt, 1, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 1 # [hidden] +bind = Super+Alt, 2, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 2 # [hidden] +bind = Super+Alt, 3, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 3 # [hidden] +bind = Super+Alt, 4, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 4 # [hidden] +bind = Super+Alt, 5, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 5 # [hidden] +bind = Super+Alt, 6, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 6 # [hidden] +bind = Super+Alt, 7, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 7 # [hidden] +bind = Super+Alt, 8, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 8 # [hidden] +bind = Super+Alt, 9, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 9 # [hidden] +bind = Super+Alt, 0, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 10 # [hidden] bind = Ctrl+Super+Shift, Up, movetoworkspacesilent, special # [hidden] @@ -184,8 +184,6 @@ bind = Super+Alt, Equal, exec, notify-send "Urgent notification" "Ah hell no" -u bindl= Super+Shift, N, exec, playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` # Next track bindl= ,XF86AudioNext, exec, playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` # [hidden] bindl= ,XF86AudioPrev, exec, playerctl previous # [hidden] -bindel = Super+Shift, Comma, exec, ~/.config/ags/scripts/music/adjust-volume.sh -0.03 # Raise music volume -bindel = Super+Shift, Period, exec, ~/.config/ags/scripts/music/adjust-volume.sh 0.03 # Lower music volume bind = Super+Shift+Alt, mouse:275, exec, playerctl previous # [hidden] bind = Super+Shift+Alt, mouse:276, exec, playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` # [hidden] bindl= Super+Shift, B, exec, playerctl previous # Previous track diff --git a/.config/hypr/hyprland/workspace_action.sh b/.config/hypr/hyprland/workspace_action.sh new file mode 100755 index 000000000..a43badfc8 --- /dev/null +++ b/.config/hypr/hyprland/workspace_action.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +hyprctl dispatch "$1" $(((($(hyprctl activeworkspace -j | jq -r .id) - 1) / 10) * 10 + $2)) diff --git a/README.md b/README.md index c246d1d00..77c395dc6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ - **Assumption**: You are already using the AGS illogical-impulse - **Install Qt packages** (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-graphicaleffects qt5-imageformats qt5-quickcontrols qt5-quickcontrols2 qt5-svg qt5-translations qt5-wayland qt5-x11extras qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland qt6-webchannel qt6-webengine qt6-websockets qt6-webview` -- **Install quickshell and more stuff**: `yay -S quickshell matugen-bin` +- **Install quickshell and more stuff**: `yay -S quickshell matugen-bin grimblast` - **Copy** `.config/quickshell` folder and hyprland config files in `.config/hypr/hyprland/` (backing up is your responsibility) - **Run quickshell** with `qs` and see how things are - it's not finished for daily use, but **feedback is very welcome** - We currently have bar, right sidebar, search/overview From 6f6b3876fbe8ac04320a290c3bfa1600ac633bbf Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 26 Apr 2025 15:28:11 +0200 Subject: [PATCH 107/824] Update config.fish --- .config/fish/config.fish | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/fish/config.fish b/.config/fish/config.fish index 2eab1b489..7f9114d72 100755 --- a/.config/fish/config.fish +++ b/.config/fish/config.fish @@ -13,8 +13,8 @@ if status is-interactive end starship init fish | source -if test -f ~/.cache/ags/user/generated/terminal/sequences.txt - cat ~/.cache/ags/user/generated/terminal/sequences.txt +if test -f ~/.local/state/quickshell/user/generated/terminal/sequences.txt + cat ~/.local/state/quickshell/user/generated/terminal/sequences.txt end alias pamcan=pacman From af4ecc55ce965c8d70b10c11e35fe435ce5307c8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 26 Apr 2025 21:24:08 +0200 Subject: [PATCH 108/824] overview: drag and drop to move windows --- .../modules/overview/OverviewWidget.qml | 166 ++++++++++++++---- .../modules/overview/OverviewWindow.qml | 41 ++--- .../modules/overview/SearchWidget.qml | 1 + .../quickshell/modules/session/Session.qml | 2 +- .../modules/sidebarRight/todo/TodoWidget.qml | 1 + .config/quickshell/services/HyprlandData.qml | 5 +- 6 files changed, 150 insertions(+), 66 deletions(-) diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index f586af2e1..b20f4eea8 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -24,8 +24,22 @@ Item { property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id) property real scale: ConfigOptions.overview.scale + property real workspaceImplicitWidth: (monitor.width - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale + property real workspaceImplicitHeight: (monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale + property real workspaceNumberMargin: 80 property real workspaceNumberSize: 80 + property int workspaceZ: 0 + property int windowZ: 1 + property int windowDraggingZ: 99999 + property real workspaceSpacing: 5 + + property int draggingFromWorkspace: -1 + property int draggingTargetWorkspace: -1 + + onDraggingFromWorkspaceChanged: { + console.log("draggingTargetWorkspace", draggingFromWorkspace) + } implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 @@ -43,21 +57,23 @@ Item { anchors.fill: parent - implicitWidth: columnLayout.implicitWidth + 5 * 2 - implicitHeight: columnLayout.implicitHeight + 5 * 2 + implicitWidth: workspaceColumnLayout.implicitWidth + 5 * 2 + implicitHeight: workspaceColumnLayout.implicitHeight + 5 * 2 color: Appearance.colors.colLayer0 radius: Appearance.rounding.screenRounding * root.scale + 5 * 2 ColumnLayout { - id: columnLayout - anchors.centerIn: parent - spacing: 5 + id: workspaceColumnLayout + z: root.workspaceZ + anchors.centerIn: parent + spacing: workspaceSpacing Repeater { model: ConfigOptions.overview.numOfRows delegate: RowLayout { id: row property int rowIndex: index + spacing: workspaceSpacing Repeater { // Workspace repeater model: ConfigOptions.overview.numOfCols @@ -65,44 +81,127 @@ Item { id: workspace property int colIndex: index property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * ConfigOptions.overview.numOfCols + colIndex + 1 + property color defaultColor: Appearance.colors.colLayer1 // TODO: reconsider this color for a cleaner look - implicitWidth: (monitor.width - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale - implicitHeight: (monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale - color: Appearance.colors.colLayer1 // TODO: reconsider this color for a cleaner look + implicitWidth: root.workspaceImplicitWidth + implicitHeight: root.workspaceImplicitHeight + color: defaultColor radius: Appearance.rounding.screenRounding * root.scale + border.width: 2 + border.color: "transparent" MouseArea { - id: mouseArea + id: workspaceArea anchors.fill: parent - onClicked: (event) => { - closeOverview.running = true - Hyprland.dispatch(`workspace ${workspace.workspaceValue}`) + acceptedButtons: Qt.LeftButton + onClicked: { + if (root.draggingTargetWorkspace === -1) { + closeOverview.running = true + Hyprland.dispatch(`workspace ${workspaceValue}`) + } } } - StyledText { - z: 9999 - anchors.left: parent.left - anchors.top: parent.top - anchors.leftMargin: root.workspaceNumberMargin * root.scale - anchors.topMargin: root.workspaceNumberMargin * root.scale - font.pixelSize: root.workspaceNumberSize * root.scale - color: Appearance.colors.colSubtext - text: workspaceValue + DropArea { + anchors.fill: parent + onEntered: { + root.draggingTargetWorkspace = workspaceValue + if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return; + border.color = Appearance.colors.colLayer2Hover + workspace.color = Appearance.mix(defaultColor, Appearance.colors.colLayer1Hover, 0.1) + } + onExited: { + border.color = "transparent" + workspace.color = defaultColor + if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1 + } } - Repeater { // Window repeater - model: windowAddresses.filter((address) => { - var win = windowByAddress[address] - return (win?.workspace?.id === workspace.workspaceValue) - }) - delegate: OverviewWindow { - windowData: windowByAddress[modelData] - monitorData: root.monitorData - scale: root.scale - availableWorkspaceWidth: workspace.implicitWidth - availableWorkspaceHeight: workspace.implicitHeight - } + } + } + } + } + } + + Item { + id: windowSpace + anchors.centerIn: parent + implicitWidth: workspaceColumnLayout.implicitWidth + implicitHeight: workspaceColumnLayout.implicitHeight + + Repeater { // Window repeater + model: windowAddresses.filter((address) => { + var win = windowByAddress[address] + return (root.workspaceGroup * root.workspacesShown < win.workspace.id && win.workspace.id <= (root.workspaceGroup + 1) * root.workspacesShown) + }) + delegate: OverviewWindow { + id: window + windowData: windowByAddress[modelData] + monitorData: root.monitorData + scale: root.scale + availableWorkspaceWidth: root.workspaceImplicitWidth + availableWorkspaceHeight: root.workspaceImplicitHeight + + property bool atInitPosition: (initX == x && initY == y) + restrictToWorkspace: Drag.active || atInitPosition + + property int workspaceColIndex: (windowData?.workspace.id - 1) % ConfigOptions.overview.numOfCols + property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / ConfigOptions.overview.numOfCols) + xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex + yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex + + Timer { + id: updateWindowPosition + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + running: false + onTriggered: { + window.x = Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset + window.y = Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset + } + } + + z: atInitPosition ? root.windowZ : root.windowDraggingZ + Drag.hotSpot.x: targetWindowWidth / 2 + Drag.hotSpot.y: targetWindowHeight / 2 + MouseArea { + id: dragArea + anchors.fill: parent + hoverEnabled: true + onEntered: hovered = true + onExited: hovered = false + acceptedButtons: Qt.LeftButton | Qt.MiddleButton + drag.target: parent + onPressed: { + root.draggingFromWorkspace = windowData?.workspace.id + window.pressed = true + window.Drag.active = true + window.Drag.source = window + } + onReleased: { + const targetWorkspace = root.draggingTargetWorkspace + window.pressed = false + window.Drag.active = false + root.draggingFromWorkspace = -1 + if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) { + Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace}, address:${window.windowData?.address}`) + updateWindowPosition.restart() + } + else { + window.x = window.initX + window.y = window.initY + } + } + onClicked: (event) => { + if (!windowData) return; + + if (event.button === Qt.LeftButton) { + closeOverview.running = true + Hyprland.dispatch(`workspace ${windowData.workspace.id}`) + event.accepted = true + } else if (event.button === Qt.MiddleButton) { + Hyprland.dispatch(`closewindow address:${windowData.address}`) + event.accepted = true } } } @@ -112,6 +211,7 @@ Item { } DropShadow { + z: -9999 anchors.fill: overviewBackground horizontalOffset: 0 verticalOffset: 2 diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index f492ee155..13343342b 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -18,6 +18,11 @@ Rectangle { // Window property var scale property var availableWorkspaceWidth property var availableWorkspaceHeight + property bool restrictToWorkspace: true + property real initX: Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset + property real initY: Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset + property real xOffset: 0 + property real yOffset: 0 property var targetWindowWidth: windowData?.size[0] * scale property var targetWindowHeight: windowData?.size[1] * scale @@ -29,12 +34,11 @@ Rectangle { // Window property var iconToWindowRatioCompact: 0.6 property var iconPath: Quickshell.iconPath(Icons.noKnowledgeIconGuess(windowData?.class)) property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth - - z: 1 - x: Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) - y: Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) - width: Math.min(windowData?.size[0] * root.scale, availableWorkspaceWidth - x) - height: Math.min(windowData?.size[1] * root.scale, availableWorkspaceHeight - y) + + x: initX + y: initY + width: Math.min(windowData?.size[0] * root.scale, (restrictToWorkspace ? windowData?.size[0] : availableWorkspaceWidth - x + xOffset)) + height: Math.min(windowData?.size[1] * root.scale, (restrictToWorkspace ? windowData?.size[1] : availableWorkspaceHeight - y + yOffset)) radius: Appearance.rounding.windowRounding * root.scale color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 @@ -72,29 +76,6 @@ Rectangle { // Window command: ["bash", "-c", "qs ipc call overview close &"] // Somehow has to by async to work? } - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - onEntered: root.hovered = true - onExited: root.hovered = false - onPressed: root.pressed = true - onReleased: root.pressed = false - acceptedButtons: Qt.LeftButton | Qt.MiddleButton - onClicked: (event) => { - if (!windowData) return; - - if (event.button === Qt.LeftButton) { - closeOverview.running = true - Hyprland.dispatch(`workspace ${windowData.workspace.id}`) - event.accepted = true - } else if (event.button === Qt.MiddleButton) { - Hyprland.dispatch(`closewindow address:${windowData.address}`) - event.accepted = true - } - } - } - ColumnLayout { anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left @@ -116,7 +97,7 @@ Rectangle { // Window IconImage { id: xwaylandIndicator - visible: ConfigOptions.overview.showXwaylandIndicator && windowData?.xwayland + visible: (ConfigOptions.overview.showXwaylandIndicator && windowData?.xwayland) ?? false anchors.right: parent.right anchors.bottom: parent.bottom source: Quickshell.iconPath("xorg") diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 73a2d96d0..ab694f6fc 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -215,6 +215,7 @@ Item { // Wrapper focus: root.panelWindow.visible || GlobalStates.overviewOpen Layout.rightMargin: 15 padding: 15 + renderType: Text.NativeRendering color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant selectedTextColor: Appearance.m3colors.m3onPrimary selectionColor: Appearance.m3colors.m3primary diff --git a/.config/quickshell/modules/session/Session.qml b/.config/quickshell/modules/session/Session.qml index a2be293da..947f44b11 100644 --- a/.config/quickshell/modules/session/Session.qml +++ b/.config/quickshell/modules/session/Session.qml @@ -233,7 +233,7 @@ Scope { } Process { id: logout - command: ["bash", "-c", "loginctl terminate-session $XDG_SESSION_ID"] + command: ["bash", "-c", "pkill Hyprland"] // loginctl terminate-session hangs SDDM } Process { id: hibernate diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index 7db2e1c50..6878754e0 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -273,6 +273,7 @@ Item { Layout.rightMargin: 16 padding: 10 color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant + renderType: Text.NativeRendering selectedTextColor: Appearance.m3colors.m3onPrimary selectionColor: Appearance.m3colors.m3primary placeholderText: qsTr("Task description") diff --git a/.config/quickshell/services/HyprlandData.qml b/.config/quickshell/services/HyprlandData.qml index bc129465d..5ca4cf6fa 100644 --- a/.config/quickshell/services/HyprlandData.qml +++ b/.config/quickshell/services/HyprlandData.qml @@ -43,11 +43,12 @@ Singleton { stdout: SplitParser { onRead: (data) => { root.windowList = JSON.parse(data) - root.windowByAddress = {} + let tempWinByAddress = {} for (var i = 0; i < root.windowList.length; ++i) { var win = root.windowList[i] - root.windowByAddress[win.address] = win + tempWinByAddress[win.address] = win } + root.windowByAddress = tempWinByAddress root.addresses = root.windowList.map((win) => win.address) } } From f52ba7ad2b517a82d13789bd181f2c6147b3fabd Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 26 Apr 2025 21:32:27 +0200 Subject: [PATCH 109/824] remove debug print --- .config/quickshell/modules/overview/OverviewWidget.qml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index b20f4eea8..636564b15 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -37,10 +37,6 @@ Item { property int draggingFromWorkspace: -1 property int draggingTargetWorkspace: -1 - onDraggingFromWorkspaceChanged: { - console.log("draggingTargetWorkspace", draggingFromWorkspace) - } - implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 From 5e8f4048dadf898f51ccea7d3447bce0423ff4c6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 26 Apr 2025 23:50:46 +0200 Subject: [PATCH 110/824] show biggest app icon on workspaces, faster keybinds --- .config/hypr/hyprland/keybinds.conf | 16 ++- .config/quickshell/GlobalStates.qml | 28 +++++ .../quickshell/modules/bar/SysTrayItem.qml | 15 ++- .config/quickshell/modules/bar/Workspaces.qml | 57 +++++++-- .../quickshell/modules/common/Appearance.qml | 112 +++++++++--------- .../modules/common/ConfigOptions.qml | 6 +- .../{overview => common/functions}/icons.js | 0 .../common/widgets/NotificationWidget.qml | 2 +- .../quickshell/modules/overview/Overview.qml | 17 +++ .../modules/overview/OverviewWidget.qml | 3 +- .../modules/overview/OverviewWindow.qml | 2 +- .../quickshell/modules/session/Session.qml | 14 +++ .../modules/sidebarRight/SidebarRight.qml | 35 +++++- .config/quickshell/shell.qml | 1 - 14 files changed, 225 insertions(+), 83 deletions(-) rename .config/quickshell/modules/{overview => common/functions}/icons.js (100%) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 3bfe21045..2dc2df7c7 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -160,21 +160,27 @@ bind = Alt, Tab, bringactivetotop, # [hidden] bring it to the top bindr = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool; agsv1 & # Restart widgets bindr = Ctrl+Super+Alt, R, exec, hyprctl reload; killall agsv1 ydotool; agsv1 & # [hidden] bind = Ctrl+Alt, Slash, exec, agsv1 run-js 'cycleMode();' # Cycle bar mode (normal, focus) -bindir = Super, Super_L, exec, qs ipc call overview toggle || agsv1 -t 'overview' # Toggle overview/launcher -bind = Super, Tab, exec, qs ipc call overview toggle || agsv1 -t 'overview' # [hidden] +bindr = Super, Super_L, exec, qs ipc call overview toggle || agsv1 -t 'overview' # Toggle overview/launcher +# bind = Super, Tab, exec, qs ipc call overview toggle || agsv1 -t 'overview' # [hidden] bind = Super, Slash, exec, for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "cheatsheet""$i"; done # Show cheatsheet bind = Super, B, exec, qs ipc call sidebarLeft toggle || agsv1 -t 'sideleft' # Toggle left sidebar bind = Super, A, exec, qs ipc call sidebarLeft toggle || agsv1 -t 'sideleft' # [hidden] bind = Super, O, exec, qs ipc call sidebarLeft toggle || agsv1 -t 'sideleft' # [hidden] -bind = Super, N, exec, qs ipc call sidebarRight toggle || agsv1 -t 'sideright' # Toggle right sidebar +# bind = Super, N, exec, qs ipc call sidebarRight toggle || agsv1 -t 'sideright' # Toggle right sidebar bind = Super, M, exec, agsv1 run-js 'openMusicControls.value = (!mpris.getPlayer() ? false : !openMusicControls.value);' # Toggle music controls bind = Super, Comma, exec, agsv1 run-js 'openColorScheme.value = true; Utils.timeout(2000, () => openColorScheme.value = false);' # View color scheme and options bind = Super, K, exec, for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "osk""$i"; done # Toggle on-screen keyboard -bind = Ctrl+Alt, Delete, exec, qs ipc call session toggle || for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "session""$i"; done # Toggle power menu +# bind = Ctrl+Alt, Delete, exec, qs ipc call session toggle || for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "session""$i"; done # Toggle power menu bind = Ctrl+Super, G, exec, for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "crosshair""$i"; done # Toggle crosshair bindle=, XF86MonBrightnessUp, exec, qs ipc call brightness increment || agsv1 run-js 'brightness.screen_value += 0.05; indicator.popup(1);' # [hidden] bindle=, XF86MonBrightnessDown, exec, qs ipc call brightness decrement || agsv1 run-js 'brightness.screen_value -= 0.05; indicator.popup(1);' # [hidden] +# bind = Super, Super_L, global, quickshell:overviewToggleRelease +bindit = ,Super_L, global, quickshell:workspaceNumber +bind = Super, Tab, global, quickshell:overviewToggle +bind = Super, N, global, quickshell:sidebarRightToggle +bind = Ctrl+Alt, Delete, global, quickshell:sessionToggle + # Testing bind = Super+Alt, f11, exec, bash -c 'RANDOM_IMAGE=$(find ~/Pictures -type f | grep -v -i "nipple" | grep -v -i "pussy" | shuf -n 1); ACTION=$(notify-send "Test notification with body image" "This notification should contain your user account image and Discord icon. Oh and here is a random image in your Pictures folder: \"Testing" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Open the random image" -A "action3=Useless button"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] bind = Super+Alt, f12, exec, bash -c 'ACTION=$(notify-send "Test notification" "This notification should contain your user account image and Discord icon.\nFlick right to dismiss!" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Useless button" -A "action3=Cry more"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] @@ -214,3 +220,5 @@ bind = Super+Alt, Slash, exec, pkill fuzzel || fuzzel # Toggle fallback launcher bind = Ctrl+Super, Backslash, resizeactive, exact 640 480 # [hidden] + + diff --git a/.config/quickshell/GlobalStates.qml b/.config/quickshell/GlobalStates.qml index 674d586c3..dffb7f4b6 100644 --- a/.config/quickshell/GlobalStates.qml +++ b/.config/quickshell/GlobalStates.qml @@ -1,9 +1,37 @@ +import "root:/modules/common/" import QtQuick import Quickshell +import Quickshell.Hyprland +import Quickshell.Io pragma Singleton pragma ComponentBehavior: Bound Singleton { + id: root property int sidebarRightOpenCount: 0 property bool overviewOpen: false + property bool workspaceShowNumbers: false + + Timer { + id: workspaceShowNumbersTimer + interval: ConfigOptions.bar.workspaces.showNumberDelay + // interval: 0 + repeat: false + onTriggered: { + workspaceShowNumbers = true + } + } + + GlobalShortcut { + name: "workspaceNumber" + description: "Hold to show workspace numbers, release to show icons" + + onPressed: { + workspaceShowNumbersTimer.start() + } + onReleased: { + workspaceShowNumbersTimer.stop() + workspaceShowNumbers = false + } + } } \ No newline at end of file diff --git a/.config/quickshell/modules/bar/SysTrayItem.qml b/.config/quickshell/modules/bar/SysTrayItem.qml index c0c2e6bad..70804a17e 100644 --- a/.config/quickshell/modules/bar/SysTrayItem.qml +++ b/.config/quickshell/modules/bar/SysTrayItem.qml @@ -1,8 +1,10 @@ +import "root:/modules/common/" import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Services.SystemTray import Quickshell.Widgets +import Qt5Compat.GraphicalEffects MouseArea { id: root @@ -21,9 +23,9 @@ MouseArea { item.activate(); break; case Qt.RightButton: - if (item.hasMenu) - menu.open(); - + if (item.hasMenu) menu.open(); + event.accepted = true; + break; } } @@ -39,10 +41,17 @@ MouseArea { } IconImage { + id: trayIcon source: root.item.icon anchors.centerIn: parent width: parent.width height: parent.height } + ColorOverlay { + anchors.fill: trayIcon + source: trayIcon + color: Appearance.colors.colOnLayer0 + } + } diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index f1311ec79..f2722cf75 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -1,5 +1,8 @@ +import "root:/" +import "root:/services/" import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/icons.js" as Icons import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -7,18 +10,21 @@ import Quickshell import Quickshell.Wayland import Quickshell.Hyprland import Quickshell.Io +import Quickshell.Widgets +import Qt5Compat.GraphicalEffects Item { required property var bar readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) readonly property Toplevel activeWindow: ToplevelManager.activeToplevel - readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / ConfigOptions.bar.workspacesShown) + readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / ConfigOptions.bar.workspaces.shown) property list workspaceOccupied: [] property int widgetPadding: 4 property int workspaceButtonWidth: 26 + property int workspaceIconSize: workspaceButtonWidth * 0.8 property int activeWorkspaceMargin: 1 - property double animatedActiveWorkspaceIndex: (monitor.activeWorkspace?.id - 1) % ConfigOptions.bar.workspacesShown + property double animatedActiveWorkspaceIndex: (monitor.activeWorkspace?.id - 1) % ConfigOptions.bar.workspaces.shown Behavior on animatedActiveWorkspaceIndex { NumberAnimation { @@ -30,8 +36,8 @@ Item { // Function to update workspaceOccupied function updateWorkspaceOccupied() { - workspaceOccupied = Array.from({ length: ConfigOptions.bar.workspacesShown }, (_, i) => { - return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * ConfigOptions.bar.workspacesShown + i + 1); + workspaceOccupied = Array.from({ length: ConfigOptions.bar.workspaces.shown }, (_, i) => { + return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * ConfigOptions.bar.workspaces.shown + i + 1); }) } @@ -91,7 +97,7 @@ Item { implicitHeight: 40 Repeater { - model: ConfigOptions.bar.workspacesShown + model: ConfigOptions.bar.workspaces.shown Rectangle { z: 1 @@ -156,7 +162,7 @@ Item { implicitHeight: 40 Repeater { - model: ConfigOptions.bar.workspacesShown + model: ConfigOptions.bar.workspaces.shown Button { id: button @@ -165,19 +171,31 @@ Item { width: workspaceButtonWidth background: Item { + id: workspaceButtonBackground implicitWidth: workspaceButtonWidth implicitHeight: workspaceButtonWidth + property int workspaceValue: workspaceGroup * ConfigOptions.bar.workspaces.shown + index + 1 + property var biggestWindow: { + const windowsInThisWorkspace = HyprlandData.windowList.filter(w => w.workspace.id == workspaceButtonBackground.workspaceValue) + return windowsInThisWorkspace.reduce((maxWin, win) => { + const maxArea = (maxWin?.size?.[0] ?? 0) * (maxWin?.size?.[1] ?? 0) + const winArea = (win?.size?.[0] ?? 0) * (win?.size?.[1] ?? 0) + return winArea > maxArea ? win : maxWin + }, null) + } + property var mainAppIconSource: Quickshell.iconPath(Icons.noKnowledgeIconGuess(biggestWindow?.class)) + StyledText { + opacity: (ConfigOptions.bar.workspaces.alwaysShowNumbers || GlobalStates.workspaceShowNumbers || !workspaceButtonBackground.biggestWindow) ? 1 : 0 z: 3 - property int workspaceValue: workspaceGroup * ConfigOptions.bar.workspacesShown + index + 1 anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter font.pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== "10") * 2) - text: `${workspaceValue}` + text: `${workspaceButtonBackground.workspaceValue}` elide: Text.ElideRight - color: (monitor.activeWorkspace?.id == workspaceValue) ? Appearance.m3colors.m3onPrimary : (workspaceOccupied[index] ? Appearance.colors.colOnLayer1 : Appearance.colors.colOnLayer1Inactive) + color: (monitor.activeWorkspace?.id == workspaceButtonBackground.workspaceValue) ? Appearance.m3colors.m3onPrimary : (workspaceOccupied[index] ? Appearance.colors.colOnLayer1 : Appearance.colors.colOnLayer1Inactive) Behavior on color { ColorAnimation { @@ -187,6 +205,27 @@ Item { } + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } + + } + IconImage { + id: mainAppIcon + opacity: (workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? 1 : 0 + source: workspaceButtonBackground.mainAppIconSource + anchors.centerIn: parent + implicitSize: workspaceIconSize + + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } } } diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index fecdfb8e7..cbb54071c 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -26,64 +26,64 @@ Singleton { m3colors: QtObject { property bool darkmode: false property bool transparent: false - property color m3primary_paletteKeyColor: "#8A7175" - property color m3secondary_paletteKeyColor: "#847376" - property color m3tertiary_paletteKeyColor: "#8F6E74" - property color m3neutral_paletteKeyColor: "#7B7676" - property color m3neutral_variant_paletteKeyColor: "#7B7676" - property color m3background: "#FFF8F7" - property color m3onBackground: "#1E1B1B" - property color m3surface: "#FFF8F7" - property color m3surfaceDim: "#E0D8D8" - property color m3surfaceBright: "#FFF8F7" - property color m3surfaceContainerLowest: "#FFFFFF" - property color m3surfaceContainerLow: "#FAF2F2" - property color m3surfaceContainer: "#F4ECEC" - property color m3surfaceContainerHigh: "#EEE6E6" - property color m3surfaceContainerHighest: "#E8E1E1" - property color m3onSurface: "#1E1B1B" - property color m3surfaceVariant: "#E8E1E1" - property color m3onSurfaceVariant: "#4A4646" - property color m3inverseSurface: "#332F30" - property color m3inverseOnSurface: "#F7EFEF" - property color m3outline: "#797373" - property color m3outlineVariant: "#CCC5C5" + property color m3primary_paletteKeyColor: "#91689E" + property color m3secondary_paletteKeyColor: "#837186" + property color m3tertiary_paletteKeyColor: "#9D6A67" + property color m3neutral_paletteKeyColor: "#7C757B" + property color m3neutral_variant_paletteKeyColor: "#7D747D" + property color m3background: "#161217" + property color m3onBackground: "#EAE0E7" + property color m3surface: "#161217" + property color m3surfaceDim: "#161217" + property color m3surfaceBright: "#3D373D" + property color m3surfaceContainerLowest: "#110D12" + property color m3surfaceContainerLow: "#1F1A1F" + property color m3surfaceContainer: "#231E23" + property color m3surfaceContainerHigh: "#2D282E" + property color m3surfaceContainerHighest: "#383339" + property color m3onSurface: "#EAE0E7" + property color m3surfaceVariant: "#4C444D" + property color m3onSurfaceVariant: "#CFC3CD" + property color m3inverseSurface: "#EAE0E7" + property color m3inverseOnSurface: "#342F34" + property color m3outline: "#988E97" + property color m3outlineVariant: "#4C444D" property color m3shadow: "#000000" property color m3scrim: "#000000" - property color m3surfaceTint: "#70585D" - property color m3primary: "#70585D" - property color m3onPrimary: "#FFFFFF" - property color m3primaryContainer: "#FADBE0" - property color m3onPrimaryContainer: "#564145" - property color m3inversePrimary: "#DDBFC4" - property color m3secondary: "#6A5A5D" - property color m3onSecondary: "#FFFFFF" - property color m3secondaryContainer: "#F3DDE0" - property color m3onSecondaryContainer: "#524346" - property color m3tertiary: "#8D6C72" - property color m3onTertiary: "#FFFFFF" - property color m3tertiaryContainer: "#8D6C72" - property color m3onTertiaryContainer: "#FFFFFF" - property color m3error: "#BA1A1A" - property color m3onError: "#FFFFFF" - property color m3errorContainer: "#FFDAD6" - property color m3onErrorContainer: "#93000A" - property color m3primaryFixed: "#FADBE0" - property color m3primaryFixedDim: "#DDBFC4" - property color m3onPrimaryFixed: "#28171A" - property color m3onPrimaryFixedVariant: "#564145" - property color m3secondaryFixed: "#F3DDE0" - property color m3secondaryFixedDim: "#D6C2C4" - property color m3onSecondaryFixed: "#24191B" - property color m3onSecondaryFixedVariant: "#524346" - property color m3tertiaryFixed: "#FFD9DF" - property color m3tertiaryFixedDim: "#E4BDC3" - property color m3onTertiaryFixed: "#2B151A" - property color m3onTertiaryFixedVariant: "#5B3F45" - property color m3success: "#4F6354" - property color m3onSuccess: "#FFFFFF" - property color m3successContainer: "#D1E8D5" - property color m3onSuccessContainer: "#0C1F13" + property color m3surfaceTint: "#E5B6F2" + property color m3primary: "#E5B6F2" + property color m3onPrimary: "#452152" + property color m3primaryContainer: "#5D386A" + property color m3onPrimaryContainer: "#F9D8FF" + property color m3inversePrimary: "#775084" + property color m3secondary: "#D5C0D7" + property color m3onSecondary: "#392C3D" + property color m3secondaryContainer: "#534457" + property color m3onSecondaryContainer: "#F2DCF3" + property color m3tertiary: "#F5B7B3" + property color m3onTertiary: "#4C2523" + property color m3tertiaryContainer: "#BA837F" + property color m3onTertiaryContainer: "#000000" + property color m3error: "#FFB4AB" + property color m3onError: "#690005" + property color m3errorContainer: "#93000A" + property color m3onErrorContainer: "#FFDAD6" + property color m3primaryFixed: "#F9D8FF" + property color m3primaryFixedDim: "#E5B6F2" + property color m3onPrimaryFixed: "#2E0A3C" + property color m3onPrimaryFixedVariant: "#5D386A" + property color m3secondaryFixed: "#F2DCF3" + property color m3secondaryFixedDim: "#D5C0D7" + property color m3onSecondaryFixed: "#241727" + property color m3onSecondaryFixedVariant: "#514254" + property color m3tertiaryFixed: "#FFDAD7" + property color m3tertiaryFixedDim: "#F5B7B3" + property color m3onTertiaryFixed: "#331110" + property color m3onTertiaryFixedVariant: "#663B39" + property color m3success: "#B5CCBA" + property color m3onSuccess: "#213528" + property color m3successContainer: "#374B3E" + property color m3onSuccessContainer: "#D1E9D6" property color term0: "#EDE4E4" property color term1: "#B52755" property color term2: "#A97363" diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 2dff5bf6c..19a374d1c 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -18,12 +18,16 @@ Singleton { } property QtObject bar: QtObject { - property int workspacesShown: 10 property int batteryLowThreshold: 20 property QtObject resources: QtObject { property bool alwaysShowSwap: true property bool alwaysShowCpu: false } + property QtObject workspaces: QtObject { + property int shown: 10 + property bool alwaysShowNumbers: false + property int showNumberDelay: 150 // milliseconds + } } property QtObject osd: QtObject { diff --git a/.config/quickshell/modules/overview/icons.js b/.config/quickshell/modules/common/functions/icons.js similarity index 100% rename from .config/quickshell/modules/overview/icons.js rename to .config/quickshell/modules/common/functions/icons.js diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 6ce566051..095653d7b 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -25,7 +25,7 @@ Item { Process { id: closeSidebarProcess - command: ["bash", "-c", `qs ipc call sidebarRight close`] + command: ["qs", "ipc", "call", "sidebarRight", "close"] } Process { diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 7a7599166..c12143f52 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -113,4 +113,21 @@ Scope { } } + GlobalShortcut { + name: "overviewToggle" + description: "Toggles overview on press" + + onPressed: { + GlobalStates.overviewOpen = !GlobalStates.overviewOpen + } + } + GlobalShortcut { + name: "overviewToggleRelease" + description: "Toggles overview on release" + + onReleased: { + GlobalStates.overviewOpen = !GlobalStates.overviewOpen + } + } + } diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index 636564b15..b1db1ec58 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -9,7 +9,6 @@ import Quickshell.Io import Quickshell.Widgets import Quickshell.Wayland import Quickshell.Hyprland -import "./icons.js" as Icons Item { id: root @@ -128,7 +127,7 @@ Item { Repeater { // Window repeater model: windowAddresses.filter((address) => { var win = windowByAddress[address] - return (root.workspaceGroup * root.workspacesShown < win.workspace.id && win.workspace.id <= (root.workspaceGroup + 1) * root.workspacesShown) + return (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown) }) delegate: OverviewWindow { id: window diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index 13343342b..d44a4c9f4 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -1,3 +1,4 @@ +import "root:/modules/common/functions/icons.js" as Icons import "root:/services/" import "root:/modules/common" import "root:/modules/common/widgets" @@ -8,7 +9,6 @@ import Quickshell import Quickshell.Widgets import Quickshell.Io import Quickshell.Hyprland -import "./icons.js" as Icons Rectangle { // Window id: root diff --git a/.config/quickshell/modules/session/Session.qml b/.config/quickshell/modules/session/Session.qml index 947f44b11..07bcf26ab 100644 --- a/.config/quickshell/modules/session/Session.qml +++ b/.config/quickshell/modules/session/Session.qml @@ -287,4 +287,18 @@ Scope { } } + GlobalShortcut { + name: "sessionToggle" + description: "Toggles session screen on press" + + onPressed: { + for (let i = 0; i < sessionVariants.instances.length; i++) { + let panelWindow = sessionVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = !panelWindow.visible; + } + } + } + } + } diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index f5b6472e5..cc291703a 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -99,11 +99,21 @@ Scope { Layout.topMargin: 5 Layout.bottomMargin: 0 - // CustomIcon { - // width: 25 - // height: 25 - // source: SystemInfo.distroIcon - // } + Item { + implicitWidth: distroIcon.width + implicitHeight: distroIcon.height + CustomIcon { + id: distroIcon + width: 25 + height: 25 + source: SystemInfo.distroIcon + } + ColorOverlay { + anchors.fill: distroIcon + source: distroIcon + color: Appearance.colors.colOnLayer0 + } + } StyledText { font.pixelSize: Appearance.font.pixelSize.normal @@ -221,4 +231,19 @@ Scope { } } + GlobalShortcut { + name: "sidebarRightToggle" + description: "Toggles right sidebar on press" + + onPressed: { + for (let i = 0; i < sidebarVariants.instances.length; i++) { + let panelWindow = sidebarVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = !panelWindow.visible; + if(panelWindow.visible) Notifications.timeoutAll(); + } + } + } + } + } diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index e6ca21795..72cf366a9 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -16,7 +16,6 @@ import "./services/" ShellRoot { Component.onCompleted: { - console.log("ShellRoot loaded") MaterialTheme.reapplyTheme() } From 3b8595748cf303a5d17d202f396b951d81072a83 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 27 Apr 2025 00:13:27 +0200 Subject: [PATCH 111/824] workspace app icons: nicer transition --- .config/quickshell/modules/bar/Workspaces.qml | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index f2722cf75..ec8d1a643 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -22,7 +22,10 @@ Item { property list workspaceOccupied: [] property int widgetPadding: 4 property int workspaceButtonWidth: 26 - property int workspaceIconSize: workspaceButtonWidth * 0.8 + property real workspaceIconSize: workspaceButtonWidth * 0.8 + property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55 + property real workspaceIconOpacityShrinked: 1 + property real workspaceIconMarginShrinked: -4 property int activeWorkspaceMargin: 1 property double animatedActiveWorkspaceIndex: (monitor.activeWorkspace?.id - 1) % ConfigOptions.bar.workspaces.shown @@ -213,12 +216,23 @@ Item { } } + Item { + anchors.centerIn: parent + width: workspaceButtonWidth + height: workspaceButtonWidth IconImage { id: mainAppIcon - opacity: (workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? 1 : 0 + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.bottomMargin: (!GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? + (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked + anchors.rightMargin: (!GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? + (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked + + opacity: (workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? + 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 source: workspaceButtonBackground.mainAppIconSource - anchors.centerIn: parent - implicitSize: workspaceIconSize + implicitSize: (!GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? workspaceIconSize : workspaceIconSizeShrinked Behavior on opacity { NumberAnimation { @@ -226,6 +240,25 @@ Item { easing.type: Appearance.animation.elementDecelFast.type } } + Behavior on anchors.bottomMargin { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } + Behavior on anchors.rightMargin { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } + Behavior on implicitSize { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } + } } } From 0ef575b082b834215d6f21a6fda351d67d96a980 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 27 Apr 2025 18:32:40 +0200 Subject: [PATCH 112/824] use dbus keybind for releasing super for overview AAAAAAAAAAAAAAAA LETS FUCKING GO FINALLY A GOOD LOOKING LAUNCHER THAT POPS UP INSTANTLY --- .config/hypr/hyprland.conf | 3 ++ .config/hypr/hyprland/execs.conf | 2 +- .config/hypr/hyprland/keybinds.conf | 35 +++++++++++-------- .../quickshell/modules/overview/Overview.qml | 24 ++++++++++++- 4 files changed, 47 insertions(+), 17 deletions(-) diff --git a/.config/hypr/hyprland.conf b/.config/hypr/hyprland.conf index aa7c076c8..1255db74a 100644 --- a/.config/hypr/hyprland.conf +++ b/.config/hypr/hyprland.conf @@ -1,6 +1,9 @@ # This file sources other files in `hyprland` and `custom` folders # You wanna add your stuff in file in `custom` +exec = hyprctl dispatch submap global # DO NOT REMOVE THIS OR YOU WON'T BE ABLE TO USE ANY KEYBIND +submap = global # This is required for catchall to work + # Defaults source=~/.config/hypr/hyprland/env.conf source=~/.config/hypr/hyprland/execs.conf diff --git a/.config/hypr/hyprland/execs.conf b/.config/hypr/hyprland/execs.conf index 232f7e1da..a9f867d14 100644 --- a/.config/hypr/hyprland/execs.conf +++ b/.config/hypr/hyprland/execs.conf @@ -1,7 +1,7 @@ # Bar, wallpaper exec-once = swww-daemon --format xrgb exec-once = /usr/lib/geoclue-2.0/demos/agent & gammastep -exec-once = agsv1 & +exec-once = qs & # Input method exec-once = fcitx5 diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 2dc2df7c7..c86220d18 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -1,3 +1,23 @@ +# These absolutely need to be the first, or they won't work consistently +bindid = Super, Super_L, Toggle overview, global, quickshell:overviewToggleRelease +binditn = Super, catchall, global, quickshell:overviewToggleReleaseInterrupt +bind = Super, mouse:272, global, quickshell:overviewToggleReleaseInterrupt +bind = Super, mouse:273, global, quickshell:overviewToggleReleaseInterrupt +bind = Super, mouse:274, global, quickshell:overviewToggleReleaseInterrupt +bind = Super, mouse:275, global, quickshell:overviewToggleReleaseInterrupt +bind = Super, mouse:276, global, quickshell:overviewToggleReleaseInterrupt +bind = Super, mouse:277, global, quickshell:overviewToggleReleaseInterrupt +bind = Super, mouse_up, global, quickshell:overviewToggleReleaseInterrupt +bind = Super, mouse_down,global, quickshell:overviewToggleReleaseInterrupt + +bindit = ,Super_L, global, quickshell:workspaceNumber +bind = Super, Tab, global, quickshell:overviewToggle +bind = Super, B, global, quickshell:sidebarLeftToggle +bind = Super, A, global, quickshell:sidebarLeftToggle +bind = Super, O, global, quickshell:sidebarLeftToggle +bind = Super, N, global, quickshell:sidebarRightToggle +bind = Ctrl+Alt, Delete, global, quickshell:sessionToggle + # Lines ending with `# [hidden]` won't be shown on cheatsheet # Lines starting with #! are section headings @@ -160,13 +180,7 @@ bind = Alt, Tab, bringactivetotop, # [hidden] bring it to the top bindr = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool; agsv1 & # Restart widgets bindr = Ctrl+Super+Alt, R, exec, hyprctl reload; killall agsv1 ydotool; agsv1 & # [hidden] bind = Ctrl+Alt, Slash, exec, agsv1 run-js 'cycleMode();' # Cycle bar mode (normal, focus) -bindr = Super, Super_L, exec, qs ipc call overview toggle || agsv1 -t 'overview' # Toggle overview/launcher -# bind = Super, Tab, exec, qs ipc call overview toggle || agsv1 -t 'overview' # [hidden] bind = Super, Slash, exec, for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "cheatsheet""$i"; done # Show cheatsheet -bind = Super, B, exec, qs ipc call sidebarLeft toggle || agsv1 -t 'sideleft' # Toggle left sidebar -bind = Super, A, exec, qs ipc call sidebarLeft toggle || agsv1 -t 'sideleft' # [hidden] -bind = Super, O, exec, qs ipc call sidebarLeft toggle || agsv1 -t 'sideleft' # [hidden] -# bind = Super, N, exec, qs ipc call sidebarRight toggle || agsv1 -t 'sideright' # Toggle right sidebar bind = Super, M, exec, agsv1 run-js 'openMusicControls.value = (!mpris.getPlayer() ? false : !openMusicControls.value);' # Toggle music controls bind = Super, Comma, exec, agsv1 run-js 'openColorScheme.value = true; Utils.timeout(2000, () => openColorScheme.value = false);' # View color scheme and options bind = Super, K, exec, for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "osk""$i"; done # Toggle on-screen keyboard @@ -175,11 +189,6 @@ bind = Ctrl+Super, G, exec, for ((i=0; i<$(hyprctl monitors -j | jq length); i++ bindle=, XF86MonBrightnessUp, exec, qs ipc call brightness increment || agsv1 run-js 'brightness.screen_value += 0.05; indicator.popup(1);' # [hidden] bindle=, XF86MonBrightnessDown, exec, qs ipc call brightness decrement || agsv1 run-js 'brightness.screen_value -= 0.05; indicator.popup(1);' # [hidden] -# bind = Super, Super_L, global, quickshell:overviewToggleRelease -bindit = ,Super_L, global, quickshell:workspaceNumber -bind = Super, Tab, global, quickshell:overviewToggle -bind = Super, N, global, quickshell:sidebarRightToggle -bind = Ctrl+Alt, Delete, global, quickshell:sessionToggle # Testing bind = Super+Alt, f11, exec, bash -c 'RANDOM_IMAGE=$(find ~/Pictures -type f | grep -v -i "nipple" | grep -v -i "pussy" | shuf -n 1); ACTION=$(notify-send "Test notification with body image" "This notification should contain your user account image and Discord icon. Oh and here is a random image in your Pictures folder: \"Testing" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Open the random image" -A "action3=Useless button"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] @@ -218,7 +227,3 @@ bind = Super+Alt, Slash, exec, pkill fuzzel || fuzzel # Toggle fallback launcher # Cursed stuff ## Make window not amogus large bind = Ctrl+Super, Backslash, resizeactive, exact 640 480 # [hidden] - - - - diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index c12143f52..aed8c0ab0 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -10,7 +10,8 @@ import Quickshell.Wayland import Quickshell.Hyprland Scope { - id: overview + id: root + property bool overviewReleaseMightTrigger: true Variants { model: Quickshell.screens @@ -111,6 +112,9 @@ Scope { function open() { GlobalStates.overviewOpen = true } + function toggleReleaseInterrupt() { + root.overviewReleaseMightTrigger = false + } } GlobalShortcut { @@ -125,9 +129,27 @@ Scope { name: "overviewToggleRelease" description: "Toggles overview on release" + onPressed: { + root.overviewReleaseMightTrigger = true + } + onReleased: { + if (!root.overviewReleaseMightTrigger) { + root.overviewReleaseMightTrigger = true + return + } GlobalStates.overviewOpen = !GlobalStates.overviewOpen } } + GlobalShortcut { + name: "overviewToggleReleaseInterrupt" + description: "Interrupts possibility of overview being toggled on release" + + "This is necessary because onReleased triggers whether or not you press something else while holding the key." + + "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything." + + onPressed: { + root.overviewReleaseMightTrigger = false + } + } } From f2a9641a953f6a772281345f92d30cf9c380aacc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 27 Apr 2025 18:58:06 +0200 Subject: [PATCH 113/824] left sidebar foundations --- .config/quickshell/GlobalStates.qml | 1 + .config/quickshell/modules/bar/Bar.qml | 5 + .config/quickshell/modules/bar/Workspaces.qml | 2 +- .../modules/common/widgets/PrimaryTabBar.qml | 86 ++++++++ .../modules/sidebarLeft/SidebarLeft.qml | 196 ++++++++++++++++++ .../sidebarRight/CenterWidgetGroup.qml | 79 +------ .config/quickshell/shell.qml | 2 + 7 files changed, 300 insertions(+), 71 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/PrimaryTabBar.qml create mode 100644 .config/quickshell/modules/sidebarLeft/SidebarLeft.qml diff --git a/.config/quickshell/GlobalStates.qml b/.config/quickshell/GlobalStates.qml index dffb7f4b6..154435e86 100644 --- a/.config/quickshell/GlobalStates.qml +++ b/.config/quickshell/GlobalStates.qml @@ -8,6 +8,7 @@ pragma ComponentBehavior: Bound Singleton { id: root + property int sidebarLeftOpenCount: 0 property int sidebarRightOpenCount: 0 property bool overviewOpen: false property bool workspaceShowNumbers: false diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 942e2b0b3..d3a06efcc 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -199,6 +199,11 @@ Scope { barLeftSideMouseArea.hovered = false barLeftSideMouseArea.trackingScroll = false } + onPressed: (event) => { + if (event.button === Qt.LeftButton) { + openSidebarLeft.running = true + } + } // Scroll to change brightness WheelHandler { onWheel: (event) => { diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index ec8d1a643..afb2c1ebc 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -22,7 +22,7 @@ Item { property list workspaceOccupied: [] property int widgetPadding: 4 property int workspaceButtonWidth: 26 - property real workspaceIconSize: workspaceButtonWidth * 0.8 + property real workspaceIconSize: workspaceButtonWidth * 0.75 property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55 property real workspaceIconOpacityShrinked: 1 property real workspaceIconMarginShrinked: -4 diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml b/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml new file mode 100644 index 000000000..97eb3c6fa --- /dev/null +++ b/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml @@ -0,0 +1,86 @@ +import "root:/modules/common" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell + +ColumnLayout { + id: root + spacing: 0 + required property var tabButtonList // Something like [{"icon": "notifications", "name": qsTr("Notifications")}, {"icon": "volume_up", "name": qsTr("Volume mixer")}] + required property var externalTrackedTab + property bool enableIndicatorAnimation: false + signal currentIndexChanged(int index) + + TabBar { + id: tabBar + Layout.fillWidth: true + currentIndex: root.externalTrackedTab + onCurrentIndexChanged: root.onCurrentIndexChanged(currentIndex) + + background: Item { + WheelHandler { + onWheel: (event) => { + if (event.angleDelta.y < 0) + tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1) + else if (event.angleDelta.y > 0) + tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0) + } + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } + } + + Repeater { + model: root.tabButtonList + delegate: PrimaryTabButton { + selected: (index == root.externalTrackedTab) + buttonText: modelData.name + buttonIcon: modelData.icon + } + } + } + + Item { // Tab indicator + id: tabIndicator + Layout.fillWidth: true + height: 3 + Connections { + target: root + function onExternalTrackedTabChanged() { + root.enableIndicatorAnimation = true + } + } + Rectangle { + color: Appearance.m3colors.m3primary + radius: Appearance.rounding.full + z: 2 + + anchors.fill: parent + anchors.leftMargin: { + const tabCount = root.tabButtonList.length + const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth + const fullTabSize = root.width / tabCount; + return fullTabSize * root.externalTrackedTab + (fullTabSize - targetWidth) / 2; + } + anchors.rightMargin: { + const tabCount = root.tabButtonList.length + const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth + const fullTabSize = root.width / tabCount; + return fullTabSize * (tabCount - root.externalTrackedTab - 1) + (fullTabSize - targetWidth) / 2; + } + Behavior on anchors.leftMargin { + enabled: root.enableIndicatorAnimation + SmoothedAnimation { + velocity: Appearance.animation.positionShift.velocity + } + } + Behavior on anchors.rightMargin { + enabled: root.enableIndicatorAnimation + SmoothedAnimation { + velocity: Appearance.animation.positionShift.velocity + } + } + + } + } +} diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml new file mode 100644 index 000000000..0a2e1f8e6 --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -0,0 +1,196 @@ +import "root:/" +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io +import Quickshell +import Quickshell.Widgets +import Quickshell.Wayland +import Quickshell.Hyprland +import Qt5Compat.GraphicalEffects + +Scope { // Scope + id: root + property int sidebarWidth: Appearance.sizes.sidebarWidth + property int sidebarPadding: 15 + property var tabButtonList: [{"icon": "neurology", "name": qsTr("LLMs")}, {"icon": "flare", "name": qsTr("Waifus")}] + + Variants { // Window repeater + id: sidebarVariants + model: Quickshell.screens + + PanelWindow { // Window + id: sidebarRoot + visible: false + focusable: true + property int currentTab: 0 + + onVisibleChanged: { + GlobalStates.sidebarLeftOpenCount += visible ? 1 : -1 + } + + property var modelData + + screen: modelData + exclusiveZone: 0 + width: sidebarWidth + WlrLayershell.namespace: "quickshell:sidebarLeft" + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + color: "transparent" + + anchors { + top: true + left: true + bottom: true + } + + HyprlandFocusGrab { // Click outside to close + id: grab + windows: [ sidebarRoot ] + active: false + onCleared: () => { + if (!active) sidebarRoot.visible = false + } + } + + Connections { + target: sidebarRoot + function onVisibleChanged() { + delayedGrabTimer.start() + } + } + + Timer { + id: delayedGrabTimer + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + onTriggered: { + grab.active = sidebarRoot.visible + } + } + + // Background + Rectangle { + id: sidebarLeftBackground + + anchors.centerIn: parent + width: parent.width - Appearance.sizes.hyprlandGapsOut * 2 + height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 + color: Appearance.colors.colLayer0 + radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 + + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Escape) { + sidebarRoot.visible = false; + } + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: sidebarPadding + + spacing: sidebarPadding + + PrimaryTabBar { // Tab strip + id: tabBar + tabButtonList: root.tabButtonList + externalTrackedTab: sidebarRoot.currentTab + function onCurrentIndexChanged(currentIndex) { + sidebarRoot.currentTab = currentIndex + } + } + + SwipeView { // Content pages + id: swipeView + Layout.topMargin: 5 + Layout.fillWidth: true + Layout.fillHeight: true + currentIndex: currentTab + onCurrentIndexChanged: { + tabBar.enableIndicatorAnimation = true + sidebarRoot.currentTab = currentIndex + } + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: swipeView.width + height: swipeView.height + radius: Appearance.rounding.small + } + } + + Item {} + Item {} + } + + } + } + + // Shadow + DropShadow { + anchors.fill: sidebarLeftBackground + horizontalOffset: 0 + verticalOffset: 2 + radius: Appearance.sizes.elevationMargin + samples: Appearance.sizes.elevationMargin * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs + color: Appearance.colors.colShadow + source: sidebarLeftBackground + } + + } + + } + + IpcHandler { + target: "sidebarLeft" + + function toggle(): void { + for (let i = 0; i < sidebarVariants.instances.length; i++) { + let panelWindow = sidebarVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = !panelWindow.visible; + if(panelWindow.visible) Notifications.timeoutAll(); + } + } + } + + function close(): void { + for (let i = 0; i < sidebarVariants.instances.length; i++) { + let panelWindow = sidebarVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = false; + } + } + } + + function open(): void { + for (let i = 0; i < sidebarVariants.instances.length; i++) { + let panelWindow = sidebarVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = true; + if(panelWindow.visible) Notifications.timeoutAll(); + } + } + } + } + + GlobalShortcut { + name: "sidebarLeftToggle" + description: "Toggles left sidebar on press" + + onPressed: { + for (let i = 0; i < sidebarVariants.instances.length; i++) { + let panelWindow = sidebarVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = !panelWindow.visible; + if(panelWindow.visible) Notifications.timeoutAll(); + } + } + } + } + +} diff --git a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml index be69cd5b1..1503ef107 100644 --- a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml @@ -35,76 +35,12 @@ Rectangle { anchors.fill: parent spacing: 0 - TabBar { + PrimaryTabBar { id: tabBar - Layout.fillWidth: true - currentIndex: currentTab - onCurrentIndexChanged: currentTab = currentIndex - - background: Item { - WheelHandler { - onWheel: (event) => { - if (event.angleDelta.y < 0) - tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1) - else if (event.angleDelta.y > 0) - tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0) - } - acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad - } - } - - Repeater { - model: root.tabButtonList - delegate: PrimaryTabButton { - selected: (index == currentTab) - buttonText: modelData.name - buttonIcon: modelData.icon - } - } - } - - Item { // Tab indicator - id: tabIndicator - Layout.fillWidth: true - height: 3 - property bool enableIndicatorAnimation: false - Connections { - target: root - function onCurrentTabChanged() { - tabIndicator.enableIndicatorAnimation = true - } - } - Rectangle { - color: Appearance.m3colors.m3primary - radius: Appearance.rounding.full - z: 2 - - anchors.fill: parent - anchors.leftMargin: { - const tabCount = root.tabButtonList.length - const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth - const fullTabSize = tabBar.width / tabCount; - return fullTabSize * currentTab + (fullTabSize - targetWidth) / 2; - } - anchors.rightMargin: { - const tabCount = root.tabButtonList.length - const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth - const fullTabSize = tabBar.width / tabCount; - return fullTabSize * (tabCount - currentTab - 1) + (fullTabSize - targetWidth) / 2; - } - Behavior on anchors.leftMargin { - enabled: tabIndicator.enableIndicatorAnimation - SmoothedAnimation { - velocity: Appearance.animation.positionShift.velocity - } - } - Behavior on anchors.rightMargin { - enabled: tabIndicator.enableIndicatorAnimation - SmoothedAnimation { - velocity: Appearance.animation.positionShift.velocity - } - } - + tabButtonList: root.tabButtonList + externalTrackedTab: root.currentTab + function onCurrentIndexChanged(currentIndex) { + root.currentTab = currentIndex } } @@ -114,7 +50,10 @@ Rectangle { Layout.fillWidth: true Layout.fillHeight: true currentIndex: currentTab - onCurrentIndexChanged: currentTab = currentIndex + onCurrentIndexChanged: { + tabBar.enableIndicatorAnimation = true + root.currentTab = currentIndex + } layer.enabled: true layer.effect: OpacityMask { diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 72cf366a9..8de63090f 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -6,6 +6,7 @@ import "./modules/onScreenDisplay/" import "./modules/overview/" import "./modules/screenCorners/" import "./modules/session/" +import "./modules/sidebarLeft/" import "./modules/sidebarRight/" import QtQuick import QtQuick.Controls @@ -27,6 +28,7 @@ ShellRoot { ReloadPopup {} ScreenCorners {} Session {} + SidebarLeft {} SidebarRight {} } From 7f9dc766982136fac59b879988e5cf0319e1a31d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 27 Apr 2025 20:35:01 +0200 Subject: [PATCH 114/824] bar: cleaner width --- .config/quickshell/modules/bar/ActiveWindow.qml | 9 +++++---- .config/quickshell/modules/bar/Bar.qml | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/modules/bar/ActiveWindow.qml b/.config/quickshell/modules/bar/ActiveWindow.qml index a675240a1..446bdb07f 100644 --- a/.config/quickshell/modules/bar/ActiveWindow.qml +++ b/.config/quickshell/modules/bar/ActiveWindow.qml @@ -9,7 +9,6 @@ Item { required property var bar readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) readonly property Toplevel activeWindow: ToplevelManager.activeToplevel - property int preferredWidth: Appearance.sizes.barPreferredSideSectionWidth height: parent.height width: colLayout.width @@ -19,21 +18,23 @@ Item { ColumnLayout { id: colLayout - anchors.centerIn: parent + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: parent.right spacing: -4 StyledText { + Layout.fillWidth: true font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colSubtext - Layout.preferredWidth: preferredWidth elide: Text.ElideRight text: activeWindow?.activated ? activeWindow?.appId : qsTr("Desktop") } StyledText { + Layout.fillWidth: true font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnLayer0 - Layout.preferredWidth: preferredWidth elide: Text.ElideRight text: activeWindow?.activated ? activeWindow?.title : `${qsTr("Workspace")} ${monitor.activeWorkspace?.id}` } diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index d3a06efcc..718b79a13 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -70,8 +70,10 @@ Scope { id: leftSection anchors.left: parent.left implicitHeight: barHeight + width: (barRoot.width - middleSection.width) / 2 ActiveWindow { + Layout.fillWidth: true bar: barRoot } } @@ -134,7 +136,7 @@ Scope { id: rightSection anchors.right: parent.right implicitHeight: barHeight - width: Appearance.sizes.barPreferredSideSectionWidth + width: (barRoot.width - middleSection.width) / 2 spacing: 5 layoutDirection: Qt.RightToLeft From a34d65f342d7d18befcbc148bb8808fbb017391e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 27 Apr 2025 20:35:11 +0200 Subject: [PATCH 115/824] session: remove unnecessary focus --- .config/quickshell/modules/session/Session.qml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.config/quickshell/modules/session/Session.qml b/.config/quickshell/modules/session/Session.qml index 07bcf26ab..65b52e585 100644 --- a/.config/quickshell/modules/session/Session.qml +++ b/.config/quickshell/modules/session/Session.qml @@ -118,7 +118,6 @@ Scope { } SessionActionButton { id: sessionSleep - focus: sessionRoot.visible buttonIcon: "dark_mode" buttonText: qsTr("Sleep") onClicked: { sleep.running = true; sessionRoot.visible = false } @@ -129,7 +128,6 @@ Scope { } SessionActionButton { id: sessionLogout - focus: sessionRoot.visible buttonIcon: "logout" buttonText: qsTr("Logout") onClicked: { logout.running = true; sessionRoot.visible = false } @@ -140,7 +138,6 @@ Scope { } SessionActionButton { id: sessionTaskManager - focus: sessionRoot.visible buttonIcon: "browse_activity" buttonText: qsTr("Task Manager") onClicked: { taskManager.running = true; sessionRoot.visible = false } @@ -154,7 +151,6 @@ Scope { spacing: 15 SessionActionButton { id: sessionHibernate - focus: sessionRoot.visible buttonIcon: "downloading" buttonText: qsTr("Hibernate") onClicked: { hibernate.running = true; sessionRoot.visible = false } @@ -164,7 +160,6 @@ Scope { } SessionActionButton { id: sessionShutdown - focus: sessionRoot.visible buttonIcon: "power_settings_new" buttonText: qsTr("Shutdown") onClicked: { shutdown.running = true; sessionRoot.visible = false } @@ -175,7 +170,6 @@ Scope { } SessionActionButton { id: sessionReboot - focus: sessionRoot.visible buttonIcon: "restart_alt" buttonText: qsTr("Reboot") onClicked: { reboot.running = true; sessionRoot.visible = false } @@ -186,7 +180,6 @@ Scope { } SessionActionButton { id: sessionFirmwareReboot - focus: sessionRoot.visible buttonIcon: "settings_applications" buttonText: qsTr("Reboot to firmware settings") onClicked: { firmwareReboot.running = true; sessionRoot.visible = false } From ad9452e6563bac410de7b3e8e7395f8064edc8c2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 27 Apr 2025 20:35:28 +0200 Subject: [PATCH 116/824] left sidebar: add keybinds --- .../quickshell/modules/sidebarLeft/SidebarLeft.qml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index 0a2e1f8e6..e9d43e844 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -16,7 +16,7 @@ Scope { // Scope id: root property int sidebarWidth: Appearance.sizes.sidebarWidth property int sidebarPadding: 15 - property var tabButtonList: [{"icon": "neurology", "name": qsTr("LLMs")}, {"icon": "flare", "name": qsTr("Waifus")}] + property var tabButtonList: [{"icon": "neurology", "name": qsTr("Intelligence")}, {"icon": "flare", "name": qsTr("Waifus")}] Variants { // Window repeater id: sidebarVariants @@ -81,11 +81,21 @@ Scope { // Scope height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 color: Appearance.colors.colLayer0 radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 + focus: sidebarRoot.visible Keys.onPressed: (event) => { if (event.key === Qt.Key_Escape) { sidebarRoot.visible = false; } + if (event.modifiers === Qt.ControlModifier) { + console.log("Control pressed") + if (event.key === Qt.Key_PageDown) { + sidebarRoot.currentTab = Math.min(sidebarRoot.currentTab + 1, root.tabButtonList.length - 1) + } else if (event.key === Qt.Key_PageUp) { + sidebarRoot.currentTab = Math.max(sidebarRoot.currentTab - 1, 0) + } + event.accepted = true; + } } ColumnLayout { From 454800007714ed8a1d5af86f69fbbe0b2614ed20 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 27 Apr 2025 20:55:41 +0200 Subject: [PATCH 117/824] fix indentation --- .config/quickshell/modules/bar/Workspaces.qml | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index afb2c1ebc..01edc014f 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -220,45 +220,45 @@ Item { anchors.centerIn: parent width: workspaceButtonWidth height: workspaceButtonWidth - IconImage { - id: mainAppIcon - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.bottomMargin: (!GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? - (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked - anchors.rightMargin: (!GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? - (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked + IconImage { + id: mainAppIcon + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.bottomMargin: (!GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? + (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked + anchors.rightMargin: (!GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? + (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked - opacity: (workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? - 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 - source: workspaceButtonBackground.mainAppIconSource - implicitSize: (!GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? workspaceIconSize : workspaceIconSizeShrinked + opacity: (workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? + 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 + source: workspaceButtonBackground.mainAppIconSource + implicitSize: (!GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? workspaceIconSize : workspaceIconSizeShrinked - Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } + Behavior on anchors.bottomMargin { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } + Behavior on anchors.rightMargin { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } + } + Behavior on implicitSize { + NumberAnimation { + duration: Appearance.animation.elementDecelFast.duration + easing.type: Appearance.animation.elementDecelFast.type + } } } - Behavior on anchors.bottomMargin { - NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type - } - } - Behavior on anchors.rightMargin { - NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type - } - } - Behavior on implicitSize { - NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type - } - } - } } } From 9b0d76959869d3338f67f30b536fe766e281b7f5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 27 Apr 2025 23:06:22 +0200 Subject: [PATCH 118/824] use hyprland global keybind dispatch for window toggling --- .config/quickshell/modules/bar/Bar.qml | 32 ++-------- .../common/widgets/NotificationWidget.qml | 8 +-- .../OnScreenDisplayBrightness.qml | 18 ++++++ .../onScreenDisplay/OnScreenDisplayVolume.qml | 17 +++++ .../quickshell/modules/overview/Overview.qml | 16 ++++- .../modules/overview/OverviewWidget.qml | 9 +-- .../modules/overview/OverviewWindow.qml | 5 -- .../modules/overview/SearchItem.qml | 8 +-- .../quickshell/modules/session/Session.qml | 62 +++++++------------ .../modules/sidebarLeft/SidebarLeft.qml | 15 +++++ .../modules/sidebarRight/SidebarRight.qml | 34 ++++++++-- .../quickToggles/BluetoothToggle.qml | 9 ++- .../quickToggles/NetworkToggle.qml | 12 ++-- 13 files changed, 134 insertions(+), 111 deletions(-) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 718b79a13..b71cdc401 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -5,6 +5,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell +import Quickshell.Hyprland import Quickshell.Io import Quickshell.Services.Mpris @@ -15,27 +16,6 @@ Scope { readonly property int barCenterSideModuleWidth: Appearance.sizes.barCenterSideModuleWidth readonly property int osdHideMouseMoveThreshold: 20 - Process { - id: openSidebarRight - command: ["qs", "ipc", "call", "sidebarRight", "open"] - } - Process { - id: openSidebarLeft - command: ["qs", "ipc", "call", "sidebarLeft", "open"] - } - Process { - id: hideOsdBrightness - command: ["qs", "ipc", "call", "osdBrightness", "hide"] - } - Process { - id: hideOsdVolume - command: ["qs", "ipc", "call", "osdVolume", "hide"] - } - Process { - id: toggleOverview - command: ["qs", "ipc", "call", "overview", "toggle"] - } - Variants { // For each monitor model: Quickshell.screens @@ -203,7 +183,7 @@ Scope { } onPressed: (event) => { if (event.button === Qt.LeftButton) { - openSidebarLeft.running = true + Hyprland.dispatch('global quickshell:sidebarLeftOpen') } } // Scroll to change brightness @@ -225,7 +205,7 @@ Scope { const dx = mouse.x - barLeftSideMouseArea.lastScrollX; const dy = mouse.y - barLeftSideMouseArea.lastScrollY; if (Math.sqrt(dx*dx + dy*dy) > osdHideMouseMoveThreshold) { - hideOsdBrightness.running = true; + Hyprland.dispatch('global quickshell:osdBrightnessHide') barLeftSideMouseArea.trackingScroll = false; } } @@ -239,7 +219,7 @@ Scope { onPressed: (event) => { if (event.button === Qt.RightButton) { - toggleOverview.running = true; + Hyprland.dispatch('global quickshell:overviewToggle') } } @@ -264,7 +244,7 @@ Scope { } onPressed: (event) => { if (event.button === Qt.LeftButton) { - openSidebarRight.running = true + Hyprland.dispatch('global quickshell:sidebarRightOpen') } else if (event.button === Qt.RightButton) { MprisController.activePlayer.next() @@ -291,7 +271,7 @@ Scope { const dx = mouse.x - barRightSideMouseArea.lastScrollX; const dy = mouse.y - barRightSideMouseArea.lastScrollY; if (Math.sqrt(dx*dx + dy*dy) > osdHideMouseMoveThreshold) { - hideOsdVolume.running = true; + Hyprland.dispatch('global quickshell:osdVolumeHide') barRightSideMouseArea.trackingScroll = false; } } diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 095653d7b..7dfcfd217 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -7,6 +7,7 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Widgets +import Quickshell.Hyprland import Quickshell.Services.Notifications import "./notification_utils.js" as NotificationUtils @@ -23,11 +24,6 @@ Item { Layout.fillWidth: true clip: !popup - Process { - id: closeSidebarProcess - command: ["qs", "ipc", "call", "sidebarRight", "close"] - } - Process { id: copyNotificationBody command: ["bash", "-c", `wl-copy "${notificationObject.body}"`] @@ -456,7 +452,7 @@ Item { : notificationObject.body.replace(/ { @@ -125,6 +127,14 @@ Scope { GlobalStates.overviewOpen = !GlobalStates.overviewOpen } } + GlobalShortcut { + name: "overviewClose" + description: "Closes overview" + + onPressed: { + GlobalStates.overviewOpen = false + } + } GlobalShortcut { name: "overviewToggleRelease" description: "Toggles overview on release" diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index b1db1ec58..d15d548ee 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -42,11 +42,6 @@ Item { property Component windowComponent: OverviewWindow {} property list windowWidgets: [] - Process { - id: closeOverview - command: ["bash", "-c", "qs ipc call overview close &"] // Somehow has to be async to work? - } - Rectangle { id: overviewBackground @@ -91,7 +86,7 @@ Item { acceptedButtons: Qt.LeftButton onClicked: { if (root.draggingTargetWorkspace === -1) { - closeOverview.running = true + Hyprland.dispatch(`global quickshell:overviewClose`) Hyprland.dispatch(`workspace ${workspaceValue}`) } } @@ -191,7 +186,7 @@ Item { if (!windowData) return; if (event.button === Qt.LeftButton) { - closeOverview.running = true + Hyprland.dispatch(`global quickshell:overviewClose`) Hyprland.dispatch(`workspace ${windowData.workspace.id}`) event.accepted = true } else if (event.button === Qt.MiddleButton) { diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index d44a4c9f4..9be71a3d7 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -71,11 +71,6 @@ Rectangle { // Window } } - Process { - id: closeOverview - command: ["bash", "-c", "qs ipc call overview close &"] // Somehow has to by async to work? - } - ColumnLayout { anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index 1f874752b..2b10c5186 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -8,6 +8,7 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Widgets +import Quickshell.Hyprland Button { id: root @@ -35,7 +36,7 @@ Button { PointingHandInteraction {} onClicked: { root.itemExecute() - closeOverview.running = true + Hyprland.dispatch("global quickshell:overviewClose") } Keys.onPressed: (event) => { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { @@ -116,9 +117,4 @@ Button { text: root.itemClickActionName } } - - Process { - id: closeOverview - command: ["bash", "-c", "qs ipc call overview close &"] // Somehow has to be async to work? - } } \ No newline at end of file diff --git a/.config/quickshell/modules/session/Session.qml b/.config/quickshell/modules/session/Session.qml index 65b52e585..47bed1435 100644 --- a/.config/quickshell/modules/session/Session.qml +++ b/.config/quickshell/modules/session/Session.qml @@ -111,7 +111,7 @@ Scope { focus: sessionRoot.visible buttonIcon: "lock" buttonText: qsTr("Lock") - onClicked: { lock.running = true; sessionRoot.visible = false } + onClicked: { Hyprland.dispatch("exec loginctl lock-session"); sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.right: sessionSleep KeyNavigation.down: sessionHibernate @@ -120,7 +120,7 @@ Scope { id: sessionSleep buttonIcon: "dark_mode" buttonText: qsTr("Sleep") - onClicked: { sleep.running = true; sessionRoot.visible = false } + onClicked: { Hyprland.dispatch("exec systemctl suspend || loginctl suspend"); sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.left: sessionLock KeyNavigation.right: sessionLogout @@ -130,7 +130,7 @@ Scope { id: sessionLogout buttonIcon: "logout" buttonText: qsTr("Logout") - onClicked: { logout.running = true; sessionRoot.visible = false } + onClicked: { Hyprland.dispatch("exec pkill Hyprland"); sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.left: sessionSleep KeyNavigation.right: sessionTaskManager @@ -140,7 +140,7 @@ Scope { id: sessionTaskManager buttonIcon: "browse_activity" buttonText: qsTr("Task Manager") - onClicked: { taskManager.running = true; sessionRoot.visible = false } + onClicked: { Hyprland.dispatch("exec gnome-system-monitor & disown"); sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.left: sessionLogout KeyNavigation.down: sessionFirmwareReboot @@ -153,7 +153,7 @@ Scope { id: sessionHibernate buttonIcon: "downloading" buttonText: qsTr("Hibernate") - onClicked: { hibernate.running = true; sessionRoot.visible = false } + onClicked: { Hyprland.dispatch("exec systemctl hibernate || loginctl hibernate"); sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.up: sessionLock KeyNavigation.right: sessionShutdown @@ -162,7 +162,7 @@ Scope { id: sessionShutdown buttonIcon: "power_settings_new" buttonText: qsTr("Shutdown") - onClicked: { shutdown.running = true; sessionRoot.visible = false } + onClicked: { Hyprland.dispatch("exec systemctl poweroff || loginctl poweroff"); sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.left: sessionHibernate KeyNavigation.right: sessionReboot @@ -172,7 +172,7 @@ Scope { id: sessionReboot buttonIcon: "restart_alt" buttonText: qsTr("Reboot") - onClicked: { reboot.running = true; sessionRoot.visible = false } + onClicked: { Hyprland.dispatch("exec reboot || loginctl reboot"); sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.left: sessionShutdown KeyNavigation.right: sessionFirmwareReboot @@ -182,7 +182,7 @@ Scope { id: sessionFirmwareReboot buttonIcon: "settings_applications" buttonText: qsTr("Reboot to firmware settings") - onClicked: { firmwareReboot.running = true; sessionRoot.visible = false } + onClicked: { Hyprland.dispatch("exec systemctl reboot --firmware-setup || loginctl reboot --firmware-setup"); sessionRoot.visible = false } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.up: sessionTaskManager KeyNavigation.left: sessionReboot @@ -216,39 +216,6 @@ Scope { } - Process { - id: lock - command: ["bash", "-c", "loginctl lock-session"] - } - Process { - id: sleep - command: ["bash", "-c", "systemctl suspend || loginctl suspend"] - } - Process { - id: logout - command: ["bash", "-c", "pkill Hyprland"] // loginctl terminate-session hangs SDDM - } - Process { - id: hibernate - command: ["bash", "-c", "systemctl hibernate || loginctl hibernate"] - } - Process { - id: shutdown - command: ["bash", "-c", "systemctl poweroff || loginctl poweroff"] - } - Process { - id: reboot - command: ["bash", "-c", "systemctl reboot || loginctl reboot"] - } - Process { - id: firmwareReboot - command: ["bash", "-c", "systemctl reboot --firmware-setup || loginctl reboot --firmware-setup"] - } - Process { - id: taskManager - command: ["bash", "-c", "gnome-system-monitor & disown"] - } - IpcHandler { target: "session" @@ -293,5 +260,18 @@ Scope { } } } + GlobalShortcut { + name: "sessionOpen" + description: "Opens session screen on press" + + onPressed: { + for (let i = 0; i < sessionVariants.instances.length; i++) { + let panelWindow = sessionVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = true + } + } + } + } } diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index e9d43e844..74254d4ad 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -203,4 +203,19 @@ Scope { // Scope } } + GlobalShortcut { + name: "sidebarLeftOpen" + description: "Opens left sidebar on press" + + onPressed: { + for (let i = 0; i < sidebarVariants.instances.length; i++) { + let panelWindow = sidebarVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = true; + if(panelWindow.visible) Notifications.timeoutAll(); + } + } + } + } + } diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index cc291703a..537fa0245 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -130,11 +130,7 @@ Scope { toggled: false buttonIcon: "power_settings_new" onClicked: { - openSessionMenu.running = true - } - Process { - id: openSessionMenu - command: ["qs", "ipc", "call", "session", "open"] + Hyprland.dispatch("global quickshell:sessionOpen") } StyledToolTip { content: qsTr("Session") @@ -245,5 +241,33 @@ Scope { } } } + GlobalShortcut { + name: "sidebarRightOpen" + description: "Opens right sidebar on press" + + onPressed: { + for (let i = 0; i < sidebarVariants.instances.length; i++) { + let panelWindow = sidebarVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = true; + if(panelWindow.visible) Notifications.timeoutAll(); + } + } + } + } + GlobalShortcut { + name: "sidebarRightClose" + description: "Closes right sidebar on press" + + onPressed: { + for (let i = 0; i < sidebarVariants.instances.length; i++) { + let panelWindow = sidebarVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = false; + if(panelWindow.visible) Notifications.timeoutAll(); + } + } + } + } } diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml index c7a24fc70..d73a66076 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml @@ -5,6 +5,7 @@ import "root:/modules/common/widgets" import QtQuick import Quickshell import Quickshell.Io +import Quickshell.Hyprland QuickToggleButton { toggled: Bluetooth.bluetoothEnabled @@ -17,17 +18,15 @@ QuickToggleButton { toggleBluetooth.running = true } if (mouse.button === Qt.RightButton) { - configureBluetooth.running = true + Hyprland.dispatch(`exec ${ConfigOptions.apps.bluetooth}`) + Hyprland.dispatch("global quickshell:sidebarRightClose") + } } hoverEnabled: false propagateComposedEvents: true cursorShape: Qt.PointingHandCursor } - Process { - id: configureBluetooth - command: ["bash", "-c", `${ConfigOptions.apps.bluetooth} & qs ipc call sidebarRight close`] - } Process { id: toggleBluetooth command: ["bash", "-c", `bluetoothctl power ${Bluetooth.bluetoothEnabled ? "off" : "on"}`] diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml index 8c916aac9..742a2f480 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml @@ -2,9 +2,10 @@ import "root:/modules/common" import "root:/modules/common/widgets" import "root:/services" import "../" -import Quickshell.Io -import Quickshell import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland QuickToggleButton { toggled: Network.networkName.length > 0 && Network.networkName != "lo" @@ -23,17 +24,14 @@ QuickToggleButton { toggleNetwork.running = true } if (mouse.button === Qt.RightButton) { - configureNetwork.running = true + Hyprland.dispatch(`exec ${ConfigOptions.apps.network}`) + Hyprland.dispatch("global quickshell:sidebarRightClose") } } hoverEnabled: false propagateComposedEvents: true cursorShape: Qt.PointingHandCursor } - Process { - id: configureNetwork - command: ["bash", "-c", `${ConfigOptions.apps.network} & qs ipc call sidebarRight close`] - } Process { id: toggleNetwork command: ["bash", "-c", "nmcli radio wifi | grep -q enabled && nmcli radio wifi off || nmcli radio wifi on"] From ab9b17a1887db9e8f0fb25b3d93610ced6a8b50a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 27 Apr 2025 23:22:27 +0200 Subject: [PATCH 119/824] use more hyprland dispatch exec instead of a process for simple stuff --- .config/quickshell/modules/bar/UtilButtons.qml | 17 +++-------------- .../common/widgets/NotificationWidget.qml | 9 ++------- .../modules/overview/SearchWidget.qml | 13 ++----------- .../sidebarRight/quickToggles/GameMode.qml | 18 +++++++----------- .config/quickshell/services/Brightness.qml | 6 +++--- .config/quickshell/services/Network.qml | 2 -- 6 files changed, 17 insertions(+), 48 deletions(-) diff --git a/.config/quickshell/modules/bar/UtilButtons.qml b/.config/quickshell/modules/bar/UtilButtons.qml index 64f24a0c9..168e3e030 100644 --- a/.config/quickshell/modules/bar/UtilButtons.qml +++ b/.config/quickshell/modules/bar/UtilButtons.qml @@ -4,6 +4,7 @@ import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Io +import Quickshell.Hyprland Rectangle { Layout.alignment: Qt.AlignVCenter @@ -12,18 +13,6 @@ Rectangle { color: Appearance.colors.colLayer1 radius: Appearance.rounding.small - Process { - id: screenSnip - - command: ["grimblast", "copy", "area"] - } - - Process { - id: pickColor - - command: ["hyprpicker", "-a"] - } - RowLayout { id: rowLayout @@ -32,7 +21,7 @@ Rectangle { SmallCircleButton { Layout.alignment: Qt.AlignVCenter - onClicked: screenSnip.running = true + onClicked: Hyprland.dispatch("exec grimblast copy area") MaterialSymbol { anchors.centerIn: parent @@ -45,7 +34,7 @@ Rectangle { SmallCircleButton { Layout.alignment: Qt.AlignVCenter - onClicked: pickColor.running = true + onClicked: Hyprland.dispatch("exec hyprpicker -a") MaterialSymbol { anchors.centerIn: parent diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 7dfcfd217..f912c9cef 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -24,11 +24,6 @@ Item { Layout.fillWidth: true clip: !popup - Process { - id: copyNotificationBody - command: ["bash", "-c", `wl-copy "${notificationObject.body}"`] - } - implicitHeight: ready ? notificationColumnLayout.implicitHeight + notificationListSpacing : 0 Behavior on implicitHeight { enabled: enableAnimation @@ -125,7 +120,7 @@ Item { } onPressAndHold: (mouse) => { if (mouse.button === Qt.LeftButton) { - copyNotificationBody.running = true + Hyprland.dispatch(`exec wl-copy '${notificationObject.body}'`) notificationSummaryText.text = `${notificationObject.summary} (copied)` } } @@ -531,7 +526,7 @@ Item { (contentItem.implicitWidth + leftPadding + rightPadding) onClicked: { - copyNotificationBody.running = true + Hyprland.dispatch(`exec wl-copy '${notificationObject.body}'`) copyIcon.text = "inventory" copyIconTimer.stop() copyIconTimer.start() diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index ab694f6fc..5cd2338d2 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -9,6 +9,7 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io +import Quickshell.Hyprland Item { // Wrapper id: root @@ -85,16 +86,6 @@ Item { // Wrapper } } - Process { - id: copyText - property list baseCommand: ["wl-copy"] - function copyTextToClipboard(text) { - copyText.running = false - copyText.command = baseCommand.concat(text) - copyText.running = true - } - } - Process { id: webSearch property list baseCommand: ["xdg-open"] @@ -338,7 +329,7 @@ Item { // Wrapper fontType: "monospace", materialSymbol: 'calculate', execute: () => { - copyText.copyTextToClipboard(root.mathResult); + Hyprland.dispatch(`exec wl-copy '${root.mathResult}'`) } }); // Run command diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml b/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml index 4818f0c42..1cd56acc7 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml @@ -1,29 +1,25 @@ import "root:/modules/common" import "root:/modules/common/widgets" import "../" -import Quickshell.Io import Quickshell +import Quickshell.Io +import Quickshell.Hyprland QuickToggleButton { property bool enabled: false buttonIcon: "gamepad" toggled: enabled + onClicked: { enabled = !enabled if (enabled) { - gameModeOn.running = true + // gameModeOn.running = true + Hyprland.dispatch(`exec hyprctl --batch "keyword animations:enabled 0; keyword decoration:shadow:enabled 0; keyword decoration:blur:enabled 0; keyword general:gaps_in 0; keyword general:gaps_out 0; keyword general:border_size 1; keyword decoration:rounding 0; keyword general:allow_tearing 1"`) } else { - gameModeOff.running = true + Hyprland.dispatch("exec hyprctl reload") } } - Process { - id: gameModeOn - command: ['bash', '-c', `hyprctl --batch "keyword animations:enabled 0; keyword decoration:shadow:enabled 0; keyword decoration:blur:enabled 0; keyword general:gaps_in 0; keyword general:gaps_out 0; keyword general:border_size 1; keyword decoration:rounding 0; keyword general:allow_tearing 1"`] - } - Process { - id: gameModeOff - command: ['bash', '-c', `hyprctl reload`] - } + StyledToolTip { content: qsTr("Game mode") } diff --git a/.config/quickshell/services/Brightness.qml b/.config/quickshell/services/Brightness.qml index 742b5c5a2..5911c5ebe 100644 --- a/.config/quickshell/services/Brightness.qml +++ b/.config/quickshell/services/Brightness.qml @@ -1,7 +1,8 @@ -import Quickshell -import Quickshell.Io pragma Singleton pragma ComponentBehavior: Bound +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland Singleton { id: root @@ -88,5 +89,4 @@ Singleton { root.increment = -1 } } - } diff --git a/.config/quickshell/services/Network.qml b/.config/quickshell/services/Network.qml index 1a8402586..e790c39bc 100644 --- a/.config/quickshell/services/Network.qml +++ b/.config/quickshell/services/Network.qml @@ -34,7 +34,6 @@ Singleton { stdout: SplitParser { onRead: data => { root.networkName = data - // console.log("Network: " + data); } } } @@ -46,7 +45,6 @@ Singleton { stdout: SplitParser { onRead: data => { root.networkStrength = parseInt(data); - // console.log("Network Strength: " + data); } } } From cc69a4bac473c206907539d76c6de4688b088dfc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 27 Apr 2025 23:48:30 +0200 Subject: [PATCH 120/824] bar: more intuitive buttons, topleft icon --- .../quickshell/modules/bar/ActiveWindow.qml | 1 - .config/quickshell/modules/bar/Bar.qml | 36 +++++++++++++++++-- .../modules/common/ConfigOptions.qml | 1 + 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/bar/ActiveWindow.qml b/.config/quickshell/modules/bar/ActiveWindow.qml index 446bdb07f..574cdb8c6 100644 --- a/.config/quickshell/modules/bar/ActiveWindow.qml +++ b/.config/quickshell/modules/bar/ActiveWindow.qml @@ -12,7 +12,6 @@ Item { height: parent.height width: colLayout.width - Layout.leftMargin: Appearance.rounding.screenRounding ColumnLayout { diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index b71cdc401..8582d5486 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -1,9 +1,11 @@ +import "root:/" import "root:/modules/common" import "root:/modules/common/widgets" import "root:/services" import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Hyprland import Quickshell.Io @@ -51,6 +53,34 @@ Scope { anchors.left: parent.left implicitHeight: barHeight width: (barRoot.width - middleSection.width) / 2 + spacing: 10 + + Rectangle { + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.leftMargin: Appearance.rounding.screenRounding + Layout.fillWidth: false + + // Layout.fillHeight: true + radius: Appearance.rounding.full + color: (barLeftSideMouseArea.pressed || GlobalStates.sidebarLeftOpenCount > 0) ? Appearance.colors.colLayer1Active : barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : "transparent" + implicitWidth: distroIcon.width + 5*2 + implicitHeight: distroIcon.height + 5*2 + + CustomIcon { + id: distroIcon + anchors.centerIn: parent + width: 19.5 + height: 19.5 + source: ConfigOptions.bar.topLeftIcon == 'distro' ? + SystemInfo.distroIcon : "google-gemini-symbolic" + + } + ColorOverlay { + anchors.fill: distroIcon + source: distroIcon + color: Appearance.colors.colOnLayer0 + } + } ActiveWindow { Layout.fillWidth: true @@ -124,11 +154,11 @@ Scope { Layout.margins: 4 Layout.rightMargin: Appearance.rounding.screenRounding Layout.fillHeight: true - implicitWidth: rowLayout.implicitWidth + 10*2 + implicitWidth: indicatorsRowLayout.implicitWidth + 10*2 radius: Appearance.rounding.full - color: barRightSideMouseArea.pressed ? Appearance.colors.colLayer1Active : barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : "transparent" + color: (barRightSideMouseArea.pressed || GlobalStates.sidebarRightOpenCount > 0) ? Appearance.colors.colLayer1Active : barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : "transparent" RowLayout { - id: rowLayout + id: indicatorsRowLayout anchors.centerIn: parent spacing: 15 diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 19a374d1c..f9434de60 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -19,6 +19,7 @@ Singleton { property QtObject bar: QtObject { property int batteryLowThreshold: 20 + property string topLeftIcon: "gemini" // Options: distro, gemini property QtObject resources: QtObject { property bool alwaysShowSwap: true property bool alwaysShowCpu: false From 36e3171391bc7a9f39a67e430b53c45502e0f2cc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 27 Apr 2025 23:49:15 +0200 Subject: [PATCH 121/824] separator lines for tabbar --- .../modules/common/widgets/PrimaryTabBar.qml | 7 +++++++ .config/quickshell/modules/overview/SearchWidget.qml | 2 +- .../quickshell/modules/sidebarRight/SidebarRight.qml | 2 +- .../sidebarRight/calendar/CalendarDayButton.qml | 2 +- .../modules/sidebarRight/todo/TodoWidget.qml | 12 +++++++++++- 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml b/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml index 97eb3c6fa..7be539a7e 100644 --- a/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml +++ b/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml @@ -83,4 +83,11 @@ ColumnLayout { } } + + Rectangle { // Tabbar bottom border + id: tabBarBottomBorder + Layout.fillWidth: true + height: 1 + color: Appearance.m3colors.m3outlineVariant + } } diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 5cd2338d2..461f0dcf4 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -254,7 +254,7 @@ Item { // Wrapper visible: root.showResults Layout.fillWidth: true height: 1 - color: Appearance.m3colors.m3outline + color: Appearance.m3colors.m3outlineVariant } ListView { // App results diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 537fa0245..8b08af8ea 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -6,12 +6,12 @@ import "./quickToggles/" import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Qt5Compat.GraphicalEffects import Quickshell.Io import Quickshell import Quickshell.Widgets import Quickshell.Wayland import Quickshell.Hyprland -import Qt5Compat.GraphicalEffects Scope { property int sidebarWidth: Appearance.sizes.sidebarWidth diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml index da4f5c8f5..aa523c75b 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml @@ -42,7 +42,7 @@ Button { font.weight: bold ? Font.Bold : isToday == -1 ? Font.Normal : Font.DemiBold color: (isToday == 1) ? Appearance.m3colors.m3onPrimary : (isToday == 0) ? Appearance.colors.colOnLayer1 : - Appearance.m3colors.m3outline + Appearance.m3colors.m3outlineVariant Behavior on color { ColorAnimation { diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index 6878754e0..ba6dd67f6 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -113,6 +113,13 @@ Item { } } + Rectangle { // Tabbar bottom border + id: tabBarBottomBorder + Layout.fillWidth: true + height: 1 + color: Appearance.m3colors.m3outlineVariant + } + SwipeView { id: swipeView Layout.topMargin: 10 @@ -120,7 +127,10 @@ Item { Layout.fillHeight: true clip: true currentIndex: currentTab - onCurrentIndexChanged: currentTab = currentIndex + onCurrentIndexChanged: { + tabIndicator.enableIndicatorAnimation = true + currentTab = currentIndex + } // To Do tab TaskList { From c596cd21f6e8895cd3556d6d942930a5b66794f3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 27 Apr 2025 23:56:33 +0200 Subject: [PATCH 122/824] tooltips: add delay --- .../modules/common/widgets/StyledToolTip.qml | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index 13959fd8f..3a6c1a401 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -7,9 +7,31 @@ import QtQuick.Layouts ToolTip { property string content property bool extraVisibleCondition: true + property bool internalVisibleCondition: false padding: 7 - visible: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) + visible: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered) && internalVisibleCondition) + + Connections { + target: parent + onHoveredChanged: { + if (parent.hovered) { + tooltipShowDelay.restart() + } else { + internalVisibleCondition = false + } + } + } + + Timer { + id: tooltipShowDelay + interval: 200 + repeat: false + running: false + onTriggered: { + internalVisibleCondition = true + } + } background: Rectangle { color: Appearance.colors.colTooltip From c0eff65377432ebf27ffa78d4d5a749f07013610 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 28 Apr 2025 00:07:51 +0200 Subject: [PATCH 123/824] fix overview focus --- .config/quickshell/modules/overview/Overview.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 9ef1ebb1c..2ddbe9b07 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -26,7 +26,7 @@ Scope { WlrLayershell.namespace: "quickshell:overview" WlrLayershell.layer: WlrLayer.Overlay - // WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None color: "transparent" mask: Region { From 160a55d859f545bed889ecf742f55595e03c1b08 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 28 Apr 2025 10:20:27 +0200 Subject: [PATCH 124/824] booru: api caller service --- .config/quickshell/modules/bar/Bar.qml | 1 + .../modules/common/widgets/PrimaryTabBar.qml | 1 + .../modules/common/widgets/StyledToolTip.qml | 2 +- .../quickshell/modules/sidebarLeft/Anime.qml | 85 ++++++++ .../modules/sidebarLeft/SidebarLeft.qml | 10 +- .config/quickshell/services/Booru.qml | 195 ++++++++++++++++++ 6 files changed, 289 insertions(+), 5 deletions(-) create mode 100644 .config/quickshell/modules/sidebarLeft/Anime.qml create mode 100644 .config/quickshell/services/Booru.qml diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 8582d5486..123351e55 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -83,6 +83,7 @@ Scope { } ActiveWindow { + Layout.rightMargin: Appearance.rounding.screenRounding Layout.fillWidth: true bar: barRoot } diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml b/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml index 7be539a7e..9ceac9e4d 100644 --- a/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml +++ b/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml @@ -56,6 +56,7 @@ ColumnLayout { z: 2 anchors.fill: parent + // TODO: make the end point in the moving direction go first anchors.leftMargin: { const tabCount = root.tabButtonList.length const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index 3a6c1a401..8fa4f60c8 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -14,7 +14,7 @@ ToolTip { Connections { target: parent - onHoveredChanged: { + function onHoveredChanged() { if (parent.hovered) { tooltipShowDelay.restart() } else { diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml new file mode 100644 index 000000000..d92b2201e --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -0,0 +1,85 @@ +import "root:/" +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io +import Quickshell +import Quickshell.Widgets +import Quickshell.Wayland +import Quickshell.Hyprland +import Qt5Compat.GraphicalEffects + +Item { + id: root + onFocusChanged: (focus) => { + if (focus) { + tagInputField.forceActiveFocus() + } + } + + ColumnLayout { + anchors.fill: parent + + ListView { // Booru responses + id: booruResponseListView + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + model: Booru.responses + delegate: StyledText { + id: booruResponseText + text: JSON.stringify(modelData) + } + } + + Rectangle { + Layout.fillWidth: true + radius: Appearance.rounding.small + border.width: 1 + border.color: Appearance.m3colors.m3outlineVariant + color: "transparent" + implicitWidth: tagInputField.implicitWidth + implicitHeight: tagInputField.implicitHeight + + Behavior on implicitHeight { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + + TextArea { + id: tagInputField + anchors.fill: parent + wrapMode: TextArea.Wrap + + padding: 10 + color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant + renderType: Text.NativeRendering + selectedTextColor: Appearance.m3colors.m3onPrimary + selectionColor: Appearance.m3colors.m3primary + placeholderText: qsTr("Enter tags") + placeholderTextColor: Appearance.m3colors.m3outline + + Keys.onPressed: (event) => { + if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { + if (event.modifiers & Qt.ShiftModifier) { + // Insert newline + tagInputField.insert(tagInputField.cursorPosition, "\n") + event.accepted = true + } else { + // Submit on Enter or Ctrl+Enter + const tagList = tagInputField.text.split(/\s+/); + Booru.makeRequest(tagList); + tagInputField.clear() + event.accepted = true + } + } + } + } + } + } +} diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index 74254d4ad..97b9e9b3d 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -16,7 +16,7 @@ Scope { // Scope id: root property int sidebarWidth: Appearance.sizes.sidebarWidth property int sidebarPadding: 15 - property var tabButtonList: [{"icon": "neurology", "name": qsTr("Intelligence")}, {"icon": "flare", "name": qsTr("Waifus")}] + property var tabButtonList: [{"icon": "neurology", "name": qsTr("Intelligence")}, {"icon": "bookmark_heart", "name": qsTr("Anime")}] Variants { // Window repeater id: sidebarVariants @@ -88,7 +88,6 @@ Scope { // Scope sidebarRoot.visible = false; } if (event.modifiers === Qt.ControlModifier) { - console.log("Control pressed") if (event.key === Qt.Key_PageDown) { sidebarRoot.currentTab = Math.min(sidebarRoot.currentTab + 1, root.tabButtonList.length - 1) } else if (event.key === Qt.Key_PageUp) { @@ -133,8 +132,11 @@ Scope { // Scope } } - Item {} - Item {} + StyledText { + text: "To be implemented" + horizontalAlignment: Text.AlignHCenter + } + Anime {} } } diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml new file mode 100644 index 000000000..d49d93c3c --- /dev/null +++ b/.config/quickshell/services/Booru.qml @@ -0,0 +1,195 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import Quickshell; +import Quickshell.Io; +import Qt.labs.platform +import QtQuick; + +Singleton { + id: root + + property var getWorkingImageSource: (url) => { + if (url.includes('pximg.net')) { + return `https://www.pixiv.net/en/artworks/${url.substring(url.lastIndexOf('/') + 1).replace(/_p\d+\.(png|jpg|jpeg|gif)$/, '')}`; + } + return url; + } + + property var providerList: ["yandere", "konachan", "danbooru", "gelbooru"] + property var providers: { + "yandere": { + "name": "yande.re", + "url": "https://yande.re", + "api": "https://yande.re/post.json", + "listAccess": [], + "mapFunc": (response) => { + return response.map(item => { + return { + "id": item.id, + "aspect_ratio": item.width / item.height, + "tags": item.tags, + "rating": item.rating, + "is_nsfw": (item.rating != 's'), + "md5": item.md5, + "preview_url": item.preview_url, + "sample_url": item.sample_url ?? item.file_url, + "file_url": item.file_url, + "file_ext": item.file_ext, + "source": getWorkingImageSource(item.source), + } + }) + } + }, + "konachan": { + "name": "Konachan", + "url": "https://konachan.com", + "api": "https://konachan.com/post.json", + "listAccess": [], + "mapFunc": (response) => { + return response.map(item => { + return { + "id": item.id, + "aspect_ratio": item.width / item.height, + "tags": item.tags, + "rating": item.rating, + "is_nsfw": (item.rating != 's'), + "md5": item.md5, + "preview_url": item.preview_url, + "sample_url": item.sample_url ?? item.file_url, + "file_url": item.file_url, + "file_ext": item.file_ext, + "source": getWorkingImageSource(item.source), + } + }) + } + }, + "danbooru": { + "name": "Danbooru", + "url": "https://danbooru.donmai.us", + "api": "https://danbooru.donmai.us/posts.json", + "listAccess": [], + "mapFunc": (response) => { + return response.map(item => { + return { + "id": item.id, + "aspect_ratio": item.image_width / item.image_height, + "tags": item.tag_string, + "rating": item.rating, + "is_nsfw": (item.rating != 's'), + "md5": item.md5, + "preview_url": item.preview_file_url, + "sample_url": item.file_url ?? item.large_file_url, + "file_url": item.large_file_url, + "file_ext": item.file_ext, + "source": getWorkingImageSource(item.source), + } + }) + } + }, + "gelbooru": { + "name": "Gelbooru", + "url": "https://gelbooru.com", + "api": "https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1", + "listAccess": ["post"], + "mapFunc": (response) => { + return response.map(item => { + return { + "id": item.id, + "aspect_ratio": item.width / item.height, + "tags": item.tags, + "rating": item.rating.replace('general', 's').charAt(0), + "is_nsfw": (item.rating != 's'), + "md5": item.md5, + "preview_url": item.preview_url, + "sample_url": item.sample_url ?? item.file_url, + "file_url": item.file_url, + "file_ext": item.file_url.split('.').pop(), + "source": getWorkingImageSource(item.source), + } + }) + } + } + } + property var responses: [] + onResponsesChanged: { + console.log("[Booru] Responses changed: " + JSON.stringify(responses)) + } + property var currentProvider: "yandere" + + function setProvider(provider) { + if (providerList.indexOf(provider) !== -1) { + currentProvider = provider + } else { + console.log("[Booru] Invalid provider: " + provider) + } + } + + function constructRequestUrl(tags, nsfw=true, limit=20) { + var provider = providers[currentProvider] + var baseUrl = provider.api + var tagString = tags.join(" ") + if (!nsfw) { + tagString += " rating:safe" + } + var params = [] + // Danbooru, Yandere, Konachan: tags & limit + if (currentProvider === "danbooru" || currentProvider === "yandere" || currentProvider === "konachan") { + params.push("tags=" + encodeURIComponent(tagString)) + params.push("limit=" + limit) + } + var url = baseUrl + if (baseUrl.indexOf("?") === -1) { + url += "?" + params.join("&") + } else { + url += "&" + params.join("&") + } + return url + } + + function makeRequest(tags, nsfw=true, limit=20) { + var url = constructRequestUrl(tags, nsfw, limit) + console.log("[Booru] Making request to " + url) + + var xhr = new XMLHttpRequest() + xhr.open("GET", url) + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { + try { + // console.log("[Booru] Raw response length: " + xhr.responseText.length) + var response = JSON.parse(xhr.responseText) + + // Access nested properties based on listAccess + var accessList = providers[currentProvider].listAccess + for (var i = 0; i < accessList.length; ++i) { + if (response && response.hasOwnProperty(accessList[i])) { + response = response[accessList[i]] + } else { + break + } + } + response = providers[currentProvider].mapFunc(response) + // console.log("[Booru] Scoped & mapped response: " + JSON.stringify(response)) + var newResponses = root.responses.slice() // make a shallow copy + newResponses.push(response) + root.responses = newResponses + + } catch (e) { + console.log("[Booru] Failed to parse response: " + e) + } + } + else if (xhr.readyState === XMLHttpRequest.DONE) { + console.log("[Booru] Request failed with status: " + xhr.status) + } + } + + try { + // Required for danbooru + xhr.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36") + } catch (e) { + console.log("Could not set User-Agent:", e) + } + xhr.send() + } +} + From 1f5ea7b98321e6615cc50eee00731e3abe9d7029 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 28 Apr 2025 23:24:11 +0200 Subject: [PATCH 125/824] booru: images, clear, provider setting --- .../modules/common/ConfigOptions.qml | 11 ++ .../quickshell/modules/sidebarLeft/Anime.qml | 69 +++++-- .../modules/sidebarLeft/BooruResponse.qml | 187 ++++++++++++++++++ .../modules/sidebarLeft/BooruTagButton.qml | 34 ++++ .../modules/sidebarLeft/SidebarLeft.qml | 6 +- .config/quickshell/services/Booru.qml | 109 ++++++++-- 6 files changed, 380 insertions(+), 36 deletions(-) create mode 100644 .config/quickshell/modules/sidebarLeft/BooruResponse.qml create mode 100644 .config/quickshell/modules/sidebarLeft/BooruTagButton.qml diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index f9434de60..c3062dd42 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -55,6 +55,17 @@ Singleton { } } + property QtObject sidebar: QtObject { + property QtObject booru: QtObject { + property bool allowNsfw: false + property string defaultProvider: "yandere" + property int limit: 20 // Images per page + property QtObject zerochan: QtObject { + // property string username + } + } + } + property QtObject hacks: QtObject { property int arbitraryRaceConditionDelay: 10 // milliseconds } diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index d92b2201e..2db36420d 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -5,15 +5,16 @@ import "root:/modules/common/widgets" import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Qt5Compat.GraphicalEffects import Quickshell.Io import Quickshell -import Quickshell.Widgets -import Quickshell.Wayland import Quickshell.Hyprland -import Qt5Compat.GraphicalEffects Item { id: root + property var panelWindow + property var inputField: tagInputField + onFocusChanged: (focus) => { if (focus) { tagInputField.forceActiveFocus() @@ -21,6 +22,7 @@ Item { } ColumnLayout { + id: columnLayout anchors.fill: parent ListView { // Booru responses @@ -28,21 +30,33 @@ Item { Layout.fillWidth: true Layout.fillHeight: true clip: true - model: Booru.responses - delegate: StyledText { - id: booruResponseText - text: JSON.stringify(modelData) + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: swipeView.width + height: swipeView.height + radius: Appearance.rounding.small + } + } + + spacing: 10 + model: ScriptModel { + values: Booru.responses + } + delegate: BooruResponse { + responseData: modelData + tagInputField: root.inputField } } Rectangle { + id: tagInputFieldContainer Layout.fillWidth: true radius: Appearance.rounding.small - border.width: 1 - border.color: Appearance.m3colors.m3outlineVariant - color: "transparent" + color: Appearance.colors.colLayer1 implicitWidth: tagInputField.implicitWidth - implicitHeight: tagInputField.implicitHeight + implicitHeight: Math.max(tagInputField.implicitHeight, 45) + clip: true Behavior on implicitHeight { NumberAnimation { @@ -53,7 +67,9 @@ Item { TextArea { id: tagInputField - anchors.fill: parent + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter wrapMode: TextArea.Wrap padding: 10 @@ -71,9 +87,32 @@ Item { tagInputField.insert(tagInputField.cursorPosition, "\n") event.accepted = true } else { - // Submit on Enter or Ctrl+Enter - const tagList = tagInputField.text.split(/\s+/); - Booru.makeRequest(tagList); + const inputText = tagInputField.text + if (inputText.startsWith("/")) { + // Handle special commands + const command = inputText.split(" ")[0].substring(1); + if (command === "clear") { + Booru.clearResponses(); + } + else if (command === "mode") { + const newProvider = inputText.split(" ")[1]; + Booru.setProvider(newProvider); + Booru.addSystemMessage(`Provider set to ${Booru.providers[newProvider].name}`); + } + } + else { + // Create tag list + const tagList = inputText.split(/\s+/); + let pageIndex = 1; + for (let i = 0; i < tagList.length; ++i) { // Detect page number + if (/^\d+$/.test(tagList[i])) { + pageIndex = parseInt(tagList[i], 10); + tagList.splice(i, 1); + break; + } + } + Booru.makeRequest(tagList, ConfigOptions.sidebar.booru.allowNsfw, ConfigOptions.sidebar.booru.limit, pageIndex); + } tagInputField.clear() event.accepted = true } diff --git a/.config/quickshell/modules/sidebarLeft/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/BooruResponse.qml new file mode 100644 index 000000000..9c4925f7c --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/BooruResponse.qml @@ -0,0 +1,187 @@ +import "root:/" +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io +import Quickshell +import Quickshell.Widgets +import Quickshell.Wayland +import Quickshell.Hyprland +import Qt5Compat.GraphicalEffects + +Rectangle { + id: root + property var responseData + property var tagInputField + + property real availableWidth: parent?.width + property real rowTooShortThreshold: 100 + property real imageSpacing: 5 + property real responsePadding: 5 + + anchors.left: parent?.left + anchors.right: parent?.right + implicitHeight: columnLayout.implicitHeight + root.responsePadding * 2 + + radius: Appearance.rounding.normal + color: Appearance.colors.colLayer1 + + ColumnLayout { + id: columnLayout + + anchors.fill: parent + anchors.margins: responsePadding + spacing: root.imageSpacing + + // Header: provider name + Rectangle { + id: providerNameWrapper + color: Appearance.m3colors.m3secondaryContainer + radius: Appearance.rounding.small + // height: providerName.implicitHeight + implicitWidth: providerName.implicitWidth + 10 * 2 + implicitHeight: Math.max(providerName.implicitHeight + 5 * 2, 30) + Layout.alignment: Qt.AlignLeft + + StyledText { + id: providerName + anchors.centerIn: parent + font.pixelSize: Appearance.font.pixelSize.large + color: Appearance.m3colors.m3onSecondaryContainer + text: Booru.providers[root.responseData.provider].name + } + } + + // Tags + Flickable { + id: tagsFlickable + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: { + console.log(root.responseData) + return true + } + implicitHeight: tagRowLayout.implicitHeight + // height: tagRowLayout.implicitHeight + contentWidth: tagRowLayout.implicitWidth + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: tagsFlickable.width + height: tagsFlickable.height + radius: Appearance.rounding.small + } + } + + Behavior on height { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + Behavior on implicitHeight { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + + RowLayout { + id: tagRowLayout + Layout.alignment: Qt.AlignBottom + + Repeater { + id: tagRepeater + model: root.responseData.tags + + BooruTagButton { + Layout.fillWidth: false + buttonText: modelData + onClicked: { + root.tagInputField.text += " " + modelData + } + } + } + + } + } + + Repeater { + model: { + // Group two images every row, ensuring they are of the same height + // If the height ends up being too small, put one image in the row and continue + // In other words, this is similar to Android's gallery layout at largest zoom level + let i = 0; + let rows = []; + const responseList = root.responseData.images; + while (i < responseList.length) { + let row = { + height: 0, + images: [], + }; + if (i + 1 < responseList.length) { + const img1 = responseList[i]; + const img2 = responseList[i + 1]; + // Calculate combined height if both are in the same row + // Let h = row height, w1 = h * aspect1, w2 = h * aspect2 + // w1 + w2 = availableWidth => h = availableWidth / (aspect1 + aspect2) + const combinedAspect = img1.aspect_ratio + img2.aspect_ratio; + const rowHeight = (availableWidth - root.imageSpacing - responsePadding * 2) / combinedAspect; + if (rowHeight >= rowTooShortThreshold) { + row.height = rowHeight; + row.images.push(img1); + row.images.push(img2); + rows.push(row); + i += 2; + continue; + } + } + // Otherwise, put only one image in the row + rows.push({ + height: availableWidth / responseList[i].aspect_ratio, + images: [responseList[i]], + }); + i += 1; + } + return rows; + } + delegate: RowLayout { + id: imageRow + property var rowHeight: modelData.height + spacing: root.imageSpacing + + Repeater { + model: modelData.images + Rectangle { + implicitWidth: image.width + implicitHeight: image.height + radius: Appearance.rounding.small + color: Appearance.colors.colLayer2 + Image { + id: image + anchors.fill: parent + sourceSize.width: imageRow.rowHeight * modelData.aspect_ratio + sourceSize.height: imageRow.rowHeight + fillMode: Image.PreserveAspectFit + source: modelData.preview_url + width: imageRow.rowHeight * modelData.aspect_ratio + height: imageRow.rowHeight + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: image.width + height: image.height + radius: Appearance.rounding.small + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/BooruTagButton.qml b/.config/quickshell/modules/sidebarLeft/BooruTagButton.qml new file mode 100644 index 000000000..02ed3883c --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/BooruTagButton.qml @@ -0,0 +1,34 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.Notifications + +Button { + id: button + property string buttonText + + implicitHeight: 30 + leftPadding: 10 + rightPadding: 10 + + // PointingHandInteraction {} + + background: Rectangle { + radius: Appearance.rounding.small + color: (button.down ? Appearance.colors.colSurfaceContainerHighestActive : + button.hovered ? Appearance.colors.colSurfaceContainerHighestHover : + Appearance.m3colors.m3surfaceContainerHighest) + + + } + + contentItem: StyledText { + horizontalAlignment: Text.AlignHCenter + text: buttonText + color: Appearance.m3colors.m3onSurface + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index 97b9e9b3d..7dfaaa42a 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -5,12 +5,12 @@ import "root:/modules/common/widgets" import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Qt5Compat.GraphicalEffects import Quickshell.Io import Quickshell import Quickshell.Widgets import Quickshell.Wayland import Quickshell.Hyprland -import Qt5Compat.GraphicalEffects Scope { // Scope id: root @@ -136,7 +136,9 @@ Scope { // Scope text: "To be implemented" horizontalAlignment: Text.AlignHCenter } - Anime {} + Anime { + panelWindow: sidebarRoot + } } } diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index d49d93c3c..4f4bf8549 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -1,6 +1,7 @@ pragma Singleton pragma ComponentBehavior: Bound +import "root:/modules/common" import Quickshell; import Quickshell.Io; import Qt.labs.platform @@ -9,6 +10,7 @@ import QtQuick; Singleton { id: root + property var responses: [] property var getWorkingImageSource: (url) => { if (url.includes('pximg.net')) { return `https://www.pixiv.net/en/artworks/${url.substring(url.lastIndexOf('/') + 1).replace(/_p\d+\.(png|jpg|jpeg|gif)$/, '')}`; @@ -16,8 +18,10 @@ Singleton { return url; } - property var providerList: ["yandere", "konachan", "danbooru", "gelbooru"] + property var defaultUserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" + property var providerList: ["yandere", "konachan", "zerochan", "danbooru", "gelbooru"] property var providers: { + "system": { "name": "System" }, "yandere": { "name": "yande.re", "url": "https://yande.re", @@ -27,6 +31,8 @@ Singleton { return response.map(item => { return { "id": item.id, + "width": item.width, + "height": item.height, "aspect_ratio": item.width / item.height, "tags": item.tags, "rating": item.rating, @@ -50,6 +56,8 @@ Singleton { return response.map(item => { return { "id": item.id, + "width": item.width, + "height": item.height, "aspect_ratio": item.width / item.height, "tags": item.tags, "rating": item.rating, @@ -64,6 +72,32 @@ Singleton { }) } }, + "zerochan": { + "name": "Zerochan", + "url": "https://www.zerochan.net", + "api": "https://www.zerochan.net/?json", + "listAccess": ["items"], + "mapFunc": (response) => { + return response.map(item => { + return { + "id": item.id, + "width": item.width, + "height": item.height, + "aspect_ratio": item.width / item.height, + "tags": item.tags.join(" "), + "rating": "safe", // Zerochan doesn't have nsfw + "is_nsfw": false, + "md5": item.md5, + "preview_url": item.thumbnail, + "sample_url": item.thumbnail, + "file_url": item.thumbnail, + "file_ext": "avif", + "source": getWorkingImageSource(item.source), + "character": item.tag + } + }) + } + }, "danbooru": { "name": "Danbooru", "url": "https://danbooru.donmai.us", @@ -73,6 +107,8 @@ Singleton { return response.map(item => { return { "id": item.id, + "width": item.image_width, + "height": item.image_height, "aspect_ratio": item.image_width / item.image_height, "tags": item.tag_string, "rating": item.rating, @@ -96,6 +132,8 @@ Singleton { return response.map(item => { return { "id": item.id, + "width": item.width, + "height": item.height, "aspect_ratio": item.width / item.height, "tags": item.tags, "rating": item.rating.replace('general', 's').charAt(0), @@ -111,11 +149,8 @@ Singleton { } } } - property var responses: [] - onResponsesChanged: { - console.log("[Booru] Responses changed: " + JSON.stringify(responses)) - } - property var currentProvider: "yandere" + + property var currentProvider: ConfigOptions.sidebar.booru.defaultProvider function setProvider(provider) { if (providerList.indexOf(provider) !== -1) { @@ -125,18 +160,40 @@ Singleton { } } - function constructRequestUrl(tags, nsfw=true, limit=20) { + function clearResponses() { + responses = [] + } + + function addSystemMessage(message) { + responses.push({ + "provider": "system", + "tags": [], + "page": 1, + "images": [], + "message": `${message}` + }) + } + + function constructRequestUrl(tags, nsfw=true, limit=20, page=1) { var provider = providers[currentProvider] var baseUrl = provider.api var tagString = tags.join(" ") - if (!nsfw) { + if (!nsfw && currentProvider !== "zerochan") { tagString += " rating:safe" } var params = [] - // Danbooru, Yandere, Konachan: tags & limit - if (currentProvider === "danbooru" || currentProvider === "yandere" || currentProvider === "konachan") { + // Tags & limit + if (currentProvider === "zerochan") { + params.push("c=" + tagString) // zerochan doesn't have search in api, so we use color + params.push("l=" + limit) + params.push("s=" + "fav") + params.push("t=" + 1) + params.push("p=" + page) + } + else { params.push("tags=" + encodeURIComponent(tagString)) params.push("limit=" + limit) + params.push("page=" + page) } var url = baseUrl if (baseUrl.indexOf("?") === -1) { @@ -147,8 +204,8 @@ Singleton { return url } - function makeRequest(tags, nsfw=true, limit=20) { - var url = constructRequestUrl(tags, nsfw, limit) + function makeRequest(tags, nsfw=false, limit=20, page=1) { + var url = constructRequestUrl(tags, nsfw, limit, page) console.log("[Booru] Making request to " + url) var xhr = new XMLHttpRequest() @@ -157,6 +214,7 @@ Singleton { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { try { // console.log("[Booru] Raw response length: " + xhr.responseText.length) + console.log("[Booru] Raw response: " + xhr.responseText) var response = JSON.parse(xhr.responseText) // Access nested properties based on listAccess @@ -169,9 +227,15 @@ Singleton { } } response = providers[currentProvider].mapFunc(response) - // console.log("[Booru] Scoped & mapped response: " + JSON.stringify(response)) + console.log("[Booru] Scoped & mapped response: " + JSON.stringify(response)) var newResponses = root.responses.slice() // make a shallow copy - newResponses.push(response) + newResponses.push({ + "provider": currentProvider, + "tags": tags, + "page": page, + "images": response, + "message": "" + }) root.responses = newResponses } catch (e) { @@ -185,11 +249,18 @@ Singleton { try { // Required for danbooru - xhr.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36") - } catch (e) { - console.log("Could not set User-Agent:", e) - } - xhr.send() + if (currentProvider == "danbooru") { + xhr.setRequestHeader("User-Agent", defaultUserAgent) + } + else if (currentProvider == "zerochan") { + const userAgent = ConfigOptions.sidebar.booru.zerochan.username ? `Desktop sidebar booru viewer - ${ConfigOptions.sidebar.booru.zerochan.username}` : defaultUserAgent + console.log("Setting User-Agent for zerochan: " + userAgent) + xhr.setRequestHeader("User-Agent", userAgent) + } + xhr.send() + } catch (error) { + console.log("Could not set User-Agent:", error) + } } } From f24cd8fa35758d5c0b3d49c7acb960ce6f954074 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 28 Apr 2025 23:58:10 +0200 Subject: [PATCH 126/824] booru: page number --- .../quickshell/modules/sidebarLeft/Anime.qml | 19 +++++ .../modules/sidebarLeft/BooruImage.qml | 44 +++++++++++ .../modules/sidebarLeft/BooruResponse.qml | 78 +++++++++---------- 3 files changed, 101 insertions(+), 40 deletions(-) create mode 100644 .config/quickshell/modules/sidebarLeft/BooruImage.qml diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 2db36420d..da64020cd 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -15,6 +15,17 @@ Item { property var panelWindow property var inputField: tagInputField + Keys.onPressed: (event) => { + tagInputField.forceActiveFocus() + if (event.key === Qt.Key_PageUp) { + booruResponseListView.contentY = Math.max(0, booruResponseListView.contentY - booruResponseListView.height / 2) + event.accepted = true + } else if (event.key === Qt.Key_PageDown) { + booruResponseListView.contentY = Math.min(booruResponseListView.contentHeight - booruResponseListView.height, booruResponseListView.contentY + booruResponseListView.height / 2) + event.accepted = true + } + } + onFocusChanged: (focus) => { if (focus) { tagInputField.forceActiveFocus() @@ -39,6 +50,14 @@ Item { } } + Behavior on contentY { + NumberAnimation { + id: scrollAnim + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + spacing: 10 model: ScriptModel { values: Booru.responses diff --git a/.config/quickshell/modules/sidebarLeft/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/BooruImage.qml new file mode 100644 index 000000000..7d5297791 --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/BooruImage.qml @@ -0,0 +1,44 @@ +import "root:/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects + +Button { + id: root + property var imageData + property var rowHeight + + padding: 0 + + PointingHandInteraction {} + + background: Rectangle { + implicitWidth: imageData.width + implicitHeight: imageData.height + radius: Appearance.rounding.small + color: Appearance.colors.colLayer2 + } + + contentItem: Image { + id: imageData + anchors.fill: parent + sourceSize.width: imageRow.rowHeight * modelData.aspect_ratio + sourceSize.height: imageRow.rowHeight + fillMode: Image.PreserveAspectFit + source: modelData.preview_url + width: imageRow.rowHeight * modelData.aspect_ratio + height: imageRow.rowHeight + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: imageData.width + height: imageData.height + radius: Appearance.rounding.small + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/BooruResponse.qml index 9c4925f7c..6dd5c3498 100644 --- a/.config/quickshell/modules/sidebarLeft/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/BooruResponse.qml @@ -32,26 +32,45 @@ Rectangle { ColumnLayout { id: columnLayout - anchors.fill: parent + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top anchors.margins: responsePadding spacing: root.imageSpacing - // Header: provider name - Rectangle { - id: providerNameWrapper - color: Appearance.m3colors.m3secondaryContainer - radius: Appearance.rounding.small - // height: providerName.implicitHeight - implicitWidth: providerName.implicitWidth + 10 * 2 - implicitHeight: Math.max(providerName.implicitHeight + 5 * 2, 30) - Layout.alignment: Qt.AlignLeft + // Provider name + RowLayout { + Rectangle { + id: providerNameWrapper + color: Appearance.m3colors.m3secondaryContainer + radius: Appearance.rounding.small + implicitWidth: providerName.implicitWidth + 10 * 2 + implicitHeight: Math.max(providerName.implicitHeight + 5 * 2, 30) + Layout.alignment: Qt.AlignVCenter - StyledText { - id: providerName - anchors.centerIn: parent - font.pixelSize: Appearance.font.pixelSize.large - color: Appearance.m3colors.m3onSecondaryContainer - text: Booru.providers[root.responseData.provider].name + StyledText { + id: providerName + anchors.centerIn: parent + font.pixelSize: Appearance.font.pixelSize.large + color: Appearance.m3colors.m3onSecondaryContainer + text: Booru.providers[root.responseData.provider].name + } + } + Item { Layout.fillWidth: true } + Rectangle { + color: Appearance.colors.colLayer2 + radius: Appearance.rounding.small + implicitWidth: Math.max(pageNumber.implicitWidth + 10 * 2, 20) + implicitHeight: pageNumber.implicitHeight + 5 * 2 + Layout.alignment: Qt.AlignVCenter + + StyledText { + id: pageNumber + anchors.centerIn: parent + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.colors.colOnLayer2 + text: `Page ${root.responseData.page}` + } } } @@ -155,30 +174,9 @@ Rectangle { Repeater { model: modelData.images - Rectangle { - implicitWidth: image.width - implicitHeight: image.height - radius: Appearance.rounding.small - color: Appearance.colors.colLayer2 - Image { - id: image - anchors.fill: parent - sourceSize.width: imageRow.rowHeight * modelData.aspect_ratio - sourceSize.height: imageRow.rowHeight - fillMode: Image.PreserveAspectFit - source: modelData.preview_url - width: imageRow.rowHeight * modelData.aspect_ratio - height: imageRow.rowHeight - - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: image.width - height: image.height - radius: Appearance.rounding.small - } - } - } + delegate: BooruImage { + imageData: modelData + rowHeight: imageRow.rowHeight } } } From 5de8414b6417b98f94b3e323ae4be5b7e0289aa6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 28 Apr 2025 23:59:49 +0200 Subject: [PATCH 127/824] booru image click to open sourcee --- .config/quickshell/modules/sidebarLeft/BooruImage.qml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.config/quickshell/modules/sidebarLeft/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/BooruImage.qml index 7d5297791..8442754e9 100644 --- a/.config/quickshell/modules/sidebarLeft/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/BooruImage.qml @@ -4,6 +4,7 @@ import "root:/modules/common/widgets" import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Quickshell.Hyprland import Qt5Compat.GraphicalEffects Button { @@ -15,6 +16,10 @@ Button { PointingHandInteraction {} + onClicked: { + Hyprland.dispatch(`exec xdg-open ${imageData.source}`) + } + background: Rectangle { implicitWidth: imageData.width implicitHeight: imageData.height From b605cf33dd6a8af98cb862a822837558622d975b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 29 Apr 2025 07:21:15 +0200 Subject: [PATCH 128/824] move anime stuff in their own folder --- .config/quickshell/modules/sidebarLeft/Anime.qml | 1 + .../quickshell/modules/sidebarLeft/{ => anime}/BooruImage.qml | 0 .../quickshell/modules/sidebarLeft/{ => anime}/BooruResponse.qml | 0 .../modules/sidebarLeft/{ => anime}/BooruTagButton.qml | 0 4 files changed, 1 insertion(+) rename .config/quickshell/modules/sidebarLeft/{ => anime}/BooruImage.qml (100%) rename .config/quickshell/modules/sidebarLeft/{ => anime}/BooruResponse.qml (100%) rename .config/quickshell/modules/sidebarLeft/{ => anime}/BooruTagButton.qml (100%) diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index da64020cd..56db81ec0 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -2,6 +2,7 @@ import "root:/" import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" +import "./anime/" import QtQuick import QtQuick.Controls import QtQuick.Layouts diff --git a/.config/quickshell/modules/sidebarLeft/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml similarity index 100% rename from .config/quickshell/modules/sidebarLeft/BooruImage.qml rename to .config/quickshell/modules/sidebarLeft/anime/BooruImage.qml diff --git a/.config/quickshell/modules/sidebarLeft/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml similarity index 100% rename from .config/quickshell/modules/sidebarLeft/BooruResponse.qml rename to .config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml diff --git a/.config/quickshell/modules/sidebarLeft/BooruTagButton.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruTagButton.qml similarity index 100% rename from .config/quickshell/modules/sidebarLeft/BooruTagButton.qml rename to .config/quickshell/modules/sidebarLeft/anime/BooruTagButton.qml From 5543efac7ac4b87dc92985eac3448aadde401309 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 29 Apr 2025 10:44:44 +0200 Subject: [PATCH 129/824] booru: more sexy --- .../common/widgets/NotificationWidget.qml | 4 +- .../modules/common/widgets/StyledSwitch.qml | 71 +++++ .../quickshell/modules/sidebarLeft/Anime.qml | 271 +++++++++++++----- .../modules/sidebarLeft/SidebarLeft.qml | 9 +- .../modules/sidebarLeft/anime/BooruImage.qml | 24 +- .../sidebarLeft/anime/BooruResponse.qml | 47 ++- .../sidebarLeft/anime/BooruTagButton.qml | 2 - .../sidebarRight/CenterWidgetGroup.qml | 8 + .../modules/sidebarRight/todo/TaskList.qml | 4 +- .config/quickshell/services/Booru.qml | 36 ++- 10 files changed, 363 insertions(+), 113 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/StyledSwitch.qml diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index f912c9cef..c6d12bc83 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -445,7 +445,7 @@ Item { ? `` + `${notificationObject.body.replace(/\n/g, "
")}` : notificationObject.body.replace(/ { Qt.openUrlExternally(link) Hyprland.dispatch("global quickshell:sidebarRightClose") } @@ -453,7 +453,7 @@ Item { anchors.fill: parent acceptedButtons: Qt.NoButton // Only for hover hoverEnabled: true - cursorShape: notificationBodyText.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor + cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor } } } diff --git a/.config/quickshell/modules/common/widgets/StyledSwitch.qml b/.config/quickshell/modules/common/widgets/StyledSwitch.qml new file mode 100644 index 000000000..c0b0a4ffb --- /dev/null +++ b/.config/quickshell/modules/common/widgets/StyledSwitch.qml @@ -0,0 +1,71 @@ +import "root:/modules/common/" +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Qt5Compat.GraphicalEffects + +Switch { + id: root + property real scale: 1 + implicitHeight: 32 * root.scale + implicitWidth: 52 * root.scale + + // Custom track styling + background: Rectangle { + width: parent.width + height: parent.height + radius: Appearance.rounding.full + color: root.checked ? Appearance.m3colors.m3primary : Appearance.m3colors.m3surfaceContainerHighest + border.width: 2 * root.scale + border.color: root.checked ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + Behavior on border.color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } + + // Custom thumb styling + indicator: Rectangle { + width: root.pressed ? (28 * root.scale) : root.checked ? (24 * root.scale) : (16 * root.scale) + height: root.pressed ? (28 * root.scale) : root.checked ? (24 * root.scale) : (16 * root.scale) + radius: Appearance.rounding.full + color: root.checked ? Appearance.m3colors.m3onPrimary : Appearance.m3colors.m3outline + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: root.checked ? (root.pressed ? (22 * root.scale) : 24 * root.scale) : (root.pressed ? (2 * root.scale) : 8 * root.scale) + + Behavior on anchors.leftMargin { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + Behavior on width { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + Behavior on height { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } +} diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 56db81ec0..79aa9fa46 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -16,14 +16,48 @@ Item { property var panelWindow property var inputField: tagInputField + function handleInput(inputText) { + if (inputText.startsWith("/")) { + // Handle special commands + const command = inputText.split(" ")[0].substring(1); + const args = inputText.split(" ").slice(1); + if (command === "clear") { + Booru.clearResponses(); + } + else if (command === "mode") { + const newProvider = args[0]; + Booru.setProvider(newProvider); + } + else if (command == "lewd" || command == "nsfw") { + ConfigOptions.sidebar.booru.allowNsfw = !ConfigOptions.sidebar.booru.allowNsfw + } + + } + else { + // Create tag list + const tagList = inputText.split(/\s+/); + let pageIndex = 1; + for (let i = 0; i < tagList.length; ++i) { // Detect page number + if (/^\d+$/.test(tagList[i])) { + pageIndex = parseInt(tagList[i], 10); + tagList.splice(i, 1); + break; + } + } + Booru.makeRequest(tagList, ConfigOptions.sidebar.booru.allowNsfw, ConfigOptions.sidebar.booru.limit, pageIndex); + } + } + Keys.onPressed: (event) => { tagInputField.forceActiveFocus() - if (event.key === Qt.Key_PageUp) { - booruResponseListView.contentY = Math.max(0, booruResponseListView.contentY - booruResponseListView.height / 2) - event.accepted = true - } else if (event.key === Qt.Key_PageDown) { - booruResponseListView.contentY = Math.min(booruResponseListView.contentHeight - booruResponseListView.height, booruResponseListView.contentY + booruResponseListView.height / 2) - event.accepted = true + if (event.modifiers === Qt.NoModifier) { + if (event.key === Qt.Key_PageUp) { + booruResponseListView.contentY = Math.max(0, booruResponseListView.contentY - booruResponseListView.height / 2) + event.accepted = true + } else if (event.key === Qt.Key_PageDown) { + booruResponseListView.contentY = Math.min(booruResponseListView.contentHeight - booruResponseListView.height / 2, booruResponseListView.contentY + booruResponseListView.height / 2) + event.accepted = true + } } } @@ -37,46 +71,75 @@ Item { id: columnLayout anchors.fill: parent - ListView { // Booru responses - id: booruResponseListView + Item { Layout.fillWidth: true Layout.fillHeight: true - clip: true - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: swipeView.width - height: swipeView.height - radius: Appearance.rounding.small + ListView { // Booru responses + id: booruResponseListView + anchors.fill: parent + clip: true + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: swipeView.width + height: swipeView.height + radius: Appearance.rounding.small + } + } + + Behavior on contentY { + NumberAnimation { + id: scrollAnim + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + + spacing: 10 + model: ScriptModel { + values: Booru.responses + } + delegate: BooruResponse { + responseData: modelData + tagInputField: root.inputField } } - Behavior on contentY { - NumberAnimation { - id: scrollAnim - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type - } - } + Item { // Placeholder when list is empty + visible: Booru.responses.length === 0 + anchors.fill: parent - spacing: 10 - model: ScriptModel { - values: Booru.responses - } - delegate: BooruResponse { - responseData: modelData - tagInputField: root.inputField + ColumnLayout { + anchors.centerIn: parent + spacing: 5 + + MaterialSymbol { + Layout.alignment: Qt.AlignHCenter + font.pixelSize: 55 + color: Appearance.m3colors.m3outline + text: "bookmark_heart" + } + StyledText { + Layout.alignment: Qt.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3outline + horizontalAlignment: Text.AlignHCenter + text: "Anime boorus" + } + } } } - Rectangle { - id: tagInputFieldContainer + Rectangle { // Tag input field + id: tagInputContainer Layout.fillWidth: true radius: Appearance.rounding.small color: Appearance.colors.colLayer1 - implicitWidth: tagInputField.implicitWidth - implicitHeight: Math.max(tagInputField.implicitHeight, 45) + implicitWidth: tagInputColumnLayout.implicitWidth + implicitHeight: Math.max(tagInputColumnLayout.implicitHeight, 45) clip: true + border.color: Appearance.m3colors.m3outlineVariant + border.width: 1 Behavior on implicitHeight { NumberAnimation { @@ -85,56 +148,118 @@ Item { } } - TextArea { - id: tagInputField + ColumnLayout { + id: tagInputColumnLayout anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - wrapMode: TextArea.Wrap + TextArea { // The actual input field widget + id: tagInputField + wrapMode: TextArea.Wrap + Layout.fillWidth: true + Layout.topMargin: 5 + padding: 10 + color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant + renderType: Text.NativeRendering + selectedTextColor: Appearance.m3colors.m3onPrimary + selectionColor: Appearance.m3colors.m3primary + placeholderText: qsTr("Enter tags") + placeholderTextColor: Appearance.m3colors.m3outline - padding: 10 - color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant - renderType: Text.NativeRendering - selectedTextColor: Appearance.m3colors.m3onPrimary - selectionColor: Appearance.m3colors.m3primary - placeholderText: qsTr("Enter tags") - placeholderTextColor: Appearance.m3colors.m3outline + background: Item {} - Keys.onPressed: (event) => { - if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { - if (event.modifiers & Qt.ShiftModifier) { - // Insert newline - tagInputField.insert(tagInputField.cursorPosition, "\n") - event.accepted = true - } else { - const inputText = tagInputField.text - if (inputText.startsWith("/")) { - // Handle special commands - const command = inputText.split(" ")[0].substring(1); - if (command === "clear") { - Booru.clearResponses(); - } - else if (command === "mode") { - const newProvider = inputText.split(" ")[1]; - Booru.setProvider(newProvider); - Booru.addSystemMessage(`Provider set to ${Booru.providers[newProvider].name}`); + Keys.onPressed: (event) => { + if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { + if (event.modifiers & Qt.ShiftModifier) { + // Insert newline + tagInputField.insert(tagInputField.cursorPosition, "\n") + event.accepted = true + } else { // Accept text + const inputText = tagInputField.text + root.handleInput(inputText) + tagInputField.clear() + event.accepted = true + } + } + } + } + + RowLayout { // Controls + id: commandButtonsRow + spacing: 5 + Layout.bottomMargin: 5 + Layout.leftMargin: 5 + Layout.rightMargin: 5 + + property var commands: [ + { + name: "/mode", + sendDirectly: false, + }, + { + name: "/clear", + sendDirectly: true, + }, + ] + + Rectangle { + implicitWidth: switchesRow.implicitWidth + + RowLayout { + id: switchesRow + spacing: 5 + anchors.centerIn: parent + + StyledText { + Layout.fillHeight: true + Layout.leftMargin: 10 + Layout.alignment: Qt.AlignVCenter + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.colors.coloOnLayer1 + text: qsTr("NSFW") + } + StyledSwitch { + id: nsfwSwitch + scale: 0.75 + Layout.alignment: Qt.AlignVCenter + checked: ConfigOptions.sidebar.booru.allowNsfw + onCheckedChanged: { + ConfigOptions.sidebar.booru.allowNsfw = checked } } - else { - // Create tag list - const tagList = inputText.split(/\s+/); - let pageIndex = 1; - for (let i = 0; i < tagList.length; ++i) { // Detect page number - if (/^\d+$/.test(tagList[i])) { - pageIndex = parseInt(tagList[i], 10); - tagList.splice(i, 1); - break; + } + } + + Item { Layout.fillWidth: true } + + Repeater { // Command buttons + id: commandRepeater + model: commandButtonsRow.commands + delegate: BooruTagButton { + id: tagButton + buttonText: modelData.name + background: Rectangle { + radius: Appearance.rounding.small + color: (tagButton.down ? Appearance.colors.colLayer1Active : + tagButton.hovered ? Appearance.colors.colLayer1Hover : + Appearance.transparentize(Appearance.colors.colLayer1, 1)) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type } } - Booru.makeRequest(tagList, ConfigOptions.sidebar.booru.allowNsfw, ConfigOptions.sidebar.booru.limit, pageIndex); } - tagInputField.clear() - event.accepted = true + onClicked: { + if(modelData.sendDirectly) { + root.handleInput(modelData.name) + } else { + tagInputField.text = modelData.name + " " + tagInputField.cursorPosition = tagInputField.text.length + tagInputField.forceActiveFocus() + } + } } } } diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index 7dfaaa42a..71f7713f1 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -90,9 +90,16 @@ Scope { // Scope if (event.modifiers === Qt.ControlModifier) { if (event.key === Qt.Key_PageDown) { sidebarRoot.currentTab = Math.min(sidebarRoot.currentTab + 1, root.tabButtonList.length - 1) - } else if (event.key === Qt.Key_PageUp) { + } + else if (event.key === Qt.Key_PageUp) { sidebarRoot.currentTab = Math.max(sidebarRoot.currentTab - 1, 0) } + else if (event.key === Qt.Key_Tab) { + sidebarRoot.currentTab = (sidebarRoot.currentTab + 1) % root.tabButtonList.length; + } + else if (event.key === Qt.Key_Backtab) { + sidebarRoot.currentTab = (sidebarRoot.currentTab - 1 + root.tabButtonList.length) % root.tabButtonList.length; + } event.accepted = true; } } diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index 8442754e9..6bd450a42 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -12,7 +12,13 @@ Button { property var imageData property var rowHeight + // onImageDataChanged: { + // console.log("Image data changed:", imageData) + // } + padding: 0 + implicitWidth: imageObject.width + implicitHeight: imageObject.height PointingHandInteraction {} @@ -21,27 +27,27 @@ Button { } background: Rectangle { - implicitWidth: imageData.width - implicitHeight: imageData.height + implicitWidth: imageObject.width + implicitHeight: imageObject.height radius: Appearance.rounding.small color: Appearance.colors.colLayer2 } contentItem: Image { - id: imageData + id: imageObject anchors.fill: parent - sourceSize.width: imageRow.rowHeight * modelData.aspect_ratio - sourceSize.height: imageRow.rowHeight + sourceSize.width: root.rowHeight * modelData.aspect_ratio + sourceSize.height: root.rowHeight fillMode: Image.PreserveAspectFit source: modelData.preview_url - width: imageRow.rowHeight * modelData.aspect_ratio - height: imageRow.rowHeight + width: root.rowHeight * modelData.aspect_ratio + height: root.rowHeight layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { - width: imageData.width - height: imageData.height + width: imageObject.width + height: imageObject.height radius: Appearance.rounding.small } } diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index 6dd5c3498..42aaf8117 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -14,9 +14,13 @@ import Qt5Compat.GraphicalEffects Rectangle { id: root - property var responseData + property var responseData: {} property var tagInputField + onResponseDataChanged: { + console.log("Response data changed:", responseData) + } + property real availableWidth: parent?.width property real rowTooShortThreshold: 100 property real imageSpacing: 5 @@ -38,9 +42,8 @@ Rectangle { anchors.margins: responsePadding spacing: root.imageSpacing - // Provider name - RowLayout { - Rectangle { + RowLayout { // Header + Rectangle { // Provider name id: providerNameWrapper color: Appearance.m3colors.m3secondaryContainer radius: Appearance.rounding.small @@ -52,31 +55,33 @@ Rectangle { id: providerName anchors.centerIn: parent font.pixelSize: Appearance.font.pixelSize.large + font.weight: Font.DemiBold color: Appearance.m3colors.m3onSecondaryContainer text: Booru.providers[root.responseData.provider].name } } Item { Layout.fillWidth: true } - Rectangle { + Rectangle { // Page number + visible: root.responseData.page != "" && root.responseData.page > 0 color: Appearance.colors.colLayer2 radius: Appearance.rounding.small - implicitWidth: Math.max(pageNumber.implicitWidth + 10 * 2, 20) + implicitWidth: Math.max(pageNumber.implicitWidth + 10 * 2, 30) implicitHeight: pageNumber.implicitHeight + 5 * 2 - Layout.alignment: Qt.AlignVCenter + Layout.alignment: Qt.AlignTop StyledText { id: pageNumber anchors.centerIn: parent - font.pixelSize: Appearance.font.pixelSize.small + font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colOnLayer2 text: `Page ${root.responseData.page}` } } } - // Tags - Flickable { + Flickable { // Tag strip id: tagsFlickable + visible: root.responseData.tags.length > 0 Layout.alignment: Qt.AlignLeft Layout.fillWidth: { console.log(root.responseData) @@ -128,6 +133,28 @@ Rectangle { } } + StyledText { // Message + id: messageText + Layout.fillWidth: true + visible: root.responseData.message.length > 0 + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.colors.colOnLayer1 + text: root.responseData.message + wrapMode: Text.WordWrap + Layout.margins: responsePadding + textFormat: Text.MarkdownText + onLinkActivated: (link) => { + Qt.openUrlExternally(link) + Hyprland.dispatch("global quickshell:sidebarLeftClose") + } + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton // Only for hover + hoverEnabled: true + cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor + } + } + Repeater { model: { // Group two images every row, ensuring they are of the same height diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruTagButton.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruTagButton.qml index 02ed3883c..0154cdd23 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruTagButton.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruTagButton.qml @@ -22,8 +22,6 @@ Button { color: (button.down ? Appearance.colors.colSurfaceContainerHighestActive : button.hovered ? Appearance.colors.colSurfaceContainerHighestHover : Appearance.m3colors.m3surfaceContainerHighest) - - } contentItem: StyledText { diff --git a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml index 1503ef107..3a4ed520f 100644 --- a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml @@ -28,6 +28,14 @@ Rectangle { } event.accepted = true; } + if (event.modifiers === Qt.ControlModifier) { + if (event.key === Qt.Key_Tab) { + root.currentTab = (root.currentTab + 1) % root.tabButtonList.length; + } else if (event.key === Qt.Key_Backtab) { + root.currentTab = (root.currentTab - 1 + root.tabButtonList.length) % root.tabButtonList.length; + } + event.accepted = true; + } } ColumnLayout { diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml index f68dadaec..401ed1010 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -146,8 +146,8 @@ Item { } } } - // Placeholder when list is empty - Item { + + Item { // Placeholder when list is empty visible: taskList.length === 0 anchors.fill: parent diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index 4f4bf8549..1357d4f17 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -10,6 +10,13 @@ import QtQuick; Singleton { id: root + Connections { + target: ConfigOptions.sidebar.booru + function onAllowNsfwChanged() { + root.addSystemMessage(ConfigOptions.sidebar.booru.allowNsfw ? qsTr("Tiddies enabled") : qsTr("No horny")) + } + } + property var responses: [] property var getWorkingImageSource: (url) => { if (url.includes('pximg.net')) { @@ -42,7 +49,7 @@ Singleton { "sample_url": item.sample_url ?? item.file_url, "file_url": item.file_url, "file_ext": item.file_ext, - "source": getWorkingImageSource(item.source), + "source": getWorkingImageSource(item.source) ?? item.file_url, } }) } @@ -67,7 +74,7 @@ Singleton { "sample_url": item.sample_url ?? item.file_url, "file_url": item.file_url, "file_ext": item.file_ext, - "source": getWorkingImageSource(item.source), + "source": getWorkingImageSource(item.source) ?? item.file_url, } }) } @@ -92,7 +99,7 @@ Singleton { "sample_url": item.thumbnail, "file_url": item.thumbnail, "file_ext": "avif", - "source": getWorkingImageSource(item.source), + "source": getWorkingImageSource(item.source) ?? item.thumbnail, "character": item.tag } }) @@ -118,7 +125,7 @@ Singleton { "sample_url": item.file_url ?? item.large_file_url, "file_url": item.large_file_url, "file_ext": item.file_ext, - "source": getWorkingImageSource(item.source), + "source": getWorkingImageSource(item.source) ?? item.file_url, } }) } @@ -143,7 +150,7 @@ Singleton { "sample_url": item.sample_url ?? item.file_url, "file_url": item.file_url, "file_ext": item.file_url.split('.').pop(), - "source": getWorkingImageSource(item.source), + "source": getWorkingImageSource(item.source) ?? item.file_url, } }) } @@ -155,8 +162,11 @@ Singleton { function setProvider(provider) { if (providerList.indexOf(provider) !== -1) { currentProvider = provider + root.addSystemMessage(qsTr("Provider set to ") + providers[provider].name + + (provider == "zerochan" ? qsTr(". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!") : "")) } else { console.log("[Booru] Invalid provider: " + provider) + root.addSystemMessage(qsTr("Invalid provider. Supported providers: \n- ") + providerList.join("\n- ")) } } @@ -165,13 +175,13 @@ Singleton { } function addSystemMessage(message) { - responses.push({ + responses = [...responses, { "provider": "system", "tags": [], - "page": 1, + "page": -1, "images": [], "message": `${message}` - }) + }] } function constructRequestUrl(tags, nsfw=true, limit=20, page=1) { @@ -214,7 +224,7 @@ Singleton { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { try { // console.log("[Booru] Raw response length: " + xhr.responseText.length) - console.log("[Booru] Raw response: " + xhr.responseText) + // console.log("[Booru] Raw response: " + xhr.responseText) var response = JSON.parse(xhr.responseText) // Access nested properties based on listAccess @@ -227,16 +237,14 @@ Singleton { } } response = providers[currentProvider].mapFunc(response) - console.log("[Booru] Scoped & mapped response: " + JSON.stringify(response)) - var newResponses = root.responses.slice() // make a shallow copy - newResponses.push({ + // console.log("[Booru] Scoped & mapped response: " + JSON.stringify(response)) + root.responses = [...root.responses, { "provider": currentProvider, "tags": tags, "page": page, "images": response, "message": "" - }) - root.responses = newResponses + }] } catch (e) { console.log("[Booru] Failed to parse response: " + e) From 891da6c5222c1cbfe069fe5e5647652c11123388 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 29 Apr 2025 11:10:53 +0200 Subject: [PATCH 130/824] booru: style changes, tag button dont add space at beginning --- .../quickshell/modules/sidebarLeft/Anime.qml | 20 ++++++++++++++++--- .../sidebarLeft/anime/BooruResponse.qml | 11 +++++----- .../modules/sidebarRight/todo/TaskList.qml | 10 +++++++++- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 79aa9fa46..17eceb72d 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -97,18 +97,32 @@ Item { spacing: 10 model: ScriptModel { - values: Booru.responses + values: { + console.log(JSON.stringify(Booru.responses)) + return Booru.responses + } } delegate: BooruResponse { - responseData: modelData + responseData: { + console.log("Data at index " + index + ": " + JSON.stringify(modelData)) + return modelData + } tagInputField: root.inputField } } Item { // Placeholder when list is empty - visible: Booru.responses.length === 0 + opacity: Booru.responses.length === 0 ? 1 : 0 + visible: opacity > 0 anchors.fill: parent + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + ColumnLayout { anchors.centerIn: parent spacing: 5 diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index 42aaf8117..1abe82694 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -61,10 +61,8 @@ Rectangle { } } Item { Layout.fillWidth: true } - Rectangle { // Page number + Item { // Page number visible: root.responseData.page != "" && root.responseData.page > 0 - color: Appearance.colors.colLayer2 - radius: Appearance.rounding.small implicitWidth: Math.max(pageNumber.implicitWidth + 10 * 2, 30) implicitHeight: pageNumber.implicitHeight + 5 * 2 Layout.alignment: Qt.AlignTop @@ -125,7 +123,8 @@ Rectangle { Layout.fillWidth: false buttonText: modelData onClicked: { - root.tagInputField.text += " " + modelData + if(root.tagInputField.text.length !== 0) root.tagInputField.text += " " + root.tagInputField.text += modelData } } } @@ -168,6 +167,7 @@ Rectangle { height: 0, images: [], }; + const availableImageWidth = availableWidth - root.imageSpacing - (responsePadding * 2) if (i + 1 < responseList.length) { const img1 = responseList[i]; const img2 = responseList[i + 1]; @@ -175,7 +175,7 @@ Rectangle { // Let h = row height, w1 = h * aspect1, w2 = h * aspect2 // w1 + w2 = availableWidth => h = availableWidth / (aspect1 + aspect2) const combinedAspect = img1.aspect_ratio + img2.aspect_ratio; - const rowHeight = (availableWidth - root.imageSpacing - responsePadding * 2) / combinedAspect; + const rowHeight = availableImageWidth / combinedAspect; if (rowHeight >= rowTooShortThreshold) { row.height = rowHeight; row.images.push(img1); @@ -186,6 +186,7 @@ Rectangle { } } // Otherwise, put only one image in the row + const rowHeight = availableImageWidth / responseList[i].aspect_ratio; rows.push({ height: availableWidth / responseList[i].aspect_ratio, images: [responseList[i]], diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml index 401ed1010..a77915b54 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -148,9 +148,17 @@ Item { } Item { // Placeholder when list is empty - visible: taskList.length === 0 + visible: opacity > 0 + opacity: taskList.length === 0 ? 1 : 0 anchors.fill: parent + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + ColumnLayout { anchors.centerIn: parent spacing: 5 From f21f670d2146d069a7c1a497fdb6982f1df32b40 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 29 Apr 2025 11:17:35 +0200 Subject: [PATCH 131/824] remove debug print --- .config/quickshell/modules/sidebarLeft/Anime.qml | 5 +---- .../quickshell/modules/sidebarLeft/anime/BooruResponse.qml | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 17eceb72d..f73cff822 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -103,10 +103,7 @@ Item { } } delegate: BooruResponse { - responseData: { - console.log("Data at index " + index + ": " + JSON.stringify(modelData)) - return modelData - } + responseData: modelData tagInputField: root.inputField } } diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index 1abe82694..dabf247cf 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -14,7 +14,7 @@ import Qt5Compat.GraphicalEffects Rectangle { id: root - property var responseData: {} + property var responseData property var tagInputField onResponseDataChanged: { From 54514522d433d44f3cb23d6b008bc34d414e8c0c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:18:27 +0200 Subject: [PATCH 132/824] booru: add api provider indicator --- .../modules/common/widgets/StyledToolTip.qml | 6 ++-- .../quickshell/modules/sidebarLeft/Anime.qml | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index 8fa4f60c8..73a06c92f 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -7,10 +7,11 @@ import QtQuick.Layouts ToolTip { property string content property bool extraVisibleCondition: true + property bool alternativeVisibleCondition: false property bool internalVisibleCondition: false padding: 7 - visible: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered) && internalVisibleCondition) + visible: ((extraVisibleCondition && (parent.hovered === undefined || parent?.hovered) && internalVisibleCondition)) || alternativeVisibleCondition Connections { target: parent @@ -36,7 +37,8 @@ ToolTip { background: Rectangle { color: Appearance.colors.colTooltip radius: Appearance.rounding.small - width: tooltipTextObject.width + 2 * padding + implicitWidth: tooltipTextObject.width + 2 * padding + implicitHeight: tooltipTextObject.height + 2 * padding Behavior on opacity { OpacityAnimator { duration: Appearance.animation.elementDecel.duration diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index f73cff822..e7076214d 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -71,6 +71,41 @@ Item { id: columnLayout anchors.fill: parent + Rectangle { + Layout.alignment: Qt.AlignHCenter + implicitHeight: providerRowLayout.implicitHeight + 5 * 2 + implicitWidth: providerRowLayout.implicitWidth + 10 * 2 + radius: Appearance.rounding.small + color: Appearance.colors.colLayer1 + RowLayout { + id: providerRowLayout + anchors.centerIn: parent + + MaterialSymbol { + text: "api" + font.pixelSize: Appearance.font.pixelSize.large + } + StyledText { + id: providerName + font.pixelSize: Appearance.font.pixelSize.large + font.weight: Font.DemiBold + color: Appearance.m3colors.m3onSurface + text: Booru.providers[Booru.currentProvider].name + } + } + StyledToolTip { + id: toolTip + alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered + content: qsTr("The current API used. Endpoint: ") + Booru.providers[Booru.currentProvider].url + qsTr("\nSet with /mode PROVIDER") + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + } + } + Item { Layout.fillWidth: true Layout.fillHeight: true From f7a73edaead5659cb0a5afc9c3d39cdf5f950899 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:28:43 +0200 Subject: [PATCH 133/824] relocate api indicator --- .../modules/common/widgets/StyledSwitch.qml | 2 + .../quickshell/modules/sidebarLeft/Anime.qml | 80 ++++++++++--------- .../sidebarLeft/anime/BooruTagButton.qml | 2 +- 3 files changed, 47 insertions(+), 37 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/StyledSwitch.qml b/.config/quickshell/modules/common/widgets/StyledSwitch.qml index c0b0a4ffb..73734f3b1 100644 --- a/.config/quickshell/modules/common/widgets/StyledSwitch.qml +++ b/.config/quickshell/modules/common/widgets/StyledSwitch.qml @@ -10,6 +10,8 @@ Switch { implicitHeight: 32 * root.scale implicitWidth: 52 * root.scale + PointingHandInteraction {} + // Custom track styling background: Rectangle { width: parent.width diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index e7076214d..e5e41aeda 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -71,41 +71,6 @@ Item { id: columnLayout anchors.fill: parent - Rectangle { - Layout.alignment: Qt.AlignHCenter - implicitHeight: providerRowLayout.implicitHeight + 5 * 2 - implicitWidth: providerRowLayout.implicitWidth + 10 * 2 - radius: Appearance.rounding.small - color: Appearance.colors.colLayer1 - RowLayout { - id: providerRowLayout - anchors.centerIn: parent - - MaterialSymbol { - text: "api" - font.pixelSize: Appearance.font.pixelSize.large - } - StyledText { - id: providerName - font.pixelSize: Appearance.font.pixelSize.large - font.weight: Font.DemiBold - color: Appearance.m3colors.m3onSurface - text: Booru.providers[Booru.currentProvider].name - } - } - StyledToolTip { - id: toolTip - alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered - content: qsTr("The current API used. Endpoint: ") + Booru.providers[Booru.currentProvider].url + qsTr("\nSet with /mode PROVIDER") - } - - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - } - } - Item { Layout.fillWidth: true Layout.fillHeight: true @@ -248,6 +213,47 @@ Item { }, ] + Item { + implicitHeight: providerRowLayout.implicitHeight + 5 * 2 + implicitWidth: providerRowLayout.implicitWidth + 10 * 2 + // radius: Appearance.rounding.small + // color: Appearance.colors.colLayer1 + + RowLayout { + id: providerRowLayout + anchors.centerIn: parent + + MaterialSymbol { + text: "api" + font.pixelSize: Appearance.font.pixelSize.large + } + StyledText { + id: providerName + font.pixelSize: Appearance.font.pixelSize.small + font.weight: Font.DemiBold + color: Appearance.m3colors.m3onSurface + text: Booru.providers[Booru.currentProvider].name + } + } + StyledToolTip { + id: toolTip + alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered + content: qsTr("The current API used. Endpoint: ") + Booru.providers[Booru.currentProvider].url + qsTr("\nSet with /mode PROVIDER") + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + } + } + + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.colors.colOnLayer1 + text: "•" + } + Rectangle { implicitWidth: switchesRow.implicitWidth @@ -266,10 +272,12 @@ Item { } StyledSwitch { id: nsfwSwitch + enabled: Booru.currentProvider !== "zerochan" scale: 0.75 Layout.alignment: Qt.AlignVCenter - checked: ConfigOptions.sidebar.booru.allowNsfw + checked: (ConfigOptions.sidebar.booru.allowNsfw && Booru.currentProvider !== "zerochan") onCheckedChanged: { + if (!nsfwSwitch.enabled) return; ConfigOptions.sidebar.booru.allowNsfw = checked } } diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruTagButton.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruTagButton.qml index 0154cdd23..25bfdbb87 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruTagButton.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruTagButton.qml @@ -15,7 +15,7 @@ Button { leftPadding: 10 rightPadding: 10 - // PointingHandInteraction {} + PointingHandInteraction {} background: Rectangle { radius: Appearance.rounding.small From f72e480e9132025453e69694ccfe733d4b412689 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 29 Apr 2025 21:00:03 +0200 Subject: [PATCH 134/824] make the icon named more accurately they cant copyright a fucking 4 point star right???????? --- .../assets/icons/google-gemini-symbolic.svg | 57 +------------------ .../assets/icons/spark-symbolic.svg | 56 ++++++++++++++++++ .config/quickshell/modules/bar/Bar.qml | 2 +- .../modules/common/ConfigOptions.qml | 2 +- 4 files changed, 59 insertions(+), 58 deletions(-) mode change 100644 => 120000 .config/quickshell/assets/icons/google-gemini-symbolic.svg create mode 100644 .config/quickshell/assets/icons/spark-symbolic.svg diff --git a/.config/quickshell/assets/icons/google-gemini-symbolic.svg b/.config/quickshell/assets/icons/google-gemini-symbolic.svg deleted file mode 100644 index 9de741be6..000000000 --- a/.config/quickshell/assets/icons/google-gemini-symbolic.svg +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - ionicons-v5_logos - - - - - ionicons-v5_logos - - - - diff --git a/.config/quickshell/assets/icons/google-gemini-symbolic.svg b/.config/quickshell/assets/icons/google-gemini-symbolic.svg new file mode 120000 index 000000000..7aa8c18b6 --- /dev/null +++ b/.config/quickshell/assets/icons/google-gemini-symbolic.svg @@ -0,0 +1 @@ +spark-symbolic.svg \ No newline at end of file diff --git a/.config/quickshell/assets/icons/spark-symbolic.svg b/.config/quickshell/assets/icons/spark-symbolic.svg new file mode 100644 index 000000000..9de741be6 --- /dev/null +++ b/.config/quickshell/assets/icons/spark-symbolic.svg @@ -0,0 +1,56 @@ + + + + + + + ionicons-v5_logos + + + + + ionicons-v5_logos + + + + diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 123351e55..989d3cf33 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -72,7 +72,7 @@ Scope { width: 19.5 height: 19.5 source: ConfigOptions.bar.topLeftIcon == 'distro' ? - SystemInfo.distroIcon : "google-gemini-symbolic" + SystemInfo.distroIcon : "spark-symbolic" } ColorOverlay { diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index c3062dd42..1638984b7 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -19,7 +19,7 @@ Singleton { property QtObject bar: QtObject { property int batteryLowThreshold: 20 - property string topLeftIcon: "gemini" // Options: distro, gemini + property string topLeftIcon: "spark" // Options: distro, spark property QtObject resources: QtObject { property bool alwaysShowSwap: true property bool alwaysShowCpu: false From f2b523545bfcfd020f5b7a3688a2287786bea373 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 29 Apr 2025 22:26:45 +0200 Subject: [PATCH 135/824] booru: proper danbooru support, fix gelbooru --- .../quickshell/modules/common/Appearance.qml | 2 + .../modules/common/widgets/StyledToolTip.qml | 3 +- .../quickshell/modules/sidebarLeft/Anime.qml | 118 +++++++++++++----- .../modules/sidebarLeft/anime/BooruImage.qml | 29 ++++- .../sidebarLeft/anime/BooruResponse.qml | 12 +- .config/quickshell/services/Booru.qml | 19 ++- 6 files changed, 145 insertions(+), 38 deletions(-) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index cbb54071c..cb000bfab 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -113,12 +113,14 @@ Singleton { property color colOnLayer1Inactive: mix(colOnLayer1, colLayer1, 0.45); property color colLayer2: mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.55); property color colOnLayer2: m3colors.m3onSurface; + property color colOnLayer2Disabled: mix(colOnLayer2, m3colors.m3background, 0.4); property color colLayer3: mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96); property color colOnLayer3: m3colors.m3onSurface; property color colLayer1Hover: mix(colLayer1, colOnLayer1, 0.88); property color colLayer1Active: mix(colLayer1, colOnLayer1, 0.77); property color colLayer2Hover: mix(colLayer2, colOnLayer2, 0.90); property color colLayer2Active: mix(colLayer2, colOnLayer2, 0.80); + property color colLayer2Disabled: mix(colLayer2, m3colors.m3background, 0.8); property color colLayer3Hover: mix(colLayer3, colOnLayer3, 0.90); property color colLayer3Active: mix(colLayer3, colOnLayer3, 0.80); property color colPrimaryHover: mix(m3colors.m3primary, colLayer1Hover, 0.85) diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index 73a06c92f..ff01a424f 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -9,7 +9,7 @@ ToolTip { property bool extraVisibleCondition: true property bool alternativeVisibleCondition: false property bool internalVisibleCondition: false - padding: 7 + padding: 5 visible: ((extraVisibleCondition && (parent.hovered === undefined || parent?.hovered) && internalVisibleCondition)) || alternativeVisibleCondition @@ -50,6 +50,7 @@ ToolTip { StyledText { id: tooltipTextObject text: content + font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colOnTooltip wrapMode: Text.Wrap } diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index e5e41aeda..ec070f261 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -3,6 +3,7 @@ import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" import "./anime/" +import Qt.labs.platform import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -15,6 +16,14 @@ Item { id: root property var panelWindow property var inputField: tagInputField + property string previewDownloadPath: `${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/media/waifus`.replace("file://", "") + property string downloadPath: (StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + "/homework").replace("file://", "") + property string nsfwPath: (StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + "/homework/🌶️").replace("file://", "") + + Component.onCompleted: { + Hyprland.dispatch(`exec rm -rf ${previewDownloadPath}`) + Hyprland.dispatch(`exec mkdir -p ${previewDownloadPath}`) + } function handleInput(inputText) { if (inputText.startsWith("/")) { @@ -28,9 +37,15 @@ Item { const newProvider = args[0]; Booru.setProvider(newProvider); } - else if (command == "lewd" || command == "nsfw") { + else if (command == "nsfw") { ConfigOptions.sidebar.booru.allowNsfw = !ConfigOptions.sidebar.booru.allowNsfw } + else if (command == "safe") { + ConfigOptions.sidebar.booru.allowNsfw = false + } + else if (command == "lewd") { + ConfigOptions.sidebar.booru.allowNsfw = true + } } else { @@ -105,6 +120,9 @@ Item { delegate: BooruResponse { responseData: modelData tagInputField: root.inputField + previewDownloadPath: root.previewDownloadPath + downloadPath: root.downloadPath + nsfwPath: root.nsfwPath } } @@ -164,34 +182,78 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - TextArea { // The actual input field widget - id: tagInputField - wrapMode: TextArea.Wrap - Layout.fillWidth: true + + RowLayout { Layout.topMargin: 5 - padding: 10 - color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant - renderType: Text.NativeRendering - selectedTextColor: Appearance.m3colors.m3onPrimary - selectionColor: Appearance.m3colors.m3primary - placeholderText: qsTr("Enter tags") - placeholderTextColor: Appearance.m3colors.m3outline + spacing: 0 + TextArea { // The actual input field widget + id: tagInputField + wrapMode: TextArea.Wrap + Layout.fillWidth: true + padding: 10 + color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant + renderType: Text.NativeRendering + selectedTextColor: Appearance.m3colors.m3onPrimary + selectionColor: Appearance.m3colors.m3primary + placeholderText: qsTr("Enter tags") + placeholderTextColor: Appearance.m3colors.m3outline - background: Item {} + background: Item {} - Keys.onPressed: (event) => { - if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { - if (event.modifiers & Qt.ShiftModifier) { - // Insert newline - tagInputField.insert(tagInputField.cursorPosition, "\n") - event.accepted = true - } else { // Accept text + Keys.onPressed: (event) => { + if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { + if (event.modifiers & Qt.ShiftModifier) { + // Insert newline + tagInputField.insert(tagInputField.cursorPosition, "\n") + event.accepted = true + } else { // Accept text + const inputText = tagInputField.text + root.handleInput(inputText) + tagInputField.clear() + event.accepted = true + } + } + } + } + Button { // Send button + id: sendButton + Layout.alignment: Qt.AlignTop + Layout.rightMargin: 5 + implicitWidth: 40 + implicitHeight: 40 + enabled: tagInputField.text.length > 0 + + MouseArea { + anchors.fill: parent + cursorShape: sendButton.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: { const inputText = tagInputField.text root.handleInput(inputText) tagInputField.clear() - event.accepted = true } } + + background: Rectangle { + radius: Appearance.rounding.small + color: sendButton.enabled ? (sendButton.down ? Appearance.colors.colPrimaryActive : + sendButton.hovered ? Appearance.colors.colPrimaryHover : + Appearance.m3colors.m3primary) : Appearance.colors.colLayer2Disabled + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } + + contentItem: MaterialSymbol { + anchors.centerIn: parent + text: "send" + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.larger + color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled + } } } @@ -216,8 +278,6 @@ Item { Item { implicitHeight: providerRowLayout.implicitHeight + 5 * 2 implicitWidth: providerRowLayout.implicitWidth + 10 * 2 - // radius: Appearance.rounding.small - // color: Appearance.colors.colLayer1 RowLayout { id: providerRowLayout @@ -249,7 +309,7 @@ Item { } StyledText { - font.pixelSize: Appearance.font.pixelSize.small + font.pixelSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer1 text: "•" } @@ -267,13 +327,13 @@ Item { Layout.leftMargin: 10 Layout.alignment: Qt.AlignVCenter font.pixelSize: Appearance.font.pixelSize.smaller - color: Appearance.colors.coloOnLayer1 + color: Appearance.colors.colOnLayer1 text: qsTr("NSFW") } StyledSwitch { id: nsfwSwitch enabled: Booru.currentProvider !== "zerochan" - scale: 0.75 + scale: 0.6 Layout.alignment: Qt.AlignVCenter checked: (ConfigOptions.sidebar.booru.allowNsfw && Booru.currentProvider !== "zerochan") onCheckedChanged: { @@ -294,9 +354,9 @@ Item { buttonText: modelData.name background: Rectangle { radius: Appearance.rounding.small - color: (tagButton.down ? Appearance.colors.colLayer1Active : - tagButton.hovered ? Appearance.colors.colLayer1Hover : - Appearance.transparentize(Appearance.colors.colLayer1, 1)) + color: tagButton.down ? Appearance.colors.colLayer2Active : + tagButton.hovered ? Appearance.colors.colLayer2Hover : + Appearance.colors.colLayer2 Behavior on color { ColorAnimation { diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index 6bd450a42..3160b1b58 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -1,9 +1,11 @@ import "root:/" import "root:/modules/common" import "root:/modules/common/widgets" +import Qt.labs.platform import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Quickshell.Io import Quickshell.Hyprland import Qt5Compat.GraphicalEffects @@ -11,10 +13,31 @@ Button { id: root property var imageData property var rowHeight + property bool manualDownload: false + property string previewDownloadPath + property string downloadPath + property string nsfwPath + property string fileName: decodeURIComponent((imageData.file_url).substring((imageData.file_url).lastIndexOf('/') + 1)) - // onImageDataChanged: { - // console.log("Image data changed:", imageData) - // } + Process { + id: downloadProcess + running: false + command: ["bash", "-c", `curl '${imageData.preview_url}' -o '${previewDownloadPath}/${root.fileName}' && echo 'done'`] + stdout: SplitParser { + onRead: (data) => { + console.log("Download output:", data) + if(data.includes("done")) { + imageObject.source = `${previewDownloadPath}/${root.fileName}` + } + } + } + } + + Component.onCompleted: { + if (root.manualDownload) { + downloadProcess.running = true + } + } padding: 0 implicitWidth: imageObject.width diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index dabf247cf..681906b16 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -17,11 +17,15 @@ Rectangle { property var responseData property var tagInputField + property string previewDownloadPath + property string downloadPath + property string nsfwPath + onResponseDataChanged: { console.log("Response data changed:", responseData) } - property real availableWidth: parent?.width + property real availableWidth: parent.width ?? 0 property real rowTooShortThreshold: 100 property real imageSpacing: 5 property real responsePadding: 5 @@ -65,7 +69,7 @@ Rectangle { visible: root.responseData.page != "" && root.responseData.page > 0 implicitWidth: Math.max(pageNumber.implicitWidth + 10 * 2, 30) implicitHeight: pageNumber.implicitHeight + 5 * 2 - Layout.alignment: Qt.AlignTop + Layout.alignment: Qt.AlignVCenter StyledText { id: pageNumber @@ -205,6 +209,10 @@ Rectangle { delegate: BooruImage { imageData: modelData rowHeight: imageRow.rowHeight + manualDownload: root.responseData.provider == "danbooru" + previewDownloadPath: root.previewDownloadPath + downloadPath: root.downloadPath + nsfwPath: root.nsfwPath } } } diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index 1357d4f17..d22985a23 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -17,6 +17,7 @@ Singleton { } } + property string failMessage: qsTr("That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number") property var responses: [] property var getWorkingImageSource: (url) => { if (url.includes('pximg.net')) { @@ -203,7 +204,12 @@ Singleton { else { params.push("tags=" + encodeURIComponent(tagString)) params.push("limit=" + limit) - params.push("page=" + page) + if (currentProvider == "gelbooru") { + params.push("pid=" + page) + } + else { + params.push("page=" + page) + } } var url = baseUrl if (baseUrl.indexOf("?") === -1) { @@ -224,7 +230,7 @@ Singleton { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { try { // console.log("[Booru] Raw response length: " + xhr.responseText.length) - // console.log("[Booru] Raw response: " + xhr.responseText) + console.log("[Booru] Raw response: " + xhr.responseText) var response = JSON.parse(xhr.responseText) // Access nested properties based on listAccess @@ -243,11 +249,18 @@ Singleton { "tags": tags, "page": page, "images": response, - "message": "" + "message": response.length > 0 ? "" : root.failMessage }] } catch (e) { console.log("[Booru] Failed to parse response: " + e) + root.responses = [...root.responses, { + "provider": currentProvider, + "tags": tags, + "page": page, + "images": [], + "message": root.failMessage + }] } } else if (xhr.readyState === XMLHttpRequest.DONE) { From d08d0a8914d06587ba9430a007d616a2084a9d05 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 29 Apr 2025 23:38:29 +0200 Subject: [PATCH 136/824] "booru": waifu.im support --- .../modules/sidebarLeft/anime/BooruImage.qml | 7 ++- .../sidebarLeft/anime/BooruResponse.qml | 3 +- .config/quickshell/services/Booru.qml | 43 +++++++++++++++++-- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index 3160b1b58..f869d059d 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -22,10 +22,10 @@ Button { Process { id: downloadProcess running: false - command: ["bash", "-c", `curl '${imageData.preview_url}' -o '${previewDownloadPath}/${root.fileName}' && echo 'done'`] + command: ["bash", "-c", `curl '${root.imageData.preview_url ?? root.imageData.sample_url}' -o '${root.previewDownloadPath}/${root.fileName}' && echo 'done'`] stdout: SplitParser { onRead: (data) => { - console.log("Download output:", data) + // console.log("Download output:", data) if(data.includes("done")) { imageObject.source = `${previewDownloadPath}/${root.fileName}` } @@ -35,6 +35,9 @@ Button { Component.onCompleted: { if (root.manualDownload) { + // console.log("Manual download triggered") + // console.log("Image data:", JSON.stringify(root.imageData)) + // console.log("Download command:", downloadProcess.command.join(" ")) downloadProcess.running = true } } diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index 681906b16..780e3fe9a 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -207,9 +207,10 @@ Rectangle { Repeater { model: modelData.images delegate: BooruImage { + required property var modelData imageData: modelData rowHeight: imageRow.rowHeight - manualDownload: root.responseData.provider == "danbooru" + manualDownload: ["danbooru", "waifu.im"].includes(root.responseData.provider) previewDownloadPath: root.previewDownloadPath downloadPath: root.downloadPath nsfwPath: root.nsfwPath diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index d22985a23..5523fcae6 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -27,7 +27,7 @@ Singleton { } property var defaultUserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" - property var providerList: ["yandere", "konachan", "zerochan", "danbooru", "gelbooru"] + property var providerList: ["yandere", "konachan", "zerochan", "danbooru", "gelbooru", "waifu.im"] property var providers: { "system": { "name": "System" }, "yandere": { @@ -155,7 +155,32 @@ Singleton { } }) } - } + }, + "waifu.im": { + "name": "waifu.im", + "url": "https://waifu.im", + "api": "https://api.waifu.im/search", + "listAccess": ["images"], + "mapFunc": (response) => { + return response.map(item => { + return { + "id": item.image_id, + "width": item.width, + "height": item.height, + "aspect_ratio": item.width / item.height, + "tags": item.tags.map(tag => {return tag.name}).join(" "), + "rating": item.is_nsfw ? "e" : "s", + "is_nsfw": item.is_nsfw, + "md5": item.md5, + "preview_url": item.sample_url ?? item.url, // preview_url just says access denied (maybe i fucked up and sent too many requests idk) + "sample_url": item.url, + "file_url": item.url, + "file_ext": item.extension, + "source": getWorkingImageSource(item.source) ?? item.url, + } + }) + } + }, } property var currentProvider: ConfigOptions.sidebar.booru.defaultProvider @@ -189,7 +214,7 @@ Singleton { var provider = providers[currentProvider] var baseUrl = provider.api var tagString = tags.join(" ") - if (!nsfw && currentProvider !== "zerochan") { + if (!nsfw && !(["zerochan", "waifu.im"].includes(currentProvider))) { tagString += " rating:safe" } var params = [] @@ -201,6 +226,14 @@ Singleton { params.push("t=" + 1) params.push("p=" + page) } + else if (currentProvider === "waifu.im") { + var tagsArray = tagString.split(" "); + tagsArray.forEach(tag => { + params.push("included_tags=" + encodeURIComponent(tag)); + }); + params.push("limit=" + Math.min(limit, 30)) // Only admin can do > 30 + params.push("is_nsfw=" + (nsfw ? "null" : "false")) // null is random + } else { params.push("tags=" + encodeURIComponent(tagString)) params.push("limit=" + limit) @@ -230,12 +263,14 @@ Singleton { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { try { // console.log("[Booru] Raw response length: " + xhr.responseText.length) - console.log("[Booru] Raw response: " + xhr.responseText) + // console.log("[Booru] Raw response: " + xhr.responseText) var response = JSON.parse(xhr.responseText) // Access nested properties based on listAccess var accessList = providers[currentProvider].listAccess for (var i = 0; i < accessList.length; ++i) { + // console.log("[Booru] Accessing property: " + accessList[i]) + // console.log("[Booru] Current response: " + JSON.stringify(response)) if (response && response.hasOwnProperty(accessList[i])) { response = response[accessList[i]] } else { From fef0cc366a4b800d985d4ab4bbe6e1ca32529f88 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 30 Apr 2025 00:43:10 +0200 Subject: [PATCH 137/824] booru: fix 1image row width, add actions menu --- .../modules/common/widgets/MenuButton.qml | 51 ++++++++ .../quickshell/modules/sidebarLeft/Anime.qml | 5 +- .../modules/sidebarLeft/SidebarLeft.qml | 14 ++ .../modules/sidebarLeft/anime/BooruImage.qml | 120 +++++++++++++++--- .../sidebarLeft/anime/BooruResponse.qml | 6 +- 5 files changed, 170 insertions(+), 26 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/MenuButton.qml diff --git a/.config/quickshell/modules/common/widgets/MenuButton.qml b/.config/quickshell/modules/common/widgets/MenuButton.qml new file mode 100644 index 000000000..957c2229d --- /dev/null +++ b/.config/quickshell/modules/common/widgets/MenuButton.qml @@ -0,0 +1,51 @@ +import "root:/modules/common" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io + +Button { + id: button + + property string buttonText + implicitHeight: 36 + implicitWidth: buttonTextWidget.implicitWidth + 14 * 2 + + PointingHandInteraction {} + + background: Rectangle { + anchors.fill: parent + color: (button.down && button.enabled) ? Appearance.transparentize(Appearance.m3colors.m3onSurface, 0.84) : + ((button.hovered && button.enabled) ? Appearance.transparentize(Appearance.m3colors.m3onSurface, 0.92) : + Appearance.transparentize(Appearance.m3colors.m3onSurface, 1)) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } + + } + + contentItem: StyledText { + id: buttonTextWidget + anchors.fill: parent + anchors.leftMargin: 14 + anchors.rightMargin: 14 + text: buttonText + horizontalAlignment: Text.AlignLeft + font.pixelSize: Appearance.font.pixelSize.small + color: button.enabled ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3outline + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } + +} diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index ec070f261..ed2f26237 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -112,10 +112,7 @@ Item { spacing: 10 model: ScriptModel { - values: { - console.log(JSON.stringify(Booru.responses)) - return Booru.responses - } + values: Booru.responses } delegate: BooruResponse { responseData: modelData diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index 71f7713f1..9652bf01d 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -229,4 +229,18 @@ Scope { // Scope } } + GlobalShortcut { + name: "sidebarLeftClose" + description: "Closes left sidebar on press" + + onPressed: { + for (let i = 0; i < sidebarVariants.instances.length; i++) { + let panelWindow = sidebarVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + panelWindow.visible = false; + } + } + } + } + } diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index f869d059d..cbd2acbbb 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -19,6 +19,8 @@ Button { property string nsfwPath property string fileName: decodeURIComponent((imageData.file_url).substring((imageData.file_url).lastIndexOf('/') + 1)) + property bool showActions: false + Process { id: downloadProcess running: false @@ -46,11 +48,7 @@ Button { implicitWidth: imageObject.width implicitHeight: imageObject.height - PointingHandInteraction {} - - onClicked: { - Hyprland.dispatch(`exec xdg-open ${imageData.source}`) - } + // PointingHandInteraction {} background: Rectangle { implicitWidth: imageObject.width @@ -59,23 +57,107 @@ Button { color: Appearance.colors.colLayer2 } - contentItem: Image { - id: imageObject + contentItem: Item { anchors.fill: parent - sourceSize.width: root.rowHeight * modelData.aspect_ratio - sourceSize.height: root.rowHeight - fillMode: Image.PreserveAspectFit - source: modelData.preview_url - width: root.rowHeight * modelData.aspect_ratio - height: root.rowHeight - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: imageObject.width - height: imageObject.height - radius: Appearance.rounding.small + Image { + id: imageObject + anchors.fill: parent + sourceSize.width: root.rowHeight * modelData.aspect_ratio + sourceSize.height: root.rowHeight + fillMode: Image.PreserveAspectFit + source: modelData.preview_url + width: root.rowHeight * modelData.aspect_ratio + height: root.rowHeight + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: imageObject.width + height: imageObject.height + radius: Appearance.rounding.small + } } } + + Button { + id: menuButton + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: 8 + implicitHeight: 30 + implicitWidth: 30 + + PointingHandInteraction {} + + background: Rectangle { + color: menuButton.down ? Appearance.mix(Appearance.colors.colScrim, Appearance.m3colors.m3onSurface, 0.6) : + menuButton.hovered ? Appearance.mix(Appearance.colors.colScrim, Appearance.m3colors.m3onSurface, 0.8) : + Appearance.colors.colScrim + radius: Appearance.rounding.full + } + + contentItem: MaterialSymbol { + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.large + color: Appearance.m3colors.m3onSurface + text: "more_vert" + } + + onClicked: { + root.showActions = !root.showActions + } + } + + Rectangle { + id: contextMenu + visible: root.showActions + radius: Appearance.rounding.small + color: Appearance.m3colors.m3surfaceContainer + anchors.top: menuButton.bottom + anchors.right: parent.right + anchors.margins: 8 + implicitHeight: contextMenuColumnLayout.implicitHeight + radius * 2 + implicitWidth: contextMenuColumnLayout.implicitWidth + + ColumnLayout { + id: contextMenuColumnLayout + anchors.centerIn: parent + spacing: 0 + + MenuButton { + id: openFileLinkButton + Layout.fillWidth: true + buttonText: "Open file link" + onClicked: { + root.showActions = false + Hyprland.dispatch("global quickshell:sidebarLeftClose") + Hyprland.dispatch(`exec xdg-open '${root.imageData.file_url}'`) + } + } + MenuButton { + id: sourceButton + Layout.fillWidth: true + buttonText: "Go to source" + enabled: root.imageData.source && root.imageData.source.length > 0 + onClicked: { + root.showActions = false + Hyprland.dispatch("global quickshell:sidebarLeftClose") + Hyprland.dispatch(`exec xdg-open '${root.imageData.source}'`) + } + } + MenuButton { + id: downloadButton + Layout.fillWidth: true + buttonText: "Download" + onClicked: { + root.showActions = false + Hyprland.dispatch("global quickshell:sidebarLeftClose") + Hyprland.dispatch(`exec curl '${root.imageData.file_url}' -o '${root.imageData.is_nsfw ? root.nsfwPath : root.downloadPath}/${root.fileName}' && notify-send 'Download complete' '${root.downloadPath}/${root.fileName}'`) + } + } + } + } + } } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index 780e3fe9a..64cb757fc 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -26,7 +26,7 @@ Rectangle { } property real availableWidth: parent.width ?? 0 - property real rowTooShortThreshold: 100 + property real rowTooShortThreshold: 185 property real imageSpacing: 5 property real responsePadding: 5 @@ -190,9 +190,9 @@ Rectangle { } } // Otherwise, put only one image in the row - const rowHeight = availableImageWidth / responseList[i].aspect_ratio; + const rowHeight = (availableWidth - (responsePadding * 2)) / responseList[i].aspect_ratio; rows.push({ - height: availableWidth / responseList[i].aspect_ratio, + height: rowHeight, images: [responseList[i]], }); i += 1; From 1e5079cd610cd848b4ec64de500afe7095216eb6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 30 Apr 2025 11:18:06 +0200 Subject: [PATCH 138/824] booru: next button/command --- .../quickshell/modules/sidebarLeft/Anime.qml | 32 +++++++++++++-- .../modules/sidebarLeft/anime/BooruImage.qml | 10 ++--- .../sidebarLeft/anime/BooruResponse.qml | 39 +++++++++++++++++++ 3 files changed, 73 insertions(+), 8 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index ed2f26237..aa4b256f6 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -19,6 +19,7 @@ Item { property string previewDownloadPath: `${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/media/waifus`.replace("file://", "") property string downloadPath: (StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + "/homework").replace("file://", "") property string nsfwPath: (StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + "/homework/🌶️").replace("file://", "") + property real scrollOnNewResponse: 100 Component.onCompleted: { Hyprland.dispatch(`exec rm -rf ${previewDownloadPath}`) @@ -46,7 +47,18 @@ Item { else if (command == "lewd") { ConfigOptions.sidebar.booru.allowNsfw = true } - + else if (command == "next") { + if (Booru.responses.length > 0) { + const lastResponse = Booru.responses[Booru.responses.length - 1] + root.handleInput(lastResponse.tags.join(" ") + ` ${parseInt(lastResponse.page) + 1}`); + } + } + } + else if (inputText.trim() == "+") { + if (Booru.responses.length > 0) { + const lastResponse = Booru.responses[Booru.responses.length - 1] + root.handleInput(lastResponse.tags.join(" ") + ` ${parseInt(lastResponse.page) + 1}`); + } } else { // Create tag list @@ -92,7 +104,9 @@ Item { ListView { // Booru responses id: booruResponseListView anchors.fill: parent - clip: true + + property int lastResponseLength: 0 + layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { @@ -112,7 +126,14 @@ Item { spacing: 10 model: ScriptModel { - values: Booru.responses + values: { + if(Booru.responses.length > booruResponseListView.lastResponseLength) { + if (booruResponseListView.lastResponseLength > 0 && Booru.responses[booruResponseListView.lastResponseLength].provider != "system") + booruResponseListView.contentY = booruResponseListView.contentY + root.scrollOnNewResponse + booruResponseListView.lastResponseLength = Booru.responses.length + } + return Booru.responses + } } delegate: BooruResponse { responseData: modelData @@ -197,6 +218,11 @@ Item { background: Item {} + function accept() { + root.handleInput(text) + text = "" + } + Keys.onPressed: (event) => { if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { if (event.modifiers & Qt.ShiftModifier) { diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index cbd2acbbb..de41d96c2 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -91,9 +91,9 @@ Button { PointingHandInteraction {} background: Rectangle { - color: menuButton.down ? Appearance.mix(Appearance.colors.colScrim, Appearance.m3colors.m3onSurface, 0.6) : - menuButton.hovered ? Appearance.mix(Appearance.colors.colScrim, Appearance.m3colors.m3onSurface, 0.8) : - Appearance.colors.colScrim + color: menuButton.down ? Appearance.transparentize(Appearance.mix(Appearance.m3colors.m3surface, Appearance.m3colors.m3onSurface, 0.6), 0.1) : + menuButton.hovered ? Appearance.transparentize(Appearance.mix(Appearance.m3colors.m3surface, Appearance.m3colors.m3onSurface, 0.8), 0.2) : + Appearance.transparentize(Appearance.m3colors.m3surface, 0.3) radius: Appearance.rounding.full } @@ -131,7 +131,7 @@ Button { buttonText: "Open file link" onClicked: { root.showActions = false - Hyprland.dispatch("global quickshell:sidebarLeftClose") + // Hyprland.dispatch("global quickshell:sidebarLeftClose") Hyprland.dispatch(`exec xdg-open '${root.imageData.file_url}'`) } } @@ -152,7 +152,7 @@ Button { buttonText: "Download" onClicked: { root.showActions = false - Hyprland.dispatch("global quickshell:sidebarLeftClose") + // Hyprland.dispatch("global quickshell:sidebarLeftClose") Hyprland.dispatch(`exec curl '${root.imageData.file_url}' -o '${root.imageData.is_nsfw ? root.nsfwPath : root.downloadPath}/${root.fileName}' && notify-send 'Download complete' '${root.downloadPath}/${root.fileName}'`) } } diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index 64cb757fc..e68bd876d 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -218,5 +218,44 @@ Rectangle { } } } + + Button { // Next page button + id: button + property string buttonText + visible: root.responseData.page != "" && root.responseData.page > 0 + + Layout.alignment: Qt.AlignRight + implicitHeight: 30 + leftPadding: 10 + rightPadding: 10 + + PointingHandInteraction {} + onClicked: { + tagInputField.text = `${responseData.tags.join(" ")} ${parseInt(root.responseData.page) + 1}` + tagInputField.accept() + } + + background: Rectangle { + radius: Appearance.rounding.small + color: (button.down ? Appearance.colors.colSurfaceContainerHighestActive : + button.hovered ? Appearance.colors.colSurfaceContainerHighestHover : + Appearance.m3colors.m3surfaceContainerHighest) + } + + contentItem: RowLayout { + spacing: 0 + StyledText { + Layout.alignment: Text.AlignVCenter + text: "Next page" + color: Appearance.m3colors.m3onSurface + } + MaterialSymbol { + Layout.alignment: Text.AlignVCenter + font.pixelSize: Appearance.font.pixelSize.larger + color: Appearance.m3colors.m3onSurface + text: "chevron_right" + } + } + } } } \ No newline at end of file From 34268147e636d78855c14ca3ad4023d99904d8a3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 30 Apr 2025 22:19:50 +0200 Subject: [PATCH 139/824] set namespace for bar --- .config/quickshell/modules/bar/Bar.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 989d3cf33..b4b35e87c 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -7,6 +7,7 @@ import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell +import Quickshell.Wayland import Quickshell.Hyprland import Quickshell.Io import Quickshell.Services.Mpris @@ -27,6 +28,7 @@ Scope { property var modelData screen: modelData + WlrLayershell.namespace: "quickshell:bar" height: barHeight + Appearance.rounding.screenRounding exclusiveZone: barHeight mask: Region { From 17935cdc13aed558c5dab0f65c670834db258c65 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 30 Apr 2025 22:19:57 +0200 Subject: [PATCH 140/824] adjust ws icon size --- .config/quickshell/modules/bar/Workspaces.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 01edc014f..7b3cf37cc 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -22,7 +22,7 @@ Item { property list workspaceOccupied: [] property int widgetPadding: 4 property int workspaceButtonWidth: 26 - property real workspaceIconSize: workspaceButtonWidth * 0.75 + property real workspaceIconSize: workspaceButtonWidth * 0.7 property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55 property real workspaceIconOpacityShrinked: 1 property real workspaceIconMarginShrinked: -4 From 7d71d9d50749e4e9ec0f13b3478f0368e3e3aeda Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 30 Apr 2025 22:20:19 +0200 Subject: [PATCH 141/824] add substitution for zen browser --- .config/quickshell/modules/common/functions/icons.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/modules/common/functions/icons.js b/.config/quickshell/modules/common/functions/icons.js index 3eeeab2b9..56b009887 100644 --- a/.config/quickshell/modules/common/functions/icons.js +++ b/.config/quickshell/modules/common/functions/icons.js @@ -8,6 +8,7 @@ const substitutions = { "wps": "wps-office2019-kprometheus", "wpsoffice": "wps-office2019-kprometheus", "footclient": "foot", + "zen": "zen-browser", "": "image-missing" } const regexSubstitutions = [ From f0c1f0adff6f836ae7de7ef13441dd3ab9918e25 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 30 Apr 2025 22:21:10 +0200 Subject: [PATCH 142/824] brightness: use exponential adjustment --- .config/quickshell/services/Brightness.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/services/Brightness.qml b/.config/quickshell/services/Brightness.qml index 5911c5ebe..8d8f88489 100644 --- a/.config/quickshell/services/Brightness.qml +++ b/.config/quickshell/services/Brightness.qml @@ -48,7 +48,7 @@ Singleton { Process { id: decreaseBrightness - command: ["brightnessctl", "set", "5%-"] + command: ["brightnessctl", "set", "5%-", "-e"] running: false onExited: { running = false; @@ -59,7 +59,7 @@ Singleton { Process { id: increaseBrightness - command: ["brightnessctl", "set", "5%+"] + command: ["brightnessctl", "set", "5%+", "-e"] running: false onExited: { running = false; @@ -70,7 +70,7 @@ Singleton { Process { id: preventPitchBlack - command: ["brightnessctl", "set", "1%+"] + command: ["brightnessctl", "set", "1%+", "-e"] running: false onExited: { running = false; From 1aa721dac853a22cd451bd7d8f69e4d92c32a1d0 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 30 Apr 2025 23:12:19 +0200 Subject: [PATCH 143/824] overview make window focus work more --- .config/quickshell/modules/overview/OverviewWidget.qml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index d15d548ee..1af09f983 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -1,3 +1,4 @@ +import "root:/" import "root:/services/" import "root:/modules/common" import "root:/modules/common/widgets" @@ -86,7 +87,8 @@ Item { acceptedButtons: Qt.LeftButton onClicked: { if (root.draggingTargetWorkspace === -1) { - Hyprland.dispatch(`global quickshell:overviewClose`) + // Hyprland.dispatch(`exec qs ipc call overview close`) + GlobalStates.overviewOpen = false Hyprland.dispatch(`workspace ${workspaceValue}`) } } @@ -186,8 +188,8 @@ Item { if (!windowData) return; if (event.button === Qt.LeftButton) { - Hyprland.dispatch(`global quickshell:overviewClose`) - Hyprland.dispatch(`workspace ${windowData.workspace.id}`) + GlobalStates.overviewOpen = false + Hyprland.dispatch(`focuswindow address:${windowData.address}`) event.accepted = true } else if (event.button === Qt.MiddleButton) { Hyprland.dispatch(`closewindow address:${windowData.address}`) From bcfb4e169ee1a27f529cbdd6fc4112a731478500 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 1 May 2025 07:49:40 +0200 Subject: [PATCH 144/824] add missing clipping for scrollable content --- .../quickshell/modules/common/widgets/NotificationWidget.qml | 3 ++- .config/quickshell/modules/overview/SearchWidget.qml | 1 + .config/quickshell/modules/sidebarLeft/Anime.qml | 1 + .config/quickshell/modules/sidebarLeft/SidebarLeft.qml | 1 + .config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml | 1 + .config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml | 1 + .../modules/sidebarRight/notifications/NotificationList.qml | 1 + .config/quickshell/modules/sidebarRight/todo/TaskList.qml | 1 + .../modules/sidebarRight/volumeMixer/VolumeMixer.qml | 1 + 9 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index c6d12bc83..b8f58a463 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -305,7 +305,7 @@ Item { height: size sourceSize.width: size sourceSize.height: size - + layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { @@ -472,6 +472,7 @@ Item { height: expanded ? actionRowLayout.implicitHeight : 0 contentWidth: actionRowLayout.implicitWidth + clip: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 461f0dcf4..186b0ef1b 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -181,6 +181,7 @@ Item { // Wrapper anchors.centerIn: parent spacing: 0 + clip: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index aa4b256f6..4b467f303 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -107,6 +107,7 @@ Item { property int lastResponseLength: 0 + clip: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index 9652bf01d..3c02db96c 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -130,6 +130,7 @@ Scope { // Scope sidebarRoot.currentTab = currentIndex } + clip: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index e68bd876d..c0649e1db 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -93,6 +93,7 @@ Rectangle { // height: tagRowLayout.implicitHeight contentWidth: tagRowLayout.implicitWidth + clip: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { diff --git a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml index 3a4ed520f..d69403a38 100644 --- a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml @@ -63,6 +63,7 @@ Rectangle { root.currentTab = currentIndex } + clip: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml index 87c62b554..a57c6d7ee 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -72,6 +72,7 @@ Item { anchors.bottom: statusRow.top contentHeight: columnLayout.height + clip: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml index a77915b54..7ad34a461 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -21,6 +21,7 @@ Item { anchors.fill: parent contentHeight: columnLayout.height + clip: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml index cc5a6a4a9..3884e8cfb 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml @@ -40,6 +40,7 @@ Item { anchors.fill: parent contentHeight: volumeMixerColumnLayout.height + clip: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { From 059e5c6761bf84b4caa021f376d6b2f8ad6cf59b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 1 May 2025 07:50:03 +0200 Subject: [PATCH 145/824] booru: filter empty tags --- .config/quickshell/modules/sidebarLeft/Anime.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 4b467f303..3d6100948 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -62,7 +62,7 @@ Item { } else { // Create tag list - const tagList = inputText.split(/\s+/); + const tagList = inputText.split(/\s+/).filter(tag => tag.length > 0); let pageIndex = 1; for (let i = 0; i < tagList.length; ++i) { // Detect page number if (/^\d+$/.test(tagList[i])) { From 730488f3c57e3176b8bf28e944832b369e43f9c7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 1 May 2025 16:34:24 +0200 Subject: [PATCH 146/824] booru: minor spacing adjustment --- .config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index c0649e1db..5156b796e 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -228,7 +228,7 @@ Rectangle { Layout.alignment: Qt.AlignRight implicitHeight: 30 leftPadding: 10 - rightPadding: 10 + rightPadding: 5 PointingHandInteraction {} onClicked: { From 1beb723cf3c6d14b8a3f9e4481a1ea240738e37f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 1 May 2025 19:57:10 +0200 Subject: [PATCH 147/824] booru: tag suggestions --- .../quickshell/modules/sidebarLeft/Anime.qml | 483 +++++++++++------- .../modules/sidebarLeft/anime/BooruImage.qml | 2 +- .config/quickshell/services/Booru.qml | 117 ++++- 3 files changed, 396 insertions(+), 206 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 3d6100948..e11372600 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -19,7 +19,19 @@ Item { property string previewDownloadPath: `${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/media/waifus`.replace("file://", "") property string downloadPath: (StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + "/homework").replace("file://", "") property string nsfwPath: (StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + "/homework/🌶️").replace("file://", "") + property string commandPrefix: "/" property real scrollOnNewResponse: 100 + property int tagSuggestionDelay: 210 + property var suggestionQuery: "" + property var suggestionList: [] + + Connections { + target: Booru + function onTagSuggestion(query, suggestions) { + root.suggestionQuery = query; + root.suggestionList = suggestions; + } + } Component.onCompleted: { Hyprland.dispatch(`exec rm -rf ${previewDownloadPath}`) @@ -27,7 +39,7 @@ Item { } function handleInput(inputText) { - if (inputText.startsWith("/")) { + if (inputText.startsWith(root.commandPrefix)) { // Handle special commands const command = inputText.split(" ")[0].substring(1); const args = inputText.split(" ").slice(1); @@ -168,23 +180,98 @@ Item { text: "bookmark_heart" } StyledText { + id: widgetNameText Layout.alignment: Qt.AlignHCenter font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3outline horizontalAlignment: Text.AlignHCenter - text: "Anime boorus" + text: qsTr("Anime boorus") } } } } - Rectangle { // Tag input field + Flow { // Tag suggestions + id: tagSuggestions + visible: root.suggestionList.length > 0 && + tagInputField.text.length > 0 + property int selectedIndex: 0 + Layout.fillWidth: true + spacing: 5 + + Repeater { + id: tagSuggestionRepeater + model: { + tagSuggestions.selectedIndex = 0 + return root.suggestionList.slice(0, 10) + } + delegate: BooruTagButton { + id: tagButton + // buttonText: `${modelData.name}_{${modelData.count}}` + background: Rectangle { + radius: Appearance.rounding.small + color: tagSuggestions.selectedIndex === index ? Appearance.colors.colLayer2Hover : + tagButton.down ? Appearance.colors.colLayer2Active : + tagButton.hovered ? Appearance.colors.colLayer2Hover : + Appearance.colors.colLayer2 + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } + contentItem: RowLayout { + spacing: 5 + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.m3colors.m3onSurface + text: modelData.name + } + StyledText { + visible: modelData.count !== undefined + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.m3colors.m3outline + text: modelData.count ?? "" + } + } + onClicked: { + tagSuggestions.acceptTag(modelData.name) + } + } + } + + function acceptTag(tag) { + const words = tagInputField.text.trim().split(/\s+/); + if (words.length > 0) { + words[words.length - 1] = tag; + } else { + words.push(tag); + } + const updatedText = words.join(" ") + " "; + tagInputField.text = updatedText; + tagInputField.cursorPosition = tagInputField.text.length; + tagInputField.forceActiveFocus(); + } + + function acceptSelectedTag() { + if (tagSuggestions.selectedIndex >= 0 && tagSuggestions.selectedIndex < tagSuggestionRepeater.count) { + const tag = root.suggestionList[tagSuggestions.selectedIndex].name; + tagSuggestions.acceptTag(tag); + } + } + } + + Rectangle { // Tag input area id: tagInputContainer + property real columnSpacing: 5 Layout.fillWidth: true radius: Appearance.rounding.small color: Appearance.colors.colLayer1 - implicitWidth: tagInputColumnLayout.implicitWidth - implicitHeight: Math.max(tagInputColumnLayout.implicitHeight, 45) + implicitWidth: tagInputField.implicitWidth + implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin + + commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45) clip: true border.color: Appearance.m3colors.m3outlineVariant border.width: 1 @@ -196,72 +283,225 @@ Item { } } - ColumnLayout { - id: tagInputColumnLayout + RowLayout { // Input field and send button + id: inputFieldRowLayout + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - RowLayout { - Layout.topMargin: 5 - spacing: 0 - TextArea { // The actual input field widget - id: tagInputField - wrapMode: TextArea.Wrap - Layout.fillWidth: true - padding: 10 - color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant - renderType: Text.NativeRendering - selectedTextColor: Appearance.m3colors.m3onPrimary - selectionColor: Appearance.m3colors.m3primary - placeholderText: qsTr("Enter tags") - placeholderTextColor: Appearance.m3colors.m3outline + anchors.topMargin: 5 + spacing: 0 - background: Item {} + TextArea { // The actual TextArea + id: tagInputField + wrapMode: TextArea.Wrap + Layout.fillWidth: true + padding: 10 + color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant + renderType: Text.NativeRendering + selectedTextColor: Appearance.m3colors.m3onPrimary + selectionColor: Appearance.m3colors.m3primary + placeholderText: qsTr("Enter tags") + placeholderTextColor: Appearance.m3colors.m3outline - function accept() { - root.handleInput(text) - text = "" - } + background: Item {} - Keys.onPressed: (event) => { - if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { - if (event.modifiers & Qt.ShiftModifier) { - // Insert newline - tagInputField.insert(tagInputField.cursorPosition, "\n") - event.accepted = true - } else { // Accept text - const inputText = tagInputField.text - root.handleInput(inputText) - tagInputField.clear() - event.accepted = true - } + property Timer searchTimer: Timer { + interval: root.tagSuggestionDelay + repeat: false + onTriggered: { + const inputText = tagInputField.text + if (inputText.length === 0 || inputText.startsWith(root.commandPrefix)) return; + const words = inputText.trim().split(/\s+/); + if (words.length > 0) { + Booru.triggerTagSearch(words[words.length - 1]); } } } - Button { // Send button - id: sendButton - Layout.alignment: Qt.AlignTop - Layout.rightMargin: 5 - implicitWidth: 40 - implicitHeight: 40 - enabled: tagInputField.text.length > 0 - MouseArea { - anchors.fill: parent - cursorShape: sendButton.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor - onClicked: { + onTextChanged: { + if(tagInputField.text.length === 0) { + root.suggestionQuery = "" + root.suggestionList = [] + return + } + searchTimer.restart(); + } + + function accept() { + root.handleInput(text) + text = "" + } + + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Tab) { + tagSuggestions.acceptSelectedTag(); + event.accepted = true; + } else if (event.key === Qt.Key_Up) { + tagSuggestions.selectedIndex = Math.max(0, tagSuggestions.selectedIndex - 1); + event.accepted = true; + } else if (event.key === Qt.Key_Down) { + tagSuggestions.selectedIndex = Math.min(root.suggestionList.length - 1, tagSuggestions.selectedIndex + 1); + event.accepted = true; + } else if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { + if (event.modifiers & Qt.ShiftModifier) { + // Insert newline + tagInputField.insert(tagInputField.cursorPosition, "\n") + event.accepted = true + } else { // Accept text const inputText = tagInputField.text root.handleInput(inputText) tagInputField.clear() + event.accepted = true } } + } + } + Button { // Send button + id: sendButton + Layout.alignment: Qt.AlignTop + Layout.rightMargin: 5 + implicitWidth: 40 + implicitHeight: 40 + enabled: tagInputField.text.length > 0 + + MouseArea { + anchors.fill: parent + cursorShape: sendButton.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: { + const inputText = tagInputField.text + root.handleInput(inputText) + tagInputField.clear() + } + } + + background: Rectangle { + radius: Appearance.rounding.small + color: sendButton.enabled ? (sendButton.down ? Appearance.colors.colPrimaryActive : + sendButton.hovered ? Appearance.colors.colPrimaryHover : + Appearance.m3colors.m3primary) : Appearance.colors.colLayer2Disabled + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } + + contentItem: MaterialSymbol { + anchors.centerIn: parent + text: "send" + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.larger + color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled + } + } + } + + RowLayout { // Controls + id: commandButtonsRow + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: 5 + anchors.leftMargin: 5 + anchors.rightMargin: 5 + spacing: 5 + + property var commands: [ + { + name: "/mode", + sendDirectly: false, + }, + { + name: "/clear", + sendDirectly: true, + }, + ] + + Item { + implicitHeight: providerRowLayout.implicitHeight + 5 * 2 + implicitWidth: providerRowLayout.implicitWidth + 10 * 2 + + RowLayout { + id: providerRowLayout + anchors.centerIn: parent + + MaterialSymbol { + text: "api" + font.pixelSize: Appearance.font.pixelSize.large + } + StyledText { + id: providerName + font.pixelSize: Appearance.font.pixelSize.small + font.weight: Font.DemiBold + color: Appearance.m3colors.m3onSurface + text: Booru.providers[Booru.currentProvider].name + } + } + StyledToolTip { + id: toolTip + alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered + content: qsTr("The current API used. Endpoint: ") + Booru.providers[Booru.currentProvider].url + qsTr("\nSet with /mode PROVIDER") + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + } + } + + StyledText { + font.pixelSize: Appearance.font.pixelSize.large + color: Appearance.colors.colOnLayer1 + text: "•" + } + + Rectangle { + implicitWidth: switchesRow.implicitWidth + + RowLayout { + id: switchesRow + spacing: 5 + anchors.centerIn: parent + + StyledText { + Layout.fillHeight: true + Layout.leftMargin: 10 + Layout.alignment: Qt.AlignVCenter + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.colors.colOnLayer1 + text: qsTr("NSFW") + } + StyledSwitch { + id: nsfwSwitch + enabled: Booru.currentProvider !== "zerochan" + scale: 0.6 + Layout.alignment: Qt.AlignVCenter + checked: (ConfigOptions.sidebar.booru.allowNsfw && Booru.currentProvider !== "zerochan") + onCheckedChanged: { + if (!nsfwSwitch.enabled) return; + ConfigOptions.sidebar.booru.allowNsfw = checked + } + } + } + } + + Item { Layout.fillWidth: true } + + Repeater { // Command buttons + id: commandRepeater + model: commandButtonsRow.commands + delegate: BooruTagButton { + id: tagButton + buttonText: modelData.name background: Rectangle { radius: Appearance.rounding.small - color: sendButton.enabled ? (sendButton.down ? Appearance.colors.colPrimaryActive : - sendButton.hovered ? Appearance.colors.colPrimaryHover : - Appearance.m3colors.m3primary) : Appearance.colors.colLayer2Disabled + color: tagButton.down ? Appearance.colors.colLayer2Active : + tagButton.hovered ? Appearance.colors.colLayer2Hover : + Appearance.colors.colLayer2 Behavior on color { ColorAnimation { @@ -270,138 +510,19 @@ Item { } } } - - contentItem: MaterialSymbol { - anchors.centerIn: parent - text: "send" - horizontalAlignment: Text.AlignHCenter - font.pixelSize: Appearance.font.pixelSize.larger - color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled - } - } - } - - RowLayout { // Controls - id: commandButtonsRow - spacing: 5 - Layout.bottomMargin: 5 - Layout.leftMargin: 5 - Layout.rightMargin: 5 - - property var commands: [ - { - name: "/mode", - sendDirectly: false, - }, - { - name: "/clear", - sendDirectly: true, - }, - ] - - Item { - implicitHeight: providerRowLayout.implicitHeight + 5 * 2 - implicitWidth: providerRowLayout.implicitWidth + 10 * 2 - - RowLayout { - id: providerRowLayout - anchors.centerIn: parent - - MaterialSymbol { - text: "api" - font.pixelSize: Appearance.font.pixelSize.large - } - StyledText { - id: providerName - font.pixelSize: Appearance.font.pixelSize.small - font.weight: Font.DemiBold - color: Appearance.m3colors.m3onSurface - text: Booru.providers[Booru.currentProvider].name - } - } - StyledToolTip { - id: toolTip - alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered - content: qsTr("The current API used. Endpoint: ") + Booru.providers[Booru.currentProvider].url + qsTr("\nSet with /mode PROVIDER") - } - - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - } - } - - StyledText { - font.pixelSize: Appearance.font.pixelSize.large - color: Appearance.colors.colOnLayer1 - text: "•" - } - - Rectangle { - implicitWidth: switchesRow.implicitWidth - - RowLayout { - id: switchesRow - spacing: 5 - anchors.centerIn: parent - - StyledText { - Layout.fillHeight: true - Layout.leftMargin: 10 - Layout.alignment: Qt.AlignVCenter - font.pixelSize: Appearance.font.pixelSize.smaller - color: Appearance.colors.colOnLayer1 - text: qsTr("NSFW") - } - StyledSwitch { - id: nsfwSwitch - enabled: Booru.currentProvider !== "zerochan" - scale: 0.6 - Layout.alignment: Qt.AlignVCenter - checked: (ConfigOptions.sidebar.booru.allowNsfw && Booru.currentProvider !== "zerochan") - onCheckedChanged: { - if (!nsfwSwitch.enabled) return; - ConfigOptions.sidebar.booru.allowNsfw = checked - } - } - } - } - - Item { Layout.fillWidth: true } - - Repeater { // Command buttons - id: commandRepeater - model: commandButtonsRow.commands - delegate: BooruTagButton { - id: tagButton - buttonText: modelData.name - background: Rectangle { - radius: Appearance.rounding.small - color: tagButton.down ? Appearance.colors.colLayer2Active : - tagButton.hovered ? Appearance.colors.colLayer2Hover : - Appearance.colors.colLayer2 - - Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type - } - } - } - onClicked: { - if(modelData.sendDirectly) { - root.handleInput(modelData.name) - } else { - tagInputField.text = modelData.name + " " - tagInputField.cursorPosition = tagInputField.text.length - tagInputField.forceActiveFocus() - } + onClicked: { + if(modelData.sendDirectly) { + root.handleInput(modelData.name) + } else { + tagInputField.text = modelData.name + " " + tagInputField.cursorPosition = tagInputField.text.length + tagInputField.forceActiveFocus() } } } } } + } } } diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index de41d96c2..d91bb95a9 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -153,7 +153,7 @@ Button { onClicked: { root.showActions = false // Hyprland.dispatch("global quickshell:sidebarLeftClose") - Hyprland.dispatch(`exec curl '${root.imageData.file_url}' -o '${root.imageData.is_nsfw ? root.nsfwPath : root.downloadPath}/${root.fileName}' && notify-send 'Download complete' '${root.downloadPath}/${root.fileName}'`) + Hyprland.dispatch(`exec curl '${root.imageData.file_url}' -o '${root.imageData.is_nsfw ? root.nsfwPath : root.downloadPath}/${root.fileName}' && notify-send '${qsTr("Download complete")}' '${root.downloadPath}/${root.fileName}'`) } } } diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index 5523fcae6..2d8518abd 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -9,6 +9,7 @@ import QtQuick; Singleton { id: root + signal tagSuggestion(string query, var suggestions) Connections { target: ConfigOptions.sidebar.booru @@ -34,7 +35,6 @@ Singleton { "name": "yande.re", "url": "https://yande.re", "api": "https://yande.re/post.json", - "listAccess": [], "mapFunc": (response) => { return response.map(item => { return { @@ -53,13 +53,21 @@ Singleton { "source": getWorkingImageSource(item.source) ?? item.file_url, } }) + }, + "tagSearchTemplate": "https://yande.re/tag.json?order=count&name={{query}}*", + "tagMapFunc": (response) => { + return response.map(item => { + return { + "name": item.name, + "count": item.count + } + }) } }, "konachan": { "name": "Konachan", "url": "https://konachan.com", "api": "https://konachan.com/post.json", - "listAccess": [], "mapFunc": (response) => { return response.map(item => { return { @@ -78,14 +86,23 @@ Singleton { "source": getWorkingImageSource(item.source) ?? item.file_url, } }) + }, + "tagSearchTemplate": "https://konachan.com/tag.json?order=count&name={{query}}*", + "tagMapFunc": (response) => { + return response.map(item => { + return { + "name": item.name, + "count": item.count + } + }) } }, "zerochan": { "name": "Zerochan", "url": "https://www.zerochan.net", "api": "https://www.zerochan.net/?json", - "listAccess": ["items"], "mapFunc": (response) => { + response = response.items return response.map(item => { return { "id": item.id, @@ -110,7 +127,6 @@ Singleton { "name": "Danbooru", "url": "https://danbooru.donmai.us", "api": "https://danbooru.donmai.us/posts.json", - "listAccess": [], "mapFunc": (response) => { return response.map(item => { return { @@ -129,14 +145,24 @@ Singleton { "source": getWorkingImageSource(item.source) ?? item.file_url, } }) + }, + "tagSearchTemplate": "https://danbooru.donmai.us/tags.json?search[name_matches]={{query}}*", + "tagMapFunc": (response) => { + return response.map(item => { + return { + "name": item.name, + "count": item.post_count + } + }) } + }, "gelbooru": { "name": "Gelbooru", "url": "https://gelbooru.com", "api": "https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1", - "listAccess": ["post"], "mapFunc": (response) => { + response = response.post return response.map(item => { return { "id": item.id, @@ -154,14 +180,23 @@ Singleton { "source": getWorkingImageSource(item.source) ?? item.file_url, } }) + }, + "tagSearchTemplate": "https://gelbooru.com/index.php?page=dapi&s=tag&q=index&json=1&orderby=count&name_pattern={{query}}%", + "tagMapFunc": (response) => { + return response.tag.map(item => { + return { + "name": item.name, + "count": item.count + } + }) } }, "waifu.im": { "name": "waifu.im", "url": "https://waifu.im", "api": "https://api.waifu.im/search", - "listAccess": ["images"], "mapFunc": (response) => { + response = response.images return response.map(item => { return { "id": item.image_id, @@ -179,6 +214,11 @@ Singleton { "source": getWorkingImageSource(item.source) ?? item.url, } }) + }, + "tagSearchTemplate": "https://api.waifu.im/tags", + "tagMapFunc": (response) => { + return [...response.versatile.map(item => {return {"name": item}}), + ...response.nsfw.map(item => {return {"name": item}})] } }, } @@ -191,8 +231,7 @@ Singleton { root.addSystemMessage(qsTr("Provider set to ") + providers[provider].name + (provider == "zerochan" ? qsTr(". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!") : "")) } else { - console.log("[Booru] Invalid provider: " + provider) - root.addSystemMessage(qsTr("Invalid provider. Supported providers: \n- ") + providerList.join("\n- ")) + root.addSystemMessage(qsTr("Invalid API provider. Supported: \n- ") + providerList.join("\n- ")) } } @@ -255,30 +294,17 @@ Singleton { function makeRequest(tags, nsfw=false, limit=20, page=1) { var url = constructRequestUrl(tags, nsfw, limit, page) - console.log("[Booru] Making request to " + url) + // console.log("[Booru] Making request to " + url) var xhr = new XMLHttpRequest() xhr.open("GET", url) xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { try { - // console.log("[Booru] Raw response length: " + xhr.responseText.length) // console.log("[Booru] Raw response: " + xhr.responseText) var response = JSON.parse(xhr.responseText) - - // Access nested properties based on listAccess - var accessList = providers[currentProvider].listAccess - for (var i = 0; i < accessList.length; ++i) { - // console.log("[Booru] Accessing property: " + accessList[i]) - // console.log("[Booru] Current response: " + JSON.stringify(response)) - if (response && response.hasOwnProperty(accessList[i])) { - response = response[accessList[i]] - } else { - break - } - } response = providers[currentProvider].mapFunc(response) - // console.log("[Booru] Scoped & mapped response: " + JSON.stringify(response)) + // console.log("[Booru] Mapped response: " + JSON.stringify(response)) root.responses = [...root.responses, { "provider": currentProvider, "tags": tags, @@ -310,7 +336,6 @@ Singleton { } else if (currentProvider == "zerochan") { const userAgent = ConfigOptions.sidebar.booru.zerochan.username ? `Desktop sidebar booru viewer - ${ConfigOptions.sidebar.booru.zerochan.username}` : defaultUserAgent - console.log("Setting User-Agent for zerochan: " + userAgent) xhr.setRequestHeader("User-Agent", userAgent) } xhr.send() @@ -318,5 +343,49 @@ Singleton { console.log("Could not set User-Agent:", error) } } + + property var currentTagRequest: null + function triggerTagSearch(query) { + if (currentTagRequest) { + currentTagRequest.abort(); + } + + var provider = providers[currentProvider] + if (!provider.tagSearchTemplate) { + return + } + var url = provider.tagSearchTemplate.replace("{{query}}", encodeURIComponent(query)) + + var xhr = new XMLHttpRequest() + currentTagRequest = xhr + xhr.open("GET", url) + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { + currentTagRequest = null + try { + // console.log("[Booru] Raw response: " + xhr.responseText) + var response = JSON.parse(xhr.responseText) + response = provider.tagMapFunc(response) + // console.log("[Booru] Mapped response: " + JSON.stringify(response)) + root.tagSuggestion(query, response) + } catch (e) { + console.log("[Booru] Failed to parse response: " + e) + } + } + else if (xhr.readyState === XMLHttpRequest.DONE) { + console.log("[Booru] Request failed with status: " + xhr.status) + } + } + + try { + // Required for danbooru + if (currentProvider == "danbooru") { + xhr.setRequestHeader("User-Agent", defaultUserAgent) + } + xhr.send() + } catch (error) { + console.log("Could not set User-Agent:", error) + } + } } From 4a44d7838949d95068844ed90bbe5c958fcf4b0b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 3 May 2025 15:49:01 +0200 Subject: [PATCH 148/824] launcher: fuzzy search --- .../modules/common/functions/fuzzysort.js | 682 ++++++++++++++++++ .../modules/common/functions/icons.js | 2 + .../modules/overview/SearchWidget.qml | 28 +- .config/quickshell/services/AppSearch.qml | 22 + licenses/MIT.txt | 9 + licenses/README.md | 3 + 6 files changed, 730 insertions(+), 16 deletions(-) create mode 100644 .config/quickshell/modules/common/functions/fuzzysort.js create mode 100644 .config/quickshell/services/AppSearch.qml create mode 100644 licenses/MIT.txt create mode 100644 licenses/README.md diff --git a/.config/quickshell/modules/common/functions/fuzzysort.js b/.config/quickshell/modules/common/functions/fuzzysort.js new file mode 100644 index 000000000..1c1d9b9db --- /dev/null +++ b/.config/quickshell/modules/common/functions/fuzzysort.js @@ -0,0 +1,682 @@ +.pragma library + +// https://github.com/farzher/fuzzysort +// License: MIT | Copyright (c) 2018 Stephen Kamenar +// A copy of the license is available in the `licenses` folder of this repository + +var single = (search, target) => { + if(!search || !target) return NULL + + var preparedSearch = getPreparedSearch(search) + if(!isPrepared(target)) target = getPrepared(target) + + var searchBitflags = preparedSearch.bitflags + if((searchBitflags & target._bitflags) !== searchBitflags) return NULL + + return algorithm(preparedSearch, target) +} + +var go = (search, targets, options) => { + if(!search) return options?.all ? all(targets, options) : noResults + + var preparedSearch = getPreparedSearch(search) + var searchBitflags = preparedSearch.bitflags + var containsSpace = preparedSearch.containsSpace + + var threshold = denormalizeScore( options?.threshold || 0 ) + var limit = options?.limit || INFINITY + + var resultsLen = 0; var limitedCount = 0 + var targetsLen = targets.length + + function push_result(result) { + if(resultsLen < limit) { q.add(result); ++resultsLen } + else { + ++limitedCount + if(result._score > q.peek()._score) q.replaceTop(result) + } + } + + // This code is copy/pasted 3 times for performance reasons [options.key, options.keys, no keys] + + // options.key + if(options?.key) { + var key = options.key + for(var i = 0; i < targetsLen; ++i) { var obj = targets[i] + var target = getValue(obj, key) + if(!target) continue + if(!isPrepared(target)) target = getPrepared(target) + + if((searchBitflags & target._bitflags) !== searchBitflags) continue + var result = algorithm(preparedSearch, target) + if(result === NULL) continue + if(result._score < threshold) continue + + result.obj = obj + push_result(result) + } + + // options.keys + } else if(options?.keys) { + var keys = options.keys + var keysLen = keys.length + + outer: for(var i = 0; i < targetsLen; ++i) { var obj = targets[i] + + { // early out based on bitflags + var keysBitflags = 0 + for (var keyI = 0; keyI < keysLen; ++keyI) { + var key = keys[keyI] + var target = getValue(obj, key) + if(!target) { tmpTargets[keyI] = noTarget; continue } + if(!isPrepared(target)) target = getPrepared(target) + tmpTargets[keyI] = target + + keysBitflags |= target._bitflags + } + + if((searchBitflags & keysBitflags) !== searchBitflags) continue + } + + if(containsSpace) for(let i=0; i -1000) { + if(keysSpacesBestScores[i] > NEGATIVE_INFINITY) { + var tmp = (keysSpacesBestScores[i] + allowPartialMatchScores[i]) / 4/*bonus score for having multiple matches*/ + if(tmp > keysSpacesBestScores[i]) keysSpacesBestScores[i] = tmp + } + } + if(allowPartialMatchScores[i] > keysSpacesBestScores[i]) keysSpacesBestScores[i] = allowPartialMatchScores[i] + } + } + + if(containsSpace) { + for(let i=0; i -1000) { + if(score > NEGATIVE_INFINITY) { + var tmp = (score + result._score) / 4/*bonus score for having multiple matches*/ + if(tmp > score) score = tmp + } + } + if(result._score > score) score = result._score + } + } + + objResults.obj = obj + objResults._score = score + if(options?.scoreFn) { + score = options.scoreFn(objResults) + if(!score) continue + score = denormalizeScore(score) + objResults._score = score + } + + if(score < threshold) continue + push_result(objResults) + } + + // no keys + } else { + for(var i = 0; i < targetsLen; ++i) { var target = targets[i] + if(!target) continue + if(!isPrepared(target)) target = getPrepared(target) + + if((searchBitflags & target._bitflags) !== searchBitflags) continue + var result = algorithm(preparedSearch, target) + if(result === NULL) continue + if(result._score < threshold) continue + + push_result(result) + } + } + + if(resultsLen === 0) return noResults + var results = new Array(resultsLen) + for(var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll() + results.total = resultsLen + limitedCount + return results +} + + +// this is written as 1 function instead of 2 for minification. perf seems fine ... +// except when minified. the perf is very slow +var highlight = (result, open='', close='') => { + var callback = typeof open === 'function' ? open : undefined + + var target = result.target + var targetLen = target.length + var indexes = result.indexes + var highlighted = '' + var matchI = 0 + var indexesI = 0 + var opened = false + var parts = [] + + for(var i = 0; i < targetLen; ++i) { var char = target[i] + if(indexes[indexesI] === i) { + ++indexesI + if(!opened) { opened = true + if(callback) { + parts.push(highlighted); highlighted = '' + } else { + highlighted += open + } + } + + if(indexesI === indexes.length) { + if(callback) { + highlighted += char + parts.push(callback(highlighted, matchI++)); highlighted = '' + parts.push(target.substr(i+1)) + } else { + highlighted += char + close + target.substr(i+1) + } + break + } + } else { + if(opened) { opened = false + if(callback) { + parts.push(callback(highlighted, matchI++)); highlighted = '' + } else { + highlighted += close + } + } + } + highlighted += char + } + + return callback ? parts : highlighted +} + + +var prepare = (target) => { + if(typeof target === 'number') target = ''+target + else if(typeof target !== 'string') target = '' + var info = prepareLowerInfo(target) + return new_result(target, {_targetLower:info._lower, _targetLowerCodes:info.lowerCodes, _bitflags:info.bitflags}) +} + +var cleanup = () => { preparedCache.clear(); preparedSearchCache.clear() } + + +// Below this point is only internal code +// Below this point is only internal code +// Below this point is only internal code +// Below this point is only internal code + + +class Result { + get ['indexes']() { return this._indexes.slice(0, this._indexes.len).sort((a,b)=>a-b) } + set ['indexes'](indexes) { return this._indexes = indexes } + ['highlight'](open, close) { return highlight(this, open, close) } + get ['score']() { return normalizeScore(this._score) } + set ['score'](score) { this._score = denormalizeScore(score) } +} + +class KeysResult extends Array { + get ['score']() { return normalizeScore(this._score) } + set ['score'](score) { this._score = denormalizeScore(score) } +} + +var new_result = (target, options) => { + const result = new Result() + result['target'] = target + result['obj'] = options.obj ?? NULL + result._score = options._score ?? NEGATIVE_INFINITY + result._indexes = options._indexes ?? [] + result._targetLower = options._targetLower ?? '' + result._targetLowerCodes = options._targetLowerCodes ?? NULL + result._nextBeginningIndexes = options._nextBeginningIndexes ?? NULL + result._bitflags = options._bitflags ?? 0 + return result +} + + +var normalizeScore = score => { + if(score === NEGATIVE_INFINITY) return 0 + if(score > 1) return score + return Math.E ** ( ((-score + 1)**.04307 - 1) * -2) +} +var denormalizeScore = normalizedScore => { + if(normalizedScore === 0) return NEGATIVE_INFINITY + if(normalizedScore > 1) return normalizedScore + return 1 - Math.pow((Math.log(normalizedScore) / -2 + 1), 1 / 0.04307) +} + + +var prepareSearch = (search) => { + if(typeof search === 'number') search = ''+search + else if(typeof search !== 'string') search = '' + search = search.trim() + var info = prepareLowerInfo(search) + + var spaceSearches = [] + if(info.containsSpace) { + var searches = search.split(/\s+/) + searches = [...new Set(searches)] // distinct + for(var i=0; i { + if(target.length > 999) return prepare(target) // don't cache huge targets + var targetPrepared = preparedCache.get(target) + if(targetPrepared !== undefined) return targetPrepared + targetPrepared = prepare(target) + preparedCache.set(target, targetPrepared) + return targetPrepared +} +var getPreparedSearch = (search) => { + if(search.length > 999) return prepareSearch(search) // don't cache huge searches + var searchPrepared = preparedSearchCache.get(search) + if(searchPrepared !== undefined) return searchPrepared + searchPrepared = prepareSearch(search) + preparedSearchCache.set(search, searchPrepared) + return searchPrepared +} + + +var all = (targets, options) => { + var results = []; results.total = targets.length // this total can be wrong if some targets are skipped + + var limit = options?.limit || INFINITY + + if(options?.key) { + for(var i=0;i= limit) return results + } + } else if(options?.keys) { + for(var i=0;i= 0; --keyI) { + var target = getValue(obj, options.keys[keyI]) + if(!target) { objResults[keyI] = noTarget; continue } + if(!isPrepared(target)) target = getPrepared(target) + target._score = NEGATIVE_INFINITY + target._indexes.len = 0 + objResults[keyI] = target + } + objResults.obj = obj + objResults._score = NEGATIVE_INFINITY + results.push(objResults); if(results.length >= limit) return results + } + } else { + for(var i=0;i= limit) return results + } + } + + return results +} + + +var algorithm = (preparedSearch, prepared, allowSpaces=false, allowPartialMatch=false) => { + if(allowSpaces===false && preparedSearch.containsSpace) return algorithmSpaces(preparedSearch, prepared, allowPartialMatch) + + var searchLower = preparedSearch._lower + var searchLowerCodes = preparedSearch.lowerCodes + var searchLowerCode = searchLowerCodes[0] + var targetLowerCodes = prepared._targetLowerCodes + var searchLen = searchLowerCodes.length + var targetLen = targetLowerCodes.length + var searchI = 0 // where we at + var targetI = 0 // where you at + var matchesSimpleLen = 0 + + // very basic fuzzy match; to remove non-matching targets ASAP! + // walk through target. find sequential matches. + // if all chars aren't found then exit + for(;;) { + var isMatch = searchLowerCode === targetLowerCodes[targetI] + if(isMatch) { + matchesSimple[matchesSimpleLen++] = targetI + ++searchI; if(searchI === searchLen) break + searchLowerCode = searchLowerCodes[searchI] + } + ++targetI; if(targetI >= targetLen) return NULL // Failed to find searchI + } + + var searchI = 0 + var successStrict = false + var matchesStrictLen = 0 + + var nextBeginningIndexes = prepared._nextBeginningIndexes + if(nextBeginningIndexes === NULL) nextBeginningIndexes = prepared._nextBeginningIndexes = prepareNextBeginningIndexes(prepared.target) + targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1] + + // Our target string successfully matched all characters in sequence! + // Let's try a more advanced and strict test to improve the score + // only count it as a match if it's consecutive or a beginning character! + var backtrackCount = 0 + if(targetI !== targetLen) for(;;) { + if(targetI >= targetLen) { + // We failed to find a good spot for this search char, go back to the previous search char and force it forward + if(searchI <= 0) break // We failed to push chars forward for a better match + + ++backtrackCount; if(backtrackCount > 200) break // exponential backtracking is taking too long, just give up and return a bad match + + --searchI + var lastMatch = matchesStrict[--matchesStrictLen] + targetI = nextBeginningIndexes[lastMatch] + + } else { + var isMatch = searchLowerCodes[searchI] === targetLowerCodes[targetI] + if(isMatch) { + matchesStrict[matchesStrictLen++] = targetI + ++searchI; if(searchI === searchLen) { successStrict = true; break } + ++targetI + } else { + targetI = nextBeginningIndexes[targetI] + } + } + } + + // check if it's a substring match + var substringIndex = searchLen <= 1 ? -1 : prepared._targetLower.indexOf(searchLower, matchesSimple[0]) // perf: this is slow + var isSubstring = !!~substringIndex + var isSubstringBeginning = !isSubstring ? false : substringIndex===0 || prepared._nextBeginningIndexes[substringIndex-1] === substringIndex + + // if it's a substring match but not at a beginning index, let's try to find a substring starting at a beginning index for a better score + if(isSubstring && !isSubstringBeginning) { + for(var i=0; i { + var score = 0 + + var extraMatchGroupCount = 0 + for(var i = 1; i < searchLen; ++i) { + if(matches[i] - matches[i-1] !== 1) {score -= matches[i]; ++extraMatchGroupCount} + } + var unmatchedDistance = matches[searchLen-1] - matches[0] - (searchLen-1) + + score -= (12+unmatchedDistance) * extraMatchGroupCount // penality for more groups + + if(matches[0] !== 0) score -= matches[0]*matches[0]*.2 // penality for not starting near the beginning + + if(!successStrict) { + score *= 1000 + } else { + // successStrict on a target with too many beginning indexes loses points for being a bad target + var uniqueBeginningIndexes = 1 + for(var i = nextBeginningIndexes[0]; i < targetLen; i=nextBeginningIndexes[i]) ++uniqueBeginningIndexes + + if(uniqueBeginningIndexes > 24) score *= (uniqueBeginningIndexes-24)*10 // quite arbitrary numbers here ... + } + + score -= (targetLen - searchLen)/2 // penality for longer targets + + if(isSubstring) score /= 1+searchLen*searchLen*1 // bonus for being a full substring + if(isSubstringBeginning) score /= 1+searchLen*searchLen*1 // bonus for substring starting on a beginningIndex + + score -= (targetLen - searchLen)/2 // penality for longer targets + + return score + } + + if(!successStrict) { + if(isSubstring) for(var i=0; i { + var seen_indexes = new Set() + var score = 0 + var result = NULL + + var first_seen_index_last_search = 0 + var searches = preparedSearch.spaceSearches + var searchesLen = searches.length + var changeslen = 0 + + // Return _nextBeginningIndexes back to its normal state + var resetNextBeginningIndexes = () => { + for(let i=changeslen-1; i>=0; i--) target._nextBeginningIndexes[nextBeginningIndexesChanges[i*2 + 0]] = nextBeginningIndexesChanges[i*2 + 1] + } + + var hasAtLeast1Match = false + for(var i=0; i=0; i--) { + if(toReplace !== target._nextBeginningIndexes[i]) break + target._nextBeginningIndexes[i] = newBeginningIndex + nextBeginningIndexesChanges[changeslen*2 + 0] = i + nextBeginningIndexesChanges[changeslen*2 + 1] = toReplace + changeslen++ + } + } + } + + score += result._score / searchesLen + allowPartialMatchScores[i] = result._score / searchesLen + + // dock points based on order otherwise "c man" returns Manifest.cpp instead of CheatManager.h + if(result._indexes[0] < first_seen_index_last_search) { + score -= (first_seen_index_last_search - result._indexes[0]) * 2 + } + first_seen_index_last_search = result._indexes[0] + + for(var j=0; j score) { + if(allowPartialMatch) { + for(var i=0; i str.replace(/\p{Script=Latin}+/gu, match => match.normalize('NFD')).replace(/[\u0300-\u036f]/g, '') + +var prepareLowerInfo = (str) => { + str = remove_accents(str) + var strLen = str.length + var lower = str.toLowerCase() + var lowerCodes = [] // new Array(strLen) sparse array is too slow + var bitflags = 0 + var containsSpace = false // space isn't stored in bitflags because of how searching with a space works + + for(var i = 0; i < strLen; ++i) { + var lowerCode = lowerCodes[i] = lower.charCodeAt(i) + + if(lowerCode === 32) { + containsSpace = true + continue // it's important that we don't set any bitflags for space + } + + var bit = lowerCode>=97&&lowerCode<=122 ? lowerCode-97 // alphabet + : lowerCode>=48&&lowerCode<=57 ? 26 // numbers + // 3 bits available + : lowerCode<=127 ? 30 // other ascii + : 31 // other utf8 + bitflags |= 1< { + var targetLen = target.length + var beginningIndexes = []; var beginningIndexesLen = 0 + var wasUpper = false + var wasAlphanum = false + for(var i = 0; i < targetLen; ++i) { + var targetCode = target.charCodeAt(i) + var isUpper = targetCode>=65&&targetCode<=90 + var isAlphanum = isUpper || targetCode>=97&&targetCode<=122 || targetCode>=48&&targetCode<=57 + var isBeginning = isUpper && !wasUpper || !wasAlphanum || !isAlphanum + wasUpper = isUpper + wasAlphanum = isAlphanum + if(isBeginning) beginningIndexes[beginningIndexesLen++] = i + } + return beginningIndexes +} +var prepareNextBeginningIndexes = (target) => { + target = remove_accents(target) + var targetLen = target.length + var beginningIndexes = prepareBeginningIndexes(target) + var nextBeginningIndexes = [] // new Array(targetLen) sparse array is too slow + var lastIsBeginning = beginningIndexes[0] + var lastIsBeginningI = 0 + for(var i = 0; i < targetLen; ++i) { + if(lastIsBeginning > i) { + nextBeginningIndexes[i] = lastIsBeginning + } else { + lastIsBeginning = beginningIndexes[++lastIsBeginningI] + nextBeginningIndexes[i] = lastIsBeginning===undefined ? targetLen : lastIsBeginning + } + } + return nextBeginningIndexes +} + +var preparedCache = new Map() +var preparedSearchCache = new Map() + +// the theory behind these being globals is to reduce garbage collection by not making new arrays +var matchesSimple = []; var matchesStrict = [] +var nextBeginningIndexesChanges = [] // allows straw berry to match strawberry well, by modifying the end of a substring to be considered a beginning index for the rest of the search +var keysSpacesBestScores = []; var allowPartialMatchScores = [] +var tmpTargets = []; var tmpResults = [] + +// prop = 'key' 2.5ms optimized for this case, seems to be about as fast as direct obj[prop] +// prop = 'key1.key2' 10ms +// prop = ['key1', 'key2'] 27ms +// prop = obj => obj.tags.join() ??ms +var getValue = (obj, prop) => { + var tmp = obj[prop]; if(tmp !== undefined) return tmp + if(typeof prop === 'function') return prop(obj) // this should run first. but that makes string props slower + var segs = prop + if(!Array.isArray(prop)) segs = prop.split('.') + var len = segs.length + var i = -1 + while (obj && (++i < len)) obj = obj[segs[i]] + return obj +} + +var isPrepared = (x) => { return typeof x === 'object' && typeof x._bitflags === 'number' } +var INFINITY = Infinity; var NEGATIVE_INFINITY = -INFINITY +var noResults = []; noResults.total = 0 +var NULL = null + +var noTarget = prepare('') + +// Hacked version of https://github.com/lemire/FastPriorityQueue.js +var fastpriorityqueue=r=>{var e=[],o=0,a={},v=r=>{for(var a=0,v=e[a],c=1;c>1]=e[a],c=1+(a<<1)}for(var f=a-1>>1;a>0&&v._score>1)e[a]=e[f];e[a]=v};return a.add=(r=>{var a=o;e[o++]=r;for(var v=a-1>>1;a>0&&r._score>1)e[a]=e[v];e[a]=r}),a.poll=(r=>{if(0!==o){var a=e[0];return e[0]=e[--o],v(),a}}),a.peek=(r=>{if(0!==o)return e[0]}),a.replaceTop=(r=>{e[0]=r,v()}),a} +var q = fastpriorityqueue() // reuse this diff --git a/.config/quickshell/modules/common/functions/icons.js b/.config/quickshell/modules/common/functions/icons.js index 56b009887..e92fcc03b 100644 --- a/.config/quickshell/modules/common/functions/icons.js +++ b/.config/quickshell/modules/common/functions/icons.js @@ -1,3 +1,5 @@ +.pragma library + const substitutions = { "code-url-handler": "visual-studio-code", "Code": "visual-studio-code", diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 186b0ef1b..358653b75 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -294,11 +294,7 @@ Item { // Wrapper // Add filtered application entries result = result.concat( - DesktopEntries.applications.values - .filter((entry) => { - if (root.searchingText == "") return false - return entry.name.toLowerCase().includes(root.searchingText.toLowerCase()) - }) + AppSearch.fuzzyQuery(root.searchingText) .map((entry) => { entry.clickActionName = "Launch"; entry.type = "App" @@ -322,17 +318,6 @@ Item { // Wrapper }); } } - // Qalc math result - result.push({ - name: root.mathResult, - clickActionName: "Copy", - type: qsTr("Math result"), - fontType: "monospace", - materialSymbol: 'calculate', - execute: () => { - Hyprland.dispatch(`exec wl-copy '${root.mathResult}'`) - } - }); // Run command result.push({ name: searchingText, @@ -344,6 +329,17 @@ Item { // Wrapper executor.executeCommand(searchingText.startsWith('sudo') ? `${ConfigOptions.apps.terminal} fish -C '${root.searchingText}'` : root.searchingText); } }); + // Qalc math result + result.push({ + name: root.mathResult, + clickActionName: "Copy", + type: qsTr("Math result"), + fontType: "monospace", + materialSymbol: 'calculate', + execute: () => { + Hyprland.dispatch(`exec wl-copy '${root.mathResult}'`) + } + }); // Web search result.push({ name: root.searchingText, diff --git a/.config/quickshell/services/AppSearch.qml b/.config/quickshell/services/AppSearch.qml new file mode 100644 index 000000000..9c15a0ad9 --- /dev/null +++ b/.config/quickshell/services/AppSearch.qml @@ -0,0 +1,22 @@ +pragma Singleton + +import "root:/modules/common/functions/fuzzysort.js" as Fuzzy +import Quickshell +import Quickshell.Io + +Singleton { + id: root + + readonly property list list: DesktopEntries.applications.values.filter(a => !a.noDisplay).sort((a, b) => a.name.localeCompare(b.name)) + readonly property list preppedNames: list.map(a => ({ + name: Fuzzy.prepare(a.name), + entry: a + })) + + function fuzzyQuery(search: string): var { // Idk why list doesn't work + return Fuzzy.go(search, preppedNames, { + all: true, + key: "name" + }).map(r => r.obj.entry); + } +} diff --git a/licenses/MIT.txt b/licenses/MIT.txt new file mode 100644 index 000000000..9a7392926 --- /dev/null +++ b/licenses/MIT.txt @@ -0,0 +1,9 @@ +MIT License + +Copyright + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/licenses/README.md b/licenses/README.md new file mode 100644 index 000000000..02bf9af72 --- /dev/null +++ b/licenses/README.md @@ -0,0 +1,3 @@ +# Licenses + +This repository contains code from other repositories. Files containing such code should include a license notice, and a copy should be stored in this folder. From 3284e4154535f45e67a7229ec9f3eafead5dd158 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 3 May 2025 18:12:06 +0200 Subject: [PATCH 149/824] animations: new curve --- .config/quickshell/modules/bar/Battery.qml | 10 +- .config/quickshell/modules/bar/Resource.qml | 10 +- .../modules/bar/SmallCircleButton.qml | 5 +- .config/quickshell/modules/bar/Workspaces.qml | 45 +++--- .../quickshell/modules/common/Appearance.qml | 32 ++++- .../modules/common/widgets/DialogButton.qml | 10 +- .../modules/common/widgets/MenuButton.qml | 10 +- .../modules/common/widgets/NavRailButton.qml | 10 +- .../common/widgets/NotificationWidget.qml | 54 +++++--- .../common/widgets/PrimaryTabButton.qml | 15 +- .../common/widgets/SecondaryTabButton.qml | 15 +- .../common/widgets/StyledProgressBar.qml | 5 +- .../modules/common/widgets/StyledSlider.qml | 12 +- .../modules/common/widgets/StyledSwitch.qml | 30 ++-- .../modules/common/widgets/StyledToolTip.qml | 10 +- .../modules/overview/OverviewWindow.qml | 25 ++-- .../modules/overview/SearchItem.qml | 2 +- .../modules/overview/SearchWidget.qml | 49 +++++-- .../quickshell/modules/session/Session.qml | 2 +- .../modules/session/SessionActionButton.qml | 5 +- .../quickshell/modules/sidebarLeft/Anime.qml | 128 ++++++++++++------ .../modules/sidebarLeft/anime/BooruImage.qml | 41 +++++- .../sidebarLeft/anime/BooruResponse.qml | 10 +- .../sidebarRight/BottomWidgetGroup.qml | 19 +-- .../calendar/CalendarDayButton.qml | 10 +- .../calendar/CalendarHeaderButton.qml | 7 +- .../notifications/NotificationList.qml | 5 +- .../NotificationStatusButton.qml | 7 +- .../quickToggles/QuickToggleButton.qml | 10 +- .../modules/sidebarRight/todo/TaskList.qml | 12 +- .../todo/TodoItemActionButton.qml | 7 +- .../modules/sidebarRight/todo/TodoWidget.qml | 17 +-- .../volumeMixer/AudioDeviceSelectorButton.qml | 5 +- .../sidebarRight/volumeMixer/VolumeMixer.qml | 35 +++-- .config/quickshell/services/AppSearch.qml | 9 +- 35 files changed, 442 insertions(+), 236 deletions(-) diff --git a/.config/quickshell/modules/bar/Battery.qml b/.config/quickshell/modules/bar/Battery.qml index 8e22532af..b4142dd1c 100644 --- a/.config/quickshell/modules/bar/Battery.qml +++ b/.config/quickshell/modules/bar/Battery.qml @@ -31,8 +31,9 @@ Rectangle { Behavior on implicitWidth { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } @@ -76,8 +77,9 @@ Rectangle { Behavior on opacity { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } diff --git a/.config/quickshell/modules/bar/Resource.qml b/.config/quickshell/modules/bar/Resource.qml index 9457c4487..40c2aff5d 100644 --- a/.config/quickshell/modules/bar/Resource.qml +++ b/.config/quickshell/modules/bar/Resource.qml @@ -43,8 +43,9 @@ Item { Behavior on x { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } @@ -52,8 +53,9 @@ Item { Behavior on implicitWidth { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } \ No newline at end of file diff --git a/.config/quickshell/modules/bar/SmallCircleButton.qml b/.config/quickshell/modules/bar/SmallCircleButton.qml index 97cb93079..32c79d352 100644 --- a/.config/quickshell/modules/bar/SmallCircleButton.qml +++ b/.config/quickshell/modules/bar/SmallCircleButton.qml @@ -25,8 +25,9 @@ Button { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 7b3cf37cc..101baf296 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -120,21 +120,24 @@ Item { Behavior on opacity { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } Behavior on radiusLeft { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } Behavior on radiusRight { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } @@ -202,16 +205,18 @@ Item { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } Behavior on opacity { NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } @@ -236,26 +241,30 @@ Item { Behavior on opacity { NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } Behavior on anchors.bottomMargin { NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } Behavior on anchors.rightMargin { NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } Behavior on implicitSize { NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } } diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index cb000bfab..cf3e49f8d 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -6,6 +6,7 @@ pragma ComponentBehavior: Bound Singleton { property QtObject m3colors property QtObject animation + property QtObject animationCurves property QtObject colors property QtObject rounding property QtObject font @@ -171,24 +172,41 @@ Singleton { } } + animationCurves: QtObject { + readonly property list emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] + readonly property list emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] + readonly property list emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1] + readonly property list standard: [0.2, 0, 0, 1, 1, 1] + readonly property list standardAccel: [0.3, 0, 1, 1, 1, 1] + readonly property list standardDecel: [0, 0, 0, 1, 1, 1] + } + animation: QtObject { - property QtObject elementDecel: QtObject { - property int duration: 200 - property int type: Easing.OutCirc + property QtObject elementMove: QtObject { + property int duration: 450 + property int type: Easing.BezierSpline + property list bezierCurve: animationCurves.emphasized property int velocity: 650 } - property QtObject elementDecelFast: QtObject { - property int duration: 140 - property int type: Easing.OutCirc + property QtObject elementMoveFast: QtObject { + property int duration: 200 + property int type: Easing.BezierSpline + property list bezierCurve: animationCurves.standardDecel property int velocity: 850 } + property QtObject scroll: QtObject { + property int duration: 400 + property int type: Easing.BezierSpline + property list bezierCurve: animationCurves.standardDecel + } property QtObject menuDecel: QtObject { property int duration: 350 property int type: Easing.OutExpo } property QtObject positionShift: QtObject { property int duration: 300 - property int type: Easing.InOutExpo + property int type: Easing.BezierSpline + property list bezierCurve: animationCurves.emphasized property int velocity: 650 } } diff --git a/.config/quickshell/modules/common/widgets/DialogButton.qml b/.config/quickshell/modules/common/widgets/DialogButton.qml index cc8ea6174..d4310dbdf 100644 --- a/.config/quickshell/modules/common/widgets/DialogButton.qml +++ b/.config/quickshell/modules/common/widgets/DialogButton.qml @@ -21,8 +21,9 @@ Button { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } @@ -41,8 +42,9 @@ Button { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } diff --git a/.config/quickshell/modules/common/widgets/MenuButton.qml b/.config/quickshell/modules/common/widgets/MenuButton.qml index 957c2229d..f5de52c5e 100644 --- a/.config/quickshell/modules/common/widgets/MenuButton.qml +++ b/.config/quickshell/modules/common/widgets/MenuButton.qml @@ -22,8 +22,9 @@ Button { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } @@ -42,8 +43,9 @@ Button { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } diff --git a/.config/quickshell/modules/common/widgets/NavRailButton.qml b/.config/quickshell/modules/common/widgets/NavRailButton.qml index 2ccbe5529..ed7b1f8f2 100644 --- a/.config/quickshell/modules/common/widgets/NavRailButton.qml +++ b/.config/quickshell/modules/common/widgets/NavRailButton.qml @@ -34,8 +34,9 @@ Button { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } MaterialSymbol { @@ -47,8 +48,9 @@ Button { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index b8f58a463..c9df0c186 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -28,8 +28,9 @@ Item { Behavior on implicitHeight { enabled: enableAnimation NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } @@ -77,7 +78,7 @@ Item { Timer { id: destroyTimer1 - interval: Appearance.animation.elementDecelFast.duration + interval: Appearance.animation.elementMoveFast.duration repeat: false onTriggered: { notificationRowWrapper.anchors.top = undefined @@ -89,7 +90,7 @@ Item { Timer { id: destroyTimer2 - interval: Appearance.animation.elementDecelFast.duration + interval: Appearance.animation.elementMoveFast.duration repeat: false onTriggered: { root.destroy() @@ -186,15 +187,17 @@ Item { Behavior on x { enabled: enableAnimation NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } Behavior on height { enabled: enableAnimation NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } } @@ -224,8 +227,9 @@ Item { Behavior on x { enabled: enableAnimation NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } @@ -241,8 +245,9 @@ Item { Behavior on implicitHeight { enabled: enableAnimation NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } @@ -400,8 +405,9 @@ Item { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } @@ -418,8 +424,9 @@ Item { rotation: expanded ? 180 : 0 Behavior on rotation { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } } @@ -486,20 +493,23 @@ Item { visible: opacity > 0 Behavior on opacity { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } Behavior on height { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } Behavior on implicitHeight { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml index 3a7565281..beb61f607 100644 --- a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml +++ b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml @@ -24,8 +24,9 @@ TabButton { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } @@ -43,8 +44,9 @@ TabButton { color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } @@ -57,8 +59,9 @@ TabButton { text: buttonText Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } diff --git a/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml b/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml index 9a8f7e16a..20f338579 100644 --- a/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml +++ b/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml @@ -24,8 +24,9 @@ TabButton { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } @@ -43,8 +44,9 @@ TabButton { color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } @@ -56,8 +58,9 @@ TabButton { text: buttonText Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } diff --git a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml index 078570ffe..12b9c0cd2 100644 --- a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml +++ b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml @@ -16,8 +16,9 @@ ProgressBar { Behavior on value { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } diff --git a/.config/quickshell/modules/common/widgets/StyledSlider.qml b/.config/quickshell/modules/common/widgets/StyledSlider.qml index 17433c591..f800e2a3d 100644 --- a/.config/quickshell/modules/common/widgets/StyledSlider.qml +++ b/.config/quickshell/modules/common/widgets/StyledSlider.qml @@ -24,14 +24,15 @@ Slider { Behavior on value { // This makes the adjusted value (like volume) shift smoothly SmoothedAnimation { - velocity: Appearance.animation.elementDecel.velocity + velocity: Appearance.animation.elementMove.velocity } } Behavior on handleMargins { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } @@ -92,8 +93,9 @@ Slider { Behavior on implicitWidth { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } diff --git a/.config/quickshell/modules/common/widgets/StyledSwitch.qml b/.config/quickshell/modules/common/widgets/StyledSwitch.qml index 73734f3b1..8406a47d1 100644 --- a/.config/quickshell/modules/common/widgets/StyledSwitch.qml +++ b/.config/quickshell/modules/common/widgets/StyledSwitch.qml @@ -23,14 +23,16 @@ Switch { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } Behavior on border.color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } @@ -47,26 +49,30 @@ Switch { Behavior on anchors.leftMargin { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } Behavior on width { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } Behavior on height { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index ff01a424f..5b37b2611 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -40,9 +40,11 @@ ToolTip { implicitWidth: tooltipTextObject.width + 2 * padding implicitHeight: tooltipTextObject.height + 2 * padding Behavior on opacity { - OpacityAnimator { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + NumberAnimation { + target: opacity + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } opacity: visible ? 1 : 0 @@ -50,7 +52,7 @@ ToolTip { StyledText { id: tooltipTextObject text: content - font.pixelSize: Appearance.font.pixelSize.smaller + font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnTooltip wrapMode: Text.Wrap } diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index 9be71a3d7..935960ad0 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -48,26 +48,30 @@ Rectangle { // Window Behavior on x { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } Behavior on y { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } Behavior on width { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } Behavior on height { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } @@ -85,8 +89,9 @@ Rectangle { // Window Behavior on implicitSize { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index 2b10c5186..d1f65952d 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -102,7 +102,7 @@ Button { color: Appearance.m3colors.m3onSurface horizontalAlignment: Text.AlignLeft elide: Text.ElideRight - text: root.itemName + text: `${root.itemName}` } } diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 358653b75..9bab9078c 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -217,8 +217,9 @@ Item { // Wrapper Behavior on implicitWidth { NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } @@ -318,7 +319,23 @@ Item { // Wrapper }); } } - // Run command + + // Insert math result before command if search starts with a number + const startsWithNumber = /^\d/.test(root.searchingText); + if (startsWithNumber) { + result.push({ + name: root.mathResult, + clickActionName: "Copy", + type: qsTr("Math result"), + fontType: "monospace", + materialSymbol: 'calculate', + execute: () => { + Hyprland.dispatch(`exec wl-copy '${root.mathResult}'`) + } + }); + } + + // Command result.push({ name: searchingText, clickActionName: "Run", @@ -329,17 +346,21 @@ Item { // Wrapper executor.executeCommand(searchingText.startsWith('sudo') ? `${ConfigOptions.apps.terminal} fish -C '${root.searchingText}'` : root.searchingText); } }); - // Qalc math result - result.push({ - name: root.mathResult, - clickActionName: "Copy", - type: qsTr("Math result"), - fontType: "monospace", - materialSymbol: 'calculate', - execute: () => { - Hyprland.dispatch(`exec wl-copy '${root.mathResult}'`) - } - }); + + // If not already added, add math result after command + if (!startsWithNumber) { + result.push({ + name: root.mathResult, + clickActionName: "Copy", + type: qsTr("Math result"), + fontType: "monospace", + materialSymbol: 'calculate', + execute: () => { + Hyprland.dispatch(`exec wl-copy '${root.mathResult}'`) + } + }); + } + // Web search result.push({ name: root.searchingText, diff --git a/.config/quickshell/modules/session/Session.qml b/.config/quickshell/modules/session/Session.qml index 47bed1435..ff08dfdd4 100644 --- a/.config/quickshell/modules/session/Session.qml +++ b/.config/quickshell/modules/session/Session.qml @@ -199,7 +199,7 @@ Scope { Behavior on implicitWidth { SmoothedAnimation { - velocity: Appearance.animation.elementDecelFast.velocity + velocity: Appearance.animation.elementMoveFast.velocity } } diff --git a/.config/quickshell/modules/session/SessionActionButton.qml b/.config/quickshell/modules/session/SessionActionButton.qml index c0ff0a95e..3e0d7ccea 100644 --- a/.config/quickshell/modules/session/SessionActionButton.qml +++ b/.config/quickshell/modules/session/SessionActionButton.qml @@ -38,8 +38,9 @@ Button { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index e11372600..c43a71a37 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -38,32 +38,64 @@ Item { Hyprland.dispatch(`exec mkdir -p ${previewDownloadPath}`) } + property var allCommands: [ + { + name: "clear", + description: qsTr("Clear the current list of images"), + execute: () => { + Booru.clearResponses(); + } + }, + { + name: "mode", + description: qsTr("Set the current API provider"), + execute: (args) => { + Booru.setProvider(args[0]); + } + }, + { + name: "nsfw", + description: qsTr("Toggle NSFW mode"), + execute: () => { + ConfigOptions.sidebar.booru.allowNsfw = !ConfigOptions.sidebar.booru.allowNsfw; + } + }, + { + name: "safe", + description: qsTr("Set NSFW mode to false"), + execute: () => { + ConfigOptions.sidebar.booru.allowNsfw = false; + } + }, + { + name: "lewd", + description: qsTr("Set NSFW mode to true"), + execute: () => { + ConfigOptions.sidebar.booru.allowNsfw = true; + } + }, + { + name: "next", + description: qsTr("Get the next page of results"), + execute: () => { + if (Booru.responses.length > 0) { + const lastResponse = Booru.responses[Booru.responses.length - 1]; + root.handleInput(lastResponse.tags.join(" ") + ` ${parseInt(lastResponse.page) + 1}`); + } + } + } + ] + function handleInput(inputText) { if (inputText.startsWith(root.commandPrefix)) { // Handle special commands const command = inputText.split(" ")[0].substring(1); const args = inputText.split(" ").slice(1); - if (command === "clear") { - Booru.clearResponses(); - } - else if (command === "mode") { - const newProvider = args[0]; - Booru.setProvider(newProvider); - } - else if (command == "nsfw") { - ConfigOptions.sidebar.booru.allowNsfw = !ConfigOptions.sidebar.booru.allowNsfw - } - else if (command == "safe") { - ConfigOptions.sidebar.booru.allowNsfw = false - } - else if (command == "lewd") { - ConfigOptions.sidebar.booru.allowNsfw = true - } - else if (command == "next") { - if (Booru.responses.length > 0) { - const lastResponse = Booru.responses[Booru.responses.length - 1] - root.handleInput(lastResponse.tags.join(" ") + ` ${parseInt(lastResponse.page) + 1}`); - } + const commandObj = root.allCommands.find(cmd => cmd.name === `${command}`); + if (commandObj) { + commandObj.execute(args); + } else { + root.addSystemMessage(qsTr("Unknown command: ") + command); } } else if (inputText.trim() == "+") { @@ -132,8 +164,9 @@ Item { Behavior on contentY { NumberAnimation { id: scrollAnim - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.scroll.duration + easing.type: Appearance.animation.scroll.type + easing.bezierCurve: Appearance.animation.scroll.bezierCurve } } @@ -164,8 +197,9 @@ Item { Behavior on opacity { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } @@ -217,8 +251,9 @@ Item { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } @@ -278,8 +313,9 @@ Item { Behavior on implicitHeight { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } @@ -324,6 +360,15 @@ Item { root.suggestionList = [] return } + if(tagInputField.text.startsWith(root.commandPrefix)) { + root.suggestionQuery = "" + root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(tagInputField.text.substring(1))).map(cmd => { + return { + name: `${root.commandPrefix}${cmd.name}`, + } + }) + return + } searchTimer.restart(); } @@ -383,8 +428,9 @@ Item { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } @@ -409,13 +455,13 @@ Item { anchors.rightMargin: 5 spacing: 5 - property var commands: [ + property var commandsShown: [ { - name: "/mode", + name: "mode", sendDirectly: false, }, { - name: "/clear", + name: "clear", sendDirectly: true, }, ] @@ -493,10 +539,11 @@ Item { Repeater { // Command buttons id: commandRepeater - model: commandButtonsRow.commands + model: commandButtonsRow.commandsShown delegate: BooruTagButton { id: tagButton - buttonText: modelData.name + property string commandRepresentation: `${root.commandPrefix}${modelData.name}` + buttonText: commandRepresentation background: Rectangle { radius: Appearance.rounding.small color: tagButton.down ? Appearance.colors.colLayer2Active : @@ -505,16 +552,17 @@ Item { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } onClicked: { if(modelData.sendDirectly) { - root.handleInput(modelData.name) + root.handleInput(commandRepresentation) } else { - tagInputField.text = modelData.name + " " + tagInputField.text = commandRepresentation + " " tagInputField.cursorPosition = tagInputField.text.length tagInputField.forceActiveFocus() } diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index d91bb95a9..38a20ed13 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -69,6 +69,8 @@ Button { source: modelData.preview_url width: root.rowHeight * modelData.aspect_ratio height: root.rowHeight + visible: opacity > 0 + opacity: status === Image.Ready ? 1 : 0 layer.enabled: true layer.effect: OpacityMask { @@ -78,6 +80,14 @@ Button { radius: Appearance.rounding.small } } + + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } } Button { @@ -111,7 +121,8 @@ Button { Rectangle { id: contextMenu - visible: root.showActions + opacity: root.showActions ? 1 : 0 + visible: opacity > 0 radius: Appearance.rounding.small color: Appearance.m3colors.m3surfaceContainer anchors.top: menuButton.bottom @@ -120,6 +131,14 @@ Button { implicitHeight: contextMenuColumnLayout.implicitHeight + radius * 2 implicitWidth: contextMenuColumnLayout.implicitWidth + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } + ColumnLayout { id: contextMenuColumnLayout anchors.centerIn: parent @@ -159,5 +178,25 @@ Button { } } + DropShadow { + opacity: root.showActions ? 1 : 0 + visible: opacity > 0 + anchors.fill: contextMenu + source: contextMenu + radius: Appearance.sizes.elevationMargin + samples: radius * 2 + 1 + color: Appearance.colors.colShadow + verticalOffset: 2 + horizontalOffset: 0 + + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } + } + } } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index 5156b796e..c43837503 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -105,14 +105,16 @@ Rectangle { Behavior on height { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } Behavior on implicitHeight { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index bb8fc9036..1c94bbb50 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -23,8 +23,9 @@ Rectangle { Behavior on implicitHeight { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } @@ -42,7 +43,7 @@ Rectangle { Timer { id: collapseCleanFadeTimer - interval: Appearance.animation.elementDecel.duration / 2 + interval: Appearance.animation.elementMove.duration / 2 repeat: false onTriggered: { if(collapsed) collapsedBottomWidgetGroupRow.opacity = 1 @@ -68,8 +69,9 @@ Rectangle { visible: opacity > 0 Behavior on opacity { NumberAnimation { - duration: Appearance.animation.elementDecel.duration / 2 - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration / 2 + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } @@ -107,8 +109,9 @@ Rectangle { visible: opacity > 0 Behavior on opacity { NumberAnimation { - duration: Appearance.animation.elementDecel.duration / 2 - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration / 2 + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } @@ -166,7 +169,7 @@ Rectangle { height: tabStack.children[0]?.tabLoader?.implicitHeight // TODO: make this less stupid Layout.alignment: Qt.AlignVCenter property int realIndex: 0 - property int animationDuration: Appearance.animation.elementDecelFast.duration * 1.5 + property int animationDuration: Appearance.animation.elementMoveFast.duration * 1.5 // Switch the tab on halfway of the anim duration Connections { diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml index aa523c75b..6977c6bc4 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml @@ -28,8 +28,9 @@ Button { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } @@ -46,8 +47,9 @@ Button { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml index 342652837..b0c394641 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml @@ -14,7 +14,7 @@ Button { implicitWidth: forceCircle ? implicitHeight : (contentItem.implicitWidth + 10 * 2) Behavior on implicitWidth { SmoothedAnimation { - velocity: Appearance.animation.elementDecel.velocity + velocity: Appearance.animation.elementMove.velocity } } @@ -27,8 +27,9 @@ Button { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml index a57c6d7ee..a2f7210df 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -143,8 +143,9 @@ Item { visible: opacity > 0 Behavior on opacity { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml index 39a1f4fe9..60ea795c8 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml @@ -13,7 +13,7 @@ Button { implicitWidth: contentRowLayout.implicitWidth + 10 * 2 Behavior on implicitWidth { SmoothedAnimation { - velocity: Appearance.animation.elementDecel.velocity + velocity: Appearance.animation.elementMove.velocity } } @@ -26,8 +26,9 @@ Button { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml b/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml index 07c02f46d..528e5b99f 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml @@ -24,8 +24,9 @@ Button { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } @@ -38,8 +39,9 @@ Button { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml index 7ad34a461..d8b9740c8 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -53,8 +53,9 @@ Item { Behavior on implicitHeight { enabled: enableHeightAnimation NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } @@ -66,7 +67,7 @@ Item { Timer { id: actionTimer - interval: Appearance.animation.elementDecelFast.duration + interval: Appearance.animation.elementMoveFast.duration repeat: false onTriggered: { if (todoItem.pendingDelete) { @@ -155,8 +156,9 @@ Item { Behavior on opacity { NumberAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml b/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml index 43c8626a0..d6ca97855 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml @@ -16,7 +16,7 @@ Button { Behavior on implicitWidth { SmoothedAnimation { - velocity: Appearance.animation.elementDecel.velocity + velocity: Appearance.animation.elementMove.velocity } } @@ -27,8 +27,9 @@ Button { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index ba6dd67f6..a22c977bd 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -174,12 +174,11 @@ Item { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } - } - } DropShadow { @@ -195,8 +194,9 @@ Item { Behavior on verticalOffset { NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } } @@ -217,8 +217,9 @@ Item { opacity: root.showAddDialog ? 1 : 0 Behavior on opacity { NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml index cdb89ed27..5ebdc179f 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml @@ -19,8 +19,9 @@ Button { Behavior on color { ColorAnimation { - duration: Appearance.animation.elementDecel.duration - easing.type: Appearance.animation.elementDecel.type + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml index 3884e8cfb..c3a6f3382 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml @@ -139,8 +139,9 @@ Item { opacity: root.showDeviceSelector ? 1 : 0 Behavior on opacity { NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } @@ -262,20 +263,23 @@ Item { Behavior on opacity { NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } Behavior on width { NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } Behavior on height { NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } @@ -292,20 +296,23 @@ Item { Behavior on opacity { NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } Behavior on width { NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } Behavior on height { NumberAnimation { - duration: Appearance.animation.elementDecelFast.duration - easing.type: Appearance.animation.elementDecelFast.type + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } } diff --git a/.config/quickshell/services/AppSearch.qml b/.config/quickshell/services/AppSearch.qml index 9c15a0ad9..41f447d26 100644 --- a/.config/quickshell/services/AppSearch.qml +++ b/.config/quickshell/services/AppSearch.qml @@ -7,9 +7,10 @@ import Quickshell.Io Singleton { id: root - readonly property list list: DesktopEntries.applications.values.filter(a => !a.noDisplay).sort((a, b) => a.name.localeCompare(b.name)) + readonly property list list: Array.from(DesktopEntries.applications.values) + .sort((a, b) => a.name.localeCompare(b.name)) readonly property list preppedNames: list.map(a => ({ - name: Fuzzy.prepare(a.name), + name: Fuzzy.prepare(`${a.name} `), entry: a })) @@ -17,6 +18,8 @@ Singleton { return Fuzzy.go(search, preppedNames, { all: true, key: "name" - }).map(r => r.obj.entry); + }).map(r => { + return r.obj.entry + }); } } From a2722478ba1afc0ac78d2a379dee7c8da72146b6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 3 May 2025 18:43:07 +0200 Subject: [PATCH 150/824] booru: add api provider suggestions --- .../quickshell/modules/sidebarLeft/Anime.qml | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index c43a71a37..e00a684f0 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -2,6 +2,7 @@ import "root:/" import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/fuzzysort.js" as Fuzzy import "./anime/" import Qt.labs.platform import QtQuick @@ -119,6 +120,13 @@ Item { } } + Connections { + target: panelWindow + function onVisibleChanged(visible) { + tagInputField.forceActiveFocus() + } + } + Keys.onPressed: (event) => { tagInputField.forceActiveFocus() if (event.modifiers === Qt.NoModifier) { @@ -346,7 +354,6 @@ Item { repeat: false onTriggered: { const inputText = tagInputField.text - if (inputText.length === 0 || inputText.startsWith(root.commandPrefix)) return; const words = inputText.trim().split(/\s+/); if (words.length > 0) { Booru.triggerTagSearch(words[words.length - 1]); @@ -358,15 +365,37 @@ Item { if(tagInputField.text.length === 0) { root.suggestionQuery = "" root.suggestionList = [] + searchTimer.stop(); + return + } + if(tagInputField.text.startsWith(`${root.commandPrefix}mode`)) { + root.suggestionQuery = tagInputField.text.split(" ")[1] ?? "" + const providerResults = Fuzzy.go(root.suggestionQuery, Booru.providerList.map(provider => { + return { + name: Fuzzy.prepare(provider), + obj: provider, + } + }), { + all: true, + key: "name" + }) + console.log(JSON.stringify(providerResults)) + root.suggestionList = providerResults.map(provider => { + return { + name: `${tagInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "mode ") : ""}${provider.target}`, + } + }) + searchTimer.stop(); return } if(tagInputField.text.startsWith(root.commandPrefix)) { - root.suggestionQuery = "" + root.suggestionQuery = tagInputField.text root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(tagInputField.text.substring(1))).map(cmd => { return { name: `${root.commandPrefix}${cmd.name}`, } }) + searchTimer.stop(); return } searchTimer.restart(); From bc8b01a6f62447a62a82add0841f4d3ebcf50419 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 3 May 2025 21:31:31 +0200 Subject: [PATCH 151/824] booru: show provider and command description --- .../modules/common/functions/string_utils.js | 5 + .../quickshell/modules/sidebarLeft/Anime.qml | 97 ++++++++++++++----- .config/quickshell/services/Booru.qml | 12 ++- 3 files changed, 89 insertions(+), 25 deletions(-) create mode 100644 .config/quickshell/modules/common/functions/string_utils.js diff --git a/.config/quickshell/modules/common/functions/string_utils.js b/.config/quickshell/modules/common/functions/string_utils.js new file mode 100644 index 000000000..82cae4e72 --- /dev/null +++ b/.config/quickshell/modules/common/functions/string_utils.js @@ -0,0 +1,5 @@ +function format(str, ...args) { + return str.replace(/{(\d+)}/g, (match, index) => + typeof args[index] !== 'undefined' ? args[index] : match + ); +} diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index e00a684f0..c092b5315 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -3,6 +3,7 @@ import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" import "root:/modules/common/functions/fuzzysort.js" as Fuzzy +import "root:/modules/common/functions/string_utils.js" as StringUtils import "./anime/" import Qt.labs.platform import QtQuick @@ -55,36 +56,29 @@ Item { } }, { - name: "nsfw", - description: qsTr("Toggle NSFW mode"), + name: "next", + description: qsTr("Get the next page of results"), execute: () => { - ConfigOptions.sidebar.booru.allowNsfw = !ConfigOptions.sidebar.booru.allowNsfw; + if (Booru.responses.length > 0) { + const lastResponse = Booru.responses[Booru.responses.length - 1]; + root.handleInput(`${lastResponse.tags.join(" ")} ${parseInt(lastResponse.page) + 1}`); + } } }, { name: "safe", - description: qsTr("Set NSFW mode to false"), + description: qsTr("Disable NSFW content"), execute: () => { ConfigOptions.sidebar.booru.allowNsfw = false; } }, { name: "lewd", - description: qsTr("Set NSFW mode to true"), + description: qsTr("Allow NSFW content"), execute: () => { ConfigOptions.sidebar.booru.allowNsfw = true; } }, - { - name: "next", - description: qsTr("Get the next page of results"), - execute: () => { - if (Booru.responses.length > 0) { - const lastResponse = Booru.responses[Booru.responses.length - 1]; - root.handleInput(lastResponse.tags.join(" ") + ` ${parseInt(lastResponse.page) + 1}`); - } - } - } ] function handleInput(inputText) { @@ -233,10 +227,47 @@ Item { } } + Item { // Tag suggestion description + opacity: tagDescriptionText.text.length > 0 ? 1 : 0 + visible: opacity > 0 + Layout.fillWidth: true + implicitHeight: tagDescriptionBackground.implicitHeight + + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } + + Rectangle { + id: tagDescriptionBackground + color: Appearance.colors.colTooltip + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + implicitHeight: tagDescriptionText.implicitHeight + 5 * 2 + radius: Appearance.rounding.verysmall + + StyledText { + id: tagDescriptionText + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 10 + anchors.rightMargin: 10 + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.colors.colOnTooltip + wrapMode: Text.Wrap + text: root.suggestionList[tagSuggestions.selectedIndex]?.description ?? "" + } + } + } + Flow { // Tag suggestions id: tagSuggestions - visible: root.suggestionList.length > 0 && - tagInputField.text.length > 0 + visible: root.suggestionList.length > 0 && tagInputField.text.length > 0 property int selectedIndex: 0 Layout.fillWidth: true spacing: 5 @@ -249,7 +280,7 @@ Item { } delegate: BooruTagButton { id: tagButton - // buttonText: `${modelData.name}_{${modelData.count}}` + background: Rectangle { radius: Appearance.rounding.small color: tagSuggestions.selectedIndex === index ? Appearance.colors.colLayer2Hover : @@ -270,7 +301,7 @@ Item { StyledText { font.pixelSize: Appearance.font.pixelSize.small color: Appearance.m3colors.m3onSurface - text: modelData.name + text: modelData.displayName ?? modelData.name } StyledText { visible: modelData.count !== undefined @@ -279,6 +310,12 @@ Item { text: modelData.count ?? "" } } + + onHoveredChanged: { + if (tagButton.hovered) { + tagSuggestions.selectedIndex = index; + } + } onClicked: { tagSuggestions.acceptTag(modelData.name) } @@ -344,7 +381,7 @@ Item { renderType: Text.NativeRendering selectedTextColor: Appearance.m3colors.m3onPrimary selectionColor: Appearance.m3colors.m3primary - placeholderText: qsTr("Enter tags") + placeholderText: StringUtils.format(qsTr('Enter tags, or "{0}" for commands'), root.commandPrefix) placeholderTextColor: Appearance.m3colors.m3outline background: Item {} @@ -379,10 +416,11 @@ Item { all: true, key: "name" }) - console.log(JSON.stringify(providerResults)) root.suggestionList = providerResults.map(provider => { return { name: `${tagInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "mode ") : ""}${provider.target}`, + displayName: `${Booru.providers[provider.target].name}`, + description: `${Booru.providers[provider.target].description}`, } }) searchTimer.stop(); @@ -393,6 +431,7 @@ Item { root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(tagInputField.text.substring(1))).map(cmd => { return { name: `${root.commandPrefix}${cmd.name}`, + description: `${cmd.description}`, } }) searchTimer.stop(); @@ -517,6 +556,7 @@ Item { } StyledToolTip { id: toolTip + extraVisibleCondition: false alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered content: qsTr("The current API used. Endpoint: ") + Booru.providers[Booru.currentProvider].url + qsTr("\nSet with /mode PROVIDER") } @@ -534,7 +574,7 @@ Item { text: "•" } - Rectangle { + Rectangle { // NSFW toggle implicitWidth: switchesRow.implicitWidth RowLayout { @@ -542,13 +582,22 @@ Item { spacing: 5 anchors.centerIn: parent + MouseArea { + anchors.fill: parent + hoverEnabled: true + PointingHandInteraction {} + onClicked: { + nsfwSwitch.checked = !nsfwSwitch.checked + } + } + StyledText { Layout.fillHeight: true Layout.leftMargin: 10 Layout.alignment: Qt.AlignVCenter font.pixelSize: Appearance.font.pixelSize.smaller - color: Appearance.colors.colOnLayer1 - text: qsTr("NSFW") + color: nsfwSwitch.enabled ? Appearance.colors.colOnLayer1 : Appearance.m3colors.m3outline + text: qsTr("Allow NSFW") } StyledSwitch { id: nsfwSwitch diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index 2d8518abd..9246d6633 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -35,6 +35,7 @@ Singleton { "name": "yande.re", "url": "https://yande.re", "api": "https://yande.re/post.json", + "description": "All-rounder | Good quality, decent quantity", "mapFunc": (response) => { return response.map(item => { return { @@ -68,6 +69,7 @@ Singleton { "name": "Konachan", "url": "https://konachan.com", "api": "https://konachan.com/post.json", + "description": "For desktop wallpapers | Good quality", "mapFunc": (response) => { return response.map(item => { return { @@ -101,6 +103,7 @@ Singleton { "name": "Zerochan", "url": "https://www.zerochan.net", "api": "https://www.zerochan.net/?json", + "description": "Clean stuff | Excellent quality, no NSFW", "mapFunc": (response) => { response = response.items return response.map(item => { @@ -127,6 +130,7 @@ Singleton { "name": "Danbooru", "url": "https://danbooru.donmai.us", "api": "https://danbooru.donmai.us/posts.json", + "description": "The popular one | Best quantity, but quality can vary wildly", "mapFunc": (response) => { return response.map(item => { return { @@ -161,6 +165,7 @@ Singleton { "name": "Gelbooru", "url": "https://gelbooru.com", "api": "https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1", + "description": "The hentai one | Great quantity, a lot of NSFW, quality varies wildly", "mapFunc": (response) => { response = response.post return response.map(item => { @@ -195,6 +200,7 @@ Singleton { "name": "waifu.im", "url": "https://waifu.im", "api": "https://api.waifu.im/search", + "description": "Waifus only | Excellent quality, limited quantity", "mapFunc": (response) => { response = response.images return response.map(item => { @@ -226,6 +232,7 @@ Singleton { property var currentProvider: ConfigOptions.sidebar.booru.defaultProvider function setProvider(provider) { + provider = provider.toLowerCase() if (providerList.indexOf(provider) !== -1) { currentProvider = provider root.addSystemMessage(qsTr("Provider set to ") + providers[provider].name @@ -254,7 +261,10 @@ Singleton { var baseUrl = provider.api var tagString = tags.join(" ") if (!nsfw && !(["zerochan", "waifu.im"].includes(currentProvider))) { - tagString += " rating:safe" + if (currentProvider == "gelbooru") + tagString += " rating:general"; + else + tagString += " rating:safe"; } var params = [] // Tags & limit From ceac2931ab8f19fd94ccc5f355e5ffca0f115676 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 3 May 2025 21:31:43 +0200 Subject: [PATCH 152/824] fancier tooltips --- .../quickshell/modules/common/Appearance.qml | 4 +- .../modules/common/widgets/StyledSlider.qml | 14 +-- .../modules/common/widgets/StyledToolTip.qml | 99 +++++++++++-------- 3 files changed, 65 insertions(+), 52 deletions(-) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index cf3e49f8d..c1029c3ee 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -134,8 +134,8 @@ Singleton { property color colSecondaryContainerActive: mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54) property color colSurfaceContainerHighestHover: mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.95) property color colSurfaceContainerHighestActive: mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.85) - property color colTooltip: m3colors.m3inverseSurface - property color colOnTooltip: m3colors.m3inverseOnSurface + property color colTooltip: "#3C4043" // m3colors.m3inverseSurface in the specs, but the m3 website actually uses this color + property color colOnTooltip: "#F8F9FA" // m3colors.m3inverseOnSurface in the specs, but the m3 website actually uses this color property color colScrim: transparentize(m3colors.m3scrim, 0.5) property color colShadow: transparentize(m3colors.m3shadow, 0.75) } diff --git a/.config/quickshell/modules/common/widgets/StyledSlider.qml b/.config/quickshell/modules/common/widgets/StyledSlider.qml index f800e2a3d..b39a9a419 100644 --- a/.config/quickshell/modules/common/widgets/StyledSlider.qml +++ b/.config/quickshell/modules/common/widgets/StyledSlider.qml @@ -24,15 +24,15 @@ Slider { Behavior on value { // This makes the adjusted value (like volume) shift smoothly SmoothedAnimation { - velocity: Appearance.animation.elementMove.velocity + velocity: Appearance.animation.elementMoveFast.velocity } } Behavior on handleMargins { NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } @@ -93,9 +93,9 @@ Slider { Behavior on implicitWidth { NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index 5b37b2611..a6dcb12ef 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -5,55 +5,68 @@ import QtQuick.Controls import QtQuick.Layouts ToolTip { + id: root property string content property bool extraVisibleCondition: true property bool alternativeVisibleCondition: false - property bool internalVisibleCondition: false - padding: 5 + property bool internalVisibleCondition: { + const ans = (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition + return ans + } + verticalPadding: 5 + horizontalPadding: 10 - visible: ((extraVisibleCondition && (parent.hovered === undefined || parent?.hovered) && internalVisibleCondition)) || alternativeVisibleCondition + opacity: internalVisibleCondition ? 1 : 0 + visible: opacity > 0 - Connections { - target: parent - function onHoveredChanged() { - if (parent.hovered) { - tooltipShowDelay.restart() - } else { - internalVisibleCondition = false + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } + + background: Item {} + + contentItem: Item { + id: contentItemBackground + implicitWidth: tooltipTextObject.width + 2 * root.horizontalPadding + implicitHeight: tooltipTextObject.height + 2 * root.verticalPadding + + Rectangle { + id: backgroundRectangle + anchors.bottom: contentItemBackground.bottom + anchors.horizontalCenter: contentItemBackground.horizontalCenter + color: Appearance.colors.colTooltip + radius: Appearance.rounding.verysmall + width: internalVisibleCondition ? (tooltipTextObject.width + 2 * padding) : 0 + height: internalVisibleCondition ? (tooltipTextObject.height + 2 * padding) : 0 + clip: true + + Behavior on width { + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } } - } - } - - Timer { - id: tooltipShowDelay - interval: 200 - repeat: false - running: false - onTriggered: { - internalVisibleCondition = true - } - } - - background: Rectangle { - color: Appearance.colors.colTooltip - radius: Appearance.rounding.small - implicitWidth: tooltipTextObject.width + 2 * padding - implicitHeight: tooltipTextObject.height + 2 * padding - Behavior on opacity { - NumberAnimation { - target: opacity - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + Behavior on height { + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } } - } - opacity: visible ? 1 : 0 - } - StyledText { - id: tooltipTextObject - text: content - font.pixelSize: Appearance.font.pixelSize.small - color: Appearance.colors.colOnTooltip - wrapMode: Text.Wrap + + StyledText { + id: tooltipTextObject + anchors.centerIn: parent + text: content + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.colors.colOnTooltip + wrapMode: Text.Wrap + } + } } } \ No newline at end of file From 3404eacf4b44d7a2e9be62c34d12b12abf3c8a55 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 3 May 2025 21:40:13 +0200 Subject: [PATCH 153/824] remove extra space --- .config/quickshell/modules/common/widgets/StyledToolTip.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index a6dcb12ef..a0e7ced7e 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -14,8 +14,7 @@ ToolTip { return ans } verticalPadding: 5 - horizontalPadding: 10 - + horizontalPadding: 10 opacity: internalVisibleCondition ? 1 : 0 visible: opacity > 0 From dc0a15e63b4c538aeddec0714a7290fc472489ff Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 4 May 2025 00:05:04 +0200 Subject: [PATCH 154/824] booru: small refractor --- .../quickshell/modules/sidebarLeft/Anime.qml | 23 +++++++++++-------- .config/quickshell/services/Booru.qml | 16 +++++++------ .../quickshell/services/BooruResponseData.qml | 13 +++++++++++ 3 files changed, 35 insertions(+), 17 deletions(-) create mode 100644 .config/quickshell/services/BooruResponseData.qml diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index c092b5315..06fbeacbd 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -18,6 +18,7 @@ Item { id: root property var panelWindow property var inputField: tagInputField + readonly property list responses: Booru.responses property string previewDownloadPath: `${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/media/waifus`.replace("file://", "") property string downloadPath: (StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + "/homework").replace("file://", "") property string nsfwPath: (StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + "/homework/🌶️").replace("file://", "") @@ -59,8 +60,8 @@ Item { name: "next", description: qsTr("Get the next page of results"), execute: () => { - if (Booru.responses.length > 0) { - const lastResponse = Booru.responses[Booru.responses.length - 1]; + if (root.responses.length > 0) { + const lastResponse = root.responses[root.responses.length - 1]; root.handleInput(`${lastResponse.tags.join(" ")} ${parseInt(lastResponse.page) + 1}`); } } @@ -94,8 +95,8 @@ Item { } } else if (inputText.trim() == "+") { - if (Booru.responses.length > 0) { - const lastResponse = Booru.responses[Booru.responses.length - 1] + if (root.responses.length > 0) { + const lastResponse = root.responses[root.responses.length - 1] root.handleInput(lastResponse.tags.join(" ") + ` ${parseInt(lastResponse.page) + 1}`); } } @@ -175,12 +176,12 @@ Item { spacing: 10 model: ScriptModel { values: { - if(Booru.responses.length > booruResponseListView.lastResponseLength) { - if (booruResponseListView.lastResponseLength > 0 && Booru.responses[booruResponseListView.lastResponseLength].provider != "system") + if(root.responses.length > booruResponseListView.lastResponseLength) { + if (booruResponseListView.lastResponseLength > 0 && root.responses[booruResponseListView.lastResponseLength].provider != "system") booruResponseListView.contentY = booruResponseListView.contentY + root.scrollOnNewResponse - booruResponseListView.lastResponseLength = Booru.responses.length + booruResponseListView.lastResponseLength = root.responses.length } - return Booru.responses + return root.responses } } delegate: BooruResponse { @@ -193,7 +194,7 @@ Item { } Item { // Placeholder when list is empty - opacity: Booru.responses.length === 0 ? 1 : 0 + opacity: root.responses.length === 0 ? 1 : 0 visible: opacity > 0 anchors.fill: parent @@ -558,7 +559,9 @@ Item { id: toolTip extraVisibleCondition: false alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered - content: qsTr("The current API used. Endpoint: ") + Booru.providers[Booru.currentProvider].url + qsTr("\nSet with /mode PROVIDER") + // content: qsTr("The current API used. Endpoint: ") + Booru.providers[Booru.currentProvider].url + qsTr("\nSet with /mode PROVIDER") + content: StringUtils.format(qsTr("Current API endpoint: {0}\nSet it with {1}mode PROVIDER"), + Booru.providers[Booru.currentProvider].url, root.commandPrefix) } MouseArea { diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index 9246d6633..c87c0f86d 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -9,6 +9,8 @@ import QtQuick; Singleton { id: root + property Component booruResponseDataComponent: BooruResponseData {} + signal tagSuggestion(string query, var suggestions) Connections { @@ -19,7 +21,7 @@ Singleton { } property string failMessage: qsTr("That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number") - property var responses: [] + property list responses: [] property var getWorkingImageSource: (url) => { if (url.includes('pximg.net')) { return `https://www.pixiv.net/en/artworks/${url.substring(url.lastIndexOf('/') + 1).replace(/_p\d+\.(png|jpg|jpeg|gif)$/, '')}`; @@ -247,13 +249,13 @@ Singleton { } function addSystemMessage(message) { - responses = [...responses, { + responses = [...responses, root.booruResponseDataComponent.createObject(null, { "provider": "system", "tags": [], "page": -1, "images": [], "message": `${message}` - }] + })] } function constructRequestUrl(tags, nsfw=true, limit=20, page=1) { @@ -315,23 +317,23 @@ Singleton { var response = JSON.parse(xhr.responseText) response = providers[currentProvider].mapFunc(response) // console.log("[Booru] Mapped response: " + JSON.stringify(response)) - root.responses = [...root.responses, { + root.responses = [...root.responses, root.booruResponseDataComponent.createObject(null, { "provider": currentProvider, "tags": tags, "page": page, "images": response, "message": response.length > 0 ? "" : root.failMessage - }] + })] } catch (e) { console.log("[Booru] Failed to parse response: " + e) - root.responses = [...root.responses, { + root.responses = [...root.responses, root.responseDataComponent.createObject(null, { "provider": currentProvider, "tags": tags, "page": page, "images": [], "message": root.failMessage - }] + })] } } else if (xhr.readyState === XMLHttpRequest.DONE) { diff --git a/.config/quickshell/services/BooruResponseData.qml b/.config/quickshell/services/BooruResponseData.qml new file mode 100644 index 000000000..775da2568 --- /dev/null +++ b/.config/quickshell/services/BooruResponseData.qml @@ -0,0 +1,13 @@ +import "root:/modules/common" +import Quickshell; +import Quickshell.Io; +import Qt.labs.platform +import QtQuick; + +QtObject { + property string provider + property list tags + property var page + property list images + property string message +} From 41e82f06937a6b358d11e0f73eefdf5eb0a2ef39 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 4 May 2025 00:11:36 +0200 Subject: [PATCH 155/824] booru: instant feedback on enter --- .../quickshell/modules/sidebarLeft/Anime.qml | 2 +- .config/quickshell/services/AppSearch.qml | 2 +- .config/quickshell/services/Booru.qml | 28 +++++++++---------- .../quickshell/services/BooruResponseData.qml | 7 ++--- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 06fbeacbd..f126dd3b6 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -18,7 +18,7 @@ Item { id: root property var panelWindow property var inputField: tagInputField - readonly property list responses: Booru.responses + readonly property var responses: Booru.responses property string previewDownloadPath: `${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/media/waifus`.replace("file://", "") property string downloadPath: (StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + "/homework").replace("file://", "") property string nsfwPath: (StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + "/homework/🌶️").replace("file://", "") diff --git a/.config/quickshell/services/AppSearch.qml b/.config/quickshell/services/AppSearch.qml index 41f447d26..1ecaa886c 100644 --- a/.config/quickshell/services/AppSearch.qml +++ b/.config/quickshell/services/AppSearch.qml @@ -9,7 +9,7 @@ Singleton { readonly property list list: Array.from(DesktopEntries.applications.values) .sort((a, b) => a.name.localeCompare(b.name)) - readonly property list preppedNames: list.map(a => ({ + readonly property var preppedNames: list.map(a => ({ name: Fuzzy.prepare(`${a.name} `), entry: a })) diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index c87c0f86d..3762d6793 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -21,7 +21,7 @@ Singleton { } property string failMessage: qsTr("That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number") - property list responses: [] + property var responses: [] property var getWorkingImageSource: (url) => { if (url.includes('pximg.net')) { return `https://www.pixiv.net/en/artworks/${url.substring(url.lastIndexOf('/') + 1).replace(/_p\d+\.(png|jpg|jpeg|gif)$/, '')}`; @@ -308,6 +308,15 @@ Singleton { var url = constructRequestUrl(tags, nsfw, limit, page) // console.log("[Booru] Making request to " + url) + const newResponse = root.booruResponseDataComponent.createObject(null, { + "provider": currentProvider, + "tags": tags, + "page": page, + "images": [], + "message": "" + }) + root.responses = [...root.responses, newResponse] + var xhr = new XMLHttpRequest() xhr.open("GET", url) xhr.onreadystatechange = function() { @@ -317,23 +326,12 @@ Singleton { var response = JSON.parse(xhr.responseText) response = providers[currentProvider].mapFunc(response) // console.log("[Booru] Mapped response: " + JSON.stringify(response)) - root.responses = [...root.responses, root.booruResponseDataComponent.createObject(null, { - "provider": currentProvider, - "tags": tags, - "page": page, - "images": response, - "message": response.length > 0 ? "" : root.failMessage - })] + newResponse.images = response + newResponse.message = response.length > 0 ? "" : root.failMessage } catch (e) { console.log("[Booru] Failed to parse response: " + e) - root.responses = [...root.responses, root.responseDataComponent.createObject(null, { - "provider": currentProvider, - "tags": tags, - "page": page, - "images": [], - "message": root.failMessage - })] + newResponse.message = root.failMessage } } else if (xhr.readyState === XMLHttpRequest.DONE) { diff --git a/.config/quickshell/services/BooruResponseData.qml b/.config/quickshell/services/BooruResponseData.qml index 775da2568..f0409a1ba 100644 --- a/.config/quickshell/services/BooruResponseData.qml +++ b/.config/quickshell/services/BooruResponseData.qml @@ -1,13 +1,10 @@ import "root:/modules/common" -import Quickshell; -import Quickshell.Io; -import Qt.labs.platform import QtQuick; QtObject { property string provider - property list tags + property var tags property var page - property list images + property var images property string message } From 1fe568150f4ebe4193b5509fc5ce8a4e0b438b99 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 4 May 2025 00:45:20 +0200 Subject: [PATCH 156/824] booru: make immediate response nicer --- .../quickshell/modules/sidebarLeft/Anime.qml | 52 +++++++++++++++---- .config/quickshell/services/Booru.qml | 6 ++- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index f126dd3b6..0911fed4b 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -226,22 +226,54 @@ Item { } } } + + Item { // Queries awaiting response + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 10 + implicitHeight: pendingBackground.implicitHeight + opacity: Booru.runningRequests > 0 ? 1 : 0 + visible: opacity > 0 + + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + } + } + + Rectangle { + id: pendingBackground + color: Appearance.m3colors.m3inverseSurface + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + implicitHeight: pendingText.implicitHeight + 12 * 2 + radius: Appearance.rounding.verysmall + + StyledText { + id: pendingText + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 12 + anchors.rightMargin: 12 + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.m3colors.m3inverseOnSurface + wrapMode: Text.Wrap + text: StringUtils.format(qsTr("{0} queries pending"), Booru.runningRequests) + } + } + } } Item { // Tag suggestion description - opacity: tagDescriptionText.text.length > 0 ? 1 : 0 - visible: opacity > 0 + visible: tagDescriptionText.text.length > 0 Layout.fillWidth: true implicitHeight: tagDescriptionBackground.implicitHeight - Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } - } - Rectangle { id: tagDescriptionBackground color: Appearance.colors.colTooltip diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index 3762d6793..2276b0945 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -22,6 +22,7 @@ Singleton { property string failMessage: qsTr("That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number") property var responses: [] + property int runningRequests: 0 property var getWorkingImageSource: (url) => { if (url.includes('pximg.net')) { return `https://www.pixiv.net/en/artworks/${url.substring(url.lastIndexOf('/') + 1).replace(/_p\d+\.(png|jpg|jpeg|gif)$/, '')}`; @@ -315,7 +316,6 @@ Singleton { "images": [], "message": "" }) - root.responses = [...root.responses, newResponse] var xhr = new XMLHttpRequest() xhr.open("GET", url) @@ -332,6 +332,9 @@ Singleton { } catch (e) { console.log("[Booru] Failed to parse response: " + e) newResponse.message = root.failMessage + } finally { + root.runningRequests--; + root.responses = [...root.responses, newResponse] } } else if (xhr.readyState === XMLHttpRequest.DONE) { @@ -348,6 +351,7 @@ Singleton { const userAgent = ConfigOptions.sidebar.booru.zerochan.username ? `Desktop sidebar booru viewer - ${ConfigOptions.sidebar.booru.zerochan.username}` : defaultUserAgent xhr.setRequestHeader("User-Agent", userAgent) } + root.runningRequests++; xhr.send() } catch (error) { console.log("Could not set User-Agent:", error) From 84ae535756a7823a533d6f281aa81bb86eec062c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 4 May 2025 20:44:37 +0200 Subject: [PATCH 157/824] booru: add domain name to go to source button --- .../quickshell/modules/common/functions/string_utils.js | 5 +++++ .../quickshell/modules/sidebarLeft/anime/BooruImage.qml | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/common/functions/string_utils.js b/.config/quickshell/modules/common/functions/string_utils.js index 82cae4e72..c3884e5d9 100644 --- a/.config/quickshell/modules/common/functions/string_utils.js +++ b/.config/quickshell/modules/common/functions/string_utils.js @@ -3,3 +3,8 @@ function format(str, ...args) { typeof args[index] !== 'undefined' ? args[index] : match ); } + +function getDomain(url) { + const match = url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/); + return match ? match[1] : null; +} diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index 38a20ed13..6c3b00b51 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -1,6 +1,7 @@ import "root:/" import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/string_utils.js" as StringUtils import Qt.labs.platform import QtQuick import QtQuick.Controls @@ -147,17 +148,17 @@ Button { MenuButton { id: openFileLinkButton Layout.fillWidth: true - buttonText: "Open file link" + buttonText: qsTr("Open file link") onClicked: { root.showActions = false - // Hyprland.dispatch("global quickshell:sidebarLeftClose") Hyprland.dispatch(`exec xdg-open '${root.imageData.file_url}'`) } } MenuButton { id: sourceButton + visible: root.imageData.source && root.imageData.source.length > 0 Layout.fillWidth: true - buttonText: "Go to source" + buttonText: StringUtils.format(qsTr("Go to source ({0})"), StringUtils.getDomain(root.imageData.source)) enabled: root.imageData.source && root.imageData.source.length > 0 onClicked: { root.showActions = false @@ -171,7 +172,6 @@ Button { buttonText: "Download" onClicked: { root.showActions = false - // Hyprland.dispatch("global quickshell:sidebarLeftClose") Hyprland.dispatch(`exec curl '${root.imageData.file_url}' -o '${root.imageData.is_nsfw ? root.nsfwPath : root.downloadPath}/${root.fileName}' && notify-send '${qsTr("Download complete")}' '${root.downloadPath}/${root.fileName}'`) } } From e02875890b497a5be471d29263efd0477921332f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 01:10:46 +0200 Subject: [PATCH 158/824] booru: allow provider description to be translated --- .config/quickshell/services/Booru.qml | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index 2276b0945..e8fa44cf2 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -23,13 +23,6 @@ Singleton { property string failMessage: qsTr("That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number") property var responses: [] property int runningRequests: 0 - property var getWorkingImageSource: (url) => { - if (url.includes('pximg.net')) { - return `https://www.pixiv.net/en/artworks/${url.substring(url.lastIndexOf('/') + 1).replace(/_p\d+\.(png|jpg|jpeg|gif)$/, '')}`; - } - return url; - } - property var defaultUserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" property var providerList: ["yandere", "konachan", "zerochan", "danbooru", "gelbooru", "waifu.im"] property var providers: { @@ -38,7 +31,7 @@ Singleton { "name": "yande.re", "url": "https://yande.re", "api": "https://yande.re/post.json", - "description": "All-rounder | Good quality, decent quantity", + "description": qsTr("All-rounder | Good quality, decent quantity"), "mapFunc": (response) => { return response.map(item => { return { @@ -72,7 +65,7 @@ Singleton { "name": "Konachan", "url": "https://konachan.com", "api": "https://konachan.com/post.json", - "description": "For desktop wallpapers | Good quality", + "description": qsTr("For desktop wallpapers | Good quality"), "mapFunc": (response) => { return response.map(item => { return { @@ -106,7 +99,7 @@ Singleton { "name": "Zerochan", "url": "https://www.zerochan.net", "api": "https://www.zerochan.net/?json", - "description": "Clean stuff | Excellent quality, no NSFW", + "description": qsTr("Clean stuff | Excellent quality, no NSFW"), "mapFunc": (response) => { response = response.items return response.map(item => { @@ -133,7 +126,7 @@ Singleton { "name": "Danbooru", "url": "https://danbooru.donmai.us", "api": "https://danbooru.donmai.us/posts.json", - "description": "The popular one | Best quantity, but quality can vary wildly", + "description": qsTr("The popular one | Best quantity, but quality can vary wildly"), "mapFunc": (response) => { return response.map(item => { return { @@ -168,7 +161,7 @@ Singleton { "name": "Gelbooru", "url": "https://gelbooru.com", "api": "https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1", - "description": "The hentai one | Great quantity, a lot of NSFW, quality varies wildly", + "description": qsTr("The hentai one | Great quantity, a lot of NSFW, quality varies wildly"), "mapFunc": (response) => { response = response.post return response.map(item => { @@ -203,7 +196,7 @@ Singleton { "name": "waifu.im", "url": "https://waifu.im", "api": "https://api.waifu.im/search", - "description": "Waifus only | Excellent quality, limited quantity", + "description": qsTr("Waifus only | Excellent quality, limited quantity"), "mapFunc": (response) => { response = response.images return response.map(item => { @@ -231,8 +224,14 @@ Singleton { } }, } - property var currentProvider: ConfigOptions.sidebar.booru.defaultProvider + + function getWorkingImageSource(url) { + if (url.includes('pximg.net')) { + return `https://www.pixiv.net/en/artworks/${url.substring(url.lastIndexOf('/') + 1).replace(/_p\d+\.(png|jpg|jpeg|gif)$/, '')}`; + } + return url; + } function setProvider(provider) { provider = provider.toLowerCase() From 94ef226b92423e5494b9e7920571200248b05499 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 01:13:41 +0200 Subject: [PATCH 159/824] ai chat --- .../quickshell/modules/sidebarLeft/AiChat.qml | 373 ++++++++++++++++++ .../quickshell/modules/sidebarLeft/Anime.qml | 21 +- ...ooruTagButton.qml => ApiCommandButton.qml} | 0 .../modules/sidebarLeft/SidebarLeft.qml | 5 +- .../modules/sidebarLeft/aiChat/AiMessage.qml | 87 ++++ .../sidebarLeft/anime/BooruResponse.qml | 7 +- .config/quickshell/services/Ai.qml | 136 +++++++ .config/quickshell/services/AiMessageData.qml | 10 + 8 files changed, 620 insertions(+), 19 deletions(-) create mode 100644 .config/quickshell/modules/sidebarLeft/AiChat.qml rename .config/quickshell/modules/sidebarLeft/{anime/BooruTagButton.qml => ApiCommandButton.qml} (100%) create mode 100644 .config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml create mode 100644 .config/quickshell/services/Ai.qml create mode 100644 .config/quickshell/services/AiMessageData.qml diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml new file mode 100644 index 000000000..33f3bac06 --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -0,0 +1,373 @@ +import "root:/" +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "./aiChat/" +import "root:/modules/common/functions/string_utils.js" as StringUtils +import Qt.labs.platform +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import Quickshell.Io +import Quickshell +import Quickshell.Hyprland + +Item { + id: root + property var panelWindow + property var inputField: messageInputField + readonly property var messages: Ai.messages + property string commandPrefix: "/" + property real scrollOnNewResponse: 60 + + Connections { + target: panelWindow + function onVisibleChanged(visible) { + messageInputField.forceActiveFocus() + } + } + onFocusChanged: (focus) => { + if (focus) { + messageInputField.forceActiveFocus() + } + } + + Keys.onPressed: (event) => { + messageInputField.forceActiveFocus() + if (event.modifiers === Qt.NoModifier) { + if (event.key === Qt.Key_PageUp) { + messageListView.contentY = Math.max(0, messageListView.contentY - messageListView.height / 2) + event.accepted = true + } else if (event.key === Qt.Key_PageDown) { + messageListView.contentY = Math.min(messageListView.contentHeight - messageListView.height / 2, messageListView.contentY + messageListView.height / 2) + event.accepted = true + } + } + } + + property var allCommands: [ + { + name: "clear", + description: qsTr("Clear chat history"), + execute: () => { + Ai.clearMessages(); + } + }, + { + name: "model", + description: qsTr("Choose model"), + execute: (args) => { + Ai.setModel(args[0]); + } + }, + ] + + function handleInput(inputText) { + if (inputText.startsWith(root.commandPrefix)) { + // Handle special commands + const command = inputText.split(" ")[0].substring(1); + const args = inputText.split(" ").slice(1); + const commandObj = root.allCommands.find(cmd => cmd.name === `${command}`); + if (commandObj) { + commandObj.execute(args); + } else { + Ai.addMessage(qsTr("Unknown command: ") + command, "interface"); + } + } + else { + Ai.sendUserMessage(inputText); + } + } + + ColumnLayout { + id: columnLayout + anchors.fill: parent + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + ListView { // Messages + id: messageListView + anchors.fill: parent + + property int lastResponseLength: 0 + + clip: true + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: swipeView.width + height: swipeView.height + radius: Appearance.rounding.small + } + } + + Behavior on contentY { + NumberAnimation { + id: scrollAnim + duration: Appearance.animation.scroll.duration + easing.type: Appearance.animation.scroll.type + easing.bezierCurve: Appearance.animation.scroll.bezierCurve + } + } + + spacing: 10 + model: ScriptModel { + values: { + if(root.messages.length > messageListView.lastResponseLength) { + if (messageListView.lastResponseLength > 0 && root.messages[messageListView.lastResponseLength].provider != "system") + messageListView.contentY = messageListView.contentY + root.scrollOnNewResponse + messageListView.lastResponseLength = root.messages.length + } + return root.messages + } + // values: root.messages + } + delegate: AiMessage { + messageData: modelData + messageInputField: root.inputField + } + } + + Item { // Placeholder when list is empty + opacity: root.messages.length === 0 ? 1 : 0 + visible: opacity > 0 + anchors.fill: parent + + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + } + } + + ColumnLayout { + anchors.centerIn: parent + spacing: 5 + + MaterialSymbol { + Layout.alignment: Qt.AlignHCenter + font.pixelSize: 55 + color: Appearance.m3colors.m3outline + text: "neurology" + } + StyledText { + id: widgetNameText + Layout.alignment: Qt.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3outline + horizontalAlignment: Text.AlignHCenter + text: qsTr("Large language models") + } + } + } + } + + Rectangle { // Tag input area + id: tagInputContainer + property real columnSpacing: 5 + Layout.fillWidth: true + radius: Appearance.rounding.small + color: Appearance.colors.colLayer1 + implicitWidth: messageInputField.implicitWidth + implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin + + commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45) + clip: true + border.color: Appearance.m3colors.m3outlineVariant + border.width: 1 + + Behavior on implicitHeight { + NumberAnimation { + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + } + } + + RowLayout { // Input field and send button + id: inputFieldRowLayout + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 5 + spacing: 0 + + TextArea { // The actual TextArea + id: messageInputField + wrapMode: TextArea.Wrap + Layout.fillWidth: true + padding: 10 + color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant + renderType: Text.NativeRendering + selectedTextColor: Appearance.m3colors.m3onPrimary + selectionColor: Appearance.m3colors.m3primary + placeholderText: StringUtils.format(qsTr('Message the model... "{0}" for commands'), root.commandPrefix) + placeholderTextColor: Appearance.m3colors.m3outline + + background: Item {} + + function accept() { + root.handleInput(text) + text = "" + } + + Keys.onPressed: (event) => { + if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { + if (event.modifiers & Qt.ShiftModifier) { + // Insert newline + messageInputField.insert(messageInputField.cursorPosition, "\n") + event.accepted = true + } else { // Accept text + const inputText = messageInputField.text + root.handleInput(inputText) + messageInputField.clear() + event.accepted = true + } + } + } + } + + Button { // Send button + id: sendButton + Layout.alignment: Qt.AlignTop + Layout.rightMargin: 5 + implicitWidth: 40 + implicitHeight: 40 + enabled: messageInputField.text.length > 0 + + MouseArea { + anchors.fill: parent + cursorShape: sendButton.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: { + const inputText = messageInputField.text + root.handleInput(inputText) + messageInputField.clear() + } + } + + background: Rectangle { + radius: Appearance.rounding.small + color: sendButton.enabled ? (sendButton.down ? Appearance.colors.colPrimaryActive : + sendButton.hovered ? Appearance.colors.colPrimaryHover : + Appearance.m3colors.m3primary) : Appearance.colors.colLayer2Disabled + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + } + } + } + + contentItem: MaterialSymbol { + anchors.centerIn: parent + text: "send" + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.larger + color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled + } + } + } + + RowLayout { // Controls + id: commandButtonsRow + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: 5 + anchors.leftMargin: 5 + anchors.rightMargin: 5 + spacing: 5 + + property var commandsShown: [ + { + name: "model", + sendDirectly: false, + }, + { + name: "clear", + sendDirectly: true, + }, + ] + + Item { + implicitHeight: providerRowLayout.implicitHeight + 5 * 2 + implicitWidth: providerRowLayout.implicitWidth + 10 * 2 + + RowLayout { + id: providerRowLayout + anchors.centerIn: parent + + MaterialSymbol { + text: "api" + font.pixelSize: Appearance.font.pixelSize.large + } + StyledText { + id: providerName + font.pixelSize: Appearance.font.pixelSize.small + font.weight: Font.DemiBold + color: Appearance.m3colors.m3onSurface + elide: Text.ElideRight + text: Ai.models[Ai.currentModel].name + } + } + StyledToolTip { + id: toolTip + extraVisibleCondition: false + alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered + // content: qsTr("The current API used. Endpoint: ") + Booru.providers[Booru.currentProvider].url + qsTr("\nSet with /mode PROVIDER") + content: StringUtils.format(qsTr("Current model: {0}\nSet it with {1}model MODEL"), + Ai.models[Ai.currentModel].name, root.commandPrefix) + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + } + } + + Item { Layout.fillWidth: true } + + Repeater { // Command buttons + id: commandRepeater + model: commandButtonsRow.commandsShown + delegate: ApiCommandButton { + id: tagButton + property string commandRepresentation: `${root.commandPrefix}${modelData.name}` + buttonText: commandRepresentation + background: Rectangle { + radius: Appearance.rounding.small + color: tagButton.down ? Appearance.colors.colLayer2Active : + tagButton.hovered ? Appearance.colors.colLayer2Hover : + Appearance.colors.colLayer2 + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + } + } + } + onClicked: { + if(modelData.sendDirectly) { + root.handleInput(commandRepresentation) + } else { + messageInputField.text = commandRepresentation + " " + messageInputField.cursorPosition = messageInputField.text.length + messageInputField.forceActiveFocus() + } + } + } + } + } + + } + + } + +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 0911fed4b..40550b158 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -91,7 +91,7 @@ Item { if (commandObj) { commandObj.execute(args); } else { - root.addSystemMessage(qsTr("Unknown command: ") + command); + Booru.addSystemMessage(qsTr("Unknown command: ") + command); } } else if (inputText.trim() == "+") { @@ -121,6 +121,11 @@ Item { tagInputField.forceActiveFocus() } } + onFocusChanged: (focus) => { + if (focus) { + tagInputField.forceActiveFocus() + } + } Keys.onPressed: (event) => { tagInputField.forceActiveFocus() @@ -135,11 +140,6 @@ Item { } } - onFocusChanged: (focus) => { - if (focus) { - tagInputField.forceActiveFocus() - } - } ColumnLayout { id: columnLayout @@ -311,7 +311,7 @@ Item { tagSuggestions.selectedIndex = 0 return root.suggestionList.slice(0, 10) } - delegate: BooruTagButton { + delegate: ApiCommandButton { id: tagButton background: Rectangle { @@ -419,7 +419,7 @@ Item { background: Item {} - property Timer searchTimer: Timer { + property Timer searchTimer: Timer { // Timer for tag suggestions interval: root.tagSuggestionDelay repeat: false onTriggered: { @@ -431,7 +431,7 @@ Item { } } - onTextChanged: { + onTextChanged: { // Handle tag suggestions if(tagInputField.text.length === 0) { root.suggestionQuery = "" root.suggestionList = [] @@ -618,7 +618,6 @@ Item { anchors.centerIn: parent MouseArea { - anchors.fill: parent hoverEnabled: true PointingHandInteraction {} onClicked: { @@ -653,7 +652,7 @@ Item { Repeater { // Command buttons id: commandRepeater model: commandButtonsRow.commandsShown - delegate: BooruTagButton { + delegate: ApiCommandButton { id: tagButton property string commandRepresentation: `${root.commandPrefix}${modelData.name}` buttonText: commandRepresentation diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruTagButton.qml b/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml similarity index 100% rename from .config/quickshell/modules/sidebarLeft/anime/BooruTagButton.qml rename to .config/quickshell/modules/sidebarLeft/ApiCommandButton.qml diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index 3c02db96c..717196b53 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -140,9 +140,8 @@ Scope { // Scope } } - StyledText { - text: "To be implemented" - horizontalAlignment: Text.AlignHCenter + AiChat { + panelWindow: sidebarRoot } Anime { panelWindow: sidebarRoot diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml new file mode 100644 index 000000000..57ae0a8a2 --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -0,0 +1,87 @@ +import "root:/" +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "../" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io +import Quickshell +import Quickshell.Widgets +import Quickshell.Wayland +import Quickshell.Hyprland +import Qt5Compat.GraphicalEffects + +Rectangle { + id: root + property var messageData + property var messageInputField + + property real availableWidth: parent.width ?? 0 + property real messagePadding: 7 + property real contentSpacing: 3 + + anchors.left: parent?.left + anchors.right: parent?.right + implicitHeight: columnLayout.implicitHeight + root.messagePadding * 2 + + radius: Appearance.rounding.normal + color: Appearance.colors.colLayer1 + + ColumnLayout { + id: columnLayout + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: messagePadding + spacing: root.contentSpacing + + RowLayout { // Header + Rectangle { // Name + id: nameWrapper + color: Appearance.m3colors.m3secondaryContainer + radius: Appearance.rounding.small + implicitWidth: providerName.implicitWidth + 10 * 2 + implicitHeight: Math.max(providerName.implicitHeight + 5 * 2, 30) + Layout.alignment: Qt.AlignVCenter + + StyledText { + id: providerName + anchors.centerIn: parent + font.pixelSize: Appearance.font.pixelSize.large + font.weight: Font.DemiBold + color: Appearance.m3colors.m3onSecondaryContainer + text: messageData.role == 'assistant' ? Ai.models[messageData.model].name : + messageData.role == 'user' ? "User" : + "System" + } + } + } + + StyledText { // Message + id: messageText + Layout.fillWidth: true + Layout.margins: messagePadding + + // font.family: Appearance.font.family.reading + font.pixelSize: Appearance.font.pixelSize.small + wrapMode: Text.WordWrap + color: Appearance.colors.colOnLayer1 + textFormat: Text.MarkdownText + text: root.messageData.content + + onLinkActivated: (link) => { + Qt.openUrlExternally(link) + Hyprland.dispatch("global quickshell:sidebarLeftClose") + } + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton // Only for hover + hoverEnabled: true + cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index c43837503..7355fff11 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -2,6 +2,7 @@ import "root:/" import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" +import "../" import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -21,10 +22,6 @@ Rectangle { property string downloadPath property string nsfwPath - onResponseDataChanged: { - console.log("Response data changed:", responseData) - } - property real availableWidth: parent.width ?? 0 property real rowTooShortThreshold: 185 property real imageSpacing: 5 @@ -126,7 +123,7 @@ Rectangle { id: tagRepeater model: root.responseData.tags - BooruTagButton { + ApiCommandButton { Layout.fillWidth: false buttonText: modelData onClicked: { diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml new file mode 100644 index 000000000..d1a3596b7 --- /dev/null +++ b/.config/quickshell/services/Ai.qml @@ -0,0 +1,136 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common" +import Quickshell; +import Quickshell.Io; +import Qt.labs.platform +import QtQuick; + +Singleton { + id: root + + property Component aiMessageComponent: AiMessageData {} + property var messages: [] + property var modelList: ["ollama-llama-3.2", "gemini-2.0-flash"] + property var models: { // TODO: Auto-detect installed ollama models + "interface": { + "name": "System", + }, + "ollama-llama-3.2": { + "name": "Ollama - Llama 3.2", + "icon": "ollama-symbolic", + "description": "Ollama - Llama 3.2", + "endpoint": "http://localhost:11434/api/chat", + "model": "llama3.2", + }, + "gemini-2.0-flash": { + "name": "Gemini 2.0 Flash", + "icon": "gemini-symbolic", + "description": "Gemini 2.0 Flash", + "endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent", + "model": "gemini-2.0-flash", + "messageMapFunc": function (message) { + return { + "role": message.role, + "parts": [{text: message.content}], + } + }, + }, + } + property var currentModel: "ollama-llama-3.2" + + function addMessage(message, role) { + if (message.length === 0) return; + const aiMessage = aiMessageComponent.createObject(root, { + "role": role, + "content": message, + "thinking": false, + "done": true, + }); + root.messages = [...root.messages, aiMessage]; + } + + function setModel(model) { + if (!model) model = "" + model = model.toLowerCase() + if (modelList.indexOf(model) !== -1) { + currentModel = model + root.addMessage("Model set to " + models[model].name, "interface") + } else { + root.addMessage(qsTr("Invalid model. Supported: \n- ") + modelList.join("\n- "), "interface") + } + } + + function clearMessages() { + messages = []; + } + + Process { + id: requester + property var baseCommand: ["curl", "--no-buffer"] + property var message + + function makeRequest() { + const model = models[currentModel]; + + let endpoint = model.endpoint; + + // Build request data using OpenAI's format. If the model has a custom requestDataBuilder, use that instead. + let data = model.requestDataBuilder ? model.requestDataBuilder(root.messages.filter(message => (message.role != "interface"))) : { + "model": model.model, + "messages": root.messages.filter(message => (message.role != "interface")).map(message => { + return { // Remove unecessary properties + "role": message.role, + "content": message.content, + } + }), + } + + let requestHeaders = { + "Content-Type": "application/json", + // "Authorization": model.endpoint.startsWith("http") ? "Bearer " + model.apiKey : "", + } + + requester.message = root.aiMessageComponent.createObject(root, { + "role": "assistant", + "model": currentModel, + "content": "", + "thinking": true, + "done": false, + }); + root.messages = [...root.messages, requester.message]; + requester.command = baseCommand.concat([endpoint, "-d", JSON.stringify(data)]); + console.log("Request command: ", requester.command.join(" ")); + requester.running = true + } + + stdout: SplitParser { + onRead: data => { + // console.log("Received data: ", data); + if (data.length === 0) return; + const dataJson = JSON.parse(data); + if (requester.message.thinking) requester.message.thinking = false; + + requester.message.content += dataJson.message.content + + if (dataJson.done) requester.message.done = true; + } + } + } + + function sendUserMessage(message) { + if (message.length === 0) return; + + const userMessage = aiMessageComponent.createObject(root, { + "role": "user", + "content": message, + "thinking": false, + "done": true, + }); + root.messages = [...root.messages, userMessage]; + + requester.makeRequest(); + } + +} diff --git a/.config/quickshell/services/AiMessageData.qml b/.config/quickshell/services/AiMessageData.qml new file mode 100644 index 000000000..7bc5108e7 --- /dev/null +++ b/.config/quickshell/services/AiMessageData.qml @@ -0,0 +1,10 @@ +import "root:/modules/common" +import QtQuick; + +QtObject { + property string role + property string content + property string model + property bool thinking: true + property bool done: false +} From 352d389cc4a2a18f088b8699a1d5f4f431909bc1 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 11:05:58 +0200 Subject: [PATCH 160/824] material symbols: support filling --- .config/quickshell/modules/bar/Bar.qml | 6 ++--- .config/quickshell/modules/bar/Battery.qml | 4 ++-- .config/quickshell/modules/bar/Media.qml | 2 +- .config/quickshell/modules/bar/Resource.qml | 2 +- .../quickshell/modules/bar/UtilButtons.qml | 4 ++-- .../modules/common/widgets/CustomIcon.qml | 1 + .../modules/common/widgets/MaterialSymbol.qml | 20 +++++++++++++++- .../modules/common/widgets/NavRailButton.qml | 5 ++-- .../common/widgets/NotificationWidget.qml | 8 +++---- .../common/widgets/PrimaryTabButton.qml | 3 ++- .../common/widgets/SecondaryTabButton.qml | 3 ++- .../onScreenDisplay/OsdValueIndicator.qml | 2 +- .../modules/overview/SearchItem.qml | 5 ++-- .../modules/overview/SearchWidget.qml | 8 +++---- .../modules/session/SessionActionButton.qml | 2 +- .../quickshell/modules/sidebarLeft/Anime.qml | 23 ++++++++++--------- .../modules/sidebarLeft/anime/BooruImage.qml | 2 +- .../sidebarLeft/anime/BooruResponse.qml | 2 +- .../sidebarRight/BottomWidgetGroup.qml | 4 ++-- .../sidebarRight/calendar/CalendarWidget.qml | 4 ++-- .../notifications/NotificationList.qml | 2 +- .../NotificationStatusButton.qml | 2 +- .../sidebarRight/quickToggles/NightLight.qml | 4 ++-- .../quickToggles/QuickToggleButton.qml | 3 ++- .../modules/sidebarRight/todo/TaskList.qml | 6 ++--- .../modules/sidebarRight/todo/TodoWidget.qml | 2 +- .../volumeMixer/AudioDeviceSelectorButton.qml | 2 +- .../sidebarRight/volumeMixer/VolumeMixer.qml | 2 +- .config/quickshell/services/Ai.qml | 4 ++-- .config/quickshell/services/SystemInfo.qml | 12 ++++++++++ 30 files changed, 91 insertions(+), 58 deletions(-) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index b4b35e87c..1e14d60f8 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -75,8 +75,8 @@ Scope { height: 19.5 source: ConfigOptions.bar.topLeftIcon == 'distro' ? SystemInfo.distroIcon : "spark-symbolic" - } + ColorOverlay { anchors.fill: distroIcon source: distroIcon @@ -173,12 +173,12 @@ Scope { Network.networkStrength > 20 ? "network_wifi_1_bar" : "signal_wifi_0_bar" ) : "signal_wifi_off" - font.pixelSize: Appearance.font.pixelSize.larger + iconSize: Appearance.font.pixelSize.larger color: Appearance.colors.colOnLayer0 } MaterialSymbol { text: Bluetooth.bluetoothConnected ? "bluetooth_connected" : Bluetooth.bluetoothEnabled ? "bluetooth" : "bluetooth_disabled" - font.pixelSize: Appearance.font.pixelSize.larger + iconSize: Appearance.font.pixelSize.larger color: Appearance.colors.colOnLayer0 } } diff --git a/.config/quickshell/modules/bar/Battery.qml b/.config/quickshell/modules/bar/Battery.qml index b4142dd1c..acfd3a5de 100644 --- a/.config/quickshell/modules/bar/Battery.qml +++ b/.config/quickshell/modules/bar/Battery.qml @@ -56,7 +56,7 @@ Rectangle { MaterialSymbol { anchors.centerIn: parent text: "battery_full" - font.pixelSize: Appearance.font.pixelSize.normal + iconSize: Appearance.font.pixelSize.normal color: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer } @@ -70,7 +70,7 @@ Rectangle { anchors.left: rowLayout.left anchors.verticalCenter: rowLayout.verticalCenter text: "bolt" - font.pixelSize: Appearance.font.pixelSize.large + iconSize: Appearance.font.pixelSize.large color: Appearance.m3colors.m3onSecondaryContainer visible: opacity > 0 // Only show when charging opacity: isCharging ? 1 : 0 // Keep opacity for visibility diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml index 910bf4c6b..ad6722806 100644 --- a/.config/quickshell/modules/bar/Media.qml +++ b/.config/quickshell/modules/bar/Media.qml @@ -63,7 +63,7 @@ Item { MaterialSymbol { anchors.centerIn: parent text: activePlayer?.isPlaying ? "pause" : "play_arrow" - font.pixelSize: Appearance.font.pixelSize.normal + iconSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3onSecondaryContainer } diff --git a/.config/quickshell/modules/bar/Resource.qml b/.config/quickshell/modules/bar/Resource.qml index 40c2aff5d..c13a3e0df 100644 --- a/.config/quickshell/modules/bar/Resource.qml +++ b/.config/quickshell/modules/bar/Resource.qml @@ -29,7 +29,7 @@ Item { MaterialSymbol { anchors.centerIn: parent text: iconName - font.pixelSize: Appearance.font.pixelSize.normal + iconSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3onSecondaryContainer } diff --git a/.config/quickshell/modules/bar/UtilButtons.qml b/.config/quickshell/modules/bar/UtilButtons.qml index 168e3e030..24d773157 100644 --- a/.config/quickshell/modules/bar/UtilButtons.qml +++ b/.config/quickshell/modules/bar/UtilButtons.qml @@ -26,7 +26,7 @@ Rectangle { MaterialSymbol { anchors.centerIn: parent text: "screenshot_region" - font.pixelSize: Appearance.font.pixelSize.normal + iconSize: Appearance.font.pixelSize.normal color: Appearance.colors.colOnLayer2 } @@ -39,7 +39,7 @@ Rectangle { MaterialSymbol { anchors.centerIn: parent text: "colorize" - font.pixelSize: Appearance.font.pixelSize.normal + iconSize: Appearance.font.pixelSize.normal color: Appearance.colors.colOnLayer2 } diff --git a/.config/quickshell/modules/common/widgets/CustomIcon.qml b/.config/quickshell/modules/common/widgets/CustomIcon.qml index 25f5af00a..8905b0715 100644 --- a/.config/quickshell/modules/common/widgets/CustomIcon.qml +++ b/.config/quickshell/modules/common/widgets/CustomIcon.qml @@ -19,5 +19,6 @@ Item { } return root.source } + implicitSize: root.height } } diff --git a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml index 324d77208..9952c7b51 100644 --- a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml +++ b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml @@ -3,9 +3,27 @@ import QtQuick import QtQuick.Layouts Text { + id: root + property real iconSize: Appearance.font.pixelSize.small + property real fill: 0 renderType: Text.NativeRendering verticalAlignment: Text.AlignVCenter font.family: Appearance.font.family.iconMaterial - font.pixelSize: Appearance.font.pixelSize.small + font.pixelSize: iconSize color: Appearance.m3colors.m3onBackground + + Behavior on fill { + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } + + font.variableAxes: { + "FILL": fill, + // "wght": font.weight, + // "GRAD": 0, + "opsz": iconSize, + } } diff --git a/.config/quickshell/modules/common/widgets/NavRailButton.qml b/.config/quickshell/modules/common/widgets/NavRailButton.qml index ed7b1f8f2..13bf2f949 100644 --- a/.config/quickshell/modules/common/widgets/NavRailButton.qml +++ b/.config/quickshell/modules/common/widgets/NavRailButton.qml @@ -25,7 +25,7 @@ Button { spacing: 5 Rectangle { width: 62 - implicitHeight: navRailButtonIcon.height + 2*2 + implicitHeight: navRailButtonIcon.height + 2 * 2 Layout.alignment: Qt.AlignHCenter radius: Appearance.rounding.full color: toggled ? @@ -42,7 +42,8 @@ Button { MaterialSymbol { id: navRailButtonIcon anchors.centerIn: parent - font.pixelSize: Appearance.font.pixelSize.hugeass + iconSize: Appearance.font.pixelSize.hugeass + fill: toggled ? 1 : 0 text: buttonIcon color: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1 diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index c9df0c186..772aca0ae 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -280,7 +280,7 @@ Item { color: (notificationObject.urgency == NotificationUrgency.Critical) ? Appearance.mix(Appearance.m3colors.m3onSecondary, Appearance.m3colors.m3onSecondaryContainer, 0.1) : Appearance.m3colors.m3onSecondaryContainer - font.pixelSize: 27 + iconSize: 27 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } @@ -419,7 +419,7 @@ Item { text: "keyboard_arrow_down" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - font.pixelSize: Appearance.font.pixelSize.normal + iconSize: Appearance.font.pixelSize.normal color: Appearance.colors.colOnLayer2 rotation: expanded ? 180 : 0 Behavior on rotation { @@ -554,7 +554,7 @@ Item { contentItem: MaterialSymbol { id: copyIcon - font.pixelSize: Appearance.font.pixelSize.large + iconSize: Appearance.font.pixelSize.large horizontalAlignment: Text.AlignHCenter color: (notificationObject.urgency == NotificationUrgency.Critical) ? Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface @@ -574,7 +574,7 @@ Item { } contentItem: MaterialSymbol { - font.pixelSize: Appearance.font.pixelSize.large + iconSize: Appearance.font.pixelSize.large horizontalAlignment: Text.AlignHCenter color: (notificationObject.urgency == NotificationUrgency.Critical) ? Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml index beb61f607..e5993d8e0 100644 --- a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml +++ b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml @@ -40,7 +40,8 @@ TabButton { Layout.alignment: Qt.AlignHCenter horizontalAlignment: Text.AlignHCenter text: buttonIcon - font.pixelSize: 24 + iconSize: Appearance.font.pixelSize.hugeass + fill: selected ? 1 : 0 color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 Behavior on color { ColorAnimation { diff --git a/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml b/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml index 20f338579..3ca0fd3e3 100644 --- a/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml +++ b/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml @@ -40,7 +40,8 @@ TabButton { Layout.rightMargin: 5 verticalAlignment: Text.AlignVCenter text: buttonIcon - font.pixelSize: Appearance.font.pixelSize.huge + iconSize: Appearance.font.pixelSize.huge + fill: selected ? 1 : 0 color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 Behavior on color { ColorAnimation { diff --git a/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml b/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml index d79dd1e56..63b6609d6 100644 --- a/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml +++ b/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml @@ -40,7 +40,7 @@ Item { Layout.bottomMargin: valueIndicatorVerticalPadding color: Appearance.colors.colOnLayer0 text: root.icon - font.pixelSize: 30 + iconSize: 30 } ColumnLayout { // Stuff Layout.alignment: Qt.AlignVCenter diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index d1f65952d..9da24176a 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -77,10 +77,9 @@ Button { MaterialSymbol { visible: root.materialSymbol != "" text: root.materialSymbol - font.pixelSize: 30 + iconSize: 30 + fill: (root.hovered || root.focus) ? 1 : 0 color: Appearance.m3colors.m3onSurface - // width: 35 - // height: 35 } // Main text diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 9bab9078c..43d78e4ef 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -90,13 +90,12 @@ Item { // Wrapper id: webSearch property list baseCommand: ["xdg-open"] function search(query) { - webSearch.running = false let url = ConfigOptions.search.engineBaseUrl + query for (let site of ConfigOptions.search.excludedSites) { url += ` -site:${site}`; } webSearch.command = baseCommand.concat(url) - webSearch.running = true + webSearch.startDetached() } } @@ -104,11 +103,10 @@ Item { // Wrapper id: executor property list baseCommand: ["bash", "-c"] function executeCommand(command) { - executor.running = false executor.command = baseCommand.concat( `${command} || ${ConfigOptions.apps.terminal} fish -C 'echo "${qsTr("Searching for package with that command")}..." && pacman -F ${command}'` ) - executor.running = true + executor.startDetached() } } @@ -197,7 +195,7 @@ Item { // Wrapper MaterialSymbol { id: searchIcon Layout.leftMargin: 15 - font.pixelSize: Appearance.font.pixelSize.huge + iconSize: Appearance.font.pixelSize.huge color: Appearance.m3colors.m3onSurface text: "search" } diff --git a/.config/quickshell/modules/session/SessionActionButton.qml b/.config/quickshell/modules/session/SessionActionButton.qml index 3e0d7ccea..b55c096b0 100644 --- a/.config/quickshell/modules/session/SessionActionButton.qml +++ b/.config/quickshell/modules/session/SessionActionButton.qml @@ -52,8 +52,8 @@ Button { anchors.fill: parent color: Appearance.colors.colOnLayer2 horizontalAlignment: Text.AlignHCenter + iconSize: 40 text: buttonIcon - font.pixelSize: 40 } StyledToolTip { diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 40550b158..e89ee5c8a 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -42,13 +42,6 @@ Item { } property var allCommands: [ - { - name: "clear", - description: qsTr("Clear the current list of images"), - execute: () => { - Booru.clearResponses(); - } - }, { name: "mode", description: qsTr("Set the current API provider"), @@ -56,6 +49,13 @@ Item { Booru.setProvider(args[0]); } }, + { + name: "clear", + description: qsTr("Clear the current list of images"), + execute: () => { + Booru.clearResponses(); + } + }, { name: "next", description: qsTr("Get the next page of results"), @@ -212,7 +212,7 @@ Item { MaterialSymbol { Layout.alignment: Qt.AlignHCenter - font.pixelSize: 55 + iconSize: 55 color: Appearance.m3colors.m3outline text: "bookmark_heart" } @@ -538,9 +538,10 @@ Item { contentItem: MaterialSymbol { anchors.centerIn: parent - text: "send" + text: "arrow_upward" horizontalAlignment: Text.AlignHCenter - font.pixelSize: Appearance.font.pixelSize.larger + iconSize: Appearance.font.pixelSize.larger + fill: sendButton.enabled ? 1 : 0 color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled } } @@ -577,7 +578,7 @@ Item { MaterialSymbol { text: "api" - font.pixelSize: Appearance.font.pixelSize.large + iconSize: Appearance.font.pixelSize.large } StyledText { id: providerName diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index 6c3b00b51..c2fa3833f 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -110,7 +110,7 @@ Button { contentItem: MaterialSymbol { horizontalAlignment: Text.AlignHCenter - font.pixelSize: Appearance.font.pixelSize.large + iconSize: Appearance.font.pixelSize.large color: Appearance.m3colors.m3onSurface text: "more_vert" } diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index 7355fff11..1df4bbb77 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -251,7 +251,7 @@ Rectangle { } MaterialSymbol { Layout.alignment: Text.AlignVCenter - font.pixelSize: Appearance.font.pixelSize.larger + iconSize: Appearance.font.pixelSize.larger color: Appearance.m3colors.m3onSurface text: "chevron_right" } diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index 1c94bbb50..1e551c4e3 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -86,7 +86,7 @@ Rectangle { } contentItem: MaterialSymbol { text: "keyboard_arrow_up" - font.pixelSize: Appearance.font.pixelSize.larger + iconSize: Appearance.font.pixelSize.larger horizontalAlignment: Text.AlignHCenter color: Appearance.colors.colOnLayer1 } @@ -155,7 +155,7 @@ Rectangle { } contentItem: MaterialSymbol { text: "keyboard_arrow_down" - font.pixelSize: Appearance.font.pixelSize.larger + iconSize: Appearance.font.pixelSize.larger horizontalAlignment: Text.AlignHCenter color: Appearance.colors.colOnLayer1 } diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml index eb4f62f58..659b5505a 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml @@ -64,7 +64,7 @@ Item { } contentItem: MaterialSymbol { text: "chevron_left" - font.pixelSize: Appearance.font.pixelSize.larger + iconSize: Appearance.font.pixelSize.larger horizontalAlignment: Text.AlignHCenter color: Appearance.colors.colOnLayer1 } @@ -76,7 +76,7 @@ Item { } contentItem: MaterialSymbol { text: "chevron_right" - font.pixelSize: Appearance.font.pixelSize.larger + iconSize: Appearance.font.pixelSize.larger horizontalAlignment: Text.AlignHCenter color: Appearance.colors.colOnLayer1 } diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml index a2f7210df..ff42a186e 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -112,7 +112,7 @@ Item { MaterialSymbol { Layout.alignment: Qt.AlignHCenter - font.pixelSize: 55 + iconSize: 55 color: Appearance.m3colors.m3outline text: "notifications_active" } diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml index 60ea795c8..0a1db2c43 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml @@ -42,7 +42,7 @@ Button { MaterialSymbol { text: buttonIcon Layout.fillWidth: false - font.pixelSize: Appearance.font.pixelSize.larger + iconSize: Appearance.font.pixelSize.larger color: Appearance.colors.colOnLayer1 } StyledText { diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/NightLight.qml b/.config/quickshell/modules/sidebarRight/quickToggles/NightLight.qml index 260b508a6..72df3e1ef 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/NightLight.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/NightLight.qml @@ -12,10 +12,10 @@ QuickToggleButton { onClicked: { nightLightButton.enabled = !nightLightButton.enabled if (enabled) { - nightLightOn.running = true + nightLightOn.startDetached() } else { - nightLightOff.running = true + nightLightOff.startDetached() } } Process { diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml b/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml index 528e5b99f..ac17f34b1 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml @@ -33,7 +33,8 @@ Button { MaterialSymbol { anchors.centerIn: parent - font.pixelSize: Appearance.font.pixelSize.larger + iconSize: Appearance.font.pixelSize.larger + fill: toggled ? 1 : 0 text: buttonIcon color: toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1 diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml index d8b9740c8..5d688fae4 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -118,7 +118,7 @@ Item { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter text: modelData.done ? "remove_done" : "check" - font.pixelSize: Appearance.font.pixelSize.larger + iconSize: Appearance.font.pixelSize.larger color: Appearance.colors.colOnLayer1 } } @@ -132,7 +132,7 @@ Item { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter text: "delete_forever" - font.pixelSize: Appearance.font.pixelSize.larger + iconSize: Appearance.font.pixelSize.larger color: Appearance.colors.colOnLayer1 } } @@ -168,7 +168,7 @@ Item { MaterialSymbol { Layout.alignment: Qt.AlignHCenter - font.pixelSize: 55 + iconSize: 55 color: Appearance.m3colors.m3outline text: emptyPlaceholderIcon } diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index a22c977bd..6606f9f49 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -204,7 +204,7 @@ Item { contentItem: MaterialSymbol { text: "add" horizontalAlignment: Text.AlignHCenter - font.pixelSize: Appearance.font.pixelSize.huge + iconSize: Appearance.font.pixelSize.huge color: Appearance.m3colors.m3onPrimaryContainer } } diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml index 5ebdc179f..14c4c3a37 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml @@ -39,7 +39,7 @@ Button { Layout.fillWidth: false Layout.leftMargin: 5 color: Appearance.colors.colOnLayer2 - font.pixelSize: Appearance.font.pixelSize.hugeass + iconSize: Appearance.font.pixelSize.hugeass text: input ? "mic_external_on" : "media_output" } diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml index c3a6f3382..e70ac18e8 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml @@ -98,7 +98,7 @@ Item { MaterialSymbol { Layout.alignment: Qt.AlignHCenter - font.pixelSize: 55 + iconSize: 55 color: Appearance.m3colors.m3outline text: "brand_awareness" } diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index d1a3596b7..f9589f564 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -20,14 +20,14 @@ Singleton { "ollama-llama-3.2": { "name": "Ollama - Llama 3.2", "icon": "ollama-symbolic", - "description": "Ollama - Llama 3.2", + "description": "Local Ollama model - Llama 3.2", "endpoint": "http://localhost:11434/api/chat", "model": "llama3.2", }, "gemini-2.0-flash": { "name": "Gemini 2.0 Flash", "icon": "gemini-symbolic", - "description": "Gemini 2.0 Flash", + "description": "Online Gemini 2.0 Flash", "endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent", "model": "gemini-2.0-flash", "messageMapFunc": function (message) { diff --git a/.config/quickshell/services/SystemInfo.qml b/.config/quickshell/services/SystemInfo.qml index 2572b42dc..23f798264 100644 --- a/.config/quickshell/services/SystemInfo.qml +++ b/.config/quickshell/services/SystemInfo.qml @@ -9,12 +9,14 @@ Singleton { property string distroName: "Unknown" property string distroId: "unknown" property string distroIcon: "linux-symbolic" + property string username: "user" Timer { interval: 1 running: true repeat: false onTriggered: { + getUsername.running = true fileOsRelease.reload() const textOsRelease = fileOsRelease.text() @@ -46,6 +48,16 @@ Singleton { } } + Process { + id: getUsername + command: ["whoami"] + stdout: SplitParser { + onRead: data => { + username = data.trim() + } + } + } + FileView { id: fileOsRelease path: "/etc/os-release" From 6e348311836156a9f0fa39993db31e7bb4e7624b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 11:06:52 +0200 Subject: [PATCH 161/824] ai chat: show message nicely, command suggestions --- .../quickshell/modules/sidebarLeft/AiChat.qml | 206 +++++++++++++++--- .../modules/sidebarLeft/aiChat/AiMessage.qml | 92 ++++++-- 2 files changed, 257 insertions(+), 41 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 33f3bac06..69643b461 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -3,6 +3,7 @@ import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" import "./aiChat/" +import "root:/modules/common/functions/fuzzysort.js" as Fuzzy import "root:/modules/common/functions/string_utils.js" as StringUtils import Qt.labs.platform import QtQuick @@ -19,7 +20,9 @@ Item { property var inputField: messageInputField readonly property var messages: Ai.messages property string commandPrefix: "/" - property real scrollOnNewResponse: 60 + + property var suggestionQuery: "" + property var suggestionList: [] Connections { target: panelWindow @@ -47,6 +50,13 @@ Item { } property var allCommands: [ + { + name: "model", + description: qsTr("Choose model"), + execute: (args) => { + Ai.setModel(args[0]); + } + }, { name: "clear", description: qsTr("Clear chat history"), @@ -55,10 +65,10 @@ Item { } }, { - name: "model", - description: qsTr("Choose model"), - execute: (args) => { - Ai.setModel(args[0]); + name: "test", + description: qsTr("Markdown test message"), + execute: () => { + Ai.addMessage("## ✏️ Markdown test\n- **Bold**, *Italic*, `Monospace`, [Link](https://example.com)\n", "interface"); } }, ] @@ -84,10 +94,10 @@ Item { id: columnLayout anchors.fill: parent - Item { + Item { // Messages Layout.fillWidth: true Layout.fillHeight: true - ListView { // Messages + ListView { // Message list id: messageListView anchors.fill: parent @@ -114,15 +124,7 @@ Item { spacing: 10 model: ScriptModel { - values: { - if(root.messages.length > messageListView.lastResponseLength) { - if (messageListView.lastResponseLength > 0 && root.messages[messageListView.lastResponseLength].provider != "system") - messageListView.contentY = messageListView.contentY + root.scrollOnNewResponse - messageListView.lastResponseLength = root.messages.length - } - return root.messages - } - // values: root.messages + values: root.messages } delegate: AiMessage { messageData: modelData @@ -149,7 +151,7 @@ Item { MaterialSymbol { Layout.alignment: Qt.AlignHCenter - font.pixelSize: 55 + iconSize: 55 color: Appearance.m3colors.m3outline text: "neurology" } @@ -165,8 +167,115 @@ Item { } } - Rectangle { // Tag input area - id: tagInputContainer + Item { // Suggestion description + visible: descriptionText.text.length > 0 + Layout.fillWidth: true + implicitHeight: descriptionBackground.implicitHeight + + Rectangle { + id: descriptionBackground + color: Appearance.colors.colTooltip + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + implicitHeight: descriptionText.implicitHeight + 5 * 2 + radius: Appearance.rounding.verysmall + + StyledText { + id: descriptionText + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 10 + anchors.rightMargin: 10 + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.colors.colOnTooltip + wrapMode: Text.Wrap + text: root.suggestionList[suggestions.selectedIndex]?.description ?? "" + } + } + } + + Flow { // Suggestions + id: suggestions + visible: root.suggestionList.length > 0 && messageInputField.text.length > 0 + property int selectedIndex: 0 + Layout.fillWidth: true + spacing: 5 + + Repeater { + id: suggestionRepeater + model: { + suggestions.selectedIndex = 0 + return root.suggestionList.slice(0, 10) + } + delegate: ApiCommandButton { + id: commandButton + + background: Rectangle { + radius: Appearance.rounding.small + color: suggestions.selectedIndex === index ? Appearance.colors.colLayer2Hover : + commandButton.down ? Appearance.colors.colLayer2Active : + commandButton.hovered ? Appearance.colors.colLayer2Hover : + Appearance.colors.colLayer2 + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + } + } + } + contentItem: RowLayout { + spacing: 5 + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.m3colors.m3onSurface + text: modelData.displayName ?? modelData.name + } + StyledText { + visible: modelData.count !== undefined + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.m3colors.m3outline + text: modelData.count ?? "" + } + } + + onHoveredChanged: { + if (commandButton.hovered) { + suggestions.selectedIndex = index; + } + } + onClicked: { + suggestions.acceptSuggestion(modelData.name) + } + } + } + + function acceptSuggestion(word) { + const words = messageInputField.text.trim().split(/\s+/); + if (words.length > 0) { + words[words.length - 1] = word; + } else { + words.push(word); + } + const updatedText = words.join(" ") + " "; + messageInputField.text = updatedText; + messageInputField.cursorPosition = messageInputField.text.length; + messageInputField.forceActiveFocus(); + } + + function acceptSelectedWord() { + if (suggestions.selectedIndex >= 0 && suggestions.selectedIndex < suggestionRepeater.count) { + const word = root.suggestionList[suggestions.selectedIndex].name; + suggestions.acceptSuggestion(word); + } + } + } + + Rectangle { // Input area + id: inputWrapper property real columnSpacing: 5 Layout.fillWidth: true radius: Appearance.rounding.small @@ -208,13 +317,56 @@ Item { background: Item {} + onTextChanged: { // Handle suggestions + if(messageInputField.text.length === 0) { + root.suggestionQuery = "" + root.suggestionList = [] + return + } else if(messageInputField.text.startsWith(`${root.commandPrefix}model`)) { + root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "" + const modelResults = Fuzzy.go(root.suggestionQuery, Ai.modelList.map(model => { + return { + name: Fuzzy.prepare(model), + obj: model, + } + }), { + all: true, + key: "name" + }) + root.suggestionList = modelResults.map(model => { + return { + name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "model ") : ""}${model.target}`, + displayName: `${Ai.models[model.target].name}`, + description: `${Ai.models[model.target].description}`, + } + }) + } else if(messageInputField.text.startsWith(root.commandPrefix)) { + root.suggestionQuery = messageInputField.text + root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(messageInputField.text.substring(1))).map(cmd => { + return { + name: `${root.commandPrefix}${cmd.name}`, + description: `${cmd.description}`, + } + }) + } + } + function accept() { root.handleInput(text) text = "" } Keys.onPressed: (event) => { - if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { + if (event.key === Qt.Key_Tab) { + suggestions.acceptSelectedWord(); + event.accepted = true; + } else if (event.key === Qt.Key_Up) { + suggestions.selectedIndex = Math.max(0, suggestions.selectedIndex - 1); + event.accepted = true; + } else if (event.key === Qt.Key_Down) { + suggestions.selectedIndex = Math.min(root.suggestionList.length - 1, suggestions.selectedIndex + 1); + event.accepted = true; + } else if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { if (event.modifiers & Qt.ShiftModifier) { // Insert newline messageInputField.insert(messageInputField.cursorPosition, "\n") @@ -264,10 +416,11 @@ Item { contentItem: MaterialSymbol { anchors.centerIn: parent - text: "send" horizontalAlignment: Text.AlignHCenter - font.pixelSize: Appearance.font.pixelSize.larger + iconSize: Appearance.font.pixelSize.larger + fill: sendButton.enabled ? 1 : 0 color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled + text: "arrow_upward" } } } @@ -303,7 +456,7 @@ Item { MaterialSymbol { text: "api" - font.pixelSize: Appearance.font.pixelSize.large + iconSize: Appearance.font.pixelSize.large } StyledText { id: providerName @@ -318,7 +471,6 @@ Item { id: toolTip extraVisibleCondition: false alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered - // content: qsTr("The current API used. Endpoint: ") + Booru.providers[Booru.currentProvider].url + qsTr("\nSet with /mode PROVIDER") content: StringUtils.format(qsTr("Current model: {0}\nSet it with {1}model MODEL"), Ai.models[Ai.currentModel].name, root.commandPrefix) } @@ -336,13 +488,13 @@ Item { id: commandRepeater model: commandButtonsRow.commandsShown delegate: ApiCommandButton { - id: tagButton + id: commandButton property string commandRepresentation: `${root.commandPrefix}${modelData.name}` buttonText: commandRepresentation background: Rectangle { radius: Appearance.rounding.small - color: tagButton.down ? Appearance.colors.colLayer2Active : - tagButton.hovered ? Appearance.colors.colLayer2Hover : + color: commandButton.down ? Appearance.colors.colLayer2Active : + commandButton.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 Behavior on color { diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index 57ae0a8a2..7ecbe42dc 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -43,19 +43,82 @@ Rectangle { id: nameWrapper color: Appearance.m3colors.m3secondaryContainer radius: Appearance.rounding.small - implicitWidth: providerName.implicitWidth + 10 * 2 - implicitHeight: Math.max(providerName.implicitHeight + 5 * 2, 30) + implicitWidth: nameRowLayout.implicitWidth + 10 * 2 + implicitHeight: Math.max(nameRowLayout.implicitHeight + 5 * 2, 30) Layout.alignment: Qt.AlignVCenter - StyledText { - id: providerName + RowLayout { + id: nameRowLayout anchors.centerIn: parent - font.pixelSize: Appearance.font.pixelSize.large - font.weight: Font.DemiBold - color: Appearance.m3colors.m3onSecondaryContainer - text: messageData.role == 'assistant' ? Ai.models[messageData.model].name : - messageData.role == 'user' ? "User" : - "System" + spacing: 10 + + Item { + Layout.alignment: Qt.AlignVCenter + Layout.fillHeight: true + implicitWidth: messageData.role == 'assistant' ? modelIcon.width : roleIcon.implicitWidth + implicitHeight: messageData.role == 'assistant' ? modelIcon.height : roleIcon.implicitHeight + + CustomIcon { + id: modelIcon + anchors.centerIn: parent + visible: messageData.role == 'assistant' && Ai.models[messageData.model].icon + width: Appearance.font.pixelSize.large + height: Appearance.font.pixelSize.large + source: messageData.role == 'assistant' ? Ai.models[messageData.model].icon : + messageData.role == 'user' ? 'linux-symbolic' : 'desktop-symbolic' + } + ColorOverlay { + visible: modelIcon.visible + anchors.fill: modelIcon + source: modelIcon + color: Appearance.m3colors.m3onSecondaryContainer + } + + MaterialSymbol { + id: roleIcon + anchors.centerIn: parent + visible: !modelIcon.visible + iconSize: Appearance.font.pixelSize.larger + color: Appearance.m3colors.m3onSecondaryContainer + text: messageData.role == 'user' ? 'person' : + messageData.role == 'interface' ? 'settings' : + messageData.role == 'assistant' ? 'neurology' : + 'computer' + } + } + + StyledText { + id: providerName + Layout.alignment: Qt.AlignVCenter + font.pixelSize: Appearance.font.pixelSize.large + font.weight: Font.DemiBold + color: Appearance.m3colors.m3onSecondaryContainer + text: messageData.role == 'assistant' ? Ai.models[messageData.model].name : + messageData.role == 'user' ? (SystemInfo.username ?? "User") : + "System" + } + } + } + + Item { Layout.fillWidth: true } + + Button { // Not visible to model + visible: messageData.role == 'interface' + implicitWidth: Math.max(notVisibleToModelText.implicitWidth + 10 * 2, 30) + implicitHeight: notVisibleToModelText.implicitHeight + 5 * 2 + Layout.alignment: Qt.AlignVCenter + + background: Item + + MaterialSymbol { + id: notVisibleToModelText + anchors.centerIn: parent + iconSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colSubtext + text: "visibility_off" + } + StyledToolTip { + content: qsTr("Not visible to model") } } } @@ -65,12 +128,12 @@ Rectangle { Layout.fillWidth: true Layout.margins: messagePadding - // font.family: Appearance.font.family.reading + font.family: Appearance.font.family.reading font.pixelSize: Appearance.font.pixelSize.small wrapMode: Text.WordWrap - color: Appearance.colors.colOnLayer1 + color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 textFormat: Text.MarkdownText - text: root.messageData.content + text: messageData.thinking ? qsTr("Waiting for response...") : root.messageData.content onLinkActivated: (link) => { Qt.openUrlExternally(link) @@ -84,4 +147,5 @@ Rectangle { } } } -} \ No newline at end of file +} + From 4285dce7304932533d62306dbe8fb4f7eb4b7ae2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 11:29:14 +0200 Subject: [PATCH 162/824] nicer(?) calendar collapse fade or at least more correct idk --- .config/quickshell/modules/common/Appearance.qml | 12 ++++++++++++ .../modules/sidebarRight/BottomWidgetGroup.qml | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index c1029c3ee..7a247bc9c 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -188,6 +188,18 @@ Singleton { property list bezierCurve: animationCurves.emphasized property int velocity: 650 } + property QtObject elementMoveEnter: QtObject { + property int duration: 400 + property int type: Easing.BezierSpline + property list bezierCurve: animationCurves.emphasizedDecel + property int velocity: 650 + } + property QtObject elementMoveExit: QtObject { + property int duration: 200 + property int type: Easing.BezierSpline + property list bezierCurve: animationCurves.emphasizedAccel + property int velocity: 650 + } property QtObject elementMoveFast: QtObject { property int duration: 200 property int type: Easing.BezierSpline diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index 1e551c4e3..d8b1a9208 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -36,8 +36,16 @@ Rectangle { function setCollapsed(state) { collapsed = state - if (collapsed) bottomWidgetGroupRow.opacity = 0 - else collapsedBottomWidgetGroupRow.opacity = 0 + if (collapsed) { + bottomWidgetGroupRowFade.easing.bezierCurve = Appearance.animation.elementMoveExit.bezierCurve + bottomWidgetGroupRowFade.easing.bezierCurve = Appearance.animation.elementMoveEnter.bezierCurve + bottomWidgetGroupRow.opacity = 0 + } + else { + bottomWidgetGroupRowFade.easing.bezierCurve = Appearance.animation.elementMoveExit.bezierCurve + bottomWidgetGroupRowFade.easing.bezierCurve = Appearance.animation.elementMoveEnter.bezierCurve + collapsedBottomWidgetGroupRow.opacity = 0 + } collapseCleanFadeTimer.start() } @@ -69,6 +77,7 @@ Rectangle { visible: opacity > 0 Behavior on opacity { NumberAnimation { + id: collapsedBottomWidgetGroupRowFade duration: Appearance.animation.elementMove.duration / 2 easing.type: Appearance.animation.elementMove.type easing.bezierCurve: Appearance.animation.elementMove.bezierCurve @@ -109,6 +118,7 @@ Rectangle { visible: opacity > 0 Behavior on opacity { NumberAnimation { + id: bottomWidgetGroupRowFade duration: Appearance.animation.elementMove.duration / 2 easing.type: Appearance.animation.elementMove.type easing.bezierCurve: Appearance.animation.elementMove.bezierCurve From 5506f6eae9f766b548c7796c34f888f6501967cc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 11:29:23 +0200 Subject: [PATCH 163/824] volume mixer: smaller icons --- .../modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml index ec4e6f6c2..eb671689e 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml @@ -5,6 +5,7 @@ import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Quickshell import Quickshell.Widgets import Quickshell.Services.Pipewire @@ -23,11 +24,11 @@ Item { Image { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter visible: source != "" - sourceSize.width: 50 - sourceSize.height: 50 + sourceSize.width: 38 + sourceSize.height: 38 source: { const icon = root.node.properties["application.icon-name"] ?? "audio-volume-high-symbolic"; - return `image://icon/${icon}`; + return Quickshell.iconPath(icon); } } From aa7df0a74c82702ff1f316d8cfab227d85ab62aa Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 11:34:01 +0200 Subject: [PATCH 164/824] fix console warnings for osds --- .../modules/onScreenDisplay/OnScreenDisplayBrightness.qml | 4 ++-- .../modules/onScreenDisplay/OnScreenDisplayVolume.qml | 6 +++--- .config/quickshell/services/Audio.qml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml index b0b7f466e..57a741e13 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml @@ -29,7 +29,7 @@ Scope { } Connections { - target: Brightness + target: Brightness ?? null function onValueChanged() { if (!Brightness.ready) return root.triggerOsd() @@ -37,7 +37,7 @@ Scope { } Connections { - target: Audio.sink?.audio + target: Audio.sink?.audio ?? null function onVolumeChanged() { if (!Audio.ready) return root.showOsdValues = false diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml index d2cce7bfe..4323afcda 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml @@ -29,7 +29,7 @@ Scope { } Connections { - target: Audio.sink?.audio + target: Audio.sink?.audio ?? null function onVolumeChanged() { if (!Audio.ready) return root.triggerOsd() @@ -37,7 +37,7 @@ Scope { } Connections { - target: Brightness + target: Brightness ?? null function onValueChanged() { if (!Brightness.ready) return root.showOsdValues = false @@ -96,7 +96,7 @@ Scope { OsdValueIndicator { id: osdValues anchors.centerIn: parent - value: Audio.sink?.audio.volume + value: Audio.sink?.audio.volume ?? 0 icon: "volume_up" name: qsTr("Volume") } diff --git a/.config/quickshell/services/Audio.qml b/.config/quickshell/services/Audio.qml index 5d97c3a3d..b6f0e17e7 100644 --- a/.config/quickshell/services/Audio.qml +++ b/.config/quickshell/services/Audio.qml @@ -7,7 +7,7 @@ pragma ComponentBehavior: Bound Singleton { id: root - property bool ready: Pipewire.defaultAudioSink?.ready + property bool ready: Pipewire.defaultAudioSink?.ready ?? false property var sink: Pipewire.defaultAudioSink property var source: Pipewire.defaultAudioSource From cba64710990a05ec8e4e59aee8c2f63d41725d2e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 11:47:22 +0200 Subject: [PATCH 165/824] nicer icon fill --- .config/quickshell/modules/common/widgets/MaterialSymbol.qml | 1 + .config/quickshell/modules/sidebarLeft/AiChat.qml | 4 ++-- .config/quickshell/modules/sidebarLeft/Anime.qml | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml index 9952c7b51..ff6b4f66e 100644 --- a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml +++ b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml @@ -7,6 +7,7 @@ Text { property real iconSize: Appearance.font.pixelSize.small property real fill: 0 renderType: Text.NativeRendering + font.hintingPreference: Font.PreferFullHinting verticalAlignment: Text.AlignVCenter font.family: Appearance.font.family.iconMaterial font.pixelSize: iconSize diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 69643b461..f57b05409 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -418,9 +418,9 @@ Item { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter iconSize: Appearance.font.pixelSize.larger - fill: sendButton.enabled ? 1 : 0 + // fill: sendButton.enabled ? 1 : 0 color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled - text: "arrow_upward" + text: "send" } } } diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index e89ee5c8a..f86801ba5 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -538,11 +538,11 @@ Item { contentItem: MaterialSymbol { anchors.centerIn: parent - text: "arrow_upward" horizontalAlignment: Text.AlignHCenter iconSize: Appearance.font.pixelSize.larger - fill: sendButton.enabled ? 1 : 0 + // fill: sendButton.enabled ? 1 : 0 color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled + text: "send" } } } From 0ae52eafdc6a734f80b56efd1bbeae9c659da15c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 11:52:57 +0200 Subject: [PATCH 166/824] nicer text --- .config/quickshell/modules/common/widgets/StyledText.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/modules/common/widgets/StyledText.qml b/.config/quickshell/modules/common/widgets/StyledText.qml index 14cec33b0..c5969b0b4 100644 --- a/.config/quickshell/modules/common/widgets/StyledText.qml +++ b/.config/quickshell/modules/common/widgets/StyledText.qml @@ -4,6 +4,7 @@ import QtQuick.Layouts Text { renderType: Text.NativeRendering + font.hintingPreference: Font.PreferFullHinting verticalAlignment: Text.AlignVCenter font.family: Appearance.font.family.main font.pixelSize: Appearance.font.pixelSize.small From 7469d8264fc29214fe643c8dd4dc707a27f569cf Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 12:23:35 +0200 Subject: [PATCH 167/824] sidebar resizing --- .../quickshell/modules/common/Appearance.qml | 1 + .../modules/common/widgets/StyledToolTip.qml | 1 + .../modules/sidebarLeft/SidebarLeft.qml | 29 ++++++++++++++++--- .../modules/sidebarLeft/aiChat/AiMessage.qml | 2 +- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 7a247bc9c..a02934968 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -228,6 +228,7 @@ Singleton { property int barCenterSideModuleWidth: 360 property int barPreferredSideSectionWidth: 400 property int sidebarWidth: 450 + property int sidebarWidthExtended: 700 property int notificationPopupWidth: 410 property int searchWidthCollapsed: 260 property int searchWidth: 450 diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index a0e7ced7e..838129351 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -63,6 +63,7 @@ ToolTip { anchors.centerIn: parent text: content font.pixelSize: Appearance.font.pixelSize.smaller + font.hintingPreference: Font.PreferNoHinting // Prevent shaky text color: Appearance.colors.colOnTooltip wrapMode: Text.Wrap } diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index 717196b53..b97943af7 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -14,7 +14,6 @@ import Quickshell.Hyprland Scope { // Scope id: root - property int sidebarWidth: Appearance.sizes.sidebarWidth property int sidebarPadding: 15 property var tabButtonList: [{"icon": "neurology", "name": qsTr("Intelligence")}, {"icon": "bookmark_heart", "name": qsTr("Anime")}] @@ -27,6 +26,8 @@ Scope { // Scope visible: false focusable: true property int currentTab: 0 + property bool extend: false + property real sidebarWidth: sidebarRoot.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth onVisibleChanged: { GlobalStates.sidebarLeftOpenCount += visible ? 1 : -1 @@ -36,7 +37,7 @@ Scope { // Scope screen: modelData exclusiveZone: 0 - width: sidebarWidth + width: Appearance.sizes.sidebarWidthExtended WlrLayershell.namespace: "quickshell:sidebarLeft" WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive color: "transparent" @@ -47,6 +48,10 @@ Scope { // Scope bottom: true } + mask: Region { + item: sidebarLeftBackground + } + HyprlandFocusGrab { // Click outside to close id: grab windows: [ sidebarRoot ] @@ -76,14 +81,26 @@ Scope { // Scope Rectangle { id: sidebarLeftBackground - anchors.centerIn: parent - width: parent.width - Appearance.sizes.hyprlandGapsOut * 2 + anchors.top: parent.top + anchors.left: parent.left + anchors.topMargin: Appearance.sizes.hyprlandGapsOut + anchors.leftMargin: Appearance.sizes.hyprlandGapsOut + width: sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2 height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 color: Appearance.colors.colLayer0 radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 focus: sidebarRoot.visible + Behavior on width { + NumberAnimation { + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + } + } + Keys.onPressed: (event) => { + // console.log("Key pressed: " + event.key) if (event.key === Qt.Key_Escape) { sidebarRoot.visible = false; } @@ -100,6 +117,10 @@ Scope { // Scope else if (event.key === Qt.Key_Backtab) { sidebarRoot.currentTab = (sidebarRoot.currentTab - 1 + root.tabButtonList.length) % root.tabButtonList.length; } + else if (event.key === Qt.Key_O) { + console.log("Extending sidebar") + sidebarRoot.extend = !sidebarRoot.extend; + } event.accepted = true; } } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index 7ecbe42dc..cfba5c426 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -50,7 +50,7 @@ Rectangle { RowLayout { id: nameRowLayout anchors.centerIn: parent - spacing: 10 + spacing: 5 Item { Layout.alignment: Qt.AlignVCenter From 9a1e0633a1c0cce5c6dc17f505a93f664bd9dc3d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 13:29:47 +0200 Subject: [PATCH 168/824] nicer fallback icon for volume mixer --- .../modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml index eb671689e..240e3e62e 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml @@ -1,6 +1,7 @@ import "root:/modules/common" import "root:/modules/common/widgets" import "root:/services" +import "root:/modules/common/functions/icons.js" as Icons import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls @@ -27,7 +28,7 @@ Item { sourceSize.width: 38 sourceSize.height: 38 source: { - const icon = root.node.properties["application.icon-name"] ?? "audio-volume-high-symbolic"; + const icon = Icons.noKnowledgeIconGuess(root.node.properties["application.icon-name"]); return Quickshell.iconPath(icon); } } From ddf61812718f34ced612af3ac72a57c523c26f41 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 13:29:59 +0200 Subject: [PATCH 169/824] ai chat: make messages copyable --- .../modules/overview/OverviewWindow.qml | 2 +- .../modules/sidebarLeft/aiChat/AiMessage.qml | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index 935960ad0..e659d4ec1 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -1,7 +1,7 @@ -import "root:/modules/common/functions/icons.js" as Icons import "root:/services/" import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/icons.js" as Icons import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Layouts diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index cfba5c426..b3352877d 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -123,17 +123,30 @@ Rectangle { } } - StyledText { // Message + TextEdit { // Message id: messageText Layout.fillWidth: true Layout.margins: messagePadding + readOnly: true + selectByMouse: true font.family: Appearance.font.family.reading + font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text font.pixelSize: Appearance.font.pixelSize.small wrapMode: Text.WordWrap color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 textFormat: Text.MarkdownText text: messageData.thinking ? qsTr("Waiting for response...") : root.messageData.content + + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Control) { // Prevent de-select + event.accepted = true + } + if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) { + messageText.copy() + event.accepted = true + } + } onLinkActivated: (link) => { Qt.openUrlExternally(link) From a6098a007af02a92114c85111b61e429cb1f8c47 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 15:31:27 +0200 Subject: [PATCH 170/824] remove unused var --- .config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index b3352877d..81f0900d5 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -18,7 +18,6 @@ Rectangle { property var messageData property var messageInputField - property real availableWidth: parent.width ?? 0 property real messagePadding: 7 property real contentSpacing: 3 From db0f077c3bbc5f14585726dd31680790db4debe1 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 15:31:49 +0200 Subject: [PATCH 171/824] booru: prevent GOAWAY on sidebar resize --- .../sidebarLeft/anime/BooruResponse.qml | 96 ++++++++++++------- 1 file changed, 59 insertions(+), 37 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index 1df4bbb77..098b73cfa 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -22,7 +22,7 @@ Rectangle { property string downloadPath property string nsfwPath - property real availableWidth: parent.width ?? 0 + property real availableWidth: parent.width property real rowTooShortThreshold: 185 property real imageSpacing: 5 property real responsePadding: 5 @@ -31,6 +31,26 @@ Rectangle { anchors.right: parent?.right implicitHeight: columnLayout.implicitHeight + root.responsePadding * 2 + Component.onCompleted: { + // Break property bind to prevent aggressive updates + availableWidth = parent.width + } + + Connections { + target: parent + function onWidthChanged() { + updateWidthTimer.restart() + } + } + + Timer { + id: updateWidthTimer + interval: 100 + onTriggered: { + availableWidth = parent.width + } + } + radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 @@ -159,45 +179,47 @@ Rectangle { } Repeater { - model: { - // Group two images every row, ensuring they are of the same height - // If the height ends up being too small, put one image in the row and continue - // In other words, this is similar to Android's gallery layout at largest zoom level - let i = 0; - let rows = []; - const responseList = root.responseData.images; - while (i < responseList.length) { - let row = { - height: 0, - images: [], - }; - const availableImageWidth = availableWidth - root.imageSpacing - (responsePadding * 2) - if (i + 1 < responseList.length) { - const img1 = responseList[i]; - const img2 = responseList[i + 1]; - // Calculate combined height if both are in the same row - // Let h = row height, w1 = h * aspect1, w2 = h * aspect2 - // w1 + w2 = availableWidth => h = availableWidth / (aspect1 + aspect2) - const combinedAspect = img1.aspect_ratio + img2.aspect_ratio; - const rowHeight = availableImageWidth / combinedAspect; - if (rowHeight >= rowTooShortThreshold) { - row.height = rowHeight; - row.images.push(img1); - row.images.push(img2); - rows.push(row); - i += 2; - continue; + model: ScriptModel { + values: { + // Group two images every row, ensuring they are of the same height + // If the height ends up being too small, put one image in the row and continue + // In other words, this is similar to Android's gallery layout at largest zoom level + let i = 0; + let rows = []; + const responseList = root.responseData.images; + while (i < responseList.length) { + let row = { + height: 0, + images: [], + }; + const availableImageWidth = availableWidth - root.imageSpacing - (responsePadding * 2) + if (i + 1 < responseList.length) { + const img1 = responseList[i]; + const img2 = responseList[i + 1]; + // Calculate combined height if both are in the same row + // Let h = row height, w1 = h * aspect1, w2 = h * aspect2 + // w1 + w2 = availableWidth => h = availableWidth / (aspect1 + aspect2) + const combinedAspect = img1.aspect_ratio + img2.aspect_ratio; + const rowHeight = availableImageWidth / combinedAspect; + if (rowHeight >= rowTooShortThreshold) { + row.height = rowHeight; + row.images.push(img1); + row.images.push(img2); + rows.push(row); + i += 2; + continue; + } } + // Otherwise, put only one image in the row + const rowHeight = (availableWidth - (responsePadding * 2)) / responseList[i].aspect_ratio; + rows.push({ + height: rowHeight, + images: [responseList[i]], + }); + i += 1; } - // Otherwise, put only one image in the row - const rowHeight = (availableWidth - (responsePadding * 2)) / responseList[i].aspect_ratio; - rows.push({ - height: rowHeight, - images: [responseList[i]], - }); - i += 1; + return rows; } - return rows; } delegate: RowLayout { id: imageRow From 1d39710d3f2250e19e8f0457dd05a436dcf868ab Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 15:32:01 +0200 Subject: [PATCH 172/824] remove fill for search result icons --- .config/quickshell/modules/overview/SearchItem.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index 9da24176a..3f10ab9eb 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -78,7 +78,6 @@ Button { visible: root.materialSymbol != "" text: root.materialSymbol iconSize: 30 - fill: (root.hovered || root.focus) ? 1 : 0 color: Appearance.m3colors.m3onSurface } From afa2697e4e1f84300db46d1a89ef410d7696db5a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 16:39:46 +0200 Subject: [PATCH 173/824] revert stupid anim curve --- .config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index d8b1a9208..77dba5bdc 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -37,13 +37,9 @@ Rectangle { function setCollapsed(state) { collapsed = state if (collapsed) { - bottomWidgetGroupRowFade.easing.bezierCurve = Appearance.animation.elementMoveExit.bezierCurve - bottomWidgetGroupRowFade.easing.bezierCurve = Appearance.animation.elementMoveEnter.bezierCurve bottomWidgetGroupRow.opacity = 0 } else { - bottomWidgetGroupRowFade.easing.bezierCurve = Appearance.animation.elementMoveExit.bezierCurve - bottomWidgetGroupRowFade.easing.bezierCurve = Appearance.animation.elementMoveEnter.bezierCurve collapsedBottomWidgetGroupRow.opacity = 0 } collapseCleanFadeTimer.start() From e3cf6b37e85cddaab15ee23c31acec7c71a8024c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 17:02:14 +0200 Subject: [PATCH 174/824] replace xdg-open with Qt.openUrlExternally --- .../modules/overview/SearchWidget.qml | 19 +++++-------------- .../modules/sidebarLeft/anime/BooruImage.qml | 5 +++-- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 43d78e4ef..f47ff4927 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -86,19 +86,6 @@ Item { // Wrapper } } - Process { - id: webSearch - property list baseCommand: ["xdg-open"] - function search(query) { - let url = ConfigOptions.search.engineBaseUrl + query - for (let site of ConfigOptions.search.excludedSites) { - url += ` -site:${site}`; - } - webSearch.command = baseCommand.concat(url) - webSearch.startDetached() - } - } - Process { id: executor property list baseCommand: ["bash", "-c"] @@ -366,7 +353,11 @@ Item { // Wrapper type: "Search the web", materialSymbol: 'travel_explore', execute: () => { - webSearch.search(root.searchingText); + let url = ConfigOptions.search.engineBaseUrl + root.searchingText + for (let site of ConfigOptions.search.excludedSites) { + url += ` -site:${site}`; + } + Qt.openUrlExternally(url); } }); diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index c2fa3833f..3d2856301 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -2,6 +2,7 @@ import "root:/" import "root:/modules/common" import "root:/modules/common/widgets" import "root:/modules/common/functions/string_utils.js" as StringUtils +import QtQml import Qt.labs.platform import QtQuick import QtQuick.Controls @@ -151,7 +152,7 @@ Button { buttonText: qsTr("Open file link") onClicked: { root.showActions = false - Hyprland.dispatch(`exec xdg-open '${root.imageData.file_url}'`) + Qt.openUrlExternally(root.imageData.file_url) } } MenuButton { @@ -163,7 +164,7 @@ Button { onClicked: { root.showActions = false Hyprland.dispatch("global quickshell:sidebarLeftClose") - Hyprland.dispatch(`exec xdg-open '${root.imageData.source}'`) + Qt.openUrlExternally(root.imageData.source) } } MenuButton { From 8d93f445099b2cae302b7204e84991b57889c01f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 23:47:34 +0200 Subject: [PATCH 175/824] ai chat: make it work with online models --- .../quickshell/modules/common/Appearance.qml | 2 +- .../modules/common/functions/string_utils.js | 7 + .../quickshell/modules/sidebarLeft/AiChat.qml | 23 +-- .../modules/sidebarLeft/aiChat/AiMessage.qml | 4 +- .config/quickshell/services/Ai.qml | 145 ++++++++++++++---- .../quickshell/services/KeyringStorage.qml | 98 ++++++++++++ 6 files changed, 238 insertions(+), 41 deletions(-) create mode 100644 .config/quickshell/services/KeyringStorage.qml diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index a02934968..88dc67d22 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -228,7 +228,7 @@ Singleton { property int barCenterSideModuleWidth: 360 property int barPreferredSideSectionWidth: 400 property int sidebarWidth: 450 - property int sidebarWidthExtended: 700 + property int sidebarWidthExtended: 750 property int notificationPopupWidth: 410 property int searchWidthCollapsed: 260 property int searchWidth: 450 diff --git a/.config/quickshell/modules/common/functions/string_utils.js b/.config/quickshell/modules/common/functions/string_utils.js index c3884e5d9..c655d93da 100644 --- a/.config/quickshell/modules/common/functions/string_utils.js +++ b/.config/quickshell/modules/common/functions/string_utils.js @@ -8,3 +8,10 @@ function getDomain(url) { const match = url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/); return match ? match[1] : null; } + +function shellSingleQuoteEscape(str) { + // First escape backslashes, then escape single quotes + return String(str) + .replace(/\\/g, '\\\\') + .replace(/'/g, "'\\''"); +} diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index f57b05409..eea83d1d0 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -64,11 +64,22 @@ Item { Ai.clearMessages(); } }, + { + name: "key", + description: qsTr("Set API key"), + execute: (args) => { + if (args[0] == "get") { + Ai.printApiKey() + } else { + Ai.setApiKey(args[0]); + } + } + }, { name: "test", description: qsTr("Markdown test message"), execute: () => { - Ai.addMessage("## ✏️ Markdown test\n- **Bold**, *Italic*, `Monospace`, [Link](https://example.com)\n", "interface"); + Ai.addMessage("## ✏️ Markdown test\n- **Bold**, *Italic*, `Monospace`, [Link](https://example.com)\n", Ai.interfaceRole); } }, ] @@ -82,7 +93,7 @@ Item { if (commandObj) { commandObj.execute(args); } else { - Ai.addMessage(qsTr("Unknown command: ") + command, "interface"); + Ai.addMessage(qsTr("Unknown command: ") + command, Ai.interfaceRole); } } else { @@ -218,14 +229,6 @@ Item { commandButton.down ? Appearance.colors.colLayer2Active : commandButton.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 - - Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } - } } contentItem: RowLayout { spacing: 5 diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index 81f0900d5..c87b76236 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -93,8 +93,8 @@ Rectangle { font.weight: Font.DemiBold color: Appearance.m3colors.m3onSecondaryContainer text: messageData.role == 'assistant' ? Ai.models[messageData.model].name : - messageData.role == 'user' ? (SystemInfo.username ?? "User") : - "System" + (messageData.role == 'user' && SystemInfo.username) ? SystemInfo.username : + Ai.models[messageData.role].name } } } diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index f9589f564..d7a4d32a4 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -1,6 +1,7 @@ pragma Singleton pragma ComponentBehavior: Bound +import "root:/modules/common/functions/string_utils.js" as StringUtils import "root:/modules/common" import Quickshell; import Quickshell.Io; @@ -10,36 +11,47 @@ import QtQuick; Singleton { id: root + readonly property string interfaceRole: "interface" property Component aiMessageComponent: AiMessageData {} property var messages: [] property var modelList: ["ollama-llama-3.2", "gemini-2.0-flash"] + readonly property var apiKeys: KeyringStorage.keyringData?.apiKeys ?? {} + + // Model properties: + // - name: Name of the model + // - icon: Icon name of the model + // - description: Description of the model + // - endpoint: Endpoint of the model + // - model: Model name of the model + // - requires_key: Whether the model requires an API key + // - key_id: The identifier of the API key. Use the same identifier for models that can be accessed with the same key. property var models: { // TODO: Auto-detect installed ollama models "interface": { - "name": "System", + "name": "Interface", }, "ollama-llama-3.2": { "name": "Ollama - Llama 3.2", "icon": "ollama-symbolic", "description": "Local Ollama model - Llama 3.2", - "endpoint": "http://localhost:11434/api/chat", + "endpoint": "http://localhost:11434/v1/chat/completions", "model": "llama3.2", }, "gemini-2.0-flash": { "name": "Gemini 2.0 Flash", - "icon": "gemini-symbolic", + "icon": "google-gemini-symbolic", "description": "Online Gemini 2.0 Flash", - "endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent", + "endpoint": "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", "model": "gemini-2.0-flash", - "messageMapFunc": function (message) { - return { - "role": message.role, - "parts": [{text: message.content}], - } - }, + "requires_key": true, + "key_id": "gemini", }, } property var currentModel: "ollama-llama-3.2" + Component.onCompleted: { + setModel(currentModel, false); // Do necessary setup for model + } + function addMessage(message, role) { if (message.length === 0) return; const aiMessage = aiMessageComponent.createObject(root, { @@ -51,14 +63,45 @@ Singleton { root.messages = [...root.messages, aiMessage]; } - function setModel(model) { + function setModel(model, feedback = true) { if (!model) model = "" model = model.toLowerCase() if (modelList.indexOf(model) !== -1) { currentModel = model - root.addMessage("Model set to " + models[model].name, "interface") + if (feedback) root.addMessage("Model set to " + models[model].name, Ai.interfaceRole) } else { - root.addMessage(qsTr("Invalid model. Supported: \n- ") + modelList.join("\n- "), "interface") + if (feedback) root.addMessage(qsTr("Invalid model. Supported: \n- ") + modelList.join("\n- "), Ai.interfaceRole) + } + if (models[model].requires_key) { + KeyringStorage.fetchKeyringData(); + } + } + + function setApiKey(key) { + if (!key || key.length === 0) { + root.addMessage("Please enter an API key with the command", Ai.interfaceRole); + return; + } + const model = models[currentModel]; + if (model.requires_key) { + KeyringStorage.setNestedField(["apiKeys", model.key_id], key); + root.addMessage("API key set for " + model.name, Ai.interfaceRole); + } else { + root.addMessage(`This model (${model.name}) does not require an API key`, Ai.interfaceRole); + } + } + + function printApiKey() { + const model = models[currentModel]; + if (model.requires_key) { + const key = root.apiKeys[model.key_id]; + if (key) { + root.addMessage("API key:\n\n- `" + key, Ai.interfaceRole + "`"); + } else { + root.addMessage("No API key set for " + model.name, Ai.interfaceRole); + } + } else { + root.addMessage(`This model (${model.name}) does not require an API key`, Ai.interfaceRole); } } @@ -68,30 +111,41 @@ Singleton { Process { id: requester - property var baseCommand: ["curl", "--no-buffer"] + property var baseCommand: ["bash", "-c"] property var message function makeRequest() { const model = models[currentModel]; - let endpoint = model.endpoint; - // Build request data using OpenAI's format. If the model has a custom requestDataBuilder, use that instead. - let data = model.requestDataBuilder ? model.requestDataBuilder(root.messages.filter(message => (message.role != "interface"))) : { + /* Build request data and headers */ + let baseData = { "model": model.model, - "messages": root.messages.filter(message => (message.role != "interface")).map(message => { - return { // Remove unecessary properties + "messages": root.messages.filter(message => (message.role != Ai.interfaceRole)).map(message => { + return { "role": message.role, "content": message.content, } }), - } + "stream": true, + }; + let data = model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData; let requestHeaders = { "Content-Type": "application/json", - // "Authorization": model.endpoint.startsWith("http") ? "Bearer " + model.apiKey : "", } + + /* Put API key in environment variable */ + if (model.requires_key) requester.environment = ({ + "API_KEY": root.apiKeys ? (root.apiKeys[model.key_id] ?? "") : "", + }) + console.log(JSON.stringify(root.apiKeys)) + console.log("Model:", model.key_id); + console.log(root.apiKeys[model.key_id]); + + console.log("API key: ", requester.environment.API_KEY); + /* Create message object for local storage */ requester.message = root.aiMessageComponent.createObject(root, { "role": "assistant", "model": currentModel, @@ -100,21 +154,56 @@ Singleton { "done": false, }); root.messages = [...root.messages, requester.message]; - requester.command = baseCommand.concat([endpoint, "-d", JSON.stringify(data)]); - console.log("Request command: ", requester.command.join(" ")); + + /* Build header string for curl */ + let headerString = Object.entries(requestHeaders) + .filter(([k, v]) => v && v.length > 0) + .map(([k, v]) => `-H '${k}: ${v}'`) + .join(' '); + + console.log("Request headers: ", JSON.stringify(requestHeaders)); + console.log("Header string: ", headerString); + + /* Create command string */ + const requestCommandString = `curl --no-buffer '${endpoint}'` + + ` ${headerString}` + + ' -H "Authorization: Bearer ${API_KEY}"' + + ` -d '${StringUtils.shellSingleQuoteEscape(JSON.stringify(data))}'` + // const requestCommandString = 'notify-send "api key" "${API_KEY}" && curl' + console.log("Request command: ", requestCommandString); + requester.command = baseCommand.concat([requestCommandString]); requester.running = true } stdout: SplitParser { onRead: data => { - // console.log("Received data: ", data); if (data.length === 0) return; - const dataJson = JSON.parse(data); + + // Remove 'data: ' prefix if present and trim whitespace + let cleanData = data.trim(); + if (cleanData.startsWith("data:")) { + cleanData = cleanData.slice(5).trim(); + } + console.log("Clean data: ", cleanData); + if (!cleanData) return; + if (requester.message.thinking) requester.message.thinking = false; + try { + if (cleanData === "[DONE]") { + requester.message.done = true; + return; + } + const dataJson = JSON.parse(cleanData); + requester.message.content += + (dataJson.message?.content) ?? // Ollama + (dataJson.choices[0]?.delta?.content) ?? // Normal + (dataJson.choices[0]?.delta?.reasoning_content) // Deepseek thinking - requester.message.content += dataJson.message.content - - if (dataJson.done) requester.message.done = true; + if (dataJson.done) requester.message.done = true; + } catch (e) { + console.log("Error parsing JSON: ", e); + requester.message.content += cleanData; + } } } } diff --git a/.config/quickshell/services/KeyringStorage.qml b/.config/quickshell/services/KeyringStorage.qml new file mode 100644 index 000000000..d356a1a0b --- /dev/null +++ b/.config/quickshell/services/KeyringStorage.qml @@ -0,0 +1,98 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common" +import Quickshell; +import Quickshell.Io; +import Qt.labs.platform +import QtQuick; + +Singleton { + id: root + + property var keyringData: {} + // onKeyringDataChanged: { + // console.log("[KeyringStorage] Keyring data changed:", JSON.stringify(root.keyringData)); + // } + + property var properties: { + "application": "illogical-impulse", + "explanation": "For storing API keys and other sensitive information", + } + property var propertiesAsArgs: Object.keys(root.properties).reduce( + function(arr, key) { + return arr.concat([key, root.properties[key]]); + }, [] + ) + property string keyringLabel: "illogical-impulse Safe Storage" + + function setNestedField(path, value) { + if (!root.keyringData) root.keyringData = {}; + let keys = path + let obj = root.keyringData; + for (let i = 0; i < keys.length - 1; ++i) { + if (!obj[keys[i]] || typeof obj[keys[i]] !== "object") { + obj[keys[i]] = {}; + } + obj = obj[keys[i]]; + } + obj[keys[keys.length - 1]] = value; + // console.log("[KeyringStorage] Updated keyring data:", JSON.stringify(root.keyringData)); + saveKeyringData() + } + + function fetchKeyringData() { + // console.log("[KeyringStorage] Fetching keyring data..."); + // console.log("[KeyringStorage] getData command:'" + getData.command.join("' '") + "'"); + getData.running = true; + } + + function saveKeyringData() { + saveData.stdinEnabled = true; + saveData.running = true; + } + + Process { + id: saveData + command: [ + "secret-tool", "store", "--label=" + keyringLabel, + ...propertiesAsArgs, + ] + onRunningChanged: { + if (saveData.running) { + // console.log("[KeyringStorage] Saving with command: '" + saveData.command.join("' '") + "'"); + saveData.write(JSON.stringify(root.keyringData)); + stdinEnabled = false // End input stream + } + } + } + + Process { + id: getData + command: [ // We need to use echo for a newline so splitparser does parse + "bash", "-c", `echo $(secret-tool lookup 'application' 'illogical-impulse')`, + ] + stdout: SplitParser { + onRead: data => { + if(data.length === 0) return; + try { + root.keyringData = JSON.parse(data); + // console.log("[KeyringStorage] Keyring data fetched:", JSON.stringify(root.keyringData)); + } catch (e) { + console.error("[KeyringStorage] Failed to get keyring data, reinitializing."); + root.keyringData = {}; + saveKeyringData() + } + } + } + onExited: (exitCode, exitStatus) => { + // console.log("[KeyringStorage] Keyring data fetch process exited with code:", exitCode); + if (exitCode !== 0) { + console.error("[KeyringStorage] Failed to get keyring data, reinitializing."); + root.keyringData = {}; + saveKeyringData() + } + } + } + +} From 98647d11f3cf34001c5d31abb54afcb194a45f59 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 23:47:58 +0200 Subject: [PATCH 176/824] make tag buttons feel faster --- .config/quickshell/modules/sidebarLeft/Anime.qml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index f86801ba5..7da4f639d 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -320,14 +320,7 @@ Item { tagButton.down ? Appearance.colors.colLayer2Active : tagButton.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 - - Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } - } + } contentItem: RowLayout { spacing: 5 From 8ff520e7ec70a0fd47929a568e5eb3e2abb242d4 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 5 May 2025 23:57:19 +0200 Subject: [PATCH 177/824] ai chat: smaller name --- .config/quickshell/modules/sidebarLeft/AiChat.qml | 2 +- .config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index eea83d1d0..59d2e9c41 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -111,7 +111,7 @@ Item { ListView { // Message list id: messageListView anchors.fill: parent - + property int lastResponseLength: 0 clip: true diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index c87b76236..134fbcfae 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -89,7 +89,7 @@ Rectangle { StyledText { id: providerName Layout.alignment: Qt.AlignVCenter - font.pixelSize: Appearance.font.pixelSize.large + font.pixelSize: Appearance.font.pixelSize.normal font.weight: Font.DemiBold color: Appearance.m3colors.m3onSecondaryContainer text: messageData.role == 'assistant' ? Ai.models[messageData.model].name : From 46067c68119dd23e3410b6b20617891acec140ba Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 6 May 2025 00:55:30 +0200 Subject: [PATCH 178/824] ai: detect ollama models --- .../modules/sidebarLeft/aiChat/AiMessage.qml | 2 +- .../ai/show-installed-ollama-models.sh | 16 ++++ .config/quickshell/services/Ai.qml | 90 +++++++++++++------ 3 files changed, 82 insertions(+), 26 deletions(-) create mode 100755 .config/quickshell/scripts/ai/show-installed-ollama-models.sh diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index 134fbcfae..bb16cc38b 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -94,7 +94,7 @@ Rectangle { color: Appearance.m3colors.m3onSecondaryContainer text: messageData.role == 'assistant' ? Ai.models[messageData.model].name : (messageData.role == 'user' && SystemInfo.username) ? SystemInfo.username : - Ai.models[messageData.role].name + (messageData.role == 'interface') ? qsTr("Interface") : qsTr("Unknown") } } } diff --git a/.config/quickshell/scripts/ai/show-installed-ollama-models.sh b/.config/quickshell/scripts/ai/show-installed-ollama-models.sh new file mode 100755 index 000000000..e56ac766a --- /dev/null +++ b/.config/quickshell/scripts/ai/show-installed-ollama-models.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# Get the list, skip the header, and extract the first column (model names) +model_names=$(ollama list | tail -n +2 | awk '{print $1}') + +# Build a JSON array +json_array="[" +for name in $model_names; do + json_array+="\"$name\"," +done + +# Remove trailing comma and close the array +json_array="${json_array%,}]" + +# Output the JSON array +echo "$json_array" diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index d7a4d32a4..385f66c95 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -11,10 +11,10 @@ import QtQuick; Singleton { id: root + readonly property string xdgConfigHome: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0] readonly property string interfaceRole: "interface" property Component aiMessageComponent: AiMessageData {} property var messages: [] - property var modelList: ["ollama-llama-3.2", "gemini-2.0-flash"] readonly property var apiKeys: KeyringStorage.keyringData?.apiKeys ?? {} // Model properties: @@ -25,17 +25,7 @@ Singleton { // - model: Model name of the model // - requires_key: Whether the model requires an API key // - key_id: The identifier of the API key. Use the same identifier for models that can be accessed with the same key. - property var models: { // TODO: Auto-detect installed ollama models - "interface": { - "name": "Interface", - }, - "ollama-llama-3.2": { - "name": "Ollama - Llama 3.2", - "icon": "ollama-symbolic", - "description": "Local Ollama model - Llama 3.2", - "endpoint": "http://localhost:11434/v1/chat/completions", - "model": "llama3.2", - }, + property var models: { "gemini-2.0-flash": { "name": "Gemini 2.0 Flash", "icon": "google-gemini-symbolic", @@ -46,10 +36,57 @@ Singleton { "key_id": "gemini", }, } - property var currentModel: "ollama-llama-3.2" + property var modelList: Object.keys(root.models) + property var currentModel: Object.keys(root.models)[0] Component.onCompleted: { setModel(currentModel, false); // Do necessary setup for model + getOllamaModels.running = true + } + + function guessModelLogo(model) { + if (model.includes("llama")) return "ollama-symbolic"; + if (model.includes("gemma")) return "google-gemini-symbolic"; + if (model.includes("deepseek")) return "deepseek-symbolic"; + if (/^phi\d*:/i.test(model)) return "microsoft-symbolic"; + return "ollama-symbolic"; + } + + function guessModelName(model) { + const replaced = model.replace(/-/g, ' ').replace(/:/g, ' '); + const words = replaced.split(' '); + words[words.length - 1] = words[words.length - 1].replace(/(\d+)b$/, (_, num) => `${num}B`) + words[words.length - 1] = `[${words[words.length - 1]}]`; // Surround the last word with square brackets + const result = words.join(' '); + return result.charAt(0).toUpperCase() + result.slice(1); // Capitalize the first letter + } + + Process { + id: getOllamaModels + command: ["bash", "-c", `${xdgConfigHome}/quickshell/scripts/ai/show-installed-ollama-models.sh`.replace(/file:\/\//, "")] + stdout: SplitParser { + onRead: data => { + try { + if (data.length === 0) return; + const dataJson = JSON.parse(data); + root.modelList = [...root.modelList, ...dataJson]; + dataJson.forEach(model => { + root.models[model] = { + "name": guessModelName(model), + "icon": guessModelLogo(model), + "description": `Local Ollama model: ${model}`, + "endpoint": "http://localhost:11434/v1/chat/completions", + "model": model, + } + }); + + root.modelList = Object.keys(root.models); + + } catch (e) { + console.log("Could not fetch Ollama models:", e); + } + } + } } function addMessage(message, role) { @@ -96,7 +133,7 @@ Singleton { if (model.requires_key) { const key = root.apiKeys[model.key_id]; if (key) { - root.addMessage("API key:\n\n- `" + key, Ai.interfaceRole + "`"); + root.addMessage("API key: \n\n`" + key + "`", Ai.interfaceRole); } else { root.addMessage("No API key set for " + model.name, Ai.interfaceRole); } @@ -139,11 +176,6 @@ Singleton { if (model.requires_key) requester.environment = ({ "API_KEY": root.apiKeys ? (root.apiKeys[model.key_id] ?? "") : "", }) - console.log(JSON.stringify(root.apiKeys)) - console.log("Model:", model.key_id); - console.log(root.apiKeys[model.key_id]); - - console.log("API key: ", requester.environment.API_KEY); /* Create message object for local storage */ requester.message = root.aiMessageComponent.createObject(root, { @@ -161,16 +193,15 @@ Singleton { .map(([k, v]) => `-H '${k}: ${v}'`) .join(' '); - console.log("Request headers: ", JSON.stringify(requestHeaders)); - console.log("Header string: ", headerString); + // console.log("Request headers: ", JSON.stringify(requestHeaders)); + // console.log("Header string: ", headerString); /* Create command string */ const requestCommandString = `curl --no-buffer '${endpoint}'` + ` ${headerString}` + ' -H "Authorization: Bearer ${API_KEY}"' + ` -d '${StringUtils.shellSingleQuoteEscape(JSON.stringify(data))}'` - // const requestCommandString = 'notify-send "api key" "${API_KEY}" && curl' - console.log("Request command: ", requestCommandString); + // console.log("Request command: ", requestCommandString); requester.command = baseCommand.concat([requestCommandString]); requester.running = true } @@ -184,7 +215,7 @@ Singleton { if (cleanData.startsWith("data:")) { cleanData = cleanData.slice(5).trim(); } - console.log("Clean data: ", cleanData); + // console.log("Clean data: ", cleanData); if (!cleanData) return; if (requester.message.thinking) requester.message.thinking = false; @@ -201,11 +232,20 @@ Singleton { if (dataJson.done) requester.message.done = true; } catch (e) { - console.log("Error parsing JSON: ", e); requester.message.content += cleanData; } } } + + onExited: (exitCode, exitStatus) => { + try { // to parse full response into json + // console.log("Full response: ", requester.message.content + "]"); + const parsedResponse = JSON.parse(requester.message.content + "]"); + requester.message.content = `\`\`\`json\n${JSON.stringify(parsedResponse, null, 2)}\n\`\`\``; + } catch (e) { + console.log("Could not parse response: ", e); + } + } } function sendUserMessage(message) { From e5779ff05cb14420fe2c45727dc10a7b736c951c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 6 May 2025 10:43:03 +0200 Subject: [PATCH 179/824] update markdown test message --- .../quickshell/modules/sidebarLeft/AiChat.qml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 59d2e9c41..8355f1611 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -77,9 +77,23 @@ Item { }, { name: "test", - description: qsTr("Markdown test message"), + description: qsTr("Markdown test"), execute: () => { - Ai.addMessage("## ✏️ Markdown test\n- **Bold**, *Italic*, `Monospace`, [Link](https://example.com)\n", Ai.interfaceRole); + Ai.addMessage("## ✏️ Markdown test\n" + + "- **Bold**, *Italic*, `Monospace`, [Link](https://example.com)\n\n" + + "- Table:\n\n" + + "| | Quickshell | AGS/Astal |\n" + + "|:-----------|:----------:|:---------:|\n" + + "| UI Toolkit | Qt | Gtk3/Gtk4 |\n" + + "| Language | QML | Js/Ts/Lua |\n" + + "| Reactivity | Implied | Needs declaration |\n" + + "| Widget placement | Mildly difficult | More intuitive |\n" + + "| Bluetooth & Wifi support | ❌ | ✅ |\n" + + "| No-delay keybinds
(hyprland_global_shortcuts_v1) | ✅ | ❌ |\n" + + "| Development | New APIs | New syntax |\n" + + "- Code block" + + , Ai.interfaceRole); } }, ] From 24de2181329ea75d8863b25c1f110cdf326417a9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 6 May 2025 10:43:23 +0200 Subject: [PATCH 180/824] add listview add fade anim --- .config/quickshell/modules/sidebarLeft/AiChat.qml | 12 +++++++++++- .config/quickshell/modules/sidebarLeft/Anime.qml | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 8355f1611..d587e2db9 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -125,6 +125,7 @@ Item { ListView { // Message list id: messageListView anchors.fill: parent + spacing: 10 property int lastResponseLength: 0 @@ -147,7 +148,16 @@ Item { } } - spacing: 10 + add: Transition { + NumberAnimation { + property: "opacity" + from: 0; to: 1 + duration: Appearance.animation.elementMoveEnter.duration + easing.type: Appearance.animation.elementMoveEnter.type + easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve + } + } + model: ScriptModel { values: root.messages } diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 7da4f639d..c9cbdf132 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -151,6 +151,7 @@ Item { ListView { // Booru responses id: booruResponseListView anchors.fill: parent + spacing: 10 property int lastResponseLength: 0 @@ -173,7 +174,16 @@ Item { } } - spacing: 10 + add: Transition { + NumberAnimation { + property: "opacity" + from: 0; to: 1 + duration: Appearance.animation.elementMoveEnter.duration + easing.type: Appearance.animation.elementMoveEnter.type + easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve + } + } + model: ScriptModel { values: { if(root.responses.length > booruResponseListView.lastResponseLength) { From 009bc60c411e42e3bf9f320901f83bbb371bc866 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 6 May 2025 10:44:14 +0200 Subject: [PATCH 181/824] fix api key setting --- .config/quickshell/services/Ai.qml | 41 ++++++++++++------- .../quickshell/services/KeyringStorage.qml | 23 +++++++++-- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index 385f66c95..f6cbd2aac 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -25,15 +25,18 @@ Singleton { // - model: Model name of the model // - requires_key: Whether the model requires an API key // - key_id: The identifier of the API key. Use the same identifier for models that can be accessed with the same key. + // - key_get_link: Link to get the API key property var models: { "gemini-2.0-flash": { "name": "Gemini 2.0 Flash", "icon": "google-gemini-symbolic", - "description": "Online Gemini 2.0 Flash", + "description": "Online | Google's model", + "homepage": "https://aistudio.google.com", "endpoint": "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", "model": "gemini-2.0-flash", "requires_key": true, "key_id": "gemini", + "key_get_link": "https://aistudio.google.com/app/apikey", }, } property var modelList: Object.keys(root.models) @@ -54,11 +57,14 @@ Singleton { function guessModelName(model) { const replaced = model.replace(/-/g, ' ').replace(/:/g, ' '); - const words = replaced.split(' '); + let words = replaced.split(' '); words[words.length - 1] = words[words.length - 1].replace(/(\d+)b$/, (_, num) => `${num}B`) + words = words.map((word) => { + return (word.charAt(0).toUpperCase() + word.slice(1)) + }); words[words.length - 1] = `[${words[words.length - 1]}]`; // Surround the last word with square brackets const result = words.join(' '); - return result.charAt(0).toUpperCase() + result.slice(1); // Capitalize the first letter + return result; } Process { @@ -75,6 +81,7 @@ Singleton { "name": guessModelName(model), "icon": guessModelLogo(model), "description": `Local Ollama model: ${model}`, + "homepage": `https://ollama.com/library/${model}`, "endpoint": "http://localhost:11434/v1/chat/completions", "model": model, } @@ -115,17 +122,21 @@ Singleton { } function setApiKey(key) { - if (!key || key.length === 0) { - root.addMessage("Please enter an API key with the command", Ai.interfaceRole); + const model = models[currentModel]; + if (!model.requires_key) { + root.addMessage(`${model.name} does not require an API key`, Ai.interfaceRole); return; } - const model = models[currentModel]; - if (model.requires_key) { - KeyringStorage.setNestedField(["apiKeys", model.key_id], key); - root.addMessage("API key set for " + model.name, Ai.interfaceRole); - } else { - root.addMessage(`This model (${model.name}) does not require an API key`, Ai.interfaceRole); + if (!key || key.length === 0) { + root.addMessage( + StringUtils.format(qsTr('To set an API key, pass it with the command\n\nTo view the key, pass "get" with the command

For {0}, you can grab one at:\n\n{1}'), + models[currentModel].name, models[currentModel].key_get_link), + Ai.interfaceRole + ); + return; } + KeyringStorage.setNestedField(["apiKeys", model.key_id], key); + root.addMessage("API key set for " + model.name, Ai.interfaceRole); } function printApiKey() { @@ -133,9 +144,9 @@ Singleton { if (model.requires_key) { const key = root.apiKeys[model.key_id]; if (key) { - root.addMessage("API key: \n\n`" + key + "`", Ai.interfaceRole); + root.addMessage(StringUtils.format(qsTr("API key:\n\n`{0}`"), key), Ai.interfaceRole); } else { - root.addMessage("No API key set for " + model.name, Ai.interfaceRole); + root.addMessage(StringUtils.format(qsTr("No API key set for {0}"), model.name), Ai.interfaceRole); } } else { root.addMessage(`This model (${model.name}) does not require an API key`, Ai.interfaceRole); @@ -225,11 +236,13 @@ Singleton { return; } const dataJson = JSON.parse(cleanData); - requester.message.content += + const newContent = (dataJson.message?.content) ?? // Ollama (dataJson.choices[0]?.delta?.content) ?? // Normal (dataJson.choices[0]?.delta?.reasoning_content) // Deepseek thinking + requester.message.content += newContent; + if (dataJson.done) requester.message.done = true; } catch (e) { requester.message.content += cleanData; diff --git a/.config/quickshell/services/KeyringStorage.qml b/.config/quickshell/services/KeyringStorage.qml index d356a1a0b..ef51c3ac7 100644 --- a/.config/quickshell/services/KeyringStorage.qml +++ b/.config/quickshell/services/KeyringStorage.qml @@ -28,17 +28,34 @@ Singleton { function setNestedField(path, value) { if (!root.keyringData) root.keyringData = {}; - let keys = path + let keys = path; let obj = root.keyringData; + let parents = [obj]; + + // Traverse and collect parent objects for (let i = 0; i < keys.length - 1; ++i) { if (!obj[keys[i]] || typeof obj[keys[i]] !== "object") { obj[keys[i]] = {}; } obj = obj[keys[i]]; + parents.push(obj); } + + // Set the value at the innermost key obj[keys[keys.length - 1]] = value; - // console.log("[KeyringStorage] Updated keyring data:", JSON.stringify(root.keyringData)); - saveKeyringData() + + // Reassign each parent object from the bottom up to trigger change notifications + for (let i = keys.length - 2; i >= 0; --i) { + let parent = parents[i]; + let key = keys[i]; + // Shallow clone to change object identity (spread replaced with Object.assign) + parent[key] = Object.assign({}, parent[key]); + } + + // Finally, reassign root.keyringData to trigger top-level change + root.keyringData = Object.assign({}, root.keyringData); + + saveKeyringData(); } function fetchKeyringData() { From e38a0bdac76b10a4b17048973d0ce53e0147beb3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 6 May 2025 10:50:04 +0200 Subject: [PATCH 182/824] overview: add active border --- .../modules/overview/OverviewWidget.qml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index 1af09f983..886b49e3a 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -72,14 +72,19 @@ Item { id: workspace property int colIndex: index property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * ConfigOptions.overview.numOfCols + colIndex + 1 - property color defaultColor: Appearance.colors.colLayer1 // TODO: reconsider this color for a cleaner look + property color defaultWorkspaceColor: Appearance.colors.colLayer1 // TODO: reconsider this color for a cleaner look + property color hoveredWorkspaceColor: Appearance.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1) + property color hoveredBorderColor: Appearance.colors.colLayer2Hover + property color activeBorderColor: Appearance.m3colors.m3secondary + property bool hovered: false implicitWidth: root.workspaceImplicitWidth implicitHeight: root.workspaceImplicitHeight - color: defaultColor + color: hovered ? hoveredWorkspaceColor : defaultWorkspaceColor radius: Appearance.rounding.screenRounding * root.scale border.width: 2 - border.color: "transparent" + border.color: monitor.activeWorkspace?.id == workspaceValue ? activeBorderColor : + hovered ? hoveredBorderColor : "transparent" MouseArea { id: workspaceArea @@ -99,12 +104,10 @@ Item { onEntered: { root.draggingTargetWorkspace = workspaceValue if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return; - border.color = Appearance.colors.colLayer2Hover - workspace.color = Appearance.mix(defaultColor, Appearance.colors.colLayer1Hover, 0.1) + hovered = true } onExited: { - border.color = "transparent" - workspace.color = defaultColor + hovered = false if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1 } } From 418ac5da0cb0a20aeae03aa1fca332febb484f54 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 6 May 2025 11:23:52 +0200 Subject: [PATCH 183/824] booru: better layout when expanded --- .../modules/sidebarLeft/SidebarLeft.qml | 1 - .../sidebarLeft/anime/BooruResponse.qml | 72 +++++++++++-------- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index b97943af7..cdc890dc7 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -118,7 +118,6 @@ Scope { // Scope sidebarRoot.currentTab = (sidebarRoot.currentTab - 1 + root.tabButtonList.length) % root.tabButtonList.length; } else if (event.key === Qt.Key_O) { - console.log("Extending sidebar") sidebarRoot.extend = !sidebarRoot.extend; } event.accepted = true; diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index 098b73cfa..e9fe244ab 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -23,7 +23,7 @@ Rectangle { property string nsfwPath property real availableWidth: parent.width - property real rowTooShortThreshold: 185 + property real rowTooShortThreshold: 190 property real imageSpacing: 5 property real responsePadding: 5 @@ -103,7 +103,6 @@ Rectangle { visible: root.responseData.tags.length > 0 Layout.alignment: Qt.AlignLeft Layout.fillWidth: { - console.log(root.responseData) return true } implicitHeight: tagRowLayout.implicitHeight @@ -181,42 +180,59 @@ Rectangle { Repeater { model: ScriptModel { values: { - // Group two images every row, ensuring they are of the same height - // If the height ends up being too small, put one image in the row and continue - // In other words, this is similar to Android's gallery layout at largest zoom level + // Greedily add images to a row as long as rowHeight >= rowTooShortThreshold let i = 0; let rows = []; const responseList = root.responseData.images; + const minRowHeight = rowTooShortThreshold; + const availableImageWidth = availableWidth - root.imageSpacing - (responsePadding * 2); + while (i < responseList.length) { let row = { height: 0, images: [], }; - const availableImageWidth = availableWidth - root.imageSpacing - (responsePadding * 2) - if (i + 1 < responseList.length) { - const img1 = responseList[i]; - const img2 = responseList[i + 1]; - // Calculate combined height if both are in the same row - // Let h = row height, w1 = h * aspect1, w2 = h * aspect2 - // w1 + w2 = availableWidth => h = availableWidth / (aspect1 + aspect2) - const combinedAspect = img1.aspect_ratio + img2.aspect_ratio; - const rowHeight = availableImageWidth / combinedAspect; - if (rowHeight >= rowTooShortThreshold) { - row.height = rowHeight; - row.images.push(img1); - row.images.push(img2); - rows.push(row); - i += 2; - continue; + let j = i; + let combinedAspect = 0; + let rowHeight = 0; + + // Try to add as many images as possible without going below minRowHeight + while (j < responseList.length) { + combinedAspect += responseList[j].aspect_ratio; + // Subtract imageSpacing for each gap between images in the row + let imagesInRow = j - i + 1; + let totalSpacing = root.imageSpacing * (imagesInRow - 1); + let rowAvailableWidth = availableImageWidth - totalSpacing; + rowHeight = rowAvailableWidth / combinedAspect; + if (rowHeight < minRowHeight) { + combinedAspect -= responseList[j].aspect_ratio; + imagesInRow -= 1; + totalSpacing = root.imageSpacing * (imagesInRow - 1); + rowAvailableWidth = availableImageWidth - totalSpacing; + rowHeight = rowAvailableWidth / combinedAspect; + break; } + j++; + } + + // If we couldn't add any image (shouldn't happen), add at least one + if (j === i) { + row.images.push(responseList[i]); + row.height = availableImageWidth / responseList[i].aspect_ratio; + rows.push(row); + i++; + } else { + for (let k = i; k < j; k++) { + row.images.push(responseList[k]); + } + // Recalculate spacing for the final row + let imagesInRow = j - i; + let totalSpacing = root.imageSpacing * (imagesInRow - 1); + let rowAvailableWidth = availableImageWidth - totalSpacing; + row.height = rowAvailableWidth / combinedAspect; + rows.push(row); + i = j; } - // Otherwise, put only one image in the row - const rowHeight = (availableWidth - (responsePadding * 2)) / responseList[i].aspect_ratio; - rows.push({ - height: rowHeight, - images: [responseList[i]], - }); - i += 1; } return rows; } From 5f87d3a99f86831e993ceb57e0f3c4efb4736709 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 6 May 2025 23:50:17 +0200 Subject: [PATCH 184/824] notif: escape when copying --- .../quickshell/modules/common/widgets/NotificationWidget.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 772aca0ae..d66ea6b3f 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -1,5 +1,6 @@ import "root:/modules/common" import "root:/services" +import "root:/modules/common/functions/string_utils.js" as StringUtils import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls @@ -121,7 +122,7 @@ Item { } onPressAndHold: (mouse) => { if (mouse.button === Qt.LeftButton) { - Hyprland.dispatch(`exec wl-copy '${notificationObject.body}'`) + Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(notificationObject.body)}'`) notificationSummaryText.text = `${notificationObject.summary} (copied)` } } @@ -537,7 +538,7 @@ Item { (contentItem.implicitWidth + leftPadding + rightPadding) onClicked: { - Hyprland.dispatch(`exec wl-copy '${notificationObject.body}'`) + Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(notificationObject.body)}'`) copyIcon.text = "inventory" copyIconTimer.stop() copyIconTimer.start() From ca91f528bb1915a1a582b00af0b8d4e68ab1df50 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 6 May 2025 23:50:56 +0200 Subject: [PATCH 185/824] search: refractor --- .../modules/overview/SearchWidget.qml | 80 +++++++++++-------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index f47ff4927..be6825608 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -2,6 +2,7 @@ import "root:/" import "root:/services/" import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/string_utils.js" as StringUtils import Qt5Compat.GraphicalEffects import Qt.labs.platform import QtQuick @@ -272,8 +273,45 @@ Item { // Wrapper values: { if(root.searchingText == "") return []; - // Start math and other non-app stuff + // Start math calculation, declare special result objects nonAppResultsTimer.restart(); + const mathResultObject = { + name: root.mathResult, + clickActionName: "Copy", + type: qsTr("Math result"), + fontType: "monospace", + materialSymbol: 'calculate', + execute: () => { + Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(root.mathResult)}'`) + } + } + const commandResultObject = { + name: searchingText, + clickActionName: "Run", + type: qsTr("Run command"), + fontType: "monospace", + materialSymbol: 'terminal', + execute: () => { + executor.executeCommand(searchingText.startsWith('sudo') ? `${ConfigOptions.apps.terminal} fish -C '${root.searchingText}'` : root.searchingText); + } + } + const launcherActionObjects = root.searchActions + .map(action => { + const actionString = `${ConfigOptions.search.prefix.action}${action.action}`; + if (actionString.startsWith(root.searchingText) || root.searchingText.startsWith(actionString)) { + return { + name: root.searchingText.startsWith(actionString) ? root.searchingText : actionString, + clickActionName: "Run", + type: "Action", + materialSymbol: 'settings_suggest', + execute: () => { + action.execute(root.searchingText.split(" ").slice(1).join(" ")) + }, + }; + } + return null; + }) + .filter(Boolean); // Init result array let result = []; @@ -288,7 +326,9 @@ Item { // Wrapper }) ); - // Add non-app results + // Add launcher actions + result = result.concat(launcherActionObjects); + // Launcher actions for (let action of root.searchActions) { const actionString = `${ConfigOptions.search.prefix.action}${action.action}` @@ -307,43 +347,15 @@ Item { // Wrapper // Insert math result before command if search starts with a number const startsWithNumber = /^\d/.test(root.searchingText); - if (startsWithNumber) { - result.push({ - name: root.mathResult, - clickActionName: "Copy", - type: qsTr("Math result"), - fontType: "monospace", - materialSymbol: 'calculate', - execute: () => { - Hyprland.dispatch(`exec wl-copy '${root.mathResult}'`) - } - }); - } + if (startsWithNumber) + result.push(mathResultObject); // Command - result.push({ - name: searchingText, - clickActionName: "Run", - type: qsTr("Run command"), - fontType: "monospace", - materialSymbol: 'terminal', - execute: () => { - executor.executeCommand(searchingText.startsWith('sudo') ? `${ConfigOptions.apps.terminal} fish -C '${root.searchingText}'` : root.searchingText); - } - }); + result.push(commandResultObject); // If not already added, add math result after command if (!startsWithNumber) { - result.push({ - name: root.mathResult, - clickActionName: "Copy", - type: qsTr("Math result"), - fontType: "monospace", - materialSymbol: 'calculate', - execute: () => { - Hyprland.dispatch(`exec wl-copy '${root.mathResult}'`) - } - }); + result.push(mathResultObject); } // Web search From 38efbb0d21d79b9fa0bc993c1cf7cd21d6dccdbc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 6 May 2025 23:55:18 +0200 Subject: [PATCH 186/824] ai (service): add openrouter models, handle reasoning --- .config/quickshell/services/Ai.qml | 70 +++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index f6cbd2aac..aac7b8e9e 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -37,6 +37,35 @@ Singleton { "requires_key": true, "key_id": "gemini", "key_get_link": "https://aistudio.google.com/app/apikey", + // "extraParams": { + // "tools": [ + // { + // "google_search": {} + // } + // ] + // } + }, + "openrouter-llama4-maverick": { + "name": "Llama 4 Maverick (OpenRouter)", + "icon": "ollama-symbolic", + "description": "Online | OpenRouter | Meta's model", + "homepage": "https://openrouter.ai/meta-llama/llama-4-maverick:free", + "endpoint": "https://openrouter.ai/api/v1/chat/completions", + "model": "meta-llama/llama-4-maverick:free", + "requires_key": true, + "key_id": "openrouter", + "key_get_link": "https://openrouter.ai/settings/keys", + }, + "openrouter-deepseek-r1": { + "name": "DeepSeek R1 (OpenRouter)", + "icon": "deepseek-symbolic", + "description": "Online | OpenRouter | DeepSeek's reasoning model", + "homepage": "https://openrouter.ai/deepseek/deepseek-r1:free", + "endpoint": "https://openrouter.ai/api/v1/chat/completions", + "model": "deepseek/deepseek-r1:free", + "requires_key": true, + "key_id": "openrouter", + "key_get_link": "https://openrouter.ai/settings/keys", }, } property var modelList: Object.keys(root.models) @@ -80,7 +109,7 @@ Singleton { root.models[model] = { "name": guessModelName(model), "icon": guessModelLogo(model), - "description": `Local Ollama model: ${model}`, + "description": `Local (Ollama) | ${model}`, "homepage": `https://ollama.com/library/${model}`, "endpoint": "http://localhost:11434/v1/chat/completions", "model": model, @@ -107,6 +136,12 @@ Singleton { root.messages = [...root.messages, aiMessage]; } + function removeMessage(index) { + if (index < 0 || index >= messages.length) return; + root.messages.splice(index, 1); + root.messages = [...root.messages]; + } + function setModel(model, feedback = true) { if (!model) model = "" model = model.toLowerCase() @@ -161,6 +196,7 @@ Singleton { id: requester property var baseCommand: ["bash", "-c"] property var message + property bool isReasoning function makeRequest() { const model = models[currentModel]; @@ -178,6 +214,7 @@ Singleton { "stream": true, }; let data = model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData; + let requestHeaders = { "Content-Type": "application/json", @@ -214,6 +251,9 @@ Singleton { + ` -d '${StringUtils.shellSingleQuoteEscape(JSON.stringify(data))}'` // console.log("Request command: ", requestCommandString); requester.command = baseCommand.concat([requestCommandString]); + + /* Reset vars and make the request */ + requester.isReasoning = false requester.running = true } @@ -227,7 +267,9 @@ Singleton { cleanData = cleanData.slice(5).trim(); } // console.log("Clean data: ", cleanData); - if (!cleanData) return; + if (!cleanData || + cleanData === ": OPENROUTER PROCESSING" + ) return; if (requester.message.thinking) requester.message.thinking = false; try { @@ -236,15 +278,31 @@ Singleton { return; } const dataJson = JSON.parse(cleanData); - const newContent = - (dataJson.message?.content) ?? // Ollama - (dataJson.choices[0]?.delta?.content) ?? // Normal - (dataJson.choices[0]?.delta?.reasoning_content) // Deepseek thinking + + let newContent = ""; + const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content; + const responseReasoning = dataJson.choices[0]?.delta?.reasoning || dataJson.choices[0]?.delta?.reasoning_content; + + if (responseContent && responseContent.length > 0) { + if (requester.isReasoning) { + requester.isReasoning = false; + requester.message.content += "\n\n\n\n"; + } + newContent = dataJson.choices[0]?.delta?.content || dataJson.message.content; + } else if (responseReasoning && responseReasoning.length > 0) { + // console.log("Reasoning content: ", dataJson.choices[0].delta.reasoning); + if (!requester.isReasoning) { + requester.isReasoning = true; + requester.message.content += "\n\n\n\n"; + } + newContent = dataJson.choices[0].delta.reasoning || dataJson.choices[0].delta.reasoning_content; + } requester.message.content += newContent; if (dataJson.done) requester.message.done = true; } catch (e) { + console.log("Could not parse response: ", e); requester.message.content += cleanData; } } From db173152c36adb9003cbe9c5f22e44950e39c957 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 6 May 2025 23:57:17 +0200 Subject: [PATCH 187/824] ai chat: action buttons copy, edit, toggle markdown rendering, delete --- .../quickshell/modules/sidebarLeft/AiChat.qml | 37 ++++++--- .../modules/sidebarLeft/ApiCommandButton.qml | 1 - .../modules/sidebarLeft/aiChat/AiMessage.qml | 81 +++++++++++++++++-- .../aiChat/AiMessageControlButton.qml | 34 ++++++++ 4 files changed, 137 insertions(+), 16 deletions(-) create mode 100644 .config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index d587e2db9..689a464e5 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -82,16 +82,24 @@ Item { Ai.addMessage("## ✏️ Markdown test\n" + "- **Bold**, *Italic*, `Monospace`, [Link](https://example.com)\n\n" + "- Table:\n\n" - + "| | Quickshell | AGS/Astal |\n" - + "|:-----------|:----------:|:---------:|\n" - + "| UI Toolkit | Qt | Gtk3/Gtk4 |\n" - + "| Language | QML | Js/Ts/Lua |\n" - + "| Reactivity | Implied | Needs declaration |\n" - + "| Widget placement | Mildly difficult | More intuitive |\n" - + "| Bluetooth & Wifi support | ❌ | ✅ |\n" - + "| No-delay keybinds
(hyprland_global_shortcuts_v1) | ✅ | ❌ |\n" - + "| Development | New APIs | New syntax |\n" - + "- Code block" + + "| | Quickshell | AGS/Astal |\n" + + "|:-------------------------|:----------------:|:-----------------:|\n" + + "| UI Toolkit | Qt | Gtk3/Gtk4 |\n" + + "| Language | QML | Js/Ts/Lua |\n" + + "| Reactivity | Implied | Needs declaration |\n" + + "| Widget placement | Mildly difficult | More intuitive |\n" + + "| Bluetooth & Wifi support | ❌ | ✅ |\n" + + "| No-delay keybinds | ✅ | ❌ |\n" + + "| Development | New APIs | New syntax |\n" + + "- Code block\n" + + "```cpp\n" + + "#include \n" + + "const std::string GREETING = \"UwU\";\n" + + "int main(int argc, char* argv[]) {\n" + + " std::cout << GREETING;\n" + + "}\n" + + "```\n" + , Ai.interfaceRole); } @@ -157,6 +165,15 @@ Item { easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve } } + remove: Transition { + NumberAnimation { + property: "opacity" + from: 1; to: 0 + duration: Appearance.animation.elementMoveEnter.duration + easing.type: Appearance.animation.elementMoveEnter.type + easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve + } + } model: ScriptModel { values: root.messages diff --git a/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml b/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml index 25bfdbb87..cd54e8a3c 100644 --- a/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml +++ b/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml @@ -5,7 +5,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell -import Quickshell.Services.Notifications Button { id: button diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index bb16cc38b..35a10237c 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -3,6 +3,7 @@ import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" import "../" +import "root:/modules/common/functions/string_utils.js" as StringUtils import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -15,12 +16,16 @@ import Qt5Compat.GraphicalEffects Rectangle { id: root + property int messageIndex property var messageData property var messageInputField property real messagePadding: 7 property real contentSpacing: 3 + property bool renderMarkdown: true + property bool editing: false + anchors.left: parent?.left anchors.right: parent?.right implicitHeight: columnLayout.implicitHeight + root.messagePadding * 2 @@ -38,6 +43,8 @@ Rectangle { spacing: root.contentSpacing RowLayout { // Header + spacing: 15 + Rectangle { // Name id: nameWrapper color: Appearance.m3colors.m3secondaryContainer @@ -102,9 +109,10 @@ Rectangle { Item { Layout.fillWidth: true } Button { // Not visible to model + id: modelVisibilityIndicator visible: messageData.role == 'interface' - implicitWidth: Math.max(notVisibleToModelText.implicitWidth + 10 * 2, 30) - implicitHeight: notVisibleToModelText.implicitHeight + 5 * 2 + implicitWidth: 16 + implicitHeight: 30 Layout.alignment: Qt.AlignVCenter background: Item @@ -120,21 +128,84 @@ Rectangle { content: qsTr("Not visible to model") } } + + StyledText { + visible: modelVisibilityIndicator.visible + font.pixelSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer1 + text: "•" + } + + RowLayout { + spacing: 5 + + AiMessageControlButton { + id: copyButton + buttonIcon: "content_copy" + onClicked: { + Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(root.messageData.content)}'`) + } + StyledToolTip { + content: qsTr("Copy") + } + } + AiMessageControlButton { + id: editButton + activated: root.editing + buttonIcon: "edit" + onClicked: { + root.editing = !root.editing + if (!root.editing) { // Save changes + root.messageData.content = messageText.text + } + } + StyledToolTip { + content: root.editing ? qsTr("Save") : qsTr("Edit") + } + } + AiMessageControlButton { + id: toggleMarkdownButton + activated: !root.renderMarkdown + buttonIcon: root.renderMarkdown ? "wysiwyg" : "code" + onClicked: { + root.renderMarkdown = !root.renderMarkdown + if (root.renderMarkdown && messageData.finished) { + messageText.text = root.messageData.content + } + } + StyledToolTip { + content: qsTr("Toggle Markdown rendering") + } + } + AiMessageControlButton { + id: deleteButton + buttonIcon: "close" + onClicked: { + Ai.removeMessage(root.messageIndex) + } + StyledToolTip { + content: qsTr("Delete") + } + } + } } TextEdit { // Message id: messageText Layout.fillWidth: true Layout.margins: messagePadding - readOnly: true + readOnly: !root.editing selectByMouse: true + renderType: Text.NativeRendering font.family: Appearance.font.family.reading font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text font.pixelSize: Appearance.font.pixelSize.small + selectedTextColor: Appearance.m3colors.m3onPrimary + selectionColor: Appearance.m3colors.m3primary wrapMode: Text.WordWrap color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 - textFormat: Text.MarkdownText + textFormat: root.renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText text: messageData.thinking ? qsTr("Waiting for response...") : root.messageData.content Keys.onPressed: (event) => { @@ -155,7 +226,7 @@ Rectangle { anchors.fill: parent acceptedButtons: Qt.NoButton // Only for hover hoverEnabled: true - cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor + cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.IBeamCursor } } } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml new file mode 100644 index 000000000..003cc1ba0 --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml @@ -0,0 +1,34 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell + +Button { + id: button + property string buttonIcon + property bool activated: false + + implicitHeight: 30 + implicitWidth: 30 + + PointingHandInteraction {} + + background: Rectangle { + radius: Appearance.rounding.small + color: button.activated ? Appearance.m3colors.m3primary : + button.down ? Appearance.colors.colSurfaceContainerHighestActive : + button.hovered ? Appearance.colors.colSurfaceContainerHighestHover : + Appearance.m3colors.m3surfaceContainerHighest + } + + contentItem: MaterialSymbol { + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.large + color: button.activated ? Appearance.m3colors.m3onPrimary : + Appearance.m3colors.m3onSurface + text: buttonIcon + } +} From e3e70e731640c37354f7257fcf7d14522fad1907 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 6 May 2025 23:57:30 +0200 Subject: [PATCH 188/824] oops forgot to commit this line --- .config/quickshell/modules/sidebarLeft/AiChat.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 689a464e5..66c9c698c 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -179,6 +179,7 @@ Item { values: root.messages } delegate: AiMessage { + messageIndex: index messageData: modelData messageInputField: root.inputField } From cae673bd726a7a83b4a5991a1e35e7a13858d058 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 7 May 2025 00:08:07 +0200 Subject: [PATCH 189/824] change text selection color --- .config/quickshell/modules/overview/SearchWidget.qml | 4 ++-- .config/quickshell/modules/sidebarLeft/AiChat.qml | 4 ++-- .config/quickshell/modules/sidebarLeft/Anime.qml | 4 ++-- .config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml | 4 ++-- .config/quickshell/modules/sidebarRight/todo/TodoWidget.qml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index be6825608..5b61689b4 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -195,8 +195,8 @@ Item { // Wrapper padding: 15 renderType: Text.NativeRendering color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant - selectedTextColor: Appearance.m3colors.m3onPrimary - selectionColor: Appearance.m3colors.m3primary + selectedTextColor: Appearance.m3colors.m3onSecondaryContainer + selectionColor: Appearance.m3colors.m3secondaryContainer placeholderText: qsTr("Search, calculate or run") placeholderTextColor: Appearance.m3colors.m3outline implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 66c9c698c..7e8d5c385 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -355,8 +355,8 @@ Item { padding: 10 color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant renderType: Text.NativeRendering - selectedTextColor: Appearance.m3colors.m3onPrimary - selectionColor: Appearance.m3colors.m3primary + selectedTextColor: Appearance.m3colors.m3onSecondaryContainer + selectionColor: Appearance.m3colors.m3secondaryContainer placeholderText: StringUtils.format(qsTr('Message the model... "{0}" for commands'), root.commandPrefix) placeholderTextColor: Appearance.m3colors.m3outline diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index c9cbdf132..58de08177 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -415,8 +415,8 @@ Item { padding: 10 color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant renderType: Text.NativeRendering - selectedTextColor: Appearance.m3colors.m3onPrimary - selectionColor: Appearance.m3colors.m3primary + selectedTextColor: Appearance.m3colors.m3onSecondaryContainer + selectionColor: Appearance.m3colors.m3secondaryContainer placeholderText: StringUtils.format(qsTr('Enter tags, or "{0}" for commands'), root.commandPrefix) placeholderTextColor: Appearance.m3colors.m3outline diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index 35a10237c..f1e05c31e 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -201,8 +201,8 @@ Rectangle { font.family: Appearance.font.family.reading font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text font.pixelSize: Appearance.font.pixelSize.small - selectedTextColor: Appearance.m3colors.m3onPrimary - selectionColor: Appearance.m3colors.m3primary + selectedTextColor: Appearance.m3colors.m3onSecondaryContainer + selectionColor: Appearance.m3colors.m3secondaryContainer wrapMode: Text.WordWrap color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 textFormat: root.renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index 6606f9f49..b545eba3b 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -285,8 +285,8 @@ Item { padding: 10 color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant renderType: Text.NativeRendering - selectedTextColor: Appearance.m3colors.m3onPrimary - selectionColor: Appearance.m3colors.m3primary + selectedTextColor: Appearance.m3colors.m3onSecondaryContainer + selectionColor: Appearance.m3colors.m3secondaryContainer placeholderText: qsTr("Task description") placeholderTextColor: Appearance.m3colors.m3outline focus: root.showAddDialog From f3e0f14c44e77e61f9e7db7b215c240ef400956d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 7 May 2025 09:06:35 +0200 Subject: [PATCH 190/824] cleaner ai message buttons --- .../quickshell/modules/sidebarLeft/aiChat/AiMessage.qml | 9 +-------- .../sidebarLeft/aiChat/AiMessageControlButton.qml | 9 ++++++--- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index f1e05c31e..296fb03c5 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -106,8 +106,6 @@ Rectangle { } } - Item { Layout.fillWidth: true } - Button { // Not visible to model id: modelVisibilityIndicator visible: messageData.role == 'interface' @@ -129,12 +127,7 @@ Rectangle { } } - StyledText { - visible: modelVisibilityIndicator.visible - font.pixelSize: Appearance.font.pixelSize.larger - color: Appearance.colors.colOnLayer1 - text: "•" - } + Item { Layout.fillWidth: true } RowLayout { spacing: 5 diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml index 003cc1ba0..c36406a7a 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml @@ -18,10 +18,13 @@ Button { background: Rectangle { radius: Appearance.rounding.small - color: button.activated ? Appearance.m3colors.m3primary : - button.down ? Appearance.colors.colSurfaceContainerHighestActive : + color: button.activated ? + (button.down ? Appearance.colors.colPrimaryActive : + button.hovered ? Appearance.colors.colPrimaryHover : + Appearance.m3colors.m3primary) : + (button.down ? Appearance.colors.colSurfaceContainerHighestActive : button.hovered ? Appearance.colors.colSurfaceContainerHighestHover : - Appearance.m3colors.m3surfaceContainerHighest + Appearance.transparentize(Appearance.m3colors.m3surfaceContainerHighest, 1)) } contentItem: MaterialSymbol { From 8464f0107c463925129eaa39dd562fdad67eea25 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 7 May 2025 12:16:20 +0200 Subject: [PATCH 191/824] code block syntax highlighting --- .../quickshell/modules/common/Appearance.qml | 2 + .../modules/common/functions/string_utils.js | 36 ++- .../quickshell/modules/sidebarLeft/AiChat.qml | 2 +- .../modules/sidebarLeft/aiChat/AiMessage.qml | 270 +++++++++++++++--- .config/quickshell/services/MaterialTheme.qml | 2 + README.md | 2 +- 6 files changed, 262 insertions(+), 52 deletions(-) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 88dc67d22..e2e9809f0 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -11,6 +11,7 @@ Singleton { property QtObject rounding property QtObject font property QtObject sizes + property string syntaxHighlightingTheme function mix(color1, color2, percentage) { var c1 = Qt.color(color1); @@ -238,4 +239,5 @@ Singleton { property int fabHoveredShadowRadius: 7 } + syntaxHighlightingTheme: Appearance.m3colors.darkmode ? "Monokai" : "ayu Light" } diff --git a/.config/quickshell/modules/common/functions/string_utils.js b/.config/quickshell/modules/common/functions/string_utils.js index c655d93da..4784a56ea 100644 --- a/.config/quickshell/modules/common/functions/string_utils.js +++ b/.config/quickshell/modules/common/functions/string_utils.js @@ -1,17 +1,35 @@ function format(str, ...args) { - return str.replace(/{(\d+)}/g, (match, index) => - typeof args[index] !== 'undefined' ? args[index] : match - ); + return str.replace(/{(\d+)}/g, (match, index) => + typeof args[index] !== 'undefined' ? args[index] : match + ); } function getDomain(url) { - const match = url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/); - return match ? match[1] : null; + const match = url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/); + return match ? match[1] : null; } function shellSingleQuoteEscape(str) { - // First escape backslashes, then escape single quotes - return String(str) - .replace(/\\/g, '\\\\') - .replace(/'/g, "'\\''"); + // First escape backslashes, then escape single quotes + return String(str) + .replace(/\\/g, '\\\\') + .replace(/'/g, "'\\''"); } + +function splitMarkdownBlocks(markdown) { + const regex = /```(\w+)?\n([\s\S]*?)```/g; + let result = []; + let lastIndex = 0; + let match; + while ((match = regex.exec(markdown)) !== null) { + if (match.index > lastIndex) { + result.push({ type: "text", content: markdown.slice(lastIndex, match.index) }); + } + result.push({ type: "code", lang: match[1] || "", content: match[2] }); + lastIndex = regex.lastIndex; + } + if (lastIndex < markdown.length) { + result.push({ type: "text", content: markdown.slice(lastIndex) }); + } + return result; +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 7e8d5c385..f3782d082 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -83,7 +83,7 @@ Item { + "- **Bold**, *Italic*, `Monospace`, [Link](https://example.com)\n\n" + "- Table:\n\n" + "| | Quickshell | AGS/Astal |\n" - + "|:-------------------------|:----------------:|:-----------------:|\n" + + "|--------------------------|------------------|-------------------|\n" + "| UI Toolkit | Qt | Gtk3/Gtk4 |\n" + "| Language | QML | Js/Ts/Lua |\n" + "| Reactivity | Implied | Needs declaration |\n" diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index 296fb03c5..384ca56da 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -13,6 +13,8 @@ import Quickshell.Widgets import Quickshell.Wayland import Quickshell.Hyprland import Qt5Compat.GraphicalEffects +import org.kde.syntaxhighlighting +// import org.kde.kirigami as Kirigami Rectangle { id: root @@ -22,6 +24,8 @@ Rectangle { property real messagePadding: 7 property real contentSpacing: 3 + property real codeBlockBackgroundRounding: Appearance.rounding.small + property real codeBlockComponentSpacing: 2 property bool renderMarkdown: true property bool editing: false @@ -149,7 +153,25 @@ Rectangle { onClicked: { root.editing = !root.editing if (!root.editing) { // Save changes - root.messageData.content = messageText.text + // Get all Loader children (each represents a segment) + const segments = messageContentColumnLayout.children + .map(child => child.segment) + .filter(segment => (segment)); + // console.log("Segments: " + JSON.stringify(segments)) + + // Reconstruct markdown + const newContent = segments.map(segment => { + if (segment.type === "code") { + const lang = segment.lang ? segment.lang : ""; + // Remove trailing newlines + const code = segment.content.replace(/\n+$/, ""); + return "```" + lang + "\n" + code + "\n```"; + } else { + return segment.content; + } + }).join(""); + + root.messageData.content = newContent; } } StyledToolTip { @@ -159,15 +181,12 @@ Rectangle { AiMessageControlButton { id: toggleMarkdownButton activated: !root.renderMarkdown - buttonIcon: root.renderMarkdown ? "wysiwyg" : "code" + buttonIcon: "code" onClicked: { root.renderMarkdown = !root.renderMarkdown - if (root.renderMarkdown && messageData.finished) { - messageText.text = root.messageData.content - } } StyledToolTip { - content: qsTr("Toggle Markdown rendering") + content: qsTr("View Markdown source") } } AiMessageControlButton { @@ -183,45 +202,214 @@ Rectangle { } } - TextEdit { // Message - id: messageText - Layout.fillWidth: true - Layout.margins: messagePadding - readOnly: !root.editing - selectByMouse: true - - renderType: Text.NativeRendering - font.family: Appearance.font.family.reading - font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text - font.pixelSize: Appearance.font.pixelSize.small - selectedTextColor: Appearance.m3colors.m3onSecondaryContainer - selectionColor: Appearance.m3colors.m3secondaryContainer - wrapMode: Text.WordWrap - color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 - textFormat: root.renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText - text: messageData.thinking ? qsTr("Waiting for response...") : root.messageData.content - - Keys.onPressed: (event) => { - if (event.key === Qt.Key_Control) { // Prevent de-select - event.accepted = true + ColumnLayout { + id: messageContentColumnLayout + Repeater { + model: ScriptModel { + values: { + const result = StringUtils.splitMarkdownBlocks(root.messageData.content) + // console.log(JSON.stringify(result)) + return result + } } - if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) { - messageText.copy() - event.accepted = true + delegate: Loader { + Layout.fillWidth: true + property var segment: modelData + sourceComponent: modelData.type === "code" ? codeBlockComponent : textBlockComponent } } - - onLinkActivated: (link) => { - Qt.openUrlExternally(link) - Hyprland.dispatch("global quickshell:sidebarLeftClose") - } - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton // Only for hover - hoverEnabled: true - cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.IBeamCursor - } } + + Component { // Text block + id: textBlockComponent + TextArea { + readOnly: !root.editing + renderType: Text.NativeRendering + font.family: Appearance.font.family.reading + font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text + font.pixelSize: Appearance.font.pixelSize.small + selectedTextColor: Appearance.m3colors.m3onSecondaryContainer + selectionColor: Appearance.m3colors.m3secondaryContainer + wrapMode: TextEdit.Wrap + color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 + textFormat: root.renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText + text: messageData.thinking ? qsTr("Waiting for response...") : segment.content + + onTextChanged: { + segment.content = text + } + + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Control || event.key == Qt.Key_Shift || event.key == Qt.Key_Alt || event.key == Qt.Key_Meta) { // Prevent de-select + event.accepted = true + } + if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) { + messageText.copy() + event.accepted = true + } + } + + onLinkActivated: (link) => { + Qt.openUrlExternally(link) + Hyprland.dispatch("global quickshell:sidebarLeftClose") + } + + MouseArea { // Pointing hand for links + anchors.fill: parent + acceptedButtons: Qt.NoButton // Only for hover + hoverEnabled: true + cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.IBeamCursor + } + } + } + + Component { // Code block + id: codeBlockComponent + ColumnLayout { + spacing: codeBlockComponentSpacing + Layout.fillWidth: true + + Rectangle { // Code background + Layout.fillWidth: true + topLeftRadius: codeBlockBackgroundRounding + topRightRadius: codeBlockBackgroundRounding + bottomLeftRadius: Appearance.rounding.unsharpen + bottomRightRadius: Appearance.rounding.unsharpen + color: Appearance.m3colors.m3surfaceContainerHighest + implicitHeight: codeBlockTitleBarRowLayout.implicitHeight + + RowLayout { // Language and buttons + id: codeBlockTitleBarRowLayout + spacing: 5 + + StyledText { + id: codeBlockLanguage + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: false + Layout.topMargin: 7 + Layout.bottomMargin: 7 + Layout.leftMargin: 10 + font.pixelSize: Appearance.font.pixelSize.small + font.weight: Font.DemiBold + color: Appearance.colors.colOnLayer2 + text: segment.lang ? Repository.definitionForName(segment.lang).name : "plain" + } + + Item { Layout.fillWidth: true } + } + } + + RowLayout { // Line numbers and code + spacing: codeBlockComponentSpacing + + Rectangle { // Line numbers + implicitWidth: 40 + Layout.fillHeight: true + Layout.fillWidth: false + topLeftRadius: Appearance.rounding.unsharpen + bottomLeftRadius: codeBlockBackgroundRounding + topRightRadius: Appearance.rounding.unsharpen + bottomRightRadius: Appearance.rounding.unsharpen + color: Appearance.colors.colLayer2 + + ColumnLayout { + id: lineNumberColumnLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 5 + anchors.verticalCenter: parent.verticalCenter + spacing: 0 + + Repeater { + model: codeTextArea.text.split("\n").length + Text { + Layout.fillWidth: true + Layout.alignment: Qt.AlignRight + font.family: Appearance.font.family.monospace + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.colors.colSubtext + horizontalAlignment: Text.AlignRight + text: index + 1 + } + } + } + } + + Rectangle { // Code background + Layout.fillWidth: true + topLeftRadius: Appearance.rounding.unsharpen + bottomLeftRadius: Appearance.rounding.unsharpen + topRightRadius: Appearance.rounding.unsharpen + bottomRightRadius: codeBlockBackgroundRounding + color: Appearance.colors.colLayer2 + // implicitWidth: codeTextArea.implicitWidth + implicitHeight: codeTextArea.implicitHeight + + ScrollView { + id: codeScrollView + Layout.fillWidth: true + Layout.fillHeight: true + implicitWidth: parent.width + implicitHeight: codeTextArea.contentHeight + contentWidth: codeTextArea.contentWidth + contentHeight: codeTextArea.contentHeight + clip: true + ScrollBar.vertical.policy: ScrollBar.AlwaysOff + + TextArea { // Code + + id: codeTextArea + Layout.fillWidth: true + readOnly: !root.editing + renderType: Text.NativeRendering + font.family: Appearance.font.family.monospace + font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text + font.pixelSize: Appearance.font.pixelSize.small + selectedTextColor: Appearance.m3colors.m3onSecondaryContainer + selectionColor: Appearance.m3colors.m3secondaryContainer + // wrapMode: TextEdit.Wrap + color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 + + text: segment.content + onTextChanged: { + segment.content = text + } + + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Tab) { + // Insert 4 spaces at cursor + const cursor = codeTextArea.cursorPosition; + codeTextArea.insert(cursor, " "); + codeTextArea.cursorPosition = cursor + 4; + event.accepted = true; + } else if ( + event.key === Qt.Key_Control || + event.key == Qt.Key_Shift || + event.key == Qt.Key_Alt || + event.key == Qt.Key_Meta + ) { + event.accepted = true; + } else if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) { + messageText.copy(); + event.accepted = true; + } + } + + SyntaxHighlighter { + id: highlighter + textEdit: codeTextArea + repository: Repository + definition: Repository.definitionForName(segment.lang || "plaintext") + // definition: Repository.definitionForName("cpp") + theme: Appearance.syntaxHighlightingTheme + } + } + } + } + } + } + } + } } diff --git a/.config/quickshell/services/MaterialTheme.qml b/.config/quickshell/services/MaterialTheme.qml index 94b117b42..c34441a1b 100644 --- a/.config/quickshell/services/MaterialTheme.qml +++ b/.config/quickshell/services/MaterialTheme.qml @@ -25,6 +25,8 @@ Singleton { Appearance.m3colors[m3Key] = json[key] } } + + Appearance.m3colors.darkmode = (Appearance.m3colors.m3background.hslLightness < 0.5) } Timer { diff --git a/README.md b/README.md index 77c395dc6..84be4f32b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Not ready, but feel free to try it. It's simple: - **Assumption**: You are already using the AGS illogical-impulse -- **Install Qt packages** (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-graphicaleffects qt5-imageformats qt5-quickcontrols qt5-quickcontrols2 qt5-svg qt5-translations qt5-wayland qt5-x11extras qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland qt6-webchannel qt6-webengine qt6-websockets qt6-webview` +- **Install Qt packages** (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-graphicaleffects qt5-imageformats qt5-quickcontrols qt5-quickcontrols2 qt5-svg qt5-translations qt5-wayland qt5-x11extras qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland qt6-webchannel qt6-webengine qt6-websockets qt6-webview syntax-highlighting` - **Install quickshell and more stuff**: `yay -S quickshell matugen-bin grimblast` - **Copy** `.config/quickshell` folder and hyprland config files in `.config/hypr/hyprland/` (backing up is your responsibility) - **Run quickshell** with `qs` and see how things are - it's not finished for daily use, but **feedback is very welcome** From e83dfdc5d89511275c6b091a998a7c558f7dc903 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 7 May 2025 19:12:50 +0200 Subject: [PATCH 192/824] shorter line --- .config/quickshell/modules/bar/Workspaces.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 101baf296..2993e41ec 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -201,7 +201,10 @@ Item { font.pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== "10") * 2) text: `${workspaceButtonBackground.workspaceValue}` elide: Text.ElideRight - color: (monitor.activeWorkspace?.id == workspaceButtonBackground.workspaceValue) ? Appearance.m3colors.m3onPrimary : (workspaceOccupied[index] ? Appearance.colors.colOnLayer1 : Appearance.colors.colOnLayer1Inactive) + color: (monitor.activeWorkspace?.id == workspaceButtonBackground.workspaceValue) ? + Appearance.m3colors.m3onPrimary : + (workspaceOccupied[index] ? Appearance.colors.colOnLayer1 : + Appearance.colors.colOnLayer1Inactive) Behavior on color { ColorAnimation { From a765a190cd793e52fd8cd28b2c8b181b4a6e5e10 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 7 May 2025 19:13:28 +0200 Subject: [PATCH 193/824] ai chat: better code snippets --- .../modules/common/functions/string_utils.js | 4 + .../quickshell/modules/sidebarLeft/AiChat.qml | 58 +++++--- .../modules/sidebarLeft/aiChat/AiMessage.qml | 130 +++++++++++++----- 3 files changed, 135 insertions(+), 57 deletions(-) diff --git a/.config/quickshell/modules/common/functions/string_utils.js b/.config/quickshell/modules/common/functions/string_utils.js index 4784a56ea..ee7b3eaeb 100644 --- a/.config/quickshell/modules/common/functions/string_utils.js +++ b/.config/quickshell/modules/common/functions/string_utils.js @@ -32,4 +32,8 @@ function splitMarkdownBlocks(markdown) { result.push({ type: "text", content: markdown.slice(lastIndex) }); } return result; +} + +function unEscapeBackslashes(str) { + return str.replace(/\\\\/g, '\\'); } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index f3782d082..83623f300 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -79,29 +79,45 @@ Item { name: "test", description: qsTr("Markdown test"), execute: () => { - Ai.addMessage("## ✏️ Markdown test\n" - + "- **Bold**, *Italic*, `Monospace`, [Link](https://example.com)\n\n" - + "- Table:\n\n" - + "| | Quickshell | AGS/Astal |\n" - + "|--------------------------|------------------|-------------------|\n" - + "| UI Toolkit | Qt | Gtk3/Gtk4 |\n" - + "| Language | QML | Js/Ts/Lua |\n" - + "| Reactivity | Implied | Needs declaration |\n" - + "| Widget placement | Mildly difficult | More intuitive |\n" - + "| Bluetooth & Wifi support | ❌ | ✅ |\n" - + "| No-delay keybinds | ✅ | ❌ |\n" - + "| Development | New APIs | New syntax |\n" - + "- Code block\n" - + "```cpp\n" - + "#include \n" - + "const std::string GREETING = \"UwU\";\n" - + "int main(int argc, char* argv[]) {\n" - + " std::cout << GREETING;\n" - + "}\n" - + "```\n" + Ai.addMessage(` +## ✏️ Markdown test +### Formatting +*Italic*, \`Monospace\`, **Bold**, [Link](https://example.com) - , Ai.interfaceRole); +### Table + +Quickshell vs AGS/Astal + +| | Quickshell | AGS/Astal | +|--------------------------|------------------|-------------------| +| UI Toolkit | Qt | Gtk3/Gtk4 | +| Language | QML | Js/Ts/Lua | +| Reactivity | Implied | Needs declaration | +| Widget placement | Mildly difficult | More intuitive | +| Bluetooth & Wifi support | ❌ | ✅ | +| No-delay keybinds | ✅ | ❌ | +| Development | New APIs | New syntax | + +### Code block + +Just a hello world... + +\`\`\`cpp +#include +// This is intentionally very long to test scrolling +const std::string GREETING = \"UwU\"; +int main(int argc, char* argv[]) { + std::cout << GREETING; +} +\`\`\` + +### LaTeX + +Inline: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$ + +`, + Ai.interfaceRole); } }, ] diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index 384ca56da..e09a1a37d 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -14,7 +14,6 @@ import Quickshell.Wayland import Quickshell.Hyprland import Qt5Compat.GraphicalEffects import org.kde.syntaxhighlighting -// import org.kde.kirigami as Kirigami Rectangle { id: root @@ -25,6 +24,7 @@ Rectangle { property real messagePadding: 7 property real contentSpacing: 3 property real codeBlockBackgroundRounding: Appearance.rounding.small + property real codeBlockHeaderPadding: 3 property real codeBlockComponentSpacing: 2 property bool renderMarkdown: true @@ -37,6 +37,46 @@ Rectangle { radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 + function saveMessage() { + if (!root.editing) return; + // Get all Loader children (each represents a segment) + const segments = messageContentColumnLayout.children + .map(child => child.segment) + .filter(segment => (segment)); + // console.log("Segments: " + JSON.stringify(segments)) + + // Reconstruct markdown + const newContent = segments.map(segment => { + if (segment.type === "code") { + const lang = segment.lang ? segment.lang : ""; + // Remove trailing newlines + const code = segment.content.replace(/\n+$/, ""); + return "```" + lang + "\n" + code + "\n```"; + } else { + return segment.content; + } + }).join(""); + + root.editing = false + root.messageData.content = newContent; + } + + Keys.onPressed: (event) => { + if ( // Prevent de-select + event.key === Qt.Key_Control || + event.key == Qt.Key_Shift || + event.key == Qt.Key_Alt || + event.key == Qt.Key_Meta + ) { + event.accepted = true + } + // Ctrl + S to save + if ((event.key === Qt.Key_S) && event.modifiers == Qt.ControlModifier) { + root.saveMessage(); + event.accepted = true; + } + } + ColumnLayout { id: columnLayout @@ -149,29 +189,12 @@ Rectangle { AiMessageControlButton { id: editButton activated: root.editing + enabled: root.messageData.done buttonIcon: "edit" onClicked: { root.editing = !root.editing if (!root.editing) { // Save changes - // Get all Loader children (each represents a segment) - const segments = messageContentColumnLayout.children - .map(child => child.segment) - .filter(segment => (segment)); - // console.log("Segments: " + JSON.stringify(segments)) - - // Reconstruct markdown - const newContent = segments.map(segment => { - if (segment.type === "code") { - const lang = segment.lang ? segment.lang : ""; - // Remove trailing newlines - const code = segment.content.replace(/\n+$/, ""); - return "```" + lang + "\n" + code + "\n```"; - } else { - return segment.content; - } - }).join(""); - - root.messageData.content = newContent; + root.saveMessage() } } StyledToolTip { @@ -204,6 +227,8 @@ Rectangle { ColumnLayout { id: messageContentColumnLayout + + spacing: 0 Repeater { model: ScriptModel { values: { @@ -223,6 +248,7 @@ Rectangle { Component { // Text block id: textBlockComponent TextArea { + Layout.fillWidth: true readOnly: !root.editing renderType: Text.NativeRendering font.family: Appearance.font.family.reading @@ -240,9 +266,6 @@ Rectangle { } Keys.onPressed: (event) => { - if (event.key === Qt.Key_Control || event.key == Qt.Key_Shift || event.key == Qt.Key_Alt || event.key == Qt.Key_Meta) { // Prevent de-select - event.accepted = true - } if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) { messageText.copy() event.accepted = true @@ -267,7 +290,8 @@ Rectangle { id: codeBlockComponent ColumnLayout { spacing: codeBlockComponentSpacing - Layout.fillWidth: true + anchors.left: parent.left + anchors.right: parent.right Rectangle { // Code background Layout.fillWidth: true @@ -276,10 +300,15 @@ Rectangle { bottomLeftRadius: Appearance.rounding.unsharpen bottomRightRadius: Appearance.rounding.unsharpen color: Appearance.m3colors.m3surfaceContainerHighest - implicitHeight: codeBlockTitleBarRowLayout.implicitHeight + implicitHeight: codeBlockTitleBarRowLayout.implicitHeight + codeBlockHeaderPadding * 2 RowLayout { // Language and buttons id: codeBlockTitleBarRowLayout + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: codeBlockHeaderPadding + anchors.rightMargin: codeBlockHeaderPadding spacing: 5 StyledText { @@ -296,6 +325,19 @@ Rectangle { } Item { Layout.fillWidth: true } + + AiMessageControlButton { + id: copyCodeButton + buttonIcon: "content_copy" + onClicked: { + Hyprland.dispatch(`exec wl-copy '${StringUtils.unEscapeBackslashes( + StringUtils.shellSingleQuoteEscape(segment.content) + )}'`) + } + StyledToolTip { + content: qsTr("Copy code") + } + } } } @@ -342,7 +384,6 @@ Rectangle { topRightRadius: Appearance.rounding.unsharpen bottomRightRadius: codeBlockBackgroundRounding color: Appearance.colors.colLayer2 - // implicitWidth: codeTextArea.implicitWidth implicitHeight: codeTextArea.implicitHeight ScrollView { @@ -350,11 +391,35 @@ Rectangle { Layout.fillWidth: true Layout.fillHeight: true implicitWidth: parent.width - implicitHeight: codeTextArea.contentHeight - contentWidth: codeTextArea.contentWidth - contentHeight: codeTextArea.contentHeight + implicitHeight: codeTextArea.implicitHeight + 1 + contentWidth: codeTextArea.width - 1 + // contentHeight: codeTextArea.contentHeight clip: true ScrollBar.vertical.policy: ScrollBar.AlwaysOff + + ScrollBar.horizontal: ScrollBar { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + padding: 5 + policy: ScrollBar.AsNeeded + opacity: visualSize == 1 ? 0 : 1 + visible: opacity > 0 + + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } + + contentItem: Rectangle { + implicitHeight: 6 + radius: Appearance.rounding.small + color: Appearance.colors.colLayer2Active + } + } TextArea { // Code @@ -382,13 +447,6 @@ Rectangle { codeTextArea.insert(cursor, " "); codeTextArea.cursorPosition = cursor + 4; event.accepted = true; - } else if ( - event.key === Qt.Key_Control || - event.key == Qt.Key_Shift || - event.key == Qt.Key_Alt || - event.key == Qt.Key_Meta - ) { - event.accepted = true; } else if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) { messageText.copy(); event.accepted = true; From 47a8149968a42b5ca5e0ca1a041a4d62df258aa6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 7 May 2025 19:13:44 +0200 Subject: [PATCH 194/824] ai chat: animated button color --- .../aiChat/AiMessageControlButton.qml | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml index c36406a7a..56de474dd 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml @@ -18,20 +18,37 @@ Button { background: Rectangle { radius: Appearance.rounding.small - color: button.activated ? - (button.down ? Appearance.colors.colPrimaryActive : + color: !button.enabled ? Appearance.transparentize(Appearance.m3colors.m3surfaceContainerHighest, 1) : + button.activated ? (button.down ? Appearance.colors.colPrimaryActive : button.hovered ? Appearance.colors.colPrimaryHover : Appearance.m3colors.m3primary) : (button.down ? Appearance.colors.colSurfaceContainerHighestActive : button.hovered ? Appearance.colors.colSurfaceContainerHighestHover : Appearance.transparentize(Appearance.m3colors.m3surfaceContainerHighest, 1)) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } } contentItem: MaterialSymbol { horizontalAlignment: Text.AlignHCenter font.pixelSize: Appearance.font.pixelSize.large - color: button.activated ? Appearance.m3colors.m3onPrimary : - Appearance.m3colors.m3onSurface text: buttonIcon + color: button.activated ? Appearance.m3colors.m3onPrimary : + button.enabled ? Appearance.m3colors.m3onSurface : + Appearance.colors.colOnLayer1Inactive + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } } } From 0a331061c3455ac617bc8526a93078b16e1f33b1 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 7 May 2025 23:48:24 +0200 Subject: [PATCH 195/824] fix string escaping --- .../modules/common/functions/string_utils.js | 8 ++----- .../modules/sidebarLeft/aiChat/AiMessage.qml | 21 ++++++++++++++----- .config/quickshell/services/Ai.qml | 6 +++--- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/.config/quickshell/modules/common/functions/string_utils.js b/.config/quickshell/modules/common/functions/string_utils.js index ee7b3eaeb..9fa704ada 100644 --- a/.config/quickshell/modules/common/functions/string_utils.js +++ b/.config/quickshell/modules/common/functions/string_utils.js @@ -10,9 +10,9 @@ function getDomain(url) { } function shellSingleQuoteEscape(str) { - // First escape backslashes, then escape single quotes + // escape single quotes return String(str) - .replace(/\\/g, '\\\\') + // .replace(/\\/g, '\\\\') .replace(/'/g, "'\\''"); } @@ -33,7 +33,3 @@ function splitMarkdownBlocks(markdown) { } return result; } - -function unEscapeBackslashes(str) { - return str.replace(/\\\\/g, '\\'); -} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index e09a1a37d..eb85dfd9d 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -27,6 +27,7 @@ Rectangle { property real codeBlockHeaderPadding: 3 property real codeBlockComponentSpacing: 2 + property bool enableMouseSelection: false property bool renderMarkdown: true property bool editing: false @@ -250,6 +251,7 @@ Rectangle { TextArea { Layout.fillWidth: true readOnly: !root.editing + selectByMouse: root.enableMouseSelection || root.editing renderType: Text.NativeRendering font.family: Appearance.font.family.reading font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text @@ -281,7 +283,8 @@ Rectangle { anchors.fill: parent acceptedButtons: Qt.NoButton // Only for hover hoverEnabled: true - cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.IBeamCursor + cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : + (root.enableMouseSelection || root.editing) ? Qt.IBeamCursor : Qt.ArrowCursor } } } @@ -330,9 +333,7 @@ Rectangle { id: copyCodeButton buttonIcon: "content_copy" onClicked: { - Hyprland.dispatch(`exec wl-copy '${StringUtils.unEscapeBackslashes( - StringUtils.shellSingleQuoteEscape(segment.content) - )}'`) + Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(segment.content)}'`) } StyledToolTip { content: qsTr("Copy code") @@ -422,10 +423,10 @@ Rectangle { } TextArea { // Code - id: codeTextArea Layout.fillWidth: true readOnly: !root.editing + selectByMouse: root.enableMouseSelection || root.editing renderType: Text.NativeRendering font.family: Appearance.font.family.monospace font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text @@ -463,6 +464,16 @@ Rectangle { } } } + + // MouseArea to block scrolling + MouseArea { + id: codeBlockMouseArea + anchors.fill: parent + acceptedButtons: root.editing ? Qt.NoButton : Qt.LeftButton + onWheel: (event) => { + event.accepted = false + } + } } } } diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index aac7b8e9e..a4791b04d 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -249,7 +249,7 @@ Singleton { + ` ${headerString}` + ' -H "Authorization: Bearer ${API_KEY}"' + ` -d '${StringUtils.shellSingleQuoteEscape(JSON.stringify(data))}'` - // console.log("Request command: ", requestCommandString); + console.log("Request command: ", requestCommandString); requester.command = baseCommand.concat([requestCommandString]); /* Reset vars and make the request */ @@ -302,7 +302,7 @@ Singleton { if (dataJson.done) requester.message.done = true; } catch (e) { - console.log("Could not parse response: ", e); + console.log("[AI] Could not parse response from stream: ", e); requester.message.content += cleanData; } } @@ -314,7 +314,7 @@ Singleton { const parsedResponse = JSON.parse(requester.message.content + "]"); requester.message.content = `\`\`\`json\n${JSON.stringify(parsedResponse, null, 2)}\n\`\`\``; } catch (e) { - console.log("Could not parse response: ", e); + console.log("[AI] Could not parse response on exit: ", e); } } } From 6223320d7d320430afc5f3a833f5a375bb98db79 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 8 May 2025 11:29:19 +0200 Subject: [PATCH 196/824] ai chat: code block: i beam cursor when editing --- .config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index eb85dfd9d..eebed5b87 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -470,6 +470,7 @@ Rectangle { id: codeBlockMouseArea anchors.fill: parent acceptedButtons: root.editing ? Qt.NoButton : Qt.LeftButton + cursorShape: (root.enableMouseSelection || root.editing) ? Qt.IBeamCursor : Qt.ArrowCursor onWheel: (event) => { event.accepted = false } From 3d6e7970acf8a727df757b7d54b07d530b020467 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 8 May 2025 11:53:59 +0200 Subject: [PATCH 197/824] adjust window rounding to match vscode & zen browser --- .config/hypr/hyprland/general.conf | 2 +- .config/quickshell/modules/bar/Bar.qml | 2 +- .config/quickshell/modules/common/Appearance.qml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.config/hypr/hyprland/general.conf b/.config/hypr/hyprland/general.conf index 132efc873..bcb637e7c 100644 --- a/.config/hypr/hyprland/general.conf +++ b/.config/hypr/hyprland/general.conf @@ -65,7 +65,7 @@ dwindle { } decoration { - rounding = 20 + rounding = 18 blur { enabled = true diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 1e14d60f8..7975b1173 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -316,7 +316,7 @@ Scope { Item { anchors.left: parent.left anchors.right: parent.right - anchors.bottom: parent.bottom + anchors.top: barContent.bottom height: Appearance.rounding.screenRounding RoundCorner { diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index e2e9809f0..a96ea6a34 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -146,10 +146,10 @@ Singleton { property int verysmall: 8 property int small: 12 property int normal: 17 - property int large: 25 + property int large: 23 property int full: 9999 property int screenRounding: large - property int windowRounding: 20 + property int windowRounding: 18 } font: QtObject { From 4a87cf5c8bc00d26ba294a9c80855562faf852ef Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 8 May 2025 11:55:12 +0200 Subject: [PATCH 198/824] move grimblast & record script out of ags folder --- .config/hypr/hyprland/keybinds.conf | 14 ++++++------- .config/hypr/hyprland/record-script.sh | 29 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 8 deletions(-) create mode 100755 .config/hypr/hyprland/record-script.sh diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index c86220d18..98d46c095 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -53,16 +53,15 @@ bind = Ctrl+Super+Shift,S,exec,grim -g "$(slurp $SLURP_ARGS)" "tmp.png" && tesse bind = Super+Shift, C, exec, hyprpicker -a # Pick color (Hex) >> clipboard # Fullscreen screenshot bindl=,Print,exec,grim - | wl-copy # Screenshot >> clipboard -bindl= Ctrl,Print, exec, mkdir -p ~/Pictures/Screenshots && ~/.config/ags/scripts/grimblast.sh copysave screen ~/Pictures/Screenshots/Screenshot_"$(date '+%Y-%m-%d_%H.%M.%S')".png # Screenshot >> clipboard & file +bindl= Ctrl,Print, exec, mkdir -p ~/Pictures/Screenshots && grimblast copysave screen ~/Pictures/Screenshots/Screenshot_"$(date '+%Y-%m-%d_%H.%M.%S')".png # Screenshot >> clipboard & file # AI bind = Super+Shift+Alt, mouse:273, exec, ~/.config/ags/scripts/ai/primary-buffer-query.sh # Provide AI response for selected text # Recording stuff -bind = Super+Alt, R, exec, ~/.config/ags/scripts/record-script.sh # Record region (no sound) -bind = Ctrl+Alt, R, exec, ~/.config/ags/scripts/record-script.sh --fullscreen # [hidden] Record screen (no sound) -bind = Super+Shift+Alt, R, exec, ~/.config/ags/scripts/record-script.sh --fullscreen-sound # Record screen (with sound) +bind = Super+Alt, R, exec, ~/.config/hypr/hyprland/record-script.sh # Record region (no sound) +bind = Ctrl+Alt, R, exec, ~/.config/hypr/hyprland/record-script.sh --fullscreen # [hidden] Record screen (no sound) +bind = Super+Shift+Alt, R, exec, ~/.config/hypr/hyprland/record-script.sh --fullscreen-sound # Record screen (with sound) ##! Session -bind = Ctrl+Super, L, exec, agsv1 run-js 'lock.lock()' # [hidden] bind = Super, L, exec, loginctl lock-session # Lock bind = Super+Shift, L, exec, loginctl lock-session # [hidden] bindl = Super+Shift, L, exec, sleep 0.1 && systemctl suspend || loginctl suspend # Suspend system @@ -177,8 +176,7 @@ bind = Alt, Tab, bringactivetotop, # [hidden] bring it to the top #! ##! Widgets -bindr = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool; agsv1 & # Restart widgets -bindr = Ctrl+Super+Alt, R, exec, hyprctl reload; killall agsv1 ydotool; agsv1 & # [hidden] +bindr = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool qs; qs & # Restart widgets bind = Ctrl+Alt, Slash, exec, agsv1 run-js 'cycleMode();' # Cycle bar mode (normal, focus) bind = Super, Slash, exec, for ((i=0; i<$(hyprctl monitors -j | jq length); i++)); do agsv1 -t "cheatsheet""$i"; done # Show cheatsheet bind = Super, M, exec, agsv1 run-js 'openMusicControls.value = (!mpris.getPlayer() ? false : !openMusicControls.value);' # Toggle music controls @@ -213,7 +211,7 @@ bind = Super, E, exec, nautilus --new-window # Launch file manager (Nautilus) bind = Super, Z, exec, Zed # Launch Zed (editor) bind = Super, C, exec, code # Launch VSCode (editor) bind = Super+Alt, E, exec, thunar # [hidden] -bind = Super, W, exec, google-chrome-stable || firefox # [hidden] Let's not give people (more) reason to shit on my rice +bind = Super, W, exec, zen-browser # [hidden] bind = Ctrl+Super, W, exec, firefox # Launch Firefox (browser) bind = Super, X, exec, gnome-text-editor --new-window # Launch GNOME Text Editor bind = Super+Shift, W, exec, wps # Launch WPS Office diff --git a/.config/hypr/hyprland/record-script.sh b/.config/hypr/hyprland/record-script.sh new file mode 100755 index 000000000..f9dd16dc7 --- /dev/null +++ b/.config/hypr/hyprland/record-script.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +getdate() { + date '+%Y-%m-%d_%H.%M.%S' +} +getaudiooutput() { + pactl list sources | grep 'Name' | grep 'monitor' | cut -d ' ' -f2 +} +getactivemonitor() { + hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name' +} + +mkdir -p "$(xdg-user-dir VIDEOS)" +cd "$(xdg-user-dir VIDEOS)" || exit +if pgrep wf-recorder > /dev/null; then + notify-send "Recording Stopped" "Stopped" -a 'record-script.sh' & + pkill wf-recorder & +else + notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'record-script.sh' + if [[ "$1" == "--sound" ]]; then + wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$(slurp)" --audio="$(getaudiooutput)" & disown + elif [[ "$1" == "--fullscreen-sound" ]]; then + wf-recorder -o $(getactivemonitor) --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --audio="$(getaudiooutput)" & disown + elif [[ "$1" == "--fullscreen" ]]; then + wf-recorder -o $(getactivemonitor) --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t & disown + else + wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$(slurp)" & disown + fi +fi From 706fd5cab8f804f0e9389cf7ee7669fed63ef6cd Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 8 May 2025 14:46:21 +0200 Subject: [PATCH 199/824] refractor --- .../modules/sidebarLeft/aiChat/AiMessage.qml | 241 +----------------- .../sidebarLeft/aiChat/MessageCodeBlock.qml | 213 ++++++++++++++++ .../sidebarLeft/aiChat/MessageTextBlock.qml | 63 +++++ 3 files changed, 282 insertions(+), 235 deletions(-) create mode 100644 .config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml create mode 100644 .config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index eebed5b87..d570fff1e 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -241,241 +241,12 @@ Rectangle { delegate: Loader { Layout.fillWidth: true property var segment: modelData - sourceComponent: modelData.type === "code" ? codeBlockComponent : textBlockComponent - } - } - } - - Component { // Text block - id: textBlockComponent - TextArea { - Layout.fillWidth: true - readOnly: !root.editing - selectByMouse: root.enableMouseSelection || root.editing - renderType: Text.NativeRendering - font.family: Appearance.font.family.reading - font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text - font.pixelSize: Appearance.font.pixelSize.small - selectedTextColor: Appearance.m3colors.m3onSecondaryContainer - selectionColor: Appearance.m3colors.m3secondaryContainer - wrapMode: TextEdit.Wrap - color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 - textFormat: root.renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText - text: messageData.thinking ? qsTr("Waiting for response...") : segment.content - - onTextChanged: { - segment.content = text - } - - Keys.onPressed: (event) => { - if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) { - messageText.copy() - event.accepted = true - } - } - - onLinkActivated: (link) => { - Qt.openUrlExternally(link) - Hyprland.dispatch("global quickshell:sidebarLeftClose") - } - - MouseArea { // Pointing hand for links - anchors.fill: parent - acceptedButtons: Qt.NoButton // Only for hover - hoverEnabled: true - cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : - (root.enableMouseSelection || root.editing) ? Qt.IBeamCursor : Qt.ArrowCursor - } - } - } - - Component { // Code block - id: codeBlockComponent - ColumnLayout { - spacing: codeBlockComponentSpacing - anchors.left: parent.left - anchors.right: parent.right - - Rectangle { // Code background - Layout.fillWidth: true - topLeftRadius: codeBlockBackgroundRounding - topRightRadius: codeBlockBackgroundRounding - bottomLeftRadius: Appearance.rounding.unsharpen - bottomRightRadius: Appearance.rounding.unsharpen - color: Appearance.m3colors.m3surfaceContainerHighest - implicitHeight: codeBlockTitleBarRowLayout.implicitHeight + codeBlockHeaderPadding * 2 - - RowLayout { // Language and buttons - id: codeBlockTitleBarRowLayout - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: codeBlockHeaderPadding - anchors.rightMargin: codeBlockHeaderPadding - spacing: 5 - - StyledText { - id: codeBlockLanguage - Layout.alignment: Qt.AlignLeft - Layout.fillWidth: false - Layout.topMargin: 7 - Layout.bottomMargin: 7 - Layout.leftMargin: 10 - font.pixelSize: Appearance.font.pixelSize.small - font.weight: Font.DemiBold - color: Appearance.colors.colOnLayer2 - text: segment.lang ? Repository.definitionForName(segment.lang).name : "plain" - } - - Item { Layout.fillWidth: true } - - AiMessageControlButton { - id: copyCodeButton - buttonIcon: "content_copy" - onClicked: { - Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(segment.content)}'`) - } - StyledToolTip { - content: qsTr("Copy code") - } - } - } - } - - RowLayout { // Line numbers and code - spacing: codeBlockComponentSpacing - - Rectangle { // Line numbers - implicitWidth: 40 - Layout.fillHeight: true - Layout.fillWidth: false - topLeftRadius: Appearance.rounding.unsharpen - bottomLeftRadius: codeBlockBackgroundRounding - topRightRadius: Appearance.rounding.unsharpen - bottomRightRadius: Appearance.rounding.unsharpen - color: Appearance.colors.colLayer2 - - ColumnLayout { - id: lineNumberColumnLayout - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 5 - anchors.verticalCenter: parent.verticalCenter - spacing: 0 - - Repeater { - model: codeTextArea.text.split("\n").length - Text { - Layout.fillWidth: true - Layout.alignment: Qt.AlignRight - font.family: Appearance.font.family.monospace - font.pixelSize: Appearance.font.pixelSize.small - color: Appearance.colors.colSubtext - horizontalAlignment: Text.AlignRight - text: index + 1 - } - } - } - } - - Rectangle { // Code background - Layout.fillWidth: true - topLeftRadius: Appearance.rounding.unsharpen - bottomLeftRadius: Appearance.rounding.unsharpen - topRightRadius: Appearance.rounding.unsharpen - bottomRightRadius: codeBlockBackgroundRounding - color: Appearance.colors.colLayer2 - implicitHeight: codeTextArea.implicitHeight - - ScrollView { - id: codeScrollView - Layout.fillWidth: true - Layout.fillHeight: true - implicitWidth: parent.width - implicitHeight: codeTextArea.implicitHeight + 1 - contentWidth: codeTextArea.width - 1 - // contentHeight: codeTextArea.contentHeight - clip: true - ScrollBar.vertical.policy: ScrollBar.AlwaysOff - - ScrollBar.horizontal: ScrollBar { - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - padding: 5 - policy: ScrollBar.AsNeeded - opacity: visualSize == 1 ? 0 : 1 - visible: opacity > 0 - - Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } - } - - contentItem: Rectangle { - implicitHeight: 6 - radius: Appearance.rounding.small - color: Appearance.colors.colLayer2Active - } - } - - TextArea { // Code - id: codeTextArea - Layout.fillWidth: true - readOnly: !root.editing - selectByMouse: root.enableMouseSelection || root.editing - renderType: Text.NativeRendering - font.family: Appearance.font.family.monospace - font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text - font.pixelSize: Appearance.font.pixelSize.small - selectedTextColor: Appearance.m3colors.m3onSecondaryContainer - selectionColor: Appearance.m3colors.m3secondaryContainer - // wrapMode: TextEdit.Wrap - color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 - - text: segment.content - onTextChanged: { - segment.content = text - } - - Keys.onPressed: (event) => { - if (event.key === Qt.Key_Tab) { - // Insert 4 spaces at cursor - const cursor = codeTextArea.cursorPosition; - codeTextArea.insert(cursor, " "); - codeTextArea.cursorPosition = cursor + 4; - event.accepted = true; - } else if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) { - messageText.copy(); - event.accepted = true; - } - } - - SyntaxHighlighter { - id: highlighter - textEdit: codeTextArea - repository: Repository - definition: Repository.definitionForName(segment.lang || "plaintext") - // definition: Repository.definitionForName("cpp") - theme: Appearance.syntaxHighlightingTheme - } - } - } - - // MouseArea to block scrolling - MouseArea { - id: codeBlockMouseArea - anchors.fill: parent - acceptedButtons: root.editing ? Qt.NoButton : Qt.LeftButton - cursorShape: (root.enableMouseSelection || root.editing) ? Qt.IBeamCursor : Qt.ArrowCursor - onWheel: (event) => { - event.accepted = false - } - } - } + property var messageData: root.messageData + property var editing: root.editing + property var renderMarkdown: root.renderMarkdown + property var enableMouseSelection: root.enableMouseSelection + + source: modelData.type === "code" ? "MessageCodeBlock.qml" : "MessageTextBlock.qml" } } } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml new file mode 100644 index 000000000..cc945f4a8 --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml @@ -0,0 +1,213 @@ +pragma ComponentBehavior: Bound + +import "root:/" +import "root:/services" +import "root:/modules/common/" +import "root:/modules/common/widgets" +import "../" +import "root:/modules/common/functions/string_utils.js" as StringUtils +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io +import Quickshell +import Quickshell.Widgets +import Quickshell.Wayland +import Quickshell.Hyprland +import Qt5Compat.GraphicalEffects +import org.kde.syntaxhighlighting + +ColumnLayout { + // These are needed on the parent loader + property bool editing: parent.editing ?? false + property bool renderMarkdown: parent.renderMarkdown ?? true + property bool enableMouseSelection: parent.enableMouseSelection ?? false + property var segment: parent.segment ?? {} + property var messageData: parent.messageData ?? {} + + spacing: codeBlockComponentSpacing + anchors.left: parent.left + anchors.right: parent.right + + Rectangle { // Code background + Layout.fillWidth: true + topLeftRadius: codeBlockBackgroundRounding + topRightRadius: codeBlockBackgroundRounding + bottomLeftRadius: Appearance.rounding.unsharpen + bottomRightRadius: Appearance.rounding.unsharpen + color: Appearance.m3colors.m3surfaceContainerHighest + implicitHeight: codeBlockTitleBarRowLayout.implicitHeight + codeBlockHeaderPadding * 2 + + RowLayout { // Language and buttons + id: codeBlockTitleBarRowLayout + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: codeBlockHeaderPadding + anchors.rightMargin: codeBlockHeaderPadding + spacing: 5 + + StyledText { + id: codeBlockLanguage + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: false + Layout.topMargin: 7 + Layout.bottomMargin: 7 + Layout.leftMargin: 10 + font.pixelSize: Appearance.font.pixelSize.small + font.weight: Font.DemiBold + color: Appearance.colors.colOnLayer2 + text: segment.lang ? Repository.definitionForName(segment.lang).name : "plain" + } + + Item { Layout.fillWidth: true } + + AiMessageControlButton { + id: copyCodeButton + buttonIcon: "content_copy" + onClicked: { + Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(segment.content)}'`) + } + StyledToolTip { + content: qsTr("Copy code") + } + } + } + } + + RowLayout { // Line numbers and code + spacing: codeBlockComponentSpacing + + Rectangle { // Line numbers + implicitWidth: 40 + Layout.fillHeight: true + Layout.fillWidth: false + topLeftRadius: Appearance.rounding.unsharpen + bottomLeftRadius: codeBlockBackgroundRounding + topRightRadius: Appearance.rounding.unsharpen + bottomRightRadius: Appearance.rounding.unsharpen + color: Appearance.colors.colLayer2 + + ColumnLayout { + id: lineNumberColumnLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 5 + anchors.verticalCenter: parent.verticalCenter + spacing: 0 + + Repeater { + model: codeTextArea.text.split("\n").length + Text { + Layout.fillWidth: true + Layout.alignment: Qt.AlignRight + font.family: Appearance.font.family.monospace + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.colors.colSubtext + horizontalAlignment: Text.AlignRight + text: index + 1 + } + } + } + } + + Rectangle { // Code background + Layout.fillWidth: true + topLeftRadius: Appearance.rounding.unsharpen + bottomLeftRadius: Appearance.rounding.unsharpen + topRightRadius: Appearance.rounding.unsharpen + bottomRightRadius: codeBlockBackgroundRounding + color: Appearance.colors.colLayer2 + implicitHeight: codeTextArea.implicitHeight + + ScrollView { + id: codeScrollView + Layout.fillWidth: true + Layout.fillHeight: true + implicitWidth: parent.width + implicitHeight: codeTextArea.implicitHeight + 1 + contentWidth: codeTextArea.width - 1 + // contentHeight: codeTextArea.contentHeight + clip: true + ScrollBar.vertical.policy: ScrollBar.AlwaysOff + + ScrollBar.horizontal: ScrollBar { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + padding: 5 + policy: ScrollBar.AsNeeded + opacity: visualSize == 1 ? 0 : 1 + visible: opacity > 0 + + Behavior on opacity { + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } + + contentItem: Rectangle { + implicitHeight: 6 + radius: Appearance.rounding.small + color: Appearance.colors.colLayer2Active + } + } + + TextArea { // Code + id: codeTextArea + Layout.fillWidth: true + readOnly: !editing + selectByMouse: enableMouseSelection || editing + renderType: Text.NativeRendering + font.family: Appearance.font.family.monospace + font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text + font.pixelSize: Appearance.font.pixelSize.small + selectedTextColor: Appearance.m3colors.m3onSecondaryContainer + selectionColor: Appearance.m3colors.m3secondaryContainer + // wrapMode: TextEdit.Wrap + color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 + + text: segment.content + onTextChanged: { + segment.content = text + } + + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Tab) { + // Insert 4 spaces at cursor + const cursor = codeTextArea.cursorPosition; + codeTextArea.insert(cursor, " "); + codeTextArea.cursorPosition = cursor + 4; + event.accepted = true; + } else if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) { + messageText.copy(); + event.accepted = true; + } + } + + SyntaxHighlighter { + id: highlighter + textEdit: codeTextArea + repository: Repository + definition: Repository.definitionForName(segment.lang || "plaintext") + // definition: Repository.definitionForName("cpp") + theme: Appearance.syntaxHighlightingTheme + } + } + } + + // MouseArea to block scrolling + MouseArea { + id: codeBlockMouseArea + anchors.fill: parent + acceptedButtons: editing ? Qt.NoButton : Qt.LeftButton + cursorShape: (enableMouseSelection || editing) ? Qt.IBeamCursor : Qt.ArrowCursor + onWheel: (event) => { + event.accepted = false + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml new file mode 100644 index 000000000..7ac600a00 --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml @@ -0,0 +1,63 @@ +pragma ComponentBehavior: Bound + +import "root:/" +import "root:/services" +import "root:/modules/common/" +import "root:/modules/common/widgets" +import "../" +import "root:/modules/common/functions/string_utils.js" as StringUtils +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io +import Quickshell +import Quickshell.Widgets +import Quickshell.Wayland +import Quickshell.Hyprland + +TextArea { + // These are needed on the parent loader + property bool editing: parent.editing ?? false + property bool renderMarkdown: parent.renderMarkdown ?? true + property bool enableMouseSelection: parent.enableMouseSelection ?? false + property var segment: parent.segment ?? {} + property var messageData: parent.messageData ?? {} + + Layout.fillWidth: true + readOnly: !editing + selectByMouse: enableMouseSelection || editing + renderType: Text.NativeRendering + font.family: Appearance.font.family.reading + font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text + font.pixelSize: Appearance.font.pixelSize.small + selectedTextColor: Appearance.m3colors.m3onSecondaryContainer + selectionColor: Appearance.m3colors.m3secondaryContainer + wrapMode: TextEdit.Wrap + color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 + textFormat: renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText + text: messageData.thinking ? qsTr("Waiting for response...") : segment.content + + onTextChanged: { + segment.content = text + } + + Keys.onPressed: (event) => { + if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) { + messageText.copy() + event.accepted = true + } + } + + onLinkActivated: (link) => { + Qt.openUrlExternally(link) + Hyprland.dispatch("global quickshell:sidebarLeftClose") + } + + MouseArea { // Pointing hand for links + anchors.fill: parent + acceptedButtons: Qt.NoButton // Only for hover + hoverEnabled: true + cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : + (enableMouseSelection || editing) ? Qt.IBeamCursor : Qt.ArrowCursor + } +} From 8096e91e55d47dbb23332d4b359353e60942e38f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 8 May 2025 14:57:07 +0200 Subject: [PATCH 200/824] more `?.` --- .../modules/sidebarLeft/aiChat/MessageCodeBlock.qml | 10 +++++----- .../modules/sidebarLeft/aiChat/MessageTextBlock.qml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml index cc945f4a8..a0b2928bf 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml @@ -19,11 +19,11 @@ import org.kde.syntaxhighlighting ColumnLayout { // These are needed on the parent loader - property bool editing: parent.editing ?? false - property bool renderMarkdown: parent.renderMarkdown ?? true - property bool enableMouseSelection: parent.enableMouseSelection ?? false - property var segment: parent.segment ?? {} - property var messageData: parent.messageData ?? {} + property bool editing: parent?.editing ?? false + property bool renderMarkdown: parent?.renderMarkdown ?? true + property bool enableMouseSelection: parent?.enableMouseSelection ?? false + property var segment: parent?.segment ?? {} + property var messageData: parent?.messageData ?? {} spacing: codeBlockComponentSpacing anchors.left: parent.left diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml index 7ac600a00..f750e19f4 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml @@ -17,11 +17,11 @@ import Quickshell.Hyprland TextArea { // These are needed on the parent loader - property bool editing: parent.editing ?? false - property bool renderMarkdown: parent.renderMarkdown ?? true - property bool enableMouseSelection: parent.enableMouseSelection ?? false - property var segment: parent.segment ?? {} - property var messageData: parent.messageData ?? {} + property bool editing: parent?.editing ?? false + property bool renderMarkdown: parent?.renderMarkdown ?? true + property bool enableMouseSelection: parent?.enableMouseSelection ?? false + property var segment: parent?.segment ?? {} + property var messageData: parent?.messageData ?? {} Layout.fillWidth: true readOnly: !editing From c94ec8e6b21ff239eabb1efd5801d4659911498b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 8 May 2025 17:56:15 +0200 Subject: [PATCH 201/824] ws num peek: don't peek if user already does smth w/ super quickly --- .config/quickshell/GlobalStates.qml | 6 ++++++ .config/quickshell/modules/overview/Overview.qml | 11 +++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/GlobalStates.qml b/.config/quickshell/GlobalStates.qml index 154435e86..470dfc4f8 100644 --- a/.config/quickshell/GlobalStates.qml +++ b/.config/quickshell/GlobalStates.qml @@ -12,6 +12,12 @@ Singleton { property int sidebarRightOpenCount: 0 property bool overviewOpen: false property bool workspaceShowNumbers: false + property bool superReleaseMightTrigger: true + + // When user is not reluctant while pressing super, they probably don't need to see workspace numbers + onSuperReleaseMightTriggerChanged: { + workspaceShowNumbersTimer.stop() + } Timer { id: workspaceShowNumbersTimer diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 2ddbe9b07..23be7ef58 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -11,7 +11,6 @@ import Quickshell.Hyprland Scope { id: root - property bool overviewReleaseMightTrigger: true Variants { model: Quickshell.screens @@ -115,7 +114,7 @@ Scope { GlobalStates.overviewOpen = true } function toggleReleaseInterrupt() { - root.overviewReleaseMightTrigger = false + GlobalStates.superReleaseMightTrigger = false } } @@ -140,12 +139,12 @@ Scope { description: "Toggles overview on release" onPressed: { - root.overviewReleaseMightTrigger = true + GlobalStates.superReleaseMightTrigger = true } onReleased: { - if (!root.overviewReleaseMightTrigger) { - root.overviewReleaseMightTrigger = true + if (!GlobalStates.superReleaseMightTrigger) { + GlobalStates.superReleaseMightTrigger = true return } GlobalStates.overviewOpen = !GlobalStates.overviewOpen @@ -158,7 +157,7 @@ Scope { "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything." onPressed: { - root.overviewReleaseMightTrigger = false + GlobalStates.superReleaseMightTrigger = false } } From 6df4806b82a45cb570e949020d3ff575960d1714 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 8 May 2025 17:56:43 +0200 Subject: [PATCH 202/824] ai chat: fix code block line numbers --- .../quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml index a0b2928bf..96213e25c 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml @@ -99,6 +99,7 @@ ColumnLayout { Repeater { model: codeTextArea.text.split("\n").length Text { + required property int index Layout.fillWidth: true Layout.alignment: Qt.AlignRight font.family: Appearance.font.family.monospace From 3a6d0ef4688a9f5aa43e54f81997661d335cc733 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 8 May 2025 18:04:09 +0200 Subject: [PATCH 203/824] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 84be4f32b..a6fc98402 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ - **Assumption**: You are already using the AGS illogical-impulse - **Install Qt packages** (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-graphicaleffects qt5-imageformats qt5-quickcontrols qt5-quickcontrols2 qt5-svg qt5-translations qt5-wayland qt5-x11extras qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland qt6-webchannel qt6-webengine qt6-websockets qt6-webview syntax-highlighting` - **Install quickshell and more stuff**: `yay -S quickshell matugen-bin grimblast` -- **Copy** `.config/quickshell` folder and hyprland config files in `.config/hypr/hyprland/` (backing up is your responsibility) -- **Run quickshell** with `qs` and see how things are - it's not finished for daily use, but **feedback is very welcome** +- **Copy** `.config/quickshell` folder and hyprland config files in `.config/hypr/hyprland/` (backing up is your responsibility) (or you can create a new user) +- **Run quickshell** with `qs` and see how things are - it's not finished, but **feedback is very welcome** - We currently have bar, right sidebar, search/overview - Tips: scrolled windows are flickable From ee42d9f1427155ff5bd0bbb8466741176bac002d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 8 May 2025 21:20:30 +0200 Subject: [PATCH 204/824] matugen: fix hyprland file path --- .config/matugen/config.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.config/matugen/config.toml b/.config/matugen/config.toml index f56c1a9aa..487a2b170 100644 --- a/.config/matugen/config.toml +++ b/.config/matugen/config.toml @@ -11,7 +11,7 @@ output_path = '~/.local/state/quickshell/user/generated/colors.json' [templates.hyprland] input_path = '~/.config/matugen/templates/hyprland/colors.conf' -output_path = '~/.config/hypr/hyprland/hyprland/colors.conf' +output_path = '~/.config/hypr/hyprland/colors.conf' [templates.hyprlock] input_path = '~/.config/matugen/templates/hyprland/hyprlock.conf' @@ -29,3 +29,4 @@ output_path = '~/.config/gtk-3.0/gtk.css' input_path = '~/.config/matugen/templates/gtk/gtk-colors.css' output_path = '~/.config/gtk-4.0/gtk.css' + From e56a3a591b3a79a460f3bb4efac7418014775459 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 9 May 2025 00:58:42 +0200 Subject: [PATCH 205/824] systray: more readable monochrome icons --- .config/quickshell/modules/bar/SysTrayItem.qml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/bar/SysTrayItem.qml b/.config/quickshell/modules/bar/SysTrayItem.qml index 70804a17e..99e5a258a 100644 --- a/.config/quickshell/modules/bar/SysTrayItem.qml +++ b/.config/quickshell/modules/bar/SysTrayItem.qml @@ -48,10 +48,16 @@ MouseArea { height: parent.height } - ColorOverlay { + Desaturate { + id: desaturatedIcon anchors.fill: trayIcon source: trayIcon - color: Appearance.colors.colOnLayer0 + desaturation: 1 // 1.0 means fully grayscale + } + ColorOverlay { + anchors.fill: desaturatedIcon + source: desaturatedIcon + color: Appearance.transparentize(Appearance.colors.colOnLayer0, 0.5) } } From c3323da840f66a48e5b19052fd12c92b6fe1b09d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 9 May 2025 01:07:31 +0200 Subject: [PATCH 206/824] ai chat: latex rendering --- .../modules/common/functions/string_utils.js | 8 + .../quickshell/modules/sidebarLeft/AiChat.qml | 7 +- .../modules/sidebarLeft/aiChat/AiMessage.qml | 22 ++- .../sidebarLeft/aiChat/MessageCodeBlock.qml | 36 ++--- .../sidebarLeft/aiChat/MessageTextBlock.qml | 141 ++++++++++++++---- .config/quickshell/services/LatexRenderer.qml | 83 +++++++++++ 6 files changed, 240 insertions(+), 57 deletions(-) create mode 100644 .config/quickshell/services/LatexRenderer.qml diff --git a/.config/quickshell/modules/common/functions/string_utils.js b/.config/quickshell/modules/common/functions/string_utils.js index 9fa704ada..97b830bb1 100644 --- a/.config/quickshell/modules/common/functions/string_utils.js +++ b/.config/quickshell/modules/common/functions/string_utils.js @@ -33,3 +33,11 @@ function splitMarkdownBlocks(markdown) { } return result; } + +function trimFileProtocol(str) { + return str.startsWith("file://") ? str.slice(7) : str; +} + +function escapeBackslashes(str) { + return str.replace(/\\/g, '\\\\'); +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 83623f300..07b6a0d66 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -83,7 +83,8 @@ Item { ## ✏️ Markdown test ### Formatting -*Italic*, \`Monospace\`, **Bold**, [Link](https://example.com) +- *Italic*, \`Monospace\`, **Bold**, [Link](https://example.com) +- Arch lincox icon ### Table @@ -114,7 +115,9 @@ int main(int argc, char* argv[]) { ### LaTeX -Inline: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$ +- Simple inline: $\\frac{1}{2} = \\frac{2}{4}$ +- Complex inline: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$ +- Another complex inline: \\\\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\\\] `, Ai.interfaceRole); diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index d570fff1e..77def3827 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -89,19 +89,25 @@ Rectangle { RowLayout { // Header spacing: 15 + Layout.fillWidth: true Rectangle { // Name id: nameWrapper color: Appearance.m3colors.m3secondaryContainer + // color: "transparent" radius: Appearance.rounding.small - implicitWidth: nameRowLayout.implicitWidth + 10 * 2 implicitHeight: Math.max(nameRowLayout.implicitHeight + 5 * 2, 30) + Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter RowLayout { id: nameRowLayout - anchors.centerIn: parent - spacing: 5 + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 10 + anchors.rightMargin: 10 + spacing: 7 Item { Layout.alignment: Qt.AlignVCenter @@ -141,6 +147,8 @@ Rectangle { StyledText { id: providerName Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + elide: Text.ElideRight font.pixelSize: Appearance.font.pixelSize.normal font.weight: Font.DemiBold color: Appearance.m3colors.m3onSecondaryContainer @@ -172,8 +180,6 @@ Rectangle { } } - Item { Layout.fillWidth: true } - RowLayout { spacing: 5 @@ -240,11 +246,15 @@ Rectangle { } delegate: Loader { Layout.fillWidth: true - property var segment: modelData + // property var segment: modelData + property var segmentContent: modelData.content + property var segmentLang: modelData.lang property var messageData: root.messageData property var editing: root.editing property var renderMarkdown: root.renderMarkdown property var enableMouseSelection: root.enableMouseSelection + property bool thinking: root.messageData.thinking + property bool done: root.messageData.done source: modelData.type === "code" ? "MessageCodeBlock.qml" : "MessageTextBlock.qml" } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml index 96213e25c..2cbf4ae77 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml @@ -22,7 +22,8 @@ ColumnLayout { property bool editing: parent?.editing ?? false property bool renderMarkdown: parent?.renderMarkdown ?? true property bool enableMouseSelection: parent?.enableMouseSelection ?? false - property var segment: parent?.segment ?? {} + property var segmentContent: parent?.segmentContent ?? ({}) + property var segmentLang: parent?.segmentLang ?? "plaintext" property var messageData: parent?.messageData ?? {} spacing: codeBlockComponentSpacing @@ -57,7 +58,7 @@ ColumnLayout { font.pixelSize: Appearance.font.pixelSize.small font.weight: Font.DemiBold color: Appearance.colors.colOnLayer2 - text: segment.lang ? Repository.definitionForName(segment.lang).name : "plain" + text: segmentLang ? Repository.definitionForName(segmentLang).name : "plain" } Item { Layout.fillWidth: true } @@ -66,7 +67,7 @@ ColumnLayout { id: copyCodeButton buttonIcon: "content_copy" onClicked: { - Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(segment.content)}'`) + Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(segmentContent)}'`) } StyledToolTip { content: qsTr("Copy code") @@ -160,7 +161,7 @@ ColumnLayout { id: codeTextArea Layout.fillWidth: true readOnly: !editing - selectByMouse: enableMouseSelection || editing + // selectByMouse: enableMouseSelection || editing renderType: Text.NativeRendering font.family: Appearance.font.family.monospace font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text @@ -170,9 +171,9 @@ ColumnLayout { // wrapMode: TextEdit.Wrap color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 - text: segment.content + text: segmentContent onTextChanged: { - segment.content = text + segmentContent = text } Keys.onPressed: (event) => { @@ -183,7 +184,7 @@ ColumnLayout { codeTextArea.cursorPosition = cursor + 4; event.accepted = true; } else if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) { - messageText.copy(); + codeTextArea.copy(); event.accepted = true; } } @@ -192,23 +193,22 @@ ColumnLayout { id: highlighter textEdit: codeTextArea repository: Repository - definition: Repository.definitionForName(segment.lang || "plaintext") - // definition: Repository.definitionForName("cpp") + definition: Repository.definitionForName(segmentLang || "plaintext") theme: Appearance.syntaxHighlightingTheme } } } // MouseArea to block scrolling - MouseArea { - id: codeBlockMouseArea - anchors.fill: parent - acceptedButtons: editing ? Qt.NoButton : Qt.LeftButton - cursorShape: (enableMouseSelection || editing) ? Qt.IBeamCursor : Qt.ArrowCursor - onWheel: (event) => { - event.accepted = false - } - } + // MouseArea { + // id: codeBlockMouseArea + // anchors.fill: parent + // acceptedButtons: editing ? Qt.NoButton : Qt.LeftButton + // cursorShape: (enableMouseSelection || editing) ? Qt.IBeamCursor : Qt.ArrowCursor + // onWheel: (event) => { + // event.accepted = false + // } + // } } } } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml index f750e19f4..11303784a 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml @@ -14,50 +14,129 @@ import Quickshell import Quickshell.Widgets import Quickshell.Wayland import Quickshell.Hyprland +import Qt5Compat.GraphicalEffects -TextArea { +ColumnLayout { + id: root // These are needed on the parent loader property bool editing: parent?.editing ?? false property bool renderMarkdown: parent?.renderMarkdown ?? true property bool enableMouseSelection: parent?.enableMouseSelection ?? false - property var segment: parent?.segment ?? {} + property string segmentContent: parent?.segmentContent ?? ({}) property var messageData: parent?.messageData ?? {} + property bool done: parent?.done ?? true + property list renderedLatexHashes: [] + + property string renderedSegmentContent: "" Layout.fillWidth: true - readOnly: !editing - selectByMouse: enableMouseSelection || editing - renderType: Text.NativeRendering - font.family: Appearance.font.family.reading - font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text - font.pixelSize: Appearance.font.pixelSize.small - selectedTextColor: Appearance.m3colors.m3onSecondaryContainer - selectionColor: Appearance.m3colors.m3secondaryContainer - wrapMode: TextEdit.Wrap - color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 - textFormat: renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText - text: messageData.thinking ? qsTr("Waiting for response...") : segment.content - onTextChanged: { - segment.content = text - } - - Keys.onPressed: (event) => { - if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) { - messageText.copy() - event.accepted = true + function renderLatex() { + // Regex for $...$, $$...$$, \[...\] + // Note: This is a simple approach and may need refinement for edge cases + let regex = /(\$\$([\s\S]+?)\$\$)|(\$([^\$]+?)\$)|(\\\[((?:.|\n)+?)\\\])/g; + let match; + while ((match = regex.exec(segmentContent)) !== null) { + let expression = match[1] || match[2] || match[3]; + if (expression) { + // Qt.callLater(() => { + // }); + const [renderHash, isNew] = LatexRenderer.requestRender(expression.trim()); + if (!renderedLatexHashes.includes(renderHash)) { + renderedLatexHashes.push(renderHash); + } + } } } - onLinkActivated: (link) => { - Qt.openUrlExternally(link) - Hyprland.dispatch("global quickshell:sidebarLeftClose") + function handleRenderedLatex(hash, force = false) { + if (renderedLatexHashes.includes(hash) || force) { + const imagePath = LatexRenderer.renderedImagePaths[hash]; + const markdownImage = `![latex](${imagePath})`; + + const expression = StringUtils.escapeBackslashes(LatexRenderer.processedExpressions[hash]); + renderedSegmentContent = renderedSegmentContent.replace(expression, markdownImage); + } } - MouseArea { // Pointing hand for links - anchors.fill: parent - acceptedButtons: Qt.NoButton // Only for hover - hoverEnabled: true - cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : - (enableMouseSelection || editing) ? Qt.IBeamCursor : Qt.ArrowCursor + onDoneChanged: { + renderLatex() + for (const hash of renderedLatexHashes) { + handleRenderedLatex(hash, true); + } + } + onEditingChanged: { + if (!editing) { + renderLatex() + } + } + + onSegmentContentChanged: { + // console.log("Segment content changed: " + segmentContent); + renderedSegmentContent = segmentContent; + if (!root.editing && segmentContent) { + root.renderLatex(); + } + } + + onRenderedSegmentContentChanged: { + // console.log("Rendered segment content changed: " + renderedSegmentContent); + if (renderedSegmentContent) { + textArea.text = renderedSegmentContent; + } + } + + // When something finishes rendering + // 1. Check if the hash is in the list + // 2. If it is, replace the expression with the image path + Connections { + target: LatexRenderer + function onRenderFinished(hash, imagePath) { + const expression = LatexRenderer.processedExpressions[hash]; + // console.log("Render finished: " + hash + " " + expression); + handleRenderedLatex(hash); + } + } + + TextArea { + id: textArea + + Layout.fillWidth: true + readOnly: !editing + selectByMouse: enableMouseSelection || editing + renderType: Text.NativeRendering + font.family: Appearance.font.family.reading + font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text + font.pixelSize: Appearance.font.pixelSize.small + selectedTextColor: Appearance.m3colors.m3onSecondaryContainer + selectionColor: Appearance.m3colors.m3secondaryContainer + wrapMode: TextEdit.Wrap + color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 + textFormat: renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText + text: qsTr("Waiting for response...") + + onTextChanged: { + segmentContent = text + } + + Keys.onPressed: (event) => { + if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) { + messageText.copy() + event.accepted = true + } + } + + onLinkActivated: (link) => { + Qt.openUrlExternally(link) + Hyprland.dispatch("global quickshell:sidebarLeftClose") + } + + MouseArea { // Pointing hand for links + anchors.fill: parent + acceptedButtons: Qt.NoButton // Only for hover + hoverEnabled: true + cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : + (enableMouseSelection || editing) ? Qt.IBeamCursor : Qt.ArrowCursor + } } } diff --git a/.config/quickshell/services/LatexRenderer.qml b/.config/quickshell/services/LatexRenderer.qml new file mode 100644 index 000000000..2c0b7f3d3 --- /dev/null +++ b/.config/quickshell/services/LatexRenderer.qml @@ -0,0 +1,83 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common/functions/string_utils.js" as StringUtils +import "root:/modules/common" +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland +import Qt.labs.platform + +/** +* Renders LaTeX snippets with MicroTeX. +* For every request: +* 1. Hash it +* 2. Check if the hash is already processed +* 3. If not, render it with MicroTeX and mark as processed +*/ +Singleton { + id: root + + readonly property var renderPadding: 4 // This is to prevent cutoff in the rendered images + + property list processedHashes: [] + property var processedExpressions: ({}) + property var renderedImagePaths: ({}) + property string microtexBinaryPath: Qt.resolvedUrl("/opt/MicroTeX/LaTeX") + property string latexOutputPath: StringUtils.trimFileProtocol(`${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/latex`) + + signal renderFinished(string hash, string imagePath) + + Component.onCompleted: { + Hyprland.dispatch(`exec rm -rf ${latexOutputPath} && mkdir -p ${latexOutputPath}`) + } + + /** + * Requests rendering of a LaTeX expression. + * Returns the [hash, isNew] + */ + function requestRender(expression) { + // 1. Hash it and initialize necessary variables + const hash = Qt.md5(expression) + const imagePath = `${latexOutputPath}/${hash}.svg` + + // 2. Check if the hash is already processed + if (processedHashes.includes(hash)) { + // console.log("Already processed: " + hash) + renderFinished(hash, imagePath) + return [hash, false] + } else { + root.processedHashes.push(hash) + root.processedExpressions[hash] = expression + // console.log("Rendering expression: " + expression) + } + + // 3. If not, render it with MicroTeX and mark as processed + const processQml = ` + import Quickshell.Io + Process { + id: microtexProcess${hash} + running: true + command: [ "${microtexBinaryPath}", "-headless", + "-input=${StringUtils.escapeBackslashes(expression)}", + "-output=${imagePath}", + "-textsize=${Appearance.font.pixelSize.normal}", + "-padding=${renderPadding}", + "-foreground=${Appearance.colors.colOnLayer1}", + "-maxwidth=0.85" ] + // stdout: SplitParser { + // onRead: data => { console.log("MicroTeX: " + data) } + // } + onExited: (exitCode, exitStatus) => { + renderedImagePaths["${hash}"] = "${imagePath}" + root.renderFinished("${hash}", "${imagePath}") + microtexProcess${hash}.destroy() + } + } + ` + // console.log("MicroTeX: " + processQml) + Qt.createQmlObject(processQml, root, `MicroTeXProcess_${hash}`) + return [hash, true] + } +} \ No newline at end of file From c13acd01523ee131061d561b44e88549c04df092 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 9 May 2025 01:07:47 +0200 Subject: [PATCH 207/824] wrap {} with () --- .config/quickshell/services/HyprlandData.qml | 2 +- .config/quickshell/services/KeyringStorage.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/services/HyprlandData.qml b/.config/quickshell/services/HyprlandData.qml index 5ca4cf6fa..598177ae5 100644 --- a/.config/quickshell/services/HyprlandData.qml +++ b/.config/quickshell/services/HyprlandData.qml @@ -11,7 +11,7 @@ Singleton { id: root property var windowList: [] property var addresses: [] - property var windowByAddress: {} + property var windowByAddress: ({}) property var monitors: [] function updateWindowList() { diff --git a/.config/quickshell/services/KeyringStorage.qml b/.config/quickshell/services/KeyringStorage.qml index ef51c3ac7..78254ed90 100644 --- a/.config/quickshell/services/KeyringStorage.qml +++ b/.config/quickshell/services/KeyringStorage.qml @@ -10,7 +10,7 @@ import QtQuick; Singleton { id: root - property var keyringData: {} + property var keyringData: ({}) // onKeyringDataChanged: { // console.log("[KeyringStorage] Keyring data changed:", JSON.stringify(root.keyringData)); // } From b4e3221711969267903d30a0a3c1f1a92e9729de Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 9 May 2025 01:07:55 +0200 Subject: [PATCH 208/824] comment debug print --- .config/quickshell/services/Ai.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index a4791b04d..9772b98e0 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -249,7 +249,7 @@ Singleton { + ` ${headerString}` + ' -H "Authorization: Bearer ${API_KEY}"' + ` -d '${StringUtils.shellSingleQuoteEscape(JSON.stringify(data))}'` - console.log("Request command: ", requestCommandString); + // console.log("Request command: ", requestCommandString); requester.command = baseCommand.concat([requestCommandString]); /* Reset vars and make the request */ @@ -314,7 +314,7 @@ Singleton { const parsedResponse = JSON.parse(requester.message.content + "]"); requester.message.content = `\`\`\`json\n${JSON.stringify(parsedResponse, null, 2)}\n\`\`\``; } catch (e) { - console.log("[AI] Could not parse response on exit: ", e); + // console.log("[AI] Could not parse response on exit: ", e); } } } From 2221b4d32c7bc7dd867a644454bc5253297efbec Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 9 May 2025 01:08:51 +0200 Subject: [PATCH 209/824] booru: prevent race condition in cache folder cleaning --- .config/quickshell/modules/sidebarLeft/Anime.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 58de08177..76af23ca9 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -37,8 +37,7 @@ Item { } Component.onCompleted: { - Hyprland.dispatch(`exec rm -rf ${previewDownloadPath}`) - Hyprland.dispatch(`exec mkdir -p ${previewDownloadPath}`) + Hyprland.dispatch(`exec rm -rf ${previewDownloadPath} && mkdir -p ${previewDownloadPath}`) } property var allCommands: [ From 2be5e9063b8f3951485e346cef66385dab996f36 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 9 May 2025 01:09:37 +0200 Subject: [PATCH 210/824] hyprlock: caps lock indicator --- .config/matugen/templates/hyprland/hyprlock.conf | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.config/matugen/templates/hyprland/hyprlock.conf b/.config/matugen/templates/hyprland/hyprlock.conf index 9ee4114f8..2f6331607 100644 --- a/.config/matugen/templates/hyprland/hyprlock.conf +++ b/.config/matugen/templates/hyprland/hyprlock.conf @@ -30,6 +30,18 @@ input-field { valign = center } +label { # Caps Lock Warning + monitor = + text = cmd[update:250] ${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprlock/check-capslock.sh + color = $text_color + font_size = 13 + font_family = $font_family + position = 0, -25 + halign = center + valign = center +} + + label { # Clock monitor = text = $TIME @@ -57,8 +69,6 @@ label { # User monitor = text =  $USER color = $text_color - shadow_passes = 1 - shadow_boost = 0.35 outline_thickness = 2 dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8 dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0 From bbe064150319c94a51bb46bcbb77d1687426db19 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 9 May 2025 15:49:11 +0200 Subject: [PATCH 211/824] update hyprlock config --- .config/hypr/hyprlock.conf | 14 ++++++++++++-- .config/hypr/hyprlock/check-capslock.sh | 9 +++++++++ .../sidebarLeft/aiChat/MessageTextBlock.qml | 3 +++ 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100755 .config/hypr/hyprlock/check-capslock.sh diff --git a/.config/hypr/hyprlock.conf b/.config/hypr/hyprlock.conf index cd4c90e7b..1e936ce58 100644 --- a/.config/hypr/hyprlock.conf +++ b/.config/hypr/hyprlock.conf @@ -30,6 +30,18 @@ input-field { valign = center } +label { # Caps Lock Warning + monitor = + text = cmd[update:250] ${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprlock/check-capslock.sh + color = $text_color + font_size = 13 + font_family = $font_family + position = 0, -25 + halign = center + valign = center +} + + label { # Clock monitor = text = $TIME @@ -57,8 +69,6 @@ label { # User monitor = text =  $USER color = $text_color - shadow_passes = 1 - shadow_boost = 0.35 outline_thickness = 2 dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8 dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0 diff --git a/.config/hypr/hyprlock/check-capslock.sh b/.config/hypr/hyprlock/check-capslock.sh new file mode 100755 index 000000000..ca56178a2 --- /dev/null +++ b/.config/hypr/hyprlock/check-capslock.sh @@ -0,0 +1,9 @@ +#!/bin/env bash + +MAIN_KB_CAPS=$(hyprctl devices | grep -B 6 "main: yes" | grep "capsLock" | head -1 | awk '{print $2}') + +if [ "$MAIN_KB_CAPS" = "yes" ]; then + echo "Caps Lock active" +else + echo "" +fi diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml index 11303784a..8043b9167 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml @@ -68,6 +68,9 @@ ColumnLayout { onEditingChanged: { if (!editing) { renderLatex() + } else { + console.log("Editing mode enabled", segmentContent) + textArea.text = segmentContent } } From de61b46ccb804adab0baf8e8123fe391cd1837a5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 9 May 2025 18:22:49 +0200 Subject: [PATCH 212/824] remove debug print --- .config/quickshell/modules/overview/SearchWidget.qml | 1 - .../quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 5b61689b4..ddc2b64da 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -45,7 +45,6 @@ Item { // Wrapper { action: "accentcolor", execute: (args) => { - console.log(args) executor.executeCommand( `${xdgConfigHome}/quickshell/scripts/switchwall.sh --noswitch --color ${args != '' ? ("'"+args+"'") : ""}` .replace(/file:\/\//, "")) diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml index 8043b9167..3c2c4a072 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml @@ -12,7 +12,6 @@ import QtQuick.Layouts import Quickshell.Io import Quickshell import Quickshell.Widgets -import Quickshell.Wayland import Quickshell.Hyprland import Qt5Compat.GraphicalEffects @@ -69,7 +68,7 @@ ColumnLayout { if (!editing) { renderLatex() } else { - console.log("Editing mode enabled", segmentContent) + // console.log("Editing mode enabled", segmentContent) textArea.text = segmentContent } } From efcfd375c0a3cf3a55bbcb75693b8eaf4ff1bd2a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 9 May 2025 18:23:11 +0200 Subject: [PATCH 213/824] adjust animations --- .../quickshell/modules/overview/Overview.qml | 4 +-- .../modules/overview/OverviewWindow.qml | 30 +++++++++---------- .../modules/sidebarLeft/anime/BooruImage.qml | 6 ++-- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 23be7ef58..b6d093eae 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -152,8 +152,8 @@ Scope { } GlobalShortcut { name: "overviewToggleReleaseInterrupt" - description: "Interrupts possibility of overview being toggled on release" + - "This is necessary because onReleased triggers whether or not you press something else while holding the key." + + description: "Interrupts possibility of overview being toggled on release. " + + "This is necessary because onReleased triggers whether or not you press something else while holding the key. " + "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything." onPressed: { diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index e659d4ec1..248fbf81a 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -48,30 +48,30 @@ Rectangle { // Window Behavior on x { NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + duration: Appearance.animation.elementMoveEnter.duration + easing.type: Appearance.animation.elementMoveEnter.type + easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve } } Behavior on y { NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + duration: Appearance.animation.elementMoveEnter.duration + easing.type: Appearance.animation.elementMoveEnter.type + easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve } } Behavior on width { NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + duration: Appearance.animation.elementMoveEnter.duration + easing.type: Appearance.animation.elementMoveEnter.type + easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve } } Behavior on height { NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + duration: Appearance.animation.elementMoveEnter.duration + easing.type: Appearance.animation.elementMoveEnter.type + easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve } } @@ -89,9 +89,9 @@ Rectangle { // Window Behavior on implicitSize { NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + duration: Appearance.animation.elementMoveEnter.duration + easing.type: Appearance.animation.elementMoveEnter.type + easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve } } diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index 3d2856301..e4f83685c 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -85,9 +85,9 @@ Button { Behavior on opacity { NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + duration: Appearance.animation.elementMoveEnter.duration + easing.type: Appearance.animation.elementMoveEnter.type + easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve } } } From de2ead426f984957d06b228ca80053f7d6565bc6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 9 May 2025 18:23:22 +0200 Subject: [PATCH 214/824] ai: handle thinking --- .../modules/common/functions/string_utils.js | 58 +++++- .../quickshell/modules/sidebarLeft/AiChat.qml | 6 +- .../modules/sidebarLeft/aiChat/AiMessage.qml | 16 +- .../sidebarLeft/aiChat/MessageCodeBlock.qml | 7 +- .../sidebarLeft/aiChat/MessageThinkBlock.qml | 196 ++++++++++++++++++ 5 files changed, 266 insertions(+), 17 deletions(-) create mode 100644 .config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml diff --git a/.config/quickshell/modules/common/functions/string_utils.js b/.config/quickshell/modules/common/functions/string_utils.js index 97b830bb1..d36066235 100644 --- a/.config/quickshell/modules/common/functions/string_utils.js +++ b/.config/quickshell/modules/common/functions/string_utils.js @@ -17,20 +17,70 @@ function shellSingleQuoteEscape(str) { } function splitMarkdownBlocks(markdown) { - const regex = /```(\w+)?\n([\s\S]*?)```/g; + const regex = /```(\w+)?\n([\s\S]*?)```|([\s\S]*?)<\/think>/g; let result = []; let lastIndex = 0; let match; while ((match = regex.exec(markdown)) !== null) { if (match.index > lastIndex) { - result.push({ type: "text", content: markdown.slice(lastIndex, match.index) }); + const text = markdown.slice(lastIndex, match.index); + if (text.trim()) { + result.push({ type: "text", content: text }); + } + } + if (match[0].startsWith('```')) { + if (match[2] && match[2].trim()) { + result.push({ type: "code", lang: match[1] || "", content: match[2], completed: true }); + } + } else if (match[0].startsWith('')) { + if (match[3] && match[3].trim()) { + result.push({ type: "think", content: match[3], completed: true }); + } } - result.push({ type: "code", lang: match[1] || "", content: match[2] }); lastIndex = regex.lastIndex; } + // Handle any remaining text after the last match if (lastIndex < markdown.length) { - result.push({ type: "text", content: markdown.slice(lastIndex) }); + const text = markdown.slice(lastIndex); + // Check for unfinished block + const thinkStart = text.indexOf(''); + const codeStart = text.indexOf('```'); + if ( + thinkStart !== -1 && + (codeStart === -1 || thinkStart < codeStart) + ) { + const beforeThink = text.slice(0, thinkStart); + if (beforeThink.trim()) { + result.push({ type: "text", content: beforeThink }); + } + const thinkContent = text.slice(thinkStart + 7); + if (thinkContent.trim()) { + result.push({ type: "think", content: thinkContent, completed: false }); + } + } else if (codeStart !== -1) { + const beforeCode = text.slice(0, codeStart); + if (beforeCode.trim()) { + result.push({ type: "text", content: beforeCode }); + } + // Try to detect language after ``` + const codeLangMatch = text.slice(codeStart + 3).match(/^(\w+)?\n/); + let lang = ""; + let codeContentStart = codeStart + 3; + if (codeLangMatch) { + lang = codeLangMatch[1] || ""; + codeContentStart += codeLangMatch[0].length; + } else if (text[codeStart + 3] === '\n') { + codeContentStart += 1; + } + const codeContent = text.slice(codeContentStart); + if (codeContent.trim()) { + result.push({ type: "code", lang, content: codeContent, completed: false }); + } + } else if (text.trim()) { + result.push({ type: "text", content: text }); + } } + // console.log(JSON.stringify(result, null, 2)); return result; } diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 07b6a0d66..7635b291d 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -80,6 +80,11 @@ Item { description: qsTr("Markdown test"), execute: () => { Ai.addMessage(` + +A longer think block to test revealing animation +OwO wem ipsum dowo sit amet, consekituwet awipiscing ewit, sed do eiuwsmod tempow inwididunt ut wabowe et dowo mawa. Ut enim ad minim weniam, quis nostwud exeucitation uwuwamcow bowowis nisi ut awiquip ex ea commowo consequat. Duuis aute iwuwe dowo in wepwependewit in wowuptate velit esse ciwwum dowo eu fugiat nuwa pawiatuw. Excepteuw sint occaecat cupidatat non pwowoident, sunt in cuwpa qui officia desewunt mowit anim id est wabowum. Meouw! >w< +Mowe uwu wem ipsum! + ## ✏️ Markdown test ### Formatting @@ -118,7 +123,6 @@ int main(int argc, char* argv[]) { - Simple inline: $\\frac{1}{2} = \\frac{2}{4}$ - Complex inline: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$ - Another complex inline: \\\\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\\\] - `, Ai.interfaceRole); } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index 77def3827..9da0b4cc8 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -23,9 +23,6 @@ Rectangle { property real messagePadding: 7 property real contentSpacing: 3 - property real codeBlockBackgroundRounding: Appearance.rounding.small - property real codeBlockHeaderPadding: 3 - property real codeBlockComponentSpacing: 2 property bool enableMouseSelection: false property bool renderMarkdown: true @@ -44,7 +41,6 @@ Rectangle { const segments = messageContentColumnLayout.children .map(child => child.segment) .filter(segment => (segment)); - // console.log("Segments: " + JSON.stringify(segments)) // Reconstruct markdown const newContent = segments.map(segment => { @@ -238,11 +234,7 @@ Rectangle { spacing: 0 Repeater { model: ScriptModel { - values: { - const result = StringUtils.splitMarkdownBlocks(root.messageData.content) - // console.log(JSON.stringify(result)) - return result - } + values: StringUtils.splitMarkdownBlocks(root.messageData.content) } delegate: Loader { Layout.fillWidth: true @@ -255,8 +247,12 @@ Rectangle { property var enableMouseSelection: root.enableMouseSelection property bool thinking: root.messageData.thinking property bool done: root.messageData.done + property bool completed: modelData.completed ?? false - source: modelData.type === "code" ? "MessageCodeBlock.qml" : "MessageTextBlock.qml" + source: modelData.type === "code" ? "MessageCodeBlock.qml" : + modelData.type === "think" ? "MessageThinkBlock.qml" : + "MessageTextBlock.qml" + } } } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml index 2cbf4ae77..024103459 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml @@ -12,7 +12,6 @@ import QtQuick.Layouts import Quickshell.Io import Quickshell import Quickshell.Widgets -import Quickshell.Wayland import Quickshell.Hyprland import Qt5Compat.GraphicalEffects import org.kde.syntaxhighlighting @@ -26,6 +25,10 @@ ColumnLayout { property var segmentLang: parent?.segmentLang ?? "plaintext" property var messageData: parent?.messageData ?? {} + property real codeBlockBackgroundRounding: Appearance.rounding.small + property real codeBlockHeaderPadding: 3 + property real codeBlockComponentSpacing: 2 + spacing: codeBlockComponentSpacing anchors.left: parent.left anchors.right: parent.right @@ -161,7 +164,7 @@ ColumnLayout { id: codeTextArea Layout.fillWidth: true readOnly: !editing - // selectByMouse: enableMouseSelection || editing + selectByMouse: enableMouseSelection || editing renderType: Text.NativeRendering font.family: Appearance.font.family.monospace font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml new file mode 100644 index 000000000..95566ceea --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml @@ -0,0 +1,196 @@ +pragma ComponentBehavior: Bound + +import "root:/" +import "root:/services" +import "root:/modules/common/" +import "root:/modules/common/widgets" +import "../" +import "root:/modules/common/functions/string_utils.js" as StringUtils +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io +import Quickshell +import Quickshell.Widgets +import Quickshell.Hyprland +import Qt5Compat.GraphicalEffects + +Item { + id: root + // These are needed on the parent loader + property bool editing: parent?.editing ?? false + property bool renderMarkdown: parent?.renderMarkdown ?? true + property bool enableMouseSelection: parent?.enableMouseSelection ?? false + property string segmentContent: parent?.segmentContent ?? ({}) + property var messageData: parent?.messageData ?? {} + property bool done: parent?.done ?? true + property bool completed: parent?.completed ?? false + + property real thinkBlockBackgroundRounding: Appearance.rounding.small + property real thinkBlockHeaderPaddingVertical: 3 + property real thinkBlockHeaderPaddingHorizontal: 10 + property real thinkBlockComponentSpacing: 2 + + property var collapseAnimation: messageTextBlock.implicitHeight > 40 ? Appearance.animation.elementMoveEnter : Appearance.animation.elementMoveFast + property bool collapsed: root.completed || !root.done + + Layout.fillWidth: true + implicitHeight: collapsed ? header.implicitHeight : columnLayout.implicitHeight + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: root.width + height: root.height + radius: thinkBlockBackgroundRounding + } + } + + Behavior on implicitHeight { + enabled: root.done ?? false + NumberAnimation { + duration: collapseAnimation.duration + easing.type: collapseAnimation.type + easing.bezierCurve: collapseAnimation.bezierCurve + } + } + + ColumnLayout { + id: columnLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: 0 + + Rectangle { // Header background + id: header + color: Appearance.m3colors.m3surfaceContainerHighest + Layout.fillWidth: true + implicitHeight: thinkBlockTitleBarRowLayout.implicitHeight + thinkBlockHeaderPaddingVertical * 2 + + MouseArea { // Click to reveal + id: headerMouseArea + enabled: root.done + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + onClicked: { + root.collapsed = !root.collapsed + } + } + + RowLayout { // Header content + id: thinkBlockTitleBarRowLayout + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: thinkBlockHeaderPaddingHorizontal + anchors.rightMargin: thinkBlockHeaderPaddingHorizontal + spacing: 10 + + MaterialSymbol { + Layout.fillWidth: false + Layout.topMargin: 7 + Layout.bottomMargin: 7 + Layout.leftMargin: 3 + text: "linked_services" + } + StyledText { + id: thinkBlockLanguage + Layout.fillWidth: false + Layout.alignment: Qt.AlignLeft + text: root.done ? "Chain of Thought" : "Thinking..." + } + Item { Layout.fillWidth: true } + Button { // Expand button + id: expandButton + visible: root.done + implicitWidth: 22 + implicitHeight: 22 + + PointingHandInteraction{} + onClicked: { + root.collapsed = !root.collapsed + } + + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.full + color: (headerMouseArea.pressed) ? Appearance.colors.colLayer2Active + : (headerMouseArea.containsMouse ? Appearance.colors.colLayer2Hover + : Appearance.transparentize(Appearance.colors.colLayer2, 1)) + + Behavior on color { + ColorAnimation { + duration: collapseAnimation.duration + easing.type: collapseAnimation.type + easing.bezierCurve: collapseAnimation.bezierCurve + } + + } + + } + + contentItem: MaterialSymbol { + anchors.centerIn: parent + text: "keyboard_arrow_down" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + iconSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colOnLayer2 + rotation: root.collapsed ? 0 : 180 + Behavior on rotation { + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } + } + + } + + } + + } + + Item { + id: content + Layout.fillWidth: true + implicitHeight: collapsed ? 0 : contentBackground.implicitHeight + thinkBlockComponentSpacing + clip: true + + Behavior on implicitHeight { + enabled: root.done ?? false + NumberAnimation { + duration: collapseAnimation.duration + easing.type: collapseAnimation.easing + easing.bezierCurve: collapseAnimation.bezierCurve + } + } + + Rectangle { + id: contentBackground + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + implicitHeight: messageTextBlock.implicitHeight + color: Appearance.colors.colLayer2 + + // Load data for the message at the correct scope + property bool editing: root.editing + property bool renderMarkdown: root.renderMarkdown + property bool enableMouseSelection: root.enableMouseSelection + property string segmentContent: root.segmentContent + property var messageData: root.messageData + property bool done: root.done + + MessageTextBlock { + id: messageTextBlock + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + } + } + } + } +} \ No newline at end of file From f3d0fd5313406158755be47ac0cdcca3893f037a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 9 May 2025 18:40:28 +0200 Subject: [PATCH 215/824] ai: "animate" thinking --- .../modules/sidebarLeft/aiChat/MessageThinkBlock.qml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml index 95566ceea..f2f6b35d3 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml @@ -32,7 +32,7 @@ Item { property real thinkBlockComponentSpacing: 2 property var collapseAnimation: messageTextBlock.implicitHeight > 40 ? Appearance.animation.elementMoveEnter : Appearance.animation.elementMoveFast - property bool collapsed: root.completed || !root.done + property bool collapsed: true /* should be root.completed but its kinda buggy rn so nope */ Layout.fillWidth: true implicitHeight: collapsed ? header.implicitHeight : columnLayout.implicitHeight @@ -46,7 +46,7 @@ Item { } Behavior on implicitHeight { - enabled: root.done ?? false + enabled: root.completed ?? false NumberAnimation { duration: collapseAnimation.duration easing.type: collapseAnimation.type @@ -69,7 +69,7 @@ Item { MouseArea { // Click to reveal id: headerMouseArea - enabled: root.done + enabled: root.completed anchors.fill: parent cursorShape: Qt.PointingHandCursor hoverEnabled: true @@ -98,12 +98,12 @@ Item { id: thinkBlockLanguage Layout.fillWidth: false Layout.alignment: Qt.AlignLeft - text: root.done ? "Chain of Thought" : "Thinking..." + text: root.completed ? "Chain of Thought" : ("Thinking" + ".".repeat(Math.random() * 4)) } Item { Layout.fillWidth: true } Button { // Expand button id: expandButton - visible: root.done + visible: root.completed implicitWidth: 22 implicitHeight: 22 @@ -160,7 +160,7 @@ Item { clip: true Behavior on implicitHeight { - enabled: root.done ?? false + enabled: root.completed ?? false NumberAnimation { duration: collapseAnimation.duration easing.type: collapseAnimation.easing From aa07895a972406b72dcd1740aca5f2d6bf67e5b9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 9 May 2025 20:24:39 +0200 Subject: [PATCH 216/824] rename material theme service --- .../services/MaterialThemeLoader.qml | 55 +++++++++++++++++++ .config/quickshell/shell.qml | 2 +- 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 .config/quickshell/services/MaterialThemeLoader.qml diff --git a/.config/quickshell/services/MaterialThemeLoader.qml b/.config/quickshell/services/MaterialThemeLoader.qml new file mode 100644 index 000000000..c34441a1b --- /dev/null +++ b/.config/quickshell/services/MaterialThemeLoader.qml @@ -0,0 +1,55 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common" +import QtQuick +import Quickshell +import Quickshell.Io +import Qt.labs.platform + +Singleton { + id: root + property string filePath: `${StandardPaths.standardLocations(StandardPaths.StateLocation)[0]}/user/generated/colors.json` + + function reapplyTheme() { + themeFileView.reload() + } + + function applyColors(fileContent) { + const json = JSON.parse(fileContent) + for (const key in json) { + if (json.hasOwnProperty(key)) { + // Convert snake_case to CamelCase + const camelCaseKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase()) + const m3Key = `m3${camelCaseKey}` + Appearance.m3colors[m3Key] = json[key] + } + } + + Appearance.m3colors.darkmode = (Appearance.m3colors.m3background.hslLightness < 0.5) + } + + Timer { + id: delayedFileRead + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + running: false + onTriggered: { + root.applyColors(themeFileView.text()) + } + } + + FileView { + id: themeFileView + path: root.filePath + watchChanges: true + onFileChanged: { + this.reload() + delayedFileRead.start() + } + onLoadedChanged: { + const fileContent = themeFileView.text() + root.applyColors(fileContent) + } + } +} diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 8de63090f..b0e5250a0 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -17,7 +17,7 @@ import "./services/" ShellRoot { Component.onCompleted: { - MaterialTheme.reapplyTheme() + MaterialThemeLoader.reapplyTheme() } Bar {} From b99fe1421454b17310d12167b5528f8da3c7409b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 9 May 2025 20:25:16 +0200 Subject: [PATCH 217/824] rename bar's "small circle button" + prevent init color shift --- ...lCircleButton.qml => CircleUtilButton.qml} | 9 --- .../quickshell/modules/bar/UtilButtons.qml | 4 +- .config/quickshell/services/MaterialTheme.qml | 55 ------------------- 3 files changed, 2 insertions(+), 66 deletions(-) rename .config/quickshell/modules/bar/{SmallCircleButton.qml => CircleUtilButton.qml} (72%) delete mode 100644 .config/quickshell/services/MaterialTheme.qml diff --git a/.config/quickshell/modules/bar/SmallCircleButton.qml b/.config/quickshell/modules/bar/CircleUtilButton.qml similarity index 72% rename from .config/quickshell/modules/bar/SmallCircleButton.qml rename to .config/quickshell/modules/bar/CircleUtilButton.qml index 32c79d352..0651211c3 100644 --- a/.config/quickshell/modules/bar/SmallCircleButton.qml +++ b/.config/quickshell/modules/bar/CircleUtilButton.qml @@ -23,15 +23,6 @@ Button { radius: Appearance.rounding.full color: (button.down || extraActiveCondition) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2) - Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } - - } - } } diff --git a/.config/quickshell/modules/bar/UtilButtons.qml b/.config/quickshell/modules/bar/UtilButtons.qml index 24d773157..93b34d8d9 100644 --- a/.config/quickshell/modules/bar/UtilButtons.qml +++ b/.config/quickshell/modules/bar/UtilButtons.qml @@ -19,7 +19,7 @@ Rectangle { spacing: 4 anchors.centerIn: parent - SmallCircleButton { + CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Hyprland.dispatch("exec grimblast copy area") @@ -32,7 +32,7 @@ Rectangle { } - SmallCircleButton { + CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Hyprland.dispatch("exec hyprpicker -a") diff --git a/.config/quickshell/services/MaterialTheme.qml b/.config/quickshell/services/MaterialTheme.qml deleted file mode 100644 index c34441a1b..000000000 --- a/.config/quickshell/services/MaterialTheme.qml +++ /dev/null @@ -1,55 +0,0 @@ -pragma Singleton -pragma ComponentBehavior: Bound - -import "root:/modules/common" -import QtQuick -import Quickshell -import Quickshell.Io -import Qt.labs.platform - -Singleton { - id: root - property string filePath: `${StandardPaths.standardLocations(StandardPaths.StateLocation)[0]}/user/generated/colors.json` - - function reapplyTheme() { - themeFileView.reload() - } - - function applyColors(fileContent) { - const json = JSON.parse(fileContent) - for (const key in json) { - if (json.hasOwnProperty(key)) { - // Convert snake_case to CamelCase - const camelCaseKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase()) - const m3Key = `m3${camelCaseKey}` - Appearance.m3colors[m3Key] = json[key] - } - } - - Appearance.m3colors.darkmode = (Appearance.m3colors.m3background.hslLightness < 0.5) - } - - Timer { - id: delayedFileRead - interval: ConfigOptions.hacks.arbitraryRaceConditionDelay - repeat: false - running: false - onTriggered: { - root.applyColors(themeFileView.text()) - } - } - - FileView { - id: themeFileView - path: root.filePath - watchChanges: true - onFileChanged: { - this.reload() - delayedFileRead.start() - } - onLoadedChanged: { - const fileContent = themeFileView.text() - root.applyColors(fileContent) - } - } -} From c57772aa9d48e5c33d9cab7d0639c42b643ebc6f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 00:24:29 +0200 Subject: [PATCH 218/824] gemini: search capabilities --- .config/quickshell/services/Ai.qml | 189 +++++++++++++++++++---------- 1 file changed, 126 insertions(+), 63 deletions(-) diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index 9772b98e0..174d4cb19 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -13,6 +13,7 @@ Singleton { readonly property string xdgConfigHome: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0] readonly property string interfaceRole: "interface" + readonly property string apiKeyEnvVarName: "API_KEY" property Component aiMessageComponent: AiMessageData {} property var messages: [] readonly property var apiKeys: KeyringStorage.keyringData?.apiKeys ?? {} @@ -27,23 +28,17 @@ Singleton { // - key_id: The identifier of the API key. Use the same identifier for models that can be accessed with the same key. // - key_get_link: Link to get the API key property var models: { - "gemini-2.0-flash": { + "gemini-2.0-flash-gemini-api": { "name": "Gemini 2.0 Flash", "icon": "google-gemini-symbolic", - "description": "Online | Google's model", + "description": "Online | Google's model | Has search capabilities, giving you up-to-date information", "homepage": "https://aistudio.google.com", - "endpoint": "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", + "endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent", "model": "gemini-2.0-flash", "requires_key": true, "key_id": "gemini", "key_get_link": "https://aistudio.google.com/app/apikey", - // "extraParams": { - // "tools": [ - // { - // "google_search": {} - // } - // ] - // } + "api_format": "gemini", }, "openrouter-llama4-maverick": { "name": "Llama 4 Maverick (OpenRouter)", @@ -197,15 +192,37 @@ Singleton { property var baseCommand: ["bash", "-c"] property var message property bool isReasoning + property string apiFormat: "openai" + property string geminiBuffer: "" - function makeRequest() { - const model = models[currentModel]; - let endpoint = model.endpoint; + function buildGeminiEndpoint(model) { + // console.log("ENDPOINT: " + model.endpoint + `?key=\$\{${root.apiKeyEnvVarName}\}`) + return model.endpoint + `?key=\$\{${root.apiKeyEnvVarName}\}`; + } - /* Build request data and headers */ + function buildOpenAIEndpoint(model) { + return model.endpoint; + } + + function buildGeminiRequestData(model, messages) { + let baseData = { + "contents": messages.filter(message => (message.role != Ai.interfaceRole)).map(message => ({ + "role": message.role, + "parts": [{ text: message.content }] + })), + "tools": [ + { + "google_search": {} + } + ] + }; + return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData; + } + + function buildOpenAIRequestData(model, messages) { let baseData = { "model": model.model, - "messages": root.messages.filter(message => (message.role != Ai.interfaceRole)).map(message => { + "messages": messages.filter(message => (message.role != Ai.interfaceRole)).map(message => { return { "role": message.role, "content": message.content, @@ -213,19 +230,25 @@ Singleton { }), "stream": true, }; - let data = model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData; + return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData; + } + + function makeRequest() { + const model = models[currentModel]; + requester.apiFormat = model.api_format ?? "openai"; + + /* Put API key in environment variable */ + if (model.requires_key) requester.environment[`${root.apiKeyEnvVarName}`] = root.apiKeys ? (root.apiKeys[model.key_id] ?? "") : "" + + /* Build endpoint, request data */ + const endpoint = (apiFormat === "gemini") ? buildGeminiEndpoint(model) : buildOpenAIEndpoint(model); + const data = (apiFormat === "gemini") ? buildGeminiRequestData(model, root.messages) : buildOpenAIRequestData(model, root.messages); - let requestHeaders = { "Content-Type": "application/json", } - - /* Put API key in environment variable */ - if (model.requires_key) requester.environment = ({ - "API_KEY": root.apiKeys ? (root.apiKeys[model.key_id] ?? "") : "", - }) - /* Create message object for local storage */ + /* Create local message object */ requester.message = root.aiMessageComponent.createObject(root, { "role": "assistant", "model": currentModel, @@ -245,9 +268,9 @@ Singleton { // console.log("Header string: ", headerString); /* Create command string */ - const requestCommandString = `curl --no-buffer '${endpoint}'` + const requestCommandString = `curl --no-buffer "${endpoint}"` + ` ${headerString}` - + ' -H "Authorization: Bearer ${API_KEY}"' + + ((apiFormat == "gemini") ? "" : ` -H "Authorization: Bearer \$\{${root.apiKeyEnvVarName}\}"`) + ` -d '${StringUtils.shellSingleQuoteEscape(JSON.stringify(data))}'` // console.log("Request command: ", requestCommandString); requester.command = baseCommand.concat([requestCommandString]); @@ -257,59 +280,99 @@ Singleton { requester.running = true } + function parseGeminiBuffer() { + const dataJson = JSON.parse(requester.geminiBuffer); + + const responseContent = dataJson.candidates[0]?.content?.parts[0]?.text + requester.message.content += responseContent; + requester.geminiBuffer = ""; + } + + function handleGeminiResponseLine(line) { + if (line.startsWith("[")) { + requester.geminiBuffer += line.slice(1).trim(); + } else if (line == "]") { + requester.geminiBuffer += line.slice(0, -1).trim(); + parseGeminiBuffer(); + requester.message.done = true; + } else if (line.startsWith(",")) { // end of one entry + parseGeminiBuffer(); + } else { + requester.geminiBuffer += line.trim(); + } + } + + function handleOpenAIResponseLine(line) { + // Remove 'data: ' prefix if present and trim whitespace + let cleanData = line.trim(); + if (cleanData.startsWith("data:")) { + cleanData = cleanData.slice(5).trim(); + } + // console.log("Clean data: ", cleanData); + if (!cleanData || + cleanData === ": OPENROUTER PROCESSING" + ) return; + + if (cleanData === "[DONE]") { + requester.message.done = true; + return; + } + const dataJson = JSON.parse(cleanData); + + let newContent = ""; + const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content; + const responseReasoning = dataJson.choices[0]?.delta?.reasoning || dataJson.choices[0]?.delta?.reasoning_content; + + if (responseContent && responseContent.length > 0) { + if (requester.isReasoning) { + requester.isReasoning = false; + requester.message.content += "\n\n\n\n"; + } + newContent = dataJson.choices[0]?.delta?.content || dataJson.message.content; + } else if (responseReasoning && responseReasoning.length > 0) { + // console.log("Reasoning content: ", dataJson.choices[0].delta.reasoning); + if (!requester.isReasoning) { + requester.isReasoning = true; + requester.message.content += "\n\n\n\n"; + } + newContent = dataJson.choices[0].delta.reasoning || dataJson.choices[0].delta.reasoning_content; + } + + requester.message.content += newContent; + + if (dataJson.done) requester.message.done = true; + } + stdout: SplitParser { onRead: data => { + console.log("RAW DATA: ", data); if (data.length === 0) return; - // Remove 'data: ' prefix if present and trim whitespace - let cleanData = data.trim(); - if (cleanData.startsWith("data:")) { - cleanData = cleanData.slice(5).trim(); - } - // console.log("Clean data: ", cleanData); - if (!cleanData || - cleanData === ": OPENROUTER PROCESSING" - ) return; - + // Handle response line if (requester.message.thinking) requester.message.thinking = false; try { - if (cleanData === "[DONE]") { - requester.message.done = true; - return; + if (requester.apiFormat === "gemini") { + requester.handleGeminiResponseLine(data); } - const dataJson = JSON.parse(cleanData); - - let newContent = ""; - const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content; - const responseReasoning = dataJson.choices[0]?.delta?.reasoning || dataJson.choices[0]?.delta?.reasoning_content; - - if (responseContent && responseContent.length > 0) { - if (requester.isReasoning) { - requester.isReasoning = false; - requester.message.content += "\n\n\n\n"; - } - newContent = dataJson.choices[0]?.delta?.content || dataJson.message.content; - } else if (responseReasoning && responseReasoning.length > 0) { - // console.log("Reasoning content: ", dataJson.choices[0].delta.reasoning); - if (!requester.isReasoning) { - requester.isReasoning = true; - requester.message.content += "\n\n\n\n"; - } - newContent = dataJson.choices[0].delta.reasoning || dataJson.choices[0].delta.reasoning_content; + else if (requester.apiFormat === "openai") { + requester.handleOpenAIResponseLine(data); + } + else { + console.log("Unknown API format: ", requester.apiFormat); + requester.message.content += data; } - - requester.message.content += newContent; - - if (dataJson.done) requester.message.done = true; } catch (e) { console.log("[AI] Could not parse response from stream: ", e); - requester.message.content += cleanData; + requester.message.content += data; } } } onExited: (exitCode, exitStatus) => { - try { // to parse full response into json + requester.message.done = true; + if (requester.apiFormat == "gemini") requester.parseGeminiBuffer(); + + try { // to parse full response into json for error handling // console.log("Full response: ", requester.message.content + "]"); const parsedResponse = JSON.parse(requester.message.content + "]"); requester.message.content = `\`\`\`json\n${JSON.stringify(parsedResponse, null, 2)}\n\`\`\``; From c0f5f55c638ec86f2dd1e82fcdc80a0f8daf4403 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 00:38:38 +0200 Subject: [PATCH 219/824] ai: system prompt --- .../modules/common/ConfigOptions.qml | 4 ++++ .config/quickshell/services/Ai.qml | 21 ++++++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 1638984b7..1b61cb175 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -4,6 +4,10 @@ pragma Singleton pragma ComponentBehavior: Bound Singleton { + property QtObject ai: QtObject { + property string systemPrompt: "" + } + property QtObject appearance: QtObject { property int fakeScreenRounding: 1 // 0: None | 1: Always | 2: When not fullscreen } diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index 174d4cb19..ff796f267 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -15,6 +15,7 @@ Singleton { readonly property string interfaceRole: "interface" readonly property string apiKeyEnvVarName: "API_KEY" property Component aiMessageComponent: AiMessageData {} + property string systemPrompt: ConfigOptions.ai.systemPrompt ?? "" property var messages: [] readonly property var apiKeys: KeyringStorage.keyringData?.apiKeys ?? {} @@ -214,7 +215,10 @@ Singleton { { "google_search": {} } - ] + ], + "system_instruction": { + "parts": [{ text: root.systemPrompt }] + } }; return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData; } @@ -222,12 +226,15 @@ Singleton { function buildOpenAIRequestData(model, messages) { let baseData = { "model": model.model, - "messages": messages.filter(message => (message.role != Ai.interfaceRole)).map(message => { - return { - "role": message.role, - "content": message.content, - } - }), + "messages": [ + {role: "system", content: root.systemPrompt}, + ...messages.filter(message => (message.role != Ai.interfaceRole)).map(message => { + return { + "role": message.role, + "content": message.content, + } + }), + ], "stream": true, }; return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData; From 6758d8daf390b26101d85b8bb70b04f443c3cd87 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 01:53:43 +0200 Subject: [PATCH 220/824] ai: add api key advice --- .config/quickshell/services/Ai.qml | 80 +++++++++++++------ .../quickshell/services/KeyringStorage.qml | 2 + 2 files changed, 57 insertions(+), 25 deletions(-) diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index ff796f267..0cccb740d 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -18,6 +18,7 @@ Singleton { property string systemPrompt: ConfigOptions.ai.systemPrompt ?? "" property var messages: [] readonly property var apiKeys: KeyringStorage.keyringData?.apiKeys ?? {} + readonly property var apiKeysLoaded: KeyringStorage.loaded // Model properties: // - name: Name of the model @@ -27,35 +28,46 @@ Singleton { // - model: Model name of the model // - requires_key: Whether the model requires an API key // - key_id: The identifier of the API key. Use the same identifier for models that can be accessed with the same key. - // - key_get_link: Link to get the API key + // - key_get_link: Link to get an API key + // - key_get_description: Description of pricing and how to get an API key + // - api_format: The API format of the model. Can be "openai" or "gemini". Default is "openai". + // - tools: List of tools that the model can use. Each tool is an object with the tool name as the key and an empty object as the value. + // - extraParams: Extra parameters to be passed to the model. This is a JSON object. property var models: { - "gemini-2.0-flash-gemini-api": { + "gemini-2.0-flash-search": { "name": "Gemini 2.0 Flash", "icon": "google-gemini-symbolic", - "description": "Online | Google's model | Has search capabilities, giving you up-to-date information", + "description": "Online | Google's model\nGives up-to-date information with search.", "homepage": "https://aistudio.google.com", "endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent", "model": "gemini-2.0-flash", "requires_key": true, "key_id": "gemini", "key_get_link": "https://aistudio.google.com/app/apikey", + "key_get_description": "Pricing: free. Data used for training.\n\nInstructions: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key", "api_format": "gemini", + "tools": [ + { + "google_search": {} + }, + ] }, "openrouter-llama4-maverick": { - "name": "Llama 4 Maverick (OpenRouter)", + "name": "Llama 4 Maverick", "icon": "ollama-symbolic", - "description": "Online | OpenRouter | Meta's model", + "description": "Online via OpenRouter | Meta's model", "homepage": "https://openrouter.ai/meta-llama/llama-4-maverick:free", "endpoint": "https://openrouter.ai/api/v1/chat/completions", "model": "meta-llama/llama-4-maverick:free", "requires_key": true, "key_id": "openrouter", "key_get_link": "https://openrouter.ai/settings/keys", + "key_get_description": "Pricing: free. Data use policy varies depending on your OpenRouter account settings.\n\nInstructions: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key", }, "openrouter-deepseek-r1": { - "name": "DeepSeek R1 (OpenRouter)", + "name": "DeepSeek R1", "icon": "deepseek-symbolic", - "description": "Online | OpenRouter | DeepSeek's reasoning model", + "description": "Online via OpenRouter | DeepSeek's reasoning model", "homepage": "https://openrouter.ai/deepseek/deepseek-r1:free", "endpoint": "https://openrouter.ai/api/v1/chat/completions", "model": "deepseek/deepseek-r1:free", @@ -87,7 +99,8 @@ Singleton { words = words.map((word) => { return (word.charAt(0).toUpperCase() + word.slice(1)) }); - words[words.length - 1] = `[${words[words.length - 1]}]`; // Surround the last word with square brackets + if (words[words.length - 1] === "Latest") words.pop(); + else words[words.length - 1] = `(${words[words.length - 1]})`; // Surround the last word with square brackets const result = words.join(' '); return result; } @@ -105,7 +118,7 @@ Singleton { root.models[model] = { "name": guessModelName(model), "icon": guessModelLogo(model), - "description": `Local (Ollama) | ${model}`, + "description": `Local Ollama model: ${model}`, "homepage": `https://ollama.com/library/${model}`, "endpoint": "http://localhost:11434/v1/chat/completions", "model": model, @@ -138,12 +151,26 @@ Singleton { root.messages = [...root.messages]; } + function addApiKeyAdvice(model) { + root.addMessage( + StringUtils.format(qsTr('To set an API key, pass it with the command\n\nTo view the key, pass "get" with the command

For {0}, you can grab one at:\n\n{1}\n\n{2}'), + model.name, model.key_get_link, model.key_get_description ?? qsTr("No further instruction provided")), + Ai.interfaceRole + ); + } + function setModel(model, feedback = true) { if (!model) model = "" model = model.toLowerCase() if (modelList.indexOf(model) !== -1) { currentModel = model if (feedback) root.addMessage("Model set to " + models[model].name, Ai.interfaceRole) + if (models[model].requires_key) { + // If key not there show advice + if (root.apiKeysLoaded && (!root.apiKeys[models[model].key_id] || root.apiKeys[models[model].key_id].length === 0)) { + root.addApiKeyAdvice(models[model]) + } + } } else { if (feedback) root.addMessage(qsTr("Invalid model. Supported: \n- ") + modelList.join("\n- "), Ai.interfaceRole) } @@ -159,14 +186,11 @@ Singleton { return; } if (!key || key.length === 0) { - root.addMessage( - StringUtils.format(qsTr('To set an API key, pass it with the command\n\nTo view the key, pass "get" with the command

For {0}, you can grab one at:\n\n{1}'), - models[currentModel].name, models[currentModel].key_get_link), - Ai.interfaceRole - ); + const model = models[currentModel]; + root.addApiKeyAdvice(model) return; } - KeyringStorage.setNestedField(["apiKeys", model.key_id], key); + KeyringStorage.setNestedField(["apiKeys", model.key_id], key.trim()); root.addMessage("API key set for " + model.name, Ai.interfaceRole); } @@ -175,7 +199,7 @@ Singleton { if (model.requires_key) { const key = root.apiKeys[model.key_id]; if (key) { - root.addMessage(StringUtils.format(qsTr("API key:\n\n`{0}`"), key), Ai.interfaceRole); + root.addMessage(StringUtils.format(qsTr("API key:\n\n```txt\n{0}\n```"), key), Ai.interfaceRole); } else { root.addMessage(StringUtils.format(qsTr("No API key set for {0}"), model.name), Ai.interfaceRole); } @@ -212,9 +236,7 @@ Singleton { "parts": [{ text: message.content }] })), "tools": [ - { - "google_search": {} - } + ...model.tools, ], "system_instruction": { "parts": [{ text: root.systemPrompt }] @@ -288,11 +310,15 @@ Singleton { } function parseGeminiBuffer() { - const dataJson = JSON.parse(requester.geminiBuffer); - - const responseContent = dataJson.candidates[0]?.content?.parts[0]?.text - requester.message.content += responseContent; - requester.geminiBuffer = ""; + try { + const dataJson = JSON.parse(requester.geminiBuffer); + const responseContent = dataJson.candidates[0]?.content?.parts[0]?.text + requester.message.content += responseContent; + } catch (e) { + requester.message.content += requester.geminiBuffer + } finally { + requester.geminiBuffer = ""; + } } function handleGeminiResponseLine(line) { @@ -352,7 +378,7 @@ Singleton { stdout: SplitParser { onRead: data => { - console.log("RAW DATA: ", data); + // console.log("RAW DATA: ", data); if (data.length === 0) return; // Handle response line @@ -386,6 +412,10 @@ Singleton { } catch (e) { // console.log("[AI] Could not parse response on exit: ", e); } + + if (requester.message.content.includes("API key not valid")) { + root.addApiKeyAdvice(models[requester.message.model]); + } } } diff --git a/.config/quickshell/services/KeyringStorage.qml b/.config/quickshell/services/KeyringStorage.qml index 78254ed90..39632c6ca 100644 --- a/.config/quickshell/services/KeyringStorage.qml +++ b/.config/quickshell/services/KeyringStorage.qml @@ -10,6 +10,7 @@ import QtQuick; Singleton { id: root + property bool loaded: false property var keyringData: ({}) // onKeyringDataChanged: { // console.log("[KeyringStorage] Keyring data changed:", JSON.stringify(root.keyringData)); @@ -109,6 +110,7 @@ Singleton { root.keyringData = {}; saveKeyringData() } + root.loaded = true; } } From 6ec3f91e0fc19b748dfd71ecbf97bf7c7c94cd51 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 01:55:00 +0200 Subject: [PATCH 221/824] ai: provide a default system prompt --- .config/quickshell/modules/common/ConfigOptions.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 1b61cb175..c34b07e7e 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -5,7 +5,7 @@ pragma ComponentBehavior: Bound Singleton { property QtObject ai: QtObject { - property string systemPrompt: "" + property string systemPrompt: "Use casual tone. No user knowledge is to be assumed except basic Linux literacy. Be brief and concise: When explaining concepts, use bullet points (prefer minus sign (-) over asterisk (*)) and highlight keywords in bold to pinpoint the main concepts instead of long paragraphs. You are also encouraged to split your response with h2 headers, each header title beginning with an emoji, like `## 🐧 Linux`." } property QtObject appearance: QtObject { From b3723f7dc67cac57314b8d7eecd12a9f4b8d0035 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 08:53:03 +0200 Subject: [PATCH 222/824] notifications: adjust animations --- .../common/widgets/NotificationWidget.qml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index d66ea6b3f..338cd86f6 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -22,6 +22,8 @@ Item { property bool ready: false property int defaultTimeoutValue: 5000 + property var notificationXAnimation: Appearance.animation.elementMoveEnter + Layout.fillWidth: true clip: !popup @@ -45,6 +47,7 @@ Item { interval: notificationObject.expireTimeout ?? root.defaultTimeoutValue repeat: false onTriggered: { + root.notificationXAnimation = Appearance.animation.elementMoveExit Notifications.timeoutNotification(notificationObject.id); } } @@ -135,9 +138,11 @@ Item { dragStarted = false if (mouse.button === Qt.LeftButton) { if (notificationRowWrapper.x > dragConfirmThreshold) { + root.notificationXAnimation = Appearance.animation.elementMoveEnter Notifications.discardNotification(notificationObject.id); } else { // Animate back if not far enough + root.notificationXAnimation = Appearance.animation.elementMoveFast notificationRowWrapper.x = 0 notificationBackground.x = 0 } @@ -188,9 +193,9 @@ Item { Behavior on x { enabled: enableAnimation NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + duration: root.notificationXAnimation.duration + easing.type: root.notificationXAnimation.type + easing.bezierCurve: root.notificationXAnimation.bezierCurve } } Behavior on height { @@ -228,9 +233,9 @@ Item { Behavior on x { enabled: enableAnimation NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + duration: root.notificationXAnimation.duration + easing.type: root.notificationXAnimation.type + easing.bezierCurve: root.notificationXAnimation.bezierCurve } } From b7e3b5d88722870abe8bf56cca5f7a6e9eaf6dc8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 10:55:59 +0200 Subject: [PATCH 223/824] bar: allow hiding bg --- .config/quickshell/modules/bar/Bar.qml | 9 +++++---- .config/quickshell/modules/common/ConfigOptions.qml | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 7975b1173..44f18e335 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -18,6 +18,7 @@ Scope { readonly property int barHeight: Appearance.sizes.barHeight readonly property int barCenterSideModuleWidth: Appearance.sizes.barCenterSideModuleWidth readonly property int osdHideMouseMoveThreshold: 20 + property bool showBarBackground: ConfigOptions.bar.showBackground Variants { // For each monitor model: Quickshell.screens @@ -30,7 +31,7 @@ Scope { screen: modelData WlrLayershell.namespace: "quickshell:bar" height: barHeight + Appearance.rounding.screenRounding - exclusiveZone: barHeight + exclusiveZone: showBarBackground ? barHeight : (barHeight - 4) mask: Region { item: barContent } @@ -47,7 +48,7 @@ Scope { anchors.right: parent.right anchors.left: parent.left anchors.top: parent.top - color: Appearance.colors.colLayer0 + color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" height: barHeight RowLayout { // Left section @@ -324,14 +325,14 @@ Scope { anchors.left: parent.left size: Appearance.rounding.screenRounding corner: cornerEnum.topLeft - color: Appearance.colors.colLayer0 + color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" } RoundCorner { anchors.top: parent.top anchors.right: parent.right size: Appearance.rounding.screenRounding corner: cornerEnum.topRight - color: Appearance.colors.colLayer0 + color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" } } diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index c34b07e7e..c0d6758bd 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -24,6 +24,7 @@ Singleton { property QtObject bar: QtObject { property int batteryLowThreshold: 20 property string topLeftIcon: "spark" // Options: distro, spark + property bool showBackground: true property QtObject resources: QtObject { property bool alwaysShowSwap: true property bool alwaysShowCpu: false From 0af7924be9b829420d6aed6b142a0e6d95eebac4 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 10:56:22 +0200 Subject: [PATCH 224/824] add default user agent option --- .config/quickshell/modules/common/ConfigOptions.qml | 4 ++++ .config/quickshell/services/Booru.qml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index c0d6758bd..f4852626b 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -36,6 +36,10 @@ Singleton { } } + property QtObject networking: QtObject { + property string userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" + } + property QtObject osd: QtObject { property int timeout: 1000 } diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index e8fa44cf2..27a3615eb 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -23,7 +23,7 @@ Singleton { property string failMessage: qsTr("That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number") property var responses: [] property int runningRequests: 0 - property var defaultUserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" + property var defaultUserAgent: ConfigOptions.networking.userAgent property var providerList: ["yandere", "konachan", "zerochan", "danbooru", "gelbooru", "waifu.im"] property var providers: { "system": { "name": "System" }, From f93cca8a134fbc1cf56dfb233608c0ba69a072c6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 10:56:35 +0200 Subject: [PATCH 225/824] ai: gemini: annotation sources --- .../modules/common/functions/string_utils.js | 5 ++ .../quickshell/modules/sidebarLeft/AiChat.qml | 6 ++ .../modules/sidebarLeft/aiChat/AiMessage.qml | 24 +++++- .../aiChat/AnnotationSourceButton.qml | 73 +++++++++++++++++++ .config/quickshell/services/Ai.qml | 22 ++++++ .config/quickshell/services/AiMessageData.qml | 2 + 6 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 .config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml diff --git a/.config/quickshell/modules/common/functions/string_utils.js b/.config/quickshell/modules/common/functions/string_utils.js index d36066235..e52f167dc 100644 --- a/.config/quickshell/modules/common/functions/string_utils.js +++ b/.config/quickshell/modules/common/functions/string_utils.js @@ -9,6 +9,11 @@ function getDomain(url) { return match ? match[1] : null; } +function getBaseUrl(url) { + const match = url.match(/^(https?:\/\/[^\/]+)(\/.*)?$/); + return match ? match[1] : null; +} + function shellSingleQuoteEscape(str) { // escape single quotes return String(str) diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 7635b291d..77aac582a 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -20,10 +20,15 @@ Item { property var inputField: messageInputField readonly property var messages: Ai.messages property string commandPrefix: "/" + property string faviconDownloadPath: StringUtils.trimFileProtocol(`${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/media/favicons`) property var suggestionQuery: "" property var suggestionList: [] + Component.onCompleted: { + Hyprland.dispatch(`exec mkdir -p ${faviconDownloadPath}`) + } + Connections { target: panelWindow function onVisibleChanged(visible) { @@ -205,6 +210,7 @@ int main(int argc, char* argv[]) { messageIndex: index messageData: modelData messageInputField: root.inputField + faviconDownloadPath: root.faviconDownloadPath } } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index 9da0b4cc8..7418e31ab 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -20,6 +20,7 @@ Rectangle { property int messageIndex property var messageData property var messageInputField + property string faviconDownloadPath property real messagePadding: 7 property real contentSpacing: 3 @@ -74,7 +75,7 @@ Rectangle { } } - ColumnLayout { + ColumnLayout { // Main layout of the whole thing id: columnLayout anchors.left: parent.left @@ -228,7 +229,7 @@ Rectangle { } } - ColumnLayout { + ColumnLayout { // Message content id: messageContentColumnLayout spacing: 0 @@ -257,6 +258,25 @@ Rectangle { } } + Flow { // Annotations + id: annotationFlowLayout + visible: root.messageData?.annotationSources?.length > 0 + spacing: 5 + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft + + Repeater { + model: root.messageData.annotationSources + delegate: AnnotationSourceButton { + id: annotationButton + faviconDownloadPath: root.faviconDownloadPath + displayText: modelData.text + url: modelData.url + } + } + + } + } } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml new file mode 100644 index 000000000..d60077851 --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml @@ -0,0 +1,73 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import "root:/modules/common/functions/string_utils.js" as StringUtils +import Qt.labs.platform +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io +import Quickshell.Widgets +import Quickshell.Hyprland + +Button { + id: root + property string displayText + property string url + + implicitHeight: 30 + leftPadding: 10 + rightPadding: 10 + + property string downloadUserAgent: ConfigOptions.networking.userAgent + property string faviconDownloadPath + property string domainName: url.includes("vertexaisearch") ? displayText : StringUtils.getBaseUrl(url) + // property string faviconUrl: `https://${domainName}/favicon.ico` + property string faviconUrl: `https://www.google.com/s2/favicons?domain=${domainName}&sz=32` + property string fileName: `${domainName}.ico` + property string faviconFilePath: `${faviconDownloadPath}/${fileName}` + + Process { + id: faviconDownloadProcess + running: false + command: ["bash", "-c", `[ -f ${faviconFilePath} ] || curl -s '${root.faviconUrl}' -o '${faviconFilePath}' -L -H 'User-Agent: ${downloadUserAgent}'`] + onExited: (exitCode, exitStatus) => { + root.faviconUrl = root.faviconFilePath + } + } + + Component.onCompleted: { + console.log("Favicon download:", faviconDownloadProcess.command.join(" ")) + faviconDownloadProcess.running = true + } + + PointingHandInteraction {} + onClicked: { + if (url) { + Qt.openUrlExternally(url) + Hyprland.dispatch("global quickshell:sidebarLeftClose") + } + } + + background: Rectangle { + radius: Appearance.rounding.full + color: (root.down ? Appearance.colors.colSurfaceContainerHighestActive : + root.hovered ? Appearance.colors.colSurfaceContainerHighestHover : + Appearance.m3colors.m3surfaceContainerHighest) + } + + contentItem: RowLayout { + spacing: 5 + IconImage { + id: iconImage + source: Qt.resolvedUrl(root.faviconUrl) + implicitSize: text.implicitHeight + } + StyledText { + id: text + horizontalAlignment: Text.AlignHCenter + text: displayText + color: Appearance.m3colors.m3onSurface + } + } +} diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index 0cccb740d..770c99fe2 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -310,11 +310,33 @@ Singleton { } function parseGeminiBuffer() { + // console.log("BUFFER DATA: ", requester.geminiBuffer); try { const dataJson = JSON.parse(requester.geminiBuffer); const responseContent = dataJson.candidates[0]?.content?.parts[0]?.text requester.message.content += responseContent; + const annotationSources = dataJson.candidates[0]?.groundingMetadata.groundingChunks?.map(chunk => { + return { + "type": "url_citation", + "text": chunk?.web?.title, + "url": chunk?.web?.uri, + } + }); + const annotations = dataJson.candidates[0]?.groundingMetadata.groundingSupports?.map(citation => { + return { + "type": "url_citation", + "start_index": citation.segment?.startIndex, + "end_index": citation.segment?.endIndex, + "text": citation?.segment.text, + "url": annotationSources[citation.groundingChunkIndices[0]]?.url, + "sources": citation.groundingChunkIndices + } + }); + requester.message.annotationSources = annotationSources; + requester.message.annotations = annotations; + // console.log(JSON.stringify(requester.message, null, 2)); } catch (e) { + console.log("[AI] Could not parse response from stream: ", e); requester.message.content += requester.geminiBuffer } finally { requester.geminiBuffer = ""; diff --git a/.config/quickshell/services/AiMessageData.qml b/.config/quickshell/services/AiMessageData.qml index 7bc5108e7..625818a05 100644 --- a/.config/quickshell/services/AiMessageData.qml +++ b/.config/quickshell/services/AiMessageData.qml @@ -7,4 +7,6 @@ QtObject { property string model property bool thinking: true property bool done: false + property var annotations: [] + property var annotationSources: [] } From 1b70a5eb16085cb37d885fca0a6c50e43501ba67 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 11:15:34 +0200 Subject: [PATCH 226/824] booru: cleaner download --- .../modules/sidebarLeft/anime/BooruImage.qml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index e4f83685c..1efe10106 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -20,28 +20,21 @@ Button { property string downloadPath property string nsfwPath property string fileName: decodeURIComponent((imageData.file_url).substring((imageData.file_url).lastIndexOf('/') + 1)) + property string filePath: `${root.previewDownloadPath}/${root.fileName}` property bool showActions: false Process { id: downloadProcess running: false - command: ["bash", "-c", `curl '${root.imageData.preview_url ?? root.imageData.sample_url}' -o '${root.previewDownloadPath}/${root.fileName}' && echo 'done'`] - stdout: SplitParser { - onRead: (data) => { - // console.log("Download output:", data) - if(data.includes("done")) { - imageObject.source = `${previewDownloadPath}/${root.fileName}` - } - } + command: ["bash", "-c", `[ -f ${root.filePath} ] || curl '${root.imageData.preview_url ?? root.imageData.sample_url}' -o '${root.filePath}'`] + onExited: (exitCode, exitStatus) => { + imageObject.source = `${previewDownloadPath}/${root.fileName}` } } Component.onCompleted: { if (root.manualDownload) { - // console.log("Manual download triggered") - // console.log("Image data:", JSON.stringify(root.imageData)) - // console.log("Download command:", downloadProcess.command.join(" ")) downloadProcess.running = true } } From 6d382e467a9a60911596277140c592ae8a705eff Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 11:16:01 +0200 Subject: [PATCH 227/824] ai: adjust search source button style --- .../aiChat/AnnotationSourceButton.qml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml index d60077851..c7c76978f 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml @@ -2,6 +2,7 @@ import "root:/modules/common" import "root:/modules/common/widgets" import "root:/services" import "root:/modules/common/functions/string_utils.js" as StringUtils +import Qt5Compat.GraphicalEffects import Qt.labs.platform import QtQuick import QtQuick.Controls @@ -16,7 +17,7 @@ Button { property string url implicitHeight: 30 - leftPadding: 10 + leftPadding: 5 rightPadding: 10 property string downloadUserAgent: ConfigOptions.networking.userAgent @@ -37,7 +38,7 @@ Button { } Component.onCompleted: { - console.log("Favicon download:", faviconDownloadProcess.command.join(" ")) + // console.log("Favicon download:", faviconDownloadProcess.command.join(" ")) faviconDownloadProcess.running = true } @@ -61,7 +62,16 @@ Button { IconImage { id: iconImage source: Qt.resolvedUrl(root.faviconUrl) - implicitSize: text.implicitHeight + implicitSize: 20 + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: iconImage.implicitSize + height: iconImage.implicitSize + radius: Appearance.rounding.full + } + } } StyledText { id: text From aae8429629bc0d21f85f683e8cca7de2cf590879 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 11:48:45 +0200 Subject: [PATCH 228/824] very small refractor --- .../sidebarLeft/aiChat/AnnotationSourceButton.qml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml index c7c76978f..21e4981f7 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml @@ -16,18 +16,18 @@ Button { property string displayText property string url - implicitHeight: 30 - leftPadding: 5 - rightPadding: 10 - property string downloadUserAgent: ConfigOptions.networking.userAgent property string faviconDownloadPath property string domainName: url.includes("vertexaisearch") ? displayText : StringUtils.getBaseUrl(url) - // property string faviconUrl: `https://${domainName}/favicon.ico` property string faviconUrl: `https://www.google.com/s2/favicons?domain=${domainName}&sz=32` property string fileName: `${domainName}.ico` property string faviconFilePath: `${faviconDownloadPath}/${fileName}` + property real faviconSize: 20 + implicitHeight: 30 + leftPadding: (implicitHeight - faviconSize) / 2 + rightPadding: 10 + Process { id: faviconDownloadProcess running: false @@ -38,7 +38,6 @@ Button { } Component.onCompleted: { - // console.log("Favicon download:", faviconDownloadProcess.command.join(" ")) faviconDownloadProcess.running = true } @@ -62,7 +61,7 @@ Button { IconImage { id: iconImage source: Qt.resolvedUrl(root.faviconUrl) - implicitSize: 20 + implicitSize: root.faviconSize layer.enabled: true layer.effect: OpacityMask { From 3876b07bc331c912b82420006d86e9291cf0888f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 11:49:09 +0200 Subject: [PATCH 229/824] ai: dont keep text when command fails --- .config/quickshell/modules/sidebarLeft/AiChat.qml | 2 +- .config/quickshell/services/Ai.qml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 77aac582a..167cc7322 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -447,8 +447,8 @@ int main(int argc, char* argv[]) { event.accepted = true } else { // Accept text const inputText = messageInputField.text - root.handleInput(inputText) messageInputField.clear() + root.handleInput(inputText) event.accepted = true } } diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index 770c99fe2..de265e6e8 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -44,7 +44,7 @@ Singleton { "requires_key": true, "key_id": "gemini", "key_get_link": "https://aistudio.google.com/app/apikey", - "key_get_description": "Pricing: free. Data used for training.\n\nInstructions: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key", + "key_get_description": "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key", "api_format": "gemini", "tools": [ { @@ -62,7 +62,7 @@ Singleton { "requires_key": true, "key_id": "openrouter", "key_get_link": "https://openrouter.ai/settings/keys", - "key_get_description": "Pricing: free. Data use policy varies depending on your OpenRouter account settings.\n\nInstructions: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key", + "key_get_description": "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key", }, "openrouter-deepseek-r1": { "name": "DeepSeek R1", @@ -153,7 +153,7 @@ Singleton { function addApiKeyAdvice(model) { root.addMessage( - StringUtils.format(qsTr('To set an API key, pass it with the command\n\nTo view the key, pass "get" with the command

For {0}, you can grab one at:\n\n{1}\n\n{2}'), + StringUtils.format(qsTr('To set an API key, pass it with the command\n\nTo view the key, pass "get" with the command
\n\n### For {0}:\n\n**Link**: {1}\n\n{2}'), model.name, model.key_get_link, model.key_get_description ?? qsTr("No further instruction provided")), Ai.interfaceRole ); @@ -172,9 +172,9 @@ Singleton { } } } else { - if (feedback) root.addMessage(qsTr("Invalid model. Supported: \n- ") + modelList.join("\n- "), Ai.interfaceRole) + if (feedback) root.addMessage(qsTr("Invalid model. Supported: \n```\n") + modelList.join("\n```\n```\n"), Ai.interfaceRole) + "\n```" } - if (models[model].requires_key) { + if (models[model]?.requires_key) { KeyringStorage.fetchKeyringData(); } } From 826f54e90d6158e7f35e72e755ab807b97253783 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 17:53:21 +0200 Subject: [PATCH 230/824] ai: text block: remove unecessary key hogger --- .../modules/sidebarLeft/aiChat/MessageTextBlock.qml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml index 3c2c4a072..c3850271a 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml @@ -121,13 +121,6 @@ ColumnLayout { segmentContent = text } - Keys.onPressed: (event) => { - if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) { - messageText.copy() - event.accepted = true - } - } - onLinkActivated: (link) => { Qt.openUrlExternally(link) Hyprland.dispatch("global quickshell:sidebarLeftClose") From 846677caa14a4811704d5952fe9668ef5912a6b5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 17:53:38 +0200 Subject: [PATCH 231/824] config loader --- .config/quickshell/services/ConfigLoader.qml | 106 +++++++++++++++++++ .config/quickshell/shell.qml | 1 + 2 files changed, 107 insertions(+) create mode 100644 .config/quickshell/services/ConfigLoader.qml diff --git a/.config/quickshell/services/ConfigLoader.qml b/.config/quickshell/services/ConfigLoader.qml new file mode 100644 index 000000000..749d3dc59 --- /dev/null +++ b/.config/quickshell/services/ConfigLoader.qml @@ -0,0 +1,106 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common" +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland +import Qt.labs.platform + +Singleton { + id: root + property string fileDir: `${StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0]}/illogical-impulse` + property string fileName: "config.json" + property string filePath: `${root.fileDir}/${root.fileName}` + + function toPlainObject(qtObj) { + if (qtObj === null || typeof qtObj !== "object") return qtObj; + + // Handle arrays + if (Array.isArray(qtObj)) { + return qtObj.map(toPlainObject); + } + + const result = ({}); + for (let key in qtObj) { + if ( + typeof qtObj[key] !== "function" && + !key.startsWith("objectName") && + !key.startsWith("children") && + !key.startsWith("object") && + !key.startsWith("parent") && + !key.startsWith("metaObject") && + !key.startsWith("destroyed") && + !key.startsWith("reloadableId") + ) { + result[key] = toPlainObject(qtObj[key]); + } + } + return result; + } + + function loadConfig() { + configFileView.reload() + } + + function applyConfig(fileContent) { + const json = JSON.parse(fileContent); + + function applyToQtObject(qtObj, jsonObj) { + if (!qtObj || typeof jsonObj !== "object" || jsonObj === null) return; + + for (let key in jsonObj) { + if (!qtObj.hasOwnProperty(key)) continue; + + // Check if the property is a QtObject (not a value) + const value = qtObj[key]; + const jsonValue = jsonObj[key]; + + // If it's an object and not an array, recurse + if (value && typeof value === "object" && !Array.isArray(value)) { + applyToQtObject(value, jsonValue); + } else { + // Otherwise, assign the value + qtObj[key] = jsonValue; + } + } + } + + applyToQtObject(ConfigOptions, json); + } + + Timer { + id: delayedFileRead + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + running: false + onTriggered: { + root.applyConfig(configFileView.text()) + } + } + + FileView { + id: configFileView + path: root.filePath + watchChanges: true + onFileChanged: { + this.reload() + delayedFileRead.start() + } + onLoadedChanged: { + const fileContent = configFileView.text() + root.applyConfig(fileContent) + } + onLoadFailed: (error) => { + if(error == FileViewError.FileNotFound) { + console.log("[ConfigLoader] File not found, creating new file.") + // Apply ConfigOptions json to file + const plainConfig = toPlainObject(ConfigOptions) + configFileView.setText(JSON.stringify(plainConfig, null, 2)) + } else { + Hyprland.dispatch(`exec notify-send "Failed to load config file at ${root.filePath}"`) + } + } + } +} diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index b0e5250a0..7bc831b88 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -18,6 +18,7 @@ import "./services/" ShellRoot { Component.onCompleted: { MaterialThemeLoader.reapplyTheme() + ConfigLoader.loadConfig() } Bar {} From 5d16a04ea60b57b13fd22d44f9b084f821079d37 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 20:09:48 +0200 Subject: [PATCH 232/824] config options: notify on reload --- .../common/widgets/notification_utils.js | 2 + .config/quickshell/services/ConfigLoader.qml | 48 ++++++++++++------- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/notification_utils.js b/.config/quickshell/modules/common/widgets/notification_utils.js index af97229c1..b887efd2d 100644 --- a/.config/quickshell/modules/common/widgets/notification_utils.js +++ b/.config/quickshell/modules/common/widgets/notification_utils.js @@ -13,6 +13,8 @@ function findSuitableMaterialSymbol(summary = "") { 'welcome': 'waving_hand', 'time': 'scheduleb', 'installed': 'download', + 'configuration reloaded': 'reset_wrench', + 'config': 'reset_wrench', 'update': 'update', 'ai response': 'neurology', 'startswith:file': 'folder_copy', // Declarative startsWith check diff --git a/.config/quickshell/services/ConfigLoader.qml b/.config/quickshell/services/ConfigLoader.qml index 749d3dc59..5f20c7fbe 100644 --- a/.config/quickshell/services/ConfigLoader.qml +++ b/.config/quickshell/services/ConfigLoader.qml @@ -13,6 +13,7 @@ Singleton { property string fileDir: `${StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0]}/illogical-impulse` property string fileName: "config.json" property string filePath: `${root.fileDir}/${root.fileName}` + property bool firstLoad: true function toPlainObject(qtObj) { if (qtObj === null || typeof qtObj !== "object") return qtObj; @@ -45,29 +46,40 @@ Singleton { } function applyConfig(fileContent) { - const json = JSON.parse(fileContent); + try { + const json = JSON.parse(fileContent); - function applyToQtObject(qtObj, jsonObj) { - if (!qtObj || typeof jsonObj !== "object" || jsonObj === null) return; + function applyToQtObject(qtObj, jsonObj) { + if (!qtObj || typeof jsonObj !== "object" || jsonObj === null) return; - for (let key in jsonObj) { - if (!qtObj.hasOwnProperty(key)) continue; + for (let key in jsonObj) { + if (!qtObj.hasOwnProperty(key)) continue; - // Check if the property is a QtObject (not a value) - const value = qtObj[key]; - const jsonValue = jsonObj[key]; + // Check if the property is a QtObject (not a value) + const value = qtObj[key]; + const jsonValue = jsonObj[key]; - // If it's an object and not an array, recurse - if (value && typeof value === "object" && !Array.isArray(value)) { - applyToQtObject(value, jsonValue); - } else { - // Otherwise, assign the value - qtObj[key] = jsonValue; + // If it's an object and not an array, recurse + if (value && typeof value === "object" && !Array.isArray(value)) { + applyToQtObject(value, jsonValue); + } else { + // Otherwise, assign the value + qtObj[key] = jsonValue; + } } } - } - applyToQtObject(ConfigOptions, json); + applyToQtObject(ConfigOptions, json); + if (root.firstLoad) { + root.firstLoad = false; + } else { + Hyprland.dispatch(`exec notify-send "Shell configuration reloaded" "${root.filePath}"`) + } + } catch (e) { + console.error("[ConfigLoader] Error reading file:", e); + Hyprland.dispatch(`exec notify-send "Shell configuration failed to load" "${root.filePath}"`) + return; + } } Timer { @@ -85,6 +97,7 @@ Singleton { path: root.filePath watchChanges: true onFileChanged: { + console.log("[ConfigLoader] File changed, reloading...") this.reload() delayedFileRead.start() } @@ -98,8 +111,9 @@ Singleton { // Apply ConfigOptions json to file const plainConfig = toPlainObject(ConfigOptions) configFileView.setText(JSON.stringify(plainConfig, null, 2)) + Hyprland.dispatch(`exec notify-send "Shell configuration created" "${root.filePath}"`) } else { - Hyprland.dispatch(`exec notify-send "Failed to load config file at ${root.filePath}"`) + Hyprland.dispatch(`exec notify-send "Shell configuration failed to load" "${root.filePath}"`) } } } From 96ca6db76d6a5174e28314e344e77cad7e4891a5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 20:28:57 +0200 Subject: [PATCH 233/824] adjust layer anim curves --- .config/hypr/hyprland/general.conf | 10 +++++----- .config/hypr/hyprland/rules.conf | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.config/hypr/hyprland/general.conf b/.config/hypr/hyprland/general.conf index bcb637e7c..f7ac79a18 100644 --- a/.config/hypr/hyprland/general.conf +++ b/.config/hypr/hyprland/general.conf @@ -118,7 +118,7 @@ animations { bezier = crazyshot, 0.1, 1.5, 0.76, 0.92 bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0 bezier = menu_decel, 0.1, 1, 0, 1 - bezier = menu_accel, 0.38, 0.04, 1, 0.07 + bezier = menu_accel, 0.52, 0.03, 0.72, 0.08 bezier = easeInOutCirc, 0.85, 0, 0.15, 1 bezier = easeOutCirc, 0, 0.55, 0.45, 1 bezier = easeOutExpo, 0.16, 1, 0.3, 1 @@ -131,10 +131,10 @@ animations { animation = border, 1, 10, default animation = fade, 1, 3, md3_decel # animation = layers, 1, 2, md3_decel, slide - animation = layersIn, 1, 3, menu_decel, slide - animation = layersOut, 1, 1.6, menu_accel - animation = fadeLayersIn, 1, 2, menu_decel - animation = fadeLayersOut, 1, 0.5, menu_accel + animation = layersIn, 1, 2.7, md3_decel, slide + animation = layersOut, 1, 1.8, menu_accel + animation = fadeLayersIn, 1, 0.5, menu_decel + animation = fadeLayersOut, 1, 3, menu_accel animation = workspaces, 1, 7, menu_decel, slide # animation = workspaces, 1, 2.5, softAcDecel, slide # animation = workspaces, 1, 7, menu_decel, slidefade 15% diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index cf35e5d22..1f0043004 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -112,9 +112,11 @@ layerrule = animation slide top, quickshell:onScreenDisplay layerrule = animation fade, quickshell:session layerrule = blur, quickshell:session layerrule = noanim, quickshell:overview # Launcher needs to be FAST +layerrule = noanim, gtk4-layer-shell # Launcher needs to be FAST ## outfoxxed's stuff layerrule = blur, shell:bar layerrule = ignorezero, shell:bar layerrule = blur, shell:notifications layerrule = ignorealpha 0.1, shell:notifications + From bce69c77c700530b653f65eb63e75f5b5350eb2d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 21:21:29 +0200 Subject: [PATCH 234/824] uncomment zerochan username config option --- .config/quickshell/modules/common/ConfigOptions.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index f4852626b..10a097884 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -68,9 +68,9 @@ Singleton { property QtObject booru: QtObject { property bool allowNsfw: false property string defaultProvider: "yandere" - property int limit: 20 // Images per page + property int limit: 20 property QtObject zerochan: QtObject { - // property string username + property string username: "" } } } From 8031625af7360418b5a68a6bba21c4533a65e745 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 21:22:32 +0200 Subject: [PATCH 235/824] move trimFileProtocol to file_utils.js --- .config/quickshell/modules/common/functions/file_utils.js | 4 ++++ .config/quickshell/modules/common/functions/string_utils.js | 4 ---- .config/quickshell/modules/sidebarLeft/AiChat.qml | 3 ++- .config/quickshell/services/LatexRenderer.qml | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 .config/quickshell/modules/common/functions/file_utils.js diff --git a/.config/quickshell/modules/common/functions/file_utils.js b/.config/quickshell/modules/common/functions/file_utils.js new file mode 100644 index 000000000..281921da1 --- /dev/null +++ b/.config/quickshell/modules/common/functions/file_utils.js @@ -0,0 +1,4 @@ +function trimFileProtocol(str) { + return str.startsWith("file://") ? str.slice(7) : str; +} + diff --git a/.config/quickshell/modules/common/functions/string_utils.js b/.config/quickshell/modules/common/functions/string_utils.js index e52f167dc..3251b4574 100644 --- a/.config/quickshell/modules/common/functions/string_utils.js +++ b/.config/quickshell/modules/common/functions/string_utils.js @@ -89,10 +89,6 @@ function splitMarkdownBlocks(markdown) { return result; } -function trimFileProtocol(str) { - return str.startsWith("file://") ? str.slice(7) : str; -} - function escapeBackslashes(str) { return str.replace(/\\/g, '\\\\'); } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 167cc7322..0c32d8cde 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -5,6 +5,7 @@ import "root:/modules/common/widgets" import "./aiChat/" import "root:/modules/common/functions/fuzzysort.js" as Fuzzy import "root:/modules/common/functions/string_utils.js" as StringUtils +import "root:/modules/common/functions/file_utils.js" as FileUtils import Qt.labs.platform import QtQuick import QtQuick.Controls @@ -20,7 +21,7 @@ Item { property var inputField: messageInputField readonly property var messages: Ai.messages property string commandPrefix: "/" - property string faviconDownloadPath: StringUtils.trimFileProtocol(`${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/media/favicons`) + property string faviconDownloadPath: FileUtils.trimFileProtocol(`${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/media/favicons`) property var suggestionQuery: "" property var suggestionList: [] diff --git a/.config/quickshell/services/LatexRenderer.qml b/.config/quickshell/services/LatexRenderer.qml index 2c0b7f3d3..edeaccb00 100644 --- a/.config/quickshell/services/LatexRenderer.qml +++ b/.config/quickshell/services/LatexRenderer.qml @@ -2,6 +2,7 @@ pragma Singleton pragma ComponentBehavior: Bound import "root:/modules/common/functions/string_utils.js" as StringUtils +import "root:/modules/common/functions/file_utils.js" as FileUtils import "root:/modules/common" import QtQuick import Quickshell @@ -25,7 +26,7 @@ Singleton { property var processedExpressions: ({}) property var renderedImagePaths: ({}) property string microtexBinaryPath: Qt.resolvedUrl("/opt/MicroTeX/LaTeX") - property string latexOutputPath: StringUtils.trimFileProtocol(`${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/latex`) + property string latexOutputPath: FileUtils.trimFileProtocol(`${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/latex`) signal renderFinished(string hash, string imagePath) From b05049dedf0f16197c3e32d5bf5d1d4652c6d9ba Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 21:23:20 +0200 Subject: [PATCH 236/824] move toPlainObject to object_utils.js --- .../modules/common/functions/object_utils.js | 29 +++++++++++++++ .config/quickshell/services/ConfigLoader.qml | 35 +++---------------- 2 files changed, 34 insertions(+), 30 deletions(-) create mode 100644 .config/quickshell/modules/common/functions/object_utils.js diff --git a/.config/quickshell/modules/common/functions/object_utils.js b/.config/quickshell/modules/common/functions/object_utils.js new file mode 100644 index 000000000..51eed7f92 --- /dev/null +++ b/.config/quickshell/modules/common/functions/object_utils.js @@ -0,0 +1,29 @@ +function trimFileProtocol(str) { + return str.startsWith("file://") ? str.slice(7) : str; +} + +function toPlainObject(qtObj) { + if (qtObj === null || typeof qtObj !== "object") return qtObj; + + // Handle arrays + if (Array.isArray(qtObj)) { + return qtObj.map(toPlainObject); + } + + const result = ({}); + for (let key in qtObj) { + if ( + typeof qtObj[key] !== "function" && + !key.startsWith("objectName") && + !key.startsWith("children") && + !key.startsWith("object") && + !key.startsWith("parent") && + !key.startsWith("metaObject") && + !key.startsWith("destroyed") && + !key.startsWith("reloadableId") + ) { + result[key] = toPlainObject(qtObj[key]); + } + } + return result; +} \ No newline at end of file diff --git a/.config/quickshell/services/ConfigLoader.qml b/.config/quickshell/services/ConfigLoader.qml index 5f20c7fbe..176dfd0ca 100644 --- a/.config/quickshell/services/ConfigLoader.qml +++ b/.config/quickshell/services/ConfigLoader.qml @@ -2,6 +2,8 @@ pragma Singleton pragma ComponentBehavior: Bound import "root:/modules/common" +import "root:/modules/common/functions/file_utils.js" as FileUtils +import "root:/modules/common/functions/object_utils.js" as ObjectUtils import QtQuick import Quickshell import Quickshell.Io @@ -12,35 +14,9 @@ Singleton { id: root property string fileDir: `${StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0]}/illogical-impulse` property string fileName: "config.json" - property string filePath: `${root.fileDir}/${root.fileName}` + property string filePath: FileUtils.trimFileProtocol(`${root.fileDir}/${root.fileName}`) property bool firstLoad: true - function toPlainObject(qtObj) { - if (qtObj === null || typeof qtObj !== "object") return qtObj; - - // Handle arrays - if (Array.isArray(qtObj)) { - return qtObj.map(toPlainObject); - } - - const result = ({}); - for (let key in qtObj) { - if ( - typeof qtObj[key] !== "function" && - !key.startsWith("objectName") && - !key.startsWith("children") && - !key.startsWith("object") && - !key.startsWith("parent") && - !key.startsWith("metaObject") && - !key.startsWith("destroyed") && - !key.startsWith("reloadableId") - ) { - result[key] = toPlainObject(qtObj[key]); - } - } - return result; - } - function loadConfig() { configFileView.reload() } @@ -94,7 +70,7 @@ Singleton { FileView { id: configFileView - path: root.filePath + path: Qt.resolvedUrl(root.filePath) watchChanges: true onFileChanged: { console.log("[ConfigLoader] File changed, reloading...") @@ -108,8 +84,7 @@ Singleton { onLoadFailed: (error) => { if(error == FileViewError.FileNotFound) { console.log("[ConfigLoader] File not found, creating new file.") - // Apply ConfigOptions json to file - const plainConfig = toPlainObject(ConfigOptions) + const plainConfig = ObjectUtils.toPlainObject(ConfigOptions) configFileView.setText(JSON.stringify(plainConfig, null, 2)) Hyprland.dispatch(`exec notify-send "Shell configuration created" "${root.filePath}"`) } else { From dc903de212e1e6e381ab88fed500ab190258f323 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 21:23:47 +0200 Subject: [PATCH 237/824] introduce persistent states (persistence to be added) --- .../quickshell/modules/common/PersistentStates.qml | 13 +++++++++++++ .../modules/sidebarRight/BottomWidgetGroup.qml | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 .config/quickshell/modules/common/PersistentStates.qml diff --git a/.config/quickshell/modules/common/PersistentStates.qml b/.config/quickshell/modules/common/PersistentStates.qml new file mode 100644 index 000000000..c991c47dc --- /dev/null +++ b/.config/quickshell/modules/common/PersistentStates.qml @@ -0,0 +1,13 @@ +import QtQuick +import Quickshell +pragma Singleton +pragma ComponentBehavior: Bound + +Singleton { + property QtObject sidebar: QtObject { + property QtObject bottomGroup: QtObject { + property bool collapsed: false + } + } + +} diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index 77dba5bdc..c96bbd733 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -15,7 +15,7 @@ Rectangle { clip: true implicitHeight: collapsed ? collapsedBottomWidgetGroupRow.implicitHeight : bottomWidgetGroupRow.implicitHeight property int selectedTab: 0 - property bool collapsed: false + property bool collapsed: PersistentStates.sidebar.bottomGroup.collapsed property var tabs: [ {"type": "calendar", "name": "Calendar", "icon": "calendar_month", "widget": calendarWidget}, {"type": "todo", "name": "To Do", "icon": "done_outline", "widget": todoWidget} @@ -35,7 +35,7 @@ Rectangle { } function setCollapsed(state) { - collapsed = state + PersistentStates.sidebar.bottomGroup.collapsed = state if (collapsed) { bottomWidgetGroupRow.opacity = 0 } From 2d962ce9a8a930353526fdb22f000adfe580b97a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 21:24:19 +0200 Subject: [PATCH 238/824] ai: message text block dont write back when ai is writing --- .../quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml index c3850271a..87087c6b9 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml @@ -118,6 +118,7 @@ ColumnLayout { text: qsTr("Waiting for response...") onTextChanged: { + if (!root.editing) return segmentContent = text } From 88bb486dc8952cbe7a34d208abec3cf3a055219b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 22:06:53 +0200 Subject: [PATCH 239/824] overview: fix monitor scaling --- .config/quickshell/modules/overview/OverviewWidget.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index 886b49e3a..7bc226aa2 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -24,8 +24,8 @@ Item { property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id) property real scale: ConfigOptions.overview.scale - property real workspaceImplicitWidth: (monitor.width - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale - property real workspaceImplicitHeight: (monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale + property real workspaceImplicitWidth: (monitor.width - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale + property real workspaceImplicitHeight: (monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale property real workspaceNumberMargin: 80 property real workspaceNumberSize: 80 From 116bea5f97013129a74987e29d14696475ec67d7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 22:38:09 +0200 Subject: [PATCH 240/824] overview: handle monitor transformations --- .config/quickshell/modules/overview/OverviewWidget.qml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index 7bc226aa2..090ead302 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -24,8 +24,12 @@ Item { property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id) property real scale: ConfigOptions.overview.scale - property real workspaceImplicitWidth: (monitor.width - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale - property real workspaceImplicitHeight: (monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale + property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ? + ((monitor.height - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) : + ((monitor.width - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) + property real workspaceImplicitHeight: (monitorData?.transform % 2 === 1) ? + ((monitor.width - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale) : + ((monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale) property real workspaceNumberMargin: 80 property real workspaceNumberSize: 80 From 9d51815661e807fbc303b5c7a3cc3e9993092da7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 22:56:39 +0200 Subject: [PATCH 241/824] hyprland: cleanup bezier curves --- .config/hypr/hyprland/general.conf | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/.config/hypr/hyprland/general.conf b/.config/hypr/hyprland/general.conf index f7ac79a18..556cb25c9 100644 --- a/.config/hypr/hyprland/general.conf +++ b/.config/hypr/hyprland/general.conf @@ -1,5 +1,5 @@ # MONITOR CONFIG -monitor=,preferred,auto,1 +monitor=,preferred,auto,1,transform, 0 # monitor=,addreserved, 0, 0, 0, 0 # Custom reserved area # HDMI port: mirror display. To see device name, use `hyprctl monitors` @@ -99,6 +99,7 @@ decoration { # Shader # screen_shader = ~/.config/hypr/shaders/nothing.frag # screen_shader = ~/.config/hypr/shaders/vibrance.frag + # screen_shader = /home/end/.config/hypr/shaders/antiflash.frag # Dim dim_inactive = false @@ -110,20 +111,10 @@ animations { enabled = true # Animation curves - bezier = linear, 0, 0, 1, 1 - bezier = md3_standard, 0.2, 0, 0, 1 bezier = md3_decel, 0.05, 0.7, 0.1, 1 bezier = md3_accel, 0.3, 0, 0.8, 0.15 - bezier = overshot, 0.05, 0.9, 0.1, 1.1 - bezier = crazyshot, 0.1, 1.5, 0.76, 0.92 - bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0 bezier = menu_decel, 0.1, 1, 0, 1 bezier = menu_accel, 0.52, 0.03, 0.72, 0.08 - bezier = easeInOutCirc, 0.85, 0, 0.15, 1 - bezier = easeOutCirc, 0, 0.55, 0.45, 1 - bezier = easeOutExpo, 0.16, 1, 0.3, 1 - bezier = softAcDecel, 0.26, 0.26, 0.15, 1 - bezier = md2, 0.4, 0, 0.2, 1 # use with .2s duration # Animation configs animation = windows, 1, 3, md3_decel, popin 60% animation = windowsIn, 1, 3, md3_decel, popin 60% @@ -136,9 +127,6 @@ animations { animation = fadeLayersIn, 1, 0.5, menu_decel animation = fadeLayersOut, 1, 3, menu_accel animation = workspaces, 1, 7, menu_decel, slide - # animation = workspaces, 1, 2.5, softAcDecel, slide - # animation = workspaces, 1, 7, menu_decel, slidefade 15% - # animation = specialWorkspace, 1, 3, md3_decel, slidefadevert 15% animation = specialWorkspace, 1, 3, md3_decel, slidevert } From f13912a027f0096d76aa8414c2bb4b76514e0102 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 23:05:32 +0200 Subject: [PATCH 242/824] make persistent states persistent --- .../modules/common/functions/object_utils.js | 26 ++++- .../sidebarRight/BottomWidgetGroup.qml | 9 +- .config/quickshell/services/ConfigLoader.qml | 22 +--- .../services/PersistentStateManager.qml | 100 ++++++++++++++++++ .config/quickshell/shell.qml | 1 + 5 files changed, 126 insertions(+), 32 deletions(-) create mode 100644 .config/quickshell/services/PersistentStateManager.qml diff --git a/.config/quickshell/modules/common/functions/object_utils.js b/.config/quickshell/modules/common/functions/object_utils.js index 51eed7f92..96c632fd3 100644 --- a/.config/quickshell/modules/common/functions/object_utils.js +++ b/.config/quickshell/modules/common/functions/object_utils.js @@ -1,7 +1,3 @@ -function trimFileProtocol(str) { - return str.startsWith("file://") ? str.slice(7) : str; -} - function toPlainObject(qtObj) { if (qtObj === null || typeof qtObj !== "object") return qtObj; @@ -26,4 +22,24 @@ function toPlainObject(qtObj) { } } return result; -} \ No newline at end of file +} + +function applyToQtObject(qtObj, jsonObj) { + if (!qtObj || typeof jsonObj !== "object" || jsonObj === null) return; + + for (let key in jsonObj) { + if (!qtObj.hasOwnProperty(key)) continue; + + // Check if the property is a QtObject (not a value) + const value = qtObj[key]; + const jsonValue = jsonObj[key]; + + // If it's an object and not an array, recurse + if (value && typeof value === "object" && !Array.isArray(value)) { + applyToQtObject(value, jsonValue); + } else { + // Otherwise, assign the value + qtObj[key] = jsonValue; + } + } +} diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index c96bbd733..882d3a035 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -29,13 +29,8 @@ Rectangle { } } - Component.onCompleted: { - bottomWidgetGroupRow.opacity = !collapsed - collapsedBottomWidgetGroupRow.opacity = collapsed - } - function setCollapsed(state) { - PersistentStates.sidebar.bottomGroup.collapsed = state + PersistentStateManager.setState("sidebar.bottomGroup.collapsed", state) if (collapsed) { bottomWidgetGroupRow.opacity = 0 } @@ -70,6 +65,7 @@ Rectangle { // The thing when collapsed RowLayout { id: collapsedBottomWidgetGroupRow + opacity: collapsed ? 1 : 0 visible: opacity > 0 Behavior on opacity { NumberAnimation { @@ -111,6 +107,7 @@ Rectangle { RowLayout { id: bottomWidgetGroupRow + opacity: collapsed ? 0 : 1 visible: opacity > 0 Behavior on opacity { NumberAnimation { diff --git a/.config/quickshell/services/ConfigLoader.qml b/.config/quickshell/services/ConfigLoader.qml index 176dfd0ca..1bc940d93 100644 --- a/.config/quickshell/services/ConfigLoader.qml +++ b/.config/quickshell/services/ConfigLoader.qml @@ -25,27 +25,7 @@ Singleton { try { const json = JSON.parse(fileContent); - function applyToQtObject(qtObj, jsonObj) { - if (!qtObj || typeof jsonObj !== "object" || jsonObj === null) return; - - for (let key in jsonObj) { - if (!qtObj.hasOwnProperty(key)) continue; - - // Check if the property is a QtObject (not a value) - const value = qtObj[key]; - const jsonValue = jsonObj[key]; - - // If it's an object and not an array, recurse - if (value && typeof value === "object" && !Array.isArray(value)) { - applyToQtObject(value, jsonValue); - } else { - // Otherwise, assign the value - qtObj[key] = jsonValue; - } - } - } - - applyToQtObject(ConfigOptions, json); + ObjectUtils.applyToQtObject(ConfigOptions, json); if (root.firstLoad) { root.firstLoad = false; } else { diff --git a/.config/quickshell/services/PersistentStateManager.qml b/.config/quickshell/services/PersistentStateManager.qml new file mode 100644 index 000000000..a69702a7f --- /dev/null +++ b/.config/quickshell/services/PersistentStateManager.qml @@ -0,0 +1,100 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common" +import "root:/modules/common/functions/object_utils.js" as ObjectUtils +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland +import Qt.labs.platform + +Singleton { + id: root + property string fileDir: `${StandardPaths.standardLocations(StandardPaths.StateLocation)[0]}` + property string fileName: "states.json" + property string filePath: `${root.fileDir}/${root.fileName}` + + function getState(nestedKey) { + let keys = nestedKey.split("."); + let obj = PersistentStates; + for (let i = 0; i < keys.length; ++i) { + if (obj[keys[i]] === undefined) { + console.error(`[PersistentStateManager] Key "${keys[i]}" not found in PersistentStates`); + return null; + } + obj = obj[keys[i]]; + } + return obj; + } + + function setState(nestedKey, value) { + let keys = nestedKey.split("."); + let obj = PersistentStates; + let parents = [obj]; + + // Traverse and collect parent objects + for (let i = 0; i < keys.length - 1; ++i) { + if (!obj[keys[i]] || typeof obj[keys[i]] !== "object") { + obj[keys[i]] = {}; + } + obj = obj[keys[i]]; + parents.push(obj); + } + + // Set the value at the innermost key + obj[keys[keys.length - 1]] = value; + + saveStates() + } + + function loadStates() { + stateFileView.reload() + } + + function saveStates() { + console.log("[PersistentStateManager] Saving states to file:", root.filePath) + const plainStates = ObjectUtils.toPlainObject(PersistentStates) + stateFileView.setText(JSON.stringify(plainStates, null, 2)) + } + + function applyStates(fileContent) { + try { + const json = JSON.parse(fileContent); + + ObjectUtils.applyToQtObject(PersistentStates, json); + } catch (e) { + console.error("[PersistentStateManager] Error reading file:", e); + return; + } + } + + Timer { + id: delayedFileRead + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + running: false + onTriggered: { + root.applyStates(stateFileView.text()) + } + } + + FileView { + id: stateFileView + path: root.filePath + watchChanges: true + // onFileChanged: { + // console.log("[PersistentStateManager] File changed, reloading...") + // this.reload() + // delayedFileRead.start() + // } + onLoadedChanged: { + const fileContent = stateFileView.text() + root.applyStates(fileContent) + } + onLoadFailed: (error) => { + console.log("[PersistentStateManager] File not found, creating new file") + root.saveStates() + } + } +} diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 7bc831b88..f6b60478b 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -19,6 +19,7 @@ ShellRoot { Component.onCompleted: { MaterialThemeLoader.reapplyTheme() ConfigLoader.loadConfig() + PersistentStateManager.loadStates() } Bar {} From 3c877197b49f53a5d01eb48579e859e29b834340 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 23:42:28 +0200 Subject: [PATCH 243/824] make right sidebar tab persistent --- .../modules/common/PersistentStates.qml | 4 ++++ .../modules/common/widgets/PrimaryTabBar.qml | 9 +++++++- .../sidebarRight/BottomWidgetGroup.qml | 6 ++++- .../sidebarRight/CenterWidgetGroup.qml | 23 +++++++++++-------- .../services/PersistentStateManager.qml | 2 +- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/.config/quickshell/modules/common/PersistentStates.qml b/.config/quickshell/modules/common/PersistentStates.qml index c991c47dc..eb62219d0 100644 --- a/.config/quickshell/modules/common/PersistentStates.qml +++ b/.config/quickshell/modules/common/PersistentStates.qml @@ -5,8 +5,12 @@ pragma ComponentBehavior: Bound Singleton { property QtObject sidebar: QtObject { + property QtObject centerGroup: QtObject { + property int selectedTab: 0 + } property QtObject bottomGroup: QtObject { property bool collapsed: false + property int selectedTab: 0 } } diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml b/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml index 9ceac9e4d..84fac10d7 100644 --- a/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml +++ b/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml @@ -7,6 +7,7 @@ import Quickshell ColumnLayout { id: root spacing: 0 + property bool _initialized: false required property var tabButtonList // Something like [{"icon": "notifications", "name": qsTr("Notifications")}, {"icon": "volume_up", "name": qsTr("Volume mixer")}] required property var externalTrackedTab property bool enableIndicatorAnimation: false @@ -16,7 +17,13 @@ ColumnLayout { id: tabBar Layout.fillWidth: true currentIndex: root.externalTrackedTab - onCurrentIndexChanged: root.onCurrentIndexChanged(currentIndex) + onCurrentIndexChanged: { + if (!root._initialized) { + root._initialized = true + return + } + root.onCurrentIndexChanged(currentIndex) + } background: Item { WheelHandler { diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index 882d3a035..66ebf051f 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -14,13 +14,17 @@ Rectangle { color: Appearance.colors.colLayer1 clip: true implicitHeight: collapsed ? collapsedBottomWidgetGroupRow.implicitHeight : bottomWidgetGroupRow.implicitHeight - property int selectedTab: 0 + property int selectedTab: PersistentStates.sidebar.bottomGroup.selectedTab property bool collapsed: PersistentStates.sidebar.bottomGroup.collapsed property var tabs: [ {"type": "calendar", "name": "Calendar", "icon": "calendar_month", "widget": calendarWidget}, {"type": "todo", "name": "To Do", "icon": "done_outline", "widget": todoWidget} ] + onSelectedTabChanged: { + PersistentStateManager.setState("sidebar.bottomGroup.selectedTab", selectedTab) + } + Behavior on implicitHeight { NumberAnimation { duration: Appearance.animation.elementMove.duration diff --git a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml index d69403a38..ede9e7219 100644 --- a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml @@ -16,23 +16,27 @@ Rectangle { radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 - property int currentTab: 0 + property int selectedTab: PersistentStates.sidebar.centerGroup.selectedTab property var tabButtonList: [{"icon": "notifications", "name": qsTr("Notifications")}, {"icon": "volume_up", "name": qsTr("Volume mixer")}] + onSelectedTabChanged: { + PersistentStateManager.setState("sidebar.centerGroup.selectedTab", selectedTab) + } + Keys.onPressed: (event) => { if (event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) { if (event.key === Qt.Key_PageDown) { - root.currentTab = Math.min(root.currentTab + 1, root.tabButtonList.length - 1) + PersistentStateManager.setState("sidebar.centerGroup.selectedTab", Math.min(root.selectedTab + 1, root.tabButtonList.length - 1)) } else if (event.key === Qt.Key_PageUp) { - root.currentTab = Math.max(root.currentTab - 1, 0) + PersistentStateManager.setState("sidebar.centerGroup.selectedTab", Math.max(root.selectedTab - 1, 0)) } event.accepted = true; } if (event.modifiers === Qt.ControlModifier) { if (event.key === Qt.Key_Tab) { - root.currentTab = (root.currentTab + 1) % root.tabButtonList.length; + PersistentStateManager.setState("sidebar.centerGroup.selectedTab", (root.selectedTab + 1) % root.tabButtonList.length); } else if (event.key === Qt.Key_Backtab) { - root.currentTab = (root.currentTab - 1 + root.tabButtonList.length) % root.tabButtonList.length; + PersistentStateManager.setState("sidebar.centerGroup.selectedTab", (root.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length); } event.accepted = true; } @@ -46,9 +50,10 @@ Rectangle { PrimaryTabBar { id: tabBar tabButtonList: root.tabButtonList - externalTrackedTab: root.currentTab + externalTrackedTab: root.selectedTab + function onCurrentIndexChanged(currentIndex) { - root.currentTab = currentIndex + PersistentStateManager.setState("sidebar.centerGroup.selectedTab", currentIndex) } } @@ -57,10 +62,10 @@ Rectangle { Layout.topMargin: 5 Layout.fillWidth: true Layout.fillHeight: true - currentIndex: currentTab + currentIndex: root.selectedTab onCurrentIndexChanged: { tabBar.enableIndicatorAnimation = true - root.currentTab = currentIndex + PersistentStateManager.setState("sidebar.centerGroup.selectedTab", currentIndex) } clip: true diff --git a/.config/quickshell/services/PersistentStateManager.qml b/.config/quickshell/services/PersistentStateManager.qml index a69702a7f..0f1857b86 100644 --- a/.config/quickshell/services/PersistentStateManager.qml +++ b/.config/quickshell/services/PersistentStateManager.qml @@ -29,6 +29,7 @@ Singleton { } function setState(nestedKey, value) { + // console.log(`[PersistentStateManager] Setting state: ${nestedKey} = ${value}`); let keys = nestedKey.split("."); let obj = PersistentStates; let parents = [obj]; @@ -53,7 +54,6 @@ Singleton { } function saveStates() { - console.log("[PersistentStateManager] Saving states to file:", root.filePath) const plainStates = ObjectUtils.toPlainObject(PersistentStates) stateFileView.setText(JSON.stringify(plainStates, null, 2)) } From 18366c147fbcfa3c441409900487147c6aa4bf8c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 23:51:33 +0200 Subject: [PATCH 244/824] make left sidebar selected tab persistent --- .../modules/common/PersistentStates.qml | 3 +++ .../modules/sidebarLeft/SidebarLeft.qml | 18 +++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.config/quickshell/modules/common/PersistentStates.qml b/.config/quickshell/modules/common/PersistentStates.qml index eb62219d0..d093786ec 100644 --- a/.config/quickshell/modules/common/PersistentStates.qml +++ b/.config/quickshell/modules/common/PersistentStates.qml @@ -5,6 +5,9 @@ pragma ComponentBehavior: Bound Singleton { property QtObject sidebar: QtObject { + property QtObject leftSide: QtObject { + property int selectedTab: 0 + } property QtObject centerGroup: QtObject { property int selectedTab: 0 } diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index cdc890dc7..fd0d7a877 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -25,7 +25,7 @@ Scope { // Scope id: sidebarRoot visible: false focusable: true - property int currentTab: 0 + property int selectedTab: PersistentStates.sidebar.leftSide.selectedTab property bool extend: false property real sidebarWidth: sidebarRoot.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth @@ -106,16 +106,16 @@ Scope { // Scope } if (event.modifiers === Qt.ControlModifier) { if (event.key === Qt.Key_PageDown) { - sidebarRoot.currentTab = Math.min(sidebarRoot.currentTab + 1, root.tabButtonList.length - 1) + PersistentStateManager.setState("sidebar.leftSide.selectedTab", Math.min(sidebarRoot.selectedTab + 1, root.tabButtonList.length - 1)) } else if (event.key === Qt.Key_PageUp) { - sidebarRoot.currentTab = Math.max(sidebarRoot.currentTab - 1, 0) + PersistentStateManager.setState("sidebar.leftSide.selectedTab", Math.max(sidebarRoot.selectedTab - 1, 0)) } else if (event.key === Qt.Key_Tab) { - sidebarRoot.currentTab = (sidebarRoot.currentTab + 1) % root.tabButtonList.length; + PersistentStateManager.setState("sidebar.leftSide.selectedTab", (sidebarRoot.selectedTab + 1) % root.tabButtonList.length); } else if (event.key === Qt.Key_Backtab) { - sidebarRoot.currentTab = (sidebarRoot.currentTab - 1 + root.tabButtonList.length) % root.tabButtonList.length; + PersistentStateManager.setState("sidebar.leftSide.selectedTab", (sidebarRoot.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length); } else if (event.key === Qt.Key_O) { sidebarRoot.extend = !sidebarRoot.extend; @@ -133,9 +133,9 @@ Scope { // Scope PrimaryTabBar { // Tab strip id: tabBar tabButtonList: root.tabButtonList - externalTrackedTab: sidebarRoot.currentTab + externalTrackedTab: sidebarRoot.selectedTab function onCurrentIndexChanged(currentIndex) { - sidebarRoot.currentTab = currentIndex + PersistentStateManager.setState("sidebar.leftSide.selectedTab", currentIndex) } } @@ -144,10 +144,10 @@ Scope { // Scope Layout.topMargin: 5 Layout.fillWidth: true Layout.fillHeight: true - currentIndex: currentTab + currentIndex: sidebarRoot.selectedTab onCurrentIndexChanged: { tabBar.enableIndicatorAnimation = true - sidebarRoot.currentTab = currentIndex + PersistentStateManager.setState("sidebar.leftSide.selectedTab", currentIndex) } clip: true From 6619989cf262fb20052dc24bfa74403ce0135689 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 10 May 2025 23:57:13 +0200 Subject: [PATCH 245/824] left sidebar: fix entry focus on open --- .config/quickshell/modules/sidebarLeft/AiChat.qml | 6 ------ .config/quickshell/modules/sidebarLeft/SidebarLeft.qml | 1 + 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 0c32d8cde..29ad8601c 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -30,12 +30,6 @@ Item { Hyprland.dispatch(`exec mkdir -p ${faviconDownloadPath}`) } - Connections { - target: panelWindow - function onVisibleChanged(visible) { - messageInputField.forceActiveFocus() - } - } onFocusChanged: (focus) => { if (focus) { messageInputField.forceActiveFocus() diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index fd0d7a877..204fd1eff 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -65,6 +65,7 @@ Scope { // Scope target: sidebarRoot function onVisibleChanged() { delayedGrabTimer.start() + swipeView.children[0].children[0].children[sidebarRoot.selectedTab].forceActiveFocus() } } From af5654e99f9887d469ffa9e1236001a0b58fe578 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 07:43:32 +0200 Subject: [PATCH 246/824] overview: only show ws highlight on focused monitor --- .../quickshell/modules/overview/OverviewWidget.qml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index 090ead302..781eb5bc9 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -18,6 +18,7 @@ Item { readonly property var toplevels: ToplevelManager.toplevels readonly property int workspacesShown: ConfigOptions.overview.numOfRows * ConfigOptions.overview.numOfCols readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / workspacesShown) + property bool monitorIsFocused: (Hyprland.focusedMonitor.id == monitor.id) property var windows: HyprlandData.windowList property var windowByAddress: HyprlandData.windowByAddress property var windowAddresses: HyprlandData.addresses @@ -80,15 +81,15 @@ Item { property color hoveredWorkspaceColor: Appearance.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1) property color hoveredBorderColor: Appearance.colors.colLayer2Hover property color activeBorderColor: Appearance.m3colors.m3secondary - property bool hovered: false + property bool hoveredWhileDragging: false implicitWidth: root.workspaceImplicitWidth implicitHeight: root.workspaceImplicitHeight - color: hovered ? hoveredWorkspaceColor : defaultWorkspaceColor + color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor radius: Appearance.rounding.screenRounding * root.scale border.width: 2 - border.color: monitor.activeWorkspace?.id == workspaceValue ? activeBorderColor : - hovered ? hoveredBorderColor : "transparent" + border.color: (monitor.activeWorkspace?.id == workspaceValue && root.monitorIsFocused) ? activeBorderColor : + hoveredWhileDragging ? hoveredBorderColor : "transparent" MouseArea { id: workspaceArea @@ -108,10 +109,10 @@ Item { onEntered: { root.draggingTargetWorkspace = workspaceValue if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return; - hovered = true + hoveredWhileDragging = true } onExited: { - hovered = false + hoveredWhileDragging = false if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1 } } From d90067a11dee94d515da194024db0e7be34d6842 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 08:03:55 +0200 Subject: [PATCH 247/824] make overviewToggleReleaseInterrupt description clearer --- .config/quickshell/modules/overview/Overview.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index b6d093eae..053c24507 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -153,7 +153,7 @@ Scope { GlobalShortcut { name: "overviewToggleReleaseInterrupt" description: "Interrupts possibility of overview being toggled on release. " + - "This is necessary because onReleased triggers whether or not you press something else while holding the key. " + + "This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. " + "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything." onPressed: { From b827edf2efa2815e3dd830f5fe58399e8bd0ace9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 08:08:01 +0200 Subject: [PATCH 248/824] persistent state manager: prevent writing default config before first load --- .config/quickshell/services/PersistentStateManager.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/services/PersistentStateManager.qml b/.config/quickshell/services/PersistentStateManager.qml index 0f1857b86..ba23dfeb5 100644 --- a/.config/quickshell/services/PersistentStateManager.qml +++ b/.config/quickshell/services/PersistentStateManager.qml @@ -14,6 +14,7 @@ Singleton { property string fileDir: `${StandardPaths.standardLocations(StandardPaths.StateLocation)[0]}` property string fileName: "states.json" property string filePath: `${root.fileDir}/${root.fileName}` + property bool allowWriteback: false function getState(nestedKey) { let keys = nestedKey.split("."); @@ -29,7 +30,7 @@ Singleton { } function setState(nestedKey, value) { - // console.log(`[PersistentStateManager] Setting state: ${nestedKey} = ${value}`); + if (!root.allowWriteback) return; let keys = nestedKey.split("."); let obj = PersistentStates; let parents = [obj]; @@ -61,8 +62,8 @@ Singleton { function applyStates(fileContent) { try { const json = JSON.parse(fileContent); - ObjectUtils.applyToQtObject(PersistentStates, json); + root.allowWriteback = true } catch (e) { console.error("[PersistentStateManager] Error reading file:", e); return; From 84fd898bbe6d3c1ca968676fbe5c5b2a59402d44 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 08:08:41 +0200 Subject: [PATCH 249/824] make choice of booru provider and nsfw persistent --- .config/quickshell/modules/common/PersistentStates.qml | 5 +++++ .config/quickshell/modules/sidebarLeft/Anime.qml | 10 +++++----- .config/quickshell/services/Booru.qml | 6 +++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.config/quickshell/modules/common/PersistentStates.qml b/.config/quickshell/modules/common/PersistentStates.qml index d093786ec..d3b8fa79f 100644 --- a/.config/quickshell/modules/common/PersistentStates.qml +++ b/.config/quickshell/modules/common/PersistentStates.qml @@ -17,4 +17,9 @@ Singleton { } } + property QtObject booru: QtObject { + property bool allowNsfw: false + property string provider: "yandere" + } + } diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 76af23ca9..523d2252d 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -69,14 +69,14 @@ Item { name: "safe", description: qsTr("Disable NSFW content"), execute: () => { - ConfigOptions.sidebar.booru.allowNsfw = false; + PersistentStateManager.setState("booru.allowNsfw", false); } }, { name: "lewd", description: qsTr("Allow NSFW content"), execute: () => { - ConfigOptions.sidebar.booru.allowNsfw = true; + PersistentStateManager.setState("booru.allowNsfw", true); } }, ] @@ -110,7 +110,7 @@ Item { break; } } - Booru.makeRequest(tagList, ConfigOptions.sidebar.booru.allowNsfw, ConfigOptions.sidebar.booru.limit, pageIndex); + Booru.makeRequest(tagList, PersistentStates.booru.allowNsfw, ConfigOptions.sidebar.booru.limit, pageIndex); } } @@ -641,10 +641,10 @@ Item { enabled: Booru.currentProvider !== "zerochan" scale: 0.6 Layout.alignment: Qt.AlignVCenter - checked: (ConfigOptions.sidebar.booru.allowNsfw && Booru.currentProvider !== "zerochan") + checked: (PersistentStates.booru.allowNsfw && Booru.currentProvider !== "zerochan") onCheckedChanged: { if (!nsfwSwitch.enabled) return; - ConfigOptions.sidebar.booru.allowNsfw = checked + PersistentStateManager.setState("booru.allowNsfw", checked) } } } diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index 27a3615eb..d0c5dceb2 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -16,7 +16,7 @@ Singleton { Connections { target: ConfigOptions.sidebar.booru function onAllowNsfwChanged() { - root.addSystemMessage(ConfigOptions.sidebar.booru.allowNsfw ? qsTr("Tiddies enabled") : qsTr("No horny")) + root.addSystemMessage(PersistentStates.booru.allowNsfw ? qsTr("Tiddies enabled") : qsTr("No horny")) } } @@ -224,7 +224,7 @@ Singleton { } }, } - property var currentProvider: ConfigOptions.sidebar.booru.defaultProvider + property var currentProvider: PersistentStates.booru.provider function getWorkingImageSource(url) { if (url.includes('pximg.net')) { @@ -236,7 +236,7 @@ Singleton { function setProvider(provider) { provider = provider.toLowerCase() if (providerList.indexOf(provider) !== -1) { - currentProvider = provider + PersistentStateManager.setState("booru.provider", provider) root.addSystemMessage(qsTr("Provider set to ") + providers[provider].name + (provider == "zerochan" ? qsTr(". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!") : "")) } else { From de07c95257e8d67e4cbc09aa04ab66ad180661ee Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 08:14:26 +0200 Subject: [PATCH 250/824] hyprland: make fuzzel no anim --- .config/hypr/hyprland/rules.conf | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index 1f0043004..0226a51de 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -111,8 +111,10 @@ layerrule = animation slide left, quickshell:sidebarLeft layerrule = animation slide top, quickshell:onScreenDisplay layerrule = animation fade, quickshell:session layerrule = blur, quickshell:session -layerrule = noanim, quickshell:overview # Launcher needs to be FAST -layerrule = noanim, gtk4-layer-shell # Launcher needs to be FAST +# Launchers need to be FAST +layerrule = noanim, quickshell:overview +layerrule = noanim, launcher +layerrule = noanim, gtk4-layer-shell ## outfoxxed's stuff layerrule = blur, shell:bar layerrule = ignorezero, shell:bar From 5dbf255c5f336045a10a3da87a181869822869b1 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 08:48:03 +0200 Subject: [PATCH 251/824] fix overview focus grab on init --- .config/quickshell/modules/overview/Overview.qml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 053c24507..270d0042c 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -48,22 +48,6 @@ Scope { } } - Connections { - target: root - function onVisibleChanged() { - delayedGrabTimer.start() - } - } - - Timer { - id: delayedGrabTimer - interval: ConfigOptions.hacks.arbitraryRaceConditionDelay - repeat: false - onTriggered: { - grab.active = root.visible - } - } - width: columnLayout.width height: columnLayout.height From 7fe8838999468fc7865369deb189831c750d90c2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 09:59:48 +0200 Subject: [PATCH 252/824] mic mute indicator --- .config/quickshell/modules/bar/Bar.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 44f18e335..9783c4f3a 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -166,6 +166,12 @@ Scope { anchors.centerIn: parent spacing: 15 + MaterialSymbol { + visible: Audio.source?.audio?.muted + text: "mic_off" + iconSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer0 + } MaterialSymbol { text: (Network.networkName.length > 0 && Network.networkName != "lo") ? ( Network.networkStrength > 80 ? "signal_wifi_4_bar" : From 3e66c2d0f3f520671e59bac079517dee8e04d574 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 10:00:14 +0200 Subject: [PATCH 253/824] change race condition delay --- .config/quickshell/modules/common/ConfigOptions.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 10a097884..57d6e5aa5 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -76,7 +76,7 @@ Singleton { } property QtObject hacks: QtObject { - property int arbitraryRaceConditionDelay: 10 // milliseconds + property int arbitraryRaceConditionDelay: 20 // milliseconds } } From 51686c4aa4aeae6c57c844739d797d88cdf3f4ac Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 10:01:01 +0200 Subject: [PATCH 254/824] change progress bar anim --- .../quickshell/modules/common/widgets/StyledProgressBar.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml index 12b9c0cd2..765f38d10 100644 --- a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml +++ b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml @@ -16,9 +16,9 @@ ProgressBar { Behavior on value { NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + duration: Appearance.animation.elementMoveEnter.duration + easing.type: Appearance.animation.elementMoveEnter.type + easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve } } From 9116cf83df57a3cbd6a9b6873776462f2c12223f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 10:03:03 +0200 Subject: [PATCH 255/824] volume osd: handle mute --- .../modules/onScreenDisplay/OnScreenDisplayVolume.qml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml index 4323afcda..8f7541e68 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml @@ -34,6 +34,10 @@ Scope { if (!Audio.ready) return root.triggerOsd() } + function onMutedChanged() { + if (!Audio.ready) return + root.triggerOsd() + } } Connections { @@ -97,7 +101,7 @@ Scope { id: osdValues anchors.centerIn: parent value: Audio.sink?.audio.volume ?? 0 - icon: "volume_up" + icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up" name: qsTr("Volume") } } From 64ec9bdfe467812230f41dc9382c2a4c625ca92e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 10:29:18 +0200 Subject: [PATCH 256/824] shorter --- .config/quickshell/services/Audio.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/services/Audio.qml b/.config/quickshell/services/Audio.qml index b6f0e17e7..564c8afc2 100644 --- a/.config/quickshell/services/Audio.qml +++ b/.config/quickshell/services/Audio.qml @@ -12,7 +12,7 @@ Singleton { property var source: Pipewire.defaultAudioSource PwObjectTracker { - objects: [Pipewire.defaultAudioSink, Pipewire.defaultAudioSource] + objects: [sink, source] } } From 39594baa4659af2ab0195a0c73f30351cc3c39ff Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 11:06:19 +0200 Subject: [PATCH 257/824] bar: volume mute indicator --- .config/quickshell/modules/bar/Bar.qml | 42 ++++++++++++++++--- .../modules/common/widgets/Revealer.qml | 33 +++++++++++++++ 2 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/Revealer.qml diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 9783c4f3a..2f468618d 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -164,15 +164,45 @@ Scope { RowLayout { id: indicatorsRowLayout anchors.centerIn: parent - spacing: 15 + property real realSpacing: 15 + spacing: 0 - MaterialSymbol { - visible: Audio.source?.audio?.muted - text: "mic_off" - iconSize: Appearance.font.pixelSize.larger - color: Appearance.colors.colOnLayer0 + Revealer { + reveal: Audio.sink?.audio?.muted + Layout.fillHeight: true + Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0 + Behavior on Layout.rightMargin { + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } + MaterialSymbol { + text: "volume_off" + iconSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer0 + } + } + Revealer { + reveal: Audio.source?.audio?.muted + Layout.fillHeight: true + Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0 + Behavior on Layout.rightMargin { + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } + MaterialSymbol { + text: "mic_off" + iconSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer0 + } } MaterialSymbol { + Layout.rightMargin: indicatorsRowLayout.realSpacing text: (Network.networkName.length > 0 && Network.networkName != "lo") ? ( Network.networkStrength > 80 ? "signal_wifi_4_bar" : Network.networkStrength > 60 ? "network_wifi_3_bar" : diff --git a/.config/quickshell/modules/common/widgets/Revealer.qml b/.config/quickshell/modules/common/widgets/Revealer.qml new file mode 100644 index 000000000..eaac35e50 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/Revealer.qml @@ -0,0 +1,33 @@ +import "root:/modules/common" +import QtQuick +import Quickshell + +/** + * Recreation of GTK revealer. Expects one single child. + */ +Item { + id: root + property bool reveal + property bool vertical: false + clip: true + + implicitWidth: (reveal || vertical) ? childrenRect.width : 0 + implicitHeight: (reveal || !vertical) ? childrenRect.height : 0 + + Behavior on implicitWidth { + enabled: !vertical + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } + Behavior on implicitHeight { + enabled: vertical + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } +} From 6030d21e37d12cfa1d0d7a64c0248d44168682a4 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 11:20:24 +0200 Subject: [PATCH 258/824] bar: fix messed mouse interaction hog/passthrough --- .config/quickshell/modules/bar/Bar.qml | 381 +++++++++--------- .../quickshell/modules/bar/SysTrayItem.qml | 2 +- 2 files changed, 190 insertions(+), 193 deletions(-) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 2f468618d..c4112d8bc 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -51,191 +51,11 @@ Scope { color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" height: barHeight - RowLayout { // Left section - id: leftSection + MouseArea { // Left side | scroll to change brightness + id: barLeftSideMouseArea anchors.left: parent.left implicitHeight: barHeight width: (barRoot.width - middleSection.width) / 2 - spacing: 10 - - Rectangle { - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - Layout.leftMargin: Appearance.rounding.screenRounding - Layout.fillWidth: false - - // Layout.fillHeight: true - radius: Appearance.rounding.full - color: (barLeftSideMouseArea.pressed || GlobalStates.sidebarLeftOpenCount > 0) ? Appearance.colors.colLayer1Active : barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : "transparent" - implicitWidth: distroIcon.width + 5*2 - implicitHeight: distroIcon.height + 5*2 - - CustomIcon { - id: distroIcon - anchors.centerIn: parent - width: 19.5 - height: 19.5 - source: ConfigOptions.bar.topLeftIcon == 'distro' ? - SystemInfo.distroIcon : "spark-symbolic" - } - - ColorOverlay { - anchors.fill: distroIcon - source: distroIcon - color: Appearance.colors.colOnLayer0 - } - } - - ActiveWindow { - Layout.rightMargin: Appearance.rounding.screenRounding - Layout.fillWidth: true - bar: barRoot - } - } - - RowLayout { // Middle section - id: middleSection - anchors.centerIn: parent - spacing: 8 - - RowLayout { - Layout.preferredWidth: barCenterSideModuleWidth - spacing: 4 - Layout.fillHeight: true - implicitWidth: 350 - - Resources { - } - - Media { - Layout.fillWidth: true - } - - } - - RowLayout { - Layout.fillWidth: true - Layout.fillHeight: true - spacing: 4 - - Workspaces { - bar: barRoot - } - - } - - RowLayout { - Layout.preferredWidth: barCenterSideModuleWidth - Layout.fillHeight: true - spacing: 4 - - ClockWidget { - Layout.alignment: Qt.AlignVCenter - Layout.fillWidth: true - } - - UtilButtons { - Layout.alignment: Qt.AlignVCenter - } - - Battery { - Layout.alignment: Qt.AlignVCenter - } - - } - - } - - // Right section - RowLayout { - id: rightSection - anchors.right: parent.right - implicitHeight: barHeight - width: (barRoot.width - middleSection.width) / 2 - spacing: 5 - layoutDirection: Qt.RightToLeft - - Rectangle { - Layout.margins: 4 - Layout.rightMargin: Appearance.rounding.screenRounding - Layout.fillHeight: true - implicitWidth: indicatorsRowLayout.implicitWidth + 10*2 - radius: Appearance.rounding.full - color: (barRightSideMouseArea.pressed || GlobalStates.sidebarRightOpenCount > 0) ? Appearance.colors.colLayer1Active : barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : "transparent" - RowLayout { - id: indicatorsRowLayout - anchors.centerIn: parent - property real realSpacing: 15 - spacing: 0 - - Revealer { - reveal: Audio.sink?.audio?.muted - Layout.fillHeight: true - Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0 - Behavior on Layout.rightMargin { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } - } - MaterialSymbol { - text: "volume_off" - iconSize: Appearance.font.pixelSize.larger - color: Appearance.colors.colOnLayer0 - } - } - Revealer { - reveal: Audio.source?.audio?.muted - Layout.fillHeight: true - Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0 - Behavior on Layout.rightMargin { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } - } - MaterialSymbol { - text: "mic_off" - iconSize: Appearance.font.pixelSize.larger - color: Appearance.colors.colOnLayer0 - } - } - MaterialSymbol { - Layout.rightMargin: indicatorsRowLayout.realSpacing - text: (Network.networkName.length > 0 && Network.networkName != "lo") ? ( - Network.networkStrength > 80 ? "signal_wifi_4_bar" : - Network.networkStrength > 60 ? "network_wifi_3_bar" : - Network.networkStrength > 40 ? "network_wifi_2_bar" : - Network.networkStrength > 20 ? "network_wifi_1_bar" : - "signal_wifi_0_bar" - ) : "signal_wifi_off" - iconSize: Appearance.font.pixelSize.larger - color: Appearance.colors.colOnLayer0 - } - MaterialSymbol { - text: Bluetooth.bluetoothConnected ? "bluetooth_connected" : Bluetooth.bluetoothEnabled ? "bluetooth" : "bluetooth_disabled" - iconSize: Appearance.font.pixelSize.larger - color: Appearance.colors.colOnLayer0 - } - } - } - - SysTray { - bar: barRoot - Layout.fillWidth: false - } - - Item { - Layout.fillWidth: true - } - - - } - - // Interactions - MouseArea { // Left side: scroll to change brightness - id: barLeftSideMouseArea property bool hovered: false property real lastScrollX: 0 property real lastScrollY: 0 @@ -280,28 +100,120 @@ Scope { } } } + RowLayout { // Left section + id: leftSection + anchors.fill: parent + spacing: 10 + + Rectangle { + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.leftMargin: Appearance.rounding.screenRounding + Layout.fillWidth: false + + // Layout.fillHeight: true + radius: Appearance.rounding.full + color: (barLeftSideMouseArea.pressed || GlobalStates.sidebarLeftOpenCount > 0) ? Appearance.colors.colLayer1Active : barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : "transparent" + implicitWidth: distroIcon.width + 5*2 + implicitHeight: distroIcon.height + 5*2 + + CustomIcon { + id: distroIcon + anchors.centerIn: parent + width: 19.5 + height: 19.5 + source: ConfigOptions.bar.topLeftIcon == 'distro' ? + SystemInfo.distroIcon : "spark-symbolic" + } + + ColorOverlay { + anchors.fill: distroIcon + source: distroIcon + color: Appearance.colors.colOnLayer0 + } + } + + ActiveWindow { + Layout.rightMargin: Appearance.rounding.screenRounding + Layout.fillWidth: true + bar: barRoot + } + } } - MouseArea { // Middle: right-click to toggle overview - id: barMiddleMouseArea - anchors.fill: middleSection - acceptedButtons: Qt.RightButton - - onPressed: (event) => { - if (event.button === Qt.RightButton) { - Hyprland.dispatch('global quickshell:overviewToggle') + RowLayout { // Middle section + id: middleSection + anchors.centerIn: parent + spacing: 8 + + RowLayout { + Layout.preferredWidth: barCenterSideModuleWidth + spacing: 4 + Layout.fillHeight: true + implicitWidth: 350 + + Resources { } + + Media { + Layout.fillWidth: true + } + + } + + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: true + + Workspaces { + bar: barRoot + MouseArea { // Right-click to toggle overview + anchors.fill: parent + acceptedButtons: Qt.RightButton + + onPressed: (event) => { + if (event.button === Qt.RightButton) { + Hyprland.dispatch('global quickshell:overviewToggle') + } + } + } + } + + } + + RowLayout { + Layout.preferredWidth: barCenterSideModuleWidth + Layout.fillHeight: true + spacing: 4 + + ClockWidget { + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + } + + UtilButtons { + Layout.alignment: Qt.AlignVCenter + } + + Battery { + Layout.alignment: Qt.AlignVCenter + } + } } - MouseArea { // Right side: scroll to change volume + MouseArea { // Right side | scroll to change volume id: barRightSideMouseArea + + anchors.right: parent.right + implicitHeight: barHeight + width: (barRoot.width - middleSection.width) / 2 + property bool hovered: false property real lastScrollX: 0 property real lastScrollY: 0 property bool trackingScroll: false - anchors.fill: rightSection + acceptedButtons: Qt.LeftButton hoverEnabled: true propagateComposedEvents: true @@ -346,6 +258,91 @@ Scope { } } } + + RowLayout { + id: rightSection + anchors.fill: parent + spacing: 5 + layoutDirection: Qt.RightToLeft + + Rectangle { + Layout.margins: 4 + Layout.rightMargin: Appearance.rounding.screenRounding + Layout.fillHeight: true + implicitWidth: indicatorsRowLayout.implicitWidth + 10*2 + radius: Appearance.rounding.full + color: (barRightSideMouseArea.pressed || GlobalStates.sidebarRightOpenCount > 0) ? Appearance.colors.colLayer1Active : barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : "transparent" + RowLayout { + id: indicatorsRowLayout + anchors.centerIn: parent + property real realSpacing: 15 + spacing: 0 + + Revealer { + reveal: Audio.sink?.audio?.muted + Layout.fillHeight: true + Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0 + Behavior on Layout.rightMargin { + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } + MaterialSymbol { + text: "volume_off" + iconSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer0 + } + } + Revealer { + reveal: Audio.source?.audio?.muted + Layout.fillHeight: true + Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0 + Behavior on Layout.rightMargin { + NumberAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } + MaterialSymbol { + text: "mic_off" + iconSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer0 + } + } + MaterialSymbol { + Layout.rightMargin: indicatorsRowLayout.realSpacing + text: (Network.networkName.length > 0 && Network.networkName != "lo") ? ( + Network.networkStrength > 80 ? "signal_wifi_4_bar" : + Network.networkStrength > 60 ? "network_wifi_3_bar" : + Network.networkStrength > 40 ? "network_wifi_2_bar" : + Network.networkStrength > 20 ? "network_wifi_1_bar" : + "signal_wifi_0_bar" + ) : "signal_wifi_off" + iconSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer0 + } + MaterialSymbol { + text: Bluetooth.bluetoothConnected ? "bluetooth_connected" : Bluetooth.bluetoothEnabled ? "bluetooth" : "bluetooth_disabled" + iconSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer0 + } + } + } + + SysTray { + bar: barRoot + Layout.fillWidth: false + } + + Item { + Layout.fillWidth: true + } + + + } } } diff --git a/.config/quickshell/modules/bar/SysTrayItem.qml b/.config/quickshell/modules/bar/SysTrayItem.qml index 99e5a258a..dc658d2ef 100644 --- a/.config/quickshell/modules/bar/SysTrayItem.qml +++ b/.config/quickshell/modules/bar/SysTrayItem.qml @@ -24,9 +24,9 @@ MouseArea { break; case Qt.RightButton: if (item.hasMenu) menu.open(); - event.accepted = true; break; } + event.accepted = true; } QsMenuAnchor { From ce553c2c0eed259566dce84a2304522495228d19 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 11:27:49 +0200 Subject: [PATCH 259/824] fix tab bar interaction with persistent states --- .config/quickshell/modules/common/widgets/PrimaryTabBar.qml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml b/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml index 84fac10d7..e0fbd6d90 100644 --- a/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml +++ b/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml @@ -7,7 +7,6 @@ import Quickshell ColumnLayout { id: root spacing: 0 - property bool _initialized: false required property var tabButtonList // Something like [{"icon": "notifications", "name": qsTr("Notifications")}, {"icon": "volume_up", "name": qsTr("Volume mixer")}] required property var externalTrackedTab property bool enableIndicatorAnimation: false @@ -18,10 +17,6 @@ ColumnLayout { Layout.fillWidth: true currentIndex: root.externalTrackedTab onCurrentIndexChanged: { - if (!root._initialized) { - root._initialized = true - return - } root.onCurrentIndexChanged(currentIndex) } From 9ce47952668a02ada5e101bbdf43b0af4b6c33a5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 11:48:38 +0200 Subject: [PATCH 260/824] bar: resolve some warnings --- .config/quickshell/modules/bar/Bar.qml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index c4112d8bc..15cd9a84b 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -60,7 +60,6 @@ Scope { property real lastScrollX: 0 property real lastScrollY: 0 property bool trackingScroll: false - anchors.fill: leftSection acceptedButtons: Qt.LeftButton hoverEnabled: true propagateComposedEvents: true @@ -279,7 +278,7 @@ Scope { spacing: 0 Revealer { - reveal: Audio.sink?.audio?.muted + reveal: Audio.sink?.audio?.muted ?? false Layout.fillHeight: true Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0 Behavior on Layout.rightMargin { @@ -296,7 +295,7 @@ Scope { } } Revealer { - reveal: Audio.source?.audio?.muted + reveal: Audio.source?.audio?.muted ?? false Layout.fillHeight: true Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0 Behavior on Layout.rightMargin { From b91dde50afc3c7ead86e74fc684d3512c6c804d9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 12:05:08 +0200 Subject: [PATCH 261/824] fix overview toggle mouse hog --- .../quickshell/modules/overview/Overview.qml | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 270d0042c..d734ff93c 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -19,6 +19,8 @@ Scope { id: root property var modelData property string searchingText: "" + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) + property bool monitorIsFocused: (Hyprland.focusedMonitor.id == monitor.id) screen: modelData // visible: GlobalStates.overviewOpen visible: true @@ -42,12 +44,30 @@ Scope { HyprlandFocusGrab { id: grab windows: [ root ] - active: GlobalStates.overviewOpen + property bool canBeActive: root.monitorIsFocused + active: false onCleared: () => { if (!active) GlobalStates.overviewOpen = false } } + Connections { + target: GlobalStates + function onOverviewOpenChanged() { + delayedGrabTimer.start() + } + } + + Timer { + id: delayedGrabTimer + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + onTriggered: { + if (!grab.canBeActive) return + grab.active = GlobalStates.overviewOpen + } + } + width: columnLayout.width height: columnLayout.height From 23af7648e4a43a86d3d9ac7df8c285566c66bfea Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 11 May 2025 15:49:03 +0200 Subject: [PATCH 262/824] matugen: add ags sourceview themes --- .config/matugen/config.toml | 7 + .../templates/ags/sourceviewtheme-light.xml | 95 ++++++++++++++ .../matugen/templates/ags/sourceviewtheme.xml | 121 ++++++++++++++++++ 3 files changed, 223 insertions(+) create mode 100644 .config/matugen/templates/ags/sourceviewtheme-light.xml create mode 100644 .config/matugen/templates/ags/sourceviewtheme.xml diff --git a/.config/matugen/config.toml b/.config/matugen/config.toml index 487a2b170..9883e5062 100644 --- a/.config/matugen/config.toml +++ b/.config/matugen/config.toml @@ -29,4 +29,11 @@ output_path = '~/.config/gtk-3.0/gtk.css' input_path = '~/.config/matugen/templates/gtk/gtk-colors.css' output_path = '~/.config/gtk-4.0/gtk.css' +[templates.ags_source_view] +input_path = '~/.config/matugen/templates/ags/sourceviewtheme.xml' +output_path = '~/.config/ags/assets/themes/sourceviewtheme.xml' + +[templates.ags_source_view_light] +input_path = '~/.config/matugen/templates/ags/sourceviewtheme-light.xml' +output_path = '~/.config/ags/assets/themes/sourceviewtheme-light.xml' diff --git a/.config/matugen/templates/ags/sourceviewtheme-light.xml b/.config/matugen/templates/ags/sourceviewtheme-light.xml new file mode 100644 index 000000000..d501c3186 --- /dev/null +++ b/.config/matugen/templates/ags/sourceviewtheme-light.xml @@ -0,0 +1,95 @@ + + + end_4 + <_description>Catppuccin port but very random + + ` + + `${notificationObject.body.replace(/\n/g, "
")}` + } + + Flickable { // Notification actions + id: actionsFlickable + Layout.fillWidth: true + implicitHeight: actionRowLayout.implicitHeight + contentWidth: actionRowLayout.implicitWidth + clip: true + + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on height { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on implicitHeight { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + + RowLayout { + id: actionRowLayout + Layout.alignment: Qt.AlignBottom + + Repeater { + id: actionRepeater + model: notificationObject.actions + NotificationActionButton { + Layout.fillWidth: true + buttonText: modelData.text + urgency: notificationObject.urgency + onClicked: { + Notifications.attemptInvokeAction(notificationObject.id, modelData.identifier); + } + } + } + + NotificationActionButton { + Layout.fillWidth: true + urgency: notificationObject.urgency + implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) : + (contentItem.implicitWidth + leftPadding + rightPadding) + + onClicked: { + Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(notificationObject.body)}'`) + copyIcon.text = "inventory" + copyIconTimer.restart() + } + + Timer { + id: copyIconTimer + interval: 1500 + repeat: false + onTriggered: { + copyIcon.text = "content_copy" + } + } + + contentItem: MaterialSymbol { + id: copyIcon + iconSize: Appearance.font.pixelSize.large + horizontalAlignment: Text.AlignHCenter + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface + text: "content_copy" + } + } + + NotificationActionButton { + Layout.fillWidth: true + buttonText: qsTr("Close") + urgency: notificationObject.urgency + implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) : + (contentItem.implicitWidth + leftPadding + rightPadding) + + onClicked: { + root.destroyWithAnimation() + } + + contentItem: MaterialSymbol { + iconSize: Appearance.font.pixelSize.large + horizontalAlignment: Text.AlignHCenter + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface + text: "close" + } + } + + } + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/common/widgets/NotificationListView.qml b/.config/quickshell/modules/common/widgets/NotificationListView.qml new file mode 100644 index 000000000..087e4a403 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/NotificationListView.qml @@ -0,0 +1,31 @@ +import "root:/" +import "root:/modules/common/" +import "root:/modules/common/widgets" +import "root:/services" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import Quickshell.Hyprland + +StyledListView { // Scrollable window + id: root + property bool popup: false + + spacing: 3 + + model: ScriptModel { + values: root.popup ? Notifications.popupAppNameList : Notifications.appNameList + } + delegate: NotificationGroup { + required property int index + required property var modelData + popup: root.popup + anchors.left: parent?.left + anchors.right: parent?.right + notificationGroup: popup ? + Notifications.popupGroupsByAppName[modelData] : + Notifications.groupsByAppName[modelData] + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml deleted file mode 100644 index c970fd235..000000000 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ /dev/null @@ -1,577 +0,0 @@ -import "root:/modules/common" -import "root:/services" -import "root:/modules/common/functions/string_utils.js" as StringUtils -import "root:/modules/common/functions/color_utils.js" as ColorUtils -import Qt5Compat.GraphicalEffects -import QtQuick -import QtQuick.Controls -import QtQuick.Effects -import QtQuick.Layouts -import Quickshell -import Quickshell.Io -import Quickshell.Widgets -import Quickshell.Hyprland -import Quickshell.Services.Notifications -import "./notification_utils.js" as NotificationUtils - -Item { - id: root - property var notificationObject - property bool popup: false - property bool expanded: false - property bool enableAnimation: true - property int notificationListSpacing: 5 - property int defaultTimeoutValue: 5000 - - property var notificationXAnimation: Appearance.animation.elementMoveEnter - - Layout.fillWidth: true - clip: !popup - - implicitHeight: notificationColumnLayout.implicitHeight + notificationListSpacing - - Component.onCompleted: { - if (popup) timeoutTimer.start() - } - - Timer { - id: timeoutTimer - interval: notificationObject.expireTimeout ?? root.defaultTimeoutValue - repeat: false - onTriggered: { - root.notificationXAnimation = Appearance.animation.elementMoveExit - Notifications.timeoutNotification(notificationObject.id); - } - } - - function destroyWithAnimation(delay = 0) { - destroyTimer0.interval = delay - destroyTimer0.start() - } - - function toggleExpanded() { - root.enableAnimation = true - notificationRowWrapper.anchors.bottom = undefined - root.expanded = !root.expanded - } - - Timer { - id: destroyTimer0 - interval: 0 - repeat: false - onTriggered: { - notificationRowWrapper.anchors.left = undefined - notificationRowWrapper.anchors.right = undefined - notificationRowWrapper.anchors.fill = undefined - notificationBackground.anchors.left = undefined - notificationBackground.anchors.right = undefined - notificationBackground.anchors.fill = undefined - notificationRowWrapper.x = width + (Appearance.sizes.hyprlandGapsOut + Appearance.sizes.elevationMargin) * 2 // Account for shadow - notificationBackground.x = width + (Appearance.sizes.hyprlandGapsOut + Appearance.sizes.elevationMargin) * 2 // Account for shadow - destroyTimer1.start() - } - } - - Timer { - id: destroyTimer1 - interval: notificationXAnimation.duration - repeat: false - onTriggered: { - notificationRowWrapper.anchors.top = undefined - notificationRowWrapper.anchors.bottom = root.bottom - Notifications.discardNotification(notificationObject.id); - } - } - - MouseArea { - // Middle click to close - anchors.fill: parent - acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton - onClicked: (mouse) => { - if (mouse.button == Qt.MiddleButton) - root.destroyWithAnimation() - else if (mouse.button == Qt.RightButton) - root.toggleExpanded() - } - - // Flick right to dismiss/discard - property real startX: 0 - property real dragStartThreshold: 10 - property real dragConfirmThreshold: 70 - property bool dragStarted: false - - onPressed: (mouse) => { - if (mouse.button === Qt.LeftButton) { - startX = mouse.x - } - } - onPressAndHold: (mouse) => { - if (mouse.button === Qt.LeftButton) { - Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(notificationObject.body)}'`) - notificationSummaryText.text = String.format(qsTr("{0} (copied)"), notificationObject.summary) - } - } - onDragStartedChanged: () => { - // Prevent drag focus being shifted to parent flickable - if (root.parent.parent.parent.interactive !== undefined) root.parent.parent.parent.interactive = !dragStarted - root.enableAnimation = !dragStarted - } - onReleased: (mouse) => { - dragStarted = false - if (mouse.button === Qt.LeftButton) { - if (notificationRowWrapper.x > dragConfirmThreshold) { - root.notificationXAnimation = Appearance.animation.elementMoveEnter - root.destroyWithAnimation() - } else { - // Animate back if not far enough - root.notificationXAnimation = Appearance.animation.elementMoveFast - notificationRowWrapper.x = 0 - notificationBackground.x = 0 - } - } - } - onCanceled: (mouse) => { - dragStarted = false - if (notificationRowWrapper.x > dragConfirmThreshold) { - root.notificationXAnimation = Appearance.animation.elementMoveEnter - root.destroyWithAnimation() - } else { - // Animate back if not far enough - root.notificationXAnimation = Appearance.animation.elementMoveFast - notificationRowWrapper.x = 0 - notificationBackground.x = 0 - } - } - - onPositionChanged: (mouse) => { - if (mouse.buttons & Qt.LeftButton) { - let dx = mouse.x - startX - if (dragStarted || dx > dragStartThreshold) { - dragStarted = true - notificationRowWrapper.anchors.left = undefined - notificationRowWrapper.anchors.right = undefined - notificationRowWrapper.anchors.fill = undefined - notificationBackground.anchors.left = undefined - notificationBackground.anchors.right = undefined - notificationBackground.anchors.fill = undefined - notificationRowWrapper.x = Math.max(0, dx) - notificationBackground.x = Math.max(0, dx) - } - } - } - } - - // Background - Item { - id: notificationBackgroundWrapper - - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.topMargin: notificationListSpacing - implicitHeight: notificationColumnLayout.implicitHeight + notificationListSpacing - - Rectangle { - id: notificationBackground - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - // anchors.top: parent.top - implicitHeight: notificationColumnLayout.implicitHeight - - color: (notificationObject.urgency == NotificationUrgency.Critical) ? - ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, Appearance.colors.colLayer2, 0.35) : Appearance.colors.colLayer2 - radius: Appearance.rounding.normal - - layer.enabled: true - layer.effect: MultiEffect { - source: notificationBackground - anchors.fill: notificationBackground - shadowEnabled: popup - shadowColor: Appearance.colors.colShadow - shadowVerticalOffset: 1 - shadowBlur: 0.5 - } - - Behavior on x { - enabled: enableAnimation - NumberAnimation { - duration: root.notificationXAnimation.duration - easing.type: root.notificationXAnimation.type - easing.bezierCurve: root.notificationXAnimation.bezierCurve - } - } - } - } - - - Item { - id: notificationRowWrapper - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - // anchors.top: parent.top - implicitHeight: notificationColumnLayout.implicitHeight + notificationListSpacing - - Behavior on x { - enabled: enableAnimation - NumberAnimation { - duration: root.notificationXAnimation.duration - easing.type: root.notificationXAnimation.type - easing.bezierCurve: root.notificationXAnimation.bezierCurve - } - } - - ColumnLayout { - id: notificationColumnLayout - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - spacing: 0 - Item { - Layout.fillWidth: true - implicitHeight: notificationRowLayout.implicitHeight - Behavior on implicitHeight { - enabled: enableAnimation - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } - } - - RowLayout { - id: notificationRowLayout - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - Rectangle { // App icon - id: iconRectangle - implicitWidth: 47 - implicitHeight: 47 - Layout.leftMargin: 10 - Layout.topMargin: 10 - Layout.bottomMargin: 10 - Layout.alignment: Qt.AlignTop - Layout.fillWidth: false - radius: Appearance.rounding.full - color: Appearance.m3colors.m3secondaryContainer - Loader { - id: materialSymbolLoader - active: notificationObject.appIcon == "" - anchors.fill: parent - sourceComponent: MaterialSymbol { - text: { - const defaultIcon = NotificationUtils.findSuitableMaterialSymbol("") - const guessedIcon = NotificationUtils.findSuitableMaterialSymbol(notificationObject.summary) - return (notificationObject.urgency == NotificationUrgency.Critical && guessedIcon === defaultIcon) ? - "release_alert" : guessedIcon - } - anchors.fill: parent - color: (notificationObject.urgency == NotificationUrgency.Critical) ? - ColorUtils.mix(Appearance.m3colors.m3onSecondary, Appearance.m3colors.m3onSecondaryContainer, 0.1) : - Appearance.m3colors.m3onSecondaryContainer - iconSize: 27 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - } - Loader { - id: appIconLoader - active: notificationObject.image == "" && notificationObject.appIcon != "" - anchors.centerIn: parent - sourceComponent: IconImage { - implicitSize: 33 - asynchronous: true - source: Quickshell.iconPath(notificationObject.appIcon, "image-missing") - } - } - Loader { - id: notifImageLoader - active: notificationObject.image != "" - anchors.fill: parent - sourceComponent: Item { - anchors.fill: parent - Image { - id: notifImage - anchors.fill: parent - readonly property int size: parent.width - - source: notificationObject?.image - fillMode: Image.PreserveAspectCrop - cache: false - antialiasing: true - asynchronous: true - - width: size - height: size - sourceSize.width: size - sourceSize.height: size - - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: notifImage.size - height: notifImage.size - radius: Appearance.rounding.full - } - } - } - Loader { - id: notifImageAppIconLoader - active: notificationObject.appIcon != "" - anchors.bottom: parent.bottom - anchors.right: parent.right - sourceComponent: IconImage { - implicitSize: 23 - asynchronous: true - source: Quickshell.iconPath(notificationObject.appIcon, "image-missing") - } - } - } - } - } - - ColumnLayout { // Notification content - spacing: 0 - Layout.fillWidth: true - - RowLayout { // Row of summary, time and expand button - Layout.topMargin: 10 - Layout.leftMargin: 10 - Layout.rightMargin: 10 - Layout.fillWidth: true - - StyledText { // Summary - id: notificationSummaryText - Layout.fillWidth: true - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignBottom - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.colors.colOnLayer2 - text: notificationObject.summary - wrapMode: expanded ? Text.Wrap : Text.NoWrap - elide: Text.ElideRight - } - - CircularProgress { - id: notificationProgress - visible: popup - Layout.alignment: Qt.AlignVCenter - lineWidth: 2 - value: popup ? 1 : 0 - size: 20 - animationDuration: notificationObject.expireTimeout ?? root.defaultTimeoutValue - easingType: Easing.Linear - - Component.onCompleted: { - value = 0 - } - } - - StyledText { // Time - id: notificationTimeText - Layout.fillWidth: false - Layout.alignment: Qt.AlignTop - Layout.topMargin: 3 - wrapMode: Text.Wrap - horizontalAlignment: Text.AlignLeft - font.pixelSize: Appearance.font.pixelSize.smaller - color: Appearance.m3colors.m3outline - text: NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) - - Connections { - target: DateTime - function onTimeChanged() { - notificationTimeText.text = NotificationUtils.getFriendlyNotifTimeString(notificationObject.time) - } - } - } - - RippleButton { // Expand button - Layout.alignment: Qt.AlignTop - id: expandButton - implicitWidth: 22 - implicitHeight: 22 - - buttonRadius: Appearance.rounding.full - colBackgroundHover: Appearance.colors.colLayer2Hover - colRipple: Appearance.colors.colLayer2Active - - onClicked: { - root.toggleExpanded() - } - - contentItem: MaterialSymbol { - anchors.centerIn: parent - text: "keyboard_arrow_down" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - iconSize: Appearance.font.pixelSize.normal - color: Appearance.colors.colOnLayer2 - rotation: expanded ? 180 : 0 - Behavior on rotation { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } - } - } - - } - } - - StyledText { // Notification body - id: notificationBodyText - Layout.fillWidth: true - Layout.leftMargin: 10 - Layout.rightMargin: 10 - Layout.bottomMargin: 10 - clip: true - - wrapMode: expanded ? Text.Wrap : Text.NoWrap - elide: Text.ElideRight - font.pixelSize: Appearance.font.pixelSize.small - horizontalAlignment: Text.AlignLeft - color: Appearance.m3colors.m3outline - textFormat: expanded ? Text.RichText : Text.StyledText - text: expanded - ? `` + - `${notificationObject.body.replace(/\n/g, "
")}` - : notificationObject.body.replace(/ { - Qt.openUrlExternally(link) - Hyprland.dispatch("global quickshell:sidebarRightClose") - } - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton // Only for hover - hoverEnabled: true - cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor - } - } - } - } - } - - // Actions - Flickable { - id: actionsFlickable - Layout.fillWidth: true - // Layout.topMargin: -5 - Layout.leftMargin: 10 - Layout.rightMargin: 10 - Layout.bottomMargin: expanded ? 10 : 0 - implicitHeight: expanded ? actionRowLayout.implicitHeight : 0 - height: expanded ? actionRowLayout.implicitHeight : 0 - contentWidth: actionRowLayout.implicitWidth - - clip: true - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: actionsFlickable.width - height: actionsFlickable.height - radius: Appearance.rounding.small - } - } - - opacity: expanded ? 1 : 0 - visible: opacity > 0 - Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } - } - Behavior on height { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } - } - Behavior on implicitHeight { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } - } - - RowLayout { - id: actionRowLayout - Layout.alignment: Qt.AlignBottom - - Repeater { - id: actionRepeater - model: notificationObject.actions - NotificationActionButton { - Layout.fillWidth: true - buttonText: modelData.text - urgency: notificationObject.urgency - onClicked: { - Notifications.attemptInvokeAction(notificationObject.id, modelData.identifier); - } - } - } - - NotificationActionButton { - Layout.fillWidth: true - urgency: notificationObject.urgency - implicitWidth: (notificationObject.actions.length == 0) ? (actionsFlickable.width / 2) : - (contentItem.implicitWidth + leftPadding + rightPadding) - - onClicked: { - Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(notificationObject.body)}'`) - copyIcon.text = "inventory" - copyIconTimer.restart() - } - - Timer { - id: copyIconTimer - interval: 1500 - repeat: false - onTriggered: { - copyIcon.text = "content_copy" - } - } - - contentItem: MaterialSymbol { - id: copyIcon - iconSize: Appearance.font.pixelSize.large - horizontalAlignment: Text.AlignHCenter - color: (notificationObject.urgency == NotificationUrgency.Critical) ? - Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface - text: "content_copy" - } - } - - NotificationActionButton { - Layout.fillWidth: true - buttonText: qsTr("Close") - urgency: notificationObject.urgency - implicitWidth: (notificationObject.actions.length == 0) ? (actionsFlickable.width / 2) : - (contentItem.implicitWidth + leftPadding + rightPadding) - - onClicked: { - root.destroyWithAnimation() - } - - contentItem: MaterialSymbol { - iconSize: Appearance.font.pixelSize.large - horizontalAlignment: Text.AlignHCenter - color: (notificationObject.urgency == NotificationUrgency.Critical) ? - Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface - text: "close" - } - } - - } - } - } - } -} diff --git a/.config/quickshell/modules/common/widgets/StyledListView.qml b/.config/quickshell/modules/common/widgets/StyledListView.qml new file mode 100644 index 000000000..ed6aa9162 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/StyledListView.qml @@ -0,0 +1,106 @@ +import "root:/" +import "root:/modules/common/" +import "root:/modules/common/widgets" +import "root:/services" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import Quickshell.Hyprland + +ListView { // Scrollable window + id: root + spacing: 5 + property real removeOvershoot: 20 // Account for gaps and bouncy animations + property int dragIndex: -1 + property real dragDistance: 0 + + function resetDrag() { + root.dragIndex = -1 + root.dragDistance = 0 + } + + add: Transition { + animations: [ + Appearance.animation.elementMove.numberAnimation.createObject(this, { + properties: "opacity,scale", + from: 0, + to: 1, + }), + ] + } + + addDisplaced: Transition { + animations: [ + Appearance.animation.elementMove.numberAnimation.createObject(this, { + property: "y", + }), + Appearance.animation.elementMove.numberAnimation.createObject(this, { + properties: "opacity,scale", + to: 1, + }), + ] + } + + displaced: Transition { + animations: [ + Appearance.animation.elementMove.numberAnimation.createObject(this, { + property: "y", + }), + Appearance.animation.elementMove.numberAnimation.createObject(this, { + properties: "opacity,scale", + to: 1, + }), + ] + } + + move: Transition { + animations: [ + Appearance.animation.elementMove.numberAnimation.createObject(this, { + property: "y", + }), + Appearance.animation.elementMove.numberAnimation.createObject(this, { + properties: "opacity,scale", + to: 1, + }), + ] + } + moveDisplaced: Transition { + animations: [ + Appearance.animation.elementMove.numberAnimation.createObject(this, { + property: "y", + }), + Appearance.animation.elementMove.numberAnimation.createObject(this, { + properties: "opacity,scale", + to: 1, + }), + ] + } + + remove: Transition { + animations: [ + Appearance.animation.elementMove.numberAnimation.createObject(this, { + property: "x", + to: root.width + root.removeOvershoot, + }), + Appearance.animation.elementMove.numberAnimation.createObject(this, { + property: "opacity", + to: 0, + }) + ] + } + + // This is movement when something is removed, not removing animation! + removeDisplaced: Transition { + animations: [ + Appearance.animation.elementMove.numberAnimation.createObject(this, { + property: "y", + }), + Appearance.animation.elementMove.numberAnimation.createObject(this, { + properties: "opacity,scale", + to: 1, + }), + ] + } +} diff --git a/.config/quickshell/modules/notificationPopup/NotificationPopup.qml b/.config/quickshell/modules/notificationPopup/NotificationPopup.qml index e526a2769..122489d88 100644 --- a/.config/quickshell/modules/notificationPopup/NotificationPopup.qml +++ b/.config/quickshell/modules/notificationPopup/NotificationPopup.qml @@ -34,74 +34,14 @@ Scope { color: "transparent" implicitWidth: Appearance.sizes.notificationPopupWidth - ListView { // Scrollable window + NotificationListView { id: listview anchors.top: parent.top anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 5 implicitWidth: parent.width - Appearance.sizes.elevationMargin * 2 - - add: Transition { - animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { - properties: "opacity,scale", - from: 0, - to: 1, - }), - ] - } - - addDisplaced: Transition { - animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { - property: "y", - }), - Appearance.animation.elementMove.numberAnimation.createObject(this, { - properties: "opacity,scale", - to: 1, - }), - ] - } - - displaced: Transition { - animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { - property: "y", - }), - ] - } - move: Transition { - animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { - property: "y", - }), - ] - } - - remove: Transition { - animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { - property: "x", - to: listview.width, - }), - Appearance.animation.elementMove.numberAnimation.createObject(this, { - property: "opacity", - to: 0, - }) - ] - } - - model: ScriptModel { - values: Notifications.popupList.slice().reverse() - } - delegate: NotificationWidget { - required property var modelData - id: notificationWidget - popup: true - anchors.left: parent?.left - anchors.right: parent?.right - notificationObject: modelData - } + popup: true } } } diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml index 0c6e7314b..7dd4e28c6 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -11,7 +11,7 @@ import Quickshell.Widgets Item { id: root - ListView { // Scrollable window + NotificationListView { // Scrollable window id: listview anchors.left: parent.left anchors.right: parent.right @@ -28,68 +28,7 @@ Item { } } - add: Transition { - animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { - properties: "opacity,scale", - from: 0, - to: 1, - }), - ] - } - - addDisplaced: Transition { - animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { - property: "y", - }), - Appearance.animation.elementMove.numberAnimation.createObject(this, { - properties: "opacity,scale", - to: 1, - }), - ] - } - - displaced: Transition { - animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { - property: "y", - }), - ] - } - move: Transition { - animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { - property: "y", - }), - ] - } - - remove: Transition { - animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { - property: "x", - to: listview.width, - }), - Appearance.animation.elementMove.numberAnimation.createObject(this, { - property: "opacity", - to: 0, - }) - ] - } - - model: ScriptModel { - values: Notifications.list.slice().reverse() - } - delegate: NotificationWidget { - required property var modelData - id: notificationWidget - // anchors.horizontalCenter: parent.horizontalCenter - anchors.left: parent?.left - anchors.right: parent?.right - Layout.fillWidth: true - notificationObject: modelData - } + popup: false } // Placeholder when list is empty From a24825f676b6c420062d14d93eff041d0fdb0e66 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 25 May 2025 22:48:08 +0200 Subject: [PATCH 488/824] latex renderer: ensure readability after changing light/dark --- .config/quickshell/services/LatexRenderer.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/services/LatexRenderer.qml b/.config/quickshell/services/LatexRenderer.qml index 354684be6..9fb5fb0ab 100644 --- a/.config/quickshell/services/LatexRenderer.qml +++ b/.config/quickshell/services/LatexRenderer.qml @@ -65,7 +65,8 @@ Singleton { "-output=${imagePath}", "-textsize=${Appearance.font.pixelSize.normal}", "-padding=${renderPadding}", - "-foreground=${Appearance.colors.colOnLayer1}", + "-background=${Appearance.m3colors.m3tertiary}", + "-foreground=${Appearance.m3colors.m3onTertiary}", "-maxwidth=0.85" ] // stdout: SplitParser { // onRead: data => { console.log("MicroTeX: " + data) } From dbe2a922e36e401ed40a38ef465176dd3ca97be4 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 25 May 2025 23:36:18 +0200 Subject: [PATCH 489/824] notif: prevent unexpected shuffle on dismiss --- .../common/widgets/NotificationAppIcon.qml | 3 +- .../common/widgets/NotificationGroup.qml | 7 +++-- .../common/widgets/NotificationItem.qml | 17 ++++++++++- .config/quickshell/services/Notifications.qml | 28 +++++++++++++++---- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationAppIcon.qml b/.config/quickshell/modules/common/widgets/NotificationAppIcon.qml index 5a218d8f9..371093b25 100644 --- a/.config/quickshell/modules/common/widgets/NotificationAppIcon.qml +++ b/.config/quickshell/modules/common/widgets/NotificationAppIcon.qml @@ -14,7 +14,8 @@ Rectangle { // App icon property var summary: "" property var urgency: NotificationUrgency.Normal property var image: "" - property real size: 45 + property real scale: 1 + property real size: 45 * scale property real materialIconScale: 0.57 property real appIconScale: 0.7 property real smallAppIconScale: 0.49 diff --git a/.config/quickshell/modules/common/widgets/NotificationGroup.qml b/.config/quickshell/modules/common/widgets/NotificationGroup.qml index 7b1381dd3..14f29920e 100644 --- a/.config/quickshell/modules/common/widgets/NotificationGroup.qml +++ b/.config/quickshell/modules/common/widgets/NotificationGroup.qml @@ -152,14 +152,17 @@ Item { // Notification group area NotificationAppIcon { // Icons Layout.alignment: Qt.AlignTop Layout.fillWidth: false - + image: root.multipleNotifications ? "" : notificationGroup.notifications[0].image appIcon: notificationGroup.appIcon summary: notificationGroup.notifications[root.notificationCount - 1].summary } ColumnLayout { // Content Layout.fillWidth: true - spacing: expanded ? 5 : 0 + spacing: expanded ? + ((root.multipleNotifications && + notificationGroup.notifications[root.notificationCount - 1].image != "") ? 35 : + 5) : 0 Behavior on spacing { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } diff --git a/.config/quickshell/modules/common/widgets/NotificationItem.qml b/.config/quickshell/modules/common/widgets/NotificationItem.qml index ada5a95ff..fca48a542 100644 --- a/.config/quickshell/modules/common/widgets/NotificationItem.qml +++ b/.config/quickshell/modules/common/widgets/NotificationItem.qml @@ -23,7 +23,7 @@ Item { // Notification item area property real padding: 8 property real dragConfirmThreshold: 70 // Drag further to discard notification - property real dismissOvershoot: 20 // Account for gaps and bouncy animations + property real dismissOvershoot: notificationIcon.implicitWidth + 20 // Account for gaps and bouncy animations property var qmlParent: root?.parent?.parent // There's something between this and the parent ListView property var parentDragIndex: qmlParent?.dragIndex ?? -1 property var parentDragDistance: qmlParent?.dragDistance ?? 0 @@ -89,6 +89,21 @@ Item { // Notification item area } } + NotificationAppIcon { // App icon + id: notificationIcon + opacity: (!onlyNotification && notificationObject.image != "" && expanded) ? 1 : 0 + visible: opacity > 0 + + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + + image: notificationObject.image + anchors.right: background.left + anchors.top: background.top + anchors.rightMargin: 10 + } + Rectangle { // Background of notification item id: background width: parent.width diff --git a/.config/quickshell/services/Notifications.qml b/.config/quickshell/services/Notifications.qml index 81161dab5..67b2351e5 100644 --- a/.config/quickshell/services/Notifications.qml +++ b/.config/quickshell/services/Notifications.qml @@ -15,9 +15,22 @@ Singleton { property var list: [] property var popupList: [] property bool popupInhibited: GlobalStates?.sidebarRightOpen ?? false - // Quickshell's notification IDs starts at 1 on each run, while saved notifications - // can already contain higher IDs. This is for avoiding id collisions - property int idOffset + property var latestTimeForApp: ({}) + + onListChanged: { + // Update latest time for each app + root.list.forEach((notif) => { + if (!root.latestTimeForApp[notif.appName] || notif.time > root.latestTimeForApp[notif.appName]) { + root.latestTimeForApp[notif.appName] = Math.max(root.latestTimeForApp[notif.appName] || 0, notif.time); + } + }); + // Remove apps that no longer have notifications + Object.keys(root.latestTimeForApp).forEach((appName) => { + if (!root.list.some((notif) => notif.appName === appName)) { + delete root.latestTimeForApp[appName]; + } + }); + } function appNameListForGroups(groups) { return Object.keys(groups).sort((a, b) => { @@ -39,7 +52,7 @@ Singleton { } groups[notif.appName].notifications.push(notif); // Always set to the latest time in the group - groups[notif.appName].time = notif.time; + groups[notif.appName].time = latestTimeForApp[notif.appName] || notif.time; }); return groups; } @@ -49,6 +62,9 @@ Singleton { property var appNameList: appNameListForGroups(root.groupsByAppName) property var popupAppNameList: appNameListForGroups(root.popupGroupsByAppName) + // Quickshell's notification IDs starts at 1 on each run, while saved notifications + // can already contain higher IDs. This is for avoiding id collisions + property int idOffset signal initDone(); signal notify(notification: var); signal discard(id: var); @@ -87,7 +103,9 @@ Singleton { } root.list = [...root.list, newNotifObject]; // console.log(root.popupInhibited) - if (!root.popupInhibited) root.popupList = [...root.popupList, newNotifObject]; + if (!root.popupInhibited) { + root.popupList = [...root.popupList, newNotifObject]; + } root.notify(newNotifObject); notifFileView.setText(JSON.stringify(root.list, null, 2)) } From 20e517a7ca904121a008d4e4b340f856e316cbcc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 25 May 2025 23:41:51 +0200 Subject: [PATCH 490/824] notif groups: dont show invisible items --- .../quickshell/modules/common/widgets/NotificationGroup.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationGroup.qml b/.config/quickshell/modules/common/widgets/NotificationGroup.qml index 14f29920e..fa425ea9f 100644 --- a/.config/quickshell/modules/common/widgets/NotificationGroup.qml +++ b/.config/quickshell/modules/common/widgets/NotificationGroup.qml @@ -213,8 +213,8 @@ Item { // Notification group area animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } model: ScriptModel { - // values: root.expanded ? root.notifications : root.notifications.slice(0, 2) - values: root.notifications.slice().reverse() + values: root.expanded ? root.notifications.slice().reverse() : + root.notifications.slice().reverse().slice(0, 2) } delegate: NotificationItem { required property int index From 20a62d1fdca9d8ffdbfda2e956b3b6964b7cbf3b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 25 May 2025 23:55:16 +0200 Subject: [PATCH 491/824] notif items: make dragging area cover also the icon --- .config/quickshell/modules/common/widgets/NotificationItem.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationItem.qml b/.config/quickshell/modules/common/widgets/NotificationItem.qml index fca48a542..a5ace45d0 100644 --- a/.config/quickshell/modules/common/widgets/NotificationItem.qml +++ b/.config/quickshell/modules/common/widgets/NotificationItem.qml @@ -60,7 +60,8 @@ Item { // Notification item area DragManager { // Drag manager id: dragManager - anchors.fill: parent + anchors.fill: root + anchors.leftMargin: -notificationIcon.implicitWidth interactive: expanded automaticallyReset: false acceptedButtons: Qt.LeftButton From a6e3f20dc8d530442c2799106d585663fc5755b2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 25 May 2025 23:58:36 +0200 Subject: [PATCH 492/824] make layer1 color blend more with bg than layer2 --- .config/quickshell/modules/common/Appearance.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index a1889827b..940dd26ea 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -100,7 +100,7 @@ Singleton { property color colOnLayer0: m3colors.m3onBackground property color colLayer0Hover: ColorUtils.mix(colLayer0, colOnLayer0, 0.9) property color colLayer0Active: ColorUtils.mix(colLayer0, colOnLayer0, 0.8) - property color colLayer1: m3colors.m3surfaceContainerLow; + property color colLayer1: ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.7); property color colOnLayer1: m3colors.m3onSurfaceVariant; property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45); property color colLayer2: ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.55); From ce9993071c656903649355f13c5ba5bb0a38b531 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 00:09:09 +0200 Subject: [PATCH 493/824] notifications: middle click to dismiss --- .../modules/common/widgets/NotificationGroup.qml | 7 ++++--- .../modules/common/widgets/NotificationItem.qml | 10 ++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationGroup.qml b/.config/quickshell/modules/common/widgets/NotificationGroup.qml index fa425ea9f..d96f2d8a8 100644 --- a/.config/quickshell/modules/common/widgets/NotificationGroup.qml +++ b/.config/quickshell/modules/common/widgets/NotificationGroup.qml @@ -76,12 +76,13 @@ Item { // Notification group area anchors.fill: parent interactive: !expanded automaticallyReset: false - acceptedButtons: Qt.LeftButton | Qt.RightButton + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton onClicked: (mouse) => { - if (mouse.button === Qt.RightButton) { + if (mouse.button === Qt.RightButton) root.toggleExpanded(); - } + else if (mouse.button === Qt.MiddleButton) + root.destroyWithAnimation(); } onDraggingChanged: () => { diff --git a/.config/quickshell/modules/common/widgets/NotificationItem.qml b/.config/quickshell/modules/common/widgets/NotificationItem.qml index a5ace45d0..351f6bc2a 100644 --- a/.config/quickshell/modules/common/widgets/NotificationItem.qml +++ b/.config/quickshell/modules/common/widgets/NotificationItem.qml @@ -61,10 +61,16 @@ Item { // Notification item area DragManager { // Drag manager id: dragManager anchors.fill: root - anchors.leftMargin: -notificationIcon.implicitWidth + anchors.leftMargin: root.expanded ? -notificationIcon.implicitWidth : 0 interactive: expanded automaticallyReset: false - acceptedButtons: Qt.LeftButton + acceptedButtons: Qt.LeftButton | Qt.MiddleButton + + onClicked: (mouse) => { + if (mouse.button === Qt.MiddleButton) { + root.destroyWithAnimation(); + } + } onPressAndHold: (mouse) => { if (mouse.button === Qt.LeftButton) { From ea41ee4241353672d8f82267130263c557bf4d6b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 09:37:57 +0200 Subject: [PATCH 494/824] notifications: timeout, prevent some warnings when dismissing notif --- .../common/widgets/NotificationGroup.qml | 22 ++-- .../common/widgets/notification_utils.js | 1 + .config/quickshell/services/Notifications.qml | 120 ++++++++++++++---- 3 files changed, 106 insertions(+), 37 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationGroup.qml b/.config/quickshell/modules/common/widgets/NotificationGroup.qml index d96f2d8a8..62677ae10 100644 --- a/.config/quickshell/modules/common/widgets/NotificationGroup.qml +++ b/.config/quickshell/modules/common/widgets/NotificationGroup.qml @@ -21,7 +21,7 @@ import Quickshell.Services.Notifications Item { // Notification group area id: root property var notificationGroup - property var notifications: notificationGroup.notifications + property var notifications: notificationGroup?.notifications ?? [] property int notificationCount: notifications.length property bool multipleNotifications: notificationCount > 1 property bool expanded: false @@ -60,7 +60,9 @@ Item { // Notification group area } onFinished: () => { root.notifications.forEach((notif) => { - Notifications.discardNotification(notif.id); + Qt.callLater(() => { + Notifications.discardNotification(notif.id); + }); }); } } @@ -153,16 +155,16 @@ Item { // Notification group area NotificationAppIcon { // Icons Layout.alignment: Qt.AlignTop Layout.fillWidth: false - image: root.multipleNotifications ? "" : notificationGroup.notifications[0].image - appIcon: notificationGroup.appIcon - summary: notificationGroup.notifications[root.notificationCount - 1].summary + image: root?.multipleNotifications ? "" : notificationGroup?.notifications[0]?.image ?? "" + appIcon: notificationGroup?.appIcon + summary: notificationGroup?.notifications[root.notificationCount - 1]?.summary } ColumnLayout { // Content Layout.fillWidth: true spacing: expanded ? ((root.multipleNotifications && - notificationGroup.notifications[root.notificationCount - 1].image != "") ? 35 : + notificationGroup?.notifications[root.notificationCount - 1].image != "") ? 35 : 5) : 0 Behavior on spacing { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) @@ -176,9 +178,9 @@ Item { // Notification group area StyledText { id: appName - text: topRow.showAppName ? - notificationGroup.appName : - notificationGroup.notifications[0].summary + text: (topRow.showAppName ? + notificationGroup?.appName : + notificationGroup?.notifications[0]?.summary) || "" font.pixelSize: topRow.showAppName ? topRow.fontSize : Appearance.font.pixelSize.small @@ -188,7 +190,7 @@ Item { // Notification group area } StyledText { id: timeText - text: " • " + NotificationUtils.getFriendlyNotifTimeString(notificationGroup.time) + text: " • " + NotificationUtils.getFriendlyNotifTimeString(notificationGroup?.time) font.pixelSize: topRow.fontSize color: Appearance.colors.colSubtext Layout.alignment: Qt.AlignRight diff --git a/.config/quickshell/modules/common/widgets/notification_utils.js b/.config/quickshell/modules/common/widgets/notification_utils.js index ad22a0fbb..01bf3d87f 100644 --- a/.config/quickshell/modules/common/widgets/notification_utils.js +++ b/.config/quickshell/modules/common/widgets/notification_utils.js @@ -60,6 +60,7 @@ function findSuitableMaterialSymbol(summary = "") { * @returns { string } */ const getFriendlyNotifTimeString = (timestamp) => { + if (!timestamp) return ''; const messageTime = new Date(timestamp); const now = new Date(); const oneMinuteAgo = new Date(now.getTime() - 60000); diff --git a/.config/quickshell/services/Notifications.qml b/.config/quickshell/services/Notifications.qml index 67b2351e5..1b20a0fda 100644 --- a/.config/quickshell/services/Notifications.qml +++ b/.config/quickshell/services/Notifications.qml @@ -11,11 +11,67 @@ import Qt.labs.platform Singleton { id: root + component Notif: QtObject { + required property int id + property Notification notification + property list actions: notification?.actions.map((action) => ({ + "identifier": action.identifier, + "text": action.text, + })) ?? [] + property bool popup: false + property string appIcon: notification?.appIcon ?? "" + property string appName: notification?.appName ?? "" + property string body: notification?.body ?? "" + property string image: notification?.image ?? "" + property string summary: notification?.summary ?? "" + property double time + property string urgency: notification?.urgency.toString() ?? "normal" + property Timer timer + } + + function notifToJSON(notif) { + return { + "id": notif.id, + "actions": notif.actions, + "appIcon": notif.appIcon, + "appName": notif.appName, + "body": notif.body, + "image": notif.image, + "summary": notif.summary, + "time": notif.time, + "urgency": notif.urgency, + } + } + function notifToString(notif) { + return JSON.stringify(notifToJSON(notif), null, 2); + } + + component NotifTimer: Timer { + required property int id + interval: 5000 + running: true + onTriggered: () => { + root.timeoutNotification(id); + destroy() + } + } property var filePath: `${XdgDirectories.cache}/notifications/notifications.json` - property var list: [] - property var popupList: [] + property list list: [] + property var popupList: list.filter((notif) => notif.popup); property bool popupInhibited: GlobalStates?.sidebarRightOpen ?? false property var latestTimeForApp: ({}) + Component { + id: notifComponent + Notif {} + } + Component { + id: notifTimerComponent + NotifTimer {} + } + + function stringifyList(list) { + return JSON.stringify(list.map((notif) => notifToJSON(notif)), null, 2); + } onListChanged: { // Update latest time for each app @@ -85,29 +141,25 @@ Singleton { onNotification: (notification) => { notification.tracked = true - const newNotifObject = { + const newNotifObject = notifComponent.createObject(root, { "id": notification.id + root.idOffset, - "actions": notification.actions.map((action) => { - return { - "identifier": action.identifier, - "text": action.text, - } - }), - "appIcon": notification.appIcon, - "appName": notification.appName, - "body": notification.body, - "image": notification.image, - "summary": notification.summary, + "notification": notification, "time": Date.now(), - "urgency": notification.urgency.toString(), - } + }); root.list = [...root.list, newNotifObject]; - // console.log(root.popupInhibited) + + // Popup if (!root.popupInhibited) { - root.popupList = [...root.popupList, newNotifObject]; + newNotifObject.popup = true; + newNotifObject.timer = notifTimerComponent.createObject(root, { + "id": newNotifObject.id, + "interval": notification.expireTimeout < 0 ? 5000 : notification.expireTimeout, + }); } + root.notify(newNotifObject); - notifFileView.setText(JSON.stringify(root.list, null, 2)) + // console.log(notifToString(newNotifObject)); + notifFileView.setText(stringifyList(root.list)); } } @@ -116,20 +168,19 @@ Singleton { const notifServerIndex = notifServer.trackedNotifications.values.findIndex((notif) => notif.id + root.idOffset === id); if (index !== -1) { root.list.splice(index, 1); - notifFileView.setText(JSON.stringify(root.list, null, 2)) + notifFileView.setText(stringifyList(root.list)); triggerListChange() } if (notifServerIndex !== -1) { notifServer.trackedNotifications.values[notifServerIndex].dismiss() } - root.popupList = root.popupList.filter((notif) => notif.id !== id); root.discard(id); } function discardAllNotifications() { root.list = [] triggerListChange() - notifFileView.setText(JSON.stringify(root.list, null, 2)) + notifFileView.setText(stringifyList(root.list)); notifServer.trackedNotifications.values.forEach((notif) => { notif.dismiss() }) @@ -138,15 +189,18 @@ Singleton { function timeoutNotification(id) { const index = root.list.findIndex((notif) => notif.id === id); - root.popupList = root.popupList.filter((notif) => notif.id !== id); + if (root.list[index] != null) + root.list[index].popup = false; root.timeout(id); } function timeoutAll() { - root.list.forEach((notif) => { + root.popupList.forEach((notif) => { root.timeout(notif.id); }) - root.popupList = [] + root.popupList.forEach((notif) => { + notif.popup = false; + }); } function attemptInvokeAction(id, notifIdentifier) { @@ -177,7 +231,19 @@ Singleton { path: filePath onLoaded: { const fileContents = notifFileView.text() - root.list = JSON.parse(fileContents) + root.list = JSON.parse(fileContents).map((notif) => { + return notifComponent.createObject(root, { + "id": notif.id, + "actions": notif.actions, + "appIcon": notif.appIcon, + "appName": notif.appName, + "body": notif.body, + "image": notif.image, + "summary": notif.summary, + "time": notif.time, + "urgency": notif.urgency, + }); + }); // Find largest id let maxId = 0 root.list.forEach((notif) => { @@ -192,7 +258,7 @@ Singleton { if(error == FileViewError.FileNotFound) { console.log("[Notifications] File not found, creating new file.") root.list = [] - notifFileView.setText(JSON.stringify(root.list)) + notifFileView.setText(stringifyList(root.list)); } else { console.log("[Notifications] Error loading file: " + error) } From ce94bd1b6add09fe1e5aee336da1b7108b5298ff Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 10:17:16 +0200 Subject: [PATCH 495/824] media controls: filter redundant players --- .../modules/mediaControls/MediaControls.qml | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/mediaControls/MediaControls.qml b/.config/quickshell/modules/mediaControls/MediaControls.qml index 0983562b8..def25936b 100644 --- a/.config/quickshell/modules/mediaControls/MediaControls.qml +++ b/.config/quickshell/modules/mediaControls/MediaControls.qml @@ -20,6 +20,7 @@ Scope { property bool visible: false readonly property MprisPlayer activePlayer: MprisController.activePlayer readonly property var realPlayers: Mpris.players.values.filter(player => isRealPlayer(player)) + readonly property var meaningfulPlayers: filterDuplicatePlayers(realPlayers) readonly property real osdWidth: Appearance.sizes.osdWidth readonly property real widgetWidth: Appearance.sizes.mediaControlsWidth readonly property real widgetHeight: Appearance.sizes.mediaControlsHeight @@ -41,6 +42,33 @@ Scope { !(player.dbusName?.endsWith('.mpd') && !player.dbusName.endsWith('MediaPlayer2.mpd')) ); } + function filterDuplicatePlayers(players) { + let filtered = []; + let used = new Set(); + + for (let i = 0; i < players.length; ++i) { + if (used.has(i)) continue; + let p1 = players[i]; + let group = [i]; + + // Find duplicates by trackTitle prefix + for (let j = i + 1; j < players.length; ++j) { + let p2 = players[j]; + if (p1.trackTitle && p2.trackTitle && + (p1.trackTitle.startsWith(p2.trackTitle) || p2.trackTitle.startsWith(p1.trackTitle))) { + group.push(j); + } + } + + // Pick the one with non-empty trackArtUrl, or fallback to the first + let chosenIdx = group.find(idx => players[idx].trackArtUrl && players[idx].trackArtUrl.length > 0); + if (chosenIdx === undefined) chosenIdx = group[0]; + + filtered.push(players[chosenIdx]); + group.forEach(idx => used.add(idx)); + } + return filtered; + } Component.onCompleted: { Hyprland.dispatch(`exec rm -rf ${baseCoverArtDir} && mkdir -p ${baseCoverArtDir}`) @@ -84,7 +112,7 @@ Scope { Repeater { model: ScriptModel { - values: root.realPlayers + values: root.meaningfulPlayers } delegate: PlayerControl { required property MprisPlayer modelData From f765fdf531805a9890450c77b0efc48c95b01fdf Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 10:45:59 +0200 Subject: [PATCH 496/824] ai: fix invalid message when switching model --- .config/quickshell/services/Ai.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index 7c33cd249..757a2a1e3 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -8,6 +8,9 @@ import Quickshell.Io; import Qt.labs.platform import QtQuick; +/** + * Basic service to handle LLM chats. Supports Google's and OpenAI's API formats. + */ Singleton { id: root @@ -184,7 +187,7 @@ Singleton { modelId = modelId.toLowerCase() if (modelList.indexOf(modelId) !== -1) { PersistentStateManager.setState("ai.model", modelId); - if (feedback) root.addMessage(StringUtils.format(StringUtils.format("Model set to {0}"), models[modelId].name, Ai.interfaceRole)) + if (feedback) root.addMessage(StringUtils.format(StringUtils.format("Model set to {0}"), models[modelId].name), Ai.interfaceRole) if (models[modelId].requires_key) { // If key not there show advice if (root.apiKeysLoaded && (!root.apiKeys[models[modelId].key_id] || root.apiKeys[models[modelId].key_id].length === 0)) { From ede03f61bf68c2b6c1f795e20c0ddcec785bf83a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 10:46:07 +0200 Subject: [PATCH 497/824] provide basic descriptions for services --- .config/quickshell/services/AiMessageData.qml | 3 +++ .config/quickshell/services/AppSearch.qml | 3 +++ .config/quickshell/services/Audio.qml | 3 +++ .config/quickshell/services/Bluetooth.qml | 3 +++ .config/quickshell/services/Booru.qml | 3 +++ .config/quickshell/services/BooruResponseData.qml | 3 +++ .config/quickshell/services/Brightness.qml | 5 ++++- .config/quickshell/services/ConfigLoader.qml | 5 +++++ .config/quickshell/services/DateTime.qml | 3 +++ .config/quickshell/services/HyprlandData.qml | 3 +++ .config/quickshell/services/HyprlandKeybinds.qml | 13 +++++++++---- .config/quickshell/services/KeyringStorage.qml | 7 ++++--- .config/quickshell/services/LatexRenderer.qml | 12 ++++++------ .config/quickshell/services/MaterialThemeLoader.qml | 4 ++++ .config/quickshell/services/MprisController.qml | 6 ++++++ .config/quickshell/services/Network.qml | 3 +++ .config/quickshell/services/Notifications.qml | 6 ++++++ .../quickshell/services/PersistentStateManager.qml | 4 ++++ .config/quickshell/services/ResourceUsage.qml | 3 +++ .config/quickshell/services/SystemInfo.qml | 3 +++ .config/quickshell/services/Todo.qml | 4 ++++ 21 files changed, 85 insertions(+), 14 deletions(-) diff --git a/.config/quickshell/services/AiMessageData.qml b/.config/quickshell/services/AiMessageData.qml index 625818a05..daac9f3ae 100644 --- a/.config/quickshell/services/AiMessageData.qml +++ b/.config/quickshell/services/AiMessageData.qml @@ -1,6 +1,9 @@ import "root:/modules/common" import QtQuick; +/** + * Represents a message in an AI conversation. (Kind of) follows the OpenAI API message structure. + */ QtObject { property string role property string content diff --git a/.config/quickshell/services/AppSearch.qml b/.config/quickshell/services/AppSearch.qml index 1ecaa886c..10a54f835 100644 --- a/.config/quickshell/services/AppSearch.qml +++ b/.config/quickshell/services/AppSearch.qml @@ -4,6 +4,9 @@ import "root:/modules/common/functions/fuzzysort.js" as Fuzzy import Quickshell import Quickshell.Io +/** + * Eases searching for applications by name. + */ Singleton { id: root diff --git a/.config/quickshell/services/Audio.qml b/.config/quickshell/services/Audio.qml index 564c8afc2..8b5f9b760 100644 --- a/.config/quickshell/services/Audio.qml +++ b/.config/quickshell/services/Audio.qml @@ -4,6 +4,9 @@ import Quickshell.Services.Pipewire pragma Singleton pragma ComponentBehavior: Bound +/** + * A nice wrapper for default Pipewire audio sink and source. + */ Singleton { id: root diff --git a/.config/quickshell/services/Bluetooth.qml b/.config/quickshell/services/Bluetooth.qml index fed333bb5..817bbc921 100644 --- a/.config/quickshell/services/Bluetooth.qml +++ b/.config/quickshell/services/Bluetooth.qml @@ -5,6 +5,9 @@ import Quickshell; import Quickshell.Io; import QtQuick; +/** + * Basic polled Bluetooth state. + */ Singleton { id: root diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index 216fc1f5e..697e92069 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -7,6 +7,9 @@ import Quickshell.Io; import Qt.labs.platform import QtQuick; +/** + * A service for interacting with various booru APIs. + */ Singleton { id: root property Component booruResponseDataComponent: BooruResponseData {} diff --git a/.config/quickshell/services/BooruResponseData.qml b/.config/quickshell/services/BooruResponseData.qml index f0409a1ba..38e1b8c76 100644 --- a/.config/quickshell/services/BooruResponseData.qml +++ b/.config/quickshell/services/BooruResponseData.qml @@ -1,6 +1,9 @@ import "root:/modules/common" import QtQuick; +/** + * A booru response. + */ QtObject { property string provider property var tags diff --git a/.config/quickshell/services/Brightness.qml b/.config/quickshell/services/Brightness.qml index 301584d3a..132f0eda9 100644 --- a/.config/quickshell/services/Brightness.qml +++ b/.config/quickshell/services/Brightness.qml @@ -1,7 +1,7 @@ pragma Singleton pragma ComponentBehavior: Bound -// From https://github.com/caelestia-dots/shell/ (`quickshell` branch) +// From https://github.com/caelestia-dots/shell/ (`quickshell` branch) with modifications. // It does not have a license, but the author has given permission. import Quickshell @@ -9,6 +9,9 @@ import Quickshell.Io import Quickshell.Hyprland import QtQuick +/** + * For managing brightness of monitors. Supports both brightnessctl and ddcutil. + */ Singleton { id: root diff --git a/.config/quickshell/services/ConfigLoader.qml b/.config/quickshell/services/ConfigLoader.qml index 6c330c68e..658019f19 100644 --- a/.config/quickshell/services/ConfigLoader.qml +++ b/.config/quickshell/services/ConfigLoader.qml @@ -10,6 +10,11 @@ import Quickshell.Io import Quickshell.Hyprland import Qt.labs.platform +/** + * Loads and manages the shell configuration file. + * The config file is by default at XDG_CONFIG_HOME/illogical-impulse/config.json. + * Automatically reloaded when the file changes, but does not provide a way to save changes. + */ Singleton { id: root property string fileDir: `${XdgDirectories.config}/illogical-impulse` diff --git a/.config/quickshell/services/DateTime.qml b/.config/quickshell/services/DateTime.qml index ee5cf4b41..36eb7a4a1 100644 --- a/.config/quickshell/services/DateTime.qml +++ b/.config/quickshell/services/DateTime.qml @@ -5,6 +5,9 @@ import Quickshell.Io pragma Singleton pragma ComponentBehavior: Bound +/** + * A nice wrapper for date and time strings. + */ Singleton { property string time: Qt.formatDateTime(clock.date, "hh:mm") property string date: Qt.formatDateTime(clock.date, "dddd, dd/MM") diff --git a/.config/quickshell/services/HyprlandData.qml b/.config/quickshell/services/HyprlandData.qml index d72c1a041..2b88ad9cc 100644 --- a/.config/quickshell/services/HyprlandData.qml +++ b/.config/quickshell/services/HyprlandData.qml @@ -7,6 +7,9 @@ import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland +/** + * Provides access to some Hyprland data not available in Quickshell.Hyprland. + */ Singleton { id: root property var windowList: [] diff --git a/.config/quickshell/services/HyprlandKeybinds.qml b/.config/quickshell/services/HyprlandKeybinds.qml index 1b2972fe9..f0001fe20 100644 --- a/.config/quickshell/services/HyprlandKeybinds.qml +++ b/.config/quickshell/services/HyprlandKeybinds.qml @@ -9,8 +9,15 @@ import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland +/** + * A service that provides access to Hyprland keybinds. + * Uses the `get_keybinds.py` script to parse comments in config files in a certain format and convert to JSON. + */ Singleton { id: root + property string keybindParserPath: FileUtils.trimFileProtocol(`${XdgDirectories.config}/quickshell/scripts/hyprland/get_keybinds.py`) + property string defaultKeybindConfigPath: FileUtils.trimFileProtocol(`${XdgDirectories.config}/hypr/hyprland/keybinds.conf`) + property string userKeybindConfigPath: FileUtils.trimFileProtocol(`${XdgDirectories.config}/hypr/custom/keybinds.conf`) property var defaultKeybinds: {"children": []} property var userKeybinds: {"children": []} property var keybinds: ({ @@ -34,8 +41,7 @@ Singleton { Process { id: getDefaultKeybinds running: true - command: [FileUtils.trimFileProtocol(`${XdgDirectories.config}/quickshell/scripts/hyprland/get_keybinds.py`), - "--path", FileUtils.trimFileProtocol(`${XdgDirectories.config}/hypr/hyprland/keybinds.conf`),] + command: [root.keybindParserPath, "--path", root.defaultKeybindConfigPath,] stdout: SplitParser { onRead: data => { @@ -51,8 +57,7 @@ Singleton { Process { id: getUserKeybinds running: true - command: [FileUtils.trimFileProtocol(`${XdgDirectories.config}/quickshell/scripts/hyprland/get_keybinds.py`), - "--path", FileUtils.trimFileProtocol(`${XdgDirectories.config}/hypr/custom/keybinds.conf`),] + command: [root.keybindParserPath, "--path", root.userKeybindConfigPath] stdout: SplitParser { onRead: data => { diff --git a/.config/quickshell/services/KeyringStorage.qml b/.config/quickshell/services/KeyringStorage.qml index 0de619100..3f3956f98 100644 --- a/.config/quickshell/services/KeyringStorage.qml +++ b/.config/quickshell/services/KeyringStorage.qml @@ -8,14 +8,15 @@ import Quickshell.Io; import Qt.labs.platform import QtQuick; +/** + * For storing sensitive data in the keyring. + * Use this for small data only, since it stores a JSON of the contents directly and doesn't use a database. + */ Singleton { id: root property bool loaded: false property var keyringData: ({}) - // onKeyringDataChanged: { - // console.log("[KeyringStorage] Keyring data changed:", JSON.stringify(root.keyringData)); - // } property var properties: { "application": "illogical-impulse", diff --git a/.config/quickshell/services/LatexRenderer.qml b/.config/quickshell/services/LatexRenderer.qml index 9fb5fb0ab..c09f0af99 100644 --- a/.config/quickshell/services/LatexRenderer.qml +++ b/.config/quickshell/services/LatexRenderer.qml @@ -11,12 +11,12 @@ import Quickshell.Hyprland import Qt.labs.platform /** -* Renders LaTeX snippets with MicroTeX. -* For every request: -* 1. Hash it -* 2. Check if the hash is already processed -* 3. If not, render it with MicroTeX and mark as processed -*/ + * Renders LaTeX snippets with MicroTeX. + * For every request: + * 1. Hash it + * 2. Check if the hash is already processed + * 3. If not, render it with MicroTeX and mark as processed + */ Singleton { id: root diff --git a/.config/quickshell/services/MaterialThemeLoader.qml b/.config/quickshell/services/MaterialThemeLoader.qml index 339a0d772..dcee32c26 100644 --- a/.config/quickshell/services/MaterialThemeLoader.qml +++ b/.config/quickshell/services/MaterialThemeLoader.qml @@ -6,6 +6,10 @@ import QtQuick import Quickshell import Quickshell.Io +/** + * Automatically reloads generated material colors. + * It is necessary to run reapplyTheme() on startup because Singletons are lazily loaded. + */ Singleton { id: root property string filePath: `${XdgDirectories.state}/user/generated/colors.json` diff --git a/.config/quickshell/services/MprisController.qml b/.config/quickshell/services/MprisController.qml index 00ef982fc..96aa5e80b 100644 --- a/.config/quickshell/services/MprisController.qml +++ b/.config/quickshell/services/MprisController.qml @@ -1,12 +1,18 @@ pragma Singleton pragma ComponentBehavior: Bound +// From https://git.outfoxxed.me/outfoxxed/nixnew +// It does not have a license, but the author is okay with redistribution. + import QtQml.Models import QtQuick import Quickshell import Quickshell.Io import Quickshell.Services.Mpris +/** + * A service that provides easy access to the active Mpris player. + */ Singleton { id: root; property MprisPlayer trackedPlayer: null; diff --git a/.config/quickshell/services/Network.qml b/.config/quickshell/services/Network.qml index e790c39bc..e2f98005b 100644 --- a/.config/quickshell/services/Network.qml +++ b/.config/quickshell/services/Network.qml @@ -6,6 +6,9 @@ import Quickshell.Io; import Quickshell.Services.Pipewire; import QtQuick; +/** + * Simple polled network state service. + */ Singleton { id: root diff --git a/.config/quickshell/services/Notifications.qml b/.config/quickshell/services/Notifications.qml index 1b20a0fda..863a173e6 100644 --- a/.config/quickshell/services/Notifications.qml +++ b/.config/quickshell/services/Notifications.qml @@ -9,6 +9,12 @@ import Quickshell.Io import Quickshell.Services.Notifications import Qt.labs.platform +/** + * Provides extra features not in Quickshell.Services.Notifications: + * - Persistent storage + * - Popup notifications, with timeout + * - Notification groups by app + */ Singleton { id: root component Notif: QtObject { diff --git a/.config/quickshell/services/PersistentStateManager.qml b/.config/quickshell/services/PersistentStateManager.qml index 7c3fec739..a885d345e 100644 --- a/.config/quickshell/services/PersistentStateManager.qml +++ b/.config/quickshell/services/PersistentStateManager.qml @@ -9,6 +9,10 @@ import Quickshell.Io import Quickshell.Hyprland import Qt.labs.platform +/** + * Manages persistent states across sessions. + * Run loadStates() once at startup to load the states, then use setState() and getState() to modify and access them. + */ Singleton { id: root property string fileDir: XdgDirectories.state diff --git a/.config/quickshell/services/ResourceUsage.qml b/.config/quickshell/services/ResourceUsage.qml index b9d9ae20c..c9a501bc2 100644 --- a/.config/quickshell/services/ResourceUsage.qml +++ b/.config/quickshell/services/ResourceUsage.qml @@ -6,6 +6,9 @@ import QtQuick import Quickshell import Quickshell.Io +/** + * Simple polled resource usage service with RAM, Swap, and CPU usage. + */ Singleton { property double memoryTotal: 1 property double memoryFree: 1 diff --git a/.config/quickshell/services/SystemInfo.qml b/.config/quickshell/services/SystemInfo.qml index 8660693e8..ffd478b65 100644 --- a/.config/quickshell/services/SystemInfo.qml +++ b/.config/quickshell/services/SystemInfo.qml @@ -5,6 +5,9 @@ import QtQuick import Quickshell import Quickshell.Io +/** + * Provides some system info: distro, username. + */ Singleton { property string distroName: "Unknown" property string distroId: "unknown" diff --git a/.config/quickshell/services/Todo.qml b/.config/quickshell/services/Todo.qml index cbf5e02fe..f452a0178 100644 --- a/.config/quickshell/services/Todo.qml +++ b/.config/quickshell/services/Todo.qml @@ -7,6 +7,10 @@ import Quickshell.Io; import Qt.labs.platform import QtQuick; +/** + * Simple to-do list manager. + * Each item is an object with "content" and "done" properties. + */ Singleton { id: root property var filePath: `${XdgDirectories.state}/user/todo.json` From 9b8eac6b54b387bc0cd611a6bf3b97672a6c45f0 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 11:38:50 +0200 Subject: [PATCH 498/824] audio device selector more bouncy --- .../sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml index b1cd21070..cc956cc03 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml @@ -16,6 +16,7 @@ GroupButton { colBackground: Appearance.colors.colLayer2 colBackgroundHover: Appearance.colors.colLayer2Hover colBackgroundActive: Appearance.colors.colLayer2Active + clickedWidth: baseWidth + 30 contentItem: RowLayout { anchors.fill: parent From ce1418cfdbbbd9821c82122cc5fdc62e171da65a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 11:39:37 +0200 Subject: [PATCH 499/824] refractor radiobutton in volume mixer to new file --- .../common/widgets/StyledRadioButton.qml | 86 ++++++++++++++++ .../sidebarRight/volumeMixer/VolumeMixer.qml | 98 +------------------ 2 files changed, 90 insertions(+), 94 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/StyledRadioButton.qml diff --git a/.config/quickshell/modules/common/widgets/StyledRadioButton.qml b/.config/quickshell/modules/common/widgets/StyledRadioButton.qml new file mode 100644 index 000000000..f665a3f13 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/StyledRadioButton.qml @@ -0,0 +1,86 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Widgets +import Quickshell.Services.Pipewire + +RadioButton { + id: root + implicitHeight: 40 + property string description + property color activeColor: Appearance?.m3colors.m3primary ?? "#685496" + property color inactiveColor: Appearance?.m3colors.m3onSurfaceVariant ?? "#45464F" + + PointingHandInteraction {} + + indicator: Item{} + + contentItem: RowLayout { + Layout.fillWidth: true + spacing: 12 + Rectangle { + id: radio + Layout.fillWidth: false + Layout.alignment: Qt.AlignVCenter + width: 20 + height: 20 + radius: Appearance?.rounding.full + border.color: checked ? root.activeColor : root.inactiveColor + border.width: 2 + color: "transparent" + + // Checked indicator + Rectangle { + anchors.centerIn: parent + width: checked ? 10 : 4 + height: checked ? 10 : 4 + radius: Appearance?.rounding.full + color: Appearance?.m3colors.m3primary + opacity: checked ? 1 : 0 + + Behavior on opacity { + animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on width { + animation: Appearance?.animation.elementMove.numberAnimation.createObject(this) + } + Behavior on height { + animation: Appearance?.animation.elementMove.numberAnimation.createObject(this) + } + + } + + // Hover + Rectangle { + anchors.centerIn: parent + width: root.hovered ? 40 : 20 + height: root.hovered ? 40 : 20 + radius: Appearance?.rounding.full + color: Appearance?.m3colors.m3onSurface + opacity: root.hovered ? 0.1 : 0 + + Behavior on opacity { + animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on width { + animation: Appearance?.animation.elementMove.numberAnimation.createObject(this) + } + Behavior on height { + animation: Appearance?.animation.elementMove.numberAnimation.createObject(this) + } + } + } + + StyledText { + text: root.description + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + wrapMode: Text.Wrap + color: Appearance?.m3colors.m3onSurface + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml index 4fd51c75d..a7a88a403 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml @@ -212,12 +212,14 @@ Item { }) // This could and should be refractored, but all data becomes null when passed wtf - delegate: RadioButton { + delegate: StyledRadioButton { id: radioButton + required property var modelData Layout.leftMargin: root.dialogMargins Layout.rightMargin: root.dialogMargins Layout.fillWidth: true - implicitHeight: 40 + + description: modelData.description checked: modelData.id === Pipewire.defaultAudioSink?.id Connections { @@ -228,103 +230,11 @@ Item { } } - PointingHandInteraction {} - onCheckedChanged: { if (checked) { root.selectedDevice = modelData } } - - indicator: Item{} - - contentItem: RowLayout { - Layout.fillWidth: true - spacing: 12 - Rectangle { - id: radio - Layout.fillWidth: false - Layout.alignment: Qt.AlignVCenter - width: 20 - height: 20 - radius: Appearance.rounding.full - border.color: checked ? Appearance.m3colors.m3primary : Appearance.m3colors.m3onSurfaceVariant - border.width: 2 - color: "transparent" - - // Checked indicator - Rectangle { - anchors.centerIn: parent - width: checked ? 10 : 4 - height: checked ? 10 : 4 - radius: Appearance.rounding.full - color: Appearance.m3colors.m3primary - opacity: checked ? 1 : 0 - - Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } - } - Behavior on width { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } - } - Behavior on height { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } - } - - } - - // Hover - Rectangle { - anchors.centerIn: parent - width: radioButton.hovered ? 40 : 20 - height: radioButton.hovered ? 40 : 20 - radius: Appearance.rounding.full - color: Appearance.m3colors.m3onSurface - opacity: radioButton.hovered ? 0.1 : 0 - - Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } - } - Behavior on width { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } - } - Behavior on height { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } - } - } - } - StyledText { - text: modelData.description - Layout.alignment: Qt.AlignVCenter - Layout.fillWidth: true - wrapMode: Text.Wrap - color: Appearance.m3colors.m3onSurface - } - } } } Item { From 6c1b27bac9b603660552eacc6ddeb40548c32e40 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 11:39:52 +0200 Subject: [PATCH 500/824] player control: playpause button change radius according to state --- .config/quickshell/modules/mediaControls/PlayerControl.qml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/mediaControls/PlayerControl.qml b/.config/quickshell/modules/mediaControls/PlayerControl.qml index 2256e07be..69b4c4403 100644 --- a/.config/quickshell/modules/mediaControls/PlayerControl.qml +++ b/.config/quickshell/modules/mediaControls/PlayerControl.qml @@ -279,11 +279,12 @@ Item { // Player instance anchors.right: parent.right anchors.bottom: sliderRow.top anchors.bottomMargin: 5 - implicitWidth: 44 - implicitHeight: 44 + property real size: 44 + implicitWidth: size + implicitHeight: size onClicked: playerController.player.togglePlaying(); - buttonRadius: Appearance.rounding.full + buttonRadius: playerController.player?.isPlaying ? Appearance?.rounding.small : size / 2 colBackground: playerController.player?.isPlaying ? blendedColors.colPrimary : blendedColors.colSecondaryContainer colBackgroundHover: playerController.player?.isPlaying ? blendedColors.colPrimaryHover : blendedColors.colSecondaryContainerHover colRipple: playerController.player?.isPlaying ? blendedColors.colPrimaryActive : blendedColors.colSecondaryContainerActive From a2ab9d2877159796837091ceffa8a02ea4e61252 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 11:40:13 +0200 Subject: [PATCH 501/824] more hacking friendly widgets --- .../modules/common/widgets/RippleButton.qml | 25 +++---- .../modules/common/widgets/RoundCorner.qml | 6 +- .../common/widgets/SecondaryTabButton.qml | 14 ++-- .../modules/common/widgets/StyledListView.qml | 31 +++++---- .../common/widgets/StyledProgressBar.qml | 17 +++-- .../modules/common/widgets/StyledSlider.qml | 65 +++++++++---------- .../modules/common/widgets/StyledSwitch.qml | 11 +++- .../modules/common/widgets/StyledText.qml | 6 +- .../modules/common/widgets/StyledToolTip.qml | 26 ++------ 9 files changed, 100 insertions(+), 101 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/RippleButton.qml b/.config/quickshell/modules/common/widgets/RippleButton.qml index 98b6aad80..4d38236e9 100644 --- a/.config/quickshell/modules/common/widgets/RippleButton.qml +++ b/.config/quickshell/modules/common/widgets/RippleButton.qml @@ -8,6 +8,9 @@ import QtQuick.Layouts import Quickshell.Io import Quickshell.Widgets +/** + * A button with ripple effect similar to in Material Design. + */ Button { id: root property bool toggled @@ -19,12 +22,12 @@ Button { property bool rippleEnabled: true property var altAction - property color colBackground: ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) - property color colBackgroundHover: Appearance.colors.colLayer1Hover - property color colBackgroundToggled: Appearance.m3colors.m3primary - property color colBackgroundToggledHover: Appearance.colors.colPrimaryHover - property color colRipple: Appearance.colors.colLayer1Active - property color colRippleToggled: Appearance.colors.colPrimaryActive + property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "#00000000" + property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED" + property color colBackgroundToggled: Appearance?.m3colors.m3primary ?? "#65558F" + property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C" + property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2" + property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2" property color buttonColor: root.enabled ? (root.toggled ? (root.hovered ? colBackgroundToggledHover : @@ -48,8 +51,8 @@ Button { component RippleAnim: NumberAnimation { duration: rippleDuration - easing.type: Appearance.animation.elementMoveEnter.type - easing.bezierCurve: Appearance.animationCurves.standardDecel + easing.type: Appearance?.animation.elementMoveEnter.type + easing.bezierCurve: Appearance?.animationCurves.standardDecel } MouseArea { @@ -125,7 +128,7 @@ Button { color: root.buttonColor Behavior on color { - animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) } layer.enabled: true @@ -140,11 +143,11 @@ Button { Rectangle { id: ripple - radius: Appearance.rounding.full + radius: Appearance?.rounding.full ?? 9999 opacity: 0 color: root.rippleColor Behavior on color { - animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) } transform: Translate { diff --git a/.config/quickshell/modules/common/widgets/RoundCorner.qml b/.config/quickshell/modules/common/widgets/RoundCorner.qml index 59e9a0c01..c9a2827a6 100644 --- a/.config/quickshell/modules/common/widgets/RoundCorner.qml +++ b/.config/quickshell/modules/common/widgets/RoundCorner.qml @@ -58,11 +58,7 @@ Item { } Behavior on size { - NumberAnimation { - duration: root.animationDuration - easing.type: Easing.OutCubic - } - + animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } } diff --git a/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml b/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml index 6f87e719d..44677deb7 100644 --- a/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml +++ b/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml @@ -9,7 +9,7 @@ import Quickshell.Io import Quickshell.Widgets TabButton { - id: button + id: root property string buttonText property string buttonIcon property bool selected: false @@ -17,6 +17,10 @@ TabButton { height: buttonBackground.height property int tabContentWidth: buttonBackground.width - buttonBackground.radius*2 + property color colBackground: ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) + property color colBackgroundHover: Appearance.colors.colLayer1Hover + property color colRipple: Appearance.colors.colLayer1Active + PointingHandInteraction {} component RippleAnim: NumberAnimation { @@ -42,7 +46,7 @@ TabButton { rippleAnim.restart(); } onReleased: (event) => { - button.click() // Because the MouseArea already consumed the event + root.click() // Because the MouseArea already consumed the event rippleFadeAnim.restart(); } } @@ -88,9 +92,9 @@ TabButton { background: Rectangle { id: buttonBackground - radius: Appearance.rounding.small + radius: Appearance?.rounding.small ?? 7 implicitHeight: 37 - color: (button.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)) + color: (root.hovered ? root.colBackgroundHover : root.colBackground) layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { @@ -108,7 +112,7 @@ TabButton { id: ripple radius: Appearance.rounding.full - color: Appearance.colors.colLayer1Active + color: root.colRipple opacity: 0 transform: Translate { diff --git a/.config/quickshell/modules/common/widgets/StyledListView.qml b/.config/quickshell/modules/common/widgets/StyledListView.qml index ed6aa9162..c13d8082f 100644 --- a/.config/quickshell/modules/common/widgets/StyledListView.qml +++ b/.config/quickshell/modules/common/widgets/StyledListView.qml @@ -9,7 +9,10 @@ import Quickshell import Quickshell.Wayland import Quickshell.Hyprland -ListView { // Scrollable window +/** + * A ListView with animations. + */ +ListView { id: root spacing: 5 property real removeOvershoot: 20 // Account for gaps and bouncy animations @@ -23,7 +26,7 @@ ListView { // Scrollable window add: Transition { animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { + Appearance?.animation.elementMove.numberAnimation.createObject(this, { properties: "opacity,scale", from: 0, to: 1, @@ -33,10 +36,10 @@ ListView { // Scrollable window addDisplaced: Transition { animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { + Appearance?.animation.elementMove.numberAnimation.createObject(this, { property: "y", }), - Appearance.animation.elementMove.numberAnimation.createObject(this, { + Appearance?.animation.elementMove.numberAnimation.createObject(this, { properties: "opacity,scale", to: 1, }), @@ -45,10 +48,10 @@ ListView { // Scrollable window displaced: Transition { animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { + Appearance?.animation.elementMove.numberAnimation.createObject(this, { property: "y", }), - Appearance.animation.elementMove.numberAnimation.createObject(this, { + Appearance?.animation.elementMove.numberAnimation.createObject(this, { properties: "opacity,scale", to: 1, }), @@ -57,10 +60,10 @@ ListView { // Scrollable window move: Transition { animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { + Appearance?.animation.elementMove.numberAnimation.createObject(this, { property: "y", }), - Appearance.animation.elementMove.numberAnimation.createObject(this, { + Appearance?.animation.elementMove.numberAnimation.createObject(this, { properties: "opacity,scale", to: 1, }), @@ -68,10 +71,10 @@ ListView { // Scrollable window } moveDisplaced: Transition { animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { + Appearance?.animation.elementMove.numberAnimation.createObject(this, { property: "y", }), - Appearance.animation.elementMove.numberAnimation.createObject(this, { + Appearance?.animation.elementMove.numberAnimation.createObject(this, { properties: "opacity,scale", to: 1, }), @@ -80,11 +83,11 @@ ListView { // Scrollable window remove: Transition { animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { + Appearance?.animation.elementMove.numberAnimation.createObject(this, { property: "x", to: root.width + root.removeOvershoot, }), - Appearance.animation.elementMove.numberAnimation.createObject(this, { + Appearance?.animation.elementMove.numberAnimation.createObject(this, { property: "opacity", to: 0, }) @@ -94,10 +97,10 @@ ListView { // Scrollable window // This is movement when something is removed, not removing animation! removeDisplaced: Transition { animations: [ - Appearance.animation.elementMove.numberAnimation.createObject(this, { + Appearance?.animation.elementMove.numberAnimation.createObject(this, { property: "y", }), - Appearance.animation.elementMove.numberAnimation.createObject(this, { + Appearance?.animation.elementMove.numberAnimation.createObject(this, { properties: "opacity,scale", to: 1, }), diff --git a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml index 7173af88b..c5fdbf78f 100644 --- a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml +++ b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml @@ -8,22 +8,25 @@ import Quickshell import Quickshell.Widgets import Qt5Compat.GraphicalEffects +/** + * Material 3 progress bar. See https://m3.material.io/components/progress-indicators/overview + */ ProgressBar { id: root property real valueBarWidth: 120 property real valueBarHeight: 4 property real valueBarGap: 4 - property color highlightColor: Appearance.m3colors.m3primary - property color trackColor: Appearance.m3colors.m3secondaryContainer + property color highlightColor: Appearance?.m3colors.m3primary ?? "#685496" + property color trackColor: Appearance?.m3colors.m3secondaryContainer ?? "#F1D3F9" Behavior on value { - animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + animation: Appearance?.animation.elementMoveEnter.numberAnimation.createObject(this) } background: Rectangle { anchors.fill: parent color: "transparent" - radius: Appearance.rounding.full + radius: Appearance?.rounding.full ?? 9999 implicitHeight: valueBarHeight implicitWidth: valueBarWidth } @@ -35,21 +38,21 @@ ProgressBar { Rectangle { // Left progress fill width: root.visualPosition * parent.width height: parent.height - radius: Appearance.rounding.full + radius: Appearance?.rounding.full ?? 9999 color: root.highlightColor } Rectangle { // Right remaining part fill anchors.right: parent.right width: (1 - root.visualPosition) * parent.width - valueBarGap height: parent.height - radius: Appearance.rounding.full + radius: Appearance?.rounding.full ?? 9999 color: root.trackColor } Rectangle { // Stop point anchors.right: parent.right width: valueBarGap height: valueBarGap - radius: Appearance.rounding.full + radius: Appearance?.rounding.full ?? 9999 color: root.highlightColor } } diff --git a/.config/quickshell/modules/common/widgets/StyledSlider.qml b/.config/quickshell/modules/common/widgets/StyledSlider.qml index eb241854e..00aba779c 100644 --- a/.config/quickshell/modules/common/widgets/StyledSlider.qml +++ b/.config/quickshell/modules/common/widgets/StyledSlider.qml @@ -8,22 +8,23 @@ import Quickshell.Widgets // Material 3 slider. See https://m3.material.io/components/sliders/overview Slider { - id: slider + id: root property real scale: 0.85 property real backgroundDotSize: 4 * scale property real backgroundDotMargins: 4 * scale // property real handleMargins: 0 * scale - property real handleMargins: (slider.pressed ? 0 : 2) * scale - property real handleWidth: (slider.pressed ? 3 : 5) * scale + property real handleMargins: (root.pressed ? 0 : 2) * scale + property real handleWidth: (root.pressed ? 3 : 5) * scale property real handleHeight: 44 * scale - property real handleLimit: slider.backgroundDotMargins + property real handleLimit: root.backgroundDotMargins property real trackHeight: 30 * scale property color highlightColor: Appearance.m3colors.m3primary property color trackColor: Appearance.m3colors.m3secondaryContainer property color handleColor: Appearance.m3colors.m3onSecondaryContainer property real trackRadius: Appearance.rounding.verysmall * scale + property real unsharpenRadius: Appearance.rounding.unsharpen - property real limitedHandleRangeWidth: (slider.availableWidth - handleWidth - slider.handleLimit * 2) + property real limitedHandleRangeWidth: (root.availableWidth - handleWidth - root.handleLimit * 2) property string tooltipContent: `${Math.round(value * 100)}%` Layout.fillWidth: true from: 0 @@ -46,7 +47,7 @@ Slider { MouseArea { anchors.fill: parent onPressed: (mouse) => mouse.accepted = false - cursorShape: slider.pressed ? Qt.ClosedHandCursor : Qt.PointingHandCursor + cursorShape: root.pressed ? Qt.ClosedHandCursor : Qt.PointingHandCursor } background: Item { @@ -57,60 +58,56 @@ Slider { Rectangle { anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - width: slider.handleLimit * 2 + slider.visualPosition * slider.limitedHandleRangeWidth - (slider.handleMargins + slider.handleWidth / 2) + width: root.handleLimit * 2 + root.visualPosition * root.limitedHandleRangeWidth - (root.handleMargins + root.handleWidth / 2) height: trackHeight - color: slider.highlightColor - topLeftRadius: slider.trackRadius - bottomLeftRadius: slider.trackRadius - topRightRadius: Appearance.rounding.unsharpen - bottomRightRadius: Appearance.rounding.unsharpen + color: root.highlightColor + topLeftRadius: root.trackRadius + bottomLeftRadius: root.trackRadius + topRightRadius: root.unsharpenRadius + bottomRightRadius: root.unsharpenRadius } // Fill right Rectangle { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right - width: slider.handleLimit * 2 + (1 - slider.visualPosition) * slider.limitedHandleRangeWidth - (slider.handleMargins + slider.handleWidth / 2) + width: root.handleLimit * 2 + (1 - root.visualPosition) * root.limitedHandleRangeWidth - (root.handleMargins + root.handleWidth / 2) height: trackHeight - color: slider.trackColor - topLeftRadius: Appearance.rounding.unsharpen - bottomLeftRadius: Appearance.rounding.unsharpen - topRightRadius: slider.trackRadius - bottomRightRadius: slider.trackRadius + color: root.trackColor + topLeftRadius: root.unsharpenRadius + bottomLeftRadius: root.unsharpenRadius + topRightRadius: root.trackRadius + bottomRightRadius: root.trackRadius } // Dot at the end Rectangle { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right - anchors.rightMargin: slider.backgroundDotMargins - width: slider.backgroundDotSize - height: slider.backgroundDotSize + anchors.rightMargin: root.backgroundDotMargins + width: root.backgroundDotSize + height: root.backgroundDotSize radius: Appearance.rounding.full - color: slider.handleColor + color: root.handleColor } } handle: Rectangle { id: handle - x: slider.leftPadding + slider.handleLimit + slider.visualPosition * slider.limitedHandleRangeWidth - y: slider.topPadding + slider.availableHeight / 2 - height / 2 - implicitWidth: slider.handleWidth - implicitHeight: slider.handleHeight + x: root.leftPadding + root.handleLimit + root.visualPosition * root.limitedHandleRangeWidth + y: root.topPadding + root.availableHeight / 2 - height / 2 + implicitWidth: root.handleWidth + implicitHeight: root.handleHeight radius: Appearance.rounding.full - color: slider.handleColor + color: root.handleColor Behavior on implicitWidth { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } + animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } StyledToolTip { - extraVisibleCondition: slider.pressed - content: slider.tooltipContent + extraVisibleCondition: root.pressed + content: root.tooltipContent } } } \ No newline at end of file diff --git a/.config/quickshell/modules/common/widgets/StyledSwitch.qml b/.config/quickshell/modules/common/widgets/StyledSwitch.qml index be327967b..5342d41ce 100644 --- a/.config/quickshell/modules/common/widgets/StyledSwitch.qml +++ b/.config/quickshell/modules/common/widgets/StyledSwitch.qml @@ -4,11 +4,16 @@ import QtQuick.Layouts import QtQuick.Controls import Qt5Compat.GraphicalEffects +/** + * Material 3 switch. See https://m3.material.io/components/switch/overview + */ Switch { id: root property real scale: 1 implicitHeight: 32 * root.scale implicitWidth: 52 * root.scale + property color activeColor: Appearance?.m3colors.m3primary ?? "#685496" + property color inactiveColor: Appearance?.m3colors.m3surfaceContainerHighest ?? "#45464F" PointingHandInteraction {} @@ -16,10 +21,10 @@ Switch { background: Rectangle { width: parent.width height: parent.height - radius: Appearance.rounding.full - color: root.checked ? Appearance.m3colors.m3primary : Appearance.m3colors.m3surfaceContainerHighest + radius: Appearance?.rounding.full ?? 9999 + color: root.checked ? root.activeColor : root.inactiveColor border.width: 2 * root.scale - border.color: root.checked ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline + border.color: root.checked ? root.activeColor : Appearance.m3colors.m3outline Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) diff --git a/.config/quickshell/modules/common/widgets/StyledText.qml b/.config/quickshell/modules/common/widgets/StyledText.qml index c5969b0b4..eafb395c9 100644 --- a/.config/quickshell/modules/common/widgets/StyledText.qml +++ b/.config/quickshell/modules/common/widgets/StyledText.qml @@ -6,7 +6,7 @@ Text { renderType: Text.NativeRendering font.hintingPreference: Font.PreferFullHinting verticalAlignment: Text.AlignVCenter - font.family: Appearance.font.family.main - font.pixelSize: Appearance.font.pixelSize.small - color: Appearance.m3colors.m3onBackground + font.family: Appearance?.font.family.main ?? "sans-serif" + font.pixelSize: Appearance?.font.pixelSize.small ?? 15 + color: Appearance?.m3colors.m3onBackground ?? "black" } diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index 627dfb965..1b4bd033a 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -19,11 +19,7 @@ ToolTip { visible: opacity > 0 Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } + animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } background: null @@ -37,34 +33,26 @@ ToolTip { id: backgroundRectangle anchors.bottom: contentItemBackground.bottom anchors.horizontalCenter: contentItemBackground.horizontalCenter - color: Appearance.colors.colTooltip - radius: Appearance.rounding.verysmall + color: Appearance?.colors.colTooltip ?? "#3C4043" + radius: Appearance?.rounding.verysmall ?? 7 width: internalVisibleCondition ? (tooltipTextObject.width + 2 * padding) : 0 height: internalVisibleCondition ? (tooltipTextObject.height + 2 * padding) : 0 clip: true Behavior on width { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } + animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on height { - NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } + animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } StyledText { id: tooltipTextObject anchors.centerIn: parent text: content - font.pixelSize: Appearance.font.pixelSize.smaller + font.pixelSize: Appearance?.font.pixelSize.smaller ?? 14 font.hintingPreference: Font.PreferNoHinting // Prevent shaky text - color: Appearance.colors.colOnTooltip + color: Appearance?.colors.colOnTooltip ?? "#FFFFFF" wrapMode: Text.Wrap } } From 21e705443ef76982943f6af493c5d55b0f71aeb6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 11:55:59 +0200 Subject: [PATCH 502/824] notification item: don't show unnecessary border when it's the only one in the group --- .../quickshell/modules/common/widgets/NotificationItem.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationItem.qml b/.config/quickshell/modules/common/widgets/NotificationItem.qml index 351f6bc2a..a123ad042 100644 --- a/.config/quickshell/modules/common/widgets/NotificationItem.qml +++ b/.config/quickshell/modules/common/widgets/NotificationItem.qml @@ -20,7 +20,7 @@ Item { // Notification item area property bool expanded: false property bool onlyNotification: false property real fontSize: Appearance.font.pixelSize.small - property real padding: 8 + property real padding: onlyNotification ? 0 : 8 property real dragConfirmThreshold: 70 // Drag further to discard notification property real dismissOvershoot: notificationIcon.implicitWidth + 20 // Account for gaps and bouncy animations @@ -127,7 +127,7 @@ Item { // Notification item area } } - color: expanded ? + color: (expanded && !onlyNotification) ? (notificationObject.urgency == NotificationUrgency.Critical) ? ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, Appearance.colors.colLayer2, 0.35) : (Appearance.m3colors.m3surfaceContainerHigh) : @@ -202,7 +202,7 @@ Item { // Notification item area Layout.fillWidth: true implicitHeight: actionRowLayout.implicitHeight contentWidth: actionRowLayout.implicitWidth - clip: true + clip: !onlyNotification Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) From 5434771d77c6a3e36d44e9989f4aca81042428f3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 11:56:14 +0200 Subject: [PATCH 503/824] media controls: adjust playpause button radius --- .config/quickshell/modules/mediaControls/PlayerControl.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/mediaControls/PlayerControl.qml b/.config/quickshell/modules/mediaControls/PlayerControl.qml index 69b4c4403..bdafb6a61 100644 --- a/.config/quickshell/modules/mediaControls/PlayerControl.qml +++ b/.config/quickshell/modules/mediaControls/PlayerControl.qml @@ -284,7 +284,7 @@ Item { // Player instance implicitHeight: size onClicked: playerController.player.togglePlaying(); - buttonRadius: playerController.player?.isPlaying ? Appearance?.rounding.small : size / 2 + buttonRadius: playerController.player?.isPlaying ? Appearance?.rounding.normal : size / 2 colBackground: playerController.player?.isPlaying ? blendedColors.colPrimary : blendedColors.colSecondaryContainer colBackgroundHover: playerController.player?.isPlaying ? blendedColors.colPrimaryHover : blendedColors.colSecondaryContainerHover colRipple: playerController.player?.isPlaying ? blendedColors.colPrimaryActive : blendedColors.colSecondaryContainerActive From bca1a77ae3c136e80ddf53cb1769d294381d7006 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 12:10:03 +0200 Subject: [PATCH 504/824] more hacking friendliness --- .../modules/common/widgets/ButtonGroup.qml | 4 +-- .../common/widgets/CircularProgress.qml | 7 ++-- .../modules/common/widgets/DialogButton.qml | 12 +++++-- .../modules/common/widgets/DragManager.qml | 5 ++- .../common/widgets/FlowButtonGroup.qml | 3 ++ .../modules/common/widgets/GroupButton.qml | 16 ++++++---- .../modules/common/widgets/MaterialSymbol.qml | 10 +++--- .../modules/common/widgets/MenuButton.qml | 6 ++-- .../widgets/NotificationGroupExpandButton.qml | 11 ++++--- .../modules/common/widgets/PrimaryTabBar.qml | 14 ++++---- .../common/widgets/PrimaryTabButton.qml | 32 +++++++++++-------- .../modules/common/widgets/RippleButton.qml | 2 +- 12 files changed, 75 insertions(+), 47 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/ButtonGroup.qml b/.config/quickshell/modules/common/widgets/ButtonGroup.qml index 2b997acf9..a1570c6af 100644 --- a/.config/quickshell/modules/common/widgets/ButtonGroup.qml +++ b/.config/quickshell/modules/common/widgets/ButtonGroup.qml @@ -6,8 +6,8 @@ import QtQuick.Controls import QtQuick.Layouts /** - * A container that supports bouncy children. - * https://m3.material.io/components/button-groups/overview + * A container that supports GroupButton children for bounciness. + * See https://m3.material.io/components/button-groups/overview */ Rectangle { id: root diff --git a/.config/quickshell/modules/common/widgets/CircularProgress.qml b/.config/quickshell/modules/common/widgets/CircularProgress.qml index 480098db1..19a838c4c 100644 --- a/.config/quickshell/modules/common/widgets/CircularProgress.qml +++ b/.config/quickshell/modules/common/widgets/CircularProgress.qml @@ -1,9 +1,12 @@ -// From https://github.com/rafzby/circular-progressbar +// From https://github.com/rafzby/circular-progressbar with modifications // License: LGPL-3.0 - A copy can be found in `licenses` folder of repo -// Modified so it looks like in Material 3: https://m3.material.io/components/progress-indicators/specs + import QtQuick import "root:/modules/common" +/** + * Material 3 circular progress. See https://m3.material.io/components/progress-indicators/specs + */ Item { id: root diff --git a/.config/quickshell/modules/common/widgets/DialogButton.qml b/.config/quickshell/modules/common/widgets/DialogButton.qml index 062fa3dba..977e9b95d 100644 --- a/.config/quickshell/modules/common/widgets/DialogButton.qml +++ b/.config/quickshell/modules/common/widgets/DialogButton.qml @@ -6,13 +6,19 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io +/** + * Material 3 dialog button. See https://m3.material.io/components/dialogs/overview + */ RippleButton { id: button property string buttonText implicitHeight: 30 implicitWidth: buttonTextWidget.implicitWidth + 15 * 2 - buttonRadius: Appearance.rounding.full + buttonRadius: Appearance?.rounding.full ?? 9999 + + property color colEnabled: Appearance?.m3colors.m3primary ?? "#65558F" + property color colDisabled: Appearance?.m3colors.m3outline ?? "#8D8C96" contentItem: StyledText { id: buttonTextWidget @@ -21,8 +27,8 @@ RippleButton { anchors.rightMargin: 15 text: buttonText horizontalAlignment: Text.AlignHCenter - font.pixelSize: Appearance.font.pixelSize.small - color: button.enabled ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline + font.pixelSize: Appearance?.font.pixelSize.small ?? 12 + color: button.enabled ? button.colEnabled : button.colDisabled Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) diff --git a/.config/quickshell/modules/common/widgets/DragManager.qml b/.config/quickshell/modules/common/widgets/DragManager.qml index 8876c18f2..087729edb 100644 --- a/.config/quickshell/modules/common/widgets/DragManager.qml +++ b/.config/quickshell/modules/common/widgets/DragManager.qml @@ -4,7 +4,10 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -MouseArea { // Flick to dismiss +/** + * A convenience MouseArea for handling drag events. + */ +MouseArea { id: root hoverEnabled: true acceptedButtons: Qt.LeftButton diff --git a/.config/quickshell/modules/common/widgets/FlowButtonGroup.qml b/.config/quickshell/modules/common/widgets/FlowButtonGroup.qml index d5faf4306..ec9526ef2 100644 --- a/.config/quickshell/modules/common/widgets/FlowButtonGroup.qml +++ b/.config/quickshell/modules/common/widgets/FlowButtonGroup.qml @@ -1,5 +1,8 @@ import QtQuick +/** + * This is just to make sure `RippleButton`s can be used in a Flow layout. + */ Flow { property int clickIndex: -1 } diff --git a/.config/quickshell/modules/common/widgets/GroupButton.qml b/.config/quickshell/modules/common/widgets/GroupButton.qml index f9d0195ca..4b033a72b 100644 --- a/.config/quickshell/modules/common/widgets/GroupButton.qml +++ b/.config/quickshell/modules/common/widgets/GroupButton.qml @@ -8,6 +8,10 @@ import QtQuick.Layouts import Quickshell.Io import Quickshell.Widgets +/** + * Material 3 button with expressive bounciness. + * See https://m3.material.io/components/button-groups/overview + */ Button { id: root property bool toggled @@ -35,12 +39,12 @@ Button { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } - property color colBackground: ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) - property color colBackgroundHover: Appearance.colors.colLayer1Hover - property color colBackgroundActive: Appearance.colors.colLayer1Active - property color colBackgroundToggled: Appearance.m3colors.m3primary - property color colBackgroundToggledHover: Appearance.colors.colPrimaryHover - property color colBackgroundToggledActive: Appearance.colors.colPrimaryActive + property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent" + property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED" + property color colBackgroundActive: Appearance?.colors.colLayer1Active ?? "#D6CEE2" + property color colBackgroundToggled: Appearance?.m3colors.m3primary ?? "#65558F" + property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C" + property color colBackgroundToggledActive: Appearance?.colors.colPrimaryActive ?? "#D6CEE2" property real radius: root.down ? root.buttonRadiusPressed : root.buttonRadius property color color: root.enabled ? (root.toggled ? diff --git a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml index ff6b4f66e..dbbfff009 100644 --- a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml +++ b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml @@ -4,20 +4,20 @@ import QtQuick.Layouts Text { id: root - property real iconSize: Appearance.font.pixelSize.small + property real iconSize: Appearance?.font.pixelSize.small ?? 16 property real fill: 0 renderType: Text.NativeRendering font.hintingPreference: Font.PreferFullHinting verticalAlignment: Text.AlignVCenter - font.family: Appearance.font.family.iconMaterial + font.family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded" font.pixelSize: iconSize color: Appearance.m3colors.m3onBackground Behavior on fill { NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + duration: Appearance?.animation.elementMoveFast.duration ?? 200 + easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline + easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1] } } diff --git a/.config/quickshell/modules/common/widgets/MenuButton.qml b/.config/quickshell/modules/common/widgets/MenuButton.qml index 41bd762ef..0c4c4f411 100644 --- a/.config/quickshell/modules/common/widgets/MenuButton.qml +++ b/.config/quickshell/modules/common/widgets/MenuButton.qml @@ -7,7 +7,7 @@ import Quickshell import Quickshell.Io RippleButton { - id: button + id: root buttonRadius: 0 implicitHeight: 36 @@ -18,10 +18,10 @@ RippleButton { anchors.fill: parent anchors.leftMargin: 14 anchors.rightMargin: 14 - text: button.buttonText + text: root.buttonText horizontalAlignment: Text.AlignLeft font.pixelSize: Appearance.font.pixelSize.small - color: button.enabled ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3outline + color: root.enabled ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3outline Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) diff --git a/.config/quickshell/modules/common/widgets/NotificationGroupExpandButton.qml b/.config/quickshell/modules/common/widgets/NotificationGroupExpandButton.qml index 5de02a98e..bd1a8e96d 100644 --- a/.config/quickshell/modules/common/widgets/NotificationGroupExpandButton.qml +++ b/.config/quickshell/modules/common/widgets/NotificationGroupExpandButton.qml @@ -12,16 +12,17 @@ RippleButton { // Expand button id: root required property int count required property bool expanded - property real fontSize: Appearance.font.pixelSize.small + property real fontSize: Appearance?.font.pixelSize.small ?? 12 + property real iconSize: Appearance?.font.pixelSize.normal ?? 16 implicitHeight: fontSize + 4 * 2 implicitWidth: Math.max(contentItem.implicitWidth + 5 * 2, 30) Layout.alignment: Qt.AlignVCenter Layout.fillHeight: false buttonRadius: Appearance.rounding.full - colBackground: ColorUtils.mix(Appearance.colors.colLayer2, Appearance.colors.colLayer2Hover, 0.5) - colBackgroundHover: Appearance.colors.colLayer2Hover - colRipple: Appearance.colors.colLayer2Active + colBackground: ColorUtils.mix(Appearance?.colors.colLayer2, Appearance?.colors.colLayer2Hover, 0.5) + colBackgroundHover: Appearance?.colors.colLayer2Hover ?? "#E5DFED" + colRipple: Appearance?.colors.colLayer2Active ?? "#D6CEE2" contentItem: Item { anchors.centerIn: parent @@ -38,7 +39,7 @@ RippleButton { // Expand button } MaterialSymbol { text: "keyboard_arrow_down" - iconSize: Appearance.font.pixelSize.normal + iconSize: root.iconSize color: Appearance.colors.colOnLayer2 rotation: expanded ? 180 : 0 Behavior on rotation { diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml b/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml index de80d185b..accf17ab4 100644 --- a/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml +++ b/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml @@ -10,6 +10,8 @@ ColumnLayout { required property var tabButtonList // Something like [{"icon": "notifications", "name": qsTr("Notifications")}, {"icon": "volume_up", "name": qsTr("Volume mixer")}] required property var externalTrackedTab property bool enableIndicatorAnimation: false + property color colIndicator: Appearance?.m3colors.m3primary ?? "#65558F" + property color colBorder: Appearance?.m3colors.m3outlineVariant ?? "#C6C6D0" signal currentIndexChanged(int index) TabBar { @@ -67,15 +69,15 @@ ColumnLayout { x: tabBar.currentIndex * fullTabSize + (fullTabSize - targetWidth) / 2 - color: Appearance.m3colors.m3primary - radius: Appearance.rounding.full + color: root.colIndicator + radius: Appearance?.rounding.full ?? 9999 Behavior on x { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + animation: Appearance?.animation.elementMove.numberAnimation.createObject(this) } Behavior on implicitWidth { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + animation: Appearance?.animation.elementMove.numberAnimation.createObject(this) } } } @@ -83,7 +85,7 @@ ColumnLayout { Rectangle { // Tabbar bottom border id: tabBarBottomBorder Layout.fillWidth: true - height: 1 - color: Appearance.m3colors.m3outlineVariant + implicitHeight: 1 + color: root.colBorder } } diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml index 58424032c..3a2879adc 100644 --- a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml +++ b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml @@ -17,10 +17,16 @@ TabButton { property int rippleDuration: 1200 height: buttonBackground.height + property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent" + property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED" + property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2" + property color colActive: Appearance?.m3colors.m3primary ?? "#65558F" + property color colInactive: Appearance?.colors.colOnLayer1 ?? "#45464F" + component RippleAnim: NumberAnimation { duration: rippleDuration - easing.type: Appearance.animation.elementMoveEnter.type - easing.bezierCurve: Appearance.animationCurves.standardDecel + easing.type: Appearance?.animation.elementMoveEnter.type + easing.bezierCurve: Appearance?.animationCurves.standardDecel } MouseArea { @@ -86,9 +92,9 @@ TabButton { background: Rectangle { id: buttonBackground - radius: Appearance.rounding.small + radius: Appearance?.rounding.small implicitHeight: 50 - color: (button.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)) + color: (button.hovered ? button.colBackgroundHover : button.colBackground) layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { @@ -99,14 +105,14 @@ TabButton { } Behavior on color { - animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) } Rectangle { id: ripple - radius: Appearance.rounding.full - color: Appearance.colors.colLayer1Active + radius: Appearance?.rounding.full ?? 9999 + color: button.colRipple opacity: 0 transform: Translate { @@ -126,22 +132,22 @@ TabButton { Layout.alignment: Qt.AlignHCenter horizontalAlignment: Text.AlignHCenter text: buttonIcon - iconSize: Appearance.font.pixelSize.hugeass + iconSize: Appearance?.font.pixelSize.hugeass ?? 25 fill: selected ? 1 : 0 - color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 + color: selected ? button.colActive : button.colInactive Behavior on color { - animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) } } StyledText { id: buttonTextWidget Layout.alignment: Qt.AlignHCenter horizontalAlignment: Text.AlignHCenter - font.pixelSize: Appearance.font.pixelSize.small - color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 + font.pixelSize: Appearance?.font.pixelSize.small + color: selected ? button.colActive : button.colInactive text: buttonText Behavior on color { - animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) } } } diff --git a/.config/quickshell/modules/common/widgets/RippleButton.qml b/.config/quickshell/modules/common/widgets/RippleButton.qml index 4d38236e9..e6ca5a3fd 100644 --- a/.config/quickshell/modules/common/widgets/RippleButton.qml +++ b/.config/quickshell/modules/common/widgets/RippleButton.qml @@ -22,7 +22,7 @@ Button { property bool rippleEnabled: true property var altAction - property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "#00000000" + property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent" property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED" property color colBackgroundToggled: Appearance?.m3colors.m3primary ?? "#65558F" property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C" From 58f23d3b47e52d49e20656431898cbec8136912d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 12:23:42 +0200 Subject: [PATCH 505/824] sidebar: quick toggles: make right clickable ones bigger --- .../modules/sidebarRight/quickToggles/QuickToggleButton.qml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml b/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml index e215313dd..0ad4f75c8 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml @@ -9,10 +9,9 @@ import Quickshell.Io GroupButton { id: button property string buttonIcon - baseWidth: 40 + baseWidth: altAction ? 60 : 40 baseHeight: 40 - clickedWidth: 60 - clickedHeight: 40 + clickedWidth: baseWidth + 20 toggled: false buttonRadius: Math.min(baseHeight, baseWidth) / 2 buttonRadiusPressed: Appearance?.rounding?.small From a582cc57ac66975a876bc0bb815e7cd873fa50b1 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 12:25:04 +0200 Subject: [PATCH 506/824] sidebar: quick toggles: make long buttons less round --- .../modules/sidebarRight/quickToggles/QuickToggleButton.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml b/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml index 0ad4f75c8..c80f91ba6 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml @@ -13,7 +13,7 @@ GroupButton { baseHeight: 40 clickedWidth: baseWidth + 20 toggled: false - buttonRadius: Math.min(baseHeight, baseWidth) / 2 + buttonRadius: (altAction && toggled) ? Appearance?.rounding.normal : Math.min(baseHeight, baseWidth) / 2 buttonRadiusPressed: Appearance?.rounding?.small contentItem: MaterialSymbol { From 7ab8012e0e7e5ce990897e802061d1c58b332c28 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 12:28:47 +0200 Subject: [PATCH 507/824] notif clear button: ripple -> bouncy --- .../notifications/NotificationStatusButton.qml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml index 0f29107ac..f5bb6844a 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml @@ -4,23 +4,20 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -RippleButton { +GroupButton { id: button property string buttonText: "" property string buttonIcon: "" - implicitHeight: 30 - implicitWidth: contentRowLayout.implicitWidth + 10 * 2 - Behavior on implicitWidth { - SmoothedAnimation { - velocity: Appearance.animation.elementMove.velocity - } - } + baseWidth: contentRowLayout.implicitWidth + 10 * 2 + baseHeight: 30 + clickedWidth: baseWidth + 15 - buttonRadius: Appearance.rounding.full + buttonRadius: baseHeight / 2 + buttonRadiusPressed: Appearance.rounding.small colBackground: Appearance.colors.colLayer2 colBackgroundHover: Appearance.colors.colLayer2Hover - colRipple: Appearance.colors.colLayer2Active + colBackgroundActive: Appearance.colors.colLayer2Active background.anchors.fill: button contentItem: Item { From 24b369882a8783eada5ad1e69bb4a5ecbdcca8b7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 12:42:25 +0200 Subject: [PATCH 508/824] calendar: make day of week not interactable --- .../modules/sidebarRight/calendar/CalendarDayButton.qml | 1 - .../quickshell/modules/sidebarRight/calendar/CalendarWidget.qml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml index 29bfee05c..d5989c847 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml @@ -10,7 +10,6 @@ RippleButton { property string day property int isToday property bool bold - property bool interactable: true Layout.fillWidth: false Layout.fillHeight: false diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml index 659b5505a..1f11d87a2 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml @@ -95,7 +95,7 @@ Item { day: modelData.day isToday: modelData.today bold: true - interactable: false + enabled: false } } } From 262f278bb420c64fc5a13b947ff004d2b31108dd Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 20:02:43 +0200 Subject: [PATCH 509/824] quickshell clipboard --- .config/hypr/hyprland/keybinds.conf | 3 +- .../modules/common/ConfigOptions.qml | 1 + .../quickshell/modules/overview/Overview.qml | 39 +++++++++- .../modules/overview/SearchItem.qml | 7 +- .../modules/overview/SearchWidget.qml | 72 +++++++++++++------ .config/quickshell/services/Cliphist.qml | 57 +++++++++++++++ .config/quickshell/shell.qml | 1 + 7 files changed, 153 insertions(+), 27 deletions(-) create mode 100644 .config/quickshell/services/Cliphist.qml diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 18a0ab955..7570e0751 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -17,6 +17,7 @@ bind = Super, mouse_up, global, quickshell:overviewToggleReleaseInterrupt # [hi bind = Super, mouse_down,global, quickshell:overviewToggleReleaseInterrupt # [hidden] bindit = ,Super_L, global, quickshell:workspaceNumber # [hidden] +bindd = Super, V, Clipboard history >> clipboard, global, quickshell:overviewClipboardToggle # Clipboard history >> clipboard bindd = Super, Tab, Toggle overview, global, quickshell:overviewToggle # [hidden] Toggle overview/launcher (alt) bind = Super, B, global, quickshell:sidebarLeftToggle # [hidden] bindd = Super, A, Toggle left sidebar, global, quickshell:sidebarLeftToggle # Toggle left sidebar @@ -47,7 +48,7 @@ bindd = Ctrl+Shift+Alt+Super, Delete, Shutdown, exec, systemctl poweroff || logi ##! Utilities # Screenshot, Record, OCR, Color picker, Clipboard history -bindd = Super, V, Copy clipboard history entry, exec, pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # Clipboard history >> clipboard +bindd = Super, V, Copy clipboard history entry, exec, qs ipc call TEST_ALIVE || pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # Clipboard history >> clipboard bindd = Super, Period, Copy an emoji, exec, pkill fuzzel || ~/.local/bin/fuzzel-emoji # Emoji bindd = Super+Shift, S, Screen snip, exec, grimblast --freeze copy area # Screen snip >> clipboard bindd = Super+Shift+Alt, S, Screen snip and annotate, exec, grim -g "$(slurp)" - | swappy -f - # Screen snip and annotate diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index ce4ddc0f9..f38d8f056 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -62,6 +62,7 @@ Singleton { property list excludedSites: [ "quora.com" ] property QtObject prefix: QtObject { property string action: "/" + property string clipboard: ":" } } diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 9625f6c7b..8f0ca8a8d 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -10,7 +10,10 @@ import Quickshell.Wayland import Quickshell.Hyprland Scope { + id: overviewScope + property bool dontAutoCancelSearch: false Variants { + id: overviewVariants model: Quickshell.screens PanelWindow { id: root @@ -50,7 +53,14 @@ Scope { Connections { target: GlobalStates function onOverviewOpenChanged() { - delayedGrabTimer.start() + if (!GlobalStates.overviewOpen) { + overviewScope.dontAutoCancelSearch = false; + } else { + if (!overviewScope.dontAutoCancelSearch) { + searchWidget.cancelSearch() + } + delayedGrabTimer.start() + } } } @@ -67,6 +77,10 @@ Scope { implicitWidth: columnLayout.width implicitHeight: columnLayout.height + function setSearchingText(text) { + searchWidget.setSearchingText(text); + } + ColumnLayout { id: columnLayout visible: GlobalStates.overviewOpen @@ -84,6 +98,7 @@ Scope { } SearchWidget { + id: searchWidget panelWindow: root Layout.alignment: Qt.AlignHCenter onSearchingTextChanged: (text) => { @@ -163,5 +178,27 @@ Scope { GlobalStates.superReleaseMightTrigger = false } } + GlobalShortcut { + name: "overviewClipboardToggle" + description: qsTr("Toggle clipboard query on overview widget") + + onPressed: { + if (GlobalStates.overviewOpen) { + GlobalStates.overviewOpen = false; + return; + } + for (let i = 0; i < overviewVariants.instances.length; i++) { + let panelWindow = overviewVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + overviewScope.dontAutoCancelSearch = true; + panelWindow.setSearchingText( + ConfigOptions.search.prefix.clipboard + ); + GlobalStates.overviewOpen = true; + return + } + } + } + } } diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index 95e4c744f..7008ad8d9 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -67,7 +67,7 @@ RippleButton { RowLayout { id: rowLayout - spacing: 10 + spacing: iconLoader.sourceComponent === null ? 0 : 10 anchors.fill: parent anchors.leftMargin: root.horizontalMargin + root.buttonHorizontalPadding anchors.rightMargin: root.horizontalMargin + root.buttonHorizontalPadding @@ -76,7 +76,9 @@ RippleButton { Loader { id: iconLoader active: true - sourceComponent: root.materialSymbol == "" ? iconImageComponent : materialSymbolComponent + sourceComponent: root.materialSymbol !== "" ? materialSymbolComponent : + root.itemIcon !== "" ? iconImageComponent : + null } Component { @@ -111,6 +113,7 @@ RippleButton { StyledText { Layout.fillWidth: true id: nameText + textFormat: Text.PlainText // TODO: make cliphist entry highlighting working font.pixelSize: Appearance.font.pixelSize.normal font.family: Appearance.font.family[root.fontType] color: Appearance.m3colors.m3onSurface diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 4320a96a1..1af0c6b9a 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -24,6 +24,21 @@ Item { // Wrapper implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2 property string mathResult: "" + property bool lastQueryWasClipboard: false + + onShowResultsChanged: { + lastQueryWasClipboard = false; + } + function cancelSearch() { + searchInput.selectAll() + root.searchingText = "" + } + + function setSearchingText(text) { + searchInput.text = text; + root.searchingText = text; + } + property var searchActions: [ { action: "img", @@ -194,7 +209,7 @@ Item { // Wrapper Layout.leftMargin: 15 iconSize: Appearance.font.pixelSize.huge color: Appearance.m3colors.m3onSurface - text: "search" + text: root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard) ? 'content_paste_search' : 'search' } TextField { // Search box id: searchInput @@ -219,13 +234,6 @@ Item { // Wrapper } onTextChanged: root.searchingText = text - Connections { - target: root - function onVisibleChanged() { - searchInput.selectAll() - root.searchingText = "" - } - } onAccepted: { if (appResults.count > 0) { @@ -279,10 +287,31 @@ Item { // Wrapper model: ScriptModel { id: model - values: { + values: { // Search results are handled here + ////////////////// Skip? ////////////////// if(root.searchingText == "") return []; - // Start math calculation, declare special result objects + ///////////// Special cases /////////////// + if (root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard)) { // Clipboard + if (!root.lastQueryWasClipboard) { + root.lastQueryWasClipboard = true; + Cliphist.refresh(); // Refresh clipboard entries + } + const searchString = root.searchingText.slice(ConfigOptions.search.prefix.clipboard.length); + return Cliphist.fuzzyQuery(searchString).map(entry => { + return { + name: entry.replace(/^\s*\S+\s+/, ""), + clickActionName: qsTr("Copy"), + type: `#${entry.match(/^\s*(\S+)/)?.[1] || ""}`, + execute: () => { + Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(entry)}' | cliphist decode | wl-copy`); + } + }; + }).filter(Boolean); + } + + + ////////////////// Init /////////////////// nonAppResultsTimer.restart(); const mathResultObject = { name: root.mathResult, @@ -322,10 +351,9 @@ Item { // Wrapper }) .filter(Boolean); - // Init result array let result = []; - // Add filtered application entries + //////////////// Apps ////////////////// result = result.concat( AppSearch.fuzzyQuery(root.searchingText) .map((entry) => { @@ -335,23 +363,20 @@ Item { // Wrapper }) ); - // Add launcher actions + ////////// Launcher actions //////////// result = result.concat(launcherActionObjects); - // Insert math result before command if search starts with a number + /////////// Math result & command ////////// const startsWithNumber = /^\d/.test(root.searchingText); - if (startsWithNumber) + if (startsWithNumber) { result.push(mathResultObject); - - // Command - result.push(commandResultObject); - - // If not already added, add math result after command - if (!startsWithNumber) { + result.push(commandResultObject); + } else { + result.push(commandResultObject); result.push(mathResultObject); } - // Web search + ///////////////// Web search //////////////// result.push({ name: root.searchingText, clickActionName: qsTr("Search"), @@ -369,7 +394,8 @@ Item { // Wrapper return result; } } - delegate: SearchItem { + + delegate: SearchItem { // The selectable item for each search result entry: modelData } } diff --git a/.config/quickshell/services/Cliphist.qml b/.config/quickshell/services/Cliphist.qml new file mode 100644 index 000000000..f835a5f96 --- /dev/null +++ b/.config/quickshell/services/Cliphist.qml @@ -0,0 +1,57 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common/functions/fuzzysort.js" as Fuzzy +import "root:/modules/common" +import "root:/" +import QtQuick +import Quickshell +import Quickshell.Io + +Singleton { + id: root + property list entries: [] + property string highlightPrefix: `` + property string highlightSuffix: `` + readonly property var preparedEntries: entries.map(a => ({ + name: Fuzzy.prepare(`${a}`), + entry: a + })) + function fuzzyQuery(search: string): var { + return Fuzzy.go(search, preparedEntries, { + all: true, + key: "name" + }).map(r => { + return r.obj.entry + // console.log(JSON.stringify(r)) + // return r.highlight(highlightPrefix, highlightSuffix); + }); + } + + function refresh() { + readProc.buffer = [] + readProc.running = false + readProc.running = true + } + + Process { + id: readProc + property list buffer: [] + + command: ["cliphist", "list"] + + stdout: SplitParser { + onRead: (line) => { + readProc.buffer.push(line) + } + } + + onExited: (exitCode, exitStatus) => { + if (exitCode === 0) { + root.entries = readProc.buffer + } else { + console.error("[Cliphist] Failed to refresh with code", exitCode, "and status", exitStatus) + } + } + } +} diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index af14d6b74..ec506a3b9 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -24,6 +24,7 @@ ShellRoot { MaterialThemeLoader.reapplyTheme() ConfigLoader.loadConfig() PersistentStateManager.loadStates() + Cliphist.refresh() } Bar {} From e9e6539cd0b09ccf1b2d35677c9063b33c4a0915 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 20:02:46 +0200 Subject: [PATCH 510/824] Update keybinds.conf --- .config/hypr/hyprland/keybinds.conf | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 7570e0751..1b68ac52f 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -36,7 +36,8 @@ bindle=, XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%- # bindl = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_SINK@ toggle # [hidden] bindld = Super+Shift,M, Toggle mute, exec, wpctl set-mute @DEFAULT_SINK@ toggle # [hidden] bindl = Alt ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # [hidden] -bindld = Super+Shift+Alt,M, Toggle mic, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # [hidden] +bindl = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # [hidden] +bindld = Super+Alt,M, Toggle mic, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # [hidden] bindd = Ctrl+Super, T, Change wallpaper, exec, ~/.config/quickshell/scripts/switchwall.sh # Change wallpaper bind = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool qs; qs & # Restart widgets @@ -174,8 +175,8 @@ bind = Ctrl+Super, Down, workspace, r+5 # [hidden] #! # Testing -bind = Super+Alt, f11, exec, bash -c 'RANDOM_IMAGE=$(find ~/Pictures -type f | grep -v -i "nipple" | grep -v -i "pussy" | shuf -n 1); ACTION=$(notify-send "Test notification with body image" "This notification should contain your user account image and Discord icon. Oh and here is a random image in your Pictures folder: \"Testing" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Open the random image" -A "action3=Useless button"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] -bind = Super+Alt, f12, exec, bash -c 'ACTION=$(notify-send "Test notification" "This notification should contain your user account image and Discord icon.\nFlick right to dismiss!" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Useless button" -A "action3=Cry more"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] +bind = Super+Alt, f11, exec, bash -c 'RANDOM_IMAGE=$(find ~/Pictures -type f | grep -v -i "nipple" | grep -v -i "pussy" | shuf -n 1); ACTION=$(notify-send "Test notification with body image" "This notification should contain your user account image and Discord icon. Oh and here is a random image in your Pictures folder: \"Testing" -a "Hyprland keybind" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Open the random image" -A "action3=Useless button"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] +bind = Super+Alt, f12, exec, bash -c 'ACTION=$(notify-send "Test notification" "This notification should contain your user account image and Discord icon.\nFlick right to dismiss!" -a "Discord (fake)" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Useless button" -A "action3=Cry more"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] bind = Super+Alt, Equal, exec, notify-send "Urgent notification" "Ah hell no" -u critical -a 'Hyprland keybind' # [hidden] ##! Media From cf4f7cdd2871d33e93d651ae28e01082135f6965 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 20:02:58 +0200 Subject: [PATCH 511/824] string utils: add escape html --- .../modules/common/functions/string_utils.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/functions/string_utils.js b/.config/quickshell/modules/common/functions/string_utils.js index 23b0f1f88..c22671ebf 100644 --- a/.config/quickshell/modules/common/functions/string_utils.js +++ b/.config/quickshell/modules/common/functions/string_utils.js @@ -175,4 +175,14 @@ function friendlyTimeForSeconds(seconds) { } else { return `${m}:${s.toString().padStart(2, '0')}`; } -} \ No newline at end of file +} + +function escapeHtml(str) { + if (typeof str !== 'string') return str; + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} From 60625da0672c66171766071f8cd79caa7af24730 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 20:03:05 +0200 Subject: [PATCH 512/824] format --- .config/quickshell/services/AppSearch.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/services/AppSearch.qml b/.config/quickshell/services/AppSearch.qml index 10a54f835..1178c95c5 100644 --- a/.config/quickshell/services/AppSearch.qml +++ b/.config/quickshell/services/AppSearch.qml @@ -23,6 +23,6 @@ Singleton { key: "name" }).map(r => { return r.obj.entry - }); + }); } } From a29f12549ae8415471aed6885892104a50ae23df Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 20:03:10 +0200 Subject: [PATCH 513/824] remove unused import --- .config/quickshell/services/Notifications.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/.config/quickshell/services/Notifications.qml b/.config/quickshell/services/Notifications.qml index 863a173e6..39b6fb520 100644 --- a/.config/quickshell/services/Notifications.qml +++ b/.config/quickshell/services/Notifications.qml @@ -7,7 +7,6 @@ import QtQuick import Quickshell import Quickshell.Io import Quickshell.Services.Notifications -import Qt.labs.platform /** * Provides extra features not in Quickshell.Services.Notifications: From 0b80403a4b1c1b3df2b674501483afb250c19b28 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 20:06:22 +0200 Subject: [PATCH 514/824] space --- .config/quickshell/services/AppSearch.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/services/AppSearch.qml b/.config/quickshell/services/AppSearch.qml index 1178c95c5..66676def6 100644 --- a/.config/quickshell/services/AppSearch.qml +++ b/.config/quickshell/services/AppSearch.qml @@ -12,6 +12,7 @@ Singleton { readonly property list list: Array.from(DesktopEntries.applications.values) .sort((a, b) => a.name.localeCompare(b.name)) + readonly property var preppedNames: list.map(a => ({ name: Fuzzy.prepare(`${a.name} `), entry: a From a6556f389002e0675755e77b5a3e66647541fa22 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 20:07:28 +0200 Subject: [PATCH 515/824] overview: dont close when not searching clipboard and trying to search clipboard --- .config/quickshell/modules/overview/Overview.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 8f0ca8a8d..aa09930b9 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -183,7 +183,7 @@ Scope { description: qsTr("Toggle clipboard query on overview widget") onPressed: { - if (GlobalStates.overviewOpen) { + if (GlobalStates.overviewOpen && overviewScope.dontAutoCancelSearch) { GlobalStates.overviewOpen = false; return; } From 02a3434e58ab06aa69a7685a58faaf1852a643fc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 22:52:25 +0200 Subject: [PATCH 516/824] search: add levelshtein distance based search --- .../modules/common/ConfigOptions.qml | 1 + .../modules/common/functions/levendist.js | 141 ++++++++++++++++++ .../modules/overview/SearchItem.qml | 2 +- .config/quickshell/services/AppSearch.qml | 16 +- .config/quickshell/services/Cliphist.qml | 18 ++- 5 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 .config/quickshell/modules/common/functions/levendist.js diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index f38d8f056..5a89c56f2 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -60,6 +60,7 @@ Singleton { property int nonAppResultDelay: 30 // This prevents lagging when typing property string engineBaseUrl: "https://www.google.com/search?q=" property list excludedSites: [ "quora.com" ] + property bool sloppy: false // Uses levenshtein distance based scoring instead of fuzzy sort. Very weird. property QtObject prefix: QtObject { property string action: "/" property string clipboard: ":" diff --git a/.config/quickshell/modules/common/functions/levendist.js b/.config/quickshell/modules/common/functions/levendist.js new file mode 100644 index 000000000..90180d216 --- /dev/null +++ b/.config/quickshell/modules/common/functions/levendist.js @@ -0,0 +1,141 @@ +// Original code from https://github.com/koeqaife/hyprland-material-you +// Original code license: GPLv3 +// Translated to Js from Cython with an LLM and reviewed + +function min3(a, b, c) { + return a < b && a < c ? a : b < c ? b : c; +} + +function max3(a, b, c) { + return a > b && a > c ? a : b > c ? b : c; +} + +function min2(a, b) { + return a < b ? a : b; +} + +function max2(a, b) { + return a > b ? a : b; +} + +function levenshteinDistance(s1, s2) { + let len1 = s1.length; + let len2 = s2.length; + + if (len1 === 0) return len2; + if (len2 === 0) return len1; + + if (len2 > len1) { + [s1, s2] = [s2, s1]; + [len1, len2] = [len2, len1]; + } + + let prev = new Array(len2 + 1); + let curr = new Array(len2 + 1); + + for (let j = 0; j <= len2; j++) { + prev[j] = j; + } + + for (let i = 1; i <= len1; i++) { + curr[0] = i; + for (let j = 1; j <= len2; j++) { + let cost = s1[i - 1] === s2[j - 1] ? 0 : 1; + curr[j] = min3(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost); + } + [prev, curr] = [curr, prev]; + } + + return prev[len2]; +} + +function partialRatio(shortS, longS) { + let lenS = shortS.length; + let lenL = longS.length; + let best = 0.0; + + if (lenS === 0) return 1.0; + + for (let i = 0; i <= lenL - lenS; i++) { + let sub = longS.slice(i, i + lenS); + let dist = levenshteinDistance(shortS, sub); + let score = 1.0 - (dist / lenS); + if (score > best) best = score; + } + + return best; +} + +function computeScore(s1, s2) { + if (s1 === s2) return 1.0; + + let dist = levenshteinDistance(s1, s2); + let maxLen = max2(s1.length, s2.length); + if (maxLen === 0) return 1.0; + + let full = 1.0 - (dist / maxLen); + let part = s1.length < s2.length ? partialRatio(s1, s2) : partialRatio(s2, s1); + + let score = 0.85 * full + 0.15 * part; + + if (s1 && s2 && s1[0] !== s2[0]) { + score -= 0.05; + } + + let lenDiff = Math.abs(s1.length - s2.length); + if (lenDiff >= 3) { + score -= 0.05 * lenDiff / maxLen; + } + + let commonPrefixLen = 0; + let minLen = min2(s1.length, s2.length); + for (let i = 0; i < minLen; i++) { + if (s1[i] === s2[i]) { + commonPrefixLen++; + } else { + break; + } + } + score += 0.02 * commonPrefixLen; + + if (s1.includes(s2) || s2.includes(s1)) { + score += 0.06; + } + + return Math.max(0.0, Math.min(1.0, score)); +} + +function computeTextMatchScore(s1, s2) { + if (s1 === s2) return 1.0; + + let dist = levenshteinDistance(s1, s2); + let maxLen = max2(s1.length, s2.length); + if (maxLen === 0) return 1.0; + + let full = 1.0 - (dist / maxLen); + let part = s1.length < s2.length ? partialRatio(s1, s2) : partialRatio(s2, s1); + + let score = 0.4 * full + 0.6 * part; + + let lenDiff = Math.abs(s1.length - s2.length); + if (lenDiff >= 10) { + score -= 0.02 * lenDiff / maxLen; + } + + let commonPrefixLen = 0; + let minLen = min2(s1.length, s2.length); + for (let i = 0; i < minLen; i++) { + if (s1[i] === s2[i]) { + commonPrefixLen++; + } else { + break; + } + } + score += 0.01 * commonPrefixLen; + + if (s1.includes(s2) || s2.includes(s1)) { + score += 0.2; + } + + return Math.max(0.0, Math.min(1.0, score)); +} diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index 7008ad8d9..eb2c1577a 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -113,7 +113,7 @@ RippleButton { StyledText { Layout.fillWidth: true id: nameText - textFormat: Text.PlainText // TODO: make cliphist entry highlighting working + textFormat: Text.PlainText // TODO: make cliphist entry highlighting work font.pixelSize: Appearance.font.pixelSize.normal font.family: Appearance.font.family[root.fontType] color: Appearance.m3colors.m3onSurface diff --git a/.config/quickshell/services/AppSearch.qml b/.config/quickshell/services/AppSearch.qml index 66676def6..742126e0a 100644 --- a/.config/quickshell/services/AppSearch.qml +++ b/.config/quickshell/services/AppSearch.qml @@ -1,6 +1,8 @@ pragma Singleton +import "root:/modules/common" import "root:/modules/common/functions/fuzzysort.js" as Fuzzy +import "root:/modules/common/functions/levendist.js" as Levendist import Quickshell import Quickshell.Io @@ -9,16 +11,28 @@ import Quickshell.Io */ Singleton { id: root + property bool sloppySearch: ConfigOptions?.search.sloppy ?? false + property real scoreThreshold: 0.2 readonly property list list: Array.from(DesktopEntries.applications.values) .sort((a, b) => a.name.localeCompare(b.name)) - + readonly property var preppedNames: list.map(a => ({ name: Fuzzy.prepare(`${a.name} `), entry: a })) function fuzzyQuery(search: string): var { // Idk why list doesn't work + if (root.sloppySearch) { + const results = list.map(obj => ({ + entry: obj, + score: Levendist.computeScore(obj.name.toLowerCase(), search.toLowerCase()) + })).filter(item => item.score > root.scoreThreshold) + .sort((a, b) => b.score - a.score) + return results + .map(item => item.entry) + } + return Fuzzy.go(search, preppedNames, { all: true, key: "name" diff --git a/.config/quickshell/services/Cliphist.qml b/.config/quickshell/services/Cliphist.qml index f835a5f96..a2650a19e 100644 --- a/.config/quickshell/services/Cliphist.qml +++ b/.config/quickshell/services/Cliphist.qml @@ -2,6 +2,8 @@ pragma Singleton pragma ComponentBehavior: Bound import "root:/modules/common/functions/fuzzysort.js" as Fuzzy +import "root:/modules/common/functions/levendist.js" as Levendist +import "root:/modules/common/functions/string_utils.js" as StringUtils import "root:/modules/common" import "root:/" import QtQuick @@ -10,21 +12,29 @@ import Quickshell.Io Singleton { id: root + property bool sloppySearch: ConfigOptions?.search.sloppy ?? false + property real scoreThreshold: 0.2 property list entries: [] - property string highlightPrefix: `` - property string highlightSuffix: `` readonly property var preparedEntries: entries.map(a => ({ name: Fuzzy.prepare(`${a}`), entry: a })) function fuzzyQuery(search: string): var { + if (root.sloppySearch) { + const results = entries.slice(0, 100).map(str => ({ + entry: str, + score: Levendist.computeTextMatchScore(str.toLowerCase(), search.toLowerCase()) + })).filter(item => item.score > root.scoreThreshold) + .sort((a, b) => b.score - a.score) + return results + .map(item => item.entry) + } + return Fuzzy.go(search, preparedEntries, { all: true, key: "name" }).map(r => { return r.obj.entry - // console.log(JSON.stringify(r)) - // return r.highlight(highlightPrefix, highlightSuffix); }); } From 536fb27941a0662a8be56c95f258fbba2ff46435 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 26 May 2025 23:31:31 +0200 Subject: [PATCH 517/824] search: highlight results --- .../modules/overview/SearchItem.qml | 40 ++++++++++++++++++- .../modules/overview/SearchWidget.qml | 4 ++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index eb2c1577a..72b647747 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -3,6 +3,8 @@ import "root:/" import "root:/modules/common" import "root:/modules/common/widgets" import "root:/modules/common/functions/color_utils.js" as ColorUtils +import "root:/modules/common/functions/string_utils.js" as StringUtils +import "root:/modules/common/functions/fuzzysort.js" as Fuzzy import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -14,6 +16,7 @@ import Quickshell.Hyprland RippleButton { id: root property var entry + property string query property bool entryShown: entry?.shown ?? true property string itemType: entry?.type property string itemName: entry?.name @@ -22,6 +25,39 @@ RippleButton { property string fontType: entry?.fontType ?? "main" property string itemClickActionName: entry?.clickActionName property string materialSymbol: entry?.materialSymbol ?? "" + + property string highlightPrefix: `` + property string highlightSuffix: `` + function highlightContent(content, query) { + if (!query || query.length === 0 || content == query || fontType === "monospace") + return StringUtils.escapeHtml(content); + + let contentLower = content.toLowerCase(); + let queryLower = query.toLowerCase(); + + let result = ""; + let lastIndex = 0; + let qIndex = 0; + + for (let i = 0; i < content.length && qIndex < query.length; i++) { + if (contentLower[i] === queryLower[qIndex]) { + // Add non-highlighted part (escaped) + if (i > lastIndex) + result += StringUtils.escapeHtml(content.slice(lastIndex, i)); + // Add highlighted character (escaped) + result += root.highlightPrefix + StringUtils.escapeHtml(content[i]) + root.highlightSuffix; + lastIndex = i + 1; + qIndex++; + } + } + // Add the rest of the string (escaped) + if (lastIndex < content.length) + result += StringUtils.escapeHtml(content.slice(lastIndex)); + + return result; + } + + property string displayContent: highlightContent(root.itemName, root.query) visible: root.entryShown property int horizontalMargin: 10 @@ -113,13 +149,13 @@ RippleButton { StyledText { Layout.fillWidth: true id: nameText - textFormat: Text.PlainText // TODO: make cliphist entry highlighting work + textFormat: Text.StyledText // RichText also works, but StyledText ensures elide work font.pixelSize: Appearance.font.pixelSize.normal font.family: Appearance.font.family[root.fontType] color: Appearance.m3colors.m3onSurface horizontalAlignment: Text.AlignLeft elide: Text.ElideRight - text: `${root.itemName}` + text: `${root.displayContent}` } } diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 1af0c6b9a..419c0671d 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -396,7 +396,11 @@ Item { // Wrapper } delegate: SearchItem { // The selectable item for each search result + required property var modelData entry: modelData + query: root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard) ? + root.searchingText.slice(ConfigOptions.search.prefix.clipboard.length) : + root.searchingText; } } From 80203c17c1c6c635e807aee2e07c5b0421c68cd1 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 00:00:00 +0200 Subject: [PATCH 518/824] search: disable expand anim for clipboard showup --- .../quickshell/modules/overview/Overview.qml | 1 + .../modules/overview/SearchWidget.qml | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index aa09930b9..7d671b779 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -54,6 +54,7 @@ Scope { target: GlobalStates function onOverviewOpenChanged() { if (!GlobalStates.overviewOpen) { + searchWidget.disableExpandAnimation() overviewScope.dontAutoCancelSearch = false; } else { if (!overviewScope.dontAutoCancelSearch) { diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 419c0671d..d649a42b5 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -29,9 +29,15 @@ Item { // Wrapper onShowResultsChanged: { lastQueryWasClipboard = false; } + + function disableExpandAnimation() { + searchWidthBehavior.enabled = false; + } + function cancelSearch() { searchInput.selectAll() root.searchingText = "" + searchWidthBehavior.enabled = true; } function setSearchingText(text) { @@ -226,10 +232,15 @@ Item { // Wrapper implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth Behavior on implicitWidth { + id: searchWidthBehavior + onEnabledChanged: { + console.log("Search width behavior enabled:", enabled); + } + enabled: false NumberAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + duration: 300 + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } From 6d036e972d144c09790c736b0d63375f6a7cd9d2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 09:02:19 +0200 Subject: [PATCH 519/824] search: change highlighting to underline --- .config/quickshell/modules/overview/SearchItem.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index 72b647747..3fad2ae3e 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -26,8 +26,8 @@ RippleButton { property string itemClickActionName: entry?.clickActionName property string materialSymbol: entry?.materialSymbol ?? "" - property string highlightPrefix: `` - property string highlightSuffix: `` + property string highlightPrefix: `` + property string highlightSuffix: `` function highlightContent(content, query) { if (!query || query.length === 0 || content == query || fontType === "monospace") return StringUtils.escapeHtml(content); From 54224557f03d2ea3a0b41139e85a4a106b09db6d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 09:02:44 +0200 Subject: [PATCH 520/824] generalize the `:` thing in llm apis --- .config/quickshell/services/Ai.qml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index 757a2a1e3..4b0c4928a 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -391,9 +391,7 @@ Singleton { cleanData = cleanData.slice(5).trim(); } // console.log("Clean data: ", cleanData); - if (!cleanData || - cleanData === ": OPENROUTER PROCESSING" - ) return; + if (!cleanData || cleanData.startsWith(":")) return; if (cleanData === "[DONE]") { requester.message.done = true; From b78d489dfdec166b87c3d3001e66d7e8d0184a90 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 09:02:54 +0200 Subject: [PATCH 521/824] update soramane's dotfiles license --- .config/quickshell/services/Brightness.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/services/Brightness.qml b/.config/quickshell/services/Brightness.qml index 132f0eda9..aba664aa9 100644 --- a/.config/quickshell/services/Brightness.qml +++ b/.config/quickshell/services/Brightness.qml @@ -2,7 +2,7 @@ pragma Singleton pragma ComponentBehavior: Bound // From https://github.com/caelestia-dots/shell/ (`quickshell` branch) with modifications. -// It does not have a license, but the author has given permission. +// License: GPLv3 import Quickshell import Quickshell.Io From a76a9bed3b2a0fcf079db9e144a06f5499a07bdb Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 09:16:01 +0200 Subject: [PATCH 522/824] adjust notif action button position --- .../common/widgets/NotificationItem.qml | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationItem.qml b/.config/quickshell/modules/common/widgets/NotificationItem.qml index a123ad042..267a2d041 100644 --- a/.config/quickshell/modules/common/widgets/NotificationItem.qml +++ b/.config/quickshell/modules/common/widgets/NotificationItem.qml @@ -218,6 +218,26 @@ Item { // Notification item area id: actionRowLayout Layout.alignment: Qt.AlignBottom + NotificationActionButton { + Layout.fillWidth: true + buttonText: qsTr("Close") + urgency: notificationObject.urgency + implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) : + (contentItem.implicitWidth + leftPadding + rightPadding) + + onClicked: { + root.destroyWithAnimation() + } + + contentItem: MaterialSymbol { + iconSize: Appearance.font.pixelSize.large + horizontalAlignment: Text.AlignHCenter + color: (notificationObject.urgency == NotificationUrgency.Critical) ? + Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface + text: "close" + } + } + Repeater { id: actionRepeater model: notificationObject.actions @@ -261,26 +281,6 @@ Item { // Notification item area text: "content_copy" } } - - NotificationActionButton { - Layout.fillWidth: true - buttonText: qsTr("Close") - urgency: notificationObject.urgency - implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) : - (contentItem.implicitWidth + leftPadding + rightPadding) - - onClicked: { - root.destroyWithAnimation() - } - - contentItem: MaterialSymbol { - iconSize: Appearance.font.pixelSize.large - horizontalAlignment: Text.AlignHCenter - color: (notificationObject.urgency == NotificationUrgency.Critical) ? - Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface - text: "close" - } - } } } From 4ea6474cb6d9db82282f0ceeca6c853fffc669a4 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 09:16:11 +0200 Subject: [PATCH 523/824] make search result text smaller --- .config/quickshell/modules/overview/SearchItem.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index 3fad2ae3e..24e3affb5 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -141,7 +141,7 @@ RippleButton { Layout.alignment: Qt.AlignVCenter spacing: 0 StyledText { - font.pixelSize: Appearance.font.pixelSize.small + font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colSubtext visible: root.itemType && root.itemType != qsTr("App") text: root.itemType @@ -150,7 +150,7 @@ RippleButton { Layout.fillWidth: true id: nameText textFormat: Text.StyledText // RichText also works, but StyledText ensures elide work - font.pixelSize: Appearance.font.pixelSize.normal + font.pixelSize: Appearance.font.pixelSize.small font.family: Appearance.font.family[root.fontType] color: Appearance.m3colors.m3onSurface horizontalAlignment: Text.AlignLeft From 8ede97a2786c1d1220797f35f022a7a14f4a5d47 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 09:40:26 +0200 Subject: [PATCH 524/824] replace multieffect shadows with rectangularshadow ones --- .../modules/cheatsheet/Cheatsheet.qml | 19 +- .../quickshell/modules/common/Appearance.qml | 2 +- .../common/widgets/NotificationGroup.qml | 19 +- .../modules/mediaControls/PlayerControl.qml | 297 +++++++++--------- .../onScreenDisplay/OsdValueIndicator.qml | 17 +- .../modules/overview/OverviewWidget.qml | 19 +- .../modules/overview/SearchWidget.qml | 16 +- .../modules/sidebarLeft/SidebarLeft.qml | 17 +- .../modules/sidebarLeft/anime/BooruImage.qml | 25 +- .../modules/sidebarRight/SidebarRight.qml | 171 +++++----- .../modules/sidebarRight/todo/TodoWidget.qml | 17 +- 11 files changed, 293 insertions(+), 326 deletions(-) diff --git a/.config/quickshell/modules/cheatsheet/Cheatsheet.qml b/.config/quickshell/modules/cheatsheet/Cheatsheet.qml index f7a69266e..83af1f004 100644 --- a/.config/quickshell/modules/cheatsheet/Cheatsheet.qml +++ b/.config/quickshell/modules/cheatsheet/Cheatsheet.qml @@ -55,7 +55,16 @@ Scope { // Scope } } + // Background + RectangularShadow { + anchors.fill: cheatsheetBackground + radius: cheatsheetBackground.radius + blur: 1.2 * Appearance.sizes.elevationMargin + spread: 1 + color: Appearance.colors.colShadow + cached: true + } Rectangle { id: cheatsheetBackground anchors.centerIn: parent @@ -65,16 +74,6 @@ Scope { // Scope implicitWidth: cheatsheetColumnLayout.implicitWidth + padding * 2 implicitHeight: cheatsheetColumnLayout.implicitHeight + padding * 2 - layer.enabled: true - layer.effect: MultiEffect { - source: cheatsheetBackground - anchors.fill: cheatsheetBackground - shadowEnabled: true - shadowVerticalOffset: 1 - shadowColor: Appearance.colors.colShadow - shadowBlur: 0.5 - } - Keys.onPressed: (event) => { // Esc to close if (event.key === Qt.Key_Escape) { cheatsheetRoot.hide() diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 940dd26ea..dcb3d2221 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -128,7 +128,7 @@ Singleton { property color colTooltip: "#3C4043" // m3colors.m3inverseSurface in the specs, but the m3 website actually uses this color property color colOnTooltip: "#F8F9FA" // m3colors.m3inverseOnSurface in the specs, but the m3 website actually uses this color property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5) - property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.75) + property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.85) } rounding: QtObject { diff --git a/.config/quickshell/modules/common/widgets/NotificationGroup.qml b/.config/quickshell/modules/common/widgets/NotificationGroup.qml index 62677ae10..14669e99b 100644 --- a/.config/quickshell/modules/common/widgets/NotificationGroup.qml +++ b/.config/quickshell/modules/common/widgets/NotificationGroup.qml @@ -106,7 +106,14 @@ Item { // Notification group area } - + RectangularShadow { + visible: popup + anchors.fill: background + radius: background.radius + blur: 1.2 * Appearance.sizes.elevationMargin + spread: 1 + color: Appearance.colors.colShadow + } Rectangle { // Background of the notification id: background anchors.left: parent.left @@ -134,16 +141,6 @@ Item { // Notification group area animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } - layer.enabled: true - layer.effect: MultiEffect { - source: background - anchors.fill: background - shadowEnabled: popup - shadowColor: Appearance.colors.colShadow - shadowVerticalOffset: 1 - shadowBlur: 0.5 - } - RowLayout { // Left column for icon, right column for content id: row anchors.top: parent.top diff --git a/.config/quickshell/modules/mediaControls/PlayerControl.qml b/.config/quickshell/modules/mediaControls/PlayerControl.qml index bdafb6a61..5c770192d 100644 --- a/.config/quickshell/modules/mediaControls/PlayerControl.qml +++ b/.config/quickshell/modules/mediaControls/PlayerControl.qml @@ -74,12 +74,6 @@ Item { // Player instance id: coverArtDownloader property string targetFile: playerController.artUrl command: [ "bash", "-c", `[ -f ${artFilePath} ] || curl -sSL '${targetFile}' -o '${artFilePath}'` ] - stdout: SplitParser { - onRead: data => { - // console.log("Color quantizer output:", data) - playerController.artDominantColor = "#" + data - } - } onExited: (exitCode, exitStatus) => { colorQuantizer.targetFile = playerController.artUrl // Yes this binding break is intentional colorQuantizer.running = true @@ -93,7 +87,6 @@ Item { // Player instance command: [ "sh", "-c", `magick '${targetFile}' -scale 1x1\\! -format '%[fx:int(255*r+.5)],%[fx:int(255*g+.5)],%[fx:int(255*b+.5)]' info: | sed 's/,/\\n/g' | xargs -L 1 printf '%02x' ; echo` ] stdout: SplitParser { onRead: data => { - // console.log("Color quantizer output:", data) playerController.artDominantColor = "#" + data } } @@ -115,190 +108,182 @@ Item { // Player instance property color colOnSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3onSecondaryContainer, artDominantColor, 0.2) } - Rectangle { // Background wrapper with shadow - id: backgroundShadowLayer + + RectangularShadow { + anchors.fill: background + radius: background.radius + blur: 1.2 * Appearance.sizes.elevationMargin + spread: 1 + color: Appearance.colors.colShadow + cached: true + } + Rectangle { // Background + id: background anchors.fill: parent anchors.margins: Appearance.sizes.elevationMargin color: blendedColors.colLayer0 radius: root.popupRounding layer.enabled: true - layer.effect: MultiEffect { - source: backgroundShadowLayer - anchors.fill: backgroundShadowLayer - shadowEnabled: true - shadowColor: Appearance.colors.colShadow - shadowVerticalOffset: 1 - shadowBlur: 0.5 + layer.effect: OpacityMask { + maskSource: Rectangle { + width: background.width + height: background.height + radius: background.radius + } } - Rectangle { // Background - id: background + Image { + id: blurredArt anchors.fill: parent - color: blendedColors.colLayer0 - radius: root.popupRounding + visible: true + source: playerController.downloaded ? Qt.resolvedUrl(artFilePath) : "" + sourceSize.width: background.width + sourceSize.height: background.height + fillMode: Image.PreserveAspectCrop + cache: false + antialiasing: true + asynchronous: true layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: background.width - height: background.height - radius: background.radius - } + layer.effect: MultiEffect { + source: blurredArt + anchors.fill: blurredArt + saturation: 0.2 + blurEnabled: true + blurMax: 100 + blur: 1 } - Image { - id: blurredArt + Rectangle { anchors.fill: parent - visible: true - source: playerController.downloaded ? Qt.resolvedUrl(artFilePath) : "" - sourceSize.width: background.width - sourceSize.height: background.height - fillMode: Image.PreserveAspectCrop - cache: false - antialiasing: true - asynchronous: true + color: ColorUtils.transparentize(blendedColors.colLayer0, 0.25) + radius: root.popupRounding + } + } + + RowLayout { + anchors.fill: parent + anchors.margins: root.contentPadding + spacing: 15 + + Rectangle { // Art background + id: artBackground + Layout.fillHeight: true + implicitWidth: height + radius: root.artRounding + color: blendedColors.colLayer1 layer.enabled: true - layer.effect: MultiEffect { - source: blurredArt - anchors.fill: blurredArt - saturation: 0.2 - blurEnabled: true - blurMax: 100 - blur: 1 + layer.effect: OpacityMask { + maskSource: Rectangle { + width: artBackground.width + height: artBackground.height + radius: artBackground.radius + } } - Rectangle { + Image { // Art image + id: mediaArt + property int size: parent.height anchors.fill: parent - color: ColorUtils.transparentize(blendedColors.colLayer0, 0.25) - radius: root.popupRounding + + source: playerController.downloaded ? Qt.resolvedUrl(artFilePath) : "" + fillMode: Image.PreserveAspectCrop + cache: false + antialiasing: true + asynchronous: true + + width: size + height: size + sourceSize.width: size + sourceSize.height: size } } - RowLayout { - anchors.fill: parent - anchors.margins: root.contentPadding - spacing: 15 + ColumnLayout { // Info & controls + Layout.fillHeight: true + spacing: 2 - Rectangle { // Art background - id: artBackground - Layout.fillHeight: true - implicitWidth: height - radius: root.artRounding - color: blendedColors.colLayer1 - - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: artBackground.width - height: artBackground.height - radius: artBackground.radius - } - } - - Image { // Art image - id: mediaArt - property int size: parent.height - anchors.fill: parent - - source: playerController.downloaded ? Qt.resolvedUrl(artFilePath) : "" - fillMode: Image.PreserveAspectCrop - cache: false - antialiasing: true - asynchronous: true - - width: size - height: size - sourceSize.width: size - sourceSize.height: size - } + StyledText { + id: trackTitle + Layout.fillWidth: true + font.pixelSize: Appearance.font.pixelSize.large + color: blendedColors.colOnLayer0 + elide: Text.ElideRight + text: StringUtils.cleanMusicTitle(playerController.player?.trackTitle) || "Untitled" } - - ColumnLayout { // Info & controls - Layout.fillHeight: true - spacing: 2 + StyledText { + id: trackArtist + Layout.fillWidth: true + font.pixelSize: Appearance.font.pixelSize.smaller + color: blendedColors.colSubtext + elide: Text.ElideRight + text: playerController.player?.trackArtist + } + Item { Layout.fillHeight: true } + Item { + Layout.fillWidth: true + implicitHeight: trackTime.implicitHeight + sliderRow.implicitHeight StyledText { - id: trackTitle - Layout.fillWidth: true - font.pixelSize: Appearance.font.pixelSize.large - color: blendedColors.colOnLayer0 - elide: Text.ElideRight - text: StringUtils.cleanMusicTitle(playerController.player?.trackTitle) || "Untitled" - } - StyledText { - id: trackArtist - Layout.fillWidth: true - font.pixelSize: Appearance.font.pixelSize.smaller + id: trackTime + anchors.bottom: sliderRow.top + anchors.bottomMargin: 5 + anchors.left: parent.left + font.pixelSize: Appearance.font.pixelSize.small color: blendedColors.colSubtext elide: Text.ElideRight - text: playerController.player?.trackArtist + text: `${StringUtils.friendlyTimeForSeconds(playerController.player?.position)} / ${StringUtils.friendlyTimeForSeconds(playerController.player?.length)}` } - Item { Layout.fillHeight: true } - Item { - Layout.fillWidth: true - implicitHeight: trackTime.implicitHeight + sliderRow.implicitHeight - - StyledText { - id: trackTime - anchors.bottom: sliderRow.top - anchors.bottomMargin: 5 - anchors.left: parent.left - font.pixelSize: Appearance.font.pixelSize.small - color: blendedColors.colSubtext - elide: Text.ElideRight - text: `${StringUtils.friendlyTimeForSeconds(playerController.player?.position)} / ${StringUtils.friendlyTimeForSeconds(playerController.player?.length)}` + RowLayout { + id: sliderRow + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right } - RowLayout { - id: sliderRow - anchors { - bottom: parent.bottom - left: parent.left - right: parent.right - } - TrackChangeButton { - iconName: "skip_previous" - onClicked: playerController.player?.previous() - } - StyledProgressBar { - id: slider - Layout.fillWidth: true - highlightColor: blendedColors.colPrimary - trackColor: blendedColors.colSecondaryContainer - value: playerController.player?.position / playerController.player?.length - } - TrackChangeButton { - iconName: "skip_next" - onClicked: playerController.player?.next() - } + TrackChangeButton { + iconName: "skip_previous" + onClicked: playerController.player?.previous() } + StyledProgressBar { + id: slider + Layout.fillWidth: true + highlightColor: blendedColors.colPrimary + trackColor: blendedColors.colSecondaryContainer + value: playerController.player?.position / playerController.player?.length + } + TrackChangeButton { + iconName: "skip_next" + onClicked: playerController.player?.next() + } + } - RippleButton { - id: playPauseButton - anchors.right: parent.right - anchors.bottom: sliderRow.top - anchors.bottomMargin: 5 - property real size: 44 - implicitWidth: size - implicitHeight: size - onClicked: playerController.player.togglePlaying(); + RippleButton { + id: playPauseButton + anchors.right: parent.right + anchors.bottom: sliderRow.top + anchors.bottomMargin: 5 + property real size: 44 + implicitWidth: size + implicitHeight: size + onClicked: playerController.player.togglePlaying(); - buttonRadius: playerController.player?.isPlaying ? Appearance?.rounding.normal : size / 2 - colBackground: playerController.player?.isPlaying ? blendedColors.colPrimary : blendedColors.colSecondaryContainer - colBackgroundHover: playerController.player?.isPlaying ? blendedColors.colPrimaryHover : blendedColors.colSecondaryContainerHover - colRipple: playerController.player?.isPlaying ? blendedColors.colPrimaryActive : blendedColors.colSecondaryContainerActive + buttonRadius: playerController.player?.isPlaying ? Appearance?.rounding.normal : size / 2 + colBackground: playerController.player?.isPlaying ? blendedColors.colPrimary : blendedColors.colSecondaryContainer + colBackgroundHover: playerController.player?.isPlaying ? blendedColors.colPrimaryHover : blendedColors.colSecondaryContainerHover + colRipple: playerController.player?.isPlaying ? blendedColors.colPrimaryActive : blendedColors.colSecondaryContainerActive - contentItem: MaterialSymbol { - iconSize: Appearance.font.pixelSize.huge - fill: 1 - horizontalAlignment: Text.AlignHCenter - color: playerController.player?.isPlaying ? blendedColors.colOnPrimary : blendedColors.colOnSecondaryContainer - text: playerController.player?.isPlaying ? "pause" : "play_arrow" + contentItem: MaterialSymbol { + iconSize: Appearance.font.pixelSize.huge + fill: 1 + horizontalAlignment: Text.AlignHCenter + color: playerController.player?.isPlaying ? blendedColors.colOnPrimary : blendedColors.colOnSecondaryContainer + text: playerController.player?.isPlaying ? "pause" : "play_arrow" - Behavior on color { - animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) - } + Behavior on color { + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } diff --git a/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml b/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml index 58d4ac5ca..29829b43d 100644 --- a/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml +++ b/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml @@ -25,6 +25,13 @@ Item { implicitWidth: Appearance.sizes.osdWidth implicitHeight: valueIndicator.implicitHeight + RectangularShadow { + anchors.fill: valueIndicator + radius: valueIndicator.radius + blur: 1.2 * Appearance.sizes.elevationMargin + spread: 1 + color: Appearance.colors.colShadow + } WrapperRectangle { id: valueIndicator anchors.fill: parent @@ -32,16 +39,6 @@ Item { color: Appearance.colors.colLayer0 implicitWidth: valueRow.implicitWidth - layer.enabled: true - layer.effect: MultiEffect { - source: valueIndicator - anchors.fill: valueIndicator - shadowEnabled: true - shadowColor: Appearance.colors.colShadow - shadowVerticalOffset: 1 - shadowBlur: 0.5 - } - RowLayout { // Icon on the left, stuff on the right id: valueRow Layout.margins: 10 diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index f4498408e..85d83242a 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -49,7 +49,14 @@ Item { property Component windowComponent: OverviewWindow {} property list windowWidgets: [] - Rectangle { + RectangularShadow { // Background shadow + anchors.fill: overviewBackground + radius: overviewBackground.radius + blur: 1.2 * Appearance.sizes.elevationMargin + spread: 1 + color: Appearance.colors.colShadow + } + Rectangle { // Background id: overviewBackground anchors.fill: parent @@ -59,16 +66,6 @@ Item { color: Appearance.colors.colLayer0 radius: Appearance.rounding.screenRounding * root.scale + 5 * 2 - layer.enabled: true - layer.effect: MultiEffect { - source: overviewBackground - anchors.fill: overviewBackground - shadowEnabled: true - shadowColor: Appearance.colors.colShadow - shadowVerticalOffset: 1 - shadowBlur: 0.5 - } - ColumnLayout { id: workspaceColumnLayout diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index d649a42b5..38ee4e2ee 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -175,6 +175,13 @@ Item { // Wrapper } } + RectangularShadow { // Background shadow + anchors.fill: searchWidgetContent + radius: searchWidgetContent.radius + blur: 1.2 * Appearance.sizes.elevationMargin + spread: 1 + color: Appearance.colors.colShadow + } Rectangle { // Background id: searchWidgetContent anchors.centerIn: parent @@ -182,15 +189,6 @@ Item { // Wrapper implicitHeight: columnLayout.implicitHeight radius: Appearance.rounding.large color: Appearance.colors.colLayer0 - layer.enabled: true - layer.effect: MultiEffect { - source: searchWidgetContent - anchors.fill: searchWidgetContent - shadowEnabled: true - shadowColor: Appearance.colors.colShadow - shadowVerticalOffset: 1 - shadowBlur: 0.5 - } ColumnLayout { id: columnLayout diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index 6963c50ab..a5bb59d6f 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -65,6 +65,13 @@ Scope { // Scope } // Background + RectangularShadow { // Background shadow + anchors.fill: sidebarLeftBackground + radius: sidebarLeftBackground.radius + blur: 1.2 * Appearance.sizes.elevationMargin + spread: 1 + color: Appearance.colors.colShadow + } Rectangle { id: sidebarLeftBackground @@ -78,16 +85,6 @@ Scope { // Scope radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 focus: sidebarRoot.visible - layer.enabled: true - layer.effect: MultiEffect { - source: sidebarLeftBackground - anchors.fill: sidebarLeftBackground - shadowEnabled: true - shadowColor: Appearance.colors.colShadow - shadowVerticalOffset: 1 - shadowBlur: 0.5 - } - Behavior on width { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index f9e9f4eb8..29768cfb0 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -43,6 +43,10 @@ Button { } } + StyledToolTip { + content: `${StringUtils.wordWrap(root.imageData.tags, root.maxTagStringLineLength)}` + } + padding: 0 implicitWidth: root.rowHeight * modelData.aspect_ratio implicitHeight: root.rowHeight @@ -97,10 +101,6 @@ Button { colBackgroundHover: ColorUtils.transparentize(ColorUtils.mix(Appearance.m3colors.m3surface, Appearance.m3colors.m3onSurface, 0.8), 0.2) colRipple: ColorUtils.transparentize(ColorUtils.mix(Appearance.m3colors.m3surface, Appearance.m3colors.m3onSurface, 0.6), 0.1) - StyledToolTip { - content: `${StringUtils.wordWrap(root.imageData.tags, root.maxTagStringLineLength)}\n${qsTr("Click for options")}` - } - contentItem: MaterialSymbol { horizontalAlignment: Text.AlignHCenter iconSize: Appearance.font.pixelSize.large @@ -124,6 +124,13 @@ Button { width: contextMenu.width height: contextMenu.height + RectangularShadow { // Background shadow + anchors.fill: contextMenu + radius: contextMenu.radius + blur: 1.2 * Appearance.sizes.elevationMargin + spread: 1 + color: Appearance.colors.colShadow + } Rectangle { id: contextMenu anchors.centerIn: parent @@ -134,16 +141,6 @@ Button { implicitHeight: contextMenuColumnLayout.implicitHeight + radius * 2 implicitWidth: contextMenuColumnLayout.implicitWidth - layer.enabled: true - layer.effect: MultiEffect { - source: contextMenu - anchors.fill: contextMenu - shadowEnabled: true - shadowColor: Appearance.colors.colShadow - shadowVerticalOffset: 1 - shadowBlur: 0.5 - } - Behavior on opacity { NumberAnimation { duration: Appearance.animation.elementMoveFast.duration diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index e509efc67..d2dfa34a8 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -56,109 +56,112 @@ Scope { width: sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2 height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 - sourceComponent: Rectangle { - id: sidebarRightBackground + sourceComponent: Item { + implicitHeight: sidebarRightBackground.implicitHeight + implicitWidth: sidebarRightBackground.implicitWidth - implicitHeight: parent.height - Appearance.sizes.hyprlandGapsOut * 2 - implicitWidth: sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2 - color: Appearance.colors.colLayer0 - radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 - - layer.enabled: true - layer.effect: MultiEffect { - source: sidebarRightBackground + RectangularShadow { // Background shadow anchors.fill: sidebarRightBackground - shadowEnabled: true - shadowColor: Appearance.colors.colShadow - shadowVerticalOffset: 1 - shadowBlur: 0.5 + radius: sidebarRightBackground.radius + blur: 1.2 * Appearance.sizes.elevationMargin + spread: 1 + color: Appearance.colors.colShadow } + Rectangle { + id: sidebarRightBackground - Keys.onPressed: (event) => { - if (event.key === Qt.Key_Escape) { - sidebarRoot.hide(); - } - } - - - ColumnLayout { - spacing: sidebarPadding anchors.fill: parent - anchors.margins: sidebarPadding + implicitHeight: parent.height - Appearance.sizes.hyprlandGapsOut * 2 + implicitWidth: sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2 + color: Appearance.colors.colLayer0 + radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 - RowLayout { - Layout.fillHeight: false - spacing: 10 - Layout.margins: 10 - Layout.topMargin: 5 - Layout.bottomMargin: 0 + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Escape) { + sidebarRoot.hide(); + } + } - Item { - implicitWidth: distroIcon.width - implicitHeight: distroIcon.height - CustomIcon { - id: distroIcon - width: 25 - height: 25 - source: SystemInfo.distroIcon + + ColumnLayout { + spacing: sidebarPadding + anchors.fill: parent + anchors.margins: sidebarPadding + + RowLayout { + Layout.fillHeight: false + spacing: 10 + Layout.margins: 10 + Layout.topMargin: 5 + Layout.bottomMargin: 0 + + Item { + implicitWidth: distroIcon.width + implicitHeight: distroIcon.height + CustomIcon { + id: distroIcon + width: 25 + height: 25 + source: SystemInfo.distroIcon + } + ColorOverlay { + anchors.fill: distroIcon + source: distroIcon + color: Appearance.colors.colOnLayer0 + } } - ColorOverlay { - anchors.fill: distroIcon - source: distroIcon + + StyledText { + font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.colors.colOnLayer0 + text: StringUtils.format(qsTr("Uptime: {0}"), DateTime.uptime) + textFormat: Text.MarkdownText + } + + Item { + Layout.fillWidth: true + } + + QuickToggleButton { + toggled: false + buttonIcon: "power_settings_new" + onClicked: { + Hyprland.dispatch("global quickshell:sessionOpen") + } + StyledToolTip { + content: qsTr("Session") + } } } - StyledText { - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.colors.colOnLayer0 - text: StringUtils.format(qsTr("Uptime: {0}"), DateTime.uptime) - textFormat: Text.MarkdownText + ButtonGroup { + Layout.alignment: Qt.AlignHCenter + spacing: 5 + padding: 5 + color: Appearance.colors.colLayer1 + + NetworkToggle {} + BluetoothToggle {} + NightLight {} + GameMode {} + IdleInhibitor {} } - Item { + // Center widget group + CenterWidgetGroup { + focus: sidebarRoot.visible + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: true Layout.fillWidth: true } - QuickToggleButton { - toggled: false - buttonIcon: "power_settings_new" - onClicked: { - Hyprland.dispatch("global quickshell:sessionOpen") - } - StyledToolTip { - content: qsTr("Session") - } + BottomWidgetGroup { + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: false + Layout.fillWidth: true + Layout.preferredHeight: implicitHeight } } - - ButtonGroup { - Layout.alignment: Qt.AlignHCenter - spacing: 5 - padding: 5 - color: Appearance.colors.colLayer1 - - NetworkToggle {} - BluetoothToggle {} - NightLight {} - GameMode {} - IdleInhibitor {} - } - - // Center widget group - CenterWidgetGroup { - focus: sidebarRoot.visible - Layout.alignment: Qt.AlignHCenter - Layout.fillHeight: true - Layout.fillWidth: true - } - - BottomWidgetGroup { - Layout.alignment: Qt.AlignHCenter - Layout.fillHeight: false - Layout.fillWidth: true - Layout.preferredHeight: implicitHeight - } } } } diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index 96e96a5a7..e4f441b50 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -152,6 +152,13 @@ Item { } // + FAB + RectangularShadow { // Background shadow + anchors.fill: fabButton + radius: Appearance.rounding.normal + blur: 1.2 * Appearance.sizes.elevationMargin + spread: 1 + color: Appearance.colors.colShadow + } Button { id: fabButton anchors.right: parent.right @@ -173,16 +180,6 @@ Item { Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } - - layer.enabled: true - layer.effect: MultiEffect { - source: fabBackground - anchors.fill: fabBackground - shadowEnabled: true - shadowColor: Appearance.colors.colShadow - shadowBlur: 0.6 - shadowVerticalOffset: fabButton.hovered ? 4 : 2 - } } contentItem: MaterialSymbol { From 4e93bd03070a58fac6a8560a991d72e7e622a657 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 11:22:06 +0200 Subject: [PATCH 525/824] notif add link click --- .../modules/common/widgets/NotificationItem.qml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.config/quickshell/modules/common/widgets/NotificationItem.qml b/.config/quickshell/modules/common/widgets/NotificationItem.qml index 267a2d041..26286290c 100644 --- a/.config/quickshell/modules/common/widgets/NotificationItem.qml +++ b/.config/quickshell/modules/common/widgets/NotificationItem.qml @@ -195,6 +195,17 @@ Item { // Notification item area textFormat: Text.RichText text: `` + `${notificationObject.body.replace(/\n/g, "
")}` + + onLinkActivated: (link) => { + Qt.openUrlExternally(link) + Hyprland.dispatch("global quickshell:sidebarRightClose") + } + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton // Only for hover + hoverEnabled: true + cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor + } } Flickable { // Notification actions From a759ec4e1cb40b311700709bfd1e7a46a00bca95 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 11:22:19 +0200 Subject: [PATCH 526/824] newline --- .config/quickshell/modules/common/widgets/RippleButton.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/widgets/RippleButton.qml b/.config/quickshell/modules/common/widgets/RippleButton.qml index e6ca5a3fd..e80a319c9 100644 --- a/.config/quickshell/modules/common/widgets/RippleButton.qml +++ b/.config/quickshell/modules/common/widgets/RippleButton.qml @@ -160,4 +160,4 @@ Button { contentItem: StyledText { text: root.buttonText } -} \ No newline at end of file +} From d4e8ca51a4be4dee7f76cc26fcf0a6cb4047afeb Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 11:23:50 +0200 Subject: [PATCH 527/824] search: change anchor declaration --- .config/quickshell/modules/overview/Overview.qml | 1 - .config/quickshell/modules/overview/SearchItem.qml | 2 -- .config/quickshell/modules/overview/SearchWidget.qml | 12 +++++------- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 7d671b779..1f1da52c5 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -100,7 +100,6 @@ Scope { SearchWidget { id: searchWidget - panelWindow: root Layout.alignment: Qt.AlignHCenter onSearchingTextChanged: (text) => { root.searchingText = searchingText diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index 24e3affb5..12677ba5f 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -65,8 +65,6 @@ RippleButton { property int buttonVerticalPadding: 5 property bool keyboardDown: false - anchors.left: parent?.left - anchors.right: parent?.right implicitHeight: rowLayout.implicitHeight + root.buttonVerticalPadding * 2 implicitWidth: rowLayout.implicitWidth + root.buttonHorizontalPadding * 2 buttonRadius: Appearance.rounding.normal diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 38ee4e2ee..fb363e409 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -15,7 +15,6 @@ import Quickshell.Hyprland Item { // Wrapper id: root - required property var panelWindow readonly property string xdgConfigHome: XdgDirectories.config property string searchingText: "" property bool showResults: searchingText != "" @@ -195,7 +194,7 @@ Item { // Wrapper anchors.centerIn: parent spacing: 0 - clip: true + // clip: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { @@ -218,7 +217,7 @@ Item { // Wrapper TextField { // Search box id: searchInput - focus: root.panelWindow.visible || GlobalStates.overviewOpen + focus: GlobalStates.overviewOpen Layout.rightMargin: 15 padding: 15 renderType: Text.NativeRendering @@ -231,9 +230,6 @@ Item { // Wrapper Behavior on implicitWidth { id: searchWidthBehavior - onEnabledChanged: { - console.log("Search width behavior enabled:", enabled); - } enabled: false NumberAnimation { duration: 300 @@ -276,7 +272,7 @@ Item { // Wrapper visible: root.showResults Layout.fillWidth: true implicitHeight: Math.min(600, appResults.contentHeight + topMargin + bottomMargin) - clip: true + // clip: true topMargin: 10 bottomMargin: 10 spacing: 0 @@ -406,6 +402,8 @@ Item { // Wrapper delegate: SearchItem { // The selectable item for each search result required property var modelData + anchors.left: parent?.left + anchors.right: parent?.right entry: modelData query: root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard) ? root.searchingText.slice(ConfigOptions.search.prefix.clipboard.length) : From 5402893c16b666459decc371288d8dd2ee906728 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 19:34:22 +0200 Subject: [PATCH 528/824] overview: always show again --- .config/quickshell/modules/overview/Overview.qml | 4 ++-- .config/quickshell/modules/overview/SearchItem.qml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 1f1da52c5..714e1d7ab 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -22,11 +22,11 @@ Scope { readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor.id) screen: modelData - visible: GlobalStates.overviewOpen + visible: true WlrLayershell.namespace: "quickshell:overview" WlrLayershell.layer: WlrLayer.Overlay - // WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None + WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None color: "transparent" mask: Region { diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index 12677ba5f..eb1d7ed29 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -168,4 +168,4 @@ RippleButton { text: root.itemClickActionName } } -} \ No newline at end of file +} From 7bd910bb44d095a6a4fa31fe6300deb4cff83df0 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 20:09:46 +0200 Subject: [PATCH 529/824] refractor favicon --- .../modules/common/widgets/Favicon.qml | 59 +++++++++++++++++++ .../quickshell/modules/sidebarLeft/AiChat.qml | 2 - .../modules/sidebarLeft/aiChat/AiMessage.qml | 2 - .../aiChat/AnnotationSourceButton.qml | 58 ++++++------------ 4 files changed, 77 insertions(+), 44 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/Favicon.qml diff --git a/.config/quickshell/modules/common/widgets/Favicon.qml b/.config/quickshell/modules/common/widgets/Favicon.qml new file mode 100644 index 000000000..c7133b089 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/Favicon.qml @@ -0,0 +1,59 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import "root:/modules/common/functions/string_utils.js" as StringUtils +import "root:/modules/common/functions/file_utils.js" as FileUtils +import Qt5Compat.GraphicalEffects +import Qt.labs.platform +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io +import Quickshell.Widgets +import Quickshell.Hyprland + +IconImage { + id: root + property string url + + property real size: 32 + property string downloadUserAgent: ConfigOptions?.networking.userAgent ?? "" + property string faviconDownloadPath: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/favicons`) + property string domainName: url.includes("vertexaisearch") ? displayText : StringUtils.getBaseUrl(url) + property string faviconUrl: `https://www.google.com/s2/favicons?domain=${domainName}&sz=32` + property string fileName: `${domainName}.ico` + property string faviconFilePath: `${faviconDownloadPath}/${fileName}` + + Process { + id: faviconDownloadProcess + running: false + command: ["bash", "-c", `[ -f ${faviconFilePath} ] || curl -s '${root.faviconUrl}' -o '${faviconFilePath}' -L -H 'User-Agent: ${downloadUserAgent}'`] + onExited: (exitCode, exitStatus) => { + console.log("Favicon download process exited with code:", exitCode, "and status:", exitStatus) + console.log("Favicon file path:", faviconFilePath) + root.faviconUrl = root.faviconFilePath + } + } + + Component.onCompleted: { + console.log("faviconDownloadPath: ", faviconDownloadPath) + console.log("faviconFilePath: ", faviconFilePath) + console.log("faviconUrl: ", root.faviconUrl) + console.log("domainName: ", root.domainName) + console.log("fileName: ", root.fileName) + console.log("downloading to ", faviconFilePath, "from", root.faviconUrl) + faviconDownloadProcess.running = true + } + + source: Qt.resolvedUrl(root.faviconUrl) + implicitSize: root.size + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: root.implicitSize + height: root.implicitSize + radius: Appearance.rounding.full + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 440f44244..0bac7203c 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -19,7 +19,6 @@ Item { property var panelWindow property var inputField: messageInputField property string commandPrefix: "/" - property string faviconDownloadPath: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/favicons`) property var suggestionQuery: "" property var suggestionList: [] @@ -203,7 +202,6 @@ int main(int argc, char* argv[]) { Ai.messageByID[modelData] } messageInputField: root.inputField - faviconDownloadPath: root.faviconDownloadPath } } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index 9d8f1ae02..1122406a2 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -20,7 +20,6 @@ Rectangle { property int messageIndex property var messageData property var messageInputField - property string faviconDownloadPath property real messagePadding: 7 property real contentSpacing: 3 @@ -282,7 +281,6 @@ Rectangle { model: root.messageData?.annotationSources delegate: AnnotationSourceButton { id: annotationButton - faviconDownloadPath: root.faviconDownloadPath displayText: modelData.text url: modelData.url } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml index 1c279edd6..a92b92ee6 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml @@ -16,13 +16,6 @@ RippleButton { property string displayText property string url - property string downloadUserAgent: ConfigOptions.networking.userAgent - property string faviconDownloadPath - property string domainName: url.includes("vertexaisearch") ? displayText : StringUtils.getBaseUrl(url) - property string faviconUrl: `https://www.google.com/s2/favicons?domain=${domainName}&sz=32` - property string fileName: `${domainName}.ico` - property string faviconFilePath: `${faviconDownloadPath}/${fileName}` - property real faviconSize: 20 implicitHeight: 30 leftPadding: (implicitHeight - faviconSize) / 2 @@ -32,19 +25,6 @@ RippleButton { colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover colRipple: Appearance.colors.colSurfaceContainerHighestActive - Process { - id: faviconDownloadProcess - running: false - command: ["bash", "-c", `[ -f ${faviconFilePath} ] || curl -s '${root.faviconUrl}' -o '${faviconFilePath}' -L -H 'User-Agent: ${downloadUserAgent}'`] - onExited: (exitCode, exitStatus) => { - root.faviconUrl = root.faviconFilePath - } - } - - Component.onCompleted: { - faviconDownloadProcess.running = true - } - PointingHandInteraction {} onClicked: { if (url) { @@ -53,27 +33,25 @@ RippleButton { } } - contentItem: RowLayout { - spacing: 5 - IconImage { - id: iconImage - source: Qt.resolvedUrl(root.faviconUrl) - implicitSize: root.faviconSize - - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: iconImage.implicitSize - height: iconImage.implicitSize - radius: Appearance.rounding.full - } + contentItem: Item { + anchors.centerIn: parent + implicitWidth: rowLayout.implicitWidth + implicitHeight: rowLayout.implicitHeight + RowLayout { + id: rowLayout + anchors.fill: parent + spacing: 5 + Favicon { + Layout.alignment: Qt.AlignVCenter + url: root.url + size: root.faviconSize + } + StyledText { + id: text + horizontalAlignment: Text.AlignHCenter + text: displayText + color: Appearance.m3colors.m3onSurface } - } - StyledText { - id: text - horizontalAlignment: Text.AlignHCenter - text: displayText - color: Appearance.m3colors.m3onSurface } } } From 37f1cd2992429d0e200d57d54c67bbf0c3091b71 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 20:10:00 +0200 Subject: [PATCH 530/824] ai: add gemini flash 2.5 (preview) --- .config/quickshell/services/Ai.qml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index 4b0c4928a..c75b0933c 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -65,6 +65,24 @@ Singleton { }, ] }, + "gemini-2.5-flash-preview-05-20": { + "name": "Gemini 2.5 Flash (preview)", + "icon": "google-gemini-symbolic", + "description": qsTr("Online | Google's model\nGives up-to-date information with search."), + "homepage": "https://aistudio.google.com", + "endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:streamGenerateContent", + "model": "gemini-2.5-flash-preview-05-20", + "requires_key": true, + "key_id": "gemini", + "key_get_link": "https://aistudio.google.com/app/apikey", + "key_get_description": qsTr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"), + "api_format": "gemini", + "tools": [ + { + "google_search": {} + }, + ] + }, "openrouter-llama4-maverick": { "name": "Llama 4 Maverick", "icon": "ollama-symbolic", From 1a79012c61ad36ea71da6ad4af54533a5ac64454 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 20:33:36 +0200 Subject: [PATCH 531/824] ai search source: pass displaytext to favicon --- .../modules/sidebarLeft/aiChat/AnnotationSourceButton.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml index a92b92ee6..f344308ca 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml @@ -42,9 +42,9 @@ RippleButton { anchors.fill: parent spacing: 5 Favicon { - Layout.alignment: Qt.AlignVCenter url: root.url size: root.faviconSize + displayText: root.displayText } StyledText { id: text From fb2c9ac7df599a523007b824e4361e80da71e4e8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 20:33:56 +0200 Subject: [PATCH 532/824] favicon for links in clipboard entries --- .../modules/common/widgets/Favicon.qml | 10 +++-- .../modules/overview/SearchItem.qml | 40 ++++++++++++++----- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/Favicon.qml b/.config/quickshell/modules/common/widgets/Favicon.qml index c7133b089..e36881002 100644 --- a/.config/quickshell/modules/common/widgets/Favicon.qml +++ b/.config/quickshell/modules/common/widgets/Favicon.qml @@ -15,14 +15,16 @@ import Quickshell.Hyprland IconImage { id: root property string url + property string displayText property real size: 32 property string downloadUserAgent: ConfigOptions?.networking.userAgent ?? "" property string faviconDownloadPath: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/favicons`) - property string domainName: url.includes("vertexaisearch") ? displayText : StringUtils.getBaseUrl(url) + property string domainName: url.includes("vertexaisearch") ? displayText : StringUtils.getDomain(url) property string faviconUrl: `https://www.google.com/s2/favicons?domain=${domainName}&sz=32` property string fileName: `${domainName}.ico` property string faviconFilePath: `${faviconDownloadPath}/${fileName}` + property string urlToLoad Process { id: faviconDownloadProcess @@ -31,11 +33,13 @@ IconImage { onExited: (exitCode, exitStatus) => { console.log("Favicon download process exited with code:", exitCode, "and status:", exitStatus) console.log("Favicon file path:", faviconFilePath) - root.faviconUrl = root.faviconFilePath + root.urlToLoad = root.faviconFilePath } } Component.onCompleted: { + console.log("URL: ", root.url) + console.log("DOMAIN: ", root.domainName) console.log("faviconDownloadPath: ", faviconDownloadPath) console.log("faviconFilePath: ", faviconFilePath) console.log("faviconUrl: ", root.faviconUrl) @@ -45,7 +49,7 @@ IconImage { faviconDownloadProcess.running = true } - source: Qt.resolvedUrl(root.faviconUrl) + source: Qt.resolvedUrl(root.urlToLoad) implicitSize: root.size layer.enabled: true diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index eb1d7ed29..6f04165de 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -56,8 +56,16 @@ RippleButton { return result; } - property string displayContent: highlightContent(root.itemName, root.query) + + property list urls: { + if (!root.itemName) return []; + // Regular expression to match URLs + const urlRegex = /https?:\/\/[^\s<>"{}|\\^`[\]]+/gi; + const matches = root.itemName.match(urlRegex) + .filter(url => !url.includes("…")) // Elided = invalid + return matches ? matches : []; + } visible: root.entryShown property int horizontalMargin: 10 @@ -144,16 +152,26 @@ RippleButton { visible: root.itemType && root.itemType != qsTr("App") text: root.itemType } - StyledText { - Layout.fillWidth: true - id: nameText - textFormat: Text.StyledText // RichText also works, but StyledText ensures elide work - font.pixelSize: Appearance.font.pixelSize.small - font.family: Appearance.font.family[root.fontType] - color: Appearance.m3colors.m3onSurface - horizontalAlignment: Text.AlignLeft - elide: Text.ElideRight - text: `${root.displayContent}` + RowLayout { + Repeater { + model: root.query == root.itemName ? [] : root.urls + Favicon { + required property var modelData + size: parent.height + url: modelData + } + } + StyledText { + Layout.fillWidth: true + id: nameText + textFormat: Text.StyledText // RichText also works, but StyledText ensures elide work + font.pixelSize: Appearance.font.pixelSize.small + font.family: Appearance.font.family[root.fontType] + color: Appearance.m3colors.m3onSurface + horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight + text: `${root.displayContent}` + } } } From 02712868f9f82aeb4bee8db5a759ae8563642213 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 20:35:57 +0200 Subject: [PATCH 533/824] search: fix item clipping --- .config/quickshell/modules/overview/SearchWidget.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index fb363e409..ce98da6f2 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -272,7 +272,7 @@ Item { // Wrapper visible: root.showResults Layout.fillWidth: true implicitHeight: Math.min(600, appResults.contentHeight + topMargin + bottomMargin) - // clip: true + clip: true topMargin: 10 bottomMargin: 10 spacing: 0 From 5ea497068c7d4ace1ec0c5ddf9ddbf08ad0508f5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 22:28:28 +0200 Subject: [PATCH 534/824] Revert "use better-control instead of gnome settings and blueberry" This reverts commit e1ee645e87721ad995bb2cae38f1c34a711ad470. --- .config/ags/modules/.configuration/default_options.jsonc | 6 +++--- .config/hypr/hyprland/keybinds.conf | 2 +- .config/quickshell/modules/common/ConfigOptions.qml | 6 +++--- arch-packages/illogical-impulse-gnome/PKGBUILD | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.config/ags/modules/.configuration/default_options.jsonc b/.config/ags/modules/.configuration/default_options.jsonc index eaddb9eb1..e20975e1b 100644 --- a/.config/ags/modules/.configuration/default_options.jsonc +++ b/.config/ags/modules/.configuration/default_options.jsonc @@ -46,10 +46,10 @@ "fakeScreenRounding": 2 // 0: None | 1: Always | 2: When not fullscreen }, "apps": { - "bluetooth": "better-control --bluetooth", + "bluetooth": "blueberry", "imageViewer": "loupe", - "network": "better-control --wifi", - "settings": "better-control", + "network": "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi", + "settings": "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center", "taskManager": "gnome-usage", "terminal": "foot" // This is only for shell actions }, diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 1b68ac52f..3ef5363ea 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -201,8 +201,8 @@ bind = Super+Alt, E, exec, thunar # [hidden] bind = Super, W, exec, zen-browser # [hidden] bind = Super+Shift, W, exec, wps # WPS Office bind = Ctrl+Super, V, exec, pavucontrol # Pavucontrol (volume mixer) -bind = Super, I, exec, better-control # Better Control (settings app) bind = Super, X, exec, gnome-text-editor --new-window # GNOME Text Editor +bind = Super, I, exec, XDG_CURRENT_DESKTOP="gnome" gnome-control-center # GNOME Settings bind = Ctrl+Shift, Escape, exec, gnome-system-monitor # GNOME System monitor # Cursed stuff diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 5a89c56f2..56b735dd7 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -13,10 +13,10 @@ Singleton { } property QtObject apps: QtObject { - property string bluetooth: "better-control --bluetooth" + property string bluetooth: "blueberry" property string imageViewer: "loupe" - property string network: "better-control --wifi" - property string settings: "better-control" + property string network: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi" + property string settings: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center" property string taskManager: "gnome-usage" property string terminal: "foot" // This is only for shell actions } diff --git a/arch-packages/illogical-impulse-gnome/PKGBUILD b/arch-packages/illogical-impulse-gnome/PKGBUILD index 3e773bdaf..33af4b8ba 100644 --- a/arch-packages/illogical-impulse-gnome/PKGBUILD +++ b/arch-packages/illogical-impulse-gnome/PKGBUILD @@ -7,6 +7,6 @@ license=(None) depends=( polkit-gnome gnome-keyring - networkmanager - better-control-git + gnome-control-center + blueberry networkmanager ) From 66c75342d426e208e801c1e27b1ff5abd8f64e59 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 22:49:03 +0200 Subject: [PATCH 535/824] move common dirs to Directories <--renamed-- XdgDirectories --- .../quickshell/modules/common/Directories.qml | 40 +++++++++++++++++++ .../modules/common/XdgDirectories.qml | 13 ------ .../modules/common/widgets/Favicon.qml | 12 +----- .../modules/mediaControls/MediaControls.qml | 6 --- .../modules/mediaControls/PlayerControl.qml | 3 +- .../modules/overview/SearchWidget.qml | 2 +- .../quickshell/modules/sidebarLeft/AiChat.qml | 4 -- .../quickshell/modules/sidebarLeft/Anime.qml | 11 ++--- .../sidebarLeft/aiChat/MessageCodeBlock.qml | 5 ++- .config/quickshell/services/Ai.qml | 2 +- .config/quickshell/services/ConfigLoader.qml | 4 +- .../quickshell/services/HyprlandKeybinds.qml | 6 +-- .config/quickshell/services/LatexRenderer.qml | 6 +-- .../services/MaterialThemeLoader.qml | 4 +- .config/quickshell/services/Notifications.qml | 4 +- .../services/PersistentStateManager.qml | 2 +- .config/quickshell/services/Todo.qml | 4 +- 17 files changed, 63 insertions(+), 65 deletions(-) create mode 100644 .config/quickshell/modules/common/Directories.qml delete mode 100644 .config/quickshell/modules/common/XdgDirectories.qml diff --git a/.config/quickshell/modules/common/Directories.qml b/.config/quickshell/modules/common/Directories.qml new file mode 100644 index 000000000..db210d7ad --- /dev/null +++ b/.config/quickshell/modules/common/Directories.qml @@ -0,0 +1,40 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common/functions/file_utils.js" as FileUtils +import Qt.labs.platform +import QtQuick +import Quickshell +import Quickshell.Hyprland + +Singleton { + // XDG Dirs, with "file://" + readonly property string config: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0] + readonly property string state: StandardPaths.standardLocations(StandardPaths.StateLocation)[0] + readonly property string cache: StandardPaths.standardLocations(StandardPaths.CacheLocation)[0] + readonly property string pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + readonly property string downloads: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0] + + // Other dirs used by the shell, without "file://" + property string favicons: FileUtils.trimFileProtocol(`${Directories.cache}/media/favicons`) + property string coverArt: FileUtils.trimFileProtocol(`${Directories.cache}/media/coverart`) + property string booruPreviews: FileUtils.trimFileProtocol(`${Directories.cache}/media/boorus`) + property string booruDownloads: FileUtils.trimFileProtocol(Directories.pictures + "/homework") + property string booruDownloadsNsfw: FileUtils.trimFileProtocol(Directories.pictures + "/homework/🌶️") + property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/latex`) + property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse`) + property string shellConfigName: "config.json" + property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}` + property string todoPath: FileUtils.trimFileProtocol(`${Directories.state}/user/todo.json`) + property string notificationsPath: `${Directories.cache}/notifications/notifications.json` + property string generatedMaterialThemePath: `${Directories.state}/user/generated/colors.json` + // Cleanup on init + Component.onCompleted: { + Hyprland.dispatch(`exec mkdir -p ${Directories.shellConfig}`) + Hyprland.dispatch(`exec mkdir -p ${favicons}`) + Hyprland.dispatch(`exec rm -rf ${coverArt} && mkdir -p ${coverArt}`) + Hyprland.dispatch(`exec rm -rf '${booruPreviews}' && mkdir -p '${booruPreviews}'`) + Hyprland.dispatch(`exec mkdir -p '${booruDownloads}' && mkdir -p '${booruDownloadsNsfw}'`) + Hyprland.dispatch(`exec rm -rf ${latexOutput} && mkdir -p ${latexOutput}`) + } +} diff --git a/.config/quickshell/modules/common/XdgDirectories.qml b/.config/quickshell/modules/common/XdgDirectories.qml deleted file mode 100644 index c8297fbb1..000000000 --- a/.config/quickshell/modules/common/XdgDirectories.qml +++ /dev/null @@ -1,13 +0,0 @@ -import Qt.labs.platform -import QtQuick -import Quickshell -pragma Singleton -pragma ComponentBehavior: Bound - -Singleton { - readonly property string config: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0] - readonly property string state: StandardPaths.standardLocations(StandardPaths.StateLocation)[0] - readonly property string cache: StandardPaths.standardLocations(StandardPaths.CacheLocation)[0] - readonly property string pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] - readonly property string downloads: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0] -} diff --git a/.config/quickshell/modules/common/widgets/Favicon.qml b/.config/quickshell/modules/common/widgets/Favicon.qml index e36881002..74fc6d749 100644 --- a/.config/quickshell/modules/common/widgets/Favicon.qml +++ b/.config/quickshell/modules/common/widgets/Favicon.qml @@ -19,7 +19,7 @@ IconImage { property real size: 32 property string downloadUserAgent: ConfigOptions?.networking.userAgent ?? "" - property string faviconDownloadPath: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/favicons`) + property string faviconDownloadPath: Directories.favicons property string domainName: url.includes("vertexaisearch") ? displayText : StringUtils.getDomain(url) property string faviconUrl: `https://www.google.com/s2/favicons?domain=${domainName}&sz=32` property string fileName: `${domainName}.ico` @@ -31,21 +31,11 @@ IconImage { running: false command: ["bash", "-c", `[ -f ${faviconFilePath} ] || curl -s '${root.faviconUrl}' -o '${faviconFilePath}' -L -H 'User-Agent: ${downloadUserAgent}'`] onExited: (exitCode, exitStatus) => { - console.log("Favicon download process exited with code:", exitCode, "and status:", exitStatus) - console.log("Favicon file path:", faviconFilePath) root.urlToLoad = root.faviconFilePath } } Component.onCompleted: { - console.log("URL: ", root.url) - console.log("DOMAIN: ", root.domainName) - console.log("faviconDownloadPath: ", faviconDownloadPath) - console.log("faviconFilePath: ", faviconFilePath) - console.log("faviconUrl: ", root.faviconUrl) - console.log("domainName: ", root.domainName) - console.log("fileName: ", root.fileName) - console.log("downloading to ", faviconFilePath, "from", root.faviconUrl) faviconDownloadProcess.running = true } diff --git a/.config/quickshell/modules/mediaControls/MediaControls.qml b/.config/quickshell/modules/mediaControls/MediaControls.qml index def25936b..8bdba8608 100644 --- a/.config/quickshell/modules/mediaControls/MediaControls.qml +++ b/.config/quickshell/modules/mediaControls/MediaControls.qml @@ -27,7 +27,6 @@ Scope { property real contentPadding: 13 property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 property real artRounding: Appearance.rounding.verysmall - property string baseCoverArtDir: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/coverart`) property bool hasPlasmaIntegration: false function isRealPlayer(player) { @@ -70,10 +69,6 @@ Scope { return filtered; } - Component.onCompleted: { - Hyprland.dispatch(`exec rm -rf ${baseCoverArtDir} && mkdir -p ${baseCoverArtDir}`) - } - Loader { id: mediaControlsLoader active: false @@ -116,7 +111,6 @@ Scope { } delegate: PlayerControl { required property MprisPlayer modelData - artDownloadLocation: root.baseCoverArtDir player: modelData } } diff --git a/.config/quickshell/modules/mediaControls/PlayerControl.qml b/.config/quickshell/modules/mediaControls/PlayerControl.qml index 5c770192d..ea8bd2348 100644 --- a/.config/quickshell/modules/mediaControls/PlayerControl.qml +++ b/.config/quickshell/modules/mediaControls/PlayerControl.qml @@ -3,6 +3,7 @@ import "root:/modules/common/widgets" import "root:/services" import "root:/modules/common/functions/string_utils.js" as StringUtils import "root:/modules/common/functions/color_utils.js" as ColorUtils +import "root:/modules/common/functions/file_utils.js" as FileUtils import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Effects @@ -19,7 +20,7 @@ Item { // Player instance id: playerController required property MprisPlayer player property var artUrl: player?.trackArtUrl - property string artDownloadLocation: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/coverart`) + property string artDownloadLocation: Directories.coverArt property string artFileName: Qt.md5(artUrl) + ".jpg" property string artFilePath: `${artDownloadLocation}/${artFileName}` property color artDominantColor: Appearance.m3colors.m3secondaryContainer diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index ce98da6f2..ed8e5f01d 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -15,7 +15,7 @@ import Quickshell.Hyprland Item { // Wrapper id: root - readonly property string xdgConfigHome: XdgDirectories.config + readonly property string xdgConfigHome: Directories.config property string searchingText: "" property bool showResults: searchingText != "" property real searchBarHeight: searchBar.height + Appearance.sizes.elevationMargin * 2 diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 0bac7203c..8bd6b6832 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -23,10 +23,6 @@ Item { property var suggestionQuery: "" property var suggestionList: [] - Component.onCompleted: { - Hyprland.dispatch(`exec mkdir -p ${faviconDownloadPath}`) - } - onFocusChanged: (focus) => { if (focus) { root.inputField.forceActiveFocus() diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 7ab051cd8..3c0e6b633 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -20,9 +20,9 @@ Item { property var panelWindow property var inputField: tagInputField readonly property var responses: Booru.responses - property string previewDownloadPath: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/waifus`) - property string downloadPath: FileUtils.trimFileProtocol(XdgDirectories.pictures + "/homework") - property string nsfwPath: FileUtils.trimFileProtocol(XdgDirectories.pictures + "/homework/🌶️") + property string previewDownloadPath: Directories.booruPreviews + property string downloadPath: Directories.booruDownloads + property string nsfwPath: Directories.booruDownloadsNsfw property string commandPrefix: "/" property real scrollOnNewResponse: 100 property int tagSuggestionDelay: 210 @@ -37,11 +37,6 @@ Item { } } - Component.onCompleted: { - Hyprland.dispatch(`exec rm -rf '${previewDownloadPath}' && mkdir -p '${previewDownloadPath}'`) - Hyprland.dispatch(`exec mkdir -p '${downloadPath}' && mkdir -p '${downloadPath}'`) - } - property var allCommands: [ { name: "mode", diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml index c38afb9b6..fa4af99e0 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml @@ -95,8 +95,9 @@ ColumnLayout { buttonIcon: activated ? "check" : "save" onClicked: { - Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(segmentContent)}' > '${FileUtils.trimFileProtocol(XdgDirectories.downloads)}/code.${segmentLang || "txt"}'`) - Hyprland.dispatch(`exec notify-send 'Code saved to file' '${FileUtils.trimFileProtocol(XdgDirectories.downloads)}/code.${segmentLang || "txt"}'`) + const downloadPath = FileUtils.trimFileProtocol(Directories.downloads) + Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(segmentContent)}' > '${downloadPath}/code.${segmentLang || "txt"}'`) + Hyprland.dispatch(`exec notify-send 'Code saved to file' '${downloadPath}/code.${segmentLang || "txt"}'`) saveCodeButton.activated = true saveIconTimer.restart() } diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index c75b0933c..fb7e6529b 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -139,7 +139,7 @@ Singleton { Process { id: getOllamaModels - command: ["bash", "-c", `${XdgDirectories.config}/quickshell/scripts/ai/show-installed-ollama-models.sh`.replace(/file:\/\//, "")] + command: ["bash", "-c", `${Directories.config}/quickshell/scripts/ai/show-installed-ollama-models.sh`.replace(/file:\/\//, "")] stdout: SplitParser { onRead: data => { try { diff --git a/.config/quickshell/services/ConfigLoader.qml b/.config/quickshell/services/ConfigLoader.qml index 658019f19..e8e711d7f 100644 --- a/.config/quickshell/services/ConfigLoader.qml +++ b/.config/quickshell/services/ConfigLoader.qml @@ -17,9 +17,7 @@ import Qt.labs.platform */ Singleton { id: root - property string fileDir: `${XdgDirectories.config}/illogical-impulse` - property string fileName: "config.json" - property string filePath: FileUtils.trimFileProtocol(`${root.fileDir}/${root.fileName}`) + property string filePath: Directories.shellConfigPath property bool firstLoad: true function loadConfig() { diff --git a/.config/quickshell/services/HyprlandKeybinds.qml b/.config/quickshell/services/HyprlandKeybinds.qml index f0001fe20..189ba76d5 100644 --- a/.config/quickshell/services/HyprlandKeybinds.qml +++ b/.config/quickshell/services/HyprlandKeybinds.qml @@ -15,9 +15,9 @@ import Quickshell.Hyprland */ Singleton { id: root - property string keybindParserPath: FileUtils.trimFileProtocol(`${XdgDirectories.config}/quickshell/scripts/hyprland/get_keybinds.py`) - property string defaultKeybindConfigPath: FileUtils.trimFileProtocol(`${XdgDirectories.config}/hypr/hyprland/keybinds.conf`) - property string userKeybindConfigPath: FileUtils.trimFileProtocol(`${XdgDirectories.config}/hypr/custom/keybinds.conf`) + property string keybindParserPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/scripts/hyprland/get_keybinds.py`) + property string defaultKeybindConfigPath: FileUtils.trimFileProtocol(`${Directories.config}/hypr/hyprland/keybinds.conf`) + property string userKeybindConfigPath: FileUtils.trimFileProtocol(`${Directories.config}/hypr/custom/keybinds.conf`) property var defaultKeybinds: {"children": []} property var userKeybinds: {"children": []} property var keybinds: ({ diff --git a/.config/quickshell/services/LatexRenderer.qml b/.config/quickshell/services/LatexRenderer.qml index c09f0af99..d3b9ade1f 100644 --- a/.config/quickshell/services/LatexRenderer.qml +++ b/.config/quickshell/services/LatexRenderer.qml @@ -26,14 +26,10 @@ Singleton { property var processedExpressions: ({}) property var renderedImagePaths: ({}) property string microtexBinaryPath: Qt.resolvedUrl("/opt/MicroTeX/LaTeX") - property string latexOutputPath: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/latex`) + property string latexOutputPath: Directories.latexOutput signal renderFinished(string hash, string imagePath) - Component.onCompleted: { - Hyprland.dispatch(`exec rm -rf ${latexOutputPath} && mkdir -p ${latexOutputPath}`) - } - /** * Requests rendering of a LaTeX expression. * Returns the [hash, isNew] diff --git a/.config/quickshell/services/MaterialThemeLoader.qml b/.config/quickshell/services/MaterialThemeLoader.qml index dcee32c26..cd4eb686b 100644 --- a/.config/quickshell/services/MaterialThemeLoader.qml +++ b/.config/quickshell/services/MaterialThemeLoader.qml @@ -12,7 +12,7 @@ import Quickshell.Io */ Singleton { id: root - property string filePath: `${XdgDirectories.state}/user/generated/colors.json` + property string filePath: Directories.generatedMaterialThemePath function reapplyTheme() { themeFileView.reload() @@ -44,7 +44,7 @@ Singleton { FileView { id: themeFileView - path: root.filePath + path: Qt.resolvedUrl(root.filePath) watchChanges: true onFileChanged: { this.reload() diff --git a/.config/quickshell/services/Notifications.qml b/.config/quickshell/services/Notifications.qml index 39b6fb520..d5c7e63cb 100644 --- a/.config/quickshell/services/Notifications.qml +++ b/.config/quickshell/services/Notifications.qml @@ -60,7 +60,7 @@ Singleton { destroy() } } - property var filePath: `${XdgDirectories.cache}/notifications/notifications.json` + property var filePath: Directories.notificationsPath property list list: [] property var popupList: list.filter((notif) => notif.popup); property bool popupInhibited: GlobalStates?.sidebarRightOpen ?? false @@ -233,7 +233,7 @@ Singleton { FileView { id: notifFileView - path: filePath + path: Qt.resolvedUrl(filePath) onLoaded: { const fileContents = notifFileView.text() root.list = JSON.parse(fileContents).map((notif) => { diff --git a/.config/quickshell/services/PersistentStateManager.qml b/.config/quickshell/services/PersistentStateManager.qml index a885d345e..c3d1536ed 100644 --- a/.config/quickshell/services/PersistentStateManager.qml +++ b/.config/quickshell/services/PersistentStateManager.qml @@ -15,7 +15,7 @@ import Qt.labs.platform */ Singleton { id: root - property string fileDir: XdgDirectories.state + property string fileDir: Directories.state property string fileName: "states.json" property string filePath: `${root.fileDir}/${root.fileName}` property bool allowWriteback: false diff --git a/.config/quickshell/services/Todo.qml b/.config/quickshell/services/Todo.qml index f452a0178..2cbf0d7dc 100644 --- a/.config/quickshell/services/Todo.qml +++ b/.config/quickshell/services/Todo.qml @@ -13,7 +13,7 @@ import QtQuick; */ Singleton { id: root - property var filePath: `${XdgDirectories.state}/user/todo.json` + property var filePath: Directories.todoPath property var list: [] function addItem(item) { @@ -68,7 +68,7 @@ Singleton { FileView { id: todoFileView - path: filePath + path: Qt.resolvedUrl(root.filePath) onLoaded: { const fileContents = todoFileView.text() root.list = JSON.parse(fileContents) From f04d5f62023c250c7a3a309fee03d46893b01ec6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 27 May 2025 22:49:40 +0200 Subject: [PATCH 536/824] overview: fix kb focus on hl 0.49, mask visible region --- .config/quickshell/modules/overview/Overview.qml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 714e1d7ab..27434a5d3 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -22,16 +22,20 @@ Scope { readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor.id) screen: modelData - visible: true + visible: GlobalStates.overviewOpen WlrLayershell.namespace: "quickshell:overview" WlrLayershell.layer: WlrLayer.Overlay - WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None + // WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None color: "transparent" mask: Region { item: GlobalStates.overviewOpen ? columnLayout : null } + HyprlandWindow.visibleMask: Region { + item: GlobalStates.overviewOpen ? columnLayout : null + } + anchors { top: true From 442ddc1a7b68377d4b1d809e25eb5bd6a8f68918 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 28 May 2025 00:09:38 +0200 Subject: [PATCH 537/824] clipboard history: images --- .../quickshell/modules/common/Directories.qml | 15 +-- .../modules/common/widgets/CliphistImage.qml | 96 +++++++++++++++++++ .../quickshell/modules/overview/Overview.qml | 2 + .../modules/overview/SearchItem.qml | 15 ++- .../modules/overview/SearchWidget.qml | 6 +- .config/quickshell/services/Cliphist.qml | 1 - 6 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/CliphistImage.qml diff --git a/.config/quickshell/modules/common/Directories.qml b/.config/quickshell/modules/common/Directories.qml index db210d7ad..fd18f193a 100644 --- a/.config/quickshell/modules/common/Directories.qml +++ b/.config/quickshell/modules/common/Directories.qml @@ -26,15 +26,16 @@ Singleton { property string shellConfigName: "config.json" property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}` property string todoPath: FileUtils.trimFileProtocol(`${Directories.state}/user/todo.json`) - property string notificationsPath: `${Directories.cache}/notifications/notifications.json` - property string generatedMaterialThemePath: `${Directories.state}/user/generated/colors.json` + property string notificationsPath: FileUtils.trimFileProtocol(`${Directories.cache}/notifications/notifications.json`) + property string generatedMaterialThemePath: FileUtils.trimFileProtocol(`${Directories.state}/user/generated/colors.json`) + property string cliphistDecode: FileUtils.trimFileProtocol(`${Directories.cache}/media/cliphist`) // Cleanup on init Component.onCompleted: { - Hyprland.dispatch(`exec mkdir -p ${Directories.shellConfig}`) - Hyprland.dispatch(`exec mkdir -p ${favicons}`) - Hyprland.dispatch(`exec rm -rf ${coverArt} && mkdir -p ${coverArt}`) - Hyprland.dispatch(`exec rm -rf '${booruPreviews}' && mkdir -p '${booruPreviews}'`) + Hyprland.dispatch(`exec mkdir -p '${favicons}'`) + Hyprland.dispatch(`exec rm -rf '${coverArt}'; mkdir -p '${coverArt}'`) + Hyprland.dispatch(`exec rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`) Hyprland.dispatch(`exec mkdir -p '${booruDownloads}' && mkdir -p '${booruDownloadsNsfw}'`) - Hyprland.dispatch(`exec rm -rf ${latexOutput} && mkdir -p ${latexOutput}`) + Hyprland.dispatch(`exec rm -rf '${latexOutput}'; mkdir -p '${latexOutput}'`) + Hyprland.dispatch(`exec rm -rf '${cliphistDecode}'; mkdir -p '${cliphistDecode}'`) } } diff --git a/.config/quickshell/modules/common/widgets/CliphistImage.qml b/.config/quickshell/modules/common/widgets/CliphistImage.qml new file mode 100644 index 000000000..f0b07d3f1 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/CliphistImage.qml @@ -0,0 +1,96 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import "root:/modules/common/functions/string_utils.js" as StringUtils +import "root:/modules/common/functions/file_utils.js" as FileUtils +import Qt5Compat.GraphicalEffects +import Qt.labs.platform +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io +import Quickshell.Widgets +import Quickshell.Hyprland + +Rectangle { + id: root + property string entry + property real maxWidth + property real maxHeight + + property string imageDecodePath: Directories.cliphistDecode + property string imageDecodeFileName: `${entryNumber}` + property string imageDecodeFilePath: `${imageDecodePath}/${imageDecodeFileName}` + property string source + + property int entryNumber: { + if (!root.entry) return 0 + const match = root.entry.match(/^(\d+)\t/) + return match ? parseInt(match[1]) : 0 + } + property int imageWidth: { + if (!root.entry) return 0 + const match = root.entry.match(/(\d+)x(\d+)/) + return match ? parseInt(match[1]) : 0 + } + property int imageHeight: { + if (!root.entry) return 0 + const match = root.entry.match(/(\d+)x(\d+)/) + return match ? parseInt(match[2]) : 0 + } + property real scale: { + return Math.min( + root.maxWidth / imageWidth, + root.maxHeight / imageHeight + ) + } + + color: Appearance.colors.colLayer1 + radius: Appearance.rounding.small + implicitHeight: imageHeight * scale + implicitWidth: imageWidth * scale + + Component.onCompleted: { + decodeImageProcess.running = true + } + + Process { + id: decodeImageProcess + command: ["bash", "-c", + `[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(root.entry)}' | cliphist decode > '${imageDecodeFilePath}'` + ] + onExited: (exitCode, exitStatus) => { + if (exitCode === 0) { + root.source = imageDecodeFilePath + } else { + console.error("[CliphistImage] Failed to decode image for entry:", root.entry) + root.source = "" + } + } + } + + Image { + id: image + anchors.fill: parent + + source: Qt.resolvedUrl(root.source) + fillMode: Image.PreserveAspectFit + antialiasing: true + asynchronous: true + + width: root.imageWidth * root.scale + height: root.imageHeight * root.scale + sourceSize.width: width + sourceSize.height: height + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: image.width + height: image.height + radius: root.radius + } + } + } +} + diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 27434a5d3..5711a9434 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -1,4 +1,5 @@ import "root:/" +import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" import QtQuick @@ -191,6 +192,7 @@ Scope { GlobalStates.overviewOpen = false; return; } + Cliphist.refresh() for (let i = 0; i < overviewVariants.instances.length; i++) { let panelWindow = overviewVariants.instances[i]; if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index 6f04165de..cd415bb3b 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -25,6 +25,7 @@ RippleButton { property string fontType: entry?.fontType ?? "main" property string itemClickActionName: entry?.clickActionName property string materialSymbol: entry?.materialSymbol ?? "" + property string cliphistRawString: entry?.cliphistRawString ?? "" property string highlightPrefix: `` property string highlightSuffix: `` @@ -62,8 +63,8 @@ RippleButton { if (!root.itemName) return []; // Regular expression to match URLs const urlRegex = /https?:\/\/[^\s<>"{}|\\^`[\]]+/gi; - const matches = root.itemName.match(urlRegex) - .filter(url => !url.includes("…")) // Elided = invalid + const matches = root.itemName?.match(urlRegex) + ?.filter(url => !url.includes("…")) // Elided = invalid return matches ? matches : []; } @@ -143,6 +144,7 @@ RippleButton { // Main text ColumnLayout { + id: contentColumn Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter spacing: 0 @@ -173,6 +175,15 @@ RippleButton { text: `${root.displayContent}` } } + Loader { + active: root.cliphistRawString && /^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(root.cliphistRawString) + sourceComponent: CliphistImage { + Layout.fillWidth: true + entry: root.cliphistRawString + maxWidth: contentColumn.width + maxHeight: 140 + } + } } // Action text diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index ed8e5f01d..84de2f6f6 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -275,7 +275,7 @@ Item { // Wrapper clip: true topMargin: 10 bottomMargin: 10 - spacing: 0 + spacing: 2 KeyNavigation.up: searchBar onFocusChanged: { @@ -305,11 +305,13 @@ Item { // Wrapper const searchString = root.searchingText.slice(ConfigOptions.search.prefix.clipboard.length); return Cliphist.fuzzyQuery(searchString).map(entry => { return { + cliphistRawString: entry, name: entry.replace(/^\s*\S+\s+/, ""), - clickActionName: qsTr("Copy"), + clickActionName: "", type: `#${entry.match(/^\s*(\S+)/)?.[1] || ""}`, execute: () => { Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(entry)}' | cliphist decode | wl-copy`); + Cliphist.refresh() } }; }).filter(Boolean); diff --git a/.config/quickshell/services/Cliphist.qml b/.config/quickshell/services/Cliphist.qml index a2650a19e..51514e864 100644 --- a/.config/quickshell/services/Cliphist.qml +++ b/.config/quickshell/services/Cliphist.qml @@ -40,7 +40,6 @@ Singleton { function refresh() { readProc.buffer = [] - readProc.running = false readProc.running = true } From a5abe198547ac6ffabc880ed2e3e792c26c9d30e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 28 May 2025 11:35:19 +0200 Subject: [PATCH 538/824] dock: pin button, launcher button --- .../modules/common/ConfigOptions.qml | 6 + .../modules/common/widgets/GroupButton.qml | 13 +- .../common/widgets/VerticalButtonGroup.qml | 47 ++++++ .config/quickshell/modules/dock/Dock.qml | 141 ++++++++++++++++++ .config/quickshell/modules/dock/DockApps.qml | 17 +++ .../quickshell/modules/dock/DockButton.qml | 15 ++ .../quickshell/modules/dock/DockSeparator.qml | 13 ++ 7 files changed, 246 insertions(+), 6 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/VerticalButtonGroup.qml create mode 100644 .config/quickshell/modules/dock/Dock.qml create mode 100644 .config/quickshell/modules/dock/DockApps.qml create mode 100644 .config/quickshell/modules/dock/DockButton.qml create mode 100644 .config/quickshell/modules/dock/DockSeparator.qml diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 56b735dd7..c8fde86d7 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -37,6 +37,12 @@ Singleton { } } + property QtObject dock: QtObject { + property real height: 60 + property real hoverRegionHeight: 3 + property bool pinnedOnStartup: false + } + property QtObject networking: QtObject { property string userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" } diff --git a/.config/quickshell/modules/common/widgets/GroupButton.qml b/.config/quickshell/modules/common/widgets/GroupButton.qml index 4b033a72b..a369ec16e 100644 --- a/.config/quickshell/modules/common/widgets/GroupButton.qml +++ b/.config/quickshell/modules/common/widgets/GroupButton.qml @@ -27,9 +27,10 @@ Button { property var parentGroup: root.parent property int clickIndex: parentGroup?.clickIndex ?? -1 - Layout.fillWidth: (clickIndex - 1 <= parentGroup.children.indexOf(button) && parentGroup.children.indexOf(button) <= clickIndex + 1) - implicitWidth: (button.down && bounce) ? clickedWidth : baseWidth - implicitHeight: (button.down && bounce) ? clickedHeight : baseHeight + Layout.fillWidth: (clickIndex - 1 <= parentGroup.children.indexOf(root) && parentGroup.children.indexOf(root) <= clickIndex + 1) + Layout.fillHeight: (clickIndex - 1 <= parentGroup.children.indexOf(root) && parentGroup.children.indexOf(root) <= clickIndex + 1) + implicitWidth: (root.down && bounce) ? clickedWidth : baseWidth + implicitHeight: (root.down && bounce) ? clickedHeight : baseHeight Behavior on implicitWidth { animation: Appearance.animation.clickBounce.numberAnimation.createObject(this) @@ -56,9 +57,9 @@ Button { colBackground)) : colBackground onDownChanged: { - if (button.down) { - if (button.parent.clickIndex !== undefined) { - button.parent.clickIndex = parent.children.indexOf(button) + if (root.down) { + if (root.parent.clickIndex !== undefined) { + root.parent.clickIndex = parent.children.indexOf(root) } } } diff --git a/.config/quickshell/modules/common/widgets/VerticalButtonGroup.qml b/.config/quickshell/modules/common/widgets/VerticalButtonGroup.qml new file mode 100644 index 000000000..7d8fc29fe --- /dev/null +++ b/.config/quickshell/modules/common/widgets/VerticalButtonGroup.qml @@ -0,0 +1,47 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +/** + * A container that supports GroupButton children for bounciness. + * See https://m3.material.io/components/button-groups/overview + */ +Rectangle { + id: root + default property alias content: columnLayout.data + property real spacing: 5 + property real padding: 0 + property int clickIndex: columnLayout.clickIndex + + property real contentHeight: { + let total = 0; + for (let i = 0; i < columnLayout.children.length; ++i) { + const child = columnLayout.children[i]; + total += child.baseHeight ?? child.implicitHeight ?? child.height; + } + return total + columnLayout.spacing * (columnLayout.children.length - 1); + } + + topLeftRadius: columnLayout.children.length > 0 ? (columnLayout.children[0].radius + padding) : + Appearance?.rounding?.small + topRightRadius: topLeftRadius + bottomLeftRadius: columnLayout.children.length > 0 ? (columnLayout.children[columnLayout.children.length - 1].radius + padding) : + Appearance?.rounding?.small + bottomRightRadius: bottomLeftRadius + + color: "transparent" + height: root.contentHeight + padding * 2 + implicitWidth: columnLayout.implicitWidth + padding * 2 + implicitHeight: root.contentHeight + padding * 2 + + children: [ColumnLayout { + id: columnLayout + anchors.fill: parent + anchors.margins: root.padding + spacing: root.spacing + property int clickIndex: -1 + }] +} diff --git a/.config/quickshell/modules/dock/Dock.qml b/.config/quickshell/modules/dock/Dock.qml new file mode 100644 index 000000000..4da45e74d --- /dev/null +++ b/.config/quickshell/modules/dock/Dock.qml @@ -0,0 +1,141 @@ +import "root:/" +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell.Io +import Quickshell +import Quickshell.Widgets +import Quickshell.Wayland +import Quickshell.Hyprland + +Scope { // Scope + id: root + property bool pinned: ConfigOptions?.dock.pinnedOnStartup ?? false + + Variants { // For each monitor + model: Quickshell.screens + PanelWindow { // Window + required property var modelData + id: dockRoot + screen: modelData + + property bool reveal: root.pinned || dockMouseArea.containsMouse + + anchors { + bottom: true + left: true + right: true + } + + function hide() { + cheatsheetLoader.active = false + } + exclusiveZone: root.pinned ? implicitHeight - Appearance.sizes.hyprlandGapsOut : 0 + Component.onCompleted: { + console.log(ConfigOptions.dock.hoverRegionHeight) + } + + implicitWidth: dockBackground.implicitWidth + WlrLayershell.namespace: "quickshell:dock" + color: "transparent" + + implicitHeight: (ConfigOptions?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut + + mask: Region { + item: dockMouseArea + } + + MouseArea { + id: dockMouseArea + anchors.top: parent.top + height: parent.height + anchors.topMargin: dockRoot.reveal ? 0 : dockRoot.implicitHeight - ConfigOptions.dock.hoverRegionHeight + anchors.left: parent.left + anchors.right: parent.right + hoverEnabled: true + + Behavior on anchors.topMargin { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + + Item { + id: dockHoverRegion + anchors.fill: parent + + Item { + id: dockBackground + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + + implicitWidth: dockRow.implicitWidth + 5 * 2 + height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut + + RectangularShadow { + anchors.fill: dockVisualBackground + radius: dockVisualBackground.radius + blur: 1.2 * Appearance.sizes.elevationMargin + spread: 1 + color: Appearance.colors.colShadow + cached: true + } + Rectangle { + id: dockVisualBackground + property real margin: Appearance.sizes.elevationMargin + anchors.fill: parent + anchors.topMargin: margin + anchors.bottomMargin: margin + color: Appearance.colors.colLayer0 + radius: Appearance.rounding.large + } + + RowLayout { + id: dockRow + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + spacing: 3 + property real padding: 5 + + VerticalButtonGroup { + GroupButton { // Pin button + baseWidth: 35 + baseHeight: 35 + clickedWidth: baseWidth + clickedHeight: baseHeight + 20 + buttonRadius: Appearance.rounding.normal + toggled: root.pinned + onClicked: root.pinned = !root.pinned + contentItem: MaterialSymbol { + text: "keep" + horizontalAlignment: Text.AlignHCenter + iconSize: Appearance.font.pixelSize.larger + color: root.pinned ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0 + } + } + } + DockSeparator {} + DockApps {} + DockSeparator {} + DockButton { + onClicked: Hyprland.dispatch("global quickshell:overviewToggle") + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + font.pixelSize: parent.width / 2 + text: "apps" + color: Appearance.colors.colOnLayer0 + } + } + } + } + } + + } + } + } +} diff --git a/.config/quickshell/modules/dock/DockApps.qml b/.config/quickshell/modules/dock/DockApps.qml new file mode 100644 index 000000000..5dbddfac2 --- /dev/null +++ b/.config/quickshell/modules/dock/DockApps.qml @@ -0,0 +1,17 @@ +import "root:/" +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell.Io +import Quickshell +import Quickshell.Widgets +import Quickshell.Wayland +import Quickshell.Hyprland + +RowLayout { + +} \ No newline at end of file diff --git a/.config/quickshell/modules/dock/DockButton.qml b/.config/quickshell/modules/dock/DockButton.qml new file mode 100644 index 000000000..f80661582 --- /dev/null +++ b/.config/quickshell/modules/dock/DockButton.qml @@ -0,0 +1,15 @@ +import "root:/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +RippleButton { + Layout.fillHeight: true + implicitWidth: background.height + buttonRadius: Appearance.rounding.normal + + topInset: dockVisualBackground.margin + dockRow.padding + bottomInset: dockVisualBackground.margin + dockRow.padding +} diff --git a/.config/quickshell/modules/dock/DockSeparator.qml b/.config/quickshell/modules/dock/DockSeparator.qml new file mode 100644 index 000000000..2b27b0daf --- /dev/null +++ b/.config/quickshell/modules/dock/DockSeparator.qml @@ -0,0 +1,13 @@ +import "root:/" +import "root:/modules/common" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Rectangle { + Layout.topMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal + Layout.bottomMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal + Layout.fillHeight: true + implicitWidth: 1 + color: Appearance.m3colors.m3outlineVariant +} From ba2b7fa1f949ae6db4af5aa928f3cb9301e93cad Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 28 May 2025 11:35:51 +0200 Subject: [PATCH 539/824] shell.qml: make it slightly more accessible for yoinkers --- .../modules/mediaControls/MediaControls.qml | 1 - .config/quickshell/shell.qml | 42 +++++++++++++------ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/.config/quickshell/modules/mediaControls/MediaControls.qml b/.config/quickshell/modules/mediaControls/MediaControls.qml index 8bdba8608..9c21bf710 100644 --- a/.config/quickshell/modules/mediaControls/MediaControls.qml +++ b/.config/quickshell/modules/mediaControls/MediaControls.qml @@ -16,7 +16,6 @@ import Quickshell.Hyprland Scope { id: root - required property var bar property bool visible: false readonly property MprisPlayer activePlayer: MprisController.activePlayer readonly property var realPlayers: Mpris.players.values.filter(player => isRealPlayer(player)) diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index ec506a3b9..005b715b5 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -4,6 +4,7 @@ import "./modules/bar/" import "./modules/cheatsheet/" +import "./modules/dock/" import "./modules/mediaControls/" import "./modules/notificationPopup/" import "./modules/onScreenDisplay/" @@ -20,6 +21,22 @@ import Quickshell import "./services/" ShellRoot { + // Enable/disable modules here. False = not loaded at all, so rest assured + // no unnecessary stuff will take up memory if you decide to only use, say, the overview. + property bool enableBar: true + property bool enableCheatsheet: true + property bool enableDock: true + property bool enableMediaControls: true + property bool enableNotificationPopup: true + property bool enableOnScreenDisplayBrightness: true + property bool enableOnScreenDisplayVolume: true + property bool enableOverview: true + property bool enableReloadPopup: true + property bool enableScreenCorners: true + property bool enableSession: true + property bool enableSidebarLeft: true + property bool enableSidebarRight: true + Component.onCompleted: { MaterialThemeLoader.reapplyTheme() ConfigLoader.loadConfig() @@ -27,17 +44,18 @@ ShellRoot { Cliphist.refresh() } - Bar {} - Cheatsheet {} - MediaControls {} - NotificationPopup {} - OnScreenDisplayBrightness {} - OnScreenDisplayVolume {} - Overview {} - ReloadPopup {} - ScreenCorners {} - Session {} - SidebarLeft {} - SidebarRight {} + Loader { active: enableBar; sourceComponent: Bar {} } + Loader { active: enableCheatsheet; sourceComponent: Cheatsheet {} } + Loader { active: enableDock; sourceComponent: Dock {} } + Loader { active: enableMediaControls; sourceComponent: MediaControls {} } + Loader { active: enableNotificationPopup; sourceComponent: NotificationPopup {} } + Loader { active: enableOnScreenDisplayBrightness; sourceComponent: OnScreenDisplayBrightness {} } + Loader { active: enableOnScreenDisplayVolume; sourceComponent: OnScreenDisplayVolume {} } + Loader { active: enableOverview; sourceComponent: Overview {} } + Loader { active: enableReloadPopup; sourceComponent: ReloadPopup {} } + Loader { active: enableScreenCorners; sourceComponent: ScreenCorners {} } + Loader { active: enableSession; sourceComponent: Session {} } + Loader { active: enableSidebarLeft; sourceComponent: SidebarLeft {} } + Loader { active: enableSidebarRight; sourceComponent: SidebarRight {} } } From d00ffdd0f16ad35f6a38e5bf1e9b0f6cdbd9ad1b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 28 May 2025 19:46:38 +0200 Subject: [PATCH 540/824] readme: remove unnecessary pkgs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 11f7e7675..9ec57df92 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Not ready, but feel free to try it. It's simple: - **Assumption**: You are already using the AGS illogical-impulse -- **Install Qt packages** (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-graphicaleffects qt5-imageformats qt5-svg qt5-translations qt5-wayland qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland syntax-highlighting` +- **Install Qt packages** (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-svg qt5-translations qt5-wayland qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland syntax-highlighting` - **Install quickshell and more stuff**: `yay -S quickshell matugen-bin grimblast wtype` - **Copy** `.config/quickshell` folder and hyprland config files in `.config/hypr/hyprland/` (backing up is your responsibility) (or you can create a new user) - **Run quickshell** with `qs` and see how things are - it's not finished, but **feedback is very welcome** From 2f0a0b88e2b932de7ba80691f421ca4557406973 Mon Sep 17 00:00:00 2001 From: Bishoy Ehab Date: Thu, 29 May 2025 02:20:38 +0300 Subject: [PATCH 541/824] make update.sh script --- update.sh | 829 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 829 insertions(+) create mode 100755 update.sh diff --git a/update.sh b/update.sh new file mode 100755 index 000000000..09da49853 --- /dev/null +++ b/update.sh @@ -0,0 +1,829 @@ +#!/bin/bash +# +# update.sh - Enhanced dotfiles update script +# +# Features: +# - Pull latest commits from remote +# - Rebuild packages if PKGBUILD files changed (user choice) +# - Handle config file conflicts with user choices +# - Respect .updateignore file for exclusions +# +set -uo pipefail + +# === Configuration === +FORCE_CHECK=false +CHECK_PACKAGES=false +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +ARCH_PACKAGES_DIR="${REPO_DIR}/arch-packages" +UPDATE_IGNORE_FILE="${REPO_DIR}/.updateignore" +HOME_UPDATE_IGNORE_FILE="${HOME}/.updateignore" + +# Directories to monitor for changes +MONITOR_DIRS=(".config" ".local/bin") + +# === Color Codes === +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +PURPLE='\033[0;35m' +NC='\033[0m' # No Color + +# === Helper Functions === +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 +} + +log_header() { + echo -e "\n${PURPLE}=== $1 ===${NC}" +} + +die() { + log_error "$1" + exit 1 +} + +# Function to safely read input with terminal compatibility +safe_read() { + local prompt="$1" + local varname="$2" + local default="${3:-}" + + # Simple approach: just use read with /dev/tty and handle errors + local input_value="" + + # Display prompt and read from terminal + echo -n "$prompt" + if read input_value /dev/null || read input_value 2>/dev/null; then + eval "$varname='$input_value'" + return 0 + else + # If read failed and we have a default, use it + if [[ -n "$default" ]]; then + echo + log_warning "Using default: $default" + eval "$varname='$default'" + return 0 + else + echo + log_error "Failed to read input" + return 1 + fi + fi +} + +# Function to check if a file should be ignored +should_ignore() { + local file_path="$1" + local relative_path="${file_path#$HOME/}" + + # Also get path relative to repo for repo-level ignores + local repo_relative="" + if [[ "$file_path" == "$REPO_DIR"* ]]; then + repo_relative="${file_path#$REPO_DIR/}" + fi + + # Check both repo and home ignore files + for ignore_file in "$UPDATE_IGNORE_FILE" "$HOME_UPDATE_IGNORE_FILE"; do + if [[ -f "$ignore_file" ]]; then + while IFS= read -r pattern || [[ -n "$pattern" ]]; do + # Skip empty lines and comments + [[ -z "$pattern" || "$pattern" =~ ^[[:space:]]*# ]] && continue + # Remove leading/trailing whitespace + pattern=$(echo "$pattern" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + [[ -z "$pattern" ]] && continue + + # Handle different gitignore-style patterns + local should_skip=false + + # Exact match + if [[ "$relative_path" == "$pattern" ]] || [[ "$repo_relative" == "$pattern" ]]; then + should_skip=true + fi + + # Wildcard patterns (basic glob matching) + if [[ "$relative_path" == $pattern ]] || [[ "$repo_relative" == $pattern ]]; then + should_skip=true + fi + + # Directory patterns (ending with /) + if [[ "$pattern" == */ ]]; then + local dir_pattern="${pattern%/}" + if [[ "$relative_path" == "$dir_pattern"/* ]] || [[ "$repo_relative" == "$dir_pattern"/* ]]; then + should_skip=true + fi + fi + + # Patterns starting with / (from root) + if [[ "$pattern" == /* ]]; then + local root_pattern="${pattern#/}" + if [[ "$relative_path" == "$root_pattern" ]] || [[ "$relative_path" == "$root_pattern"/* ]] || + [[ "$repo_relative" == "$root_pattern" ]] || [[ "$repo_relative" == "$root_pattern"/* ]]; then + should_skip=true + fi + fi + + # Patterns with wildcards + if [[ "$pattern" == *"*"* ]]; then + if [[ "$relative_path" == $pattern ]] || [[ "$repo_relative" == $pattern ]]; then + should_skip=true + fi + # Also check if any parent directory matches + local temp_path="$relative_path" + while [[ "$temp_path" == */* ]]; do + temp_path="${temp_path%/*}" + if [[ "$temp_path" == $pattern ]]; then + should_skip=true + break + fi + done + fi + + # Simple substring matching (for backward compatibility) + if [[ ! "$should_skip" == true ]]; then + if [[ "$file_path" == *"$pattern"* ]] || [[ "$relative_path" == *"$pattern"* ]]; then + should_skip=true + fi + fi + + if [[ "$should_skip" == true ]]; then + return 0 + fi + done <"$ignore_file" + fi + done + return 1 +} + +# Function to show file diff with syntax highlighting if possible +show_diff() { + local file1="$1" + local file2="$2" + + echo -e "\n${CYAN}Showing differences:${NC}" + echo -e "${CYAN}Old file: $file1${NC}" + echo -e "${CYAN}New file: $file2${NC}" + echo "----------------------------------------" + + if command -v diff &>/dev/null; then + diff -u "$file1" "$file2" || true + else + echo "diff command not available" + fi + echo "----------------------------------------" +} + +# Function to handle file conflicts +handle_file_conflict() { + local repo_file="$1" + local home_file="$2" + local filename=$(basename "$home_file") + local dirname=$(dirname "$home_file") + + echo -e "\n${YELLOW}Conflict detected:${NC} $home_file" + echo "Repository version differs from your local version." + echo + echo "Choose an action:" + echo "1) Replace local file with repository version" + echo "2) Keep local file unchanged" + echo "3) Backup local file as ${filename}.old, use repository version" + echo "4) Save repository version as ${filename}.new, keep local file" + echo "5) Show diff and decide" + echo "6) Skip this file" + echo + + while true; do + if ! safe_read "Enter your choice (1-6): " choice "6"; then + echo + log_warning "Failed to read input. Skipping file." + return + fi + + case $choice in + 1) + cp "$repo_file" "$home_file" + log_success "Replaced $home_file with repository version" + break + ;; + 2) + log_info "Keeping local version of $home_file" + break + ;; + 3) + mv "$home_file" "${dirname}/${filename}.old" + cp "$repo_file" "$home_file" + log_success "Backed up local file to ${filename}.old and updated with repository version" + break + ;; + 4) + cp "$repo_file" "${dirname}/${filename}.new" + log_success "Saved repository version as ${filename}.new, kept local file" + break + ;; + 5) + show_diff "$home_file" "$repo_file" + echo + echo "After reviewing the diff, choose:" + echo "r) Replace with repository version" + echo "k) Keep local version" + echo "b) Backup local and use repository version" + echo "n) Save repository version as .new" + echo "s) Skip this file" + + if ! safe_read "Enter your choice (r/k/b/n/s): " subchoice "s"; then + echo + log_warning "Failed to read input. Skipping file." + return + fi + + case $subchoice in + r) + cp "$repo_file" "$home_file" + log_success "Replaced $home_file with repository version" + break + ;; + k) + log_info "Keeping local version of $home_file" + break + ;; + b) + mv "$home_file" "${dirname}/${filename}.old" + cp "$repo_file" "$home_file" + log_success "Backed up local file to ${filename}.old and updated" + break + ;; + n) + cp "$repo_file" "${dirname}/${filename}.new" + log_success "Saved repository version as ${filename}.new" + break + ;; + s) + log_info "Skipping $home_file" + break + ;; + *) + echo "Invalid choice. Please try again." + ;; + esac + ;; + 6) + log_info "Skipping $home_file" + break + ;; + *) + echo "Invalid choice. Please enter 1-6." + ;; + esac + done +} + +# Function to check if PKGBUILD has changed +check_pkgbuild_changed() { + local pkg_dir="$1" + local pkgbuild_path="${pkg_dir}/PKGBUILD" + + [[ ! -f "$pkgbuild_path" ]] && return 1 + + # Get the path relative to repo + local relative_path="${pkgbuild_path#$REPO_DIR/}" + + # If force check is enabled, always return true + if [[ "$FORCE_CHECK" == true ]]; then + return 0 + fi + + # Check if file changed in the last pull + if git diff --name-only HEAD@{1} HEAD 2>/dev/null | grep -q "^${relative_path}$"; then + return 0 + fi + + return 1 +} + +# Function to list available packages +list_packages() { + local available_packages=() + local changed_packages=() + + if [[ ! -d "$ARCH_PACKAGES_DIR" ]]; then + log_warning "No arch-packages directory found" + return 1 + fi + + for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do + if [[ -f "${pkg_dir}/PKGBUILD" ]]; then + local pkg_name=$(basename "$pkg_dir") + available_packages+=("$pkg_name") + + if check_pkgbuild_changed "$pkg_dir"; then + changed_packages+=("$pkg_name") + fi + fi + done + + if [[ ${#available_packages[@]} -eq 0 ]]; then + log_info "No packages found in arch-packages directory" + return 1 + fi + + echo -e "\n${CYAN}Available packages:${NC}" + for pkg in "${available_packages[@]}"; do + if [[ " ${changed_packages[*]} " =~ " ${pkg} " ]]; then + echo -e " ${GREEN}● ${pkg}${NC} (PKGBUILD changed)" + else + echo -e " ○ ${pkg}" + fi + done + + if [[ ${#changed_packages[@]} -gt 0 ]]; then + echo -e "\n${YELLOW}Packages with changed PKGBUILDs: ${changed_packages[*]}${NC}" + fi + + return 0 +} + +# Function to build selected packages +build_packages() { + local build_mode="$1" # "changed", "all", or "select" + local packages_to_build=() + local rebuilt_packages=0 + + case "$build_mode" in + "changed") + for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do + if [[ -f "${pkg_dir}/PKGBUILD" ]]; then + local pkg_name=$(basename "$pkg_dir") + if check_pkgbuild_changed "$pkg_dir"; then + packages_to_build+=("$pkg_name") + fi + fi + done + ;; + "all") + for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do + if [[ -f "${pkg_dir}/PKGBUILD" ]]; then + local pkg_name=$(basename "$pkg_dir") + packages_to_build+=("$pkg_name") + fi + done + ;; + "select") + echo -e "\nEnter package names separated by spaces (or 'all' for all packages):" + if ! safe_read "Packages to build: " user_selection ""; then + log_warning "Failed to read input. Skipping package builds." + return + fi + + if [[ "$user_selection" == "all" ]]; then + for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do + if [[ -f "${pkg_dir}/PKGBUILD" ]]; then + local pkg_name=$(basename "$pkg_dir") + packages_to_build+=("$pkg_name") + fi + done + else + read -ra packages_to_build <<<"$user_selection" + fi + ;; + esac + + if [[ ${#packages_to_build[@]} -eq 0 ]]; then + log_info "No packages selected for building" + return + fi + + echo -e "\n${CYAN}Packages to build: ${packages_to_build[*]}${NC}" + + if ! safe_read "Proceed with building these packages? (Y/n): " confirm "Y"; then + log_warning "Failed to read input. Skipping package builds." + return + fi + + if [[ "$confirm" =~ ^[Nn]$ ]]; then + log_info "Package building cancelled by user" + return + fi + + for pkg_name in "${packages_to_build[@]}"; do + local pkg_dir="${ARCH_PACKAGES_DIR}/${pkg_name}" + + if [[ ! -d "$pkg_dir" || ! -f "${pkg_dir}/PKGBUILD" ]]; then + log_error "Package not found or missing PKGBUILD: $pkg_name" + continue + fi + + log_info "Building package: $pkg_name" + cd "$pkg_dir" || continue + + if makepkg -si --noconfirm; then + log_success "Successfully built and installed $pkg_name" + ((rebuilt_packages++)) + else + log_error "Failed to build package $pkg_name" + fi + + cd "$REPO_DIR" || die "Failed to return to repository directory" + done + + if [[ $rebuilt_packages -eq 0 ]]; then + log_warning "No packages were successfully built" + else + log_success "Successfully rebuilt $rebuilt_packages package(s)" + fi +} + +# Function to get list of changed files since last pull or all files if force check +get_changed_files() { + local dir_path="$1" + + if [[ "$FORCE_CHECK" == true ]]; then + # Return all files in the directory + find "$dir_path" -type f -print0 2>/dev/null + else + # Get files that changed in the last pull + local changed_files=() + while IFS= read -r file; do + local full_path="${REPO_DIR}/${file}" + # Check if file is in the directory we're processing + if [[ "$full_path" == "$dir_path"/* ]] && [[ -f "$full_path" ]]; then + printf '%s\0' "$full_path" + fi + done < <(git diff --name-only HEAD@{1} HEAD 2>/dev/null || true) + + # If no files changed via git, but force_check is false, still check all files + # This handles the case where there were no new commits but files might differ + if ! git diff --quiet HEAD@{1} HEAD 2>/dev/null; then + : # Files were found via git diff + else + # No git changes detected, check all files anyway for local differences + find "$dir_path" -type f -print0 2>/dev/null + fi + fi +} + +# Function to check if we have new commits +has_new_commits() { + # Check if HEAD@{1} exists (meaning there was a previous commit) + if git rev-parse --verify HEAD@{1} &>/dev/null; then + # Check if HEAD and HEAD@{1} are different + [[ "$(git rev-parse HEAD)" != "$(git rev-parse HEAD@{1})" ]] + else + # No previous commit reference, assume we have commits + return 0 + fi +} + +# Main script starts here +log_header "Dotfiles Update Script" + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -f | --force) + FORCE_CHECK=true + log_info "Force check mode enabled - will check all files regardless of git changes" + shift + ;; + -p | --packages) + CHECK_PACKAGES=true + log_info "Package checking enabled" + shift + ;; + -h | --help) + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " -f, --force Force check all files even if no new commits" + echo " -p, --packages Enable package checking and building" + echo " -h, --help Show this help message" + echo "" + echo "This script updates your dotfiles by:" + echo " 1. Pulling latest changes from git remote" + echo " 2. Optionally rebuilding packages (if -p flag is used)" + echo " 3. Syncing configuration files" + echo " 4. Updating script permissions" + echo "" + echo "Package modes (when -p is used):" + echo " - If no PKGBUILDs changed: asks if you want to check packages anyway" + echo " - If PKGBUILDs changed: offers to build changed packages" + echo " - Interactive selection of packages to build" + exit 0 + ;; + *) + log_error "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac +done + +# Check if we're in a git repository +if [[ ! -d "${REPO_DIR}/.git" ]]; then + die "Not in a git repository. Please run this script from your dotfiles repository." +fi + +cd "$REPO_DIR" || die "Failed to change to repository directory" + +# Step 1: Pull latest commits +log_header "Pulling Latest Changes" + +# Check current branch +current_branch=$(git branch --show-current) +if [[ -z "$current_branch" ]]; then + log_warning "In detached HEAD state. Checking out main/master branch..." + if git show-ref --verify --quiet refs/heads/main; then + git checkout main + current_branch="main" + elif git show-ref --verify --quiet refs/heads/master; then + git checkout master + current_branch="master" + else + die "Could not find main or master branch" + fi +fi + +log_info "Current branch: $current_branch" + +# Check for uncommitted changes +if ! git diff --quiet || ! git diff --cached --quiet; then + log_warning "You have uncommitted changes:" + git status --short + echo + + if ! safe_read "Do you want to continue? This will stash your changes. (y/N): " response "N"; then + echo + log_error "Failed to read input. Aborting." + exit 1 + fi + + if [[ ! "$response" =~ ^[Yy]$ ]]; then + die "Aborted by user" + fi + git stash push -m "Auto-stash before update $(date)" + log_info "Changes stashed" +fi + +# Check if remote exists +if git remote get-url origin &>/dev/null; then + # Pull changes + log_info "Pulling changes from origin/$current_branch..." + if git pull origin "$current_branch"; then + log_success "Successfully pulled latest changes" + else + log_warning "Failed to pull changes from remote. Continuing with local repository..." + log_info "You may need to resolve conflicts manually later." + fi +else + log_warning "No remote 'origin' configured. Skipping pull operation." + log_info "This appears to be a local-only repository." +fi + +# Step 2: Handle package building (only if requested) +rebuilt_packages=0 + +if [[ "$CHECK_PACKAGES" == true ]]; then + log_header "Package Management" + + if [[ ! -d "$ARCH_PACKAGES_DIR" ]]; then + log_warning "No arch-packages directory found. Skipping package management." + else + # Check if any PKGBUILDs have changed + changed_pkgbuilds=() + for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do + if [[ -f "${pkg_dir}/PKGBUILD" ]]; then + local pkg_name=$(basename "$pkg_dir") + if check_pkgbuild_changed "$pkg_dir"; then + changed_pkgbuilds+=("$pkg_name") + fi + fi + done + + if [[ ${#changed_pkgbuilds[@]} -gt 0 ]]; then + log_info "Found ${#changed_pkgbuilds[@]} package(s) with changed PKGBUILDs: ${changed_pkgbuilds[*]}" + echo + echo "Package build options:" + echo "1) Build only packages with changed PKGBUILDs" + echo "2) List all packages and select which to build" + echo "3) Build all packages" + echo "4) Skip package building" + echo + + if safe_read "Choose an option (1-4): " pkg_choice "1"; then + case $pkg_choice in + 1) + build_packages "changed" + ;; + 2) + if list_packages; then + build_packages "select" + fi + ;; + 3) + build_packages "all" + ;; + 4 | *) + log_info "Skipping package building" + ;; + esac + else + log_warning "Failed to read input. Skipping package building." + fi + else + log_info "No PKGBUILDs have changed since last update." + echo + if safe_read "Do you want to check and build packages anyway? (y/N): " check_anyway "N"; then + if [[ "$check_anyway" =~ ^[Yy]$ ]]; then + if list_packages; then + echo + echo "Package build options:" + echo "1) Select specific packages to build" + echo "2) Build all packages" + echo "3) Skip package building" + + if safe_read "Choose an option (1-3): " build_choice "3"; then + case $build_choice in + 1) + build_packages "select" + ;; + 2) + build_packages "all" + ;; + 3 | *) + log_info "Skipping package building" + ;; + esac + else + log_info "Skipping package building" + fi + fi + else + log_info "Skipping package management" + fi + else + log_info "Skipping package management" + fi + fi + fi +else + log_header "Package Management" + log_info "Package checking disabled. Use -p or --packages flag to enable package management." + + # Still show a hint if there are changed PKGBUILDs + if [[ -d "$ARCH_PACKAGES_DIR" ]]; then + changed_count=0 + for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do + if [[ -f "${pkg_dir}/PKGBUILD" ]] && check_pkgbuild_changed "$pkg_dir"; then + ((changed_count++)) + fi + done + + if [[ $changed_count -gt 0 ]]; then + log_warning "Note: $changed_count package(s) have changed PKGBUILDs. Use -p flag to manage packages." + fi + fi +fi + +# Step 3: Update configuration files +log_header "Updating Configuration Files" + +# Check if we should process files +process_files=false +if [[ "$FORCE_CHECK" == true ]]; then + process_files=true + log_info "Force mode: checking all configuration files" +elif has_new_commits; then + process_files=true + log_info "New commits detected: checking changed configuration files" +else + log_info "No new commits found: checking for local file differences" + process_files=true # Always check for differences even without commits +fi + +if [[ "$process_files" == true ]]; then + files_processed=0 + files_updated=0 + files_created=0 + + for dir_name in "${MONITOR_DIRS[@]}"; do + repo_dir_path="${REPO_DIR}/${dir_name}" + home_dir_path="${HOME}/${dir_name}" + + if [[ ! -d "$repo_dir_path" ]]; then + log_warning "Repository directory not found: $repo_dir_path" + continue + fi + + log_info "Processing directory: $dir_name" + + # Create home directory if it doesn't exist + mkdir -p "$home_dir_path" + + # Get files to process (changed files or all files based on mode) + while IFS= read -r -d '' repo_file; do + # Calculate relative path and corresponding home file path + rel_path="${repo_file#$repo_dir_path/}" + home_file="${home_dir_path}/${rel_path}" + + # Check if file should be ignored + if should_ignore "$home_file"; then + continue + fi + + ((files_processed++)) + + # Create directory structure if needed + mkdir -p "$(dirname "$home_file")" + + if [[ -f "$home_file" ]]; then + # File exists, check if different + if ! cmp -s "$repo_file" "$home_file"; then + log_info "Found difference in: $rel_path" + handle_file_conflict "$repo_file" "$home_file" + ((files_updated++)) + fi + else + # New file, copy it + cp "$repo_file" "$home_file" + log_success "Created new file: $home_file" + ((files_created++)) + fi + done < <(get_changed_files "$repo_dir_path") + done + + # Show processing summary + echo + log_info "File processing summary:" + log_info "- Files processed: $files_processed" + log_info "- Files with conflicts: $files_updated" + log_info "- New files created: $files_created" +else + log_info "Skipping file updates (no changes detected and not in force mode)" +fi + +# Step 4: Update script permissions +log_header "Updating Script Permissions" + +if [[ -d "${REPO_DIR}/scriptdata" ]]; then + find "${REPO_DIR}/scriptdata" -type f -name "*.sh" -exec chmod +x {} \; + find "${REPO_DIR}/scriptdata" -type f -executable -exec chmod +x {} \; + log_success "Updated script permissions" +fi + +# Make sure local bin scripts are executable +if [[ -d "${HOME}/.local/bin" ]]; then + find "${HOME}/.local/bin" -type f -exec chmod +x {} \; 2>/dev/null || true + log_success "Updated ~/.local/bin script permissions" +fi + +log_header "Update Complete" +log_success "Dotfiles update completed successfully!" + +# Show summary +echo +echo -e "${CYAN}Summary:${NC}" +echo "- Repository: $(git log -1 --pretty=format:'%h - %s (%cr)')" +echo "- Branch: $current_branch" +echo "- Mode: $([ "$FORCE_CHECK" == true ] && echo "Force check" || echo "Normal")" +echo "- Package checking: $([ "$CHECK_PACKAGES" == true ] && echo "Enabled" || echo "Disabled")" + +if [[ $rebuilt_packages -gt 0 ]]; then + echo "- Packages rebuilt: $rebuilt_packages" +fi + +if [[ "$process_files" == true ]]; then + echo "- Files processed: $files_processed" + echo "- Files updated/conflicted: $files_updated" + echo "- New files created: $files_created" +fi + +echo "- Configuration directories: ${MONITOR_DIRS[*]}" + +# Remind about ignore files and show examples +if [[ ! -f "$HOME_UPDATE_IGNORE_FILE" && ! -f "$UPDATE_IGNORE_FILE" ]]; then + echo + log_info "Tip: Create ignore files to exclude files from updates:" + echo " - Repository ignore: ${REPO_DIR}/.updateignore" + echo " - User ignore: ~/.updateignore" + echo + echo "Example patterns:" + echo " *.log # Ignore all .log files" + echo " .config/personal/ # Ignore entire directory" + echo " secret-config.conf # Ignore specific file" + echo " /temp-file # Ignore from root only" + echo " *secret* # Ignore files containing 'secret'" +fi + +echo From 9503ca2129a1e1125bd0d045d9c8b1d1a2d5a017 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 09:21:56 +0200 Subject: [PATCH 542/824] cliphist image: cleanup decoded img on destruction --- .config/quickshell/modules/common/widgets/CliphistImage.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.config/quickshell/modules/common/widgets/CliphistImage.qml b/.config/quickshell/modules/common/widgets/CliphistImage.qml index f0b07d3f1..088f6373a 100644 --- a/.config/quickshell/modules/common/widgets/CliphistImage.qml +++ b/.config/quickshell/modules/common/widgets/CliphistImage.qml @@ -69,6 +69,10 @@ Rectangle { } } + Component.onDestruction: { + Hyprland.dispatch(`exec bash -c "[ -f '${imageDecodeFilePath}' ] && rm -f '${imageDecodeFilePath}'"`) + } + Image { id: image anchors.fill: parent From 5070b51e487d3ed2d79b290533e08df6479478ed Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 09:22:12 +0200 Subject: [PATCH 543/824] clipboard: decode images to /tmp --- .config/quickshell/modules/common/Directories.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/Directories.qml b/.config/quickshell/modules/common/Directories.qml index fd18f193a..2a1b55b72 100644 --- a/.config/quickshell/modules/common/Directories.qml +++ b/.config/quickshell/modules/common/Directories.qml @@ -28,7 +28,7 @@ Singleton { property string todoPath: FileUtils.trimFileProtocol(`${Directories.state}/user/todo.json`) property string notificationsPath: FileUtils.trimFileProtocol(`${Directories.cache}/notifications/notifications.json`) property string generatedMaterialThemePath: FileUtils.trimFileProtocol(`${Directories.state}/user/generated/colors.json`) - property string cliphistDecode: FileUtils.trimFileProtocol(`${Directories.cache}/media/cliphist`) + property string cliphistDecode: FileUtils.trimFileProtocol(`/tmp/quickshell/media/cliphist`) // Cleanup on init Component.onCompleted: { Hyprland.dispatch(`exec mkdir -p '${favicons}'`) From 2553c3ba1dcb36bdf0044f271de9ffc1f0242da9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 09:22:26 +0200 Subject: [PATCH 544/824] shell.qml: add comment --- .config/quickshell/shell.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 005b715b5..93cc58f7b 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -37,6 +37,7 @@ ShellRoot { property bool enableSidebarLeft: true property bool enableSidebarRight: true + // Force initialization of some singletons Component.onCompleted: { MaterialThemeLoader.reapplyTheme() ConfigLoader.loadConfig() From c58c3d2174ee3c363d8df8a096b2e946bca67c49 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 09:22:31 +0200 Subject: [PATCH 545/824] dock: apps --- .config/quickshell/modules/dock/DockApps.qml | 41 +++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/dock/DockApps.qml b/.config/quickshell/modules/dock/DockApps.qml index 5dbddfac2..1383f41f0 100644 --- a/.config/quickshell/modules/dock/DockApps.qml +++ b/.config/quickshell/modules/dock/DockApps.qml @@ -2,6 +2,7 @@ import "root:/" import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/icons.js" as Icons import QtQuick import QtQuick.Controls import QtQuick.Effects @@ -13,5 +14,43 @@ import Quickshell.Wayland import Quickshell.Hyprland RowLayout { - + readonly property list windowList: HyprlandData.windowList + readonly property list apps: { + let uniqueClasses = new Set() + for (let window of windowList) { + if (window.class && window.class.trim() !== "") { + uniqueClasses.add(window.class) + } + } + return Array.from(uniqueClasses) + } + readonly property var windowsByApp: { + let grouped = {} + for (let window of windowList) { + if (window.class && window.class.trim() !== "") { + if (!grouped[window.class]) { + grouped[window.class] = [] + } + grouped[window.class].push(window) + } + } + return grouped + } + + Repeater { + model: apps + delegate: DockButton { + required property string modelData + property int lastFocusedIndex: -1 + contentItem: IconImage { + source: Quickshell.iconPath(Icons.noKnowledgeIconGuess(modelData), "image-missing") + } + onClicked: () => { + lastFocusedIndex = (lastFocusedIndex + 1) % windowsByApp[modelData].length + const targetWindow = windowsByApp[modelData][lastFocusedIndex]; + const targetAddress = targetWindow.address; + Hyprland.dispatch(`focuswindow address:${targetAddress}`); + } + } + } } \ No newline at end of file From 0ff0efe39c0b75df89d1b4fe4d78eb2df15b04b9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 11:19:45 +0200 Subject: [PATCH 546/824] cliphist: refresh on clipboard change --- .config/quickshell/services/Cliphist.qml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.config/quickshell/services/Cliphist.qml b/.config/quickshell/services/Cliphist.qml index 51514e864..a3292d09a 100644 --- a/.config/quickshell/services/Cliphist.qml +++ b/.config/quickshell/services/Cliphist.qml @@ -43,6 +43,13 @@ Singleton { readProc.running = true } + Connections { + target: Quickshell + function onClipboardTextChanged() { + root.refresh() // TODO: Account for race condition with cliphist + } + } + Process { id: readProc property list buffer: [] From 72fa76fde600cea4a705e8b1a93b10916325b3ab Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 11:20:08 +0200 Subject: [PATCH 547/824] clipboard history: checkmark for selected entry --- .../quickshell/modules/overview/SearchItem.qml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index cd415bb3b..e0e02ec11 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -163,6 +163,23 @@ RippleButton { url: modelData } } + Loader { + visible: itemName == Quickshell.clipboardText + active: itemName == Quickshell.clipboardText + sourceComponent: Rectangle { + implicitWidth: activeText.implicitHeight + implicitHeight: activeText.implicitHeight + radius: Appearance.rounding.full + color: Appearance.m3colors.m3primary + MaterialSymbol { + id: activeText + anchors.centerIn: parent + text: "check" + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3onPrimary + } + } + } StyledText { Layout.fillWidth: true id: nameText From 5938e6f6321f9f550ca97c524555cdcfcd8138c4 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 11:26:21 +0200 Subject: [PATCH 548/824] search item: reorder: checkmark goes first --- .../modules/overview/SearchItem.qml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index e0e02ec11..02e7c7378 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -155,15 +155,7 @@ RippleButton { text: root.itemType } RowLayout { - Repeater { - model: root.query == root.itemName ? [] : root.urls - Favicon { - required property var modelData - size: parent.height - url: modelData - } - } - Loader { + Loader { // Checkmark for copied clipboard entry visible: itemName == Quickshell.clipboardText active: itemName == Quickshell.clipboardText sourceComponent: Rectangle { @@ -180,7 +172,15 @@ RippleButton { } } } - StyledText { + Repeater { // Favicons for links + model: root.query == root.itemName ? [] : root.urls + Favicon { + required property var modelData + size: parent.height + url: modelData + } + } + StyledText { // Item name/content Layout.fillWidth: true id: nameText textFormat: Text.StyledText // RichText also works, but StyledText ensures elide work From a8533235c168984fc7a71a3adb4c7d9c2a7227c1 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 11:38:12 +0200 Subject: [PATCH 549/824] clipboard search: do not search by entry id --- .config/quickshell/services/Cliphist.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/services/Cliphist.qml b/.config/quickshell/services/Cliphist.qml index a3292d09a..0178367f9 100644 --- a/.config/quickshell/services/Cliphist.qml +++ b/.config/quickshell/services/Cliphist.qml @@ -16,7 +16,7 @@ Singleton { property real scoreThreshold: 0.2 property list entries: [] readonly property var preparedEntries: entries.map(a => ({ - name: Fuzzy.prepare(`${a}`), + name: Fuzzy.prepare(`${a.replace(/^\s*\S+\s+/, "")}`), entry: a })) function fuzzyQuery(search: string): var { From 89203e8794727a9040f94e098ee42efb0dc21dbd Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 11:41:58 +0200 Subject: [PATCH 550/824] cliphist service: delay a bit before grabbing entries from cliphist --- .config/quickshell/services/Cliphist.qml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/services/Cliphist.qml b/.config/quickshell/services/Cliphist.qml index 0178367f9..d0193f144 100644 --- a/.config/quickshell/services/Cliphist.qml +++ b/.config/quickshell/services/Cliphist.qml @@ -46,7 +46,16 @@ Singleton { Connections { target: Quickshell function onClipboardTextChanged() { - root.refresh() // TODO: Account for race condition with cliphist + delayedUpdateTimer.restart() + } + } + + Timer { + id: delayedUpdateTimer + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + onTriggered: { + root.refresh() } } From 4b0ee5b8aba70d60888132c2a308d53a01eab2b5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 11:44:52 +0200 Subject: [PATCH 551/824] clipboard history: remove unnecessary refreshes --- .config/quickshell/modules/overview/Overview.qml | 1 - .config/quickshell/modules/overview/SearchWidget.qml | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 5711a9434..969145179 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -192,7 +192,6 @@ Scope { GlobalStates.overviewOpen = false; return; } - Cliphist.refresh() for (let i = 0; i < overviewVariants.instances.length; i++) { let panelWindow = overviewVariants.instances[i]; if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 84de2f6f6..a49ae63a3 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -23,11 +23,6 @@ Item { // Wrapper implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2 property string mathResult: "" - property bool lastQueryWasClipboard: false - - onShowResultsChanged: { - lastQueryWasClipboard = false; - } function disableExpandAnimation() { searchWidthBehavior.enabled = false; @@ -298,10 +293,6 @@ Item { // Wrapper ///////////// Special cases /////////////// if (root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard)) { // Clipboard - if (!root.lastQueryWasClipboard) { - root.lastQueryWasClipboard = true; - Cliphist.refresh(); // Refresh clipboard entries - } const searchString = root.searchingText.slice(ConfigOptions.search.prefix.clipboard.length); return Cliphist.fuzzyQuery(searchString).map(entry => { return { @@ -311,7 +302,6 @@ Item { // Wrapper type: `#${entry.match(/^\s*(\S+)/)?.[1] || ""}`, execute: () => { Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(entry)}' | cliphist decode | wl-copy`); - Cliphist.refresh() } }; }).filter(Boolean); From 0d3dfe4b57f0c1d7d18b457e72aaae63ab6ef668 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 12:21:28 +0200 Subject: [PATCH 552/824] clipboard copied checkmark: only show for clipboard entries --- .config/quickshell/modules/overview/SearchItem.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index 02e7c7378..db3a3d265 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -156,8 +156,8 @@ RippleButton { } RowLayout { Loader { // Checkmark for copied clipboard entry - visible: itemName == Quickshell.clipboardText - active: itemName == Quickshell.clipboardText + visible: itemName == Quickshell.clipboardText && root.cliphistRawString + active: itemName == Quickshell.clipboardText && root.cliphistRawString sourceComponent: Rectangle { implicitWidth: activeText.implicitHeight implicitHeight: activeText.implicitHeight From e35f1ae3fd6ab226fa7acc1b599f7481f7d243fa Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 13:23:47 +0200 Subject: [PATCH 553/824] hyprland: make shadow more visible --- .config/hypr/hyprland/general.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/hypr/hyprland/general.conf b/.config/hypr/hyprland/general.conf index ff79a5b3a..4acc0d81e 100644 --- a/.config/hypr/hyprland/general.conf +++ b/.config/hypr/hyprland/general.conf @@ -66,7 +66,7 @@ decoration { range = 70 offset = 0 4 render_power = 2 - color = rgba(00000008) + color = rgba(00000020) } # Dim From 3758bdb338123c5fcdd7d17d916bd3f7a53cd432 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 13:24:11 +0200 Subject: [PATCH 554/824] hyprland: rename bezier curves --- .config/hypr/hyprland/general.conf | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/.config/hypr/hyprland/general.conf b/.config/hypr/hyprland/general.conf index 4acc0d81e..2b0bdbde5 100644 --- a/.config/hypr/hyprland/general.conf +++ b/.config/hypr/hyprland/general.conf @@ -78,19 +78,22 @@ decoration { animations { enabled = true # Curves - bezier = md3_decel, 0.05, 0.7, 0.1, 1 - bezier = md3_accel, 0.3, 0, 0.8, 0.15 - bezier = md2_decel, 0, 0, 0, 1 + bezier = expressiveFastSpatial, 0.42, 1.67, 0.21, 0.90 + bezier = expressiveSlowSpatial, 0.39, 1.29, 0.35, 0.98 + bezier = expressiveDefaultSpatial, 0.38, 1.21, 0.22, 1.00 + bezier = emphasizedDecel, 0.05, 0.7, 0.1, 1 + bezier = emphasizedAccel, 0.3, 0, 0.8, 0.15 + bezier = standardDecel, 0, 0, 0, 1 bezier = menu_decel, 0.1, 1, 0, 1 bezier = menu_accel, 0.52, 0.03, 0.72, 0.08 # Configs # windows - animation = windowsIn, 1, 3, md3_decel, popin 80% - animation = windowsOut, 1, 2, md3_decel, popin 90% - animation = windowsMove, 1, 3, md3_decel, slide - animation = border, 1, 10, md3_decel + animation = windowsIn, 1, 3, emphasizedDecel, popin 80% + animation = windowsOut, 1, 2, emphasizedDecel, popin 90% + animation = windowsMove, 1, 3, emphasizedDecel, slide + animation = border, 1, 10, emphasizedDecel # layers - animation = layersIn, 1, 2.7, md3_decel, popin 93% + animation = layersIn, 1, 2.7, emphasizedDecel, popin 93% animation = layersOut, 1, 1.8, menu_accel, popin 94% # fade animation = fadeLayersIn, 1, 0.5, menu_decel @@ -98,8 +101,8 @@ animations { # workspaces animation = workspaces, 1, 7, menu_decel, slide ## specialWorkspace - animation = specialWorkspaceIn, 1, 2.8, md3_decel, slidevert - animation = specialWorkspaceOut, 1, 1.2, md3_accel, slidevert + animation = specialWorkspaceIn, 1, 2.8, emphasizedDecel, slidevert + animation = specialWorkspaceOut, 1, 1.2, emphasizedAccel, slidevert } input { From a2eba2faf86c7af18a3201a07db667e3ef32a9cd Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 21:29:21 +0200 Subject: [PATCH 555/824] qt apps: use kde-material-you-colors --- .config/hypr/hyprland/env.conf | 2 +- .config/matugen/config.toml | 5 ++ .config/matugen/templates/kde/color.txt | 1 + .config/matugen/templates/kde/config.conf | 74 +++++++++++++++++++ .../kde/kde-material-you-colors-wrapper.sh | 14 ++++ README.md | 2 +- 6 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 .config/matugen/templates/kde/color.txt create mode 100644 .config/matugen/templates/kde/config.conf create mode 100755 .config/matugen/templates/kde/kde-material-you-colors-wrapper.sh diff --git a/.config/hypr/hyprland/env.conf b/.config/hypr/hyprland/env.conf index d168b9310..4d1fa8f7d 100644 --- a/.config/hypr/hyprland/env.conf +++ b/.config/hypr/hyprland/env.conf @@ -10,7 +10,7 @@ env = INPUT_METHOD, fcitx # ############ Themes ############# env = QT_QPA_PLATFORM, wayland -env = QT_QPA_PLATFORMTHEME, qt6ct +env = QT_QPA_PLATFORMTHEME, kde # env = QT_STYLE_OVERRIDE,kvantum # env = WLR_NO_HARDWARE_CURSORS, 1 diff --git a/.config/matugen/config.toml b/.config/matugen/config.toml index 1a7f41b1d..dcfb61aac 100644 --- a/.config/matugen/config.toml +++ b/.config/matugen/config.toml @@ -41,3 +41,8 @@ output_path = '~/.config/ags/assets/themes/sourceviewtheme-light.xml' input_path = '~/.config/matugen/templates/ags/_material.scss' output_path = '~/.local/state/ags/scss/_material.scss' post_hook = 'pidof gjs && agsv1 run-js "handleStyles(false)"' + +[templates.kde_colors] +input_path = '~/.config/matugen/templates/kde/color.txt' +output_path = '~/.local/state/quickshell/user/generated/color.txt' +post_hook = '~/.config/matugen/templates/kde/kde-material-you-colors-wrapper.sh' diff --git a/.config/matugen/templates/kde/color.txt b/.config/matugen/templates/kde/color.txt new file mode 100644 index 000000000..35142904f --- /dev/null +++ b/.config/matugen/templates/kde/color.txt @@ -0,0 +1 @@ +{{colors.source_color.default.hex}} \ No newline at end of file diff --git a/.config/matugen/templates/kde/config.conf b/.config/matugen/templates/kde/config.conf new file mode 100644 index 000000000..1880d7662 --- /dev/null +++ b/.config/matugen/templates/kde/config.conf @@ -0,0 +1,74 @@ +[CUSTOM] +# INSTRUCTIONS +# Run kde-material-you-colors with no arguments from terminal +# to debug your configuration changing in real time. + +# Monitor to get wallpaper from +# For me main is 0 but second one is 6, play with this to find yours +# Default is 0 +monitor = 0 + +# File containing absolute path of an image (Takes precedence over automatic wallpaper detection) +# Commented by default +file = /home/end/.local/state/quickshell/user/wallpaper.txt + +# List of 7 space separated colors (hex or rgb) to be used for text in pywal/konsole/KSyntaxHighlighting instead of wallpaper ones +# Accepted values are hex e.g #ff0000 and rgb e.g 255,0,0 colors (rgb is converted to hex) +# Commented by default +# Example using catppuccin color scheme: +custom_colors_list = #ED8796 #A6DA95 #EED49F #8AADF4 #F5BDE6 #8BD5CA #f5a97f + +# Enable Light mode +# Accepted values are True or False +# Commented by default to follow System Color Setting (Material You Light/Dark only) +# NOTE: +# Will fallback to dark mode if not defined here or enabled in Settings +#light = False + +# Alternative color mode (default is 0), some images return more than one color, this will use either the matched or last color +# Default is 0 +ncolor = 0 + +# Light scheme icons theme +iconslight = OneUI-light + +# Dark scheme icons theme +iconsdark = OneUI-dark + +# Use pywal to theme other programs using Material You colors +pywal=False + +# The amount of perceptible color for backgrounds in dark mode +# A number between 0 and 4.0 (limited for accessibility purposes) +# Defaults to 1 if not set +#light_blend_multiplier = 1.0 + +# The amount of perceptible color for backgrounds in dark mode +# A number between 0 and 4.0 (limited for accessibility purposes) +# Defaults to 1 if not set +#dark_blend_multiplier = 1.0 + +# A script/command that will be executed on start or wallpaper/dark/light/settings change +# example below using https://github.com/vlevit/notify-send.sh to send a desktop notification: +#on_change_hook = notify-send.sh "kde-material-you-colors" "This is a test" -t 2000 + +# Scheme Variant +# Changes between Material You scheme variants (0-8) +# 0 = Content +# 1 = Expressive +# 2 = Fidelity +# 3 = Monochrome +# 4 = Neutral +# 5 = TonalSpot +# 6 = Vibrant +# 7 = Rainbow +# 8 = FruitSalad +# Default is 5 +scheme_variant = 5 + +# Colorfulness +chroma_multiplier = 1 + +# Brightness +# An integer between 0.5 and 1.5 +tone_multiplier = 1 diff --git a/.config/matugen/templates/kde/kde-material-you-colors-wrapper.sh b/.config/matugen/templates/kde/kde-material-you-colors-wrapper.sh new file mode 100755 index 000000000..02856d603 --- /dev/null +++ b/.config/matugen/templates/kde/kde-material-you-colors-wrapper.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}" + +color=$(tr -d '\n' < "$XDG_STATE_HOME/quickshell/user/generated/color.txt") + +current_mode=$(gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null | tr -d "'") +if [[ "$current_mode" == "prefer-dark" ]]; then + mode_flag="-d" +else + mode_flag="-l" +fi + +kde-material-you-colors "$mode_flag" --color "$color" diff --git a/README.md b/README.md index 9ec57df92..38a00e61a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ - **Assumption**: You are already using the AGS illogical-impulse - **Install Qt packages** (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-svg qt5-translations qt5-wayland qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland syntax-highlighting` -- **Install quickshell and more stuff**: `yay -S quickshell matugen-bin grimblast wtype` +- **Install quickshell and more stuff**: `yay -S quickshell matugen-bin grimblast wtype kde-material-you-colors` - **Copy** `.config/quickshell` folder and hyprland config files in `.config/hypr/hyprland/` (backing up is your responsibility) (or you can create a new user) - **Run quickshell** with `qs` and see how things are - it's not finished, but **feedback is very welcome** - We currently have bar, right sidebar, search/overview From 832f1ec5714a0a04e4a5662837d453ec8de89156 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 21:52:33 +0200 Subject: [PATCH 556/824] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 38a00e61a..0f1e27caa 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ - **Assumption**: You are already using the AGS illogical-impulse - **Install Qt packages** (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-svg qt5-translations qt5-wayland qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland syntax-highlighting` - **Install quickshell and more stuff**: `yay -S quickshell matugen-bin grimblast wtype kde-material-you-colors` +- **Dolphin fix** for it asking what program to open file with every time: `sudo pacman -S archlinux-xdg-menu && XDG_MENU_PREFIX=arch- kbuildsycoca6` - **Copy** `.config/quickshell` folder and hyprland config files in `.config/hypr/hyprland/` (backing up is your responsibility) (or you can create a new user) - **Run quickshell** with `qs` and see how things are - it's not finished, but **feedback is very welcome** - We currently have bar, right sidebar, search/overview @@ -141,6 +142,7 @@ _Get yande.re and konachan images from sidebar_ - [@clsty](https://github.com/clsty) for making an actually good install script + many other stuff that I neglect - [@midn8hustlr](https://github.com/midn8hustlr) for greatly improving the color generation system + - Quickshell: [Soramane](https://github.com/caelestia-dots/shell/), [FridayFaerie](https://github.com/FridayFaerie/quickshell), [nydragon](https://github.com/nydragon/nysh) - AGS: [Aylur's config](https://github.com/Aylur/dotfiles/tree/ags-pre-ts), [kotontrion's config](https://github.com/kotontrion/dotfiles) - EWW: [fufexan's config](https://github.com/fufexan/dotfiles) (he thanks more people there btw) - AI bots for providing useful examples From 8fdb2490c6b5adcf85607e803a7254cd2047f54b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 21:58:02 +0200 Subject: [PATCH 557/824] color generation: fix qt apps not getting correct light/dark mode --- .config/quickshell/scripts/switchwall.sh | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.config/quickshell/scripts/switchwall.sh b/.config/quickshell/scripts/switchwall.sh index 2097e9082..a5954508e 100755 --- a/.config/quickshell/scripts/switchwall.sh +++ b/.config/quickshell/scripts/switchwall.sh @@ -10,12 +10,6 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" terminalscheme="$XDG_CONFIG_HOME/quickshell/scripts/terminal/scheme-base.json" pre_process() { - if [ ! -d "$CACHE_DIR"/user/generated ]; then - mkdir -p "$CACHE_DIR"/user/generated - fi -} - -post_process() { local mode_flag="$1" # Set GNOME color-scheme if mode_flag is dark or light if [[ "$mode_flag" == "dark" ]]; then @@ -25,6 +19,14 @@ post_process() { gsettings set org.gnome.desktop.interface color-scheme 'prefer-light' gsettings set org.gnome.desktop.interface gtk-theme 'adw-gtk3' fi + + if [ ! -d "$CACHE_DIR"/user/generated ]; then + mkdir -p "$CACHE_DIR"/user/generated + fi +} + +post_process() { + true } check_and_prompt_upscale() { @@ -208,7 +210,7 @@ switch() { generate_colors_material_args+=(--termscheme "$terminalscheme" --blend_bg_fg) generate_colors_material_args+=(--cache "$STATE_DIR/user/color.txt") - pre_process + pre_process "$mode_flag" matugen "${matugen_args[@]}" source "$(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate" @@ -217,7 +219,7 @@ switch() { "$SCRIPT_DIR"/applycolor.sh deactivate - post_process "$mode_flag" + post_process } main() { From 9cbbaff170c4e56a98ffd3f093d2eed9729b17e7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 22:12:35 +0200 Subject: [PATCH 558/824] add kdeglobals --- .config/kdeglobals | 180 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 .config/kdeglobals diff --git a/.config/kdeglobals b/.config/kdeglobals new file mode 100644 index 000000000..f1e47ee9f --- /dev/null +++ b/.config/kdeglobals @@ -0,0 +1,180 @@ +[ColorEffects:Disabled] +ChangeSelectionColor= +Color=#211f24 +ColorAmount=0.5 +ColorEffect=3 +ContrastAmount=0 +ContrastEffect=0 +Enable= +IntensityAmount=0 +IntensityEffect=0 + +[ColorEffects:Inactive] +ChangeSelectionColor=true +Color=#0c0a10 +ColorAmount=0.025 +ColorEffect=0 +ContrastAmount=0.1 +ContrastEffect=0 +Enable=true +IntensityAmount=0 +IntensityEffect=0 + +[Colors:Button] +BackgroundAlternate=#47434c +BackgroundNormal=#2b292f +DecorationFocus=#cdb9fb +DecorationHover=#cdb9fb +ForegroundActive=#e6e0e9 +ForegroundInactive=#948f99 +ForegroundLink=#8fc9fc +ForegroundNegative=#ffb3b4 +ForegroundNeutral=#fcb38a +ForegroundNormal=#e6e0e9 +ForegroundPositive=#00e479 +ForegroundVisited=#ebb2ff + +[Colors:Complementary] +BackgroundAlternate=#121016 +BackgroundNormal=#211f24 +DecorationFocus=#cdb9fb +DecorationHover=#cdb9fb +ForegroundActive=#e6e0e9 +ForegroundInactive=#948f99 +ForegroundLink=#8fc9fc +ForegroundNegative=#ffb3b4 +ForegroundNeutral=#fcb38a +ForegroundNormal=#cac4cf +ForegroundPositive=#00e479 +ForegroundVisited=#ebb2ff + +[Colors:Header] +BackgroundAlternate=#211f24 +BackgroundNormal=#211f24 +DecorationFocus=#cdb9fb +DecorationHover=#cdb9fb +ForegroundActive=#e6e0e9 +ForegroundInactive=#948f99 +ForegroundLink=#8fc9fc +ForegroundNegative=#ffb3b4 +ForegroundNeutral=#fcb38a +ForegroundNormal=#cac4cf +ForegroundPositive=#00e479 +ForegroundVisited=#ebb2ff + +[Colors:Header][Inactive] +BackgroundAlternate=#211f24 +BackgroundNormal=#211f24 +DecorationFocus=#cdb9fb +DecorationHover=#cdb9fb +ForegroundActive=#e6e0e9 +ForegroundInactive=#948f99 +ForegroundLink=#8fc9fc +ForegroundNegative=#ffb3b4 +ForegroundNeutral=#fcb38a +ForegroundNormal=#cac4cf +ForegroundPositive=#00e479 +ForegroundVisited=#ebb2ff + +[Colors:Selection] +BackgroundAlternate=#cdb9fb +BackgroundNormal=#cdb9fb +DecorationFocus=#cdb9fb +DecorationHover=#c9bfd8 +ForegroundActive=#36265d +ForegroundInactive=#36265d +ForegroundLink=#004b73 +ForegroundNegative=#920023 +ForegroundNeutral=#753400 +ForegroundNormal=#36265d +ForegroundPositive=#005228 +ForegroundVisited=#74009f + +[Colors:Tooltip] +BackgroundAlternate=#47434c +BackgroundNormal=#211f24 +DecorationFocus=#cdb9fb +DecorationHover=#cdb9fb +ForegroundActive=#e6e0e9 +ForegroundInactive=#948f99 +ForegroundLink=#8fc9fc +ForegroundNegative=#ffb3b4 +ForegroundNeutral=#fcb38a +ForegroundNormal=#e6e0e9 +ForegroundPositive=#00e479 +ForegroundVisited=#ebb2ff + +[Colors:View] +BackgroundAlternate=#211f24 +BackgroundNormal=#121016 +DecorationFocus=#cdb9fb +DecorationHover=#65558f +ForegroundActive=#e6e0e9 +ForegroundInactive=#948f99 +ForegroundLink=#8fc9fc +ForegroundNegative=#ffb3b4 +ForegroundNeutral=#fcb38a +ForegroundNormal=#e6e0e9 +ForegroundPositive=#00e479 +ForegroundVisited=#ebb2ff + +[Colors:Window] +BackgroundAlternate=#47434c +BackgroundNormal=#211f24 +DecorationFocus=#cdb9fb +DecorationHover=#cdb9fb +ForegroundActive=#8fc9fc +ForegroundInactive=#948f99 +ForegroundLink=#8fc9fc +ForegroundNegative=#ffb3b4 +ForegroundNeutral=#fcb38a +ForegroundNormal=#cac4cf +ForegroundPositive=#00e479 +ForegroundVisited=#ebb2ff + +[General] +ColorScheme=MaterialYouDark +ColorSchemeHash=3c0cecefbea43cdb8fe3da156e4a106f7384a526 +LastUsedCustomAccentColor=184,117,220 +XftHintStyle=hintslight +XftSubPixel=none +fixed=JetBrainsMono Nerd Font,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1 +font=Rubik,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1 +menuFont=Rubik,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1 +smallestReadableFont=Rubik,9,-1,5,400,0,0,0,0,0,0,0,0,0,0,1 +toolBarFont=Rubik,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1 + +[Icons] +Theme=OneUI-dark + +[KFileDialog Settings] +Allow Expansion=false +Automatically select filename extension=true +Breadcrumb Navigation=true +Decoration position=2 +LocationCombo Completionmode=5 +PathCombo Completionmode=5 +Show Bookmarks=false +Show Full Path=false +Show Inline Previews=true +Show Preview=false +Show Speedbar=true +Show hidden files=false +Sort by=Name +Sort directories first=true +Sort hidden files last=false +Sort reversed=false +Speedbar Width=168 +View Style=DetailTree + +[Sounds] +Theme=freedesktop + +[WM] +activeBackground=54,52,58 +activeBlend=252,252,252 +activeFont=Rubik,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1 +activeForeground=230,224,233 +inactiveBackground=76,70,90 +inactiveBlend=161,169,177 +inactiveForeground=232,222,248 From 96282c43903ea5aefe11b04efa0e9b4bd9be2493 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 22:13:18 +0200 Subject: [PATCH 559/824] fix weird bold font in some places --- .config/quickshell/modules/sidebarLeft/AiChat.qml | 1 - .config/quickshell/modules/sidebarLeft/Anime.qml | 1 - .config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml | 1 - .config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml | 1 - .../modules/sidebarRight/calendar/CalendarDayButton.qml | 2 +- 5 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 8bd6b6832..a20bbb23c 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -487,7 +487,6 @@ int main(int argc, char* argv[]) { StyledText { id: providerName font.pixelSize: Appearance.font.pixelSize.small - font.weight: Font.DemiBold color: Appearance.m3colors.m3onSurface elide: Text.ElideRight text: Ai.getModel().name diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 3c0e6b633..1bfb210ce 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -547,7 +547,6 @@ Item { StyledText { id: providerName font.pixelSize: Appearance.font.pixelSize.small - font.weight: Font.DemiBold color: Appearance.m3colors.m3onSurface text: Booru.providers[Booru.currentProvider].name } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index 1122406a2..24f4a5b0e 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -146,7 +146,6 @@ Rectangle { Layout.fillWidth: true elide: Text.ElideRight font.pixelSize: Appearance.font.pixelSize.normal - font.weight: Font.DemiBold color: Appearance.m3colors.m3onSecondaryContainer text: messageData?.role == 'assistant' ? Ai.models[messageData?.model].name : (messageData?.role == 'user' && SystemInfo.username) ? SystemInfo.username : diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index f1bc69fed..1af6c91fe 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -77,7 +77,6 @@ Rectangle { id: providerName anchors.centerIn: parent font.pixelSize: Appearance.font.pixelSize.large - font.weight: Font.DemiBold color: Appearance.m3colors.m3onSecondaryContainer text: Booru.providers[root.responseData.provider].name } diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml index d5989c847..88de9202c 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml @@ -23,7 +23,7 @@ RippleButton { anchors.fill: parent text: day horizontalAlignment: Text.AlignHCenter - font.weight: bold ? Font.Bold : isToday == -1 ? Font.Normal : Font.DemiBold + font.weight: bold ? Font.DemiBold : Font.Normal color: (isToday == 1) ? Appearance.m3colors.m3onPrimary : (isToday == 0) ? Appearance.colors.colOnLayer1 : Appearance.m3colors.m3outlineVariant From 5b6db69dd60ee1c0fe96e6bc2386aeefb121b31c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 22:24:39 +0200 Subject: [PATCH 560/824] media controls: use quickshell's color quantizer --- .../modules/mediaControls/PlayerControl.qml | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/.config/quickshell/modules/mediaControls/PlayerControl.qml b/.config/quickshell/modules/mediaControls/PlayerControl.qml index ea8bd2348..f2cb59a33 100644 --- a/.config/quickshell/modules/mediaControls/PlayerControl.qml +++ b/.config/quickshell/modules/mediaControls/PlayerControl.qml @@ -23,7 +23,7 @@ Item { // Player instance property string artDownloadLocation: Directories.coverArt property string artFileName: Qt.md5(artUrl) + ".jpg" property string artFilePath: `${artDownloadLocation}/${artFileName}` - property color artDominantColor: Appearance.m3colors.m3secondaryContainer + property color artDominantColor: colorQuantizer?.colors[0] || Appearance.m3colors.m3secondaryContainer property bool downloaded: false implicitWidth: widgetWidth @@ -76,21 +76,15 @@ Item { // Player instance property string targetFile: playerController.artUrl command: [ "bash", "-c", `[ -f ${artFilePath} ] || curl -sSL '${targetFile}' -o '${artFilePath}'` ] onExited: (exitCode, exitStatus) => { - colorQuantizer.targetFile = playerController.artUrl // Yes this binding break is intentional - colorQuantizer.running = true playerController.downloaded = true } } - Process { // Average Color Runner + ColorQuantizer { id: colorQuantizer - property string targetFile: playerController.artUrl - command: [ "sh", "-c", `magick '${targetFile}' -scale 1x1\\! -format '%[fx:int(255*r+.5)],%[fx:int(255*g+.5)],%[fx:int(255*b+.5)]' info: | sed 's/,/\\n/g' | xargs -L 1 printf '%02x' ; echo` ] - stdout: SplitParser { - onRead: data => { - playerController.artDominantColor = "#" + data - } - } + source: playerController.downloaded ? Qt.resolvedUrl(artFilePath) : "" + depth: 0 // 2^0 = 1 color + rescaleSize: 1 // Rescale to 1x1 pixel for faster processing } property QtObject blendedColors: QtObject { @@ -137,7 +131,6 @@ Item { // Player instance Image { id: blurredArt anchors.fill: parent - visible: true source: playerController.downloaded ? Qt.resolvedUrl(artFilePath) : "" sourceSize.width: background.width sourceSize.height: background.height @@ -149,7 +142,6 @@ Item { // Player instance layer.enabled: true layer.effect: MultiEffect { source: blurredArt - anchors.fill: blurredArt saturation: 0.2 blurEnabled: true blurMax: 100 From 2a90777d26783b9bd5d6a5b557fffef9e9218abf Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 22:38:23 +0200 Subject: [PATCH 561/824] add kde-material-you-colors config --- .config/kde-material-you-colors/config.conf | 74 +++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 .config/kde-material-you-colors/config.conf diff --git a/.config/kde-material-you-colors/config.conf b/.config/kde-material-you-colors/config.conf new file mode 100644 index 000000000..1880d7662 --- /dev/null +++ b/.config/kde-material-you-colors/config.conf @@ -0,0 +1,74 @@ +[CUSTOM] +# INSTRUCTIONS +# Run kde-material-you-colors with no arguments from terminal +# to debug your configuration changing in real time. + +# Monitor to get wallpaper from +# For me main is 0 but second one is 6, play with this to find yours +# Default is 0 +monitor = 0 + +# File containing absolute path of an image (Takes precedence over automatic wallpaper detection) +# Commented by default +file = /home/end/.local/state/quickshell/user/wallpaper.txt + +# List of 7 space separated colors (hex or rgb) to be used for text in pywal/konsole/KSyntaxHighlighting instead of wallpaper ones +# Accepted values are hex e.g #ff0000 and rgb e.g 255,0,0 colors (rgb is converted to hex) +# Commented by default +# Example using catppuccin color scheme: +custom_colors_list = #ED8796 #A6DA95 #EED49F #8AADF4 #F5BDE6 #8BD5CA #f5a97f + +# Enable Light mode +# Accepted values are True or False +# Commented by default to follow System Color Setting (Material You Light/Dark only) +# NOTE: +# Will fallback to dark mode if not defined here or enabled in Settings +#light = False + +# Alternative color mode (default is 0), some images return more than one color, this will use either the matched or last color +# Default is 0 +ncolor = 0 + +# Light scheme icons theme +iconslight = OneUI-light + +# Dark scheme icons theme +iconsdark = OneUI-dark + +# Use pywal to theme other programs using Material You colors +pywal=False + +# The amount of perceptible color for backgrounds in dark mode +# A number between 0 and 4.0 (limited for accessibility purposes) +# Defaults to 1 if not set +#light_blend_multiplier = 1.0 + +# The amount of perceptible color for backgrounds in dark mode +# A number between 0 and 4.0 (limited for accessibility purposes) +# Defaults to 1 if not set +#dark_blend_multiplier = 1.0 + +# A script/command that will be executed on start or wallpaper/dark/light/settings change +# example below using https://github.com/vlevit/notify-send.sh to send a desktop notification: +#on_change_hook = notify-send.sh "kde-material-you-colors" "This is a test" -t 2000 + +# Scheme Variant +# Changes between Material You scheme variants (0-8) +# 0 = Content +# 1 = Expressive +# 2 = Fidelity +# 3 = Monochrome +# 4 = Neutral +# 5 = TonalSpot +# 6 = Vibrant +# 7 = Rainbow +# 8 = FruitSalad +# Default is 5 +scheme_variant = 5 + +# Colorfulness +chroma_multiplier = 1 + +# Brightness +# An integer between 0.5 and 1.5 +tone_multiplier = 1 From 694a54e33190122aa89ae17fbaf1731379916657 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 22:44:49 +0200 Subject: [PATCH 562/824] qt apps: use breeze for colored folder icons --- .config/kde-material-you-colors/config.conf | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.config/kde-material-you-colors/config.conf b/.config/kde-material-you-colors/config.conf index 1880d7662..04c8a3faf 100644 --- a/.config/kde-material-you-colors/config.conf +++ b/.config/kde-material-you-colors/config.conf @@ -30,10 +30,12 @@ custom_colors_list = #ED8796 #A6DA95 #EED49F #8AADF4 #F5BDE6 #8BD5CA #f5a97f ncolor = 0 # Light scheme icons theme -iconslight = OneUI-light +#iconslight = OneUI-light +iconslight = breeze # Dark scheme icons theme -iconsdark = OneUI-dark +#iconsdark = OneUI-dark +iconsdark = breeze-dark # Use pywal to theme other programs using Material You colors pywal=False From f942fb086a6b7d5604dff8a7b3da33300d3049ac Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 22:45:47 +0200 Subject: [PATCH 563/824] Update kdeglobals --- .config/kdeglobals | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/kdeglobals b/.config/kdeglobals index f1e47ee9f..c46bd632f 100644 --- a/.config/kdeglobals +++ b/.config/kdeglobals @@ -145,7 +145,7 @@ smallestReadableFont=Rubik,9,-1,5,400,0,0,0,0,0,0,0,0,0,0,1 toolBarFont=Rubik,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1 [Icons] -Theme=OneUI-dark +Theme=breeze-dark [KFileDialog Settings] Allow Expansion=false From 8ecf9a259759429abe49a05e1aa15edda04f54f2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 23:27:46 +0200 Subject: [PATCH 564/824] improve icon guessing --- .config/quickshell/modules/bar/Workspaces.qml | 3 +- .../modules/common/functions/icons.js | 92 ------------------- .../common/widgets/notification_utils.js | 38 ++++---- .../modules/overview/OverviewWindow.qml | 3 +- .../volumeMixer/VolumeMixerEntry.qml | 3 +- .config/quickshell/services/AppSearch.qml | 58 +++++++++++- 6 files changed, 80 insertions(+), 117 deletions(-) delete mode 100644 .config/quickshell/modules/common/functions/icons.js diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 1c9eb4132..daa0845f8 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -2,7 +2,6 @@ import "root:/" import "root:/services/" import "root:/modules/common" import "root:/modules/common/widgets" -import "root:/modules/common/functions/icons.js" as Icons import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -178,7 +177,7 @@ Item { return winArea > maxArea ? win : maxWin }, null) } - property var mainAppIconSource: Quickshell.iconPath(Icons.noKnowledgeIconGuess(biggestWindow?.class), "image-missing") + property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing") StyledText { opacity: (ConfigOptions.bar.workspaces.alwaysShowNumbers || GlobalStates.workspaceShowNumbers || !workspaceButtonBackground.biggestWindow) ? 1 : 0 diff --git a/.config/quickshell/modules/common/functions/icons.js b/.config/quickshell/modules/common/functions/icons.js deleted file mode 100644 index 6bead3d54..000000000 --- a/.config/quickshell/modules/common/functions/icons.js +++ /dev/null @@ -1,92 +0,0 @@ -.pragma library - -/** - * @type {{[key: string]: string}} - */ -const substitutions = { - "code-url-handler": "visual-studio-code", - "Code": "visual-studio-code", - "GitHub Desktop": "github-desktop", - "Minecraft* 1.20.1": "minecraft", - "gnome-tweaks": "org.gnome.tweaks", - "pavucontrol-qt": "pavucontrol", - "wps": "wps-office2019-kprometheus", - "wpsoffice": "wps-office2019-kprometheus", - "footclient": "foot", - "zen": "zen-browser", - "": "image-missing" -} - -/** - * @type {{[key: string]: string}} - */ -const regexSubstitutions = [ - { - "regex": "/^steam_app_(\\d+)$/", - "replace": "steam_icon_$1" - } -] - -/** - * @param { string } iconName - * @returns { boolean } - */ -function iconExists(iconName) { - return false; // TODO: Make this work without Gtk -} - -/** - * @param { string } str - * @returns { string } - */ -function substitute(str) { - // Normal substitutions - if (substitutions[str]) - return substitutions[str]; - - // Regex substitutions - for (let i = 0; i < regexSubstitutions.length; i++) { - const substitution = regexSubstitutions[i]; - const replacedName = str.replace( - substitution.regex, - substitution.replace, - ); - if (replacedName != str) return replacedName; - } - - // Guess: convert to kebab case - if (!iconExists(str)) str = str.toLowerCase().replace(/\s+/g, "-"); - - // Original string - return str; -} - -/** - * @param { string | undefined } str - * @returns { string } - */ -function noKnowledgeIconGuess(str) { - if (!str) return "image-missing"; - - // Normal substitutions - if (substitutions[str]) - return substitutions[str]; - - // Regex substitutions - for (let i = 0; i < regexSubstitutions.length; i++) { - const substitution = regexSubstitutions[i]; - const replacedName = str.replace( - substitution.regex, - substitution.replace, - ); - if (replacedName != str) return replacedName; - } - - // Guess: convert to kebab case if it's not reverse domain name notation - if (!str.includes('.')) { - str = str.toLowerCase().replace(/\s+/g, "-"); - } - - // Original string - return str; -} \ No newline at end of file diff --git a/.config/quickshell/modules/common/widgets/notification_utils.js b/.config/quickshell/modules/common/widgets/notification_utils.js index 01bf3d87f..9b151055c 100644 --- a/.config/quickshell/modules/common/widgets/notification_utils.js +++ b/.config/quickshell/modules/common/widgets/notification_utils.js @@ -42,19 +42,6 @@ function findSuitableMaterialSymbol(summary = "") { return defaultType; } -// const getFriendlyNotifTimeString = (timeObject) => { -// const messageTime = GLib.DateTime.new_from_unix_local(timeObject); -// const oneMinuteAgo = GLib.DateTime.new_now_local().add_seconds(-60); -// if (messageTime.compare(oneMinuteAgo) > 0) -// return getString('Now'); -// else if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year()) -// return messageTime.format(userOptions.time.format); -// else if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year() - 1) -// return getString('Yesterday'); -// else -// return messageTime.format(userOptions.time.dateFormat); -// } - /** * @param { number | string | Date } timestamp * @returns { string } @@ -63,13 +50,28 @@ const getFriendlyNotifTimeString = (timestamp) => { if (!timestamp) return ''; const messageTime = new Date(timestamp); const now = new Date(); - const oneMinuteAgo = new Date(now.getTime() - 60000); + const diffMs = now.getTime() - messageTime.getTime(); - if (messageTime > oneMinuteAgo) + // Less than 1 minute + if (diffMs < 60000) return 'Now'; - if (messageTime.toDateString() === now.toDateString()) - return Qt.formatDateTime(messageTime, "hh:mm"); + + // Same day - show relative time + if (messageTime.toDateString() === now.toDateString()) { + const diffMinutes = Math.floor(diffMs / 60000); + const diffHours = Math.floor(diffMs / 3600000); + + if (diffHours > 0) { + return `${diffHours}h`; + } else { + return `${diffMinutes}m`; + } + } + + // Yesterday if (messageTime.toDateString() === new Date(now.getTime() - 86400000).toDateString()) return 'Yesterday'; + + // Older dates return Qt.formatDateTime(messageTime, "MMMM dd"); -}; +}; \ No newline at end of file diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index 67faa9628..658342dd7 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -1,7 +1,6 @@ import "root:/services/" import "root:/modules/common" import "root:/modules/common/widgets" -import "root:/modules/common/functions/icons.js" as Icons import "root:/modules/common/functions/color_utils.js" as ColorUtils import Qt5Compat.GraphicalEffects import QtQuick @@ -33,7 +32,7 @@ Rectangle { // Window property var iconToWindowRatio: 0.35 property var xwaylandIndicatorToIconRatio: 0.35 property var iconToWindowRatioCompact: 0.6 - property var iconPath: Quickshell.iconPath(Icons.noKnowledgeIconGuess(windowData?.class), "image-missing") + property var iconPath: Quickshell.iconPath(AppSearch.guessIcon(windowData?.class), "image-missing") property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth x: initX diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml index 78b48be28..97745977d 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml @@ -1,7 +1,6 @@ import "root:/modules/common" import "root:/modules/common/widgets" import "root:/services" -import "root:/modules/common/functions/icons.js" as Icons import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls @@ -48,7 +47,7 @@ Item { sourceSize.width: size sourceSize.height: size source: { - const icon = Icons.noKnowledgeIconGuess(root.node.properties["application.icon-name"]); + const icon = AppSearch.guessIcon(root.node.properties["application.icon-name"]); return Quickshell.iconPath(icon, "image-missing"); } } diff --git a/.config/quickshell/services/AppSearch.qml b/.config/quickshell/services/AppSearch.qml index 742126e0a..4e32eb30f 100644 --- a/.config/quickshell/services/AppSearch.qml +++ b/.config/quickshell/services/AppSearch.qml @@ -7,12 +7,32 @@ import Quickshell import Quickshell.Io /** - * Eases searching for applications by name. + * - Eases fuzzy searching for applications by name + * - Guesses icon name for window class name with normalization, possibly with desktop entry searching later */ Singleton { id: root property bool sloppySearch: ConfigOptions?.search.sloppy ?? false property real scoreThreshold: 0.2 + property var substitutions: ({ + "code-url-handler": "visual-studio-code", + "Code": "visual-studio-code", + "GitHub Desktop": "github-desktop", + "Minecraft* 1.20.1": "minecraft", + "gnome-tweaks": "org.gnome.tweaks", + "pavucontrol-qt": "pavucontrol", + "wps": "wps-office2019-kprometheus", + "wpsoffice": "wps-office2019-kprometheus", + "footclient": "foot", + "zen": "zen-browser", + "": "image-missing" + }) + property var regexSubstitutions: [ + { + "regex": "/^steam_app_(\\d+)$/", + "replace": "steam_icon_$1" + } + ] readonly property list list: Array.from(DesktopEntries.applications.values) .sort((a, b) => a.name.localeCompare(b.name)) @@ -40,4 +60,40 @@ Singleton { return r.obj.entry }); } + + function iconExists(iconName) { + return Quickshell.iconPath(iconName, true).length > 0; + } + + function guessIcon(str) { + if (!str) return "image-missing"; + + // Normal substitutions + if (substitutions[str]) + return substitutions[str]; + + // Regex substitutions + for (let i = 0; i < regexSubstitutions.length; i++) { + const substitution = regexSubstitutions[i]; + const replacedName = str.replace( + substitution.regex, + substitution.replace, + ); + if (replacedName != str) return replacedName; + } + + // If it gets detected normally, no need to guess + if (iconExists(str)) return str; + + let guessStr = str; + // Guess: Take only app name of reverse domain name notation + guessStr = str.split('.').slice(-1)[0].toLowerCase(); + if (iconExists(guessStr)) return guessStr; + // Guess: normalize to kebab case + guessStr = str.toLowerCase().replace(/\s+/g, "-"); + if (iconExists(guessStr)) return guessStr; + + // Give up + return str; + } } From 363e3327bae8776e5062f06ac7da89edd79f67f8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 23:50:51 +0200 Subject: [PATCH 565/824] app search: adjust substitutions --- .config/quickshell/services/AppSearch.qml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/services/AppSearch.qml b/.config/quickshell/services/AppSearch.qml index 4e32eb30f..182da675d 100644 --- a/.config/quickshell/services/AppSearch.qml +++ b/.config/quickshell/services/AppSearch.qml @@ -8,7 +8,7 @@ import Quickshell.Io /** * - Eases fuzzy searching for applications by name - * - Guesses icon name for window class name with normalization, possibly with desktop entry searching later + * - Guesses icon name for window class name */ Singleton { id: root @@ -17,20 +17,21 @@ Singleton { property var substitutions: ({ "code-url-handler": "visual-studio-code", "Code": "visual-studio-code", - "GitHub Desktop": "github-desktop", - "Minecraft* 1.20.1": "minecraft", "gnome-tweaks": "org.gnome.tweaks", "pavucontrol-qt": "pavucontrol", "wps": "wps-office2019-kprometheus", "wpsoffice": "wps-office2019-kprometheus", "footclient": "foot", "zen": "zen-browser", - "": "image-missing" }) property var regexSubstitutions: [ { "regex": "/^steam_app_(\\d+)$/", "replace": "steam_icon_$1" + }, + { + "regex": "Minecraft.*", + "replace": "minecraft" } ] @@ -66,7 +67,7 @@ Singleton { } function guessIcon(str) { - if (!str) return "image-missing"; + if (!str || str.length == 0) return "image-missing"; // Normal substitutions if (substitutions[str]) From 3cf64401ef897d59c6b2b45c05e8f7129e71b7dd Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 29 May 2025 23:51:38 +0200 Subject: [PATCH 566/824] overview: indicate xwayland with italics and tooltip instead of icon --- .../quickshell/modules/overview/OverviewWidget.qml | 10 ++++++++-- .../quickshell/modules/overview/OverviewWindow.qml | 14 +++----------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index 85d83242a..ad4eea02b 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -176,8 +176,8 @@ Item { id: dragArea anchors.fill: parent hoverEnabled: true - onEntered: hovered = true - onExited: hovered = false + onEntered: hovered = true // For hover color change + onExited: hovered = false // For hover color change acceptedButtons: Qt.LeftButton | Qt.MiddleButton drag.target: parent onPressed: { @@ -212,6 +212,12 @@ Item { event.accepted = true } } + + StyledToolTip { + extraVisibleCondition: false + alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active + content: `${windowData.title}\n[${windowData.class}] ${windowData.xwayland ? "[XWayland] " : ""}\n` + } } } } diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index 658342dd7..d4617b542 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -12,7 +12,6 @@ import Quickshell.Hyprland Rectangle { // Window id: root - property var windowData property var monitorData property var scale @@ -34,6 +33,8 @@ Rectangle { // Window property var iconToWindowRatioCompact: 0.6 property var iconPath: Quickshell.iconPath(AppSearch.guessIcon(windowData?.class), "image-missing") property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth + + property bool indicateXWayland: (ConfigOptions.overview.showXwaylandIndicator && windowData?.xwayland) ?? false x: initX y: initY @@ -74,15 +75,6 @@ Rectangle { // Window Behavior on implicitSize { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } - - IconImage { - id: xwaylandIndicator - visible: (ConfigOptions.overview.showXwaylandIndicator && windowData?.xwayland) ?? false - anchors.right: parent.right - anchors.bottom: parent.bottom - source: Quickshell.iconPath("xorg") - implicitSize: windowIcon.implicitSize * xwaylandIndicatorToIconRatio - } } StyledText { @@ -93,8 +85,8 @@ Rectangle { // Window Layout.fillHeight: true horizontalAlignment: Text.AlignHCenter font.pixelSize: Appearance.font.pixelSize.smaller + font.italic: indicateXWayland ? true : false elide: Text.ElideRight - // wrapMode: Text.Wrap text: windowData?.title ?? "" } } From 9cea880569ab3221ce93402071d52bf6ad230fd7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 00:12:54 +0200 Subject: [PATCH 567/824] keybinds: switch to qt apps --- .config/hypr/hyprland/keybinds.conf | 13 ++++++------- arch-packages/illogical-impulse-audio/PKGBUILD | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 3ef5363ea..ed8fd0a2b 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -176,7 +176,7 @@ bind = Ctrl+Super, Down, workspace, r+5 # [hidden] #! # Testing bind = Super+Alt, f11, exec, bash -c 'RANDOM_IMAGE=$(find ~/Pictures -type f | grep -v -i "nipple" | grep -v -i "pussy" | shuf -n 1); ACTION=$(notify-send "Test notification with body image" "This notification should contain your user account image and Discord icon. Oh and here is a random image in your Pictures folder: \"Testing" -a "Hyprland keybind" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Open the random image" -A "action3=Useless button"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] -bind = Super+Alt, f12, exec, bash -c 'ACTION=$(notify-send "Test notification" "This notification should contain your user account image and Discord icon.\nFlick right to dismiss!" -a "Discord (fake)" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Useless button" -A "action3=Cry more"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] +bind = Super+Alt, f12, exec, bash -c 'RANDOM_IMAGE=$(find ~/Pictures -type f | grep -v -i "nipple" | grep -v -i "pussy" | shuf -n 1); ACTION=$(notify-send "Test notification" "This notification should contain a random image in your Pictures folder and Discord icon.\nFlick right to dismiss!" -a "Discord (fake)" -p -h "string:image-path:$RANDOM_IMAGE" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Useless button" -A "action3=Cry more"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"' # [hidden] bind = Super+Alt, Equal, exec, notify-send "Urgent notification" "Ah hell no" -u critical -a 'Hyprland keybind' # [hidden] ##! Media @@ -194,16 +194,15 @@ bindl= ,XF86AudioPause, exec, playerctl play-pause # [hidden] bind = Super, T, exec, # [hidden] bind = Super, Return, exec, foot # Foot (terminal) bind = Super, T, exec, foot # [hidden] foot (terminal) (alt) -bind = Super, E, exec, nautilus --new-window # Nautilus (file manager) +bind = Super, E, exec, dolphin --new-window # Dolphin (file manager) bind = Ctrl+Super, W, exec, firefox # Firefox (browser) bind = Super, C, exec, code # VSCode (editor) -bind = Super+Alt, E, exec, thunar # [hidden] bind = Super, W, exec, zen-browser # [hidden] bind = Super+Shift, W, exec, wps # WPS Office -bind = Ctrl+Super, V, exec, pavucontrol # Pavucontrol (volume mixer) -bind = Super, X, exec, gnome-text-editor --new-window # GNOME Text Editor -bind = Super, I, exec, XDG_CURRENT_DESKTOP="gnome" gnome-control-center # GNOME Settings -bind = Ctrl+Shift, Escape, exec, gnome-system-monitor # GNOME System monitor +bind = Super, X, exec, kate # Kate (editor) +bind = Ctrl+Super, V, exec, pavucontrol-qt # Pavucontrol Qt (volume mixer) +bind = Super, I, exec, XDG_CURRENT_DESKTOP=gnome gnome-control-center # GNOME Control center (settings app) +bind = Ctrl+Shift, Escape, exec, plasma-systemmonitor --page-name Processes # Plasma system monitor # Cursed stuff ## Make window not amogus large diff --git a/arch-packages/illogical-impulse-audio/PKGBUILD b/arch-packages/illogical-impulse-audio/PKGBUILD index d2d1e7889..7f43e5ef4 100644 --- a/arch-packages/illogical-impulse-audio/PKGBUILD +++ b/arch-packages/illogical-impulse-audio/PKGBUILD @@ -5,7 +5,7 @@ pkgdesc='Illogical Impulse Audio Dependencies' arch=(any) license=(None) depends=( - pavucontrol + pavucontrol-qt wireplumber libdbusmenu-gtk3 playerctl From 440438ef332579b1da05023a1ae6019b972df619 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 00:36:53 +0200 Subject: [PATCH 568/824] volume mixer: guess icon also by node name --- .../modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml index 97745977d..c4600c7b8 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml @@ -47,7 +47,10 @@ Item { sourceSize.width: size sourceSize.height: size source: { - const icon = AppSearch.guessIcon(root.node.properties["application.icon-name"]); + let icon; + icon = AppSearch.guessIcon(root.node.properties["application.icon-name"]); + if (AppSearch.iconExists(icon)) return Quickshell.iconPath(icon, "image-missing"); + icon = AppSearch.guessIcon(root.node.properties["node.name"]); return Quickshell.iconPath(icon, "image-missing"); } } From c36ca265a545251e75589f3353dc7e5d2e768ef7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 00:37:21 +0200 Subject: [PATCH 569/824] guess icons also by desktop entry search --- .config/quickshell/services/AppSearch.qml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/services/AppSearch.qml b/.config/quickshell/services/AppSearch.qml index 182da675d..f76e25741 100644 --- a/.config/quickshell/services/AppSearch.qml +++ b/.config/quickshell/services/AppSearch.qml @@ -63,7 +63,8 @@ Singleton { } function iconExists(iconName) { - return Quickshell.iconPath(iconName, true).length > 0; + return (Quickshell.iconPath(iconName, true).length > 0) + && !iconName.includes("image-missing"); } function guessIcon(str) { @@ -93,6 +94,13 @@ Singleton { // Guess: normalize to kebab case guessStr = str.toLowerCase().replace(/\s+/g, "-"); if (iconExists(guessStr)) return guessStr; + // Guess: First fuzze desktop entry match + const searchResults = root.fuzzyQuery(str); + if (searchResults.length > 0) { + const firstEntry = searchResults[0]; + guessStr = firstEntry.icon + if (iconExists(guessStr)) return guessStr; + } // Give up return str; From 38a026e1ddada1f1e59b20d61d0f93e882b9a34f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 08:31:38 +0200 Subject: [PATCH 570/824] Update SysTrayItem.qml --- .config/quickshell/modules/bar/SysTrayItem.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.config/quickshell/modules/bar/SysTrayItem.qml b/.config/quickshell/modules/bar/SysTrayItem.qml index 900ef68f4..1886e6a02 100644 --- a/.config/quickshell/modules/bar/SysTrayItem.qml +++ b/.config/quickshell/modules/bar/SysTrayItem.qml @@ -43,6 +43,7 @@ MouseArea { IconImage { id: trayIcon + visible: false // There's already color overlay source: root.item.icon anchors.centerIn: parent width: parent.width @@ -51,6 +52,7 @@ MouseArea { Desaturate { id: desaturatedIcon + visible: false // There's already color overlay anchors.fill: trayIcon source: trayIcon desaturation: 1 // 1.0 means fully grayscale From 05d134dd9da0be03293010d532fd937646125929 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 08:32:01 +0200 Subject: [PATCH 571/824] workspaces: hide app icon when transparent --- .config/quickshell/modules/bar/Workspaces.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index daa0845f8..39af1b212 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -214,6 +214,7 @@ Item { opacity: (workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 + visible: opacity > 0 source: workspaceButtonBackground.mainAppIconSource implicitSize: (!GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? workspaceIconSize : workspaceIconSizeShrinked From 46fdd3306c035c76ebe8a810faa125c224c3a46c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 08:32:16 +0200 Subject: [PATCH 572/824] clipboard images: don't stretch --- .config/quickshell/modules/common/widgets/CliphistImage.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/widgets/CliphistImage.qml b/.config/quickshell/modules/common/widgets/CliphistImage.qml index 088f6373a..9de344507 100644 --- a/.config/quickshell/modules/common/widgets/CliphistImage.qml +++ b/.config/quickshell/modules/common/widgets/CliphistImage.qml @@ -41,7 +41,8 @@ Rectangle { property real scale: { return Math.min( root.maxWidth / imageWidth, - root.maxHeight / imageHeight + root.maxHeight / imageHeight, + 1 ) } From 782926de0fae23ae93d824105ea5e5cba68fe354 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 08:32:41 +0200 Subject: [PATCH 573/824] Update SearchItem.qml --- .config/quickshell/modules/overview/SearchItem.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index db3a3d265..0191ef3e9 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -192,7 +192,7 @@ RippleButton { text: `${root.displayContent}` } } - Loader { + Loader { // Clipboard image preview active: root.cliphistRawString && /^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(root.cliphistRawString) sourceComponent: CliphistImage { Layout.fillWidth: true From fc75ae4b63106d53a75468f1209c21bf623b10c5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 08:32:52 +0200 Subject: [PATCH 574/824] disable dock --- .config/quickshell/shell.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 93cc58f7b..35631100a 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -25,7 +25,7 @@ ShellRoot { // no unnecessary stuff will take up memory if you decide to only use, say, the overview. property bool enableBar: true property bool enableCheatsheet: true - property bool enableDock: true + property bool enableDock: false property bool enableMediaControls: true property bool enableNotificationPopup: true property bool enableOnScreenDisplayBrightness: true From 7966a8dadff8a9724913f76ec820f5ef15a3b631 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 08:38:37 +0200 Subject: [PATCH 575/824] use better control for bluetooth --- .config/quickshell/modules/common/ConfigOptions.qml | 2 +- arch-packages/illogical-impulse-gnome/PKGBUILD | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index c8fde86d7..cb29626a0 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -13,7 +13,7 @@ Singleton { } property QtObject apps: QtObject { - property string bluetooth: "blueberry" + property string bluetooth: "better-control --bluetooth" property string imageViewer: "loupe" property string network: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi" property string settings: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center" diff --git a/arch-packages/illogical-impulse-gnome/PKGBUILD b/arch-packages/illogical-impulse-gnome/PKGBUILD index 33af4b8ba..4521b0ce3 100644 --- a/arch-packages/illogical-impulse-gnome/PKGBUILD +++ b/arch-packages/illogical-impulse-gnome/PKGBUILD @@ -8,5 +8,5 @@ depends=( polkit-gnome gnome-keyring gnome-control-center - blueberry networkmanager + networkmanager better-control-git ) From cbe086d8350587517891bd3ecf1a985b67b2c97a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 09:32:17 +0200 Subject: [PATCH 576/824] refractor shadows --- .../quickshell/modules/cheatsheet/Cheatsheet.qml | 9 ++------- .../modules/common/widgets/NotificationGroup.qml | 9 ++------- .../common/widgets/StyledRectangularShadow.qml | 13 +++++++++++++ .config/quickshell/modules/dock/Dock.qml | 9 ++------- .../modules/mediaControls/PlayerControl.qml | 9 ++------- .../modules/onScreenDisplay/OsdValueIndicator.qml | 8 ++------ .../quickshell/modules/overview/OverviewWidget.qml | 8 ++------ .../quickshell/modules/overview/SearchWidget.qml | 8 ++------ .../quickshell/modules/sidebarLeft/SidebarLeft.qml | 8 ++------ .../modules/sidebarLeft/anime/BooruImage.qml | 8 ++------ .../modules/sidebarRight/SidebarRight.qml | 8 ++------ .../modules/sidebarRight/todo/TodoWidget.qml | 7 ++----- 12 files changed, 35 insertions(+), 69 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/StyledRectangularShadow.qml diff --git a/.config/quickshell/modules/cheatsheet/Cheatsheet.qml b/.config/quickshell/modules/cheatsheet/Cheatsheet.qml index 83af1f004..79711f88f 100644 --- a/.config/quickshell/modules/cheatsheet/Cheatsheet.qml +++ b/.config/quickshell/modules/cheatsheet/Cheatsheet.qml @@ -57,13 +57,8 @@ Scope { // Scope // Background - RectangularShadow { - anchors.fill: cheatsheetBackground - radius: cheatsheetBackground.radius - blur: 1.2 * Appearance.sizes.elevationMargin - spread: 1 - color: Appearance.colors.colShadow - cached: true + StyledRectangularShadow { + target: cheatsheetBackground } Rectangle { id: cheatsheetBackground diff --git a/.config/quickshell/modules/common/widgets/NotificationGroup.qml b/.config/quickshell/modules/common/widgets/NotificationGroup.qml index 14669e99b..009a0b3ed 100644 --- a/.config/quickshell/modules/common/widgets/NotificationGroup.qml +++ b/.config/quickshell/modules/common/widgets/NotificationGroup.qml @@ -105,14 +105,9 @@ Item { // Notification group area } } - - RectangularShadow { + StyledRectangularShadow { + target: background visible: popup - anchors.fill: background - radius: background.radius - blur: 1.2 * Appearance.sizes.elevationMargin - spread: 1 - color: Appearance.colors.colShadow } Rectangle { // Background of the notification id: background diff --git a/.config/quickshell/modules/common/widgets/StyledRectangularShadow.qml b/.config/quickshell/modules/common/widgets/StyledRectangularShadow.qml new file mode 100644 index 000000000..6e1f2e16e --- /dev/null +++ b/.config/quickshell/modules/common/widgets/StyledRectangularShadow.qml @@ -0,0 +1,13 @@ +import QtQuick +import QtQuick.Effects +import "root:/modules/common" + +RectangularShadow { + required property var target + anchors.fill: target + radius: target.radius + blur: 1.2 * Appearance.sizes.elevationMargin + spread: 1 + color: Appearance.colors.colShadow + cached: true +} diff --git a/.config/quickshell/modules/dock/Dock.qml b/.config/quickshell/modules/dock/Dock.qml index 4da45e74d..66b5172bf 100644 --- a/.config/quickshell/modules/dock/Dock.qml +++ b/.config/quickshell/modules/dock/Dock.qml @@ -75,13 +75,8 @@ Scope { // Scope implicitWidth: dockRow.implicitWidth + 5 * 2 height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut - RectangularShadow { - anchors.fill: dockVisualBackground - radius: dockVisualBackground.radius - blur: 1.2 * Appearance.sizes.elevationMargin - spread: 1 - color: Appearance.colors.colShadow - cached: true + StyledRectangularShadow { + target: dockVisualBackground } Rectangle { id: dockVisualBackground diff --git a/.config/quickshell/modules/mediaControls/PlayerControl.qml b/.config/quickshell/modules/mediaControls/PlayerControl.qml index f2cb59a33..530726900 100644 --- a/.config/quickshell/modules/mediaControls/PlayerControl.qml +++ b/.config/quickshell/modules/mediaControls/PlayerControl.qml @@ -104,13 +104,8 @@ Item { // Player instance } - RectangularShadow { - anchors.fill: background - radius: background.radius - blur: 1.2 * Appearance.sizes.elevationMargin - spread: 1 - color: Appearance.colors.colShadow - cached: true + StyledRectangularShadow { + target: background } Rectangle { // Background id: background diff --git a/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml b/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml index 29829b43d..dfbf4a6b8 100644 --- a/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml +++ b/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml @@ -25,12 +25,8 @@ Item { implicitWidth: Appearance.sizes.osdWidth implicitHeight: valueIndicator.implicitHeight - RectangularShadow { - anchors.fill: valueIndicator - radius: valueIndicator.radius - blur: 1.2 * Appearance.sizes.elevationMargin - spread: 1 - color: Appearance.colors.colShadow + StyledRectangularShadow { + target: valueIndicator } WrapperRectangle { id: valueIndicator diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index ad4eea02b..1b0673f2b 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -49,12 +49,8 @@ Item { property Component windowComponent: OverviewWindow {} property list windowWidgets: [] - RectangularShadow { // Background shadow - anchors.fill: overviewBackground - radius: overviewBackground.radius - blur: 1.2 * Appearance.sizes.elevationMargin - spread: 1 - color: Appearance.colors.colShadow + StyledRectangularShadow { + target: overviewBackground } Rectangle { // Background id: overviewBackground diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index a49ae63a3..016b82047 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -169,12 +169,8 @@ Item { // Wrapper } } - RectangularShadow { // Background shadow - anchors.fill: searchWidgetContent - radius: searchWidgetContent.radius - blur: 1.2 * Appearance.sizes.elevationMargin - spread: 1 - color: Appearance.colors.colShadow + StyledRectangularShadow { + target: searchWidgetContent } Rectangle { // Background id: searchWidgetContent diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index a5bb59d6f..57c7e6ffe 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -65,12 +65,8 @@ Scope { // Scope } // Background - RectangularShadow { // Background shadow - anchors.fill: sidebarLeftBackground - radius: sidebarLeftBackground.radius - blur: 1.2 * Appearance.sizes.elevationMargin - spread: 1 - color: Appearance.colors.colShadow + StyledRectangularShadow { + target: sidebarLeftBackground } Rectangle { id: sidebarLeftBackground diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index 29768cfb0..64c5bded9 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -124,12 +124,8 @@ Button { width: contextMenu.width height: contextMenu.height - RectangularShadow { // Background shadow - anchors.fill: contextMenu - radius: contextMenu.radius - blur: 1.2 * Appearance.sizes.elevationMargin - spread: 1 - color: Appearance.colors.colShadow + StyledRectangularShadow { + target: contextMenu } Rectangle { id: contextMenu diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index d2dfa34a8..4f774db64 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -60,12 +60,8 @@ Scope { implicitHeight: sidebarRightBackground.implicitHeight implicitWidth: sidebarRightBackground.implicitWidth - RectangularShadow { // Background shadow - anchors.fill: sidebarRightBackground - radius: sidebarRightBackground.radius - blur: 1.2 * Appearance.sizes.elevationMargin - spread: 1 - color: Appearance.colors.colShadow + StyledRectangularShadow { + target: sidebarRightBackground } Rectangle { id: sidebarRightBackground diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index e4f441b50..441a35ca1 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -152,12 +152,9 @@ Item { } // + FAB - RectangularShadow { // Background shadow - anchors.fill: fabButton + StyledRectangularShadow { + target: fabButton radius: Appearance.rounding.normal - blur: 1.2 * Appearance.sizes.elevationMargin - spread: 1 - color: Appearance.colors.colShadow } Button { id: fabButton From 198bcc6a3af0c1f91ba576a1318a8f70d7749639 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 09:32:29 +0200 Subject: [PATCH 577/824] readme: update dolphin mimetype fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f1e27caa..6a5dfcdc5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ - **Assumption**: You are already using the AGS illogical-impulse - **Install Qt packages** (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-svg qt5-translations qt5-wayland qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland syntax-highlighting` - **Install quickshell and more stuff**: `yay -S quickshell matugen-bin grimblast wtype kde-material-you-colors` -- **Dolphin fix** for it asking what program to open file with every time: `sudo pacman -S archlinux-xdg-menu && XDG_MENU_PREFIX=arch- kbuildsycoca6` +- **Dolphin fix** for it asking what program to open file with every time: `sudo pacman -S archlinux-xdg-menu && XDG_MENU_PREFIX=arch- kbuildsycoca6; sudo ln -s /etc/xdg/menus/plasma-applications.menu /etc/xdg/menus/applications.menu` - **Copy** `.config/quickshell` folder and hyprland config files in `.config/hypr/hyprland/` (backing up is your responsibility) (or you can create a new user) - **Run quickshell** with `qs` and see how things are - it's not finished, but **feedback is very welcome** - We currently have bar, right sidebar, search/overview From 3cd8865a50e084d35e94388dce5b55dd5a190674 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 11:27:57 +0200 Subject: [PATCH 578/824] dock: previews --- .../modules/common/widgets/GroupButton.qml | 9 +- .../modules/common/widgets/RippleButton.qml | 7 +- .config/quickshell/modules/dock/Dock.qml | 7 +- .../quickshell/modules/dock/DockAppButton.qml | 44 ++++ .config/quickshell/modules/dock/DockApps.qml | 240 +++++++++++++++--- .../quickshell/modules/dock/DockButton.qml | 2 +- 6 files changed, 267 insertions(+), 42 deletions(-) create mode 100644 .config/quickshell/modules/dock/DockAppButton.qml diff --git a/.config/quickshell/modules/common/widgets/GroupButton.qml b/.config/quickshell/modules/common/widgets/GroupButton.qml index a369ec16e..1f0776194 100644 --- a/.config/quickshell/modules/common/widgets/GroupButton.qml +++ b/.config/quickshell/modules/common/widgets/GroupButton.qml @@ -19,6 +19,7 @@ Button { property real buttonRadius: Appearance?.rounding?.small ?? 4 property real buttonRadiusPressed: buttonRadius property var altAction + property var middleClickAction property bool bounce: true property real baseWidth: contentItem.implicitWidth + padding * 2 property real baseHeight: contentItem.implicitHeight + padding * 2 @@ -67,9 +68,13 @@ Button { MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor - acceptedButtons: Qt.LeftButton | Qt.RightButton + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton onPressed: (event) => { - if(event.button === Qt.RightButton) { + if (event.button === Qt.MiddleButton) { + if (root.middleClickAction) root.middleClickAction(); + return; + } + if (event.button === Qt.RightButton) { if (root.altAction) root.altAction(); return; } diff --git a/.config/quickshell/modules/common/widgets/RippleButton.qml b/.config/quickshell/modules/common/widgets/RippleButton.qml index e80a319c9..3e4868405 100644 --- a/.config/quickshell/modules/common/widgets/RippleButton.qml +++ b/.config/quickshell/modules/common/widgets/RippleButton.qml @@ -21,6 +21,7 @@ Button { property int rippleDuration: 1200 property bool rippleEnabled: true property var altAction + property var middleClickAction property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent" property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED" @@ -58,12 +59,16 @@ Button { MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor - acceptedButtons: Qt.LeftButton | Qt.RightButton + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton onPressed: (event) => { if(event.button === Qt.RightButton) { if (root.altAction) root.altAction(); return; } + if(event.button === Qt.MiddleButton) { + if (root.middleClickAction) root.middleClickAction(); + return; + } root.down = true if (!root.rippleEnabled) return; const {x,y} = event diff --git a/.config/quickshell/modules/dock/Dock.qml b/.config/quickshell/modules/dock/Dock.qml index 66b5172bf..777aa6461 100644 --- a/.config/quickshell/modules/dock/Dock.qml +++ b/.config/quickshell/modules/dock/Dock.qml @@ -23,7 +23,7 @@ Scope { // Scope id: dockRoot screen: modelData - property bool reveal: root.pinned || dockMouseArea.containsMouse + property bool reveal: root.pinned || dockMouseArea.containsMouse || dockApps.requestDockShow anchors { bottom: true @@ -35,9 +35,6 @@ Scope { // Scope cheatsheetLoader.active = false } exclusiveZone: root.pinned ? implicitHeight - Appearance.sizes.hyprlandGapsOut : 0 - Component.onCompleted: { - console.log(ConfigOptions.dock.hoverRegionHeight) - } implicitWidth: dockBackground.implicitWidth WlrLayershell.namespace: "quickshell:dock" @@ -114,7 +111,7 @@ Scope { // Scope } } DockSeparator {} - DockApps {} + DockApps { id: dockApps } DockSeparator {} DockButton { onClicked: Hyprland.dispatch("global quickshell:overviewToggle") diff --git a/.config/quickshell/modules/dock/DockAppButton.qml b/.config/quickshell/modules/dock/DockAppButton.qml new file mode 100644 index 000000000..7ff11bae1 --- /dev/null +++ b/.config/quickshell/modules/dock/DockAppButton.qml @@ -0,0 +1,44 @@ +import "root:/" +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell.Io +import Quickshell +import Quickshell.Widgets +import Quickshell.Wayland +import Quickshell.Hyprland + +DockButton { + id: appButton + required property var appToplevel + property var appListRoot + property int lastFocused: -1 + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + onEntered: { + appListRoot.lastHoveredButton = appButton + appListRoot.buttonHovered = true + lastFocused = appToplevel.toplevels.length - 1 + } + onExited: { + if (appListRoot.lastHoveredButton === appButton) { + appListRoot.buttonHovered = false + } + } + } + onClicked: { + lastFocused = (lastFocused + 1) % appToplevel.toplevels.length + appToplevel.toplevels[lastFocused].activate() + } + contentItem: IconImage { + id: iconImage + source: Quickshell.iconPath(AppSearch.guessIcon(appToplevel.appId), "image-missing") + } +} diff --git a/.config/quickshell/modules/dock/DockApps.qml b/.config/quickshell/modules/dock/DockApps.qml index 1383f41f0..7704a8cf2 100644 --- a/.config/quickshell/modules/dock/DockApps.qml +++ b/.config/quickshell/modules/dock/DockApps.qml @@ -2,7 +2,7 @@ import "root:/" import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" -import "root:/modules/common/functions/icons.js" as Icons +import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Effects @@ -13,44 +13,218 @@ import Quickshell.Widgets import Quickshell.Wayland import Quickshell.Hyprland -RowLayout { - readonly property list windowList: HyprlandData.windowList - readonly property list apps: { - let uniqueClasses = new Set() - for (let window of windowList) { - if (window.class && window.class.trim() !== "") { - uniqueClasses.add(window.class) - } - } - return Array.from(uniqueClasses) - } - readonly property var windowsByApp: { - let grouped = {} - for (let window of windowList) { - if (window.class && window.class.trim() !== "") { - if (!grouped[window.class]) { - grouped[window.class] = [] +Item { + id: root + property real maxWindowPreviewHeight: 200 + property real maxWindowPreviewWidth: 350 + property Item lastHoveredButton + property bool buttonHovered: false + property bool requestDockShow: previewPopup.show + + implicitWidth: rowLayout.implicitWidth + implicitHeight: rowLayout.implicitHeight + + RowLayout { + id: rowLayout + spacing: 2 + + Repeater { + model: ScriptModel { + objectProp: "appId" + values: { + var map = new Map(); + + for (const toplevel of ToplevelManager.toplevels.values) { + if (!map.has(toplevel.appId.toLowerCase())) map.set(toplevel.appId.toLowerCase(), []); + map.get(toplevel.appId.toLowerCase()).push(toplevel); + } + + var values = []; + + for (const [key, value] of map) { + values.push({ appId: key, toplevels: value }); + } + + return values; } - grouped[window.class].push(window) + } + delegate: DockAppButton { + required property var modelData + appToplevel: modelData + appListRoot: root } } - return grouped } - Repeater { - model: apps - delegate: DockButton { - required property string modelData - property int lastFocusedIndex: -1 - contentItem: IconImage { - source: Quickshell.iconPath(Icons.noKnowledgeIconGuess(modelData), "image-missing") + PopupWindow { + id: previewPopup + property var appTopLevel: root.lastHoveredButton?.appToplevel + property bool allPreviewsReady: false + Connections { + target: root + onLastHoveredButtonChanged: previewPopup.allPreviewsReady = false; // Reset readiness when the hovered button changes + } + function updatePreviewReadiness() { + for(var i = 0; i < previewRowLayout.children.length; i++) { + const view = previewRowLayout.children[i]; + if (view.hasContent === false) { + allPreviewsReady = false; + return; + } } - onClicked: () => { - lastFocusedIndex = (lastFocusedIndex + 1) % windowsByApp[modelData].length - const targetWindow = windowsByApp[modelData][lastFocusedIndex]; - const targetAddress = targetWindow.address; - Hyprland.dispatch(`focuswindow address:${targetAddress}`); + allPreviewsReady = true; + } + property bool shouldShow: { + const hoverConditions = (popupMouseArea.containsMouse || root.buttonHovered) + return hoverConditions && allPreviewsReady; + } + property bool show: false + + onShouldShowChanged: { + if (shouldShow) { + // show = true; + updateTimer.restart(); + } else { + updateTimer.restart(); + } + } + Timer { + id: updateTimer + interval: 100 + onTriggered: { + previewPopup.show = previewPopup.shouldShow + } + } + anchor { + window: root.QsWindow.window + rect: { + if (root.lastHoveredButton === null) return; // Don't update + const parentWindow = root.QsWindow.window + const mappedPosition = parentWindow.mapFromItem(root.lastHoveredButton, root.lastHoveredButton.width / 2, root.lastHoveredButton.height / 2) + const modifiedX = mappedPosition.x - implicitWidth / 2 + const modifiedY = 0 + return Qt.rect(modifiedX, modifiedY, implicitWidth, implicitHeight) + } + gravity: Edges.Top + edges: Edges.Top + } + visible: popupBackground.visible + color: "transparent" + implicitWidth: root.QsWindow.window.width + implicitHeight: popupBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 + + MouseArea { + id: popupMouseArea + anchors.bottom: parent.bottom + implicitWidth: popupBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 + implicitHeight: popupBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 + anchors.horizontalCenter: parent.horizontalCenter + hoverEnabled: true + StyledRectangularShadow { + target: popupBackground + opacity: previewPopup.show ? 1 : 0 + visible: opacity > 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + } + Rectangle { + id: popupBackground + property real padding: 5 + opacity: previewPopup.show ? 1 : 0 + visible: opacity > 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + color: Appearance.colors.colLayer1 + radius: Appearance.rounding.normal + anchors.bottom: parent.bottom + anchors.bottomMargin: Appearance.sizes.elevationMargin + anchors.horizontalCenter: parent.horizontalCenter + implicitWidth: previewRowLayout.implicitWidth + padding * 2 + implicitHeight: root.maxWindowPreviewHeight + padding * 2 + + RowLayout { + id: previewRowLayout + anchors.centerIn: parent + Repeater { + model: previewPopup.appTopLevel?.toplevels ?? [] + RippleButton { + id: windowButton + required property var modelData + padding: 0 + middleClickAction: () => { + windowButton.modelData?.close(); + } + onClicked: { + windowButton.modelData?.activate(); + } + contentItem: Item { + implicitWidth: screencopyView.implicitWidth + implicitHeight: screencopyView.implicitHeight + ScreencopyView { + id: screencopyView + anchors.centerIn: parent + captureSource: previewPopup ? windowButton.modelData : null + live: true + paintCursor: true + constraintSize: Qt.size(root.maxWindowPreviewWidth, root.maxWindowPreviewHeight) + onHasContentChanged: { + previewPopup.updatePreviewReadiness(); + } + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: screencopyView.width + height: screencopyView.height + radius: Appearance.rounding.small + } + } + } + ButtonGroup { + contentWidth: parent.width - anchors.margins * 2 + anchors { + top: parent.top + left: parent.left + right: parent.right + margins: 3 + } + WrapperRectangle { + Layout.fillWidth: true + color: Appearance.m3colors.m3surfaceContainer + radius: Appearance.rounding.small + margin: 5 + StyledText { + Layout.fillWidth: true + font.pixelSize: Appearance.font.pixelSize.small + text: windowButton.modelData?.title + elide: Text.ElideRight + color: Appearance.m3colors.m3onSurface + } + } + GroupButton { + id: closeButton + colBackground: Appearance.m3colors.m3surfaceContainer + baseWidth: 30 + baseHeight: 30 + buttonRadius: Appearance.rounding.full + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: "close" + iconSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3onSurface + } + onClicked: { + windowButton.modelData?.close(); + } + } + } + } + } + } + } } } } -} \ No newline at end of file +} diff --git a/.config/quickshell/modules/dock/DockButton.qml b/.config/quickshell/modules/dock/DockButton.qml index f80661582..6c3010bf6 100644 --- a/.config/quickshell/modules/dock/DockButton.qml +++ b/.config/quickshell/modules/dock/DockButton.qml @@ -7,7 +7,7 @@ import QtQuick.Layouts RippleButton { Layout.fillHeight: true - implicitWidth: background.height + implicitWidth: implicitHeight - topInset - bottomInset buttonRadius: Appearance.rounding.normal topInset: dockVisualBackground.margin + dockRow.padding From 18e23618eadc4437926b3019e9b2936530b0addd Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 11:46:20 +0200 Subject: [PATCH 579/824] dock: previews: morphing anims --- .../modules/common/ConfigOptions.qml | 1 + .config/quickshell/modules/dock/DockApps.qml | 23 +++++++++++++++---- .config/quickshell/shell.qml | 3 ++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index cb29626a0..9db16c2ec 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -38,6 +38,7 @@ Singleton { } property QtObject dock: QtObject { + property bool enable: false property real height: 60 property real hoverRegionHeight: 3 property bool pinnedOnStartup: false diff --git a/.config/quickshell/modules/dock/DockApps.qml b/.config/quickshell/modules/dock/DockApps.qml index 7704a8cf2..13f6edaac 100644 --- a/.config/quickshell/modules/dock/DockApps.qml +++ b/.config/quickshell/modules/dock/DockApps.qml @@ -16,10 +16,12 @@ import Quickshell.Hyprland Item { id: root property real maxWindowPreviewHeight: 200 - property real maxWindowPreviewWidth: 350 + property real maxWindowPreviewWidth: 300 + property Item lastHoveredButton property bool buttonHovered: false property bool requestDockShow: previewPopup.show + property real popupX: 0 implicitWidth: rowLayout.implicitWidth implicitHeight: rowLayout.implicitHeight @@ -102,6 +104,7 @@ Item { const parentWindow = root.QsWindow.window const mappedPosition = parentWindow.mapFromItem(root.lastHoveredButton, root.lastHoveredButton.width / 2, root.lastHoveredButton.height / 2) const modifiedX = mappedPosition.x - implicitWidth / 2 + root.popupX = modifiedX const modifiedY = 0 return Qt.rect(modifiedX, modifiedY, implicitWidth, implicitHeight) } @@ -111,13 +114,13 @@ Item { visible: popupBackground.visible color: "transparent" implicitWidth: root.QsWindow.window.width - implicitHeight: popupBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 + implicitHeight: popupMouseArea.implicitHeight + Appearance.sizes.elevationMargin * 2 MouseArea { id: popupMouseArea anchors.bottom: parent.bottom implicitWidth: popupBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 - implicitHeight: popupBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 + implicitHeight: root.maxWindowPreviewHeight + Appearance.sizes.elevationMargin * 2 anchors.horizontalCenter: parent.horizontalCenter hoverEnabled: true StyledRectangularShadow { @@ -136,13 +139,23 @@ Item { Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } + clip: true color: Appearance.colors.colLayer1 radius: Appearance.rounding.normal anchors.bottom: parent.bottom anchors.bottomMargin: Appearance.sizes.elevationMargin - anchors.horizontalCenter: parent.horizontalCenter + implicitHeight: previewRowLayout.implicitHeight + padding * 2 implicitWidth: previewRowLayout.implicitWidth + padding * 2 - implicitHeight: root.maxWindowPreviewHeight + padding * 2 + x: root.popupX + Behavior on x { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + Behavior on implicitWidth { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on implicitHeight { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } RowLayout { id: previewRowLayout diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 35631100a..edd72ba92 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -2,6 +2,7 @@ //@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic //@ pragma Env QS_NO_RELOAD_POPUP=1 +import "./modules/common/" import "./modules/bar/" import "./modules/cheatsheet/" import "./modules/dock/" @@ -47,7 +48,7 @@ ShellRoot { Loader { active: enableBar; sourceComponent: Bar {} } Loader { active: enableCheatsheet; sourceComponent: Cheatsheet {} } - Loader { active: enableDock; sourceComponent: Dock {} } + Loader { active: enableDock || ConfigOptions?.dock.enable; sourceComponent: Dock {} } Loader { active: enableMediaControls; sourceComponent: MediaControls {} } Loader { active: enableNotificationPopup; sourceComponent: NotificationPopup {} } Loader { active: enableOnScreenDisplayBrightness; sourceComponent: OnScreenDisplayBrightness {} } From b870f6b93054b4706ded9315a9918e4a501a80f2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 12:21:56 +0200 Subject: [PATCH 580/824] search: fix slow list move --- .config/quickshell/modules/overview/SearchWidget.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 016b82047..351d9fb29 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -268,6 +268,7 @@ Item { // Wrapper bottomMargin: 10 spacing: 2 KeyNavigation.up: searchBar + highlightMoveDuration : 100 onFocusChanged: { if(focus) appResults.currentIndex = 1; From 47e26fd62f1e910d6a3c70c5b78a09d1aa510f63 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 12:22:03 +0200 Subject: [PATCH 581/824] dock: fix warnings --- .config/quickshell/modules/dock/DockApps.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/dock/DockApps.qml b/.config/quickshell/modules/dock/DockApps.qml index 13f6edaac..4e256f32b 100644 --- a/.config/quickshell/modules/dock/DockApps.qml +++ b/.config/quickshell/modules/dock/DockApps.qml @@ -64,7 +64,9 @@ Item { property bool allPreviewsReady: false Connections { target: root - onLastHoveredButtonChanged: previewPopup.allPreviewsReady = false; // Reset readiness when the hovered button changes + function onLastHoveredButtonChanged() { + previewPopup.allPreviewsReady = false; // Reset readiness when the hovered button changes + } } function updatePreviewReadiness() { for(var i = 0; i < previewRowLayout.children.length; i++) { @@ -113,7 +115,7 @@ Item { } visible: popupBackground.visible color: "transparent" - implicitWidth: root.QsWindow.window.width + implicitWidth: root.QsWindow.window?.width ?? 1 implicitHeight: popupMouseArea.implicitHeight + Appearance.sizes.elevationMargin * 2 MouseArea { From aef313a8b961e739303bb92dc30e570ac90b80bd Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 13:40:22 +0200 Subject: [PATCH 582/824] overview: fix shadow cutoff --- .config/quickshell/modules/overview/Overview.qml | 4 ++-- .config/quickshell/modules/overview/OverviewWidget.qml | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 969145179..965e151a3 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -80,8 +80,8 @@ Scope { } } - implicitWidth: columnLayout.width - implicitHeight: columnLayout.height + implicitWidth: columnLayout.implicitWidth + implicitHeight: columnLayout.implicitHeight function setSearchingText(text) { searchWidget.setSearchingText(text); diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index 1b0673f2b..a8271fee0 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -54,13 +54,14 @@ Item { } Rectangle { // Background id: overviewBackground - + property real padding: 10 anchors.fill: parent + anchors.margins: Appearance.sizes.elevationMargin - implicitWidth: workspaceColumnLayout.implicitWidth + 5 * 2 - implicitHeight: workspaceColumnLayout.implicitHeight + 5 * 2 + implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2 + implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2 + radius: Appearance.rounding.screenRounding * root.scale + padding color: Appearance.colors.colLayer0 - radius: Appearance.rounding.screenRounding * root.scale + 5 * 2 ColumnLayout { id: workspaceColumnLayout From 3a3fcc482fd9b4845e7412acbf9fc148300eb6cf Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 16:55:54 +0200 Subject: [PATCH 583/824] icon guess: update regexes --- .config/quickshell/services/AppSearch.qml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/services/AppSearch.qml b/.config/quickshell/services/AppSearch.qml index f76e25741..2af1187de 100644 --- a/.config/quickshell/services/AppSearch.qml +++ b/.config/quickshell/services/AppSearch.qml @@ -26,12 +26,16 @@ Singleton { }) property var regexSubstitutions: [ { - "regex": "/^steam_app_(\\d+)$/", + "regex": /^steam_app_(\\d+)$/, "replace": "steam_icon_$1" }, { - "regex": "Minecraft.*", + "regex": /Minecraft.*/, "replace": "minecraft" + }, + { + "regex": /.*polkit.*/, + "replace": "system-lock-screen" } ] From b1a0e3c258d3bd0d42f14cb9b74a2a2565c195fb Mon Sep 17 00:00:00 2001 From: Bishoy Ehab Date: Fri, 30 May 2025 17:40:55 +0300 Subject: [PATCH 584/824] Update update.sh script to copy with -p option (preserve mode) --- update.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/update.sh b/update.sh index 09da49853..be6d638d0 100755 --- a/update.sh +++ b/update.sh @@ -214,7 +214,7 @@ handle_file_conflict() { case $choice in 1) - cp "$repo_file" "$home_file" + cp -p "$repo_file" "$home_file" log_success "Replaced $home_file with repository version" break ;; @@ -224,12 +224,12 @@ handle_file_conflict() { ;; 3) mv "$home_file" "${dirname}/${filename}.old" - cp "$repo_file" "$home_file" + cp -p "$repo_file" "$home_file" log_success "Backed up local file to ${filename}.old and updated with repository version" break ;; 4) - cp "$repo_file" "${dirname}/${filename}.new" + cp -p "$repo_file" "${dirname}/${filename}.new" log_success "Saved repository version as ${filename}.new, kept local file" break ;; @@ -251,7 +251,7 @@ handle_file_conflict() { case $subchoice in r) - cp "$repo_file" "$home_file" + cp -p "$repo_file" "$home_file" log_success "Replaced $home_file with repository version" break ;; @@ -261,12 +261,12 @@ handle_file_conflict() { ;; b) mv "$home_file" "${dirname}/${filename}.old" - cp "$repo_file" "$home_file" + cp -p "$repo_file" "$home_file" log_success "Backed up local file to ${filename}.old and updated" break ;; n) - cp "$repo_file" "${dirname}/${filename}.new" + cp -p "$repo_file" "${dirname}/${filename}.new" log_success "Saved repository version as ${filename}.new" break ;; @@ -756,7 +756,7 @@ if [[ "$process_files" == true ]]; then fi else # New file, copy it - cp "$repo_file" "$home_file" + cp -p "$repo_file" "$home_file" log_success "Created new file: $home_file" ((files_created++)) fi From f61da8e09a0338766809a4e532dd43e985b51be5 Mon Sep 17 00:00:00 2001 From: Bishoy Ehab Date: Fri, 30 May 2025 17:45:57 +0300 Subject: [PATCH 585/824] change to pull no matter any branch --- update.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update.sh b/update.sh index be6d638d0..b71984516 100755 --- a/update.sh +++ b/update.sh @@ -580,7 +580,7 @@ fi if git remote get-url origin &>/dev/null; then # Pull changes log_info "Pulling changes from origin/$current_branch..." - if git pull origin "$current_branch"; then + if git pull; then log_success "Successfully pulled latest changes" else log_warning "Failed to pull changes from remote. Continuing with local repository..." From 9322ae2e5cb43740fc9c771b3ba8d6c5e3a35c9b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 17:01:03 +0200 Subject: [PATCH 586/824] polkit: use kde's --- .config/hypr/hyprland/execs.conf | 2 +- .../PKGBUILD | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename arch-packages/{illogical-impulse-gnome => illogical-impulse-kde}/PKGBUILD (58%) diff --git a/.config/hypr/hyprland/execs.conf b/.config/hypr/hyprland/execs.conf index a9f867d14..3301f2452 100644 --- a/.config/hypr/hyprland/execs.conf +++ b/.config/hypr/hyprland/execs.conf @@ -8,7 +8,7 @@ exec-once = fcitx5 # Core components (authentication, lock screen, notification daemon) exec-once = gnome-keyring-daemon --start --components=secrets -exec-once = /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 || /usr/libexec/polkit-gnome-authentication-agent-1 +exec-once = /usr/lib/polkit-kde-authentication-agent-1 || /usr/libexec/polkit-kde-authentication-agent-1 || /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 || /usr/libexec/polkit-gnome-authentication-agent-1 exec-once = hypridle exec-once = dbus-update-activation-environment --all exec-once = sleep 1 && dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP # Some fix idk diff --git a/arch-packages/illogical-impulse-gnome/PKGBUILD b/arch-packages/illogical-impulse-kde/PKGBUILD similarity index 58% rename from arch-packages/illogical-impulse-gnome/PKGBUILD rename to arch-packages/illogical-impulse-kde/PKGBUILD index 4521b0ce3..35f8d669f 100644 --- a/arch-packages/illogical-impulse-gnome/PKGBUILD +++ b/arch-packages/illogical-impulse-kde/PKGBUILD @@ -1,11 +1,11 @@ -pkgname=illogical-impulse-gnome +pkgname=illogical-impulse-kde pkgver=1.0 pkgrel=2 -pkgdesc='Illogical Impulse GNOME Dependencies' +pkgdesc='Illogical Impulse KDE Dependencies' arch=(any) license=(None) depends=( - polkit-gnome + polkit-kde-agent gnome-keyring gnome-control-center networkmanager better-control-git From c1364221977d0c2d67fc1a671c899c510316f729 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 17:23:09 +0200 Subject: [PATCH 587/824] update dependencies --- README.md | 17 +++--------- .../illogical-impulse-basic/PKGBUILD | 3 --- .../illogical-impulse-fonts-themes/PKGBUILD | 13 ++++----- arch-packages/illogical-impulse-gtk/PKGBUILD | 18 ------------- .../illogical-impulse-hyprland/PKGBUILD | 3 ++- .../illogical-impulse-screencapture/PKGBUILD | 2 +- .../illogical-impulse-toolkit/PKGBUILD | 27 +++++++++++++++++++ .../illogical-impulse-widgets/PKGBUILD | 8 +++--- diagnose | 2 -- 9 files changed, 43 insertions(+), 50 deletions(-) delete mode 100644 arch-packages/illogical-impulse-gtk/PKGBUILD create mode 100644 arch-packages/illogical-impulse-toolkit/PKGBUILD diff --git a/README.md b/README.md index 6a5dfcdc5..584eaf668 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,9 @@ # Quickshell-powered illogical-impulse -## Not ready, but feel free to try it. It's simple: +## Quite usable at this point, see instructions below -- **Assumption**: You are already using the AGS illogical-impulse -- **Install Qt packages** (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-svg qt5-translations qt5-wayland qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland syntax-highlighting` -- **Install quickshell and more stuff**: `yay -S quickshell matugen-bin grimblast wtype kde-material-you-colors` -- **Dolphin fix** for it asking what program to open file with every time: `sudo pacman -S archlinux-xdg-menu && XDG_MENU_PREFIX=arch- kbuildsycoca6; sudo ln -s /etc/xdg/menus/plasma-applications.menu /etc/xdg/menus/applications.menu` -- **Copy** `.config/quickshell` folder and hyprland config files in `.config/hypr/hyprland/` (backing up is your responsibility) (or you can create a new user) -- **Run quickshell** with `qs` and see how things are - it's not finished, but **feedback is very welcome** - - We currently have bar, right sidebar, search/overview - - Tips: scrolled windows are flickable - - -## Notes -- Gradience will no longer be needed +- **Installation**: Run the install script (Note: If someone does this on a fresh system, please let us know if it works or is missing anything 👉 https://github.com/end-4/dots-hyprland/pull/1276) +- **Dolphin fix** so it won't ask which program to open file with every time: `sudo pacman -S archlinux-xdg-menu && XDG_MENU_PREFIX=arch- kbuildsycoca6; sudo ln -s /etc/xdg/menus/plasma-applications.menu /etc/xdg/menus/applications.menu` +- TODO: Update install script to include the above fix

【 end_4's Hyprland dotfiles 】

diff --git a/arch-packages/illogical-impulse-basic/PKGBUILD b/arch-packages/illogical-impulse-basic/PKGBUILD index ac74a3c36..f548174e5 100644 --- a/arch-packages/illogical-impulse-basic/PKGBUILD +++ b/arch-packages/illogical-impulse-basic/PKGBUILD @@ -11,14 +11,11 @@ depends=( cliphist cmake curl - fuzzel rsync wget ripgrep jq npm meson - typescript - gjs xdg-user-dirs ) diff --git a/arch-packages/illogical-impulse-fonts-themes/PKGBUILD b/arch-packages/illogical-impulse-fonts-themes/PKGBUILD index 37f710105..82fda5700 100644 --- a/arch-packages/illogical-impulse-fonts-themes/PKGBUILD +++ b/arch-packages/illogical-impulse-fonts-themes/PKGBUILD @@ -6,18 +6,15 @@ arch=(any) license=(None) depends=( adw-gtk-theme-git - qt5ct - qt6ct - qt5-wayland + fish fontconfig + foot + kde-material-you-colors + matugen-bin + starship ttf-readex-pro ttf-jetbrains-mono-nerd ttf-material-symbols-variable-git ttf-rubik-vf ttf-gabarito-git - fish - foot - starship - kvantum - kvantum-qt5 ) diff --git a/arch-packages/illogical-impulse-gtk/PKGBUILD b/arch-packages/illogical-impulse-gtk/PKGBUILD deleted file mode 100644 index 50a57d1c8..000000000 --- a/arch-packages/illogical-impulse-gtk/PKGBUILD +++ /dev/null @@ -1,18 +0,0 @@ -pkgname=illogical-impulse-gtk -pkgver=1.0 -pkgrel=1 -pkgdesc='Illogical Impulse GTK Dependencies' -arch=(any) -license=(None) -depends=( - webp-pixbuf-loader - gtk-layer-shell - gtk3 - gtksourceview3 - gobject-introspection - upower - yad - ydotool - xdg-user-dirs-gtk -) - diff --git a/arch-packages/illogical-impulse-hyprland/PKGBUILD b/arch-packages/illogical-impulse-hyprland/PKGBUILD index c743af7ac..93c7b5f8e 100644 --- a/arch-packages/illogical-impulse-hyprland/PKGBUILD +++ b/arch-packages/illogical-impulse-hyprland/PKGBUILD @@ -12,8 +12,9 @@ depends=( hyprland-qt-support hyprland-qtutils hyprlock - xdg-desktop-portal-hyprland hyprcursor hyprwayland-scanner hyprland + xdg-desktop-portal-hyprland + wl-clipboard ) diff --git a/arch-packages/illogical-impulse-screencapture/PKGBUILD b/arch-packages/illogical-impulse-screencapture/PKGBUILD index ab5911fb9..7893cc05b 100644 --- a/arch-packages/illogical-impulse-screencapture/PKGBUILD +++ b/arch-packages/illogical-impulse-screencapture/PKGBUILD @@ -7,7 +7,7 @@ license=(None) depends=( swappy wf-recorder - grim + grimblast tesseract tesseract-data-eng slurp diff --git a/arch-packages/illogical-impulse-toolkit/PKGBUILD b/arch-packages/illogical-impulse-toolkit/PKGBUILD new file mode 100644 index 000000000..c73e804b0 --- /dev/null +++ b/arch-packages/illogical-impulse-toolkit/PKGBUILD @@ -0,0 +1,27 @@ +pkgname=illogical-impulse-toolkit +pkgver=1.0 +pkgrel=1 +pkgdesc='Illogical Impulse GTK/Qt Dependencies' +arch=(any) +license=(None) +depends=( + qt6-5compat + qt6-base + qt6-declarative + qt6-imageformats + qt6-multimedia + qt6-positioning + qt6-quicktimeline + qt6-sensors + qt6-svg + qt6-tools + qt6-translations + qt6-virtualkeyboard + qt6-wayland + syntax-highlighting + upower + wtype + xdg-user-dirs-gtk + yad + ydotool +) diff --git a/arch-packages/illogical-impulse-widgets/PKGBUILD b/arch-packages/illogical-impulse-widgets/PKGBUILD index cdff28667..48be0e88a 100644 --- a/arch-packages/illogical-impulse-widgets/PKGBUILD +++ b/arch-packages/illogical-impulse-widgets/PKGBUILD @@ -5,12 +5,12 @@ pkgdesc='Illogical Impulse Widget Dependencies' arch=(any) license=(None) depends=( - dart-sass + fuzzel hypridle - hyprutils + hyprutils hyprlock - wlogout - wl-clipboard hyprpicker nm-connection-editor + quickshell + wlogout ) diff --git a/diagnose b/diagnose index c793d7998..f0fc6d2dd 100755 --- a/diagnose +++ b/diagnose @@ -35,7 +35,6 @@ ii_check_distro() { } ii_check_venv() { source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate - which gradience-cli deactivate } ii_check_ags() { @@ -66,7 +65,6 @@ x declare -p ILLOGICAL_IMPULSE_VIRTUAL_ENV # $XDG_STATE_HOME/quickshell/.venv e "Checking directories/files" x ls -l ~/.local/state/ags/.venv -x ls -l $ILLOGICAL_IMPULSE_VIRTUAL_ENV/bin/gradience-cli x ls $XDG_DATA_HOME/glib-2.0/schemas x ls $XDG_DATA_HOME/gradience #x cat ~/.config/ags/ From e8aa344976c42389c58f4f7703a4348ff3b10d6b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 17:28:18 +0200 Subject: [PATCH 588/824] nuke ags dependencies --- arch-packages/illogical-impulse-ags/.SRCINFO | 36 ---- .../illogical-impulse-ags/.gitignore | 2 - arch-packages/illogical-impulse-ags/PKGBUILD | 49 ----- .../illogical-impulse-agsv1/.gitignore | 2 - .../illogical-impulse-agsv1/PKGBUILD | 184 ------------------ .../illogical-impulse-audio/PKGBUILD | 1 - .../illogical-impulse-backlight/PKGBUILD | 2 + .../illogical-impulse-basic/PKGBUILD | 1 - .../illogical-impulse-widgets/PKGBUILD | 1 + 9 files changed, 3 insertions(+), 275 deletions(-) delete mode 100644 arch-packages/illogical-impulse-ags/.SRCINFO delete mode 100644 arch-packages/illogical-impulse-ags/.gitignore delete mode 100644 arch-packages/illogical-impulse-ags/PKGBUILD delete mode 100644 arch-packages/illogical-impulse-agsv1/.gitignore delete mode 100644 arch-packages/illogical-impulse-agsv1/PKGBUILD diff --git a/arch-packages/illogical-impulse-ags/.SRCINFO b/arch-packages/illogical-impulse-ags/.SRCINFO deleted file mode 100644 index 8a36546b2..000000000 --- a/arch-packages/illogical-impulse-ags/.SRCINFO +++ /dev/null @@ -1,36 +0,0 @@ -pkgbase = ii-aylurs-gtk-shell-git - pkgdesc = Aylurs's Gtk Shell (AGS), version fixed for illogical-impulse dotfiles - pkgver = 1.8.2.r2.g1115022 - pkgrel = 1 - url = https://github.com/Aylur/ags - arch = x86_64 - license = GPL3 - makedepends = git - makedepends = gobject-introspection - makedepends = meson - makedepends = npm - makedepends = typescript - depends = gjs - depends = glib2 - depends = glib2-devel - depends = glibc - depends = gtk3 - depends = gtk-layer-shell - depends = libpulse - depends = pam - optdepends = gnome-bluetooth-3.0: required for bluetooth service - optdepends = greetd: required for greetd service - optdepends = libdbusmenu-gtk3: required for systemtray service - optdepends = libsoup3: required for the Utils.fetch feature - optdepends = libnotify: required for sending notifications - optdepends = networkmanager: required for network service - optdepends = power-profiles-daemon: required for powerprofiles service - optdepends = upower: required for battery service - conflicts = aylurs-gtk-shell - backup = etc/pam.d/ags - source = git+https://github.com/Aylur/ags - source = git+https://gitlab.gnome.org/GNOME/libgnome-volume-control - sha256sums = SKIP - sha256sums = SKIP - -pkgname = aylurs-gtk-shell-git diff --git a/arch-packages/illogical-impulse-ags/.gitignore b/arch-packages/illogical-impulse-ags/.gitignore deleted file mode 100644 index 02b391b25..000000000 --- a/arch-packages/illogical-impulse-ags/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/ags/ -/libgnome-volume-control/ diff --git a/arch-packages/illogical-impulse-ags/PKGBUILD b/arch-packages/illogical-impulse-ags/PKGBUILD deleted file mode 100644 index 689dacc1d..000000000 --- a/arch-packages/illogical-impulse-ags/PKGBUILD +++ /dev/null @@ -1,49 +0,0 @@ -# Modified from AUR package "aylurs-gtk-shell-git" maintained by kotontrion -pkgname=illogical-impulse-ags -_pkgname=ags -pkgver=r525.05e0f23 -pkgrel=3 -pkgdesc="Aylurs's Gtk Shell (AGS), version fixed for illogical-impulse dotfiles." -arch=('x86_64') -url="https://github.com/Aylur/ags" -license=('GPL3') -makedepends=('git' 'gobject-introspection' 'meson' 'npm' 'typescript') -depends=('gvfs' 'gjs' 'glib2' 'glib2-devel' 'glibc' 'gtk3' 'gtk-layer-shell' 'libpulse' 'pam' 'gnome-bluetooth-3.0' 'gammastep') -optdepends=('greetd: required for greetd service' - 'libdbusmenu-gtk3: required for systemtray service' - 'libsoup3: required for the Utils.fetch feature' - 'libnotify: required for sending notifications' - 'networkmanager: required for network service' - 'power-profiles-daemon: required for powerprofiles service' - 'upower: required for battery service') -conflicts=('aylurs-gtk-shell' 'aylurs-gtk-shell-git') -backup=('etc/pam.d/ags') -source=("git+${url}.git#commit=05e0f23534fa30c1db2a142664ee8f71e38db260" - "git+https://gitlab.gnome.org/GNOME/libgnome-volume-control") -sha256sums=('SKIP' - 'SKIP') - -pkgver(){ - cd $srcdir/$_pkgname - printf 'r%s.%s' "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" -} - -prepare() { - cd $srcdir/$_pkgname - git submodule init - git config submodule.subprojects/gvc.url "$srcdir/libgnome-volume-control" - git -c protocol.file.allow=always submodule update -} - -build() { - cd $srcdir/$_pkgname - npm install - arch-meson build --libdir "lib/$_pkgname" -Dbuild_types=true - meson compile -C build -} - -package() { - cd $srcdir/$_pkgname - meson install -C build --destdir "$pkgdir" - ln -sf /usr/share/com.github.Aylur.ags/com.github.Aylur.ags ${pkgdir}/usr/bin/ags -} diff --git a/arch-packages/illogical-impulse-agsv1/.gitignore b/arch-packages/illogical-impulse-agsv1/.gitignore deleted file mode 100644 index 02b391b25..000000000 --- a/arch-packages/illogical-impulse-agsv1/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/ags/ -/libgnome-volume-control/ diff --git a/arch-packages/illogical-impulse-agsv1/PKGBUILD b/arch-packages/illogical-impulse-agsv1/PKGBUILD deleted file mode 100644 index 8728adaba..000000000 --- a/arch-packages/illogical-impulse-agsv1/PKGBUILD +++ /dev/null @@ -1,184 +0,0 @@ -# Borrowed from https://github.com/kotontrion/PKGBUILDS/blob/main/agsv1/PKGBUILD -# -# Maintainer: kotontrion - -# This package is only intended to be used while migrating from ags v1.8.2 to ags v2.0.0. -# Many ags configs are quite big and it takes a while to migrate, therefore I made this package -# to install ags v1.8.2 as "agsv1", so both versions can be installed at the same time, making it -# possible to migrate bit by bit while still having a working v1 config around. -# -# First update the aylurs-gtk-shell package to v2, then install this one. -# -# This package won't receive any updates anymore, so as soon as you migrated, uninstall this one. - -pkgname=illogical-impulse-agsv1 -_pkgname=ags -pkgver=1.9.0 -pkgrel=1 -pkgdesc="Aylurs's Gtk Shell (AGS), An eww inspired gtk widget system." -arch=('x86_64') -url="https://github.com/Aylur/ags" -license=('GPL-3.0-only') -makedepends=('git' 'gobject-introspection' 'meson' 'glib2-devel' 'npm' 'typescript') -depends=('gvfs' 'gjs' 'glib2' 'glib2-devel' 'glibc' 'gtk3' 'gtk-layer-shell' 'libpulse' 'pam' 'gnome-bluetooth-3.0' 'gammastep') -optdepends=('gnome-bluetooth-3.0: required for bluetooth service' - 'greetd: required for greetd service' - 'libdbusmenu-gtk3: required for systemtray service' - 'libsoup3: required for the Utils.fetch feature' - 'libnotify: required for sending notifications' - 'networkmanager: required for network service' - 'power-profiles-daemon: required for powerprofiles service' - 'upower: required for battery service') -conflicts=('illogical-impulse-ags') -backup=('etc/pam.d/ags') -source=("$pkgname-$pkgver.tar.gz::https://github.com/Aylur/ags/archive/refs/tags/v${pkgver}.tar.gz" - "git+https://gitlab.gnome.org/GNOME/libgnome-volume-control") -sha256sums=('962f99dcf202eef30e978d1daedc7cdf213e07a3b52413c1fb7b54abc7bd08e6' - SKIP) - -prepare() { - cd "$srcdir/$_pkgname-$pkgver" - mv -T "$srcdir"/libgnome-volume-control subprojects/gvc - - # Overwrite greetd.ts with fixed version - cat > src/service/greetd.ts << 'EOF' -import App from '../app.js'; -import Service from '../service.js'; -import GLib from 'gi://GLib'; -import Gio from 'gi://Gio'; - -Gio._promisify(Gio.InputStream.prototype, 'read_bytes_async'); -const SOCK = GLib.getenv('GREETD_SOCK'); - -type Request = { - create_session: { - username: string - } - post_auth_message_response: { - response?: string - } - start_session: { - cmd: string[] - env: string[] - } - cancel_session: Record -} - -type Response = { - type: 'success' -} | { - type: 'error' - error_type: 'auth_error' | 'error' - description: string -} | { - type: 'auth_message' - auth_message_type: 'visible' | 'secret' | 'info' | 'error' - auth_message: string -} - -export class Greetd extends Service { - static { Service.register(this); } - - private _decoder = new TextDecoder; - - readonly login = async ( - username: string, - password: string, - cmd: string[] | string, - env: string[] = [], - ) => { - const session = await this.createSession(username); - if (session.type !== 'auth_message') { - this.cancelSession(); - throw session; - } - - const auth = await this.postAuth(password); - if (auth.type !== 'success') { - this.cancelSession(); - throw auth; - } - - const start = await this.startSession(cmd, env); - if (start.type !== 'success') { - this.cancelSession(); - throw start; - } - - App.quit(); - }; - - readonly createSession = (username: string) => { - return this._send('create_session', { username }); - }; - - readonly postAuth = (response?: string) => { - return this._send('post_auth_message_response', { response }); - }; - - readonly startSession = (cmd: string[] | string, env: string[] = []) => { - const cmdv = Array.isArray(cmd) - ? cmd - : GLib.shell_parse_argv(cmd)[1]; - - return this._send('start_session', { cmd: cmdv, env }); - }; - - readonly cancelSession = () => { - return this._send('cancel_session', {}); - }; - - private async _send(req: R, payload: Request[R]): Promise { - const connection = new Gio.SocketClient() - .connect(new Gio.UnixSocketAddress({ path: SOCK }), null); - - try { - const json = JSON.stringify({ type: req, ...payload }); - const ostream = new Gio.DataOutputStream({ - close_base_stream: true, - base_stream: connection.get_output_stream(), - byte_order: Gio.DataStreamByteOrder.HOST_ENDIAN, - }); - - const istream = connection.get_input_stream(); - - ostream.put_int32(json.length, null); - ostream.put_string(json, null); - - const data = await istream.read_bytes_async(4, GLib.PRIORITY_DEFAULT, null); - const raw = data.get_data(); - if (!raw) throw new Error("Failed to read length from greetd socket"); - const view = new DataView(raw.buffer, raw.byteOffset, raw.byteLength); - const length = view.getUint32(0, true); // true = little endian - - const res = await istream.read_bytes_async(length, GLib.PRIORITY_DEFAULT, null); - const resRaw = res.get_data(); - if (!resRaw) throw new Error("Failed to read response from greetd socket"); - - return JSON.parse(this._decoder.decode(resRaw)) as Response; - } finally { - connection.close(null); - } -} - -} - -export const greetd = new Greetd; -export default greetd; -EOF -} - - -build() { - cd "$srcdir/$_pkgname-$pkgver" - npm install - arch-meson build --libdir "lib/$_pkgname" -Dbuild_types=true - meson compile -C build -} - -package() { - cd "$srcdir/$_pkgname-$pkgver" - meson install -C build --destdir "$pkgdir" - rm ${pkgdir}/usr/bin/ags - ln -sf /usr/share/com.github.Aylur.ags/com.github.Aylur.ags ${pkgdir}/usr/bin/agsv1 -} diff --git a/arch-packages/illogical-impulse-audio/PKGBUILD b/arch-packages/illogical-impulse-audio/PKGBUILD index 7f43e5ef4..81054e0b7 100644 --- a/arch-packages/illogical-impulse-audio/PKGBUILD +++ b/arch-packages/illogical-impulse-audio/PKGBUILD @@ -9,6 +9,5 @@ depends=( wireplumber libdbusmenu-gtk3 playerctl - swww ) diff --git a/arch-packages/illogical-impulse-backlight/PKGBUILD b/arch-packages/illogical-impulse-backlight/PKGBUILD index fe216262c..2525be9d4 100644 --- a/arch-packages/illogical-impulse-backlight/PKGBUILD +++ b/arch-packages/illogical-impulse-backlight/PKGBUILD @@ -5,6 +5,8 @@ pkgdesc='Illogical Impulse Backlight Dependencies' arch=(any) license=(None) depends=( + gammastep + geoclue brightnessctl ddcutil ) diff --git a/arch-packages/illogical-impulse-basic/PKGBUILD b/arch-packages/illogical-impulse-basic/PKGBUILD index f548174e5..c338727f9 100644 --- a/arch-packages/illogical-impulse-basic/PKGBUILD +++ b/arch-packages/illogical-impulse-basic/PKGBUILD @@ -15,7 +15,6 @@ depends=( wget ripgrep jq - npm meson xdg-user-dirs ) diff --git a/arch-packages/illogical-impulse-widgets/PKGBUILD b/arch-packages/illogical-impulse-widgets/PKGBUILD index 48be0e88a..435bf3f74 100644 --- a/arch-packages/illogical-impulse-widgets/PKGBUILD +++ b/arch-packages/illogical-impulse-widgets/PKGBUILD @@ -12,5 +12,6 @@ depends=( hyprpicker nm-connection-editor quickshell + swww wlogout ) From cd86de26de3712cbacc70c02c71aa86fb63c20b3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 17:33:24 +0200 Subject: [PATCH 589/824] dependencies: add glib2 --- arch-packages/illogical-impulse-widgets/PKGBUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/arch-packages/illogical-impulse-widgets/PKGBUILD b/arch-packages/illogical-impulse-widgets/PKGBUILD index 435bf3f74..880116ed2 100644 --- a/arch-packages/illogical-impulse-widgets/PKGBUILD +++ b/arch-packages/illogical-impulse-widgets/PKGBUILD @@ -6,6 +6,7 @@ arch=(any) license=(None) depends=( fuzzel + glib2 # for `gsettings` it seems? hypridle hyprutils hyprlock From 1a2632909d955f74caa9e1d4854359807ee1b1bb Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 17:41:09 +0200 Subject: [PATCH 590/824] delete unnecessary copy of kde material you color config --- .config/matugen/templates/kde/config.conf | 74 ----------------------- 1 file changed, 74 deletions(-) delete mode 100644 .config/matugen/templates/kde/config.conf diff --git a/.config/matugen/templates/kde/config.conf b/.config/matugen/templates/kde/config.conf deleted file mode 100644 index 1880d7662..000000000 --- a/.config/matugen/templates/kde/config.conf +++ /dev/null @@ -1,74 +0,0 @@ -[CUSTOM] -# INSTRUCTIONS -# Run kde-material-you-colors with no arguments from terminal -# to debug your configuration changing in real time. - -# Monitor to get wallpaper from -# For me main is 0 but second one is 6, play with this to find yours -# Default is 0 -monitor = 0 - -# File containing absolute path of an image (Takes precedence over automatic wallpaper detection) -# Commented by default -file = /home/end/.local/state/quickshell/user/wallpaper.txt - -# List of 7 space separated colors (hex or rgb) to be used for text in pywal/konsole/KSyntaxHighlighting instead of wallpaper ones -# Accepted values are hex e.g #ff0000 and rgb e.g 255,0,0 colors (rgb is converted to hex) -# Commented by default -# Example using catppuccin color scheme: -custom_colors_list = #ED8796 #A6DA95 #EED49F #8AADF4 #F5BDE6 #8BD5CA #f5a97f - -# Enable Light mode -# Accepted values are True or False -# Commented by default to follow System Color Setting (Material You Light/Dark only) -# NOTE: -# Will fallback to dark mode if not defined here or enabled in Settings -#light = False - -# Alternative color mode (default is 0), some images return more than one color, this will use either the matched or last color -# Default is 0 -ncolor = 0 - -# Light scheme icons theme -iconslight = OneUI-light - -# Dark scheme icons theme -iconsdark = OneUI-dark - -# Use pywal to theme other programs using Material You colors -pywal=False - -# The amount of perceptible color for backgrounds in dark mode -# A number between 0 and 4.0 (limited for accessibility purposes) -# Defaults to 1 if not set -#light_blend_multiplier = 1.0 - -# The amount of perceptible color for backgrounds in dark mode -# A number between 0 and 4.0 (limited for accessibility purposes) -# Defaults to 1 if not set -#dark_blend_multiplier = 1.0 - -# A script/command that will be executed on start or wallpaper/dark/light/settings change -# example below using https://github.com/vlevit/notify-send.sh to send a desktop notification: -#on_change_hook = notify-send.sh "kde-material-you-colors" "This is a test" -t 2000 - -# Scheme Variant -# Changes between Material You scheme variants (0-8) -# 0 = Content -# 1 = Expressive -# 2 = Fidelity -# 3 = Monochrome -# 4 = Neutral -# 5 = TonalSpot -# 6 = Vibrant -# 7 = Rainbow -# 8 = FruitSalad -# Default is 5 -scheme_variant = 5 - -# Colorfulness -chroma_multiplier = 1 - -# Brightness -# An integer between 0.5 and 1.5 -tone_multiplier = 1 From cfba132074bd12e662e3069d4ec95815003c0cdb Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 17:51:40 +0200 Subject: [PATCH 591/824] make installation clearer since it's not yet merged --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 584eaf668..6951500af 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Quickshell-powered illogical-impulse ## Quite usable at this point, see instructions below -- **Installation**: Run the install script (Note: If someone does this on a fresh system, please let us know if it works or is missing anything 👉 https://github.com/end-4/dots-hyprland/pull/1276) +- **Installation**: Run `install.sh` (Note: If someone does this on a fresh system, please let us know if it works or is missing anything 👉 https://github.com/end-4/dots-hyprland/pull/1276) - **Dolphin fix** so it won't ask which program to open file with every time: `sudo pacman -S archlinux-xdg-menu && XDG_MENU_PREFIX=arch- kbuildsycoca6; sudo ln -s /etc/xdg/menus/plasma-applications.menu /etc/xdg/menus/applications.menu` - TODO: Update install script to include the above fix From 4079948b98043d43720ea28695c952ce8d9dc1bb Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 17:55:20 +0200 Subject: [PATCH 592/824] fix emoji picker --- .config/hypr/hyprland/keybinds.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index ed8fd0a2b..23367d9cd 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -50,7 +50,7 @@ bindd = Ctrl+Shift+Alt+Super, Delete, Shutdown, exec, systemctl poweroff || logi ##! Utilities # Screenshot, Record, OCR, Color picker, Clipboard history bindd = Super, V, Copy clipboard history entry, exec, qs ipc call TEST_ALIVE || pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # Clipboard history >> clipboard -bindd = Super, Period, Copy an emoji, exec, pkill fuzzel || ~/.local/bin/fuzzel-emoji # Emoji +bindd = Super, Period, Copy an emoji, exec, pkill fuzzel || ~/.local/bin/fuzzel-emoji copy # Emoji bindd = Super+Shift, S, Screen snip, exec, grimblast --freeze copy area # Screen snip >> clipboard bindd = Super+Shift+Alt, S, Screen snip and annotate, exec, grim -g "$(slurp)" - | swappy -f - # Screen snip and annotate # OCR From 30896812464f1999b97ec9888b21434280a5bd37 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 17:55:38 +0200 Subject: [PATCH 593/824] keybinds: add Windows close --- .config/hypr/hyprland/keybinds.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 23367d9cd..6e06c534c 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -88,6 +88,7 @@ bind = Super+Shift, Left, movewindow, l # [hidden] bind = Super+Shift, Right, movewindow, r # [hidden] bind = Super+Shift, Up, movewindow, u # [hidden] bind = Super+Shift, Down, movewindow, d # [hidden] +bind = Alt, F4, killactive, # [hidden] Close (Windows) bind = Super, Q, killactive, # Close bind = Super+Shift+Alt, Q, exec, hyprctl kill # Forcefully zap a window From e119d6d582816b8d6391692e0e527924a91f0edd Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 17:55:45 +0200 Subject: [PATCH 594/824] Update README.md --- README.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6951500af..041ac2383 100644 --- a/README.md +++ b/README.md @@ -44,15 +44,10 @@ ``` - **Manual** installation, other distros and more: - See the [Wiki](https://end-4.github.io/dots-hyprland-wiki/en/i-i/01setup/) - - (_Available in: English, Vietnamese, and Simplified Chinese. Translations are welcome._) - - **Default keybinds**: Parts similar to Windows and GNOME. Hit Super+/ for a list. -
- Here's an image, just in case... - - ![image](https://github.com/user-attachments/assets/dff2f842-5458-4f5a-89ec-3979095574de) - -
+ - **Default keybinds**: Should be somewhat familiar if you've used Windows or GNOME. + - For a list, hit `Super`+`/` + - For `foot` terminal, hit `Super`+`Enter` @@ -63,7 +58,7 @@ | Software | Purpose | | ------------- | ------------- | | [Hyprland](https://github.com/hyprwm/hyprland) | The compositor (for noobs, you can just call it a window manager) | - | [AGS](https://github.com/Aylur/ags) | A GTK widget system, responsible for the status bar, sidebars, etc. | + | [Quickshell](https://quickshell.outfoxxed.me/) | A QtQuick-based widget system, responsible for the status bar, sidebars, etc. | | [Fuzzel](https://mark.stosberg.com/fuzzel-a-great-dmenu-and-rofi-altenrative-for-wayland/) | For clipboard and emoji picker | From 47a34dc76bb148af81aeed4e60dce7a497e17f55 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 22:41:50 +0200 Subject: [PATCH 595/824] bar: add bottom position --- .config/quickshell/modules/bar/Bar.qml | 26 ++++++++++++------- .../modules/common/ConfigOptions.qml | 3 ++- .../modules/mediaControls/MediaControls.qml | 3 ++- .../OnScreenDisplayBrightness.qml | 5 +++- .../onScreenDisplay/OnScreenDisplayVolume.qml | 5 +++- .../quickshell/modules/overview/Overview.qml | 6 ++++- 6 files changed, 34 insertions(+), 14 deletions(-) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 5402b14ec..ac1f8cf8b 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -46,16 +46,20 @@ Scope { color: "transparent" anchors { - top: true + top: !ConfigOptions.bar.bottom + bottom: ConfigOptions.bar.bottom left: true right: true } Rectangle { // Bar background id: barContent - anchors.right: parent.right - anchors.left: parent.left - anchors.top: parent.top + anchors { + right: parent.right + left: parent.left + top: !ConfigOptions.bar.bottom ? parent.top : undefined + bottom: ConfigOptions.bar.bottom ? parent.bottom : undefined + } color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" height: barHeight @@ -425,23 +429,27 @@ Scope { // Round decorators Item { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: barContent.bottom + anchors { + left: parent.left + right: parent.right + // top: barContent.bottom + top: ConfigOptions.bar.bottom ? undefined : barContent.bottom + bottom: ConfigOptions.bar.bottom ? barContent.top : undefined + } height: Appearance.rounding.screenRounding RoundCorner { anchors.top: parent.top anchors.left: parent.left size: Appearance.rounding.screenRounding - corner: cornerEnum.topLeft + corner: ConfigOptions.bar.bottom ? cornerEnum.bottomLeft : cornerEnum.topLeft color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" } RoundCorner { anchors.top: parent.top anchors.right: parent.right size: Appearance.rounding.screenRounding - corner: cornerEnum.topRight + corner: ConfigOptions.bar.bottom ? cornerEnum.bottomRight : cornerEnum.topRight color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" } } diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 9db16c2ec..208223261 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -22,10 +22,11 @@ Singleton { } property QtObject bar: QtObject { + property bool bottom: false // Instead of top + property bool borderless: true property int batteryLowThreshold: 20 property string topLeftIcon: "spark" // Options: distro, spark property bool showBackground: true - property bool borderless: false property QtObject resources: QtObject { property bool alwaysShowSwap: true property bool alwaysShowCpu: false diff --git a/.config/quickshell/modules/mediaControls/MediaControls.qml b/.config/quickshell/modules/mediaControls/MediaControls.qml index 9c21bf710..81b1d62d9 100644 --- a/.config/quickshell/modules/mediaControls/MediaControls.qml +++ b/.config/quickshell/modules/mediaControls/MediaControls.qml @@ -87,7 +87,8 @@ Scope { WlrLayershell.namespace: "quickshell:mediaControls" anchors { - top: true + top: !ConfigOptions.bar.bottom + bottom: ConfigOptions.bar.bottom left: true } mask: Region { diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml index a70663b71..decb7537c 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml @@ -65,7 +65,10 @@ Scope { WlrLayershell.layer: WlrLayer.Overlay color: "transparent" - anchors.top: true + anchors { + top: !ConfigOptions.bar.bottom + bottom: ConfigOptions.bar.bottom + } mask: Region { item: osdValuesWrapper } diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml index 01d22ba8a..e1904354e 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml @@ -67,7 +67,10 @@ Scope { WlrLayershell.layer: WlrLayer.Overlay color: "transparent" - anchors.top: true + anchors { + top: !ConfigOptions.bar.bottom + bottom: ConfigOptions.bar.bottom + } mask: Region { item: osdValuesWrapper } diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 965e151a3..fe7ba1e52 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -90,7 +90,11 @@ Scope { ColumnLayout { id: columnLayout visible: GlobalStates.overviewOpen - anchors.horizontalCenter: parent.horizontalCenter + anchors { + horizontalCenter: parent.horizontalCenter + top: !ConfigOptions.bar.bottom ? parent.top : null + bottom: ConfigOptions.bar.bottom ? parent.bottom : null + } Keys.onPressed: (event) => { if (event.key === Qt.Key_Escape) { From 138fc543928355cafd99964663f53b2486cb0c02 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 22:43:02 +0200 Subject: [PATCH 596/824] dock: fix hover area accuracy, less braindead styling --- .config/quickshell/modules/dock/DockApps.qml | 116 +++++++++---------- 1 file changed, 55 insertions(+), 61 deletions(-) diff --git a/.config/quickshell/modules/dock/DockApps.qml b/.config/quickshell/modules/dock/DockApps.qml index 4e256f32b..9a8db93a9 100644 --- a/.config/quickshell/modules/dock/DockApps.qml +++ b/.config/quickshell/modules/dock/DockApps.qml @@ -2,6 +2,7 @@ import "root:/" import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls @@ -17,11 +18,12 @@ Item { id: root property real maxWindowPreviewHeight: 200 property real maxWindowPreviewWidth: 300 + property real windowControlsHeight: 30 property Item lastHoveredButton property bool buttonHovered: false property bool requestDockShow: previewPopup.show - property real popupX: 0 + property real popupX: parentWindow.mapFromItem(root.lastHoveredButton, root.lastHoveredButton.width / 2, root.lastHoveredButton.height / 2).x - implicitWidth / 2 implicitWidth: rowLayout.implicitWidth implicitHeight: rowLayout.implicitHeight @@ -101,30 +103,31 @@ Item { } anchor { window: root.QsWindow.window - rect: { - if (root.lastHoveredButton === null) return; // Don't update - const parentWindow = root.QsWindow.window - const mappedPosition = parentWindow.mapFromItem(root.lastHoveredButton, root.lastHoveredButton.width / 2, root.lastHoveredButton.height / 2) - const modifiedX = mappedPosition.x - implicitWidth / 2 - root.popupX = modifiedX - const modifiedY = 0 - return Qt.rect(modifiedX, modifiedY, implicitWidth, implicitHeight) - } - gravity: Edges.Top - edges: Edges.Top + adjustment: PopupAdjustment.None + gravity: Edges.Top | Edges.Right + edges: Edges.Top | Edges.Left + } visible: popupBackground.visible color: "transparent" implicitWidth: root.QsWindow.window?.width ?? 1 - implicitHeight: popupMouseArea.implicitHeight + Appearance.sizes.elevationMargin * 2 + implicitHeight: popupMouseArea.implicitHeight + root.windowControlsHeight + Appearance.sizes.elevationMargin * 2 MouseArea { id: popupMouseArea anchors.bottom: parent.bottom implicitWidth: popupBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 - implicitHeight: root.maxWindowPreviewHeight + Appearance.sizes.elevationMargin * 2 - anchors.horizontalCenter: parent.horizontalCenter + implicitHeight: root.maxWindowPreviewHeight + root.windowControlsHeight + Appearance.sizes.elevationMargin * 2 + // anchors.horizontalCenter: parent.horizontalCenter hoverEnabled: true + // x: previewPopup.width / 2 + root.popupX + // Behavior on x { + // animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + // } + x: { + const itemCenter = root.QsWindow.mapFromItem(root.lastHoveredButton, root.lastHoveredButton.width / 2, 0); + return itemCenter.x - width / 2 + } StyledRectangularShadow { target: popupBackground opacity: previewPopup.show ? 1 : 0 @@ -142,16 +145,13 @@ Item { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } clip: true - color: Appearance.colors.colLayer1 + color: Appearance.m3colors.m3surfaceContainer radius: Appearance.rounding.normal anchors.bottom: parent.bottom anchors.bottomMargin: Appearance.sizes.elevationMargin + anchors.horizontalCenter: parent.horizontalCenter implicitHeight: previewRowLayout.implicitHeight + padding * 2 implicitWidth: previewRowLayout.implicitWidth + padding * 2 - x: root.popupX - Behavior on x { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } Behavior on implicitWidth { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } @@ -174,12 +174,45 @@ Item { onClicked: { windowButton.modelData?.activate(); } - contentItem: Item { + contentItem: ColumnLayout { implicitWidth: screencopyView.implicitWidth implicitHeight: screencopyView.implicitHeight + + ButtonGroup { + contentWidth: parent.width - anchors.margins * 2 + WrapperRectangle { + Layout.fillWidth: true + color: ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainer) + radius: Appearance.rounding.small + margin: 5 + StyledText { + Layout.fillWidth: true + font.pixelSize: Appearance.font.pixelSize.small + text: windowButton.modelData?.title + elide: Text.ElideRight + color: Appearance.m3colors.m3onSurface + } + } + GroupButton { + id: closeButton + colBackground: ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainer) + baseWidth: windowControlsHeight + baseHeight: windowControlsHeight + buttonRadius: Appearance.rounding.full + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: "close" + iconSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3onSurface + } + onClicked: { + windowButton.modelData?.close(); + } + } + } ScreencopyView { id: screencopyView - anchors.centerIn: parent captureSource: previewPopup ? windowButton.modelData : null live: true paintCursor: true @@ -196,45 +229,6 @@ Item { } } } - ButtonGroup { - contentWidth: parent.width - anchors.margins * 2 - anchors { - top: parent.top - left: parent.left - right: parent.right - margins: 3 - } - WrapperRectangle { - Layout.fillWidth: true - color: Appearance.m3colors.m3surfaceContainer - radius: Appearance.rounding.small - margin: 5 - StyledText { - Layout.fillWidth: true - font.pixelSize: Appearance.font.pixelSize.small - text: windowButton.modelData?.title - elide: Text.ElideRight - color: Appearance.m3colors.m3onSurface - } - } - GroupButton { - id: closeButton - colBackground: Appearance.m3colors.m3surfaceContainer - baseWidth: 30 - baseHeight: 30 - buttonRadius: Appearance.rounding.full - contentItem: MaterialSymbol { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: "close" - iconSize: Appearance.font.pixelSize.normal - color: Appearance.m3colors.m3onSurface - } - onClicked: { - windowButton.modelData?.close(); - } - } - } } } } From 5a38388c622cf9b4099e35a367ca2e6afd85e034 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 22:48:12 +0200 Subject: [PATCH 597/824] fix install script trying to install non-exsistent packages --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index b852eb78a..0c9ac1a5f 100755 --- a/install.sh +++ b/install.sh @@ -100,7 +100,7 @@ install-local-pkgbuild() { } # Install core dependencies from the meta-packages -metapkgs=(./arch-packages/illogical-impulse-{audio,python,backlight,basic,fonts-themes,gnome,gtk,portal,screencapture,widgets}) +metapkgs=(./arch-packages/illogical-impulse-{audio,backlight,basic,fonts-themes,kde,portal,python,screencapture,toolkit,widgets}) metapkgs+=(./arch-packages/illogical-impulse-agsv1) metapkgs+=(./arch-packages/illogical-impulse-hyprland) metapkgs+=(./arch-packages/illogical-impulse-microtex-git) From db9754c43ef8e862ebf3dde8b671292347276c18 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 22:58:21 +0200 Subject: [PATCH 598/824] remove agsv1 pkg from install script --- install.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/install.sh b/install.sh index 0c9ac1a5f..3884cc2b4 100755 --- a/install.sh +++ b/install.sh @@ -101,7 +101,6 @@ install-local-pkgbuild() { # Install core dependencies from the meta-packages metapkgs=(./arch-packages/illogical-impulse-{audio,backlight,basic,fonts-themes,kde,portal,python,screencapture,toolkit,widgets}) -metapkgs+=(./arch-packages/illogical-impulse-agsv1) metapkgs+=(./arch-packages/illogical-impulse-hyprland) metapkgs+=(./arch-packages/illogical-impulse-microtex-git) metapkgs+=(./arch-packages/illogical-impulse-oneui4-icons-git) From a3d6f9f07d6bb1d3ac85540f190d06883fad037d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 30 May 2025 23:17:15 +0200 Subject: [PATCH 599/824] installation: update setuptools --- scriptdata/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scriptdata/requirements.txt b/scriptdata/requirements.txt index 4f4c5dc7b..fa9c4c84e 100644 --- a/scriptdata/requirements.txt +++ b/scriptdata/requirements.txt @@ -32,7 +32,7 @@ pywayland==0.4.18 # via -r scriptdata/requirements.in setproctitle==1.3.4 # via -r scriptdata/requirements.in -setuptools==75.8.0 +setuptools==80.9.0 # via setuptools-scm setuptools-scm==8.1.0 # via -r scriptdata/requirements.in From 42abc57b65af469c9981b34120d6253d7ebf271d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 01:38:34 +0200 Subject: [PATCH 600/824] warn on low battery --- .config/quickshell/modules/bar/Bar.qml | 2 +- .../bar/{Battery.qml => BatteryIndicator.qml} | 11 +++---- .config/quickshell/services/Battery.qml | 29 +++++++++++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) rename .config/quickshell/modules/bar/{Battery.qml => BatteryIndicator.qml} (89%) create mode 100644 .config/quickshell/services/Battery.qml diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index ac1f8cf8b..5e4d7eb5e 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -241,7 +241,7 @@ Scope { Layout.alignment: Qt.AlignVCenter } - Battery { + BatteryIndicator { visible: (barRoot.useShortenedForm < 2 && UPower.displayDevice.isLaptopBattery) Layout.alignment: Qt.AlignVCenter } diff --git a/.config/quickshell/modules/bar/Battery.qml b/.config/quickshell/modules/bar/BatteryIndicator.qml similarity index 89% rename from .config/quickshell/modules/bar/Battery.qml rename to .config/quickshell/modules/bar/BatteryIndicator.qml index 5def8560e..74cdf0169 100644 --- a/.config/quickshell/modules/bar/Battery.qml +++ b/.config/quickshell/modules/bar/BatteryIndicator.qml @@ -1,5 +1,6 @@ import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/services" import QtQuick import QtQuick.Layouts import Quickshell @@ -9,11 +10,11 @@ import Quickshell.Services.UPower Rectangle { id: root property bool borderless: ConfigOptions.bar.borderless - readonly property var chargeState: UPower.displayDevice.state - readonly property bool isCharging: chargeState == UPowerDeviceState.Charging - readonly property bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingCharge - readonly property real percentage: UPower.displayDevice.percentage - readonly property bool isLow: percentage <= ConfigOptions.bar.batteryLowThreshold / 100 + readonly property var chargeState: Battery.chargeState + readonly property bool isCharging: Battery.isCharging + readonly property bool isPluggedIn: Battery.isPluggedIn + readonly property real percentage: Battery.percentage + readonly property bool isLow: percentage <= ConfigOptions.battery.low / 100 readonly property color batteryLowBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3error : Appearance.m3colors.m3errorContainer readonly property color batteryLowOnBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3errorContainer : Appearance.m3colors.m3error diff --git a/.config/quickshell/services/Battery.qml b/.config/quickshell/services/Battery.qml new file mode 100644 index 000000000..372c1f1bb --- /dev/null +++ b/.config/quickshell/services/Battery.qml @@ -0,0 +1,29 @@ +pragma Singleton + +import "root:/modules/common" +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland +import Quickshell.Services.UPower + +Singleton { + property bool available: UPower.displayDevice.isLaptopBattery + property var chargeState: UPower.displayDevice.state + property bool isCharging: chargeState == UPowerDeviceState.Charging + property bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingCharge + property real percentage: UPower.displayDevice.percentage + + property bool isLow: percentage <= ConfigOptions.battery.low / 100 + property bool isCritical: percentage <= ConfigOptions.battery.critical / 100 + property bool isSuspending: percentage <= ConfigOptions.battery.suspend / 100 + + onIsLowChanged: { + if (available && isLow) Hyprland.dispatch(`exec notify-send "Low battery" "Consider plugging in your device" -u critical -a "Shell"`) + } + + onIsCriticalChanged: { + if (available && isCritical) Hyprland.dispatch(`exec notify-send "Critically low battery" "🙏 I ask for pleas charge\nAutomatic suspend triggers at ${ConfigOptions.battery.suspend}%" -u critical -a "Shell"`) + } +} From 92947e336028da7ff0726787db3df9f987a7bd64 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 01:39:06 +0200 Subject: [PATCH 601/824] easy transparency for yoinkers --- .config/quickshell/modules/bar/Workspaces.qml | 2 +- .../quickshell/modules/common/Appearance.qml | 42 +++++++++++-------- .../modules/common/ConfigOptions.qml | 10 ++++- .../modules/common/widgets/DialogButton.qml | 2 +- .../modules/common/widgets/GroupButton.qml | 2 +- .../modules/common/widgets/PrimaryTabBar.qml | 2 +- .../common/widgets/PrimaryTabButton.qml | 2 +- .../modules/common/widgets/RippleButton.qml | 2 +- .../common/widgets/SecondaryTabButton.qml | 4 +- .../common/widgets/StyledProgressBar.qml | 2 +- .../common/widgets/StyledRadioButton.qml | 4 +- .../modules/common/widgets/StyledSlider.qml | 2 +- .../modules/common/widgets/StyledSwitch.qml | 2 +- .../modules/mediaControls/PlayerControl.qml | 2 +- .../modules/overview/SearchItem.qml | 4 +- .../modules/overview/SearchWidget.qml | 2 +- .../modules/session/SessionActionButton.qml | 4 +- .../modules/sidebarRight/todo/TodoWidget.qml | 8 ++-- 18 files changed, 57 insertions(+), 41 deletions(-) diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 39af1b212..8a75b3d46 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -141,7 +141,7 @@ Item { implicitWidth: workspaceButtonWidth - activeWorkspaceMargin * 2 implicitHeight: workspaceButtonWidth - activeWorkspaceMargin * 2 radius: Appearance.rounding.full - color: Appearance.m3colors.m3primary + color: Appearance.colors.colPrimary anchors.verticalCenter: parent.verticalCenter x: animatedActiveWorkspaceIndex * workspaceButtonWidth + activeWorkspaceMargin } diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index dcb3d2221..b9b45c288 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -15,6 +15,12 @@ Singleton { property QtObject sizes property string syntaxHighlightingTheme + // [!] Enabling transparency can affect readability when using light theme. + property real transparency: 0 + property real contentTransparency: 0 + // property real transparency: 0.15 + // property real contentTransparency: 0.5 + m3colors: QtObject { property bool darkmode: false property bool transparent: false @@ -96,29 +102,31 @@ Singleton { colors: QtObject { property color colSubtext: m3colors.m3outline - property color colLayer0: m3colors.m3background + property color colLayer0: ColorUtils.transparentize(m3colors.m3background, root.transparency) property color colOnLayer0: m3colors.m3onBackground - property color colLayer0Hover: ColorUtils.mix(colLayer0, colOnLayer0, 0.9) - property color colLayer0Active: ColorUtils.mix(colLayer0, colOnLayer0, 0.8) - property color colLayer1: ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.7); + property color colLayer0Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.9, root.contentTransparency)) + property color colLayer0Active: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.8, root.contentTransparency)) + property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.7), root.contentTransparency); property color colOnLayer1: m3colors.m3onSurfaceVariant; property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45); - property color colLayer2: ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.55); + property color colLayer2: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.55), root.contentTransparency) property color colOnLayer2: m3colors.m3onSurface; property color colOnLayer2Disabled: ColorUtils.mix(colOnLayer2, m3colors.m3background, 0.4); - property color colLayer3: ColorUtils.mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96); + property color colLayer3: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96), root.contentTransparency) property color colOnLayer3: m3colors.m3onSurface; - property color colLayer1Hover: ColorUtils.mix(colLayer1, colOnLayer1, 0.92); - property color colLayer1Active: ColorUtils.mix(colLayer1, colOnLayer1, 0.85); - property color colLayer2Hover: ColorUtils.mix(colLayer2, colOnLayer2, 0.90); - property color colLayer2Active: ColorUtils.mix(colLayer2, colOnLayer2, 0.80); - property color colLayer2Disabled: ColorUtils.mix(colLayer2, m3colors.m3background, 0.8); - property color colLayer3Hover: ColorUtils.mix(colLayer3, colOnLayer3, 0.90); - property color colLayer3Active: ColorUtils.mix(colLayer3, colOnLayer3, 0.80); - property color colPrimaryHover: ColorUtils.mix(m3colors.m3primary, colLayer1Hover, 0.87) - property color colPrimaryActive: ColorUtils.mix(m3colors.m3primary, colLayer1Active, 0.7) - property color colPrimaryContainerHover: ColorUtils.mix(m3colors.m3primaryContainer, colLayer1Hover, 0.7) - property color colPrimaryContainerActive: ColorUtils.mix(m3colors.m3primaryContainer, colLayer1Active, 0.6) + property color colLayer1Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.92), root.contentTransparency) + property color colLayer1Active: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.85), root.contentTransparency); + property color colLayer2Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer2, colOnLayer2, 0.90), root.contentTransparency) + property color colLayer2Active: ColorUtils.transparentize(ColorUtils.mix(colLayer2, colOnLayer2, 0.80), root.contentTransparency); + property color colLayer2Disabled: ColorUtils.transparentize(ColorUtils.mix(colLayer2, m3colors.m3background, 0.8), root.contentTransparency); + property color colLayer3Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.90), root.contentTransparency) + property color colLayer3Active: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.80), root.contentTransparency); + property color colPrimary: m3colors.m3primary + property color colPrimaryHover: ColorUtils.mix(colors.colPrimary, colLayer1Hover, 0.87) + property color colPrimaryActive: ColorUtils.mix(colors.colPrimary, colLayer1Active, 0.7) + property color colPrimaryContainer: m3colors.m3primaryContainer + property color colPrimaryContainerHover: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Hover, 0.7) + property color colPrimaryContainerActive: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Active, 0.6) property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85) property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4) property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.6) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 208223261..34709a887 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -21,10 +21,15 @@ Singleton { property string terminal: "foot" // This is only for shell actions } + property QtObject battery: QtObject { + property int low: 20 + property int critical: 5 + property int suspend: 2 + } + property QtObject bar: QtObject { property bool bottom: false // Instead of top property bool borderless: true - property int batteryLowThreshold: 20 property string topLeftIcon: "spark" // Options: distro, spark property bool showBackground: true property QtObject resources: QtObject { @@ -43,6 +48,9 @@ Singleton { property real height: 60 property real hoverRegionHeight: 3 property bool pinnedOnStartup: false + property list pinnedApps: [ // IDs of pinned entries + "org.kde.dolphin", + ] } property QtObject networking: QtObject { diff --git a/.config/quickshell/modules/common/widgets/DialogButton.qml b/.config/quickshell/modules/common/widgets/DialogButton.qml index 977e9b95d..86150c502 100644 --- a/.config/quickshell/modules/common/widgets/DialogButton.qml +++ b/.config/quickshell/modules/common/widgets/DialogButton.qml @@ -17,7 +17,7 @@ RippleButton { implicitWidth: buttonTextWidget.implicitWidth + 15 * 2 buttonRadius: Appearance?.rounding.full ?? 9999 - property color colEnabled: Appearance?.m3colors.m3primary ?? "#65558F" + property color colEnabled: Appearance?.colors.colPrimary ?? "#65558F" property color colDisabled: Appearance?.m3colors.m3outline ?? "#8D8C96" contentItem: StyledText { diff --git a/.config/quickshell/modules/common/widgets/GroupButton.qml b/.config/quickshell/modules/common/widgets/GroupButton.qml index 1f0776194..76aa5be0e 100644 --- a/.config/quickshell/modules/common/widgets/GroupButton.qml +++ b/.config/quickshell/modules/common/widgets/GroupButton.qml @@ -44,7 +44,7 @@ Button { property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent" property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED" property color colBackgroundActive: Appearance?.colors.colLayer1Active ?? "#D6CEE2" - property color colBackgroundToggled: Appearance?.m3colors.m3primary ?? "#65558F" + property color colBackgroundToggled: Appearance?.colors.colPrimary ?? "#65558F" property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C" property color colBackgroundToggledActive: Appearance?.colors.colPrimaryActive ?? "#D6CEE2" diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml b/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml index accf17ab4..72ec166f2 100644 --- a/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml +++ b/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml @@ -10,7 +10,7 @@ ColumnLayout { required property var tabButtonList // Something like [{"icon": "notifications", "name": qsTr("Notifications")}, {"icon": "volume_up", "name": qsTr("Volume mixer")}] required property var externalTrackedTab property bool enableIndicatorAnimation: false - property color colIndicator: Appearance?.m3colors.m3primary ?? "#65558F" + property color colIndicator: Appearance?.colors.colPrimary ?? "#65558F" property color colBorder: Appearance?.m3colors.m3outlineVariant ?? "#C6C6D0" signal currentIndexChanged(int index) diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml index 3a2879adc..73502cbcd 100644 --- a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml +++ b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml @@ -20,7 +20,7 @@ TabButton { property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent" property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED" property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2" - property color colActive: Appearance?.m3colors.m3primary ?? "#65558F" + property color colActive: Appearance?.colors.colPrimary ?? "#65558F" property color colInactive: Appearance?.colors.colOnLayer1 ?? "#45464F" component RippleAnim: NumberAnimation { diff --git a/.config/quickshell/modules/common/widgets/RippleButton.qml b/.config/quickshell/modules/common/widgets/RippleButton.qml index 3e4868405..cd0d47a98 100644 --- a/.config/quickshell/modules/common/widgets/RippleButton.qml +++ b/.config/quickshell/modules/common/widgets/RippleButton.qml @@ -25,7 +25,7 @@ Button { property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent" property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED" - property color colBackgroundToggled: Appearance?.m3colors.m3primary ?? "#65558F" + property color colBackgroundToggled: Appearance?.colors.colPrimary ?? "#65558F" property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C" property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2" property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2" diff --git a/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml b/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml index 44677deb7..1d3b6381f 100644 --- a/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml +++ b/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml @@ -142,7 +142,7 @@ TabButton { text: buttonIcon iconSize: Appearance.font.pixelSize.huge fill: selected ? 1 : 0 - color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 + color: selected ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1 Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } @@ -152,7 +152,7 @@ TabButton { id: buttonTextWidget verticalAlignment: Text.AlignVCenter font.pixelSize: Appearance.font.pixelSize.small - color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 + color: selected ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1 text: buttonText Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) diff --git a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml index c5fdbf78f..5235357e3 100644 --- a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml +++ b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml @@ -16,7 +16,7 @@ ProgressBar { property real valueBarWidth: 120 property real valueBarHeight: 4 property real valueBarGap: 4 - property color highlightColor: Appearance?.m3colors.m3primary ?? "#685496" + property color highlightColor: Appearance?.colors.colPrimary ?? "#685496" property color trackColor: Appearance?.m3colors.m3secondaryContainer ?? "#F1D3F9" Behavior on value { diff --git a/.config/quickshell/modules/common/widgets/StyledRadioButton.qml b/.config/quickshell/modules/common/widgets/StyledRadioButton.qml index f665a3f13..3ef1ee8ad 100644 --- a/.config/quickshell/modules/common/widgets/StyledRadioButton.qml +++ b/.config/quickshell/modules/common/widgets/StyledRadioButton.qml @@ -12,7 +12,7 @@ RadioButton { id: root implicitHeight: 40 property string description - property color activeColor: Appearance?.m3colors.m3primary ?? "#685496" + property color activeColor: Appearance?.colors.colPrimary ?? "#685496" property color inactiveColor: Appearance?.m3colors.m3onSurfaceVariant ?? "#45464F" PointingHandInteraction {} @@ -39,7 +39,7 @@ RadioButton { width: checked ? 10 : 4 height: checked ? 10 : 4 radius: Appearance?.rounding.full - color: Appearance?.m3colors.m3primary + color: Appearance?.colors.colPrimary opacity: checked ? 1 : 0 Behavior on opacity { diff --git a/.config/quickshell/modules/common/widgets/StyledSlider.qml b/.config/quickshell/modules/common/widgets/StyledSlider.qml index 00aba779c..70491b8bb 100644 --- a/.config/quickshell/modules/common/widgets/StyledSlider.qml +++ b/.config/quickshell/modules/common/widgets/StyledSlider.qml @@ -18,7 +18,7 @@ Slider { property real handleHeight: 44 * scale property real handleLimit: root.backgroundDotMargins property real trackHeight: 30 * scale - property color highlightColor: Appearance.m3colors.m3primary + property color highlightColor: Appearance.colors.colPrimary property color trackColor: Appearance.m3colors.m3secondaryContainer property color handleColor: Appearance.m3colors.m3onSecondaryContainer property real trackRadius: Appearance.rounding.verysmall * scale diff --git a/.config/quickshell/modules/common/widgets/StyledSwitch.qml b/.config/quickshell/modules/common/widgets/StyledSwitch.qml index 5342d41ce..bbc2ae513 100644 --- a/.config/quickshell/modules/common/widgets/StyledSwitch.qml +++ b/.config/quickshell/modules/common/widgets/StyledSwitch.qml @@ -12,7 +12,7 @@ Switch { property real scale: 1 implicitHeight: 32 * root.scale implicitWidth: 52 * root.scale - property color activeColor: Appearance?.m3colors.m3primary ?? "#685496" + property color activeColor: Appearance?.colors.colPrimary ?? "#685496" property color inactiveColor: Appearance?.m3colors.m3surfaceContainerHighest ?? "#45464F" PointingHandInteraction {} diff --git a/.config/quickshell/modules/mediaControls/PlayerControl.qml b/.config/quickshell/modules/mediaControls/PlayerControl.qml index 530726900..16be41b83 100644 --- a/.config/quickshell/modules/mediaControls/PlayerControl.qml +++ b/.config/quickshell/modules/mediaControls/PlayerControl.qml @@ -93,7 +93,7 @@ Item { // Player instance property color colOnLayer0: ColorUtils.mix(Appearance.colors.colOnLayer0, artDominantColor, 0.5) property color colOnLayer1: ColorUtils.mix(Appearance.colors.colOnLayer1, artDominantColor, 0.5) property color colSubtext: ColorUtils.mix(Appearance.colors.colOnLayer1, artDominantColor, 0.5) - property color colPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.m3colors.m3primary, artDominantColor), artDominantColor, 0.5) + property color colPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimary, artDominantColor), artDominantColor, 0.5) property color colPrimaryHover: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryHover, artDominantColor), artDominantColor, 0.3) property color colPrimaryActive: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryActive, artDominantColor), artDominantColor, 0.3) property color colSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, artDominantColor, 0.3) diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index 0191ef3e9..e76067c49 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -27,7 +27,7 @@ RippleButton { property string materialSymbol: entry?.materialSymbol ?? "" property string cliphistRawString: entry?.cliphistRawString ?? "" - property string highlightPrefix: `` + property string highlightPrefix: `` property string highlightSuffix: `` function highlightContent(content, query) { if (!query || query.length === 0 || content == query || fontType === "monospace") @@ -162,7 +162,7 @@ RippleButton { implicitWidth: activeText.implicitHeight implicitHeight: activeText.implicitHeight radius: Appearance.rounding.full - color: Appearance.m3colors.m3primary + color: Appearance.colors.colPrimary MaterialSymbol { id: activeText anchors.centerIn: parent diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 351d9fb29..59740a06d 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -245,7 +245,7 @@ Item { // Wrapper cursorDelegate: Rectangle { width: 1 - color: searchInput.activeFocus ? Appearance.m3colors.m3primary : "transparent" + color: searchInput.activeFocus ? Appearance.colors.colPrimary : "transparent" radius: 1 } } diff --git a/.config/quickshell/modules/session/SessionActionButton.qml b/.config/quickshell/modules/session/SessionActionButton.qml index 1cf5a6e48..207305769 100644 --- a/.config/quickshell/modules/session/SessionActionButton.qml +++ b/.config/quickshell/modules/session/SessionActionButton.qml @@ -17,9 +17,9 @@ RippleButton { buttonRadius: (button.focus || button.down) ? size / 2 : Appearance.rounding.verylarge colBackground: button.keyboardDown ? Appearance.colors.colSecondaryContainerActive : - button.focus ? Appearance.m3colors.m3primary : + button.focus ? Appearance.colors.colPrimary : Appearance.m3colors.m3secondaryContainer - colBackgroundHover: Appearance.m3colors.m3primary + colBackgroundHover: Appearance.colors.colPrimary colRipple: Appearance.colors.colPrimaryActive property color colText: (button.down || button.keyboardDown || button.focus || button.hovered) ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0 diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index 441a35ca1..527ed6978 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -95,7 +95,7 @@ Item { x: tabBar.currentIndex * fullTabSize + (fullTabSize - targetWidth) / 2 - color: Appearance.m3colors.m3primary + color: Appearance.colors.colPrimary radius: Appearance.rounding.full Behavior on x { @@ -172,7 +172,7 @@ Item { id: fabBackground anchors.fill: parent radius: Appearance.rounding.normal - color: (fabButton.down) ? Appearance.colors.colPrimaryContainerActive : (fabButton.hovered ? Appearance.colors.colPrimaryContainerHover : Appearance.m3colors.m3primaryContainer) + color: (fabButton.down) ? Appearance.colors.colPrimaryContainerActive : (fabButton.hovered ? Appearance.colors.colPrimaryContainerHover : Appearance.colors.colPrimaryContainer) Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) @@ -274,13 +274,13 @@ Item { anchors.fill: parent radius: Appearance.rounding.verysmall border.width: 2 - border.color: todoInput.activeFocus ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline + border.color: todoInput.activeFocus ? Appearance.colors.colPrimary : Appearance.m3colors.m3outline color: "transparent" } cursorDelegate: Rectangle { width: 1 - color: todoInput.activeFocus ? Appearance.m3colors.m3primary : "transparent" + color: todoInput.activeFocus ? Appearance.colors.colPrimary : "transparent" radius: 1 } } From e5161a1f4fb0b6b5dc4c5ffe13ce937578601806 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 12:08:14 +0200 Subject: [PATCH 602/824] battery: remove unnecessary imports --- .config/quickshell/services/Battery.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.config/quickshell/services/Battery.qml b/.config/quickshell/services/Battery.qml index 372c1f1bb..d31c4ec0a 100644 --- a/.config/quickshell/services/Battery.qml +++ b/.config/quickshell/services/Battery.qml @@ -1,8 +1,6 @@ pragma Singleton import "root:/modules/common" -import QtQuick -import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Hyprland From a0b69f288432566cec662fa19725077404c4961c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 12:09:02 +0200 Subject: [PATCH 603/824] first run experience --- .../quickshell/modules/common/Directories.qml | 1 + .../modules/overview/SearchWidget.qml | 10 +++--- .../services/FirstRunExperience.qml | 36 +++++++++++++++++++ .config/quickshell/shell.qml | 1 + 4 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 .config/quickshell/services/FirstRunExperience.qml diff --git a/.config/quickshell/modules/common/Directories.qml b/.config/quickshell/modules/common/Directories.qml index 2a1b55b72..9e3e3000f 100644 --- a/.config/quickshell/modules/common/Directories.qml +++ b/.config/quickshell/modules/common/Directories.qml @@ -29,6 +29,7 @@ Singleton { property string notificationsPath: FileUtils.trimFileProtocol(`${Directories.cache}/notifications/notifications.json`) property string generatedMaterialThemePath: FileUtils.trimFileProtocol(`${Directories.state}/user/generated/colors.json`) property string cliphistDecode: FileUtils.trimFileProtocol(`/tmp/quickshell/media/cliphist`) + property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/scripts/switchwall.sh`) // Cleanup on init Component.onCompleted: { Hyprland.dispatch(`exec mkdir -p '${favicons}'`) diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 59740a06d..df3972944 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -43,27 +43,27 @@ Item { // Wrapper { action: "img", execute: () => { - executor.executeCommand(`${xdgConfigHome}/quickshell/scripts/switchwall.sh`.replace(/file:\/\//, "")) + executor.executeCommand(Directories.wallpaperSwitchScriptPath) } }, { action: "dark", execute: () => { - executor.executeCommand(`${xdgConfigHome}/quickshell/scripts/switchwall.sh --mode dark --noswitch`.replace(/file:\/\//, "")) + executor.executeCommand(`${Directories.wallpaperSwitchScriptPath} --mode dark --noswitch`) } }, { action: "light", execute: () => { - executor.executeCommand(`${xdgConfigHome}/quickshell/scripts/switchwall.sh --mode light --noswitch`.replace(/file:\/\//, "")) + executor.executeCommand(`${Directories.wallpaperSwitchScriptPath} --mode light --noswitch`) } }, { action: "accentcolor", execute: (args) => { executor.executeCommand( - `${xdgConfigHome}/quickshell/scripts/switchwall.sh --noswitch --color ${args != '' ? ("'"+args+"'") : ""}` - .replace(/file:\/\//, "")) + `${Directories.wallpaperSwitchScriptPath} --noswitch --color ${args != '' ? ("'"+args+"'") : ""}` + ) } }, { diff --git a/.config/quickshell/services/FirstRunExperience.qml b/.config/quickshell/services/FirstRunExperience.qml new file mode 100644 index 000000000..eee995a55 --- /dev/null +++ b/.config/quickshell/services/FirstRunExperience.qml @@ -0,0 +1,36 @@ +pragma Singleton + +import "root:/modules/common/functions/file_utils.js" as FileUtils +import "root:/modules/common" +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland + +Singleton { + id: root + property string firstRunFilePath: `${Directories.state}/user/first_run.txt` + property string firstRunFileContent: "This file is just here to confirm you've been greeted :>" + property string firstRunNotifSummary: "Welcome!" + property string firstRunNotifBody: "Hit Super+/ for a list of keybinds" + property string defaultWallpaperPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/assets/images/default_wallpaper.png`) + + function load() { + firstRunFileView.reload() + } + + function handleFirstRun() { + Hyprland.dispatch(`exec notify-send '${root.firstRunNotifSummary}' '${root.firstRunNotifBody}' -a 'Shell'`) + Hyprland.dispatch(`exec '${Directories.wallpaperSwitchScriptPath}' '${root.defaultWallpaperPath}'`) + } + + FileView { + id: firstRunFileView + path: Qt.resolvedUrl(firstRunFilePath) + onLoadFailed: (error) => { + if (error == FileViewError.FileNotFound) { + firstRunFileView.setText(root.firstRunFileContent) + root.handleFirstRun() + } + } + } +} diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index edd72ba92..29ab0f327 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -44,6 +44,7 @@ ShellRoot { ConfigLoader.loadConfig() PersistentStateManager.loadStates() Cliphist.refresh() + FirstRunExperience.load() } Loader { active: enableBar; sourceComponent: Bar {} } From 9af8fc137fb034aad8dbb09ea9abad2260ab74ff Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 16:26:08 +0200 Subject: [PATCH 604/824] icons: breeze -> breeze-plus --- .config/kde-material-you-colors/config.conf | 4 ++-- arch-packages/illogical-impulse-fonts-themes/PKGBUILD | 1 + install.sh | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.config/kde-material-you-colors/config.conf b/.config/kde-material-you-colors/config.conf index 04c8a3faf..1d63c8405 100644 --- a/.config/kde-material-you-colors/config.conf +++ b/.config/kde-material-you-colors/config.conf @@ -31,11 +31,11 @@ ncolor = 0 # Light scheme icons theme #iconslight = OneUI-light -iconslight = breeze +iconslight = breeze-plus # Dark scheme icons theme #iconsdark = OneUI-dark -iconsdark = breeze-dark +iconsdark = breeze-plus-dark # Use pywal to theme other programs using Material You colors pywal=False diff --git a/arch-packages/illogical-impulse-fonts-themes/PKGBUILD b/arch-packages/illogical-impulse-fonts-themes/PKGBUILD index 82fda5700..017e94b49 100644 --- a/arch-packages/illogical-impulse-fonts-themes/PKGBUILD +++ b/arch-packages/illogical-impulse-fonts-themes/PKGBUILD @@ -6,6 +6,7 @@ arch=(any) license=(None) depends=( adw-gtk-theme-git + breeze-plus fish fontconfig foot diff --git a/install.sh b/install.sh index 3884cc2b4..fc3f4be71 100755 --- a/install.sh +++ b/install.sh @@ -103,7 +103,7 @@ install-local-pkgbuild() { metapkgs=(./arch-packages/illogical-impulse-{audio,backlight,basic,fonts-themes,kde,portal,python,screencapture,toolkit,widgets}) metapkgs+=(./arch-packages/illogical-impulse-hyprland) metapkgs+=(./arch-packages/illogical-impulse-microtex-git) -metapkgs+=(./arch-packages/illogical-impulse-oneui4-icons-git) +# metapkgs+=(./arch-packages/illogical-impulse-oneui4-icons-git) [[ -f /usr/share/icons/Bibata-Modern-Classic/index.theme ]] || \ metapkgs+=(./arch-packages/illogical-impulse-bibata-modern-classic-bin) From 5fe64bfdeb2fa6c7024b08ab2d88c444a27742d5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 16:34:46 +0200 Subject: [PATCH 605/824] make fcitx5 follow qt theme --- .config/fcitx5/conf/classicui.conf | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .config/fcitx5/conf/classicui.conf diff --git a/.config/fcitx5/conf/classicui.conf b/.config/fcitx5/conf/classicui.conf new file mode 100644 index 000000000..cfbcc6150 --- /dev/null +++ b/.config/fcitx5/conf/classicui.conf @@ -0,0 +1,31 @@ +# Vertical Candidate List +Vertical Candidate List=False +# Use mouse wheel to go to prev or next page +WheelForPaging=True +# Font +Font="Rubik 11" +# Menu Font +MenuFont="Rubik 11" +# Tray Font +TrayFont="Rubik 11" +# Prefer Text Icon +PreferTextIcon=False +# Show Layout Name In Icon +ShowLayoutNameInIcon=True +# Use input method language to display text +UseInputMethodLanguageToDisplayText=True +# Theme +Theme=plasma +# Dark Theme +DarkTheme=plasma +# Follow system light/dark color scheme +UseDarkTheme=False +# Follow system accent color if it is supported by theme and desktop +UseAccentColor=True +# Use Per Screen DPI on X11 +PerScreenDPI=False +# Force font DPI on Wayland +ForceWaylandDPI=0 +# Enable fractional scale under Wayland +EnableFractionalScale=True + From 6be83d00503841bd0e0c323ba5bf505ee0aa5796 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 22:31:44 +0200 Subject: [PATCH 606/824] bar: util buttons: replace color picker with virtual kb --- .../quickshell/modules/bar/UtilButtons.qml | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/modules/bar/UtilButtons.qml b/.config/quickshell/modules/bar/UtilButtons.qml index 1f0a09f91..22fb30058 100644 --- a/.config/quickshell/modules/bar/UtilButtons.qml +++ b/.config/quickshell/modules/bar/UtilButtons.qml @@ -29,21 +29,35 @@ Rectangle { horizontalAlignment: Qt.AlignHCenter fill: 1 text: "screenshot_region" - iconSize: Appearance.font.pixelSize.normal + iconSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer2 } } + // CircleUtilButton { + // Layout.alignment: Qt.AlignVCenter + // onClicked: Hyprland.dispatch("exec hyprpicker -a") + + // MaterialSymbol { + // horizontalAlignment: Qt.AlignHCenter + // fill: 1 + // text: "colorize" + // iconSize: Appearance.font.pixelSize.large + // color: Appearance.colors.colOnLayer2 + // } + + // } + CircleUtilButton { Layout.alignment: Qt.AlignVCenter - onClicked: Hyprland.dispatch("exec hyprpicker -a") + onClicked: Hyprland.dispatch("global quickshell:oskToggle") MaterialSymbol { horizontalAlignment: Qt.AlignHCenter - fill: 1 - text: "colorize" - iconSize: Appearance.font.pixelSize.normal + fill: 0 + text: "keyboard" + iconSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer2 } From e85a899be09cb9928ea48498791a22ede1e8ade2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 22:32:18 +0200 Subject: [PATCH 607/824] battery: fix charging warning when low -> plug -> unplug --- .config/quickshell/services/Battery.qml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/services/Battery.qml b/.config/quickshell/services/Battery.qml index d31c4ec0a..c71cc8c79 100644 --- a/.config/quickshell/services/Battery.qml +++ b/.config/quickshell/services/Battery.qml @@ -17,11 +17,14 @@ Singleton { property bool isCritical: percentage <= ConfigOptions.battery.critical / 100 property bool isSuspending: percentage <= ConfigOptions.battery.suspend / 100 - onIsLowChanged: { - if (available && isLow) Hyprland.dispatch(`exec notify-send "Low battery" "Consider plugging in your device" -u critical -a "Shell"`) + property bool isLowAndNotCharging: isLow && !isCharging + property bool isCriticalAndNotCharging: isCritical && !isCharging + + onIsLowAndNotChargingChanged: { + if (available && isLowAndNotCharging) Hyprland.dispatch(`exec notify-send "Low battery" "Consider plugging in your device" -u critical -a "Shell"`) } - onIsCriticalChanged: { - if (available && isCritical) Hyprland.dispatch(`exec notify-send "Critically low battery" "🙏 I ask for pleas charge\nAutomatic suspend triggers at ${ConfigOptions.battery.suspend}%" -u critical -a "Shell"`) + onIsCriticalAndNotChargingChanged: { + if (available && isCriticalAndNotCharging) Hyprland.dispatch(`exec notify-send "Critically low battery" "🙏 I ask for pleas charge\nAutomatic suspend triggers at ${ConfigOptions.battery.suspend}%" -u critical -a "Shell"`) } } From 8b9dc8cde81fcf891a46f9bf80923b523364f5b4 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 22:32:40 +0200 Subject: [PATCH 608/824] app icon guess: add regex for polkit popup --- .config/quickshell/services/AppSearch.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.config/quickshell/services/AppSearch.qml b/.config/quickshell/services/AppSearch.qml index 2af1187de..876df1838 100644 --- a/.config/quickshell/services/AppSearch.qml +++ b/.config/quickshell/services/AppSearch.qml @@ -36,6 +36,10 @@ Singleton { { "regex": /.*polkit.*/, "replace": "system-lock-screen" + }, + { + "regex": /gcr.prompter/, + "replace": "system-lock-screen" } ] From 3448adb776d73204f25011931be034559cbafb21 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 22:33:01 +0200 Subject: [PATCH 609/824] booru: add app name to download notif --- .config/quickshell/modules/sidebarLeft/anime/BooruImage.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index 64c5bded9..c8c89f72a 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -180,7 +180,7 @@ Button { buttonText: qsTr("Download") onClicked: { root.showActions = false - Hyprland.dispatch(`exec curl '${root.imageData.file_url}' -o '${root.imageData.is_nsfw ? root.nsfwPath : root.downloadPath}/${root.fileName}' && notify-send '${qsTr("Download complete")}' '${root.downloadPath}/${root.fileName}'`) + Hyprland.dispatch(`exec curl '${root.imageData.file_url}' -o '${root.imageData.is_nsfw ? root.nsfwPath : root.downloadPath}/${root.fileName}' && notify-send '${qsTr("Download complete")}' '${root.downloadPath}/${root.fileName}' -a 'Shell'`) } } } From be29519f58a325b64516c9b34b4c761234fa0809 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 22:33:39 +0200 Subject: [PATCH 610/824] group button & ripple button: fix non-left btn release, add more actions --- .../modules/common/widgets/GroupButton.qml | 23 +++++++++++++------ .../modules/common/widgets/RippleButton.qml | 9 ++++++-- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/GroupButton.qml b/.config/quickshell/modules/common/widgets/GroupButton.qml index 76aa5be0e..5d4d9d199 100644 --- a/.config/quickshell/modules/common/widgets/GroupButton.qml +++ b/.config/quickshell/modules/common/widgets/GroupButton.qml @@ -18,8 +18,10 @@ Button { property string buttonText property real buttonRadius: Appearance?.rounding?.small ?? 4 property real buttonRadiusPressed: buttonRadius - property var altAction - property var middleClickAction + property var downAction // When left clicking (down) + property var releaseAction // When left clicking (release) + property var altAction // When right clicking + property var middleClickAction // When middle clicking property bool bounce: true property real baseWidth: contentItem.implicitWidth + padding * 2 property real baseHeight: contentItem.implicitHeight + padding * 2 @@ -37,6 +39,10 @@ Button { animation: Appearance.animation.clickBounce.numberAnimation.createObject(this) } + Behavior on implicitHeight { + animation: Appearance.animation.clickBounce.numberAnimation.createObject(this) + } + Behavior on radius { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } @@ -70,18 +76,21 @@ Button { cursorShape: Qt.PointingHandCursor acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton onPressed: (event) => { - if (event.button === Qt.MiddleButton) { - if (root.middleClickAction) root.middleClickAction(); - return; - } - if (event.button === Qt.RightButton) { + if(event.button === Qt.RightButton) { if (root.altAction) root.altAction(); return; } + if(event.button === Qt.MiddleButton) { + if (root.middleClickAction) root.middleClickAction(); + return; + } root.down = true + if (root.downAction) root.downAction(); } onReleased: (event) => { root.down = false + if (event.button != Qt.LeftButton) return; + if (root.releaseAction) root.releaseAction(); root.click() // Because the MouseArea already consumed the event } onCanceled: (event) => { diff --git a/.config/quickshell/modules/common/widgets/RippleButton.qml b/.config/quickshell/modules/common/widgets/RippleButton.qml index cd0d47a98..0a3a8743b 100644 --- a/.config/quickshell/modules/common/widgets/RippleButton.qml +++ b/.config/quickshell/modules/common/widgets/RippleButton.qml @@ -20,8 +20,10 @@ Button { property real buttonEffectiveRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius property int rippleDuration: 1200 property bool rippleEnabled: true - property var altAction - property var middleClickAction + property var downAction // When left clicking (down) + property var releaseAction // When left clicking (release) + property var altAction // When right clicking + property var middleClickAction // When middle clicking property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent" property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED" @@ -70,12 +72,15 @@ Button { return; } root.down = true + if (root.downAction) root.downAction(); if (!root.rippleEnabled) return; const {x,y} = event startRipple(x, y) } onReleased: (event) => { root.down = false + if (event.button != Qt.LeftButton) return; + if (root.releaseAction) root.releaseAction(); root.click() // Because the MouseArea already consumed the event if (!root.rippleEnabled) return; rippleFadeAnim.restart(); From f98be82f96a84fa40ddf14cfcafb6706211b5539 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 22:34:10 +0200 Subject: [PATCH 611/824] kb key: more flexible size --- .../modules/common/widgets/KeyboardKey.qml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/KeyboardKey.qml b/.config/quickshell/modules/common/widgets/KeyboardKey.qml index 0288d95a2..d6ba5ba08 100644 --- a/.config/quickshell/modules/common/widgets/KeyboardKey.qml +++ b/.config/quickshell/modules/common/widgets/KeyboardKey.qml @@ -12,20 +12,24 @@ Rectangle { property real horizontalPadding: 7 property real verticalPadding: 2 property real borderWidth: 1 - property real bottomBorderWidth: 2 + property real extraBottomBorderWidth: 2 property color borderColor: Appearance.colors.colOnLayer0 property real borderRadius: 5 property color keyColor: Appearance.m3colors.m3surfaceContainerLow implicitWidth: keyFace.implicitWidth + borderWidth * 2 - implicitHeight: keyFace.implicitHeight + borderWidth * 2 + bottomBorderWidth + implicitHeight: keyFace.implicitHeight + borderWidth * 2 + extraBottomBorderWidth radius: borderRadius color: borderColor Rectangle { id: keyFace - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - anchors.topMargin: borderWidth + anchors { + fill: parent + topMargin: borderWidth + leftMargin: borderWidth + rightMargin: borderWidth + bottomMargin: extraBottomBorderWidth + borderWidth + } implicitWidth: keyText.implicitWidth + horizontalPadding * 2 implicitHeight: keyText.implicitHeight + verticalPadding * 2 color: keyColor From 189a1aa0acdb432872e9adc1efe3c321e31c5a02 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 22:34:29 +0200 Subject: [PATCH 612/824] on screen kb: init --- .../modules/common/ConfigOptions.qml | 5 + .../onScreenKeyboard/OnScreenKeyboard.qml | 169 ++++++++++++++ .../modules/onScreenKeyboard/OskContent.qml | 47 ++++ .../modules/onScreenKeyboard/OskKey.qml | 125 ++++++++++ .../modules/onScreenKeyboard/layouts.js | 218 ++++++++++++++++++ .config/quickshell/services/Ydotool.qml | 42 ++++ .config/quickshell/shell.qml | 3 + 7 files changed, 609 insertions(+) create mode 100644 .config/quickshell/modules/onScreenKeyboard/OnScreenKeyboard.qml create mode 100644 .config/quickshell/modules/onScreenKeyboard/OskContent.qml create mode 100644 .config/quickshell/modules/onScreenKeyboard/OskKey.qml create mode 100644 .config/quickshell/modules/onScreenKeyboard/layouts.js create mode 100644 .config/quickshell/services/Ydotool.qml diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 34709a887..5f1afaea5 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -61,6 +61,11 @@ Singleton { property int timeout: 1000 } + property QtObject osk: QtObject { + property string layout: "qwerty_full" + property bool pinnedOnStartup: false + } + property QtObject overview: QtObject { property real scale: 0.18 // Relative to screen size property real numOfRows: 2 diff --git a/.config/quickshell/modules/onScreenKeyboard/OnScreenKeyboard.qml b/.config/quickshell/modules/onScreenKeyboard/OnScreenKeyboard.qml new file mode 100644 index 000000000..39862c55f --- /dev/null +++ b/.config/quickshell/modules/onScreenKeyboard/OnScreenKeyboard.qml @@ -0,0 +1,169 @@ +import "root:/" +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell.Io +import Quickshell +import Quickshell.Widgets +import Quickshell.Wayland +import Quickshell.Hyprland + +Scope { // Scope + id: root + property bool pinned: ConfigOptions?.osk.pinnedOnStartup ?? false + + component OskControlButton: GroupButton { // Pin button + baseWidth: 40 + baseHeight: 40 + clickedWidth: baseWidth + clickedHeight: baseHeight + 20 + buttonRadius: Appearance.rounding.normal + } + + Loader { + id: oskLoader + active: false + onActiveChanged: { + if (!oskLoader.active) { + Ydotool.releaseAllKeys(); + } + } + + sourceComponent: PanelWindow { // Window + id: oskRoot + visible: oskLoader.active + + anchors { + bottom: true + left: true + right: true + } + + function hide() { + oskLoader.active = false + } + exclusiveZone: root.pinned ? implicitHeight - Appearance.sizes.hyprlandGapsOut : 0 + implicitWidth: oskBackground.width + Appearance.sizes.elevationMargin * 2 + implicitHeight: oskBackground.height + Appearance.sizes.elevationMargin * 2 + WlrLayershell.namespace: "quickshell:osk" + WlrLayershell.layer: WlrLayer.Overlay + // Hyprland 0.49: Focus is always exclusive and setting this breaks mouse focus grab + // WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + color: "transparent" + + mask: Region { + item: oskBackground + } + + + // Background + StyledRectangularShadow { + target: oskBackground + } + Rectangle { + id: oskBackground + anchors.centerIn: parent + color: Appearance.colors.colLayer0 + radius: Appearance.rounding.windowRounding + property real padding: 10 + implicitWidth: oskRowLayout.implicitWidth + padding * 2 + implicitHeight: oskRowLayout.implicitHeight + padding * 2 + + Keys.onPressed: (event) => { // Esc to close + if (event.key === Qt.Key_Escape) { + oskRoot.hide() + } + } + + RowLayout { + id: oskRowLayout + anchors.centerIn: parent + spacing: 5 + VerticalButtonGroup { + OskControlButton { // Pin button + toggled: root.pinned + onClicked: root.pinned = !root.pinned + contentItem: MaterialSymbol { + text: "keep" + horizontalAlignment: Text.AlignHCenter + iconSize: Appearance.font.pixelSize.larger + color: root.pinned ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0 + } + } + OskControlButton { + onClicked: () => { + oskRoot.hide() + } + contentItem: MaterialSymbol { + horizontalAlignment: Text.AlignHCenter + text: "keyboard_hide" + iconSize: Appearance.font.pixelSize.larger + } + } + } + Rectangle { + Layout.topMargin: 20 + Layout.bottomMargin: 20 + Layout.fillHeight: true + implicitWidth: 1 + color: Appearance.m3colors.m3outlineVariant + } + OskContent { + id: oskContent + Layout.fillWidth: true + } + } + } + + } + } + + IpcHandler { + target: "osk" + + function toggle(): void { + oskLoader.active = !oskLoader.active + } + + function close(): void { + oskLoader.active = false + } + + function open(): void { + oskLoader.active = true + } + } + + GlobalShortcut { + name: "oskToggle" + description: qsTr("Toggles on screen keyboard on press") + + onPressed: { + oskLoader.active = !oskLoader.active; + } + } + + GlobalShortcut { + name: "oskOpen" + description: qsTr("Opens on screen keyboard on press") + + onPressed: { + oskLoader.active = true; + } + } + + GlobalShortcut { + name: "oskClose" + description: qsTr("Closes on screen keyboard on press") + + onPressed: { + oskLoader.active = false; + } + } + +} diff --git a/.config/quickshell/modules/onScreenKeyboard/OskContent.qml b/.config/quickshell/modules/onScreenKeyboard/OskContent.qml new file mode 100644 index 000000000..06e954adc --- /dev/null +++ b/.config/quickshell/modules/onScreenKeyboard/OskContent.qml @@ -0,0 +1,47 @@ +import "root:/" +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "layouts.js" as Layouts +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Widgets +import Quickshell.Hyprland + +Item { + id: root + property var activeLayoutName: ConfigOptions?.osk.layout ?? Layouts.defaultLayout + property var layouts: Layouts.byName + property var currentLayout: layouts[activeLayoutName] + + implicitWidth: keyRows.implicitWidth + implicitHeight: keyRows.implicitHeight + + ColumnLayout { + id: keyRows + anchors.fill: parent + spacing: 5 + + Repeater { + model: root.currentLayout.keys + + delegate: RowLayout { + id: keyRow + required property var modelData + spacing: 5 + + Repeater { + model: modelData + // A normal key looks like this: {label: "a", labelShift: "A", shape: "normal", keycode: 30, type: "normal"} + delegate: OskKey { + required property var modelData + keyData: modelData + } + } + } + } + } +} diff --git a/.config/quickshell/modules/onScreenKeyboard/OskKey.qml b/.config/quickshell/modules/onScreenKeyboard/OskKey.qml new file mode 100644 index 000000000..1f28a9e57 --- /dev/null +++ b/.config/quickshell/modules/onScreenKeyboard/OskKey.qml @@ -0,0 +1,125 @@ +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland + +RippleButton { + id: root + property var keyData + property string key: keyData.label + property string type: keyData.keytype + property var keycode: keyData.keycode + property string shape: keyData.shape + property bool isShift: Ydotool.shiftKeys.includes(keycode) + property bool isBackspace: (key.toLowerCase() == "backspace") + property bool isEnter: (key.toLowerCase() == "enter" || key.toLowerCase() == "return") + property real baseWidth: 45 + property real baseHeight: 45 + property var widthMultiplier: ({ + "normal": 1, + "fn": 1, + "tab": 1.6, + "caps": 1.9, + "shift": 2.5, + "control": 1.3 + }) + property var heightMultiplier: ({ + "normal": 1, + "fn": 0.7, + "tab": 1, + "caps": 1, + "shift": 1, + "control": 1 + }) + toggled: isShift ? Ydotool.shiftMode : false + + enabled: shape != "empty" + colBackground: shape == "empty" ? ColorUtils.transparentize(Appearance.colors.colLayer1) : Appearance.colors.colLayer1 + buttonRadius: Appearance.rounding.small + implicitWidth: baseWidth * widthMultiplier[shape] || baseWidth + implicitHeight: baseHeight * heightMultiplier[shape] || baseHeight + Layout.fillWidth: shape == "space" || shape == "expand" + + Connections { + target: Ydotool + enabled: isShift + function onShiftModeChanged() { + if (Ydotool.shiftMode == 0) { + capsLockTimer.hasStarted = false; + } + } + } + + Timer { + id: capsLockTimer + property bool hasStarted: false + property bool canCaps: false + interval: 300 + function startWaiting() { + hasStarted = true; + canCaps = true; + start(); + } + onTriggered: { + canCaps = false; + } + } + + downAction: () => { + Ydotool.press(root.keycode); + if (isShift && Ydotool.shiftMode == 0) Ydotool.shiftMode = 1; + } + releaseAction: () => { + if (root.type == "normal") { + Ydotool.release(root.keycode); + if (Ydotool.shiftMode == 1) { + Ydotool.releaseShiftKeys() + } + } else if (isShift) { + if (Ydotool.shiftMode == 1) { + if (!capsLockTimer.hasStarted) { + capsLockTimer.startWaiting(); + } else { + if (capsLockTimer.canCaps) { + Ydotool.shiftMode = 2; // Caps lock mode + } else { + Ydotool.releaseShiftKeys() + } + } + } else if (Ydotool.shiftMode == 2) { + Ydotool.releaseShiftKeys(); + } + } else if (root.type == "modkey") { + root.toggled = !root.toggled; + if (!root.toggled) { + if (isShift) { + Ydotool.releaseShiftKeys(); + } else { + Ydotool.release(root.keycode); + } + } + } + + } + + contentItem: StyledText { + id: keyText + anchors.fill: parent + font.family: (isBackspace || isEnter) ? Appearance.font.family.iconMaterial : Appearance.font.family.main + font.pixelSize: root.shape == "fn" ? Appearance.font.pixelSize.small : + (isBackspace || isEnter) ? Appearance.font.pixelSize.huge : + Appearance.font.pixelSize.large + horizontalAlignment: Text.AlignHCenter + color: root.toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1 + text: root.isBackspace ? "backspace" : root.isEnter ? "subdirectory_arrow_left" : + Ydotool.shiftMode == 2 ? (root.keyData.labelCaps || root.keyData.labelShift || root.keyData.label) : + Ydotool.shiftMode == 1 ? (root.keyData.labelShift || root.keyData.label) : + root.keyData.label + } +} diff --git a/.config/quickshell/modules/onScreenKeyboard/layouts.js b/.config/quickshell/modules/onScreenKeyboard/layouts.js new file mode 100644 index 000000000..6b8b98f06 --- /dev/null +++ b/.config/quickshell/modules/onScreenKeyboard/layouts.js @@ -0,0 +1,218 @@ +// We're going to use ydotool +// See /usr/include/linux/input-event-codes.h for keycodes + +const defaultLayout = "qwerty_full"; +const byName = { + "qwerty_full": { + name: "QWERTY - Full", + name_short: "US", + comment: "Like physical keyboard", + // A key looks like this: { k: "a", ks: "A", t: "normal" } (key, key-shift, type) + // key types are: normal, tab, caps, shift, control, fn (normal w/ half height), space, expand + // keys: [ + // [{ k: "Esc", t: "fn" }, { k: "F1", t: "fn" }, { k: "F2", t: "fn" }, { k: "F3", t: "fn" }, { k: "F4", t: "fn" }, { k: "F5", t: "fn" }, { k: "F6", t: "fn" }, { k: "F7", t: "fn" }, { k: "F8", t: "fn" }, { k: "F9", t: "fn" }, { k: "F10", t: "fn" }, { k: "F11", t: "fn" }, { k: "F12", t: "fn" }, { k: "PrtSc", t: "fn" }, { k: "Del", t: "fn" }], + // [{ k: "`", ks: "~", t: "normal" }, { k: "1", ks: "!", t: "normal" }, { k: "2", ks: "@", t: "normal" }, { k: "3", ks: "#", t: "normal" }, { k: "4", ks: "$", t: "normal" }, { k: "5", ks: "%", t: "normal" }, { k: "6", ks: "^", t: "normal" }, { k: "7", ks: "&", t: "normal" }, { k: "8", ks: "*", t: "normal" }, { k: "9", ks: "(", t: "normal" }, { k: "0", ks: ")", t: "normal" }, { k: "-", ks: "_", t: "normal" }, { k: "=", ks: "+", t: "normal" }, { k: "Backspace", t: "shift" }], + // [{ k: "Tab", t: "tab" }, { k: "q", ks: "Q", t: "normal" }, { k: "w", ks: "W", t: "normal" }, { k: "e", ks: "E", t: "normal" }, { k: "r", ks: "R", t: "normal" }, { k: "t", ks: "T", t: "normal" }, { k: "y", ks: "Y", t: "normal" }, { k: "u", ks: "U", t: "normal" }, { k: "i", ks: "I", t: "normal" }, { k: "o", ks: "O", t: "normal" }, { k: "p", ks: "P", t: "normal" }, { k: "[", ks: "{", t: "normal" }, { k: "]", ks: "}", t: "normal" }, { k: "\\", ks: "|", t: "expand" }], + // [{ k: "Caps", t: "caps" }, { k: "a", ks: "A", t: "normal" }, { k: "s", ks: "S", t: "normal" }, { k: "d", ks: "D", t: "normal" }, { k: "f", ks: "F", t: "normal" }, { k: "g", ks: "G", t: "normal" }, { k: "h", ks: "H", t: "normal" }, { k: "j", ks: "J", t: "normal" }, { k: "k", ks: "K", t: "normal" }, { k: "l", ks: "L", t: "normal" }, { k: ";", ks: ":", t: "normal" }, { k: "'", ks: '"', t: "normal" }, { k: "Enter", t: "expand" }], + // [{ k: "Shift", t: "shift" }, { k: "z", ks: "Z", t: "normal" }, { k: "x", ks: "X", t: "normal" }, { k: "c", ks: "C", t: "normal" }, { k: "v", ks: "V", t: "normal" }, { k: "b", ks: "B", t: "normal" }, { k: "n", ks: "N", t: "normal" }, { k: "m", ks: "M", t: "normal" }, { k: ",", ks: "<", t: "normal" }, { k: ".", ks: ">", t: "normal" }, { k: "/", ks: "?", t: "normal" }, { k: "Shift", t: "expand" }], + // [{ k: "Ctrl", t: "control" }, { k: "Fn", t: "normal" }, { k: "Win", t: "normal" }, { k: "Alt", t: "normal" }, { k: "Space", t: "space" }, { k: "Alt", t: "normal" }, { k: "Menu", t: "normal" }, { k: "Ctrl", t: "control" }] + // ] + // A normal key looks like this: {label: "a", labelShift: "A", shape: "normal", keycode: 30, type: "normal"} + // A modkey looks like this: {label: "Ctrl", shape: "control", keycode: 29, type: "modkey"} + // key types are: normal, tab, caps, shift, control, fn (normal w/ half height), space, expand + keys: [ + [ + { keytype: "normal", label: "Esc", shape: "fn", keycode: 1 }, + { keytype: "normal", label: "F1", shape: "fn", keycode: 59 }, + { keytype: "normal", label: "F2", shape: "fn", keycode: 60 }, + { keytype: "normal", label: "F3", shape: "fn", keycode: 61 }, + { keytype: "normal", label: "F4", shape: "fn", keycode: 62 }, + { keytype: "normal", label: "F5", shape: "fn", keycode: 63 }, + { keytype: "normal", label: "F6", shape: "fn", keycode: 64 }, + { keytype: "normal", label: "F7", shape: "fn", keycode: 65 }, + { keytype: "normal", label: "F8", shape: "fn", keycode: 66 }, + { keytype: "normal", label: "F9", shape: "fn", keycode: 67 }, + { keytype: "normal", label: "F10", shape: "fn", keycode: 68 }, + { keytype: "normal", label: "F11", shape: "fn", keycode: 87 }, + { keytype: "normal", label: "F12", shape: "fn", keycode: 88 }, + { keytype: "normal", label: "PrtSc", shape: "fn", keycode: 99 }, + { keytype: "normal", label: "Del", shape: "fn", keycode: 111 } + ], + [ + { keytype: "normal", label: "`", labelShift: "~", shape: "normal", keycode: 41 }, + { keytype: "normal", label: "1", labelShift: "!", shape: "normal", keycode: 2 }, + { keytype: "normal", label: "2", labelShift: "@", shape: "normal", keycode: 3 }, + { keytype: "normal", label: "3", labelShift: "#", shape: "normal", keycode: 4 }, + { keytype: "normal", label: "4", labelShift: "$", shape: "normal", keycode: 5 }, + { keytype: "normal", label: "5", labelShift: "%", shape: "normal", keycode: 6 }, + { keytype: "normal", label: "6", labelShift: "^", shape: "normal", keycode: 7 }, + { keytype: "normal", label: "7", labelShift: "&", shape: "normal", keycode: 8 }, + { keytype: "normal", label: "8", labelShift: "*", shape: "normal", keycode: 9 }, + { keytype: "normal", label: "9", labelShift: "(", shape: "normal", keycode: 10 }, + { keytype: "normal", label: "0", labelShift: ")", shape: "normal", keycode: 11 }, + { keytype: "normal", label: "-", labelShift: "_", shape: "normal", keycode: 12 }, + { keytype: "normal", label: "=", labelShift: "+", shape: "normal", keycode: 13 }, + { keytype: "normal", label: "Backspace", shape: "expand", keycode: 14 } + ], + [ + { keytype: "normal", label: "Tab", shape: "tab", keycode: 15 }, + { keytype: "normal", label: "q", labelShift: "Q", shape: "normal", keycode: 16 }, + { keytype: "normal", label: "w", labelShift: "W", shape: "normal", keycode: 17 }, + { keytype: "normal", label: "e", labelShift: "E", shape: "normal", keycode: 18 }, + { keytype: "normal", label: "r", labelShift: "R", shape: "normal", keycode: 19 }, + { keytype: "normal", label: "t", labelShift: "T", shape: "normal", keycode: 20 }, + { keytype: "normal", label: "y", labelShift: "Y", shape: "normal", keycode: 21 }, + { keytype: "normal", label: "u", labelShift: "U", shape: "normal", keycode: 22 }, + { keytype: "normal", label: "i", labelShift: "I", shape: "normal", keycode: 23 }, + { keytype: "normal", label: "o", labelShift: "O", shape: "normal", keycode: 24 }, + { keytype: "normal", label: "p", labelShift: "P", shape: "normal", keycode: 25 }, + { keytype: "normal", label: "[", labelShift: "{", shape: "normal", keycode: 26 }, + { keytype: "normal", label: "]", labelShift: "}", shape: "normal", keycode: 27 }, + { keytype: "normal", label: "\\", labelShift: "|", shape: "expand", keycode: 43 } + ], + [ + //{ keytype: "normal", label: "Caps", shape: "caps", keycode: 58 }, // not needed as double-pressing shift does that + { keytype: "spacer", label: "", shape: "empty" }, + { keytype: "spacer", label: "", shape: "empty" }, + { keytype: "normal", label: "a", labelShift: "A", shape: "normal", keycode: 30 }, + { keytype: "normal", label: "s", labelShift: "S", shape: "normal", keycode: 31 }, + { keytype: "normal", label: "d", labelShift: "D", shape: "normal", keycode: 32 }, + { keytype: "normal", label: "f", labelShift: "F", shape: "normal", keycode: 33 }, + { keytype: "normal", label: "g", labelShift: "G", shape: "normal", keycode: 34 }, + { keytype: "normal", label: "h", labelShift: "H", shape: "normal", keycode: 35 }, + { keytype: "normal", label: "j", labelShift: "J", shape: "normal", keycode: 36 }, + { keytype: "normal", label: "k", labelShift: "K", shape: "normal", keycode: 37 }, + { keytype: "normal", label: "l", labelShift: "L", shape: "normal", keycode: 38 }, + { keytype: "normal", label: ";", labelShift: ":", shape: "normal", keycode: 39 }, + { keytype: "normal", label: "'", labelShift: '"', shape: "normal", keycode: 40 }, + { keytype: "normal", label: "Enter", shape: "expand", keycode: 28 } + ], + [ + { keytype: "modkey", label: "Shift", labelShift: "Shift", labelCaps: "Caps", shape: "shift", keycode: 42 }, + { keytype: "normal", label: "z", labelShift: "Z", shape: "normal", keycode: 44 }, + { keytype: "normal", label: "x", labelShift: "X", shape: "normal", keycode: 45 }, + { keytype: "normal", label: "c", labelShift: "C", shape: "normal", keycode: 46 }, + { keytype: "normal", label: "v", labelShift: "V", shape: "normal", keycode: 47 }, + { keytype: "normal", label: "b", labelShift: "B", shape: "normal", keycode: 48 }, + { keytype: "normal", label: "n", labelShift: "N", shape: "normal", keycode: 49 }, + { keytype: "normal", label: "m", labelShift: "M", shape: "normal", keycode: 50 }, + { keytype: "normal", label: ",", labelShift: "<", shape: "normal", keycode: 51 }, + { keytype: "normal", label: ".", labelShift: ">", shape: "normal", keycode: 52 }, + { keytype: "normal", label: "/", labelShift: "?", shape: "normal", keycode: 53 }, + { keytype: "modkey", label: "Shift", labelShift: "Shift", labelCaps: "Caps", shape: "expand", keycode: 54 } // optional + ], + [ + { keytype: "modkey", label: "Ctrl", shape: "control", keycode: 29 }, + // { label: "Super", shape: "normal", keycode: 125 }, // dangerous + { keytype: "modkey", label: "Alt", shape: "normal", keycode: 56 }, + { keytype: "normal", label: "Space", shape: "space", keycode: 57 }, + { keytype: "modkey", label: "Alt", shape: "normal", keycode: 100 }, + // { label: "Super", shape: "normal", keycode: 126 }, // dangerous + { keytype: "normal", label: "Menu", shape: "normal", keycode: 139 }, + { keytype: "modkey", label: "Ctrl", shape: "control", keycode: 97 } + ] + ] + }, + "qwertz_full": { + name: "QWERTZ - Full", + name_short: "DE", + comment: "Keyboard layout commonly used in German-speaking countries", + keys: [ + [ + { keytype: "normal", label: "Esc", shape: "fn", keycode: 1 }, + { keytype: "normal", label: "F1", shape: "fn", keycode: 59 }, + { keytype: "normal", label: "F2", shape: "fn", keycode: 60 }, + { keytype: "normal", label: "F3", shape: "fn", keycode: 61 }, + { keytype: "normal", label: "F4", shape: "fn", keycode: 62 }, + { keytype: "normal", label: "F5", shape: "fn", keycode: 63 }, + { keytype: "normal", label: "F6", shape: "fn", keycode: 64 }, + { keytype: "normal", label: "F7", shape: "fn", keycode: 65 }, + { keytype: "normal", label: "F8", shape: "fn", keycode: 66 }, + { keytype: "normal", label: "F9", shape: "fn", keycode: 67 }, + { keytype: "normal", label: "F10", shape: "fn", keycode: 68 }, + { keytype: "normal", label: "F11", shape: "fn", keycode: 87 }, + { keytype: "normal", label: "F12", shape: "fn", keycode: 88 }, + { keytype: "normal", label: "Druck", shape: "fn", keycode: 99 }, + { keytype: "normal", label: "Entf", shape: "fn", keycode: 111 } + ], + [ + { keytype: "normal", label: "^", labelShift: "°", labelAlt: "′", shape: "normal", keycode: 41 }, + { keytype: "normal", label: "1", labelShift: "!", labelAlt: "¹", shape: "normal", keycode: 2 }, + { keytype: "normal", label: "2", labelShift: "\"", labelAlt: "²", shape: "normal", keycode: 3 }, + { keytype: "normal", label: "3", labelShift: "§", labelAlt: "³", shape: "normal", keycode: 4 }, + { keytype: "normal", label: "4", labelShift: "$", labelAlt: "¼", shape: "normal", keycode: 5 }, + { keytype: "normal", label: "5", labelShift: "%", labelAlt: "½", shape: "normal", keycode: 6 }, + { keytype: "normal", label: "6", labelShift: "&", labelAlt: "¬", shape: "normal", keycode: 7 }, + { keytype: "normal", label: "7", labelShift: "/", labelAlt: "{", shape: "normal", keycode: 8 }, + { keytype: "normal", label: "8", labelShift: "(", labelAlt: "[", shape: "normal", keycode: 9 }, + { keytype: "normal", label: "9", labelShift: ")", labelAlt: "]", shape: "normal", keycode: 10 }, + { keytype: "normal", label: "0", labelShift: "=", labelAlt: "}", shape: "normal", keycode: 11 }, + { keytype: "normal", label: "ß", labelShift: "?", labelAlt: "\\", shape: "normal", keycode: 12 }, + { keytype: "normal", label: "´", labelShift: "`", labelAlt: "¸", shape: "normal", keycode: 13 }, + { keytype: "normal", label: "⟵", shape: "expand", keycode: 14 } + ], + [ + { keytype: "normal", label: "Tab ⇆", shape: "tab", keycode: 15 }, + { keytype: "normal", label: "q", labelShift: "Q", labelAlt: "@", shape: "normal", keycode: 16 }, + { keytype: "normal", label: "w", labelShift: "W", labelAlt: "ſ", shape: "normal", keycode: 17 }, + { keytype: "normal", label: "e", labelShift: "E", labelAlt: "€", shape: "normal", keycode: 18 }, + { keytype: "normal", label: "r", labelShift: "R", labelAlt: "¶", shape: "normal", keycode: 19 }, + { keytype: "normal", label: "t", labelShift: "T", labelAlt: "ŧ", shape: "normal", keycode: 20 }, + { keytype: "normal", label: "z", labelShift: "Z", labelAlt: "←", shape: "normal", keycode: 21 }, + { keytype: "normal", label: "u", labelShift: "U", labelAlt: "↓", shape: "normal", keycode: 22 }, + { keytype: "normal", label: "i", labelShift: "I", labelAlt: "→", shape: "normal", keycode: 23 }, + { keytype: "normal", label: "o", labelShift: "O", labelAlt: "ø", shape: "normal", keycode: 24 }, + { keytype: "normal", label: "p", labelShift: "P", labelAlt: "þ", shape: "normal", keycode: 25 }, + { keytype: "normal", label: "ü", labelShift: "Ü", labelAlt: "¨", shape: "normal", keycode: 26 }, + { keytype: "normal", label: "+", labelShift: "*", labelAlt: "~", shape: "normal", keycode: 27 }, + { keytype: "normal", label: "↵", shape: "expand", keycode: 28 } + ], + [ + //{ keytype: "normal", label: "Umschalt ⇩", shape: "caps", keycode: 58 }, + { keytype: "spacer", label: "", shape: "empty" }, + { keytype: "spacer", label: "", shape: "empty" }, + { keytype: "normal", label: "a", labelShift: "A", labelAlt: "æ", shape: "normal", keycode: 30 }, + { keytype: "normal", label: "s", labelShift: "S", labelAlt: "ſ", shape: "normal", keycode: 31 }, + { keytype: "normal", label: "d", labelShift: "D", labelAlt: "ð", shape: "normal", keycode: 32 }, + { keytype: "normal", label: "f", labelShift: "F", labelAlt: "đ", shape: "normal", keycode: 33 }, + { keytype: "normal", label: "g", labelShift: "G", labelAlt: "ŋ", shape: "normal", keycode: 34 }, + { keytype: "normal", label: "h", labelShift: "H", labelAlt: "ħ", shape: "normal", keycode: 35 }, + { keytype: "normal", label: "j", labelShift: "J", labelAlt: "", shape: "normal", keycode: 36 }, + { keytype: "normal", label: "k", labelShift: "K", labelAlt: "ĸ", shape: "normal", keycode: 37 }, + { keytype: "normal", label: "l", labelShift: "L", labelAlt: "ł", shape: "normal", keycode: 38 }, + { keytype: "normal", label: "ö", labelShift: "Ö", labelAlt: "˝", shape: "normal", keycode: 39 }, + { keytype: "normal", label: "ä", labelShift: 'Ä', labelAlt: "^", shape: "normal", keycode: 40 }, + { keytype: "normal", label: "#", labelShift: '\'', labelAlt: "’", shape: "normal", keycode: 43 }, + { keytype: "spacer", label: "", shape: "empty" }, + //{ keytype: "normal", label: "↵", shape: "expand", keycode: 28 } + ], + [ + { keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "shift", keycode: 42 }, + { keytype: "normal", label: "<", labelShift: ">", labelAlt: "|", shape: "normal", keycode: 86 }, + { keytype: "normal", label: "y", labelShift: "Y", labelAlt: "»", shape: "normal", keycode: 44 }, + { keytype: "normal", label: "x", labelShift: "X", labelAlt: "«", shape: "normal", keycode: 45 }, + { keytype: "normal", label: "c", labelShift: "C", labelAlt: "¢", shape: "normal", keycode: 46 }, + { keytype: "normal", label: "v", labelShift: "V", labelAlt: "„", shape: "normal", keycode: 47 }, + { keytype: "normal", label: "b", labelShift: "B", labelAlt: "“", shape: "normal", keycode: 48 }, + { keytype: "normal", label: "n", labelShift: "N", labelAlt: "”", shape: "normal", keycode: 49 }, + { keytype: "normal", label: "m", labelShift: "M", labelAlt: "µ", shape: "normal", keycode: 50 }, + { keytype: "normal", label: ",", labelShift: ";", labelAlt: "·", shape: "normal", keycode: 51 }, + { keytype: "normal", label: ".", labelShift: ":", labelAlt: "…", shape: "normal", keycode: 52 }, + { keytype: "normal", label: "-", labelShift: "_", labelAlt: "–", shape: "normal", keycode: 53 }, + { keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "expand", keycode: 54 }, // optional + ], + [ + { keytype: "modkey", label: "Strg", shape: "control", keycode: 29 }, + //{ keytype: "normal", label: "", shape: "normal", keycode: 125 }, // dangerous + { keytype: "modkey", label: "Alt", shape: "normal", keycode: 56 }, + { keytype: "normal", label: "Leertaste", shape: "space", keycode: 57 }, + { keytype: "modkey", label: "Alt Gr", shape: "normal", keycode: 100 }, + // { label: "Super", shape: "normal", keycode: 126 }, // dangerous + //{ keytype: "normal", label: "Menu", shape: "normal", keycode: 139 }, // doesn't work? + { keytype: "modkey", label: "Strg", shape: "control", keycode: 97 }, + { keytype: "normal", label: "⇦", shape: "normal", keycode: 105 }, + { keytype: "normal", label: "⇨", shape: "normal", keycode: 106 }, + ] + ] + } +} \ No newline at end of file diff --git a/.config/quickshell/services/Ydotool.qml b/.config/quickshell/services/Ydotool.qml new file mode 100644 index 000000000..7cafcbbe2 --- /dev/null +++ b/.config/quickshell/services/Ydotool.qml @@ -0,0 +1,42 @@ +pragma Singleton + +import "root:/modules/common" +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland + +Singleton { + id: root + property int shiftMode: 0 // 0: off, 1: on, 2: lock + property list shiftKeys: [42, 54] // Keycodes for Shift keys (left and right) + property list altKeys: [56, 100] // Keycodes for Alt keys (left and right) + property list ctrlKeys: [29, 97] // Keycodes for Ctrl keys (left and right) + + onShiftModeChanged: { + if (shiftMode === 0) { + + } + } + + function releaseAllKeys() { + const keycodes = Array.from(Array(249).keys()); + const releaseCommand = `ydotool key --key-delay 0 ${keycodes.map(keycode => `${keycode}:0`).join(" ")}` + Hyprland.dispatch(`exec ${releaseCommand}`) + root.shiftMode = 0; // Reset shift mode + } + + function releaseShiftKeys() { + const releaseCommand = `ydotool key --key-delay 0 ${root.shiftKeys.map(keycode => `${keycode}:0`).join(" ")}` + Hyprland.dispatch(`exec ${releaseCommand}`) + root.shiftMode = 0; // Reset shift mode + } + + function press(keycode) { + Hyprland.dispatch(`exec ydotool key --key-delay 0 ${keycode}:1`); + } + + function release(keycode) { + Hyprland.dispatch(`exec ydotool key --key-delay 0 ${keycode}:0`); + } +} + diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 29ab0f327..6c72df6db 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -9,6 +9,7 @@ import "./modules/dock/" import "./modules/mediaControls/" import "./modules/notificationPopup/" import "./modules/onScreenDisplay/" +import "./modules/onScreenKeyboard/" import "./modules/overview/" import "./modules/screenCorners/" import "./modules/session/" @@ -31,6 +32,7 @@ ShellRoot { property bool enableNotificationPopup: true property bool enableOnScreenDisplayBrightness: true property bool enableOnScreenDisplayVolume: true + property bool enableOnScreenKeyboard: true property bool enableOverview: true property bool enableReloadPopup: true property bool enableScreenCorners: true @@ -54,6 +56,7 @@ ShellRoot { Loader { active: enableNotificationPopup; sourceComponent: NotificationPopup {} } Loader { active: enableOnScreenDisplayBrightness; sourceComponent: OnScreenDisplayBrightness {} } Loader { active: enableOnScreenDisplayVolume; sourceComponent: OnScreenDisplayVolume {} } + Loader { active: enableOnScreenKeyboard; sourceComponent: OnScreenKeyboard {} } Loader { active: enableOverview; sourceComponent: Overview {} } Loader { active: enableReloadPopup; sourceComponent: ReloadPopup {} } Loader { active: enableScreenCorners; sourceComponent: ScreenCorners {} } From 31e139da2922b81b1fa9ea5b4e045d159d9ae379 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 22:38:32 +0200 Subject: [PATCH 613/824] hyprland: adjust anims --- .config/hypr/hyprland/general.conf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.config/hypr/hyprland/general.conf b/.config/hypr/hyprland/general.conf index 2b0bdbde5..bf3475f9f 100644 --- a/.config/hypr/hyprland/general.conf +++ b/.config/hypr/hyprland/general.conf @@ -41,6 +41,7 @@ dwindle { preserve_split = true smart_split = false smart_resizing = false + # precise_mouse_move = true } decoration { @@ -94,10 +95,10 @@ animations { animation = border, 1, 10, emphasizedDecel # layers animation = layersIn, 1, 2.7, emphasizedDecel, popin 93% - animation = layersOut, 1, 1.8, menu_accel, popin 94% + animation = layersOut, 1, 2, menu_accel, popin 94% # fade animation = fadeLayersIn, 1, 0.5, menu_decel - animation = fadeLayersOut, 1, 2, menu_accel + animation = fadeLayersOut, 1, 2.2, menu_accel # workspaces animation = workspaces, 1, 7, menu_decel, slide ## specialWorkspace From 18802fef96aea12c92582e816dfccd1f7b3e1b24 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 22:38:41 +0200 Subject: [PATCH 614/824] hyprland: add osk keybind --- .config/hypr/hyprland/keybinds.conf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 6e06c534c..20ddff67c 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -24,6 +24,7 @@ bindd = Super, A, Toggle left sidebar, global, quickshell:sidebarLeftToggle # To bind = Super, O, global, quickshell:sidebarLeftToggle # [hidden] bindd = Super, N, Toggle right sidebar, global, quickshell:sidebarRightToggle # Toggle right sidebar bindd = Super, Slash, Toggle cheatsheet, global, quickshell:cheatsheetToggle # Toggle cheatsheet +bindd = Super, K, Toggle on-screen keyboard, global, quickshell:oskToggle # Toggle cheatsheet bindd = Super, M, Toggle media controls, global, quickshell:mediaControlsToggle # Toggle media controls bindd = Ctrl+Alt, Delete, Toggle session menu, global, quickshell:sessionToggle # Toggle session menu bind = Ctrl+Alt, Delete, exec, qs ipc call TEST_ALIVE || pkill wlogout || wlogout -p layer-shell # [hidden] @@ -197,10 +198,10 @@ bind = Super, Return, exec, foot # Foot (terminal) bind = Super, T, exec, foot # [hidden] foot (terminal) (alt) bind = Super, E, exec, dolphin --new-window # Dolphin (file manager) bind = Ctrl+Super, W, exec, firefox # Firefox (browser) -bind = Super, C, exec, code # VSCode (editor) +bind = Super, C, exec, code # VSCode (code editor) bind = Super, W, exec, zen-browser # [hidden] bind = Super+Shift, W, exec, wps # WPS Office -bind = Super, X, exec, kate # Kate (editor) +bind = Super, X, exec, kate # Kate (text editor) bind = Ctrl+Super, V, exec, pavucontrol-qt # Pavucontrol Qt (volume mixer) bind = Super, I, exec, XDG_CURRENT_DESKTOP=gnome gnome-control-center # GNOME Control center (settings app) bind = Ctrl+Shift, Escape, exec, plasma-systemmonitor --page-name Processes # Plasma system monitor From c334da9907463d1c858a109bd1dd64366f920f14 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 22:39:03 +0200 Subject: [PATCH 615/824] hyprland: make osk slide from bottom --- .config/hypr/hyprland/rules.conf | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index 7ed1f8d40..e7c2192fc 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -109,9 +109,16 @@ layerrule = animation slide, quickshell:bar layerrule = animation fade, quickshell:screenCorners layerrule = animation slide right, quickshell:sidebarRight layerrule = animation slide left, quickshell:sidebarLeft +layerrule = animation slide bottom, quickshell:osk layerrule = blur, quickshell:session layerrule = noanim, quickshell:session layerrule = animation fade, quickshell:notificationPopup + +# layerrule = blurpopups, quickshell:.* +# layerrule = blur, quickshell:.* +# layerrule = ignorealpha 0.79, quickshell:.* + + # Launchers need to be FAST layerrule = noanim, quickshell:overview layerrule = noanim, gtk4-layer-shell From 2046b5c58ac8fedeb631c231d7a70d51d489d18d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 22:39:52 +0200 Subject: [PATCH 616/824] make light/dark switching window not show up directly --- .config/hypr/hyprland/rules.conf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index e7c2192fc..19945f988 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -20,6 +20,12 @@ windowrulev2 = float, class:^(nm-connection-editor)$ windowrulev2 = size 45%, class:^(nm-connection-editor)$ windowrulev2 = center, class:^(nm-connection-editor)$ +# No appearance +# kde-material-you-colors spawns a window when changing dark/light theme. This is to make sure it doesn't interfere at all. +windowrulev2 = float, class:^(plasma-changeicons)$ +windowrulev2 = noinitialfocus, class:^(plasma-changeicons)$ +windowrulev2 = move 999999 999999, class:^(plasma-changeicons)$ + # Tiling windowrulev2 = tile, class:^dev\.warp\.Warp$ From 23e5df38a3405486954a7fad1650d7e3a862c2bc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 23:15:32 +0200 Subject: [PATCH 617/824] switch from foot to kitty terminal --- .config/ags/scripts/templates/fuzzel/fuzzel.ini | 2 +- .config/hypr/hyprland/keybinds.conf | 5 +++-- .config/matugen/templates/fuzzel/fuzzel.ini | 2 +- .config/quickshell/modules/common/ConfigOptions.qml | 2 +- .config/quickshell/scripts/switchwall.sh | 4 ++-- README.md | 2 +- arch-packages/illogical-impulse-fonts-themes/PKGBUILD | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.config/ags/scripts/templates/fuzzel/fuzzel.ini b/.config/ags/scripts/templates/fuzzel/fuzzel.ini index 47f6abc29..0dc348942 100644 --- a/.config/ags/scripts/templates/fuzzel/fuzzel.ini +++ b/.config/ags/scripts/templates/fuzzel/fuzzel.ini @@ -1,5 +1,5 @@ font=Gabarito -terminal=foot -e +terminal=kitty -1 prompt=">> " layer=overlay diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 20ddff67c..b15bde77a 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -194,8 +194,9 @@ bindl= ,XF86AudioPause, exec, playerctl play-pause # [hidden] ##! Apps bind = Super, T, exec, # [hidden] -bind = Super, Return, exec, foot # Foot (terminal) -bind = Super, T, exec, foot # [hidden] foot (terminal) (alt) +bind = Super, Return, exec, kitty -1 # Kitty (terminal) +bind = Super, T, exec, kitty -1 # [hidden] Kitty (terminal) (alt) +bind = Super+Alt, F, exec, foot # [hidden] Foot (terminal) bind = Super, E, exec, dolphin --new-window # Dolphin (file manager) bind = Ctrl+Super, W, exec, firefox # Firefox (browser) bind = Super, C, exec, code # VSCode (code editor) diff --git a/.config/matugen/templates/fuzzel/fuzzel.ini b/.config/matugen/templates/fuzzel/fuzzel.ini index 138799dea..da2582fe1 100644 --- a/.config/matugen/templates/fuzzel/fuzzel.ini +++ b/.config/matugen/templates/fuzzel/fuzzel.ini @@ -1,5 +1,5 @@ font=Gabarito -terminal=foot -e +terminal=kitty -1 prompt=">> " layer=overlay diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 5f1afaea5..9b02630e8 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -18,7 +18,7 @@ Singleton { property string network: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi" property string settings: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center" property string taskManager: "gnome-usage" - property string terminal: "foot" // This is only for shell actions + property string terminal: "kitty -1" // This is only for shell actions } property QtObject battery: QtObject { diff --git a/.config/quickshell/scripts/switchwall.sh b/.config/quickshell/scripts/switchwall.sh index a5954508e..22a87e158 100755 --- a/.config/quickshell/scripts/switchwall.sh +++ b/.config/quickshell/scripts/switchwall.sh @@ -59,7 +59,7 @@ check_and_prompt_upscale() { "Install Upscayl?" \ "yay -S upscayl-bin") if [[ "$action2" == "install_upscayl" ]]; then - foot yay -S upscayl-bin + kitty -1 yay -S upscayl-bin if command -v upscayl &>/dev/null; then nohup upscayl > /dev/null 2>&1 & fi @@ -156,7 +156,7 @@ switch() { "Can't switch to video wallpaper" \ "Missing dependencies: ${missing_deps[*]}") if [[ "$action" == "install_arch" ]]; then - foot sudo pacman -S "${missing_deps[*]}" + kitty -1 sudo pacman -S "${missing_deps[*]}" if command -v mpvpaper &>/dev/null && command -v ffmpeg &>/dev/null; then notify-send 'Wallpaper switcher' 'Alright, try again!' -a "Wallpaper switcher" fi diff --git a/README.md b/README.md index 041ac2383..769670c70 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ - **Default keybinds**: Should be somewhat familiar if you've used Windows or GNOME. - For a list, hit `Super`+`/` - - For `foot` terminal, hit `Super`+`Enter` + - For a terminal, hit `Super`+`Enter` diff --git a/arch-packages/illogical-impulse-fonts-themes/PKGBUILD b/arch-packages/illogical-impulse-fonts-themes/PKGBUILD index 017e94b49..c96790c0b 100644 --- a/arch-packages/illogical-impulse-fonts-themes/PKGBUILD +++ b/arch-packages/illogical-impulse-fonts-themes/PKGBUILD @@ -9,8 +9,8 @@ depends=( breeze-plus fish fontconfig - foot kde-material-you-colors + kitty matugen-bin starship ttf-readex-pro From b759580466c5a08ae66e9d07e670cea88f42d6c2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 31 May 2025 23:52:48 +0200 Subject: [PATCH 618/824] overview: show ws number, animated active indicator --- .../modules/overview/OverviewWidget.qml | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index a8271fee0..b060c8dfc 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -25,6 +25,7 @@ Item { property var windowAddresses: HyprlandData.addresses property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id) property real scale: ConfigOptions.overview.scale + property color activeBorderColor: Appearance.m3colors.m3secondary property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ? ((monitor.height - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) : @@ -34,7 +35,7 @@ Item { ((monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale) property real workspaceNumberMargin: 80 - property real workspaceNumberSize: 80 + property real workspaceNumberSize: Math.min(workspaceImplicitHeight, workspaceImplicitWidth) * monitor.scale property int workspaceZ: 0 property int windowZ: 1 property int windowDraggingZ: 99999 @@ -63,7 +64,7 @@ Item { radius: Appearance.rounding.screenRounding * root.scale + padding color: Appearance.colors.colLayer0 - ColumnLayout { + ColumnLayout { // Workspaces id: workspaceColumnLayout z: root.workspaceZ @@ -85,7 +86,6 @@ Item { property color defaultWorkspaceColor: Appearance.colors.colLayer1 // TODO: reconsider this color for a cleaner look property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1) property color hoveredBorderColor: Appearance.colors.colLayer2Hover - property color activeBorderColor: Appearance.m3colors.m3secondary property bool hoveredWhileDragging: false implicitWidth: root.workspaceImplicitWidth @@ -93,8 +93,17 @@ Item { color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor radius: Appearance.rounding.screenRounding * root.scale border.width: 2 - border.color: (monitor.activeWorkspace?.id == workspaceValue && root.monitorIsFocused) ? activeBorderColor : - hoveredWhileDragging ? hoveredBorderColor : "transparent" + border.color: hoveredWhileDragging ? hoveredBorderColor : "transparent" + + StyledText { + anchors.centerIn: parent + text: workspaceValue + font.pixelSize: root.workspaceNumberSize * root.scale + font.weight: Font.DemiBold + color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, 0.8) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } MouseArea { id: workspaceArea @@ -128,7 +137,7 @@ Item { } } - Item { + Item { // Windows & focused workspace indicator id: windowSpace anchors.centerIn: parent implicitWidth: workspaceColumnLayout.implicitWidth @@ -218,6 +227,28 @@ Item { } } } + + Rectangle { // Focused workspace indicator + id: focusedWorkspaceIndicator + property int activeWorkspaceInGroup: monitor.activeWorkspace?.id - (root.workspaceGroup * root.workspacesShown) + property int activeWorkspaceRowIndex: Math.floor((activeWorkspaceInGroup - 1) / ConfigOptions.overview.numOfCols) + property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % ConfigOptions.overview.numOfCols + x: (root.workspaceImplicitWidth + workspaceSpacing) * activeWorkspaceColIndex + y: (root.workspaceImplicitHeight + workspaceSpacing) * activeWorkspaceRowIndex + z: root.windowZ + width: root.workspaceImplicitWidth + height: root.workspaceImplicitHeight + color: "transparent" + radius: Appearance.rounding.screenRounding * root.scale + border.width: 2 + border.color: root.activeBorderColor + Behavior on x { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on y { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + } } } } From 3a6e607383e62312b130395f40cd175c6c166eb4 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 00:35:50 +0200 Subject: [PATCH 619/824] bar: workspaces: active ws trail --- .config/quickshell/modules/bar/Workspaces.qml | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 8a75b3d46..6f7d7d500 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -22,20 +22,11 @@ Item { property list workspaceOccupied: [] property int widgetPadding: 4 property int workspaceButtonWidth: 26 - property real workspaceIconSize: workspaceButtonWidth * 0.7 + property real workspaceIconSize: workspaceButtonWidth * 0.69 property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55 property real workspaceIconOpacityShrinked: 1 property real workspaceIconMarginShrinked: -4 - property int activeWorkspaceMargin: 1 - property double animatedActiveWorkspaceIndex: (monitor.activeWorkspace?.id - 1) % ConfigOptions.bar.workspaces.shown - - Behavior on animatedActiveWorkspaceIndex { - NumberAnimation { - duration: Appearance.animation.menuDecel.duration - easing.type: Appearance.animation.menuDecel.type - } - - } + property int workspaceIndexInGroup: (monitor.activeWorkspace?.id - 1) % ConfigOptions.bar.workspaces.shown // Function to update workspaceOccupied function updateWorkspaceOccupied() { @@ -138,12 +129,33 @@ Item { // Active workspace Rectangle { z: 2 - implicitWidth: workspaceButtonWidth - activeWorkspaceMargin * 2 + // Make active ws indicator, which has a brighter color, smaller to look like it is of the same size as ws occupied highlight + property real activeWorkspaceMargin: 2 implicitHeight: workspaceButtonWidth - activeWorkspaceMargin * 2 radius: Appearance.rounding.full color: Appearance.colors.colPrimary anchors.verticalCenter: parent.verticalCenter - x: animatedActiveWorkspaceIndex * workspaceButtonWidth + activeWorkspaceMargin + + property real idx1: workspaceIndexInGroup + property real idx2: workspaceIndexInGroup + x: Math.min(idx1, idx2) * workspaceButtonWidth + activeWorkspaceMargin + implicitWidth: Math.abs(idx1 - idx2) * workspaceButtonWidth + workspaceButtonWidth - activeWorkspaceMargin * 2 + + Behavior on activeWorkspaceMargin { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on idx1 { + NumberAnimation { + duration: 100 + easing.type: Easing.OutCirc + } + } + Behavior on idx2 { + NumberAnimation { + duration: 500 + easing.type: Easing.OutCirc + } + } } // Workspaces - numbers @@ -213,7 +225,7 @@ Item { (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked opacity: (workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? - 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 + 0.8 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 visible: opacity > 0 source: workspaceButtonBackground.mainAppIconSource implicitSize: (!GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? workspaceIconSize : workspaceIconSizeShrinked From 1136804c3205f5b702b0b206de251b5868652c3a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 09:15:17 +0200 Subject: [PATCH 620/824] remove redundant wallpaper command in matugen config --- .config/matugen/config.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.config/matugen/config.toml b/.config/matugen/config.toml index dcfb61aac..31d114e85 100644 --- a/.config/matugen/config.toml +++ b/.config/matugen/config.toml @@ -1,10 +1,6 @@ [config] version_check = false -[config.wallpaper] -command = "swww" -arguments = ["img", "--transition-step", "100", "--transition-fps", "120", "--transition-type", "grow", "--transition-angle", "30", "--transition-duration", "1"] - [templates.m3colors] input_path = '~/.config/matugen/templates/colors.json' output_path = '~/.local/state/quickshell/user/generated/colors.json' From 1dfde18b7d38dcec0bb45ed8fec248bed014d2ce Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 09:15:54 +0200 Subject: [PATCH 621/824] kitty: add cursor trail --- .config/kitty/kitty.conf | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.config/kitty/kitty.conf b/.config/kitty/kitty.conf index 452d594b1..ba8ea2ba1 100644 --- a/.config/kitty/kitty.conf +++ b/.config/kitty/kitty.conf @@ -1,5 +1,5 @@ # Font -font_family JetBrainsMono Nerd Font +font_family JetBrains Mono Nerd Font font_size 11.0 # Cursor shape @@ -28,3 +28,6 @@ map ctrl+kp_subtract change_font_size all -1 map ctrl+0 change_font_size all 0 map ctrl+kp_0 change_font_size all 0 + +# Cursor trail +cursor_trail 1 From 5ad78a77a13a2c48d75c26957dd67642ec6586e0 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 09:19:55 +0200 Subject: [PATCH 622/824] workspaces: adjust icon opacity --- .config/quickshell/modules/bar/Workspaces.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 6f7d7d500..ecfd697c1 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -225,7 +225,7 @@ Item { (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked opacity: (workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? - 0.8 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 + 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 visible: opacity > 0 source: workspaceButtonBackground.mainAppIconSource implicitSize: (!GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? workspaceIconSize : workspaceIconSizeShrinked From b172020f5bc6f0dc5e3fd3fbc7976a3a9ee5c278 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 09:51:45 +0200 Subject: [PATCH 623/824] workspace: adjust active indicator anim curve --- .config/quickshell/modules/bar/Workspaces.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index ecfd697c1..4639edd22 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -147,13 +147,13 @@ Item { Behavior on idx1 { NumberAnimation { duration: 100 - easing.type: Easing.OutCirc + easing.type: Easing.OutSine } } Behavior on idx2 { NumberAnimation { - duration: 500 - easing.type: Easing.OutCirc + duration: 300 + easing.type: Easing.OutSine } } } From 834a684d6f37164485e299275c8498a77cce1df4 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 10:10:57 +0200 Subject: [PATCH 624/824] remove persistent state for bottom sidebar group tabs (buggy) --- .config/quickshell/modules/common/PersistentStates.qml | 1 - .../quickshell/modules/sidebarRight/BottomWidgetGroup.qml | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.config/quickshell/modules/common/PersistentStates.qml b/.config/quickshell/modules/common/PersistentStates.qml index cbf52e8e4..30d89795b 100644 --- a/.config/quickshell/modules/common/PersistentStates.qml +++ b/.config/quickshell/modules/common/PersistentStates.qml @@ -11,7 +11,6 @@ Singleton { property QtObject sidebar: QtObject { property QtObject bottomGroup: QtObject { property bool collapsed: false - property int selectedTab: 0 } } diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index 66ebf051f..882d3a035 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -14,17 +14,13 @@ Rectangle { color: Appearance.colors.colLayer1 clip: true implicitHeight: collapsed ? collapsedBottomWidgetGroupRow.implicitHeight : bottomWidgetGroupRow.implicitHeight - property int selectedTab: PersistentStates.sidebar.bottomGroup.selectedTab + property int selectedTab: 0 property bool collapsed: PersistentStates.sidebar.bottomGroup.collapsed property var tabs: [ {"type": "calendar", "name": "Calendar", "icon": "calendar_month", "widget": calendarWidget}, {"type": "todo", "name": "To Do", "icon": "done_outline", "widget": todoWidget} ] - onSelectedTabChanged: { - PersistentStateManager.setState("sidebar.bottomGroup.selectedTab", selectedTab) - } - Behavior on implicitHeight { NumberAnimation { duration: Appearance.animation.elementMove.duration From 771ff1446277b6da099b1771a1d06e9275953993 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 10:12:37 +0200 Subject: [PATCH 625/824] notifications: add silent button --- .../notifications/NotificationList.qml | 46 +++++++++++++------ .../NotificationStatusButton.qml | 15 +++--- .config/quickshell/services/Notifications.qml | 4 +- 3 files changed, 43 insertions(+), 22 deletions(-) diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml index 7dd4e28c6..491b7683f 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -17,6 +17,7 @@ Item { anchors.right: parent.right anchors.top: parent.top anchors.bottom: statusRow.top + anchors.bottomMargin: 5 clip: true layer.enabled: true @@ -65,17 +66,24 @@ Item { } } - RowLayout { + Item { id: statusRow anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom + + Layout.fillWidth: true + implicitHeight: Math.max( + controls.implicitHeight, + statusText.implicitHeight + ) + StyledText { - Layout.margins: 10 - Layout.bottomMargin: 5 - Layout.alignment: Qt.AlignVCenter + id: statusText + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 10 horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter text: `${Notifications.list.length} notifications` opacity: Notifications.list.length > 0 ? 1 : 0 @@ -85,16 +93,26 @@ Item { } } - Item { Layout.fillWidth: true } + ButtonGroup { + id: controls + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: 5 - NotificationStatusButton { - Layout.alignment: Qt.AlignVCenter - Layout.margins: 5 - Layout.topMargin: 10 - buttonIcon: "clear_all" - buttonText: qsTr("Clear") - onClicked: () => { - Notifications.discardAllNotifications() + NotificationStatusButton { + buttonIcon: "notifications_paused" + buttonText: qsTr("Silent") + toggled: Notifications.silent + onClicked: () => { + Notifications.silent = !Notifications.silent; + } + } + NotificationStatusButton { + buttonIcon: "clear_all" + buttonText: qsTr("Clear") + onClicked: () => { + Notifications.discardAllNotifications() + } } } } diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml index f5bb6844a..4bef8903e 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml @@ -9,33 +9,34 @@ GroupButton { property string buttonText: "" property string buttonIcon: "" - baseWidth: contentRowLayout.implicitWidth + 10 * 2 + baseWidth: content.implicitWidth + 10 * 2 baseHeight: 30 - clickedWidth: baseWidth + 15 buttonRadius: baseHeight / 2 buttonRadiusPressed: Appearance.rounding.small colBackground: Appearance.colors.colLayer2 colBackgroundHover: Appearance.colors.colLayer2Hover colBackgroundActive: Appearance.colors.colLayer2Active - background.anchors.fill: button + property color colText: toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1 contentItem: Item { + id: content + anchors.fill: parent + implicitWidth: contentRowLayout.implicitWidth + implicitHeight: contentRowLayout.implicitHeight RowLayout { id: contentRowLayout anchors.centerIn: parent spacing: 0 MaterialSymbol { text: buttonIcon - Layout.fillWidth: false iconSize: Appearance.font.pixelSize.larger - color: Appearance.colors.colOnLayer1 + color: button.colText } StyledText { text: buttonText - Layout.fillWidth: false font.pixelSize: Appearance.font.pixelSize.small - color: Appearance.colors.colOnLayer1 + color: button.colText } } } diff --git a/.config/quickshell/services/Notifications.qml b/.config/quickshell/services/Notifications.qml index d5c7e63cb..75033292c 100644 --- a/.config/quickshell/services/Notifications.qml +++ b/.config/quickshell/services/Notifications.qml @@ -60,10 +60,12 @@ Singleton { destroy() } } + + property bool silent: false property var filePath: Directories.notificationsPath property list list: [] property var popupList: list.filter((notif) => notif.popup); - property bool popupInhibited: GlobalStates?.sidebarRightOpen ?? false + property bool popupInhibited: (GlobalStates?.sidebarRightOpen ?? false) || silent property var latestTimeForApp: ({}) Component { id: notifComponent From 57a2580a5d834d0b7242dc1f4b5a515466ec2c77 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 10:16:14 +0200 Subject: [PATCH 626/824] adjust notif status btn size --- .../sidebarRight/notifications/NotificationStatusButton.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml index 4bef8903e..bcfeaecfd 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml @@ -27,10 +27,10 @@ GroupButton { RowLayout { id: contentRowLayout anchors.centerIn: parent - spacing: 0 + spacing: 5 MaterialSymbol { text: buttonIcon - iconSize: Appearance.font.pixelSize.larger + iconSize: Appearance.font.pixelSize.large color: button.colText } StyledText { From 0d7769c88442bef4bca85c0afdf9f0cfb341d9c6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 11:21:28 +0200 Subject: [PATCH 627/824] make network indicator work with ethernet --- .config/quickshell/modules/bar/Bar.qml | 10 +-- .../quickToggles/NetworkToggle.qml | 8 +-- .config/quickshell/services/Network.qml | 64 +++++++++++++++---- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 5e4d7eb5e..891c9a2c8 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -10,8 +10,6 @@ import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Wayland import Quickshell.Hyprland -import Quickshell.Io -import Quickshell.Services.Mpris import Quickshell.Services.UPower Scope { @@ -393,13 +391,7 @@ Scope { } MaterialSymbol { Layout.rightMargin: indicatorsRowLayout.realSpacing - text: (Network.networkName.length > 0 && Network.networkName != "lo") ? ( - Network.networkStrength > 80 ? "signal_wifi_4_bar" : - Network.networkStrength > 60 ? "network_wifi_3_bar" : - Network.networkStrength > 40 ? "network_wifi_2_bar" : - Network.networkStrength > 20 ? "network_wifi_1_bar" : - "signal_wifi_0_bar" - ) : "signal_wifi_off" + text: Network.materialSymbol iconSize: Appearance.font.pixelSize.larger color: rightSidebarButton.colText } diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml index fc0bda11a..8f058fd53 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml @@ -10,13 +10,7 @@ import Quickshell.Hyprland QuickToggleButton { toggled: Network.networkName.length > 0 && Network.networkName != "lo" - buttonIcon: toggled ? ( - Network.networkStrength > 80 ? "signal_wifi_4_bar" : - Network.networkStrength > 60 ? "network_wifi_3_bar" : - Network.networkStrength > 40 ? "network_wifi_2_bar" : - Network.networkStrength > 20 ? "network_wifi_1_bar" : - "signal_wifi_0_bar" - ) : "signal_wifi_off" + buttonIcon: Network.materialSymbol onClicked: { toggleNetwork.running = true } diff --git a/.config/quickshell/services/Network.qml b/.config/quickshell/services/Network.qml index e2f98005b..50bfb671f 100644 --- a/.config/quickshell/services/Network.qml +++ b/.config/quickshell/services/Network.qml @@ -1,10 +1,9 @@ pragma Singleton pragma ComponentBehavior: Bound -import Quickshell; -import Quickshell.Io; -import Quickshell.Services.Pipewire; -import QtQuick; +import Quickshell +import Quickshell.Io +import QtQuick /** * Simple polled network state service. @@ -12,12 +11,23 @@ import QtQuick; Singleton { id: root + property bool wifi: true + property bool ethernet: false property int updateInterval: 1000 - property string networkName: ""; - property int networkStrength; + property string networkName: "" + property int networkStrength + property string materialSymbol: ethernet ? "lan" : + (Network.networkName.length > 0 && Network.networkName != "lo") ? ( + Network.networkStrength > 80 ? "signal_wifi_4_bar" : + Network.networkStrength > 60 ? "network_wifi_3_bar" : + Network.networkStrength > 40 ? "network_wifi_2_bar" : + Network.networkStrength > 20 ? "network_wifi_1_bar" : + "signal_wifi_0_bar" + ) : "signal_wifi_off" function update() { - updateNetworkName.running = true - updateNetworkStrength.running = true + updateConnectionType.startCheck(); + updateNetworkName.running = true; + updateNetworkStrength.running = true; } Timer { @@ -25,18 +35,47 @@ Singleton { running: true repeat: true onTriggered: { - update() + root.update(); interval = root.updateInterval; } } + Process { + id: updateConnectionType + property string buffer + command: ["sh", "-c", "nmcli -t -f NAME,TYPE,DEVICE c show --active"] + running: true + function startCheck() { + buffer = ""; + updateConnectionType.running = true; + } + stdout: SplitParser { + onRead: data => { + updateConnectionType.buffer += data + "\n"; + } + } + onExited: (exitCode, exitStatus) => { + const lines = updateConnectionType.buffer.trim().split('\n'); + let hasEthernet = false; + let hasWifi = false; + lines.forEach(line => { + if (line.includes("ethernet")) + hasEthernet = true; + else if (line.includes("wireless")) + hasWifi = true; + }); + root.ethernet = hasEthernet; + root.wifi = hasWifi; + } + } + Process { id: updateNetworkName command: ["sh", "-c", "nmcli -t -f NAME c show --active | head -1"] - running: true; + running: true stdout: SplitParser { onRead: data => { - root.networkName = data + root.networkName = data; } } } @@ -44,7 +83,7 @@ Singleton { Process { id: updateNetworkStrength running: true - command: ["sh", "-c", "nmcli -f IN-USE,SIGNAL,SSID device wifi | awk '/^\*/{if (NR!=1) {print $2}}'"]; + command: ["sh", "-c", "nmcli -f IN-USE,SIGNAL,SSID device wifi | awk '/^\*/{if (NR!=1) {print $2}}'"] stdout: SplitParser { onRead: data => { root.networkStrength = parseInt(data); @@ -52,4 +91,3 @@ Singleton { } } } - From 3f274e65ac7e3aa19ea4af14b10345400264b4b2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 12:31:07 +0200 Subject: [PATCH 628/824] boorus: add t.alcy.cc --- .../sidebarLeft/anime/BooruResponse.qml | 3 +- .config/quickshell/services/Booru.qml | 88 +++++++++++++++++-- 2 files changed, 81 insertions(+), 10 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index 1af6c91fe..c19c8bb98 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -243,7 +243,8 @@ Rectangle { imageData: modelData rowHeight: imageRow.rowHeight imageRadius: imageRow.modelData.images.length == 1 ? 50 : Appearance.rounding.normal - manualDownload: ["danbooru", "waifu.im"].includes(root.responseData.provider) + // Download manually to reduce redundant requests or make sure downloading works + manualDownload: ["danbooru", "waifu.im", "t.alcy.cc"].includes(root.responseData.provider) previewDownloadPath: root.previewDownloadPath downloadPath: root.downloadPath nsfwPath: root.nsfwPath diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index 697e92069..8baebfeb6 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -20,7 +20,7 @@ Singleton { property var responses: [] property int runningRequests: 0 property var defaultUserAgent: ConfigOptions?.networking?.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" - property var providerList: ["yandere", "konachan", "zerochan", "danbooru", "gelbooru", "waifu.im"] + property var providerList: Object.keys(providers).filter(provider => provider !== "system" && providers[provider].api) property var providers: { "system": { "name": qsTr("System") }, "yandere": { @@ -30,6 +30,7 @@ Singleton { "description": qsTr("All-rounder | Good quality, decent quantity"), "mapFunc": (response) => { return response.map(item => { + console.log(JSON.stringify(item, null, 2)) return { "id": item.id, "width": item.width, @@ -219,6 +220,60 @@ Singleton { ...response.nsfw.map(item => {return {"name": item}})] } }, + "t.alcy.cc": { + "name": "Alcy", + "url": "https://t.alcy.cc", + "api": "https://t.alcy.cc/", + "description": qsTr("Large images | God tier quality, no NSFW."), + "fixedTags": [ + { + "name": "ycy", + "count": "General" + }, + { + "name": "moez", + "count": "Moe" + }, + { + "name": "ysz", + "count": "Genshin Impact" + }, + { + "name": "fj", + "count": "Landscape" + }, + { + "name": "bd", + "count": "Girl on white background" + }, + { + "name": "xhl", + "count": "Shiggy" + }, + ], + "manualParseFunc": (responseText) => { + // Alcy just returns image links, each on a new line + const lines = responseText.trim().split('\n'); + return lines.map(line => { + return { + "id": Qt.md5(line), + // Alcy doesn't provide dimensions and images are often of god resolution + "width": 1000, + "height": 1000, + "aspect_ratio": 1, // Default aspect ratio + "tags": "[no tags]", + "rating": "s", + "is_nsfw": false, + "md5": Qt.md5(line), + "preview_url": line, + "sample_url": line, + "file_url": line, + "file_ext": line.split('.').pop(), + "source": "", + } + }); + }, + } } property var currentProvider: PersistentStates.booru.provider @@ -257,8 +312,9 @@ Singleton { function constructRequestUrl(tags, nsfw=true, limit=20, page=1) { var provider = providers[currentProvider] var baseUrl = provider.api + var url = baseUrl var tagString = tags.join(" ") - if (!nsfw && !(["zerochan", "waifu.im"].includes(currentProvider))) { + if (!nsfw && !(["zerochan", "waifu.im", "t.alcy.cc"].includes(currentProvider))) { if (currentProvider == "gelbooru") tagString += " rating:general"; else @@ -281,6 +337,12 @@ Singleton { params.push("limit=" + Math.min(limit, 30)) // Only admin can do > 30 params.push("is_nsfw=" + (nsfw ? "null" : "false")) // null is random } + else if (currentProvider === "t.alcy.cc") { + console.log(`"${tagString}"`) + url += tagString + params.push("json") + params.push("quantity=" + limit) + } else { params.push("tags=" + encodeURIComponent(tagString)) params.push("limit=" + limit) @@ -291,7 +353,6 @@ Singleton { params.push("page=" + page) } } - var url = baseUrl if (baseUrl.indexOf("?") === -1) { url += "?" + params.join("&") } else { @@ -302,7 +363,7 @@ Singleton { function makeRequest(tags, nsfw=false, limit=20, page=1) { var url = constructRequestUrl(tags, nsfw, limit, page) - // console.log("[Booru] Making request to " + url) + console.log("[Booru] Making request to " + url) const newResponse = root.booruResponseDataComponent.createObject(null, { "provider": currentProvider, @@ -317,10 +378,16 @@ Singleton { xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { try { - // console.log("[Booru] Raw response: " + xhr.responseText) - var response = JSON.parse(xhr.responseText) - response = providers[currentProvider].mapFunc(response) - // console.log("[Booru] Mapped response: " + JSON.stringify(response)) + console.log("[Booru] Raw response: " + xhr.responseText) + const provider = providers[currentProvider] + let response; + if (provider.manualParseFunc) { + response = provider.manualParseFunc(xhr.responseText) + } else { + response = JSON.parse(xhr.responseText) + response = provider.mapFunc(response) + } + console.log("[Booru] Mapped response: " + JSON.stringify(response)) newResponse.images = response newResponse.message = response.length > 0 ? "" : root.failMessage @@ -360,7 +427,10 @@ Singleton { } var provider = providers[currentProvider] - if (!provider.tagSearchTemplate) { + if (provider.fixedTags) { + root.tagSuggestion(query, provider.fixedTags) + return provider.fixedTags; + } else if (!provider.tagSearchTemplate) { return } var url = provider.tagSearchTemplate.replace("{{query}}", encodeURIComponent(query)) From 7f13abf7713c4fd5b354aeafcfdc63e644c067ac Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 13:24:10 +0200 Subject: [PATCH 629/824] update snowman-style battery nag https://discord.com/channels/961691461554950145/961693710968557598/1378504799141756991 --- .config/quickshell/services/Battery.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/services/Battery.qml b/.config/quickshell/services/Battery.qml index c71cc8c79..08bdee7cd 100644 --- a/.config/quickshell/services/Battery.qml +++ b/.config/quickshell/services/Battery.qml @@ -25,6 +25,6 @@ Singleton { } onIsCriticalAndNotChargingChanged: { - if (available && isCriticalAndNotCharging) Hyprland.dispatch(`exec notify-send "Critically low battery" "🙏 I ask for pleas charge\nAutomatic suspend triggers at ${ConfigOptions.battery.suspend}%" -u critical -a "Shell"`) + if (available && isCriticalAndNotCharging) Hyprland.dispatch(`exec notify-send "Critically low battery" "🙏 I beg for pleas charg\nAutomatic suspend triggers at ${ConfigOptions.battery.suspend}%" -u critical -a "Shell"`) } } From c9563ffb63c96eb842e867e346b0d7fa2f2cccf7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 15:26:48 +0200 Subject: [PATCH 630/824] quickshell: task manager: gnome-usage -> plasma system monitor --- .config/quickshell/modules/common/ConfigOptions.qml | 2 +- .config/quickshell/modules/session/Session.qml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 9b02630e8..ad4ccf4df 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -17,7 +17,7 @@ Singleton { property string imageViewer: "loupe" property string network: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi" property string settings: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center" - property string taskManager: "gnome-usage" + property string taskManager: "plasma-systemmonitor --page-name Processes" property string terminal: "kitty -1" // This is only for shell actions } diff --git a/.config/quickshell/modules/session/Session.qml b/.config/quickshell/modules/session/Session.qml index 9dc8300bd..94e6123ff 100644 --- a/.config/quickshell/modules/session/Session.qml +++ b/.config/quickshell/modules/session/Session.qml @@ -26,7 +26,7 @@ Scope { function hide() { sessionLoader.active = false } - + exclusionMode: ExclusionMode.Ignore WlrLayershell.namespace: "quickshell:session" @@ -121,7 +121,7 @@ Scope { id: sessionTaskManager buttonIcon: "browse_activity" buttonText: qsTr("Task Manager") - onClicked: { Hyprland.dispatch("exec gnome-system-monitor & disown"); sessionRoot.hide() } + onClicked: { Hyprland.dispatch(`exec ${ConfigOptions.apps.taskManager}`); sessionRoot.hide() } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.left: sessionLogout KeyNavigation.down: sessionFirmwareReboot From 2b0d4b2c32cb27e6b673a5576d0f4fc13375ece7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 16:21:25 +0200 Subject: [PATCH 631/824] installation: remove gradience --- diagnose | 6 +----- scriptdata/installers | 22 ---------------------- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/diagnose b/diagnose index f0fc6d2dd..ed3d79aa2 100755 --- a/diagnose +++ b/diagnose @@ -64,9 +64,7 @@ x declare -p XDG_STATE_HOME # ~/.local/state x declare -p ILLOGICAL_IMPULSE_VIRTUAL_ENV # $XDG_STATE_HOME/quickshell/.venv e "Checking directories/files" -x ls -l ~/.local/state/ags/.venv -x ls $XDG_DATA_HOME/glib-2.0/schemas -x ls $XDG_DATA_HOME/gradience +x ls -l ~/.local/state/quickshell/.venv #x cat ~/.config/ags/ #e "Checking command existence" @@ -78,8 +76,6 @@ commands+=(ags agsv1) e "Checking versions" x Hyprland --version -x ags --version -x agsv1 --version e "Finished. Output saved as \"$output_file\"." if ! command -v curl 2>&1 >>/dev/null ;then echo "\"curl\" not found, pastebin upload unavailable.";exit;fi diff --git a/scriptdata/installers b/scriptdata/installers index bfb824c89..90d439c2d 100644 --- a/scriptdata/installers +++ b/scriptdata/installers @@ -130,29 +130,7 @@ install-python-packages (){ x source $ILLOGICAL_IMPULSE_VIRTUAL_ENV/bin/activate x uv pip install -r scriptdata/requirements.txt - x mkdir -p $base/cache/blueprint-compiler && cd $base/cache/blueprint-compiler - try git init -b main - try git remote add origin https://github.com/end-4/ii-blueprint-compiler.git - x git pull origin main && git submodule update --init --recursive - x meson setup build --prefix=$VIRTUAL_ENV - x meson compile -C build - x meson install -C build - x cd - - - x mkdir -p $base/cache/gradience && cd $base/cache/gradience - try git init -b main - try git remote add origin https://github.com/end-4/ii-gradience.git - x git pull origin main && git submodule update --init --recursive - x uv pip install -r requirements.txt - x meson setup build --prefix=$VIRTUAL_ENV - x meson compile -C build - x meson install -C build - x cd - - x deactivate # We don't need the virtual environment anymore - for i in "glib-2.0" "gradience"; do - x rsync -av "$ILLOGICAL_IMPULSE_VIRTUAL_ENV"/share/$i/ "$XDG_DATA_HOME"/$i/ - done } # Only for Arch(based) distro. From 5e8f912aa92c9e6b46eaa67589c389a69558c773 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 16:23:40 +0200 Subject: [PATCH 632/824] update plasma browser integration size --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index fc3f4be71..0f4608d81 100755 --- a/install.sh +++ b/install.sh @@ -123,7 +123,7 @@ case $SKIP_PLASMAINTG in true) sleep 0;; *) if $ask;then - echo -e "\e[33m[$0]: NOTE: The size of \"plasma-browser-integration\" is about 250 MiB.\e[0m" + echo -e "\e[33m[$0]: NOTE: The size of \"plasma-browser-integration\" is about 600 MiB.\e[0m" echo -e "\e[33mIt is needed if you want playtime of media in Firefox to be shown on the music controls widget.\e[0m" echo -e "\e[33mInstall it? [y/N]\e[0m" read -p "====> " p From a6a3a144d2dd7f2c20c88aa6857cf68d6397ae05 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 17:47:00 +0200 Subject: [PATCH 633/824] remove debug print (AGAIN) --- .config/quickshell/services/Booru.qml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index 8baebfeb6..49256bfa7 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -30,7 +30,6 @@ Singleton { "description": qsTr("All-rounder | Good quality, decent quantity"), "mapFunc": (response) => { return response.map(item => { - console.log(JSON.stringify(item, null, 2)) return { "id": item.id, "width": item.width, @@ -338,7 +337,6 @@ Singleton { params.push("is_nsfw=" + (nsfw ? "null" : "false")) // null is random } else if (currentProvider === "t.alcy.cc") { - console.log(`"${tagString}"`) url += tagString params.push("json") params.push("quantity=" + limit) @@ -363,7 +361,7 @@ Singleton { function makeRequest(tags, nsfw=false, limit=20, page=1) { var url = constructRequestUrl(tags, nsfw, limit, page) - console.log("[Booru] Making request to " + url) + // console.log("[Booru] Making request to " + url) const newResponse = root.booruResponseDataComponent.createObject(null, { "provider": currentProvider, @@ -378,7 +376,7 @@ Singleton { xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { try { - console.log("[Booru] Raw response: " + xhr.responseText) + // console.log("[Booru] Raw response: " + xhr.responseText) const provider = providers[currentProvider] let response; if (provider.manualParseFunc) { @@ -387,7 +385,7 @@ Singleton { response = JSON.parse(xhr.responseText) response = provider.mapFunc(response) } - console.log("[Booru] Mapped response: " + JSON.stringify(response)) + // console.log("[Booru] Mapped response: " + JSON.stringify(response)) newResponse.images = response newResponse.message = response.length > 0 ? "" : root.failMessage From aaf652b17cd762c73405f480fe485989d6ab5287 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 17:47:32 +0200 Subject: [PATCH 634/824] detachable sidebar --- .../quickshell/modules/sidebarLeft/AiChat.qml | 1 - .../quickshell/modules/sidebarLeft/Anime.qml | 1 - .../modules/sidebarLeft/SidebarLeft.qml | 130 ++++++++---------- .../sidebarLeft/SidebarLeftContent.qml | 89 ++++++++++++ 4 files changed, 146 insertions(+), 75 deletions(-) create mode 100644 .config/quickshell/modules/sidebarLeft/SidebarLeftContent.qml diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index a20bbb23c..a17954e6a 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -16,7 +16,6 @@ import Quickshell.Hyprland Item { id: root - property var panelWindow property var inputField: messageInputField property string commandPrefix: "/" diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 1bfb210ce..8d6c77a3f 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -17,7 +17,6 @@ import Quickshell.Hyprland Item { id: root - property var panelWindow property var inputField: tagInputField readonly property var responses: Booru.responses property string previewDownloadPath: Directories.booruPreviews diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index 57c7e6ffe..a6b6c2062 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -17,25 +17,50 @@ Scope { // Scope id: root property int sidebarPadding: 15 property var tabButtonList: [{"icon": "neurology", "name": qsTr("Intelligence")}, {"icon": "bookmark_heart", "name": qsTr("Anime")}] + property int selectedTab: 0 + property bool pin: false + property Component contentComponent: SidebarLeftContent {} + property Item sidebarContent + + Component.onCompleted: { + root.sidebarContent = contentComponent.createObject(null, { + "scopeRoot": root, + }); + sidebarLoader.item.contentParent.children = [root.sidebarContent]; + } + + onPinChanged: { + console.log("Sidebar pin state changed:", root.pin); + if (root.pin) { + sidebarContent.parent = null; // Detach content from sidebar + sidebarLoader.active = false; // Unload sidebar + detachedSidebarLoader.active = true; // Load detached window + detachedSidebarLoader.item.contentParent.children = [sidebarContent]; + } else { + sidebarContent.parent = null; // Detach content from window + detachedSidebarLoader.active = false; // Unload detached window + sidebarLoader.active = true; // Load sidebar + sidebarLoader.item.contentParent.children = [sidebarContent]; + } + } Loader { id: sidebarLoader - active: GlobalStates.sidebarLeftOpen + active: true - PanelWindow { // Window + sourceComponent: PanelWindow { // Window id: sidebarRoot visible: GlobalStates.sidebarLeftOpen - property int selectedTab: 0 property bool extend: false - property bool pin: false property real sidebarWidth: sidebarRoot.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth + property var contentParent: sidebarLeftBackground function hide() { GlobalStates.sidebarLeftOpen = false } - exclusiveZone: sidebarRoot.pin ? sidebarWidth : 0 + exclusiveZone: 0 implicitWidth: Appearance.sizes.sidebarWidthExtended + Appearance.sizes.elevationMargin WlrLayershell.namespace: "quickshell:sidebarLeft" // Hyprland 0.49: OnDemand is Exclusive, Exclusive just breaks click-outside-to-close @@ -55,22 +80,22 @@ Scope { // Scope HyprlandFocusGrab { // Click outside to close id: grab windows: [ sidebarRoot ] - active: sidebarRoot.visible && !sidebarRoot.pin + active: sidebarRoot.visible onActiveChanged: { // Focus the selected tab - if (active) swipeView.currentItem.forceActiveFocus() + if (active) sidebarLeftBackground.children[0].focusActiveItem() } onCleared: () => { if (!active) sidebarRoot.hide() } } - // Background + // Content StyledRectangularShadow { target: sidebarLeftBackground + radius: sidebarLeftBackground.radius } Rectangle { id: sidebarLeftBackground - anchors.top: parent.top anchors.left: parent.left anchors.topMargin: Appearance.sizes.hyprlandGapsOut @@ -79,89 +104,48 @@ Scope { // Scope height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 color: Appearance.colors.colLayer0 radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 - focus: sidebarRoot.visible - - Behavior on width { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } Keys.onPressed: (event) => { - // console.log("Key pressed: " + event.key) if (event.key === Qt.Key_Escape) { sidebarRoot.hide(); } if (event.modifiers === Qt.ControlModifier) { - if (event.key === Qt.Key_PageDown) { - sidebarRoot.selectedTab = Math.min(sidebarRoot.selectedTab + 1, root.tabButtonList.length - 1) - } - else if (event.key === Qt.Key_PageUp) { - sidebarRoot.selectedTab = Math.max(sidebarRoot.selectedTab - 1, 0) - } - else if (event.key === Qt.Key_Tab) { - sidebarRoot.selectedTab = (sidebarRoot.selectedTab + 1) % root.tabButtonList.length; - } - else if (event.key === Qt.Key_Backtab) { - sidebarRoot.selectedTab = (sidebarRoot.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length; - } - else if (event.key === Qt.Key_O) { + if (event.key === Qt.Key_O) { sidebarRoot.extend = !sidebarRoot.extend; } else if (event.key === Qt.Key_P) { - sidebarRoot.pin = !sidebarRoot.pin; + root.pin = !root.pin; } event.accepted = true; } } + } + } + } - ColumnLayout { - anchors.fill: parent - anchors.margins: sidebarPadding - - spacing: sidebarPadding + Loader { + id: detachedSidebarLoader + active: false - PrimaryTabBar { // Tab strip - id: tabBar - tabButtonList: root.tabButtonList - externalTrackedTab: sidebarRoot.selectedTab - function onCurrentIndexChanged(currentIndex) { - sidebarRoot.selectedTab = currentIndex + sourceComponent: FloatingWindow { + id: detachedSidebarRoot + visible: GlobalStates.sidebarLeftOpen + property var contentParent: detachedSidebarBackground + + Rectangle { + id: detachedSidebarBackground + anchors.fill: parent + color: Appearance.colors.colLayer0 + + Keys.onPressed: (event) => { + if (event.modifiers === Qt.ControlModifier) { + if (event.key === Qt.Key_P) { + root.pin = !root.pin; } + event.accepted = true; } - - SwipeView { // Content pages - id: swipeView - Layout.topMargin: 5 - Layout.fillWidth: true - Layout.fillHeight: true - spacing: 10 - - currentIndex: tabBar.externalTrackedTab - onCurrentIndexChanged: { - tabBar.enableIndicatorAnimation = true - sidebarRoot.selectedTab = currentIndex - } - - clip: true - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: swipeView.width - height: swipeView.height - radius: Appearance.rounding.small - } - } - - AiChat { - panelWindow: sidebarRoot - } - Anime { - panelWindow: sidebarRoot - } - } - } } - } } diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeftContent.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeftContent.qml new file mode 100644 index 000000000..6cf7f556f --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeftContent.qml @@ -0,0 +1,89 @@ +import "root:/" +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Effects +import Qt5Compat.GraphicalEffects +import Quickshell.Io +import Quickshell +import Quickshell.Widgets +import Quickshell.Wayland +import Quickshell.Hyprland + +Item { + id: sidebarLeftBackground + required property var scopeRoot + anchors.fill: parent + + function focusActiveItem() { + swipeView.currentItem.forceActiveFocus() + } + + Keys.onPressed: (event) => { + if (event.modifiers === Qt.ControlModifier) { + if (event.key === Qt.Key_PageDown) { + scopeRoot.selectedTab = Math.min(scopeRoot.selectedTab + 1, scopeRoot.tabButtonList.length - 1) + event.accepted = true; + } + else if (event.key === Qt.Key_PageUp) { + scopeRoot.selectedTab = Math.max(scopeRoot.selectedTab - 1, 0) + event.accepted = true; + } + else if (event.key === Qt.Key_Tab) { + scopeRoot.selectedTab = (scopeRoot.selectedTab + 1) % scopeRoot.tabButtonList.length; + event.accepted = true; + } + else if (event.key === Qt.Key_Backtab) { + scopeRoot.selectedTab = (scopeRoot.selectedTab - 1 + scopeRoot.tabButtonList.length) % scopeRoot.tabButtonList.length; + event.accepted = true; + } + } + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: sidebarPadding + + spacing: sidebarPadding + + PrimaryTabBar { // Tab strip + id: tabBar + tabButtonList: scopeRoot.tabButtonList + externalTrackedTab: scopeRoot.selectedTab + function onCurrentIndexChanged(currentIndex) { + scopeRoot.selectedTab = currentIndex + } + } + + SwipeView { // Content pages + id: swipeView + Layout.topMargin: 5 + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 10 + + currentIndex: tabBar.externalTrackedTab + onCurrentIndexChanged: { + tabBar.enableIndicatorAnimation = true + scopeRoot.selectedTab = currentIndex + } + + clip: true + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: swipeView.width + height: swipeView.height + radius: Appearance.rounding.small + } + } + + AiChat {} + Anime {} + } + + } +} \ No newline at end of file From e024f896a6b7430bbc9933d234e072772cb5a02f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 17:53:28 +0200 Subject: [PATCH 635/824] left sidebar: fix width anim --- .config/quickshell/modules/sidebarLeft/SidebarLeft.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index a6b6c2062..80f5e17b0 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -30,7 +30,6 @@ Scope { // Scope } onPinChanged: { - console.log("Sidebar pin state changed:", root.pin); if (root.pin) { sidebarContent.parent = null; // Detach content from sidebar sidebarLoader.active = false; // Unload sidebar @@ -105,6 +104,10 @@ Scope { // Scope color: Appearance.colors.colLayer0 radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 + Behavior on width { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + Keys.onPressed: (event) => { if (event.key === Qt.Key_Escape) { sidebarRoot.hide(); From 1099258d07c360de85ede978e65cb8babbd93462 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 18:14:02 +0200 Subject: [PATCH 636/824] left sidebar: add detach/attach instructions + keybind --- .config/hypr/hyprland/keybinds.conf | 3 ++- .../quickshell/modules/sidebarLeft/AiChat.qml | 14 ++++++++++++-- .../quickshell/modules/sidebarLeft/Anime.qml | 4 ++-- .../modules/sidebarLeft/SidebarLeft.qml | 19 ++++++++++++++----- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index b15bde77a..8feb3c2ca 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -19,8 +19,9 @@ bind = Super, mouse_down,global, quickshell:overviewToggleReleaseInterrupt # [hi bindit = ,Super_L, global, quickshell:workspaceNumber # [hidden] bindd = Super, V, Clipboard history >> clipboard, global, quickshell:overviewClipboardToggle # Clipboard history >> clipboard bindd = Super, Tab, Toggle overview, global, quickshell:overviewToggle # [hidden] Toggle overview/launcher (alt) -bind = Super, B, global, quickshell:sidebarLeftToggle # [hidden] bindd = Super, A, Toggle left sidebar, global, quickshell:sidebarLeftToggle # Toggle left sidebar +bind = Super+Alt, A, global, quickshell:sidebarLeftToggleDetach # [hidden] +bind = Super, B, global, quickshell:sidebarLeftToggle # [hidden] bind = Super, O, global, quickshell:sidebarLeftToggle # [hidden] bindd = Super, N, Toggle right sidebar, global, quickshell:sidebarRightToggle # Toggle right sidebar bindd = Super, Slash, Toggle cheatsheet, global, quickshell:cheatsheetToggle # Toggle cheatsheet diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index a17954e6a..66d994a1d 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -215,18 +215,28 @@ int main(int argc, char* argv[]) { MaterialSymbol { Layout.alignment: Qt.AlignHCenter - iconSize: 55 + iconSize: 60 color: Appearance.m3colors.m3outline text: "neurology" } StyledText { id: widgetNameText Layout.alignment: Qt.AlignHCenter - font.pixelSize: Appearance.font.pixelSize.normal + font.pixelSize: Appearance.font.pixelSize.larger + font.family: Appearance.font.family.title color: Appearance.m3colors.m3outline horizontalAlignment: Text.AlignHCenter text: qsTr("Large language models") } + StyledText { + id: widgetDescriptionText + Layout.fillWidth: true + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.m3colors.m3outline + horizontalAlignment: Text.AlignLeft + wrapMode: Text.Wrap + text: qsTr("Ctrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window") + } } } } diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 8d6c77a3f..1edb8252e 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -205,14 +205,14 @@ Item { MaterialSymbol { Layout.alignment: Qt.AlignHCenter - iconSize: 55 + iconSize: 60 color: Appearance.m3colors.m3outline text: "bookmark_heart" } StyledText { id: widgetNameText Layout.alignment: Qt.AlignHCenter - font.pixelSize: Appearance.font.pixelSize.normal + font.pixelSize: Appearance.font.pixelSize.larger color: Appearance.m3colors.m3outline horizontalAlignment: Text.AlignHCenter text: qsTr("Anime boorus") diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index 80f5e17b0..a62593d75 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -18,7 +18,7 @@ Scope { // Scope property int sidebarPadding: 15 property var tabButtonList: [{"icon": "neurology", "name": qsTr("Intelligence")}, {"icon": "bookmark_heart", "name": qsTr("Anime")}] property int selectedTab: 0 - property bool pin: false + property bool detach: false property Component contentComponent: SidebarLeftContent {} property Item sidebarContent @@ -29,8 +29,8 @@ Scope { // Scope sidebarLoader.item.contentParent.children = [root.sidebarContent]; } - onPinChanged: { - if (root.pin) { + onDetachChanged: { + if (root.detach) { sidebarContent.parent = null; // Detach content from sidebar sidebarLoader.active = false; // Unload sidebar detachedSidebarLoader.active = true; // Load detached window @@ -117,7 +117,7 @@ Scope { // Scope sidebarRoot.extend = !sidebarRoot.extend; } else if (event.key === Qt.Key_P) { - root.pin = !root.pin; + root.detach = !root.detach; } event.accepted = true; } @@ -143,7 +143,7 @@ Scope { // Scope Keys.onPressed: (event) => { if (event.modifiers === Qt.ControlModifier) { if (event.key === Qt.Key_P) { - root.pin = !root.pin; + root.detach = !root.detach; } event.accepted = true; } @@ -195,4 +195,13 @@ Scope { // Scope } } + GlobalShortcut { + name: "sidebarLeftToggleDetach" + description: qsTr("Detach left sidebar into a window/Attach it back") + + onPressed: { + root.detach = !root.detach; + } + } + } From 368c7c3cea39a6ec235747a346d54bb97a72d66f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 20:34:29 +0200 Subject: [PATCH 637/824] Update README.md --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 769670c70..e21992d39 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ # Quickshell-powered illogical-impulse -## Quite usable at this point, see instructions below +## Current status -- **Installation**: Run `install.sh` (Note: If someone does this on a fresh system, please let us know if it works or is missing anything 👉 https://github.com/end-4/dots-hyprland/pull/1276) +It's ready if you don't need localization... so quite likely + +## Instructions + +- **Installation**: Clone the repo, checkout this branch and run `install.sh` - **Dolphin fix** so it won't ask which program to open file with every time: `sudo pacman -S archlinux-xdg-menu && XDG_MENU_PREFIX=arch- kbuildsycoca6; sudo ln -s /etc/xdg/menus/plasma-applications.menu /etc/xdg/menus/applications.menu` - TODO: Update install script to include the above fix From 138069697755710dbcd077cbe20ce747043ea2e1 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 21:33:20 +0200 Subject: [PATCH 638/824] kitty: add search --- .config/kitty/kitty.conf | 21 ++- .config/kitty/scroll_mark.py | 18 ++ .config/kitty/search.py | 341 +++++++++++++++++++++++++++++++++++ 3 files changed, 371 insertions(+), 9 deletions(-) create mode 100644 .config/kitty/scroll_mark.py create mode 100644 .config/kitty/search.py diff --git a/.config/kitty/kitty.conf b/.config/kitty/kitty.conf index ba8ea2ba1..008bbbe98 100644 --- a/.config/kitty/kitty.conf +++ b/.config/kitty/kitty.conf @@ -2,8 +2,9 @@ font_family JetBrains Mono Nerd Font font_size 11.0 -# Cursor shape +# Cursor cursor_shape beam +cursor_trail 1 # Padding (why weird value? consistency with foot) window_margin_width 21.75 @@ -14,20 +15,22 @@ confirm_os_window_close 0 # Use fish shell shell fish -# Copy for normies -map ctrl+c copy_or_interrupt +# Copy +map ctrl+c copy_or_interrupt + +# Search +map ctrl+f launch --location=hsplit --allow-remote-control kitty +kitten search.py @active-kitty-window-id +map kitty_mod+f launch --location=hsplit --allow-remote-control kitty +kitten search.py @active-kitty-window-id + +# Scroll & Zoom +map page_up scroll_page_up +map page_down scroll_page_down -# Zoom map ctrl+plus change_font_size all +1 map ctrl+equal change_font_size all +1 map ctrl+kp_add change_font_size all +1 - map ctrl+minus change_font_size all -1 map ctrl+underscore change_font_size all -1 map ctrl+kp_subtract change_font_size all -1 - map ctrl+0 change_font_size all 0 map ctrl+kp_0 change_font_size all 0 - -# Cursor trail -cursor_trail 1 diff --git a/.config/kitty/scroll_mark.py b/.config/kitty/scroll_mark.py new file mode 100644 index 000000000..0193b20b3 --- /dev/null +++ b/.config/kitty/scroll_mark.py @@ -0,0 +1,18 @@ +from kittens.tui.handler import result_handler +from kitty.boss import Boss + + +def main(args: list[str]) -> None: + pass + + +@result_handler(no_ui=True) +def handle_result( + args: list[str], answer: str, target_window_id: int, boss: Boss +) -> None: + w = boss.window_id_map.get(target_window_id) + if w is not None: + if len(args) > 1 and args[1] != "prev": + w.scroll_to_mark(prev=False) + else: + w.scroll_to_mark() diff --git a/.config/kitty/search.py b/.config/kitty/search.py new file mode 100644 index 000000000..1fd554d1f --- /dev/null +++ b/.config/kitty/search.py @@ -0,0 +1,341 @@ +# Kitty search from https://github.com/trygveaa/kitty-kitten-search +# License: GPLv3 + +import json +import re +import subprocess +from gettext import gettext as _ +from pathlib import Path +from subprocess import PIPE, run + +from kittens.tui.handler import Handler +from kittens.tui.line_edit import LineEdit +from kittens.tui.loop import Loop +from kittens.tui.operations import ( + clear_screen, + cursor, + set_line_wrapping, + set_window_title, + styled, +) +from kitty.config import cached_values_for +from kitty.key_encoding import EventType +from kitty.typing_compat import KeyEventType, ScreenSize + +NON_SPACE_PATTERN = re.compile(r"\S+") +SPACE_PATTERN = re.compile(r"\s+") +SPACE_PATTERN_END = re.compile(r"\s+$") +SPACE_PATTERN_START = re.compile(r"^\s+") + +NON_ALPHANUM_PATTERN = re.compile(r"[^\w\d]+") +NON_ALPHANUM_PATTERN_END = re.compile(r"[^\w\d]+$") +NON_ALPHANUM_PATTERN_START = re.compile(r"^[^\w\d]+") +ALPHANUM_PATTERN = re.compile(r"[\w\d]+") + + +def call_remote_control(args: list[str]) -> None: + subprocess.run(["kitty", "@", *args], capture_output=True) + + +def reindex( + text: str, pattern: re.Pattern[str], right: bool = False +) -> tuple[int, int]: + if not right: + m = pattern.search(text) + else: + matches = [x for x in pattern.finditer(text) if x] + if not matches: + raise ValueError + m = matches[-1] + + if not m: + raise ValueError + + return m.span() + + +SCROLLMARK_FILE = Path(__file__).parent.absolute() / "scroll_mark.py" + + +class Search(Handler): + def __init__( + self, cached_values: dict[str, str], window_ids: list[int], error: str = "" + ) -> None: + self.cached_values = cached_values + self.window_ids = window_ids + self.error = error + self.line_edit = LineEdit() + last_search = cached_values.get("last_search", "") + self.line_edit.add_text(last_search) + self.text_marked = bool(last_search) + self.mode = cached_values.get("mode", "text") + self.update_prompt() + self.mark() + + def update_prompt(self) -> None: + self.prompt = "~> " if self.mode == "regex" else "=> " + + def init_terminal_state(self) -> None: + self.write(set_line_wrapping(False)) + self.write(set_window_title(_("Search"))) + + def initialize(self) -> None: + self.init_terminal_state() + self.draw_screen() + + def draw_screen(self) -> None: + self.write(clear_screen()) + if self.window_ids: + input_text = self.line_edit.current_input + if self.text_marked: + self.line_edit.current_input = styled(input_text, reverse=True) + self.line_edit.write(self.write, self.prompt) + self.line_edit.current_input = input_text + if self.error: + with cursor(self.write): + self.print("") + for l in self.error.split("\n"): + self.print(l) + + def refresh(self) -> None: + self.draw_screen() + self.mark() + + def switch_mode(self) -> None: + if self.mode == "regex": + self.mode = "text" + else: + self.mode = "regex" + self.cached_values["mode"] = self.mode + self.update_prompt() + + def on_text(self, text: str, in_bracketed_paste: bool = False) -> None: + if self.text_marked: + self.text_marked = False + self.line_edit.clear() + self.line_edit.on_text(text, in_bracketed_paste) + self.refresh() + + def on_key(self, key_event: KeyEventType) -> None: + if ( + self.text_marked + and key_event.type == EventType.PRESS + and key_event.key + not in [ + "TAB", + "LEFT_CONTROL", + "RIGHT_CONTROL", + "LEFT_ALT", + "RIGHT_ALT", + "LEFT_SHIFT", + "RIGHT_SHIFT", + "LEFT_SUPER", + "RIGHT_SUPER", + ] + ): + self.text_marked = False + self.refresh() + + if self.line_edit.on_key(key_event): + self.refresh() + return + + if key_event.matches("ctrl+u"): + self.line_edit.clear() + self.refresh() + elif key_event.matches("ctrl+a"): + self.line_edit.home() + self.refresh() + elif key_event.matches("ctrl+e"): + self.line_edit.end() + self.refresh() + elif key_event.matches("ctrl+backspace") or key_event.matches("ctrl+w"): + before, _ = self.line_edit.split_at_cursor() + + try: + start, _ = reindex(before, SPACE_PATTERN_END, right=True) + except ValueError: + start = -1 + + try: + space = before[:start].rindex(" ") + except ValueError: + space = 0 + self.line_edit.backspace(len(before) - space) + self.refresh() + elif key_event.matches("ctrl+left") or key_event.matches("ctrl+b"): + before, _ = self.line_edit.split_at_cursor() + try: + start, _ = reindex(before, SPACE_PATTERN_END, right=True) + except ValueError: + start = -1 + + try: + space = before[:start].rindex(" ") + except ValueError: + space = 0 + self.line_edit.left(len(before) - space) + self.refresh() + elif key_event.matches("ctrl+right") or key_event.matches("ctrl+f"): + _, after = self.line_edit.split_at_cursor() + try: + _, end = reindex(after, SPACE_PATTERN_START) + except ValueError: + end = 0 + + try: + space = after[end:].index(" ") + 1 + except ValueError: + space = len(after) + self.line_edit.right(space) + self.refresh() + elif key_event.matches("alt+backspace") or key_event.matches("alt+w"): + before, _ = self.line_edit.split_at_cursor() + + try: + start, _ = reindex(before, NON_ALPHANUM_PATTERN_END, right=True) + except ValueError: + start = -1 + else: + self.line_edit.backspace(len(before) - start) + self.refresh() + return + + try: + start, _ = reindex(before, NON_ALPHANUM_PATTERN, right=True) + except ValueError: + self.line_edit.backspace(len(before)) + self.refresh() + return + + self.line_edit.backspace(len(before) - (start + 1)) + self.refresh() + elif key_event.matches("alt+left") or key_event.matches("alt+b"): + before, _ = self.line_edit.split_at_cursor() + + try: + start, _ = reindex(before, NON_ALPHANUM_PATTERN_END, right=True) + except ValueError: + start = -1 + else: + self.line_edit.left(len(before) - start) + self.refresh() + return + + try: + start, _ = reindex(before, NON_ALPHANUM_PATTERN, right=True) + except ValueError: + self.line_edit.left(len(before)) + self.refresh() + return + + self.line_edit.left(len(before) - (start + 1)) + self.refresh() + elif key_event.matches("alt+right") or key_event.matches("alt+f"): + _, after = self.line_edit.split_at_cursor() + + try: + _, end = reindex(after, NON_ALPHANUM_PATTERN_START) + except ValueError: + end = 0 + else: + self.line_edit.right(end) + self.refresh() + return + + try: + _, end = reindex(after, NON_ALPHANUM_PATTERN) + except ValueError: + self.line_edit.right(len(after)) + self.refresh() + return + + self.line_edit.right(end - 1) + self.refresh() + elif key_event.matches("tab"): + self.switch_mode() + self.refresh() + elif key_event.matches("up") or key_event.matches("f3"): + for match_arg in self.match_args(): + call_remote_control(["kitten", match_arg, str(SCROLLMARK_FILE)]) + elif key_event.matches("down") or key_event.matches("shift+f3"): + for match_arg in self.match_args(): + call_remote_control(["kitten", match_arg, str(SCROLLMARK_FILE), "next"]) + elif key_event.matches("enter"): + self.quit(0) + elif key_event.matches("esc"): + self.quit(1) + + def on_interrupt(self) -> None: + self.quit(1) + + def on_eot(self) -> None: + self.quit(1) + + def on_resize(self, screen_size: ScreenSize) -> None: + self.refresh() + + def match_args(self) -> list[str]: + return [f"--match=id:{window_id}" for window_id in self.window_ids] + + def mark(self) -> None: + if not self.window_ids: + return + text = self.line_edit.current_input + if text: + match_case = "i" if text.islower() else "" + match_type = match_case + self.mode + for match_arg in self.match_args(): + try: + call_remote_control( + ["create-marker", match_arg, match_type, "1", text] + ) + except SystemExit: + self.remove_mark() + else: + self.remove_mark() + + def remove_mark(self) -> None: + for match_arg in self.match_args(): + call_remote_control(["remove-marker", match_arg]) + + def quit(self, return_code: int) -> None: + self.cached_values["last_search"] = self.line_edit.current_input + self.remove_mark() + if return_code: + for match_arg in self.match_args(): + call_remote_control(["scroll-window", match_arg, "end"]) + self.quit_loop(return_code) + + +def main(args: list[str]) -> None: + call_remote_control( + ["resize-window", "--self", "--axis=vertical", "--increment", "-100"] + ) + + error = "" + if len(args) < 2 or not args[1].isdigit(): + error = "Error: Window id must be provided as the first argument." + + window_id = int(args[1]) + window_ids = [window_id] + if len(args) > 2 and args[2] == "--all-windows": + ls_output = run(["kitty", "@", "ls"], stdout=PIPE) + ls_json = json.loads(ls_output.stdout.decode()) + current_tab = None + for os_window in ls_json: + for tab in os_window["tabs"]: + for kitty_window in tab["windows"]: + if kitty_window["id"] == window_id: + current_tab = tab + if current_tab: + window_ids = [ + w["id"] for w in current_tab["windows"] if not w["is_focused"] + ] + else: + error = "Error: Could not find the window id provided." + + loop = Loop() + with cached_values_for("search") as cached_values: + handler = Search(cached_values, window_ids, error) + loop.loop(handler) From 31d06277e1b0f97973b9b816bb4a7d51da9887dc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 22:32:24 +0200 Subject: [PATCH 639/824] hyprlock: add background blur --- .config/matugen/templates/hyprland/hyprlock.conf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.config/matugen/templates/hyprland/hyprlock.conf b/.config/matugen/templates/hyprland/hyprlock.conf index 2f6331607..eab167199 100644 --- a/.config/matugen/templates/hyprland/hyprlock.conf +++ b/.config/matugen/templates/hyprland/hyprlock.conf @@ -8,11 +8,11 @@ $font_material_symbols = Material Symbols Rounded background { color = rgba(181818FF) - # path = {{image}} - # path = screenshot - # blur_size = 15 - # blur_passes = 4 + path = {{image}} + blur_size = 15 + blur_passes = 4 + brightness = 0.33 } input-field { monitor = From 751e5ca5433d32cd53637c87443f3c7ef902e807 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 1 Jun 2025 23:45:51 +0200 Subject: [PATCH 640/824] relocate scripts --- .config/hypr/hyprland/keybinds.conf | 52 +++++++++---------- .../hypr/hyprland/scripts/fuzzel-emoji.sh | 0 .../{record-script.sh => scripts/record.sh} | 0 .../{ => scripts}/workspace_action.sh | 0 .config/hypr/hyprland/{ => scripts}/zoom.sh | 0 5 files changed, 26 insertions(+), 26 deletions(-) rename .local/bin/fuzzel-emoji => .config/hypr/hyprland/scripts/fuzzel-emoji.sh (100%) rename .config/hypr/hyprland/{record-script.sh => scripts/record.sh} (100%) rename .config/hypr/hyprland/{ => scripts}/workspace_action.sh (100%) rename .config/hypr/hyprland/{ => scripts}/zoom.sh (100%) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 8feb3c2ca..3d550762f 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -52,7 +52,7 @@ bindd = Ctrl+Shift+Alt+Super, Delete, Shutdown, exec, systemctl poweroff || logi ##! Utilities # Screenshot, Record, OCR, Color picker, Clipboard history bindd = Super, V, Copy clipboard history entry, exec, qs ipc call TEST_ALIVE || pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # Clipboard history >> clipboard -bindd = Super, Period, Copy an emoji, exec, pkill fuzzel || ~/.local/bin/fuzzel-emoji copy # Emoji +bindd = Super, Period, Copy an emoji, exec, pkill fuzzel || ~/.config/hypr/hyprland/scripts/fuzzel-emoji.sh copy # Emoji bindd = Super+Shift, S, Screen snip, exec, grimblast --freeze copy area # Screen snip >> clipboard bindd = Super+Shift+Alt, S, Screen snip and annotate, exec, grim -g "$(slurp)" - | swappy -f - # Screen snip and annotate # OCR @@ -63,14 +63,14 @@ bindd = Super+Shift, C, Color picker, exec, hyprpicker -a # Pick color (Hex) >> bindld = ,Print, Screenshot >> clipboard ,exec,grim - | wl-copy # Screenshot >> clipboard bindld = Ctrl,Print, Screenshot >> clipboard & save, exec, mkdir -p ~/Pictures/Screenshots && grimblast copysave screen ~/Pictures/Screenshots/Screenshot_"$(date '+%Y-%m-%d_%H.%M.%S')".png # Screenshot >> clipboard & file # Recording stuff -bindd = Super+Alt, R, Record region (no sound), exec, ~/.config/hypr/hyprland/record-script.sh # Record region (no sound) -bindd = Ctrl+Alt, R, Record screen (no sound), exec, ~/.config/hypr/hyprland/record-script.sh --fullscreen # [hidden] Record screen (no sound) -bindd = Super+Shift+Alt, R, Record screen (with sound), exec, ~/.config/hypr/hyprland/record-script.sh --fullscreen-sound # Record screen (with sound) +bindd = Super+Alt, R, Record region (no sound), exec, ~/.config/hypr/hyprland/scripts/record.sh # Record region (no sound) +bindd = Ctrl+Alt, R, Record screen (no sound), exec, ~/.config/hypr/hyprland/scripts/record.sh --fullscreen # [hidden] Record screen (no sound) +bindd = Super+Shift+Alt, R, Record screen (with sound), exec, ~/.config/hypr/hyprland/scripts/record.sh --fullscreen-sound # Record screen (with sound) # AI bindd = Super+Shift+Alt, mouse:273, Generate AI summary for selected text, exec, ~/.config/ags/scripts/ai/primary-buffer-query.sh # AI summary for selected text # Zoom -binde = Super, Minus, exec, ~/.config/hypr/hyprland/zoom.sh decrease 0.1 -binde = Super, Equal, exec, ~/.config/hypr/hyprland/zoom.sh increase 0.1 +binde = Super, Minus, exec, ~/.config/hypr/hyprland/scripts/zoom.sh decrease 0.1 +binde = Super, Equal, exec, ~/.config/hypr/hyprland/scripts/zoom.sh increase 0.1 #! ##! Window @@ -107,16 +107,16 @@ bind = Super+Alt, F, fullscreenstate, 0 3 # Fullscreen spoof bind = Super, P, pin # Pin #/# bind = Super+Alt, Hash,, # Send to workspace # (1, 2, 3,...) -bind = Super+Alt, 1, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 1 # [hidden] -bind = Super+Alt, 2, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 2 # [hidden] -bind = Super+Alt, 3, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 3 # [hidden] -bind = Super+Alt, 4, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 4 # [hidden] -bind = Super+Alt, 5, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 5 # [hidden] -bind = Super+Alt, 6, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 6 # [hidden] -bind = Super+Alt, 7, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 7 # [hidden] -bind = Super+Alt, 8, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 8 # [hidden] -bind = Super+Alt, 9, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 9 # [hidden] -bind = Super+Alt, 0, exec, ~/.config/hypr/hyprland/workspace_action.sh movetoworkspacesilent 10 # [hidden] +bind = Super+Alt, 1, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh movetoworkspacesilent 1 # [hidden] +bind = Super+Alt, 2, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh movetoworkspacesilent 2 # [hidden] +bind = Super+Alt, 3, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh movetoworkspacesilent 3 # [hidden] +bind = Super+Alt, 4, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh movetoworkspacesilent 4 # [hidden] +bind = Super+Alt, 5, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh movetoworkspacesilent 5 # [hidden] +bind = Super+Alt, 6, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh movetoworkspacesilent 6 # [hidden] +bind = Super+Alt, 7, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh movetoworkspacesilent 7 # [hidden] +bind = Super+Alt, 8, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh movetoworkspacesilent 8 # [hidden] +bind = Super+Alt, 9, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh movetoworkspacesilent 9 # [hidden] +bind = Super+Alt, 0, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh movetoworkspacesilent 10 # [hidden] # #/# bind = Super+Shift, Scroll ↑/↓,, # Send to workspace left/right bind = Super+Shift, mouse_down, movetoworkspace, r-1 # [hidden] @@ -141,16 +141,16 @@ bind = Alt, Tab, bringactivetotop, # [hidden] bring it to the top ##! Workspace # Switching #/# bind = Super, Hash,, # Focus workspace # (1, 2, 3,...) -bind = Super, 1, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 1 # [hidden] -bind = Super, 2, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 2 # [hidden] -bind = Super, 3, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 3 # [hidden] -bind = Super, 4, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 4 # [hidden] -bind = Super, 5, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 5 # [hidden] -bind = Super, 6, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 6 # [hidden] -bind = Super, 7, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 7 # [hidden] -bind = Super, 8, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 8 # [hidden] -bind = Super, 9, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 9 # [hidden] -bind = Super, 0, exec, ~/.config/hypr/hyprland/workspace_action.sh workspace 10 # [hidden] +bind = Super, 1, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh workspace 1 # [hidden] +bind = Super, 2, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh workspace 2 # [hidden] +bind = Super, 3, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh workspace 3 # [hidden] +bind = Super, 4, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh workspace 4 # [hidden] +bind = Super, 5, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh workspace 5 # [hidden] +bind = Super, 6, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh workspace 6 # [hidden] +bind = Super, 7, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh workspace 7 # [hidden] +bind = Super, 8, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh workspace 8 # [hidden] +bind = Super, 9, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh workspace 9 # [hidden] +bind = Super, 0, exec, ~/.config/hypr/hyprland/scripts/workspace_action.sh workspace 10 # [hidden] #/# bind = Ctrl+Super, ←/→,, # Focus left/right bind = Ctrl+Super, Right, workspace, r+1 # [hidden] diff --git a/.local/bin/fuzzel-emoji b/.config/hypr/hyprland/scripts/fuzzel-emoji.sh similarity index 100% rename from .local/bin/fuzzel-emoji rename to .config/hypr/hyprland/scripts/fuzzel-emoji.sh diff --git a/.config/hypr/hyprland/record-script.sh b/.config/hypr/hyprland/scripts/record.sh similarity index 100% rename from .config/hypr/hyprland/record-script.sh rename to .config/hypr/hyprland/scripts/record.sh diff --git a/.config/hypr/hyprland/workspace_action.sh b/.config/hypr/hyprland/scripts/workspace_action.sh similarity index 100% rename from .config/hypr/hyprland/workspace_action.sh rename to .config/hypr/hyprland/scripts/workspace_action.sh diff --git a/.config/hypr/hyprland/zoom.sh b/.config/hypr/hyprland/scripts/zoom.sh similarity index 100% rename from .config/hypr/hyprland/zoom.sh rename to .config/hypr/hyprland/scripts/zoom.sh From c940b72776a2c778e6576ecadf45f7d9ac90be4d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 2 Jun 2025 00:09:47 +0200 Subject: [PATCH 641/824] quickshell emoji picker --- .config/hypr/hyprland/keybinds.conf | 3 +- .../modules/common/ConfigOptions.qml | 3 +- .../quickshell/modules/overview/Overview.qml | 23 +++++++ .../modules/overview/SearchItem.qml | 11 ++++ .../modules/overview/SearchWidget.qml | 17 ++++- .config/quickshell/services/Cliphist.qml | 1 - .config/quickshell/services/Emojis.qml | 65 +++++++++++++++++++ 7 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 .config/quickshell/services/Emojis.qml diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 3d550762f..2b6f7da02 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -18,6 +18,7 @@ bind = Super, mouse_down,global, quickshell:overviewToggleReleaseInterrupt # [hi bindit = ,Super_L, global, quickshell:workspaceNumber # [hidden] bindd = Super, V, Clipboard history >> clipboard, global, quickshell:overviewClipboardToggle # Clipboard history >> clipboard +bindd = Super, Period, Clipboard history >> clipboard, global, quickshell:overviewEmojiToggle # Emoji >> clipboard bindd = Super, Tab, Toggle overview, global, quickshell:overviewToggle # [hidden] Toggle overview/launcher (alt) bindd = Super, A, Toggle left sidebar, global, quickshell:sidebarLeftToggle # Toggle left sidebar bind = Super+Alt, A, global, quickshell:sidebarLeftToggleDetach # [hidden] @@ -52,7 +53,7 @@ bindd = Ctrl+Shift+Alt+Super, Delete, Shutdown, exec, systemctl poweroff || logi ##! Utilities # Screenshot, Record, OCR, Color picker, Clipboard history bindd = Super, V, Copy clipboard history entry, exec, qs ipc call TEST_ALIVE || pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # Clipboard history >> clipboard -bindd = Super, Period, Copy an emoji, exec, pkill fuzzel || ~/.config/hypr/hyprland/scripts/fuzzel-emoji.sh copy # Emoji +bindd = Super, Period, Copy an emoji, exec, qs ipc call TEST_ALIVE || pkill fuzzel || ~/.config/hypr/hyprland/scripts/fuzzel-emoji.sh copy # [hidden] Emoji >> clipboard bindd = Super+Shift, S, Screen snip, exec, grimblast --freeze copy area # Screen snip >> clipboard bindd = Super+Shift+Alt, S, Screen snip and annotate, exec, grim -g "$(slurp)" - | swappy -f - # Screen snip and annotate # OCR diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index ad4ccf4df..c50f5613f 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -84,7 +84,8 @@ Singleton { property bool sloppy: false // Uses levenshtein distance based scoring instead of fuzzy sort. Very weird. property QtObject prefix: QtObject { property string action: "/" - property string clipboard: ":" + property string clipboard: ";" + property string emojis: ":" } } diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index fe7ba1e52..6fa85e891 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -210,4 +210,27 @@ Scope { } } + GlobalShortcut { + name: "overviewEmojiToggle" + description: qsTr("Toggle emoji query on overview widget") + + onPressed: { + if (GlobalStates.overviewOpen && overviewScope.dontAutoCancelSearch) { + GlobalStates.overviewOpen = false; + return; + } + for (let i = 0; i < overviewVariants.instances.length; i++) { + let panelWindow = overviewVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + overviewScope.dontAutoCancelSearch = true; + panelWindow.setSearchingText( + ConfigOptions.search.prefix.emojis + ); + GlobalStates.overviewOpen = true; + return + } + } + } + } + } diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index e76067c49..00d5da743 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -24,6 +24,7 @@ RippleButton { property var itemExecute: entry?.execute property string fontType: entry?.fontType ?? "main" property string itemClickActionName: entry?.clickActionName + property string bigText: entry?.bigText ?? "" property string materialSymbol: entry?.materialSymbol ?? "" property string cliphistRawString: entry?.cliphistRawString ?? "" @@ -120,6 +121,7 @@ RippleButton { id: iconLoader active: true sourceComponent: root.materialSymbol !== "" ? materialSymbolComponent : + root.bigText ? bigTextComponent : root.itemIcon !== "" ? iconImageComponent : null } @@ -142,6 +144,15 @@ RippleButton { } } + Component { + id: bigTextComponent + StyledText { + text: root.bigText + font.pixelSize: Appearance.font.pixelSize.larger + color: Appearance.m3colors.m3onSurface + } + } + // Main text ColumnLayout { id: contentColumn diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index df3972944..88b6f70f9 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -302,7 +302,22 @@ Item { // Wrapper } }; }).filter(Boolean); - } + } + if (root.searchingText.startsWith(ConfigOptions.search.prefix.emojis)) { // Clipboard + const searchString = root.searchingText.slice(ConfigOptions.search.prefix.emojis.length); + return Emojis.fuzzyQuery(searchString).map(entry => { + return { + cliphistRawString: entry, + bigText: entry.match(/^\s*(\S+)/)?.[1] || "", + name: entry.replace(/^\s*\S+\s+/, ""), + clickActionName: "", + type: "Emoji", + execute: () => { + Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(entry.match(/^\s*(\S+)/)?.[1])}'`); + } + }; + }).filter(Boolean); + } ////////////////// Init /////////////////// diff --git a/.config/quickshell/services/Cliphist.qml b/.config/quickshell/services/Cliphist.qml index d0193f144..bebafb102 100644 --- a/.config/quickshell/services/Cliphist.qml +++ b/.config/quickshell/services/Cliphist.qml @@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound import "root:/modules/common/functions/fuzzysort.js" as Fuzzy import "root:/modules/common/functions/levendist.js" as Levendist -import "root:/modules/common/functions/string_utils.js" as StringUtils import "root:/modules/common" import "root:/" import QtQuick diff --git a/.config/quickshell/services/Emojis.qml b/.config/quickshell/services/Emojis.qml new file mode 100644 index 000000000..852c831b5 --- /dev/null +++ b/.config/quickshell/services/Emojis.qml @@ -0,0 +1,65 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common/functions/fuzzysort.js" as Fuzzy +import "root:/modules/common/functions/levendist.js" as Levendist +import "root:/modules/common" +import QtQuick +import Quickshell +import Quickshell.Io + +/** + * Emojis. + */ +Singleton { + id: root + property string emojiScriptPath: `${Directories.config}/hypr/hyprland/scripts/fuzzel-emoji.sh` + property string lineBeforeData: "### DATA ###" + property list list + readonly property var preparedEntries: list.map(a => ({ + name: Fuzzy.prepare(`${a}`), + entry: a + })) + function fuzzyQuery(search: string): var { + if (root.sloppySearch) { + const results = entries.slice(0, 100).map(str => ({ + entry: str, + score: Levendist.computeTextMatchScore(str.toLowerCase(), search.toLowerCase()) + })).filter(item => item.score > root.scoreThreshold) + .sort((a, b) => b.score - a.score) + return results + .map(item => item.entry) + } + + return Fuzzy.go(search, preparedEntries, { + all: true, + key: "name" + }).map(r => { + return r.obj.entry + }); + } + + function load() { + emojiFileView.reload() + } + + function updateEmojis(fileContent) { + const lines = fileContent.split("\n") + const dataIndex = lines.indexOf(root.lineBeforeData) + if (dataIndex === -1) { + console.warn("No data section found in emoji script file.") + return + } + const emojis = lines.slice(dataIndex + 1).filter(line => line.trim() !== "") + root.list = emojis.map(line => line.trim()) + } + + FileView { + id: emojiFileView + path: Qt.resolvedUrl(root.emojiScriptPath) + onLoadedChanged: { + const fileContent = emojiFileView.text() + root.updateEmojis(fileContent) + } + } +} From 7d749f16c5b90634ea3de217cf4b7fb32374d3fe Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 2 Jun 2025 00:10:02 +0200 Subject: [PATCH 642/824] Update fuzzel-emoji.sh --- .config/hypr/hyprland/scripts/fuzzel-emoji.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/hypr/hyprland/scripts/fuzzel-emoji.sh b/.config/hypr/hyprland/scripts/fuzzel-emoji.sh index a526a4646..7a1b9e1b4 100755 --- a/.config/hypr/hyprland/scripts/fuzzel-emoji.sh +++ b/.config/hypr/hyprland/scripts/fuzzel-emoji.sh @@ -1878,7 +1878,7 @@ exit → right arrow ↓ down arrow ←↑→↓ all directions up down left right arrows -AH↗️HA↘️HA↗️HA↘️HA↗️HA↘️HA↗️HA↘️ pekora arrows hahaha rabbit +AH↗️HA↘️HA↗️HA↘️ pekora arrows hahaha rabbit • dot circle separator 「」 japanese quote square bracket ¯\_(ツ)_/¯ shrug idk i dont know From 209cfc131a97c9b637b7b26130615ec4a4e46ee7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 2 Jun 2025 08:31:37 +0200 Subject: [PATCH 643/824] nuke ags config --- .../ags/assets/icons/ai-openai-symbolic.svg | 1 - .../ags/assets/icons/ai-oxygen-symbolic.svg | 54 - .config/ags/assets/icons/ai-zukijourney.png | Bin 312911 -> 0 bytes .config/ags/assets/icons/arch-symbolic.svg | 113 -- .config/ags/assets/icons/cachyos-symbolic.svg | 318 ----- .../assets/icons/cloudflare-dns-symbolic.svg | 10 - .../ags/assets/icons/crosshair-symbolic.svg | 65 - .config/ags/assets/icons/debian-symbolic.svg | 91 -- .../ags/assets/icons/deepseek-symbolic.svg | 47 - .config/ags/assets/icons/desktop-symbolic.svg | 4 - .../ags/assets/icons/endeavouros-symbolic.svg | 96 -- .config/ags/assets/icons/fedora-symbolic.svg | 38 - .config/ags/assets/icons/flatpak-symbolic.svg | 52 - .config/ags/assets/icons/github-symbolic.svg | 40 - .../assets/icons/google-gemini-symbolic.svg | 56 - .config/ags/assets/icons/linux-symbolic.svg | 113 -- .../ags/assets/icons/microsoft-symbolic.svg | 54 - .config/ags/assets/icons/nixos-symbolic.svg | 77 -- .config/ags/assets/icons/ollama-symbolic.svg | 60 - .config/ags/assets/icons/openai-symbolic.svg | 38 - .../ags/assets/icons/openrouter-symbolic.svg | 39 - .config/ags/assets/icons/ubuntu-symbolic.svg | 85 -- .../ags/assets/images/default_wallpaper.png | Bin 67685 -> 0 bytes .../sourceviewtheme-dark-monokai-license.txt | 339 ----- .../assets/themes/sourceviewtheme-light.xml | 95 -- .config/ags/assets/themes/sourceviewtheme.xml | 121 -- .config/ags/config.js | 81 -- .config/ags/config_overviewOnly.js | 24 - .config/ags/i18n/i18n.js | 55 - .config/ags/i18n/locales/Default.json | 250 ---- .config/ags/i18n/locales/fa_IR.json | 238 ---- .config/ags/i18n/locales/fr_FR.json | 240 ---- .config/ags/i18n/locales/it_IT.json | 238 ---- .config/ags/i18n/locales/zh_CN.json | 239 ---- .config/ags/init.js | 53 - .../ags/modules/.commondata/hyprlanddata.js | 33 - .config/ags/modules/.commondata/quotes.js | 14 - .config/ags/modules/.commondata/weather.js | 94 -- .../.commonwidgets/cairo_circularprogress.js | 106 -- .../cairo_navigationindicator.js | 71 - .../.commonwidgets/cairo_roundedcorner.js | 50 - .../modules/.commonwidgets/cairo_slider.js | 49 - .../.commonwidgets/clickcloseregion.js | 23 - .../modules/.commonwidgets/configwidgets.js | 298 ----- .../.commonwidgets/configwidgets_apps.js | 154 --- .../modules/.commonwidgets/materialicon.js | 7 - .../modules/.commonwidgets/notification.js | 505 -------- .../ags/modules/.commonwidgets/statusicons.js | 316 ----- .../.commonwidgets/statusicons_languages.js | 62 - .../modules/.commonwidgets/tabcontainer.js | 299 ----- .../.configuration/default_options.jsonc | 291 ----- .../modules/.configuration/user_options.js | 36 - .config/ags/modules/.miscutils/files.js | 14 - .config/ags/modules/.miscutils/icons.js | 28 - .config/ags/modules/.miscutils/jsonc.js | 58 - .config/ags/modules/.miscutils/mathfuncs.js | 16 - .config/ags/modules/.miscutils/md2pango.js | 98 -- .config/ags/modules/.miscutils/objects.js | 33 - .config/ags/modules/.miscutils/system.js | 61 - .../modules/.widgethacks/advancedrevealers.js | 86 -- .../ags/modules/.widgethacks/popupwindow.js | 36 - .../ags/modules/.widgetutils/clickthrough.js | 4 - .../ags/modules/.widgetutils/cursorhover.js | 36 - .config/ags/modules/.widgetutils/keybind.js | 34 - .../modules/bar/focus/workspaces_hyprland.js | 213 --- .../ags/modules/bar/focus/workspaces_sway.js | 183 --- .config/ags/modules/bar/main.js | 129 -- .config/ags/modules/bar/normal/music.js | 241 ---- .config/ags/modules/bar/normal/spaceleft.js | 96 -- .config/ags/modules/bar/normal/spaceright.js | 106 -- .config/ags/modules/bar/normal/system.js | 238 ---- .config/ags/modules/bar/normal/tray.js | 36 - .../modules/bar/normal/workspaces_hyprland.js | 224 ---- .../ags/modules/bar/normal/workspaces_sway.js | 183 --- .../ags/modules/cheatsheet/data_keybinds.js | 122 -- .../modules/cheatsheet/data_periodictable.js | 195 --- .config/ags/modules/cheatsheet/keybinds.js | 126 -- .config/ags/modules/cheatsheet/main.js | 146 --- .../ags/modules/cheatsheet/periodictable.js | 94 -- .config/ags/modules/crosshair/main.js | 21 - .../desktopbackground/data_quicklaunches.js | 14 - .config/ags/modules/desktopbackground/main.js | 24 - .../ags/modules/desktopbackground/system.js | 161 --- .../desktopbackground/timeandlaunches.js | 74 -- .../modules/desktopbackground/wallpaper.js | 119 -- .config/ags/modules/dock/dock.js | 315 ----- .config/ags/modules/dock/icons.js | 63 - .config/ags/modules/dock/main.js | 12 - .config/ags/modules/indicators/colorscheme.js | 236 ---- .../ags/modules/indicators/indicatorvalues.js | 133 -- .config/ags/modules/indicators/main.js | 32 - .../ags/modules/indicators/musiccontrols.js | 418 ------ .../modules/indicators/notificationpopups.js | 45 - .../onscreenkeyboard/data_keyboardlayouts.js | 218 ---- .config/ags/modules/onscreenkeyboard/main.js | 11 - .../onscreenkeyboard/onscreenkeyboard.js | 267 ---- .config/ags/modules/overview/actions.js | 28 - .config/ags/modules/overview/main.js | 28 - .config/ags/modules/overview/miscfunctions.js | 146 --- .../ags/modules/overview/overview_hyprland.js | 446 ------- .config/ags/modules/overview/searchbuttons.js | 189 --- .config/ags/modules/overview/searchitem.js | 65 - .config/ags/modules/overview/windowcontent.js | 213 --- .config/ags/modules/screencorners/main.js | 38 - .config/ags/modules/session/main.js | 14 - .config/ags/modules/session/sessionscreen.js | 134 -- .../modules/sideleft/apis/ai_chatmessage.js | 491 ------- .config/ags/modules/sideleft/apis/booru.js | 546 -------- .config/ags/modules/sideleft/apis/chatgpt.js | 377 ------ .config/ags/modules/sideleft/apis/gemini.js | 296 ----- .config/ags/modules/sideleft/apis/waifu.js | 418 ------ .config/ags/modules/sideleft/apiwidgets.js | 243 ---- .config/ags/modules/sideleft/main.js | 18 - .config/ags/modules/sideleft/sideleft.js | 158 --- .config/ags/modules/sideleft/toolbox.js | 22 - .../ags/modules/sideleft/tools/changeres.sh | 99 -- .config/ags/modules/sideleft/tools/color.js | 198 --- .../ags/modules/sideleft/tools/colorpicker.js | 283 ---- .../ags/modules/sideleft/tools/conversions.js | 169 --- .config/ags/modules/sideleft/tools/module.js | 57 - .config/ags/modules/sideleft/tools/name.js | 26 - .../modules/sideleft/tools/quickscripts.js | 103 -- .config/ags/modules/sideright/calendar.js | 273 ---- .../ags/modules/sideright/calendar_layout.js | 85 -- .../sideright/centermodules/audiocontrols.js | 222 ---- .../sideright/centermodules/bluetooth.js | 164 --- .../sideright/centermodules/configure.js | 104 -- .../centermodules/notificationlist.js | 181 --- .../sideright/centermodules/wifinetworks.js | 421 ------ .config/ags/modules/sideright/main.js | 18 - .config/ags/modules/sideright/quicktoggles.js | 296 ----- .config/ags/modules/sideright/sideright.js | 192 --- .config/ags/modules/sideright/todolist.js | 224 ---- .config/ags/scripts/README.md | 3 - .config/ags/scripts/ags/agsconfigurator.py | 137 -- .../ai/license_show-loaded-ollama-models.txt | 201 --- .../ags/scripts/ai/primary-buffer-query.sh | 41 - .../ai/show-installed-ollama-models.sh | 16 - .../scripts/ai/show-loaded-ollama-models.sh | 99 -- .config/ags/scripts/hyprland/get_keybinds.py | 222 ---- .../ags/scripts/hyprland/hyprconfigurator.py | 51 - .../ags/scripts/hyprland/workspace_action.sh | 2 - .../network_scripts/network_bandwidth.py | 40 - .../quickscripts/nixos-trim-generations.sh | 243 ---- .config/ags/scripts/sway/swayToRelativeWs.sh | 30 - .../templates/ags/sourceviewtheme-light.xml | 95 -- .../scripts/templates/ags/sourceviewtheme.xml | 121 -- .../ags/scripts/templates/fuzzel/fuzzel.ini | 21 - .../scripts/templates/gradience/preset.json | 144 -- .../ags/scripts/templates/gtk/gtk-colors.css | 21 - .../templates/hypr/hyprland/colors.conf | 34 - .../ags/scripts/templates/hypr/hyprlock.conf | 94 -- .../templates/terminal/scheme-base.json | 38 - .../templates/terminal/scheme-monochrome.json | 36 - .../scripts/templates/terminal/sequences.txt | 1 - .../ags/scripts/templates/wal/_musicwal.scss | 22 - .config/ags/scripts/wayland-idle-inhibitor.py | 82 -- .config/ags/scss/_bar.scss | 437 ------- .config/ags/scss/_cheatsheet.scss | 168 --- .config/ags/scss/_colors.scss | 99 -- .config/ags/scss/_common.scss | 380 ------ .config/ags/scss/_desktopbackground.scss | 92 -- .config/ags/scss/_dock.scss | 42 - .config/ags/scss/_lib_classes.scss | 515 -------- .config/ags/scss/_lib_mixins.scss | 187 --- .config/ags/scss/_music.scss | 159 --- .config/ags/scss/_notifications.scss | 255 ---- .config/ags/scss/_osd.scss | 202 --- .config/ags/scss/_osk.scss | 119 -- .config/ags/scss/_overview.scss | 139 -- .config/ags/scss/_session.scss | 40 - .config/ags/scss/_sidebars.scss | 1154 ----------------- .config/ags/scss/_wal.scss | 8 - .config/ags/scss/fallback/_material.scss | 76 -- .config/ags/scss/main.scss | 42 - .config/ags/services/booru.js | 156 --- .config/ags/services/brightness.js | 170 --- .config/ags/services/darkmode.js | 39 - .config/ags/services/gemini.js | 335 ----- .config/ags/services/gpt.js | 337 ----- .config/ags/services/indicator.js | 38 - .config/ags/services/messages.js | 62 - .config/ags/services/sway.js | 400 ------ .config/ags/services/todo.js | 83 -- .config/ags/services/waifus.js | 150 --- .config/ags/services/wallpaper.js | 70 - .config/ags/user_options.jsonc | 19 - .config/ags/variables.js | 84 -- 188 files changed, 25667 deletions(-) delete mode 120000 .config/ags/assets/icons/ai-openai-symbolic.svg delete mode 100644 .config/ags/assets/icons/ai-oxygen-symbolic.svg delete mode 100644 .config/ags/assets/icons/ai-zukijourney.png delete mode 100644 .config/ags/assets/icons/arch-symbolic.svg delete mode 100644 .config/ags/assets/icons/cachyos-symbolic.svg delete mode 100644 .config/ags/assets/icons/cloudflare-dns-symbolic.svg delete mode 100644 .config/ags/assets/icons/crosshair-symbolic.svg delete mode 100644 .config/ags/assets/icons/debian-symbolic.svg delete mode 100644 .config/ags/assets/icons/deepseek-symbolic.svg delete mode 100644 .config/ags/assets/icons/desktop-symbolic.svg delete mode 100644 .config/ags/assets/icons/endeavouros-symbolic.svg delete mode 100644 .config/ags/assets/icons/fedora-symbolic.svg delete mode 100644 .config/ags/assets/icons/flatpak-symbolic.svg delete mode 100644 .config/ags/assets/icons/github-symbolic.svg delete mode 100644 .config/ags/assets/icons/google-gemini-symbolic.svg delete mode 100644 .config/ags/assets/icons/linux-symbolic.svg delete mode 100644 .config/ags/assets/icons/microsoft-symbolic.svg delete mode 100644 .config/ags/assets/icons/nixos-symbolic.svg delete mode 100644 .config/ags/assets/icons/ollama-symbolic.svg delete mode 100644 .config/ags/assets/icons/openai-symbolic.svg delete mode 100644 .config/ags/assets/icons/openrouter-symbolic.svg delete mode 100644 .config/ags/assets/icons/ubuntu-symbolic.svg delete mode 100644 .config/ags/assets/images/default_wallpaper.png delete mode 100644 .config/ags/assets/themes/sourceviewtheme-dark-monokai-license.txt delete mode 100644 .config/ags/assets/themes/sourceviewtheme-light.xml delete mode 100644 .config/ags/assets/themes/sourceviewtheme.xml delete mode 100644 .config/ags/config.js delete mode 100644 .config/ags/config_overviewOnly.js delete mode 100644 .config/ags/i18n/i18n.js delete mode 100644 .config/ags/i18n/locales/Default.json delete mode 100644 .config/ags/i18n/locales/fa_IR.json delete mode 100644 .config/ags/i18n/locales/fr_FR.json delete mode 100644 .config/ags/i18n/locales/it_IT.json delete mode 100644 .config/ags/i18n/locales/zh_CN.json delete mode 100644 .config/ags/init.js delete mode 100644 .config/ags/modules/.commondata/hyprlanddata.js delete mode 100644 .config/ags/modules/.commondata/quotes.js delete mode 100644 .config/ags/modules/.commondata/weather.js delete mode 100644 .config/ags/modules/.commonwidgets/cairo_circularprogress.js delete mode 100644 .config/ags/modules/.commonwidgets/cairo_navigationindicator.js delete mode 100644 .config/ags/modules/.commonwidgets/cairo_roundedcorner.js delete mode 100644 .config/ags/modules/.commonwidgets/cairo_slider.js delete mode 100644 .config/ags/modules/.commonwidgets/clickcloseregion.js delete mode 100644 .config/ags/modules/.commonwidgets/configwidgets.js delete mode 100644 .config/ags/modules/.commonwidgets/configwidgets_apps.js delete mode 100644 .config/ags/modules/.commonwidgets/materialicon.js delete mode 100644 .config/ags/modules/.commonwidgets/notification.js delete mode 100644 .config/ags/modules/.commonwidgets/statusicons.js delete mode 100644 .config/ags/modules/.commonwidgets/statusicons_languages.js delete mode 100644 .config/ags/modules/.commonwidgets/tabcontainer.js delete mode 100644 .config/ags/modules/.configuration/default_options.jsonc delete mode 100644 .config/ags/modules/.configuration/user_options.js delete mode 100644 .config/ags/modules/.miscutils/files.js delete mode 100644 .config/ags/modules/.miscutils/icons.js delete mode 100644 .config/ags/modules/.miscutils/jsonc.js delete mode 100644 .config/ags/modules/.miscutils/mathfuncs.js delete mode 100644 .config/ags/modules/.miscutils/md2pango.js delete mode 100644 .config/ags/modules/.miscutils/objects.js delete mode 100644 .config/ags/modules/.miscutils/system.js delete mode 100644 .config/ags/modules/.widgethacks/advancedrevealers.js delete mode 100644 .config/ags/modules/.widgethacks/popupwindow.js delete mode 100644 .config/ags/modules/.widgetutils/clickthrough.js delete mode 100644 .config/ags/modules/.widgetutils/cursorhover.js delete mode 100644 .config/ags/modules/.widgetutils/keybind.js delete mode 100644 .config/ags/modules/bar/focus/workspaces_hyprland.js delete mode 100644 .config/ags/modules/bar/focus/workspaces_sway.js delete mode 100644 .config/ags/modules/bar/main.js delete mode 100644 .config/ags/modules/bar/normal/music.js delete mode 100644 .config/ags/modules/bar/normal/spaceleft.js delete mode 100644 .config/ags/modules/bar/normal/spaceright.js delete mode 100644 .config/ags/modules/bar/normal/system.js delete mode 100644 .config/ags/modules/bar/normal/tray.js delete mode 100644 .config/ags/modules/bar/normal/workspaces_hyprland.js delete mode 100644 .config/ags/modules/bar/normal/workspaces_sway.js delete mode 100644 .config/ags/modules/cheatsheet/data_keybinds.js delete mode 100644 .config/ags/modules/cheatsheet/data_periodictable.js delete mode 100644 .config/ags/modules/cheatsheet/keybinds.js delete mode 100644 .config/ags/modules/cheatsheet/main.js delete mode 100644 .config/ags/modules/cheatsheet/periodictable.js delete mode 100644 .config/ags/modules/crosshair/main.js delete mode 100644 .config/ags/modules/desktopbackground/data_quicklaunches.js delete mode 100644 .config/ags/modules/desktopbackground/main.js delete mode 100644 .config/ags/modules/desktopbackground/system.js delete mode 100644 .config/ags/modules/desktopbackground/timeandlaunches.js delete mode 100644 .config/ags/modules/desktopbackground/wallpaper.js delete mode 100755 .config/ags/modules/dock/dock.js delete mode 100644 .config/ags/modules/dock/icons.js delete mode 100644 .config/ags/modules/dock/main.js delete mode 100644 .config/ags/modules/indicators/colorscheme.js delete mode 100644 .config/ags/modules/indicators/indicatorvalues.js delete mode 100644 .config/ags/modules/indicators/main.js delete mode 100644 .config/ags/modules/indicators/musiccontrols.js delete mode 100644 .config/ags/modules/indicators/notificationpopups.js delete mode 100644 .config/ags/modules/onscreenkeyboard/data_keyboardlayouts.js delete mode 100644 .config/ags/modules/onscreenkeyboard/main.js delete mode 100644 .config/ags/modules/onscreenkeyboard/onscreenkeyboard.js delete mode 100644 .config/ags/modules/overview/actions.js delete mode 100644 .config/ags/modules/overview/main.js delete mode 100644 .config/ags/modules/overview/miscfunctions.js delete mode 100644 .config/ags/modules/overview/overview_hyprland.js delete mode 100644 .config/ags/modules/overview/searchbuttons.js delete mode 100644 .config/ags/modules/overview/searchitem.js delete mode 100644 .config/ags/modules/overview/windowcontent.js delete mode 100644 .config/ags/modules/screencorners/main.js delete mode 100644 .config/ags/modules/session/main.js delete mode 100644 .config/ags/modules/session/sessionscreen.js delete mode 100644 .config/ags/modules/sideleft/apis/ai_chatmessage.js delete mode 100644 .config/ags/modules/sideleft/apis/booru.js delete mode 100644 .config/ags/modules/sideleft/apis/chatgpt.js delete mode 100644 .config/ags/modules/sideleft/apis/gemini.js delete mode 100644 .config/ags/modules/sideleft/apis/waifu.js delete mode 100644 .config/ags/modules/sideleft/apiwidgets.js delete mode 100644 .config/ags/modules/sideleft/main.js delete mode 100644 .config/ags/modules/sideleft/sideleft.js delete mode 100644 .config/ags/modules/sideleft/toolbox.js delete mode 100644 .config/ags/modules/sideleft/tools/changeres.sh delete mode 100644 .config/ags/modules/sideleft/tools/color.js delete mode 100644 .config/ags/modules/sideleft/tools/colorpicker.js delete mode 100644 .config/ags/modules/sideleft/tools/conversions.js delete mode 100644 .config/ags/modules/sideleft/tools/module.js delete mode 100644 .config/ags/modules/sideleft/tools/name.js delete mode 100644 .config/ags/modules/sideleft/tools/quickscripts.js delete mode 100644 .config/ags/modules/sideright/calendar.js delete mode 100644 .config/ags/modules/sideright/calendar_layout.js delete mode 100644 .config/ags/modules/sideright/centermodules/audiocontrols.js delete mode 100644 .config/ags/modules/sideright/centermodules/bluetooth.js delete mode 100644 .config/ags/modules/sideright/centermodules/configure.js delete mode 100644 .config/ags/modules/sideright/centermodules/notificationlist.js delete mode 100644 .config/ags/modules/sideright/centermodules/wifinetworks.js delete mode 100644 .config/ags/modules/sideright/main.js delete mode 100644 .config/ags/modules/sideright/quicktoggles.js delete mode 100644 .config/ags/modules/sideright/sideright.js delete mode 100644 .config/ags/modules/sideright/todolist.js delete mode 100644 .config/ags/scripts/README.md delete mode 100755 .config/ags/scripts/ags/agsconfigurator.py delete mode 100644 .config/ags/scripts/ai/license_show-loaded-ollama-models.txt delete mode 100755 .config/ags/scripts/ai/primary-buffer-query.sh delete mode 100755 .config/ags/scripts/ai/show-installed-ollama-models.sh delete mode 100755 .config/ags/scripts/ai/show-loaded-ollama-models.sh delete mode 100755 .config/ags/scripts/hyprland/get_keybinds.py delete mode 100755 .config/ags/scripts/hyprland/hyprconfigurator.py delete mode 100755 .config/ags/scripts/hyprland/workspace_action.sh delete mode 100755 .config/ags/scripts/network_scripts/network_bandwidth.py delete mode 100755 .config/ags/scripts/quickscripts/nixos-trim-generations.sh delete mode 100755 .config/ags/scripts/sway/swayToRelativeWs.sh delete mode 100644 .config/ags/scripts/templates/ags/sourceviewtheme-light.xml delete mode 100644 .config/ags/scripts/templates/ags/sourceviewtheme.xml delete mode 100644 .config/ags/scripts/templates/fuzzel/fuzzel.ini delete mode 100644 .config/ags/scripts/templates/gradience/preset.json delete mode 100644 .config/ags/scripts/templates/gtk/gtk-colors.css delete mode 100644 .config/ags/scripts/templates/hypr/hyprland/colors.conf delete mode 100644 .config/ags/scripts/templates/hypr/hyprlock.conf delete mode 100644 .config/ags/scripts/templates/terminal/scheme-base.json delete mode 100644 .config/ags/scripts/templates/terminal/scheme-monochrome.json delete mode 100644 .config/ags/scripts/templates/terminal/sequences.txt delete mode 100644 .config/ags/scripts/templates/wal/_musicwal.scss delete mode 100755 .config/ags/scripts/wayland-idle-inhibitor.py delete mode 100644 .config/ags/scss/_bar.scss delete mode 100644 .config/ags/scss/_cheatsheet.scss delete mode 100644 .config/ags/scss/_colors.scss delete mode 100644 .config/ags/scss/_common.scss delete mode 100644 .config/ags/scss/_desktopbackground.scss delete mode 100644 .config/ags/scss/_dock.scss delete mode 100644 .config/ags/scss/_lib_classes.scss delete mode 100644 .config/ags/scss/_lib_mixins.scss delete mode 100644 .config/ags/scss/_music.scss delete mode 100644 .config/ags/scss/_notifications.scss delete mode 100644 .config/ags/scss/_osd.scss delete mode 100644 .config/ags/scss/_osk.scss delete mode 100644 .config/ags/scss/_overview.scss delete mode 100644 .config/ags/scss/_session.scss delete mode 100644 .config/ags/scss/_sidebars.scss delete mode 100644 .config/ags/scss/_wal.scss delete mode 100644 .config/ags/scss/fallback/_material.scss delete mode 100644 .config/ags/scss/main.scss delete mode 100644 .config/ags/services/booru.js delete mode 100644 .config/ags/services/brightness.js delete mode 100644 .config/ags/services/darkmode.js delete mode 100644 .config/ags/services/gemini.js delete mode 100644 .config/ags/services/gpt.js delete mode 100644 .config/ags/services/indicator.js delete mode 100644 .config/ags/services/messages.js delete mode 100644 .config/ags/services/sway.js delete mode 100644 .config/ags/services/todo.js delete mode 100644 .config/ags/services/waifus.js delete mode 100644 .config/ags/services/wallpaper.js delete mode 100644 .config/ags/user_options.jsonc delete mode 100644 .config/ags/variables.js diff --git a/.config/ags/assets/icons/ai-openai-symbolic.svg b/.config/ags/assets/icons/ai-openai-symbolic.svg deleted file mode 120000 index c9ee0b32f..000000000 --- a/.config/ags/assets/icons/ai-openai-symbolic.svg +++ /dev/null @@ -1 +0,0 @@ -openai-symbolic.svg \ No newline at end of file diff --git a/.config/ags/assets/icons/ai-oxygen-symbolic.svg b/.config/ags/assets/icons/ai-oxygen-symbolic.svg deleted file mode 100644 index 5e1cc1937..000000000 --- a/.config/ags/assets/icons/ai-oxygen-symbolic.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - diff --git a/.config/ags/assets/icons/ai-zukijourney.png b/.config/ags/assets/icons/ai-zukijourney.png deleted file mode 100644 index 917335e7284b2ffc8fc222c126016e23bbbc07ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 312911 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[Ig=D> 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 z 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 z %PiUM7hXe-F%^gF$f_)^i$BAXNT)*$vBUv4a~f7)<=-*f%Jfrs^q<@lPLmoLb) zWy2LjOIBKj!=rESn2wZriEAZ!VL2WhySIDBZoo;4Pl;8S-XvsNoW(owNjp0Fz;HO8 zzqk6X+Y3ov@XL-1VP5ddj!#z%KlKxTDYd=DyM)J~JB7z`6i1w^s3`HeNA=L+QB@%H z0x^R34y`AZrynhCcc32z+O8$rdA4Rx_qN6M_Y5wP?;S6k1F5-wwL<2SGLtOVOPahS zI8VNkXJ?pPN$fDqN8S7}1dlT?bS+LKOz@~hL#Ydj_0V%LN9tumP>M1ol$1E{QTbzKt&$)a z1NB78x+-B9$)zF}(=kvL1-(ziv85#t?^+%zNAYgtXS;^iH!tz+K;46WagBfBDa$~# z0!VbGqp37u$mp;r<>`-+-CO(4OrjPQ(Fc+bT>ova zFC=-vuP9sy^MYS-`1RlS8=oDNKmPCk(d42b+ZHHVOfz|q22miX>4yVK7WCHQy~A2V z+y#8}Jlt(LZg=cd_cp#19fz~yb>(SlGv1dt*t0l3viAyP2wcyJ$d%Hi`tJPDwhmqj>Q6; zDN#jg&wl9WZAWMwJAb4z1Gx}v4oi-CN49&zmA&SmkNh3~(BFL_$P0c&<3gAh{EEkK z`?r4Ze31SJzvJIYdMn8nl0s-iQsk7=VeLok-ZQ)&A;96`$eVW`vHzr}-`-=~NTEt< zGa{8j<^o-pEJu7Jn!vk{-m+K<>eWkJj36}G zVnwq*GK>YS_dMM`4RLA}_hy=7-W~ST#ndvDQc?qXE^%f=6(!kKMJ_XBp3z9peO+>t z0^uzfI1D|7^?Y1N?7^dQ0XneCU!f#u5m2H)E6B7a%Oc0#OhTV-VA~xT#+LBVA&?xd zc6|SD{(%cYUhoZt3t?XH4TXQ>NB_`g97;y zK(8CT@HBae%STjIP~2>&j7NOWCP#x{r}{J9548W$AvI2_@==3{@#E7>ki~U`$K;!_4fy27tloXu0w2A)W*^b z5#KauET#e3b$Gy=k*;sSdsMFQW@O*BxG)Dt`$wmsex70WPu66V52Skfyj2-hKVXAr zD6Xi@4kr>T=lS5$vC2J>Y!W{gJUKNPUKIk?smah}2a@wQf-NJ3epW z;{DrqJY03SpPTM?b9JDJA2NJST@m=}E0;(LGZzwnud_y78Pel&S4DC~&w zj^^PbMiGd{qLN_fT2wB%-=Aic69Mjz-L%W7B>Q7WT^97CIg`}h8`eN(9U%nLFwYcK z#+S@3K3lQW8~S8;=RKYOIgUQ(uz!c_eg=TU;#afoDz0vSEwErQewiV^#Yc;ek=yPK z`{vsLFhbz~KaQAVN0bsJJ#|%n`YE5`LZBZf!c7h=5NTMie(yx?07fA0_fj?X;o|A*iD`_jRTXF@p<<*AZ{ zxtg3w@}O}7S1~Xc!$w86aly(TxfKs&DNvBeLO^9}LcTz^{|{mJvwWO?2d~1< z@bcJlT>X92<68h8QU$)l_8PYs*)4uGI=>>C2mE*h?>UBWChfXPo0f# z*URQyY1UP74w81>Q)D?sCJENz+#Ex;5FFYiuZk_LH+Z*Y6$C}tp*n}r3;g15WgInM zk$)F!G4NeZWA7Glqgez)SQJoqh~fjfRG@*a=}Er8&rV2>#Au#XnI}SbKFt4gC@HC{ z;&IkFjyUgs-sgY81s6=X5atCJ{59~q{^$Sv>xkq}eD^<>PHkDGCe>-_r;2nt4qSWB z;5;bk)N2~EC861dioNN{{hq5j$B2R|m3+s-qPrdX`UBFi;vlZkd&_|mTKKltl68E` zOMOKiB&q+1`+m#&OyHb9dl{XoOk*8|R_Cuh{rEfo@=sof@q!C3xZr{dF1X-=UmX0u X>l-)x)|NV700000NkvXXu0mjfBj_ds diff --git a/.config/ags/assets/icons/arch-symbolic.svg b/.config/ags/assets/icons/arch-symbolic.svg deleted file mode 100644 index 7de9094e0..000000000 --- a/.config/ags/assets/icons/arch-symbolic.svg +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.config/ags/assets/icons/cachyos-symbolic.svg b/.config/ags/assets/icons/cachyos-symbolic.svg deleted file mode 100644 index 4a9db19ab..000000000 --- a/.config/ags/assets/icons/cachyos-symbolic.svg +++ /dev/null @@ -1,318 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.config/ags/assets/icons/cloudflare-dns-symbolic.svg b/.config/ags/assets/icons/cloudflare-dns-symbolic.svg deleted file mode 100644 index bd48d3c93..000000000 --- a/.config/ags/assets/icons/cloudflare-dns-symbolic.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/.config/ags/assets/icons/crosshair-symbolic.svg b/.config/ags/assets/icons/crosshair-symbolic.svg deleted file mode 100644 index 22967493d..000000000 --- a/.config/ags/assets/icons/crosshair-symbolic.svg +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - ionicons-v5_logos - - - - ionicons-v5_logos - - - - - - diff --git a/.config/ags/assets/icons/debian-symbolic.svg b/.config/ags/assets/icons/debian-symbolic.svg deleted file mode 100644 index 252f85334..000000000 --- a/.config/ags/assets/icons/debian-symbolic.svg +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/.config/ags/assets/icons/deepseek-symbolic.svg b/.config/ags/assets/icons/deepseek-symbolic.svg deleted file mode 100644 index 029e12663..000000000 --- a/.config/ags/assets/icons/deepseek-symbolic.svg +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - diff --git a/.config/ags/assets/icons/desktop-symbolic.svg b/.config/ags/assets/icons/desktop-symbolic.svg deleted file mode 100644 index 04f7a3b5e..000000000 --- a/.config/ags/assets/icons/desktop-symbolic.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/.config/ags/assets/icons/endeavouros-symbolic.svg b/.config/ags/assets/icons/endeavouros-symbolic.svg deleted file mode 100644 index 3be4cc406..000000000 --- a/.config/ags/assets/icons/endeavouros-symbolic.svg +++ /dev/null @@ -1,96 +0,0 @@ - - - - - EndeavourOS Logo - - - - image/svg+xml - - EndeavourOS Logo - - - - - - - - - - - - - - - - - - - diff --git a/.config/ags/assets/icons/fedora-symbolic.svg b/.config/ags/assets/icons/fedora-symbolic.svg deleted file mode 100644 index 1a4e8c873..000000000 --- a/.config/ags/assets/icons/fedora-symbolic.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - diff --git a/.config/ags/assets/icons/flatpak-symbolic.svg b/.config/ags/assets/icons/flatpak-symbolic.svg deleted file mode 100644 index 0c2bf6280..000000000 --- a/.config/ags/assets/icons/flatpak-symbolic.svg +++ /dev/null @@ -1,52 +0,0 @@ - - - - - Flatpak - - - - - Flatpak - - - - diff --git a/.config/ags/assets/icons/github-symbolic.svg b/.config/ags/assets/icons/github-symbolic.svg deleted file mode 100644 index c1c9f19c4..000000000 --- a/.config/ags/assets/icons/github-symbolic.svg +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - diff --git a/.config/ags/assets/icons/google-gemini-symbolic.svg b/.config/ags/assets/icons/google-gemini-symbolic.svg deleted file mode 100644 index 9de741be6..000000000 --- a/.config/ags/assets/icons/google-gemini-symbolic.svg +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - ionicons-v5_logos - - - - - ionicons-v5_logos - - - - diff --git a/.config/ags/assets/icons/linux-symbolic.svg b/.config/ags/assets/icons/linux-symbolic.svg deleted file mode 100644 index 63f9c7e58..000000000 --- a/.config/ags/assets/icons/linux-symbolic.svg +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.config/ags/assets/icons/microsoft-symbolic.svg b/.config/ags/assets/icons/microsoft-symbolic.svg deleted file mode 100644 index b90cfc637..000000000 --- a/.config/ags/assets/icons/microsoft-symbolic.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - diff --git a/.config/ags/assets/icons/nixos-symbolic.svg b/.config/ags/assets/icons/nixos-symbolic.svg deleted file mode 100644 index b697b0d1a..000000000 --- a/.config/ags/assets/icons/nixos-symbolic.svg +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/.config/ags/assets/icons/ollama-symbolic.svg b/.config/ags/assets/icons/ollama-symbolic.svg deleted file mode 100644 index 014548151..000000000 --- a/.config/ags/assets/icons/ollama-symbolic.svg +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - diff --git a/.config/ags/assets/icons/openai-symbolic.svg b/.config/ags/assets/icons/openai-symbolic.svg deleted file mode 100644 index 8ffc912ae..000000000 --- a/.config/ags/assets/icons/openai-symbolic.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - diff --git a/.config/ags/assets/icons/openrouter-symbolic.svg b/.config/ags/assets/icons/openrouter-symbolic.svg deleted file mode 100644 index 32aaaf50b..000000000 --- a/.config/ags/assets/icons/openrouter-symbolic.svg +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - diff --git a/.config/ags/assets/icons/ubuntu-symbolic.svg b/.config/ags/assets/icons/ubuntu-symbolic.svg deleted file mode 100644 index 07746c9f6..000000000 --- a/.config/ags/assets/icons/ubuntu-symbolic.svg +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.config/ags/assets/images/default_wallpaper.png b/.config/ags/assets/images/default_wallpaper.png deleted file mode 100644 index 77d890c21b201652214d41195a15bb586beac1be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67685 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 - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/.config/ags/assets/themes/sourceviewtheme-light.xml b/.config/ags/assets/themes/sourceviewtheme-light.xml deleted file mode 100644 index c880404dc..000000000 --- a/.config/ags/assets/themes/sourceviewtheme-light.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - end_4 - <_description>Catppuccin port but very random - - ` + - `${notificationObject.body.replace(/\n/g, "
")}` + text: { + const processedBody = processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary) + return `` + + `${processedBody.replace(/\n/g, "
")}` + } onLinkActivated: (link) => { Qt.openUrlExternally(link) @@ -298,4 +320,4 @@ Item { // Notification item area } } } -} \ No newline at end of file +} From ef1170c770ec7c8186f906298516cee0008b5bc1 Mon Sep 17 00:00:00 2001 From: _xB <65196493+xBiei@users.noreply.github.com> Date: Fri, 6 Jun 2025 01:09:38 +0300 Subject: [PATCH 674/824] dock: Show pinned apps again --- .config/quickshell/modules/dock/Dock.qml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.config/quickshell/modules/dock/Dock.qml b/.config/quickshell/modules/dock/Dock.qml index 777aa6461..cbb291185 100644 --- a/.config/quickshell/modules/dock/Dock.qml +++ b/.config/quickshell/modules/dock/Dock.qml @@ -111,6 +111,24 @@ Scope { // Scope } } DockSeparator {} + // Pinned apps + Repeater { + model: ConfigOptions?.dock.pinnedApps ?? [] + + DockButton { + required property string modelData + onClicked: { + Hyprland.dispatch(`exec gio launch ${modelData}`) + } + contentItem: IconImage { + anchors.centerIn: parent + source: Quickshell.iconPath(AppSearch.guessIcon(modelData), "image-missing") + } + } + } + + DockSeparator { visible: (ConfigOptions?.dock.pinnedApps ?? []).length > 0 } + DockApps { id: dockApps } DockSeparator {} DockButton { From aa951b0bc089153632b6d52b284d35f968b8f810 Mon Sep 17 00:00:00 2001 From: _xB <65196493+xBiei@users.noreply.github.com> Date: Fri, 6 Jun 2025 01:12:11 +0300 Subject: [PATCH 675/824] bar: add microphone control to util buttons --- .config/quickshell/modules/bar/UtilButtons.qml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.config/quickshell/modules/bar/UtilButtons.qml b/.config/quickshell/modules/bar/UtilButtons.qml index 48bdab096..d2705865c 100644 --- a/.config/quickshell/modules/bar/UtilButtons.qml +++ b/.config/quickshell/modules/bar/UtilButtons.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Hyprland +import Quickshell.Services.Pipewire Rectangle { id: root @@ -63,6 +64,20 @@ Rectangle { } + CircleUtilButton { + Layout.alignment: Qt.AlignVCenter + onClicked: Hyprland.dispatch("exec wpctl set-mute @DEFAULT_SOURCE@ toggle") + + MaterialSymbol { + horizontalAlignment: Qt.AlignHCenter + fill: 0 + text: Pipewire.defaultAudioSource?.audio?.muted ? "mic_off" : "mic" + iconSize: Appearance.font.pixelSize.large + color: Appearance.colors.colOnLayer2 + } + + } + } } From 403614d337ea39ccd3eaab79c4df793a81504a9a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 6 Jun 2025 20:39:15 +0200 Subject: [PATCH 676/824] add app name to code save notif --- .../quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml index fa4af99e0..ef0c1b5e9 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml @@ -97,7 +97,7 @@ ColumnLayout { onClicked: { const downloadPath = FileUtils.trimFileProtocol(Directories.downloads) Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(segmentContent)}' > '${downloadPath}/code.${segmentLang || "txt"}'`) - Hyprland.dispatch(`exec notify-send 'Code saved to file' '${downloadPath}/code.${segmentLang || "txt"}'`) + Hyprland.dispatch(`exec notify-send 'Code saved to file' '${downloadPath}/code.${segmentLang || "txt"}' -a Shell`) saveCodeButton.activated = true saveIconTimer.restart() } From 56b11e9ab7e895180f54dc1161dc4400cdcdab6e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 6 Jun 2025 20:39:29 +0200 Subject: [PATCH 677/824] comments --- .config/quickshell/modules/bar/Workspaces.qml | 4 ++-- .config/quickshell/services/Ai.qml | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 4639edd22..5ed0e6af1 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -144,13 +144,13 @@ Item { Behavior on activeWorkspaceMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } - Behavior on idx1 { + Behavior on idx1 { // Leading anim NumberAnimation { duration: 100 easing.type: Easing.OutSine } } - Behavior on idx2 { + Behavior on idx2 { // Following anim NumberAnimation { duration: 300 easing.type: Easing.OutSine diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index c58137938..7b3084fca 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -430,8 +430,7 @@ Singleton { "parts": [{ text: root.systemPrompt }] }, "generationConfig": { - "temperature": root.temperature, - "responseMimeType": "text/plain", + // "temperature": root.temperature, }, }; return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData; @@ -450,7 +449,7 @@ Singleton { }), ], "stream": true, - "temperature": root.temperature, + // "temperature": root.temperature, }; return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData; } @@ -612,7 +611,7 @@ Singleton { stdout: SplitParser { onRead: data => { - // console.log("RAW DATA: ", data); + console.log("RAW DATA: ", data); if (data.length === 0) return; // Handle response line From 9111ada32b4ce747ec5609b971fecb03bda71649 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 6 Jun 2025 20:39:40 +0200 Subject: [PATCH 678/824] add m3 expressive slow spatial curve --- .config/quickshell/modules/common/Appearance.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index b9b45c288..7c1bc3104 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -176,6 +176,7 @@ Singleton { animationCurves: QtObject { readonly property list expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1] // Default, 350ms readonly property list expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1] // Default, 500ms + readonly property list expressiveSlowSpatial: [0.39, 1.29, 0.35, 0.98, 1, 1] // Default, 650ms readonly property list expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1] // Default, 200ms readonly property list emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] readonly property list emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] From eeb41de46ce110cec905045e9bf6cdb6a9860c27 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 6 Jun 2025 20:50:06 +0200 Subject: [PATCH 679/824] fix listview items being weird when changing both y and height --- .../modules/common/widgets/StyledListView.qml | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/StyledListView.qml b/.config/quickshell/modules/common/widgets/StyledListView.qml index c13d8082f..835b9a43a 100644 --- a/.config/quickshell/modules/common/widgets/StyledListView.qml +++ b/.config/quickshell/modules/common/widgets/StyledListView.qml @@ -46,40 +46,40 @@ ListView { ] } - displaced: Transition { - animations: [ - Appearance?.animation.elementMove.numberAnimation.createObject(this, { - property: "y", - }), - Appearance?.animation.elementMove.numberAnimation.createObject(this, { - properties: "opacity,scale", - to: 1, - }), - ] - } + // displaced: Transition { + // animations: [ + // Appearance?.animation.elementMove.numberAnimation.createObject(this, { + // property: "y", + // }), + // Appearance?.animation.elementMove.numberAnimation.createObject(this, { + // properties: "opacity,scale", + // to: 1, + // }), + // ] + // } - move: Transition { - animations: [ - Appearance?.animation.elementMove.numberAnimation.createObject(this, { - property: "y", - }), - Appearance?.animation.elementMove.numberAnimation.createObject(this, { - properties: "opacity,scale", - to: 1, - }), - ] - } - moveDisplaced: Transition { - animations: [ - Appearance?.animation.elementMove.numberAnimation.createObject(this, { - property: "y", - }), - Appearance?.animation.elementMove.numberAnimation.createObject(this, { - properties: "opacity,scale", - to: 1, - }), - ] - } + // move: Transition { + // animations: [ + // Appearance?.animation.elementMove.numberAnimation.createObject(this, { + // property: "y", + // }), + // Appearance?.animation.elementMove.numberAnimation.createObject(this, { + // properties: "opacity,scale", + // to: 1, + // }), + // ] + // } + // moveDisplaced: Transition { + // animations: [ + // Appearance?.animation.elementMove.numberAnimation.createObject(this, { + // property: "y", + // }), + // Appearance?.animation.elementMove.numberAnimation.createObject(this, { + // properties: "opacity,scale", + // to: 1, + // }), + // ] + // } remove: Transition { animations: [ From 31e782a36f0d331f6325f17c78b7b44fb2f11414 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 6 Jun 2025 20:50:53 +0200 Subject: [PATCH 680/824] ai chat: prevent function call message from abrupt jumps --- .config/quickshell/modules/common/widgets/StyledListView.qml | 5 +++-- .config/quickshell/modules/sidebarLeft/AiChat.qml | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/StyledListView.qml b/.config/quickshell/modules/common/widgets/StyledListView.qml index 835b9a43a..76d9782b4 100644 --- a/.config/quickshell/modules/common/widgets/StyledListView.qml +++ b/.config/quickshell/modules/common/widgets/StyledListView.qml @@ -18,6 +18,7 @@ ListView { property real removeOvershoot: 20 // Account for gaps and bouncy animations property int dragIndex: -1 property real dragDistance: 0 + property bool popin: true function resetDrag() { root.dragIndex = -1 @@ -27,7 +28,7 @@ ListView { add: Transition { animations: [ Appearance?.animation.elementMove.numberAnimation.createObject(this, { - properties: "opacity,scale", + properties: popin ? "opacity,scale" : "opacity", from: 0, to: 1, }), @@ -40,7 +41,7 @@ ListView { property: "y", }), Appearance?.animation.elementMove.numberAnimation.createObject(this, { - properties: "opacity,scale", + properties: popin ? "opacity,scale" : "opacity", to: 1, }), ] diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 6eb069e94..b78ff89ce 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -162,6 +162,7 @@ int main(int argc, char* argv[]) { id: messageListView anchors.fill: parent spacing: 10 + popin: false property int lastResponseLength: 0 @@ -175,6 +176,8 @@ int main(int argc, char* argv[]) { } } + add: null // Prevent function calls from being janky + Behavior on contentY { NumberAnimation { id: scrollAnim From a2f2fb816b9ecfa498270351cdf96559fa44b222 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 6 Jun 2025 21:25:56 +0200 Subject: [PATCH 681/824] make ripples more subtle (circle -> radial gradient) --- .../common/widgets/PrimaryTabButton.qml | 23 +++++++++++++++---- .../modules/common/widgets/RippleButton.qml | 22 ++++++++++++++---- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml index e59c8a962..b7c628712 100644 --- a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml +++ b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml @@ -110,13 +110,28 @@ TabButton { animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) } - Rectangle { + Item { id: ripple - - radius: Appearance?.rounding.full ?? 9999 - color: button.colRipple + width: ripple.implicitWidth + height: ripple.implicitHeight opacity: 0 + property real implicitWidth: 0 + property real implicitHeight: 0 + + Behavior on opacity { + animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) + } + + RadialGradient { + anchors.fill: parent + gradient: Gradient { + GradientStop { position: 0.0; color: button.colRipple } + GradientStop { position: 0.3; color: button.colRipple } + GradientStop { position: 0.5 ; color: Qt.rgba(button.colRipple.r, button.colRipple.g, button.colRipple.b, 0) } + } + } + transform: Translate { x: -ripple.width / 2 y: -ripple.height / 2 diff --git a/.config/quickshell/modules/common/widgets/RippleButton.qml b/.config/quickshell/modules/common/widgets/RippleButton.qml index 0a3a8743b..cd7762b9d 100644 --- a/.config/quickshell/modules/common/widgets/RippleButton.qml +++ b/.config/quickshell/modules/common/widgets/RippleButton.qml @@ -150,16 +150,28 @@ Button { } } - Rectangle { + Item { id: ripple - - radius: Appearance?.rounding.full ?? 9999 + width: ripple.implicitWidth + height: ripple.implicitHeight opacity: 0 - color: root.rippleColor - Behavior on color { + + property real implicitWidth: 0 + property real implicitHeight: 0 + + Behavior on opacity { animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) } + RadialGradient { + anchors.fill: parent + gradient: Gradient { + GradientStop { position: 0.0; color: root.rippleColor } + GradientStop { position: 0.3; color: root.rippleColor } + GradientStop { position: 0.5; color: Qt.rgba(root.rippleColor.r, root.rippleColor.g, root.rippleColor.b, 0) } + } + } + transform: Translate { x: -ripple.width / 2 y: -ripple.height / 2 From b70dbf5f10cb0bf7a122b109132928c2e6b70fb3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 6 Jun 2025 22:56:05 +0200 Subject: [PATCH 682/824] hypridle: longer timeouts --- .config/hypr/hypridle.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.config/hypr/hypridle.conf b/.config/hypr/hypridle.conf index db2ea6082..2ec488c75 100644 --- a/.config/hypr/hypridle.conf +++ b/.config/hypr/hypridle.conf @@ -7,17 +7,17 @@ general { } listener { - timeout = 180 # 3mins + timeout = 300 # 5mins on-timeout = loginctl lock-session } listener { - timeout = 240 # 4mins + timeout = 600 # 10mins on-timeout = hyprctl dispatch dpms off on-resume = hyprctl dispatch dpms on } listener { - timeout = 540 # 9mins + timeout = 900 # 15mins on-timeout = $suspend_cmd } From 194b470cf21b59a7f265236bec95d4ea8bff8835 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 6 Jun 2025 23:29:29 +0200 Subject: [PATCH 683/824] installation: skip .local/bin --- diagnose | 1 - install.sh | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/diagnose b/diagnose index 7edbcbd1a..1964d2d66 100755 --- a/diagnose +++ b/diagnose @@ -57,7 +57,6 @@ e "Checking distro" x ii_check_distro e "Checking variables" -x declare -p XDG_BIN_HOME # ~/.local/bin x declare -p XDG_CACHE_HOME # ~/.cache x declare -p XDG_CONFIG_HOME # ~/.config x declare -p XDG_DATA_HOME # ~/.local/share diff --git a/install.sh b/install.sh index 89ee391ef..58cc6c441 100755 --- a/install.sh +++ b/install.sh @@ -249,7 +249,7 @@ esac # some foldes (eg. .local/bin) should be processed separately to avoid `--delete' for rsync, # since the files here come from different places, not only about one program. -v rsync -av ".local/bin/" "$XDG_BIN_HOME" +# v rsync -av ".local/bin/" "$XDG_BIN_HOME" # No longer needed since scripts are no longer in ~/.local/bin # Prevent hyprland from not fully loaded sleep 1 From 2b451c012beacd267d7cf60029357cb2c431b130 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 08:49:41 +0200 Subject: [PATCH 684/824] overview: anims when opening/closing windows --- .config/quickshell/modules/overview/OverviewWidget.qml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index b060c8dfc..a690536fe 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -144,10 +144,12 @@ Item { implicitHeight: workspaceColumnLayout.implicitHeight Repeater { // Window repeater - model: windowAddresses.filter((address) => { - var win = windowByAddress[address] - return (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown) - }) + model: ScriptModel { + values: windowAddresses.filter((address) => { + var win = windowByAddress[address] + return (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown) + }) + } delegate: OverviewWindow { id: window windowData: windowByAddress[modelData] From b6dcd19d04e2e4d83ecd1c2dd30161130d1c2ae7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 11:12:10 +0200 Subject: [PATCH 685/824] bar: workspaces: use secondary container for occupied --- .config/quickshell/modules/bar/Workspaces.qml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 5ed0e6af1..cb3c6551a 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -2,6 +2,7 @@ import "root:/" import "root:/services/" import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -98,15 +99,17 @@ Item { implicitWidth: workspaceButtonWidth implicitHeight: workspaceButtonWidth radius: Appearance.rounding.full - property var radiusLeft: (workspaceOccupied[index-1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index)) ? 0 : Appearance.rounding.full - property var radiusRight: (workspaceOccupied[index+1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+2)) ? 0 : Appearance.rounding.full + property var leftOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index)) + property var rightOccupied: (workspaceOccupied[index+1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+2)) + property var radiusLeft: leftOccupied ? 0 : Appearance.rounding.full + property var radiusRight: rightOccupied ? 0 : Appearance.rounding.full topLeftRadius: radiusLeft bottomLeftRadius: radiusLeft topRightRadius: radiusRight bottomRightRadius: radiusRight - color: Appearance.colors.colLayer2 + color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4) opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+1)) ? 1 : 0 Behavior on opacity { @@ -203,7 +206,7 @@ Item { elide: Text.ElideRight color: (monitor.activeWorkspace?.id == button.workspaceValue) ? Appearance.m3colors.m3onPrimary : - (workspaceOccupied[index] ? Appearance.colors.colOnLayer1 : + (workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1Inactive) Behavior on opacity { From 810ea2ce3b2fa6cfb17f0dc8fcc4ef508668ea61 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 11:14:28 +0200 Subject: [PATCH 686/824] circ prog: bigger gap angle --- .config/quickshell/modules/common/widgets/CircularProgress.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/widgets/CircularProgress.qml b/.config/quickshell/modules/common/widgets/CircularProgress.qml index 19a838c4c..d0afc6060 100644 --- a/.config/quickshell/modules/common/widgets/CircularProgress.qml +++ b/.config/quickshell/modules/common/widgets/CircularProgress.qml @@ -15,7 +15,7 @@ Item { property real value: 0 property color primaryColor: Appearance.m3colors.m3onSecondaryContainer property color secondaryColor: Appearance.m3colors.m3secondaryContainer - property real gapAngle: Math.PI / 10 + property real gapAngle: Math.PI / 9 property bool fill: false property int fillOverflow: 2 property int animationDuration: 1000 From e03800cf64cece1c3ba050b863f1d6470504a12e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 11:29:32 +0200 Subject: [PATCH 687/824] screen corners: mask layer visibility to the corners only --- .../modules/screenCorners/ScreenCorners.qml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.config/quickshell/modules/screenCorners/ScreenCorners.qml b/.config/quickshell/modules/screenCorners/ScreenCorners.qml index 32708dc60..37f0ff23b 100644 --- a/.config/quickshell/modules/screenCorners/ScreenCorners.qml +++ b/.config/quickshell/modules/screenCorners/ScreenCorners.qml @@ -5,6 +5,7 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Wayland +import Quickshell.Hyprland Scope { id: screenCorners @@ -23,6 +24,20 @@ Scope { mask: Region { item: null } + HyprlandWindow.visibleMask: Region { + Region { + item: topLeftCorner + } + Region { + item: topRightCorner + } + Region { + item: bottomLeftCorner + } + Region { + item: bottomRightCorner + } + } WlrLayershell.namespace: "quickshell:screenCorners" WlrLayershell.layer: WlrLayer.Overlay color: "transparent" @@ -35,24 +50,28 @@ Scope { } RoundCorner { + id: topLeftCorner anchors.top: parent.top anchors.left: parent.left size: Appearance.rounding.screenRounding corner: cornerEnum.topLeft } RoundCorner { + id: topRightCorner anchors.top: parent.top anchors.right: parent.right size: Appearance.rounding.screenRounding corner: cornerEnum.topRight } RoundCorner { + id: bottomLeftCorner anchors.bottom: parent.bottom anchors.left: parent.left size: Appearance.rounding.screenRounding corner: cornerEnum.bottomLeft } RoundCorner { + id: bottomRightCorner anchors.bottom: parent.bottom anchors.right: parent.right size: Appearance.rounding.screenRounding From 643b197d02c65a1895d679a8d2ca068a5a8e94af Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 14:10:31 +0200 Subject: [PATCH 688/824] notifications: fix spacing/eliding --- .../common/widgets/NotificationGroup.qml | 66 +++++++++++-------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationGroup.qml b/.config/quickshell/modules/common/widgets/NotificationGroup.qml index 009a0b3ed..cfe93327c 100644 --- a/.config/quickshell/modules/common/widgets/NotificationGroup.qml +++ b/.config/quickshell/modules/common/widgets/NotificationGroup.qml @@ -154,42 +154,56 @@ Item { // Notification group area ColumnLayout { // Content Layout.fillWidth: true - spacing: expanded ? - ((root.multipleNotifications && - notificationGroup?.notifications[root.notificationCount - 1].image != "") ? 35 : - 5) : 0 + spacing: expanded ? (root.multipleNotifications ? + (notificationGroup?.notifications[root.notificationCount - 1].image != "") ? 35 : + 5 : 0) : 0 + // spacing: 00 Behavior on spacing { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } - RowLayout { // App name (or summary when there's only 1 notif) and time + Item { // App name (or summary when there's only 1 notif) and time id: topRow - spacing: 0 + // spacing: 0 + Layout.fillWidth: true property real fontSize: Appearance.font.pixelSize.smaller property bool showAppName: root.multipleNotifications + implicitHeight: Math.max(topTextRow.implicitHeight, expandButton.implicitHeight) - StyledText { - id: appName - text: (topRow.showAppName ? - notificationGroup?.appName : - notificationGroup?.notifications[0]?.summary) || "" - font.pixelSize: topRow.showAppName ? - topRow.fontSize : - Appearance.font.pixelSize.small - color: topRow.showAppName ? - Appearance.colors.colSubtext : - Appearance.colors.colOnLayer2 + RowLayout { + id: topTextRow + anchors.left: parent.left + anchors.right: expandButton.left + anchors.verticalCenter: parent.verticalCenter + spacing: 5 + StyledText { + id: appName + elide: Text.ElideRight + Layout.fillWidth: true + text: (topRow.showAppName ? + notificationGroup?.appName : + notificationGroup?.notifications[0]?.summary) || "" + font.pixelSize: topRow.showAppName ? + topRow.fontSize : + Appearance.font.pixelSize.small + color: topRow.showAppName ? + Appearance.colors.colSubtext : + Appearance.colors.colOnLayer2 + } + StyledText { + id: timeText + // Layout.fillWidth: true + Layout.rightMargin: 10 + horizontalAlignment: Text.AlignLeft + text: NotificationUtils.getFriendlyNotifTimeString(notificationGroup?.time) + font.pixelSize: topRow.fontSize + color: Appearance.colors.colSubtext + } } - StyledText { - id: timeText - text: " • " + NotificationUtils.getFriendlyNotifTimeString(notificationGroup?.time) - font.pixelSize: topRow.fontSize - color: Appearance.colors.colSubtext - Layout.alignment: Qt.AlignRight - Layout.fillWidth: true - } - Item { Layout.fillWidth: true } NotificationGroupExpandButton { + id: expandButton + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter count: root.notificationCount expanded: root.expanded fontSize: topRow.fontSize From 341c6be9be523219f3da60a0d861442de800fff8 Mon Sep 17 00:00:00 2001 From: sam Date: Sat, 7 Jun 2025 20:24:14 +0800 Subject: [PATCH 689/824] fixed a typo lol --- .config/hypr/hyprland/env.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/hypr/hyprland/env.conf b/.config/hypr/hyprland/env.conf index bddf37cff..5ce9dedba 100644 --- a/.config/hypr/hyprland/env.conf +++ b/.config/hypr/hyprland/env.conf @@ -17,7 +17,7 @@ env = QT_QPA_PLATFORMTHEME, qt6ct # ######## Screen tearing ######### # env = WLR_DRM_NO_ATOMIC, 1 -# ######## Virtual envrionment ######### +# ######## Virtual environment ######### env = ILLOGICAL_IMPULSE_VIRTUAL_ENV, ~/.local/state/ags/.venv # ############ Others ############# From aadb38370c3984666ff7e632983271c82f080442 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 14:58:41 +0200 Subject: [PATCH 690/824] adjust shadows --- .config/hypr/hyprland/general.conf | 8 ++++---- .config/quickshell/modules/common/Appearance.qml | 4 ++-- .../quickshell/modules/sidebarLeft/SidebarLeft.qml | 2 +- .../modules/sidebarRight/SidebarRight.qml | 13 +++++++++++-- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/.config/hypr/hyprland/general.conf b/.config/hypr/hyprland/general.conf index bf3475f9f..03ad06074 100644 --- a/.config/hypr/hyprland/general.conf +++ b/.config/hypr/hyprland/general.conf @@ -64,10 +64,10 @@ decoration { shadow { enabled = true ignore_window = true - range = 70 - offset = 0 4 - render_power = 2 - color = rgba(00000020) + range = 30 + offset = 0 2 + render_power = 4 + color = rgba(00000010) } # Dim diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 7c1bc3104..9f83ac5cf 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -136,7 +136,7 @@ Singleton { property color colTooltip: "#3C4043" // m3colors.m3inverseSurface in the specs, but the m3 website actually uses this color property color colOnTooltip: "#F8F9FA" // m3colors.m3inverseOnSurface in the specs, but the m3 website actually uses this color property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5) - property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.85) + property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7) } rounding: QtObject { @@ -288,7 +288,7 @@ Singleton { property real searchWidthCollapsed: 260 property real searchWidth: 450 property real hyprlandGapsOut: 5 - property real elevationMargin: 8 + property real elevationMargin: 10 property real fabShadowRadius: 5 property real fabHoveredShadowRadius: 7 } diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index 5cfd79472..ce0b22802 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -97,7 +97,7 @@ Scope { // Scope anchors.left: parent.left anchors.topMargin: Appearance.sizes.hyprlandGapsOut anchors.leftMargin: Appearance.sizes.hyprlandGapsOut - width: sidebarRoot.sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2 + width: sidebarRoot.sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 color: Appearance.colors.colLayer0 radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1 diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 57d836b0e..2bb92723d 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -52,8 +52,17 @@ Scope { Loader { id: sidebarContentLoader active: GlobalStates.sidebarRightOpen - anchors.centerIn: parent - width: sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2 + anchors { + top: parent.top + bottom: parent.bottom + right: parent.right + left: parent.left + topMargin: Appearance.sizes.hyprlandGapsOut + rightMargin: Appearance.sizes.hyprlandGapsOut + bottomMargin: Appearance.sizes.hyprlandGapsOut + leftMargin: Appearance.sizes.elevationMargin + } + width: sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 sourceComponent: Item { From 2c548a42967aa2b72dbd50a45e22cf8fa783a1d6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 14:58:46 +0200 Subject: [PATCH 691/824] fcitx5 blur --- .config/hypr/hyprland/general.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.config/hypr/hyprland/general.conf b/.config/hypr/hyprland/general.conf index 03ad06074..e8895efdc 100644 --- a/.config/hypr/hyprland/general.conf +++ b/.config/hypr/hyprland/general.conf @@ -59,6 +59,8 @@ decoration { contrast = 1 popups = true popups_ignorealpha = 0.6 + input_methods = true + input_methods_ignorealpha = 0.8 } shadow { From c1a4670de2712f699ee311786b1cac959e3de5ce Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 16:51:02 +0200 Subject: [PATCH 692/824] yad -> kdialog (closes #1367) --- .config/quickshell/scripts/switchwall.sh | 2 +- arch-packages/illogical-impulse-toolkit/PKGBUILD | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/scripts/switchwall.sh b/.config/quickshell/scripts/switchwall.sh index 22a87e158..838bb8b9c 100755 --- a/.config/quickshell/scripts/switchwall.sh +++ b/.config/quickshell/scripts/switchwall.sh @@ -267,7 +267,7 @@ main() { # Only prompt for wallpaper if not using --color and not using --noswitch and no imgpath set if [[ -z "$imgpath" && -z "$color_flag" && -z "$noswitch_flag" ]]; then cd "$(xdg-user-dir PICTURES)/Wallpapers" 2>/dev/null || cd "$(xdg-user-dir PICTURES)" || return 1 - imgpath="$(yad --width 1200 --height 800 --file --add-preview --large-preview --title='Choose wallpaper')" + imgpath="$(kdialog --getopenfilename . --title 'Choose wallpaper')" fi switch "$imgpath" "$mode_flag" "$type_flag" "$color_flag" "$color" diff --git a/arch-packages/illogical-impulse-toolkit/PKGBUILD b/arch-packages/illogical-impulse-toolkit/PKGBUILD index c73e804b0..673e12f8b 100644 --- a/arch-packages/illogical-impulse-toolkit/PKGBUILD +++ b/arch-packages/illogical-impulse-toolkit/PKGBUILD @@ -5,6 +5,7 @@ pkgdesc='Illogical Impulse GTK/Qt Dependencies' arch=(any) license=(None) depends=( + kdialog qt6-5compat qt6-base qt6-declarative @@ -22,6 +23,5 @@ depends=( upower wtype xdg-user-dirs-gtk - yad ydotool ) From 181ac0561dca8e5e7f117961a7e18df4a822e48c Mon Sep 17 00:00:00 2001 From: Bishoy Ehab Date: Sat, 7 Jun 2025 20:12:50 +0300 Subject: [PATCH 693/824] Add some space --- update.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/update.sh b/update.sh index 4769c01fe..78202a833 100755 --- a/update.sh +++ b/update.sh @@ -822,8 +822,8 @@ if [[ ! -f "$HOME_UPDATE_IGNORE_FILE" && ! -f "$UPDATE_IGNORE_FILE" ]]; then echo " *.log # Ignore all .log files" echo " .config/personal/ # Ignore entire directory" echo " secret-config.conf # Ignore specific file" - echo " /temp-file # Ignore from root only" - echo " *secret* # Ignore files containing 'secret'" + echo " /temp-file # Ignore from root only" + echo " *secret* # Ignore files containing 'secret'" fi echo From 57327ab0fee4f255ccfb5bffb15c957584e99108 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 19:43:05 +0200 Subject: [PATCH 694/824] earbang protection --- .../modules/common/ConfigOptions.qml | 8 ++ .../OnScreenDisplayBrightness.qml | 2 +- .../onScreenDisplay/OnScreenDisplayVolume.qml | 87 +++++++++++++++---- .config/quickshell/services/Audio.qml | 37 +++++++- 4 files changed, 114 insertions(+), 20 deletions(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 91ba70408..529c04272 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -12,6 +12,14 @@ Singleton { property int fakeScreenRounding: 1 // 0: None | 1: Always | 2: When not fullscreen } + property QtObject audio: QtObject { // Values in % + property QtObject protection: QtObject { // Prevent sudden bangs + property bool enable: true + property real maxAllowedIncrease: 10 + property real maxAllowed: 90 // Realistically should already provide some protection when it's 99... + } + } + property QtObject apps: QtObject { property string bluetooth: "better-control --bluetooth" property string imageViewer: "loupe" diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml index decb7537c..765386bc8 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml @@ -73,7 +73,7 @@ Scope { item: osdValuesWrapper } - implicitWidth: Appearance.sizes.osdWidth + implicitWidth: columnLayout.implicitWidth implicitHeight: columnLayout.implicitHeight visible: osdLoader.active diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml index e1904354e..5d23b4405 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml @@ -12,6 +12,7 @@ import Quickshell.Hyprland Scope { id: root property bool showOsdValues: false + property string protectionMessage: "" property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) function triggerOsd() { @@ -25,7 +26,8 @@ Scope { repeat: false running: false onTriggered: { - showOsdValues = false + root.showOsdValues = false + root.protectionMessage = "" } } @@ -36,7 +38,7 @@ Scope { } } - Connections { + Connections { // Listen to volume changes target: Audio.sink?.audio ?? null function onVolumeChanged() { if (!Audio.ready) return @@ -48,6 +50,14 @@ Scope { } } + Connections { // Listen to protection triggers + target: Audio + function onSinkProtectionTriggered(reason) { + root.protectionMessage = reason; + root.triggerOsd() + } + } + Loader { id: osdLoader active: showOsdValues @@ -75,7 +85,7 @@ Scope { item: osdValuesWrapper } - implicitWidth: Appearance.sizes.osdWidth + implicitWidth: columnLayout.implicitWidth implicitHeight: columnLayout.implicitHeight visible: osdLoader.active @@ -85,8 +95,8 @@ Scope { Item { id: osdValuesWrapper // Extra space for shadow - implicitHeight: osdValues.implicitHeight + Appearance.sizes.elevationMargin * 2 - implicitWidth: osdValues.implicitWidth + implicitHeight: contentColumnLayout.implicitHeight + Appearance.sizes.elevationMargin * 2 + implicitWidth: contentColumnLayout.implicitWidth clip: true MouseArea { @@ -95,20 +105,63 @@ Scope { onEntered: root.showOsdValues = false } - Behavior on implicitHeight { - NumberAnimation { - duration: Appearance.animation.menuDecel.duration - easing.type: Appearance.animation.menuDecel.type + ColumnLayout { + id: contentColumnLayout + anchors { + top: parent.top + left: parent.left + right: parent.right + leftMargin: Appearance.sizes.elevationMargin + rightMargin: Appearance.sizes.elevationMargin } - } + spacing: 0 - OsdValueIndicator { - id: osdValues - anchors.fill: parent - anchors.margins: Appearance.sizes.elevationMargin - value: Audio.sink?.audio.volume ?? 0 - icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up" - name: qsTr("Volume") + OsdValueIndicator { + id: osdValues + Layout.fillWidth: true + value: Audio.sink?.audio.volume ?? 0 + icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up" + name: qsTr("Volume") + } + + Item { + id: protectionMessageWrapper + implicitHeight: protectionMessageBackground.implicitHeight + implicitWidth: protectionMessageBackground.implicitWidth + Layout.alignment: Qt.AlignHCenter + opacity: root.protectionMessage !== "" ? 1 : 0 + + StyledRectangularShadow { + target: protectionMessageBackground + } + Rectangle { + id: protectionMessageBackground + anchors.centerIn: parent + color: Appearance.m3colors.m3error + property real padding: 10 + implicitHeight: protectionMessageRowLayout.implicitHeight + padding * 2 + implicitWidth: protectionMessageRowLayout.implicitWidth + padding * 2 + radius: Appearance.rounding.normal + + RowLayout { + id: protectionMessageRowLayout + anchors.centerIn: parent + MaterialSymbol { + id: protectionMessageIcon + text: "dangerous" + iconSize: Appearance.font.pixelSize.hugeass + color: Appearance.m3colors.m3onError + } + StyledText { + id: protectionMessageTextWidget + horizontalAlignment: Text.AlignHCenter + color: Appearance.m3colors.m3onError + wrapMode: Text.Wrap + text: root.protectionMessage + } + } + } + } } } } diff --git a/.config/quickshell/services/Audio.qml b/.config/quickshell/services/Audio.qml index 8b5f9b760..dd46f0fbb 100644 --- a/.config/quickshell/services/Audio.qml +++ b/.config/quickshell/services/Audio.qml @@ -1,3 +1,4 @@ +import "root:/modules/common" import QtQuick import Quickshell import Quickshell.Services.Pipewire @@ -11,11 +12,43 @@ Singleton { id: root property bool ready: Pipewire.defaultAudioSink?.ready ?? false - property var sink: Pipewire.defaultAudioSink - property var source: Pipewire.defaultAudioSource + property PwNode sink: Pipewire.defaultAudioSink + property PwNode source: Pipewire.defaultAudioSource + + signal sinkProtectionTriggered(string reason); PwObjectTracker { objects: [sink, source] + Component.onCompleted: { + sink.audio.volume = sink.audio.volume; // Trigger initial volume change + } + } + + Connections { // Protection against sudden volume changes + target: sink?.audio ?? null + property bool lastReady: false + property real lastVolume: 0 + function onVolumeChanged() { + if (!ConfigOptions.audio.protection.enable) return; + if (!lastReady) { + lastVolume = sink.audio.volume; + lastReady = true; + return; + } + const newVolume = sink.audio.volume; + const maxAllowedIncrease = ConfigOptions.audio.protection.maxAllowedIncrease / 100; + const maxAllowed = ConfigOptions.audio.protection.maxAllowed / 100; + + if (newVolume - lastVolume > maxAllowedIncrease) { + sink.audio.volume = lastVolume; + root.sinkProtectionTriggered("Illegal increment"); + } else if (newVolume > maxAllowed) { + sink.audio.volume = lastVolume; + root.sinkProtectionTriggered("Exceeded max allowed"); + } + lastVolume = sink.audio.volume; + } + } } From 1591b72e4a49ae98236992aeb16a1c8da2b4d9c5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 21:00:51 +0200 Subject: [PATCH 695/824] installation: remove ags --- diagnose | 10 ---------- install.sh | 43 ++++++---------------------------------- manual-install-helper.sh | 1 - 3 files changed, 6 insertions(+), 48 deletions(-) diff --git a/diagnose b/diagnose index 1964d2d66..df1d0dee4 100755 --- a/diagnose +++ b/diagnose @@ -38,16 +38,6 @@ ii_check_venv() { which python deactivate } -ii_check_ags() { - pkill ags - pkill agsv1 - agsv1 > ii_ags.log 2>&1 & - GUI_PID=$! - sleep 10 - kill $GUI_PID - echo "AGS log saved to \"ii_ags.log\"." -} -#x ii_check_ags e "Checking git repo info" x git remote get-url origin diff --git a/install.sh b/install.sh index 58cc6c441..7b47af0d4 100755 --- a/install.sh +++ b/install.sh @@ -155,11 +155,11 @@ v mkdir -p $XDG_BIN_HOME $XDG_CACHE_HOME $XDG_CONFIG_HOME $XDG_DATA_HOME # original dotfiles and new ones in the SAME DIRECTORY # (eg. in ~/.config/hypr) won't be mixed together -# MISC (For .config/* but not AGS, not Fish, not Hyprland) +# MISC (For .config/* but not fish, not Hyprland) case $SKIP_MISCCONF in true) sleep 0;; *) - for i in $(find .config/ -mindepth 1 -maxdepth 1 ! -name 'ags' ! -name 'fish' ! -name 'hypr' -exec basename {} \;); do + for i in $(find .config/ -mindepth 1 -maxdepth 1 ! -name 'fish' ! -name 'hypr' -exec basename {} \;); do # i=".config/$i" echo "[$0]: Found target: .config/$i" if [ -d ".config/$i" ];then v rsync -av --delete ".config/$i/" "$XDG_CONFIG_HOME/$i/" @@ -176,24 +176,6 @@ case $SKIP_FISH in ;; esac -# For AGS -case $SKIP_AGS in - true) sleep 0;; - *) - v rsync -av --delete --exclude '/user_options.jsonc' .config/ags/ "$XDG_CONFIG_HOME"/ags/ - t="$XDG_CONFIG_HOME/ags/user_options.jsonc" - if [ -f $t ];then - echo -e "\e[34m[$0]: \"$t\" already exists.\e[0m" - # v cp -f .config/ags/user_options.jsonc $t.new - existed_ags_opt=y - else - echo -e "\e[33m[$0]: \"$t\" does not exist yet.\e[0m" - v cp .config/ags/user_options.jsonc $t - existed_ags_opt=n - fi - ;; -esac - # For Hyprland case $SKIP_HYPRLAND in true) sleep 0;; @@ -202,15 +184,9 @@ case $SKIP_HYPRLAND in t="$XDG_CONFIG_HOME/hypr/hyprland.conf" if [ -f $t ];then echo -e "\e[34m[$0]: \"$t\" already exists.\e[0m" - if [ -f "$XDG_STATE_HOME/ags/user/firstrun.txt" ] - then - v cp -f .config/hypr/hyprland.conf $t.new - existed_hypr_conf=y - else - v mv $t $t.old - v cp -f .config/hypr/hyprland.conf $t - existed_hypr_conf_firstrun=y - fi + v mv $t $t.old + v cp -f .config/hypr/hyprland.conf $t + existed_hypr_conf_firstrun=y else echo -e "\e[33m[$0]: \"$t\" does not exist yet.\e[0m" v cp .config/hypr/hyprland.conf $t @@ -260,10 +236,7 @@ grep -q 'source ${XDG_CONFIG_HOME:-~/.config}/zshrc.d/dots-hyprland.zsh' ~/.zshr warn_files=() warn_files_tests=() -warn_files_tests+=(/usr/local/bin/ags) -warn_files_tests+=(/usr/local/etc/pam.d/ags) warn_files_tests+=(/usr/local/lib/{GUtils-1.0.typelib,Gvc-1.0.typelib,libgutils.so,libgvc.so}) -warn_files_tests+=(/usr/local/share/com.github.Aylur.ags) warn_files_tests+=(/usr/local/share/fonts/TTF/Rubik{,-Italic}'[wght]'.ttf) warn_files_tests+=(/usr/local/share/licenses/ttf-rubik) warn_files_tests+=(/usr/local/share/fonts/TTF/Gabarito-{Black,Bold,ExtraBold,Medium,Regular,SemiBold}.ttf) @@ -289,10 +262,6 @@ printf "\e[36mPress \e[30m\e[46m Ctrl+Super+T \e[0m\e[36m to select a wallpaper\ printf "\e[36mPress \e[30m\e[46m Super+/ \e[0m\e[36m for a list of keybinds\e[0m\n" printf "\n" -case $existed_ags_opt in - y) printf "\n\e[33m[$0]: Warning: \"$XDG_CONFIG_HOME/ags/user_options.jsonc\" already existed before and we didn't overwrite it. \e[0m\n" -# printf "\e[33mPlease use \"$XDG_CONFIG_HOME/ags/user_options.jsonc.new\" as a reference for a proper format.\e[0m\n" -;;esac case $existed_hypr_conf_firstrun in y) printf "\n\e[33m[$0]: Warning: \"$XDG_CONFIG_HOME/hypr/hyprland.conf\" already existed before. As it seems it is your first run, we replaced it with a new one. \e[0m\n" printf "\e[33mAs it seems it is your first run, we replaced it with a new one. The old one has been renamed to \"$XDG_CONFIG_HOME/hypr/hyprland.conf.old\".\e[0m\n" @@ -311,7 +280,7 @@ case $existed_hyprlock_conf in ;;esac if [[ -z "${ILLOGICAL_IMPULSE_VIRTUAL_ENV}" ]]; then - printf "\n\e[31m[$0]: \!! Important \!! : Please ensure environment variable \e[0m \$ILLOGICAL_IMPULSE_VIRTUAL_ENV \e[31m is set to proper value (by default \"~/.local/state/ags/.venv\"), or AGS config will not work. We have already provided this configuration in ~/.config/hypr/hyprland/env.conf, but you need to ensure it is included in hyprland.conf, and also a restart is needed for applying it.\e[0m\n" + printf "\n\e[31m[$0]: \!! Important \!! : Please ensure environment variable \e[0m \$ILLOGICAL_IMPULSE_VIRTUAL_ENV \e[31m is set to proper value (by default \"~/.local/state/quickshell/.venv\"), or Quickshell config will not work. We have already provided this configuration in ~/.config/hypr/hyprland/env.conf, but you need to ensure it is included in hyprland.conf, and also a restart is needed for applying it.\e[0m\n" fi if [[ ! -z "${warn_files[@]}" ]]; then diff --git a/manual-install-helper.sh b/manual-install-helper.sh index 663f3009a..d594f2c16 100755 --- a/manual-install-helper.sh +++ b/manual-install-helper.sh @@ -11,7 +11,6 @@ source ./scriptdata/installers prevent_sudo_or_root if command -v pacman >/dev/null 2>&1;then printf "\e[31m[$0]: pacman found, it seems that the system is ArchLinux or Arch-based distro. Aborting...\e[0m\n";exit 1;fi -v install-agsv1 v install-Rubik v install-Gabarito v install-OneUI From 5364d0e4f7fc5fc6ae00190cd2a222107bd4dc14 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 21:45:52 +0200 Subject: [PATCH 696/824] dock: use quickshell-native way to open apps --- .config/quickshell/modules/dock/Dock.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/dock/Dock.qml b/.config/quickshell/modules/dock/Dock.qml index cbb291185..7123c0810 100644 --- a/.config/quickshell/modules/dock/Dock.qml +++ b/.config/quickshell/modules/dock/Dock.qml @@ -116,9 +116,11 @@ Scope { // Scope model: ConfigOptions?.dock.pinnedApps ?? [] DockButton { + id: pinnedAppButton required property string modelData + property DesktopEntry entry: DesktopEntries.byId(modelData) onClicked: { - Hyprland.dispatch(`exec gio launch ${modelData}`) + pinnedAppButton?.entry.execute(); } contentItem: IconImage { anchors.centerIn: parent From 0d4877a77ecdf7d08a8eb3cc65b33eec075b12c9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 21:57:57 +0200 Subject: [PATCH 697/824] dock: display num of open windows --- .../quickshell/modules/dock/DockAppButton.qml | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/dock/DockAppButton.qml b/.config/quickshell/modules/dock/DockAppButton.qml index 7ff11bae1..65e8bf88d 100644 --- a/.config/quickshell/modules/dock/DockAppButton.qml +++ b/.config/quickshell/modules/dock/DockAppButton.qml @@ -17,6 +17,10 @@ DockButton { required property var appToplevel property var appListRoot property int lastFocused: -1 + property real iconSize: 35 + property real countDotWidth: 10 + property real countDotHeight: 4 + MouseArea { id: mouseArea anchors.fill: parent @@ -37,8 +41,38 @@ DockButton { lastFocused = (lastFocused + 1) % appToplevel.toplevels.length appToplevel.toplevels[lastFocused].activate() } - contentItem: IconImage { - id: iconImage - source: Quickshell.iconPath(AppSearch.guessIcon(appToplevel.appId), "image-missing") + contentItem: Item { + anchors.centerIn: parent + + IconImage { + id: iconImage + anchors { + left: parent.left + right: parent.right + verticalCenter: parent.verticalCenter + } + source: Quickshell.iconPath(AppSearch.guessIcon(appToplevel.appId), "image-missing") + implicitSize: appButton.iconSize + } + + RowLayout { + spacing: 3 + anchors { + top: iconImage.bottom + topMargin: 2 + horizontalCenter: parent.horizontalCenter + } + Repeater { + model: Math.min(appToplevel.toplevels.length, 3) + delegate: Rectangle { + required property int index + radius: Appearance.rounding.full + implicitWidth: (appToplevel.toplevels.length <= 3) ? + appButton.countDotWidth : appButton.countDotHeight // Circles when too many + implicitHeight: appButton.countDotHeight + color: Appearance.m3colors.m3primary + } + } + } } } From db1fc0f6be19db6116b5f4890ff4337d8c0df673 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 22:23:03 +0200 Subject: [PATCH 698/824] dock: fix bottom spacing --- .config/quickshell/modules/dock/Dock.qml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.config/quickshell/modules/dock/Dock.qml b/.config/quickshell/modules/dock/Dock.qml index 7123c0810..0e5cc4836 100644 --- a/.config/quickshell/modules/dock/Dock.qml +++ b/.config/quickshell/modules/dock/Dock.qml @@ -34,7 +34,9 @@ Scope { // Scope function hide() { cheatsheetLoader.active = false } - exclusiveZone: root.pinned ? implicitHeight - Appearance.sizes.hyprlandGapsOut : 0 + exclusiveZone: root.pinned ? implicitHeight + - (Appearance.sizes.hyprlandGapsOut) + - (Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut) : 0 implicitWidth: dockBackground.implicitWidth WlrLayershell.namespace: "quickshell:dock" @@ -63,11 +65,13 @@ Scope { // Scope id: dockHoverRegion anchors.fill: parent - Item { + Item { // Wrapper for the dock background id: dockBackground - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter + anchors { + top: parent.top + bottom: parent.bottom + horizontalCenter: parent.horizontalCenter + } implicitWidth: dockRow.implicitWidth + 5 * 2 height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut @@ -75,12 +79,12 @@ Scope { // Scope StyledRectangularShadow { target: dockVisualBackground } - Rectangle { + Rectangle { // The real rectangle that is visible id: dockVisualBackground property real margin: Appearance.sizes.elevationMargin anchors.fill: parent anchors.topMargin: margin - anchors.bottomMargin: margin + anchors.bottomMargin: Appearance.sizes.hyprlandGapsOut color: Appearance.colors.colLayer0 radius: Appearance.rounding.large } From 99ab1b179b38fd88cb7780ede492aec9a16182a0 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 22:24:21 +0200 Subject: [PATCH 699/824] dock: pin kitty by default --- .config/quickshell/modules/common/ConfigOptions.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 529c04272..96832c48f 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -58,6 +58,7 @@ Singleton { property bool pinnedOnStartup: false property list pinnedApps: [ // IDs of pinned entries "org.kde.dolphin", + "kitty", ] } From a8474af78d7028610f86ce5e46501e3bd9543135 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 22:37:03 +0200 Subject: [PATCH 700/824] ai: comment debug print --- .config/quickshell/services/Ai.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index 7b3084fca..dbb8869d7 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -611,7 +611,7 @@ Singleton { stdout: SplitParser { onRead: data => { - console.log("RAW DATA: ", data); + // console.log("RAW DATA: ", data); if (data.length === 0) return; // Handle response line From 1add44ba2fb758abf4f256b230c31c64ca82a1b0 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 22:37:32 +0200 Subject: [PATCH 701/824] dock: show on empty workspace --- .config/quickshell/modules/common/ConfigOptions.qml | 3 ++- .config/quickshell/modules/dock/Dock.qml | 5 ++++- .config/quickshell/modules/dock/DockAppButton.qml | 1 + .config/quickshell/shell.qml | 4 ++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 96832c48f..ee0522f57 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -52,10 +52,11 @@ Singleton { } property QtObject dock: QtObject { - property bool enable: false + property bool enable: true property real height: 60 property real hoverRegionHeight: 3 property bool pinnedOnStartup: false + property bool hoverToReveal: false // When false, only reveals on empty workspace property list pinnedApps: [ // IDs of pinned entries "org.kde.dolphin", "kitty", diff --git a/.config/quickshell/modules/dock/Dock.qml b/.config/quickshell/modules/dock/Dock.qml index 0e5cc4836..69e36778a 100644 --- a/.config/quickshell/modules/dock/Dock.qml +++ b/.config/quickshell/modules/dock/Dock.qml @@ -23,7 +23,10 @@ Scope { // Scope id: dockRoot screen: modelData - property bool reveal: root.pinned || dockMouseArea.containsMouse || dockApps.requestDockShow + property bool reveal: root.pinned + || ((ConfigOptions?.dock.hoverToReveal ?? true) && dockMouseArea.containsMouse) + || dockApps.requestDockShow + || (!ToplevelManager.activeToplevel?.activated) anchors { bottom: true diff --git a/.config/quickshell/modules/dock/DockAppButton.qml b/.config/quickshell/modules/dock/DockAppButton.qml index 65e8bf88d..7e79ad626 100644 --- a/.config/quickshell/modules/dock/DockAppButton.qml +++ b/.config/quickshell/modules/dock/DockAppButton.qml @@ -20,6 +20,7 @@ DockButton { property real iconSize: 35 property real countDotWidth: 10 property real countDotHeight: 4 + property bool appIsActive: appToplevel.toplevels.find(t => (t.activated == true)) !== undefined MouseArea { id: mouseArea diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index bc2cb0221..e6e5d566c 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -27,7 +27,7 @@ ShellRoot { // no unnecessary stuff will take up memory if you decide to only use, say, the overview. property bool enableBar: true property bool enableCheatsheet: true - property bool enableDock: false + property bool enableDock: true property bool enableMediaControls: true property bool enableNotificationPopup: true property bool enableOnScreenDisplayBrightness: true @@ -51,7 +51,7 @@ ShellRoot { Loader { active: enableBar; sourceComponent: Bar {} } Loader { active: enableCheatsheet; sourceComponent: Cheatsheet {} } - Loader { active: (enableDock || ConfigOptions?.dock.enable); sourceComponent: Dock {} } + Loader { active: (enableDock && ConfigOptions?.dock.enable); sourceComponent: Dock {} } Loader { active: enableMediaControls; sourceComponent: MediaControls {} } Loader { active: enableNotificationPopup; sourceComponent: NotificationPopup {} } Loader { active: enableOnScreenDisplayBrightness; sourceComponent: OnScreenDisplayBrightness {} } From bbd2bfda5d665c84f7f28284b90d3341a9b7dfe9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 23:02:15 +0200 Subject: [PATCH 702/824] dock: adjust active color --- .config/quickshell/modules/dock/Dock.qml | 2 +- .config/quickshell/modules/dock/DockAppButton.qml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/dock/Dock.qml b/.config/quickshell/modules/dock/Dock.qml index 69e36778a..b89630228 100644 --- a/.config/quickshell/modules/dock/Dock.qml +++ b/.config/quickshell/modules/dock/Dock.qml @@ -24,7 +24,7 @@ Scope { // Scope screen: modelData property bool reveal: root.pinned - || ((ConfigOptions?.dock.hoverToReveal ?? true) && dockMouseArea.containsMouse) + || (ConfigOptions?.dock.hoverToReveal && dockMouseArea.containsMouse) || dockApps.requestDockShow || (!ToplevelManager.activeToplevel?.activated) diff --git a/.config/quickshell/modules/dock/DockAppButton.qml b/.config/quickshell/modules/dock/DockAppButton.qml index 7e79ad626..9df44c853 100644 --- a/.config/quickshell/modules/dock/DockAppButton.qml +++ b/.config/quickshell/modules/dock/DockAppButton.qml @@ -2,6 +2,7 @@ import "root:/" import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls import QtQuick.Effects @@ -71,7 +72,7 @@ DockButton { implicitWidth: (appToplevel.toplevels.length <= 3) ? appButton.countDotWidth : appButton.countDotHeight // Circles when too many implicitHeight: appButton.countDotHeight - color: Appearance.m3colors.m3primary + color: appIsActive ? Appearance.m3colors.m3primary : ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.4) } } } From c9ffb84ffc53a60803eaf8f0f53158d0a2626e54 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 7 Jun 2025 23:25:13 +0200 Subject: [PATCH 703/824] bar: borderless: add separators, no longer default --- .config/quickshell/modules/bar/Bar.qml | 15 +++++++++++++-- .../quickshell/modules/common/ConfigOptions.qml | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 891c9a2c8..ca9b1e57e 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -19,6 +19,14 @@ Scope { readonly property int osdHideMouseMoveThreshold: 20 property bool showBarBackground: ConfigOptions.bar.showBackground + component VerticalBarSeparator: Rectangle { + Layout.topMargin: barHeight / 3 + Layout.bottomMargin: barHeight / 3 + Layout.fillHeight: true + implicitWidth: 1 + color: Appearance.m3colors.m3outlineVariant + } + Variants { // For each monitor model: Quickshell.screens @@ -180,7 +188,7 @@ Scope { RowLayout { // Middle section id: middleSection anchors.centerIn: parent - spacing: 8 + spacing: ConfigOptions?.bar.borderless ? 4 : 8 RowLayout { id: leftCenterGroup @@ -201,9 +209,10 @@ Scope { } + VerticalBarSeparator {visible: ConfigOptions?.bar.borderless} + RowLayout { id: middleCenterGroup - Layout.fillWidth: true Layout.fillHeight: true Workspaces { @@ -222,6 +231,8 @@ Scope { } + VerticalBarSeparator {visible: ConfigOptions?.bar.borderless} + RowLayout { id: rightCenterGroup Layout.preferredWidth: leftCenterGroup.width diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index ee0522f57..852a4b97c 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -37,7 +37,7 @@ Singleton { property QtObject bar: QtObject { property bool bottom: false // Instead of top - property bool borderless: true + property bool borderless: false // true for no grouping of items property string topLeftIcon: "spark" // Options: distro, spark property bool showBackground: true property QtObject resources: QtObject { From 0b7f80ed0566bdcf254fb7a09da25d9735a8c431 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 8 Jun 2025 02:10:01 +0200 Subject: [PATCH 704/824] script for least busy region of image --- .config/matugen/scripts/least_busy_region.py | 253 +++++++++++++++++++ scriptdata/requirements.in | 1 + scriptdata/requirements.txt | 8 +- 3 files changed, 259 insertions(+), 3 deletions(-) create mode 100755 .config/matugen/scripts/least_busy_region.py diff --git a/.config/matugen/scripts/least_busy_region.py b/.config/matugen/scripts/least_busy_region.py new file mode 100755 index 000000000..68e08ca12 --- /dev/null +++ b/.config/matugen/scripts/least_busy_region.py @@ -0,0 +1,253 @@ +#!/usr/bin/env -S\_/bin/sh\_-c\_"source\_\$(eval\_echo\_\$ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate&&exec\_python\_-E\_"\$0"\_"\$@"" +# Disclaimer: This script is vibe-coded. + +import os +os.environ["OPENCV_LOG_LEVEL"] = "SILENT" +import cv2 +import numpy as np +import argparse +import json + +def find_least_busy_region(image_path, region_width=300, region_height=200, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill"): + img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) + if img is None: + raise FileNotFoundError(f"Image not found: {image_path}") + orig_h, orig_w = img.shape + scale = 1.0 + if screen_width is not None and screen_height is not None: + scale_w = screen_width / orig_w + scale_h = screen_height / orig_h + if screen_mode == "fill": + scale = max(scale_w, scale_h) + else: + scale = min(scale_w, scale_h) + new_w = int(orig_w * scale) + new_h = int(orig_h * scale) + if verbose: + print(f"Scaling image from {orig_w}x{orig_h} to {new_w}x{new_h} (scale: {scale:.3f}, mode: {screen_mode})") + img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4) + else: + if verbose: + print(f"Using original image size: {orig_w}x{orig_h}") + arr = img.astype(np.float64) + h, w = arr.shape + # Use OpenCV's integral for fast computation + integral = cv2.integral(arr, sdepth=cv2.CV_64F)[1:,1:] + integral_sq = cv2.integral(arr**2, sdepth=cv2.CV_64F)[1:,1:] + def region_sum(ii, x1, y1, x2, y2): + total = ii[y2, x2] + if x1 > 0: + total -= ii[y2, x1-1] + if y1 > 0: + total -= ii[y1-1, x2] + if x1 > 0 and y1 > 0: + total += ii[y1-1, x1-1] + return total + min_var = None + min_coords = (0, 0) + area = region_width * region_height + for y in range(0, h - region_height + 1, stride): + for x in range(0, w - region_width + 1, stride): + x1, y1 = x, y + x2, y2 = x + region_width - 1, y + region_height - 1 + s = region_sum(integral, x1, y1, x2, y2) + s2 = region_sum(integral_sq, x1, y1, x2, y2) + mean = s / area + var = (s2 / area) - (mean ** 2) + if (min_var is None) or (var < min_var): + min_var = var + min_coords = (x, y) + return min_coords, min_var + +def find_largest_region(image_path, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", threshold=100.0, aspect_ratio=1.0): + img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) + if img is None: + raise FileNotFoundError(f"Image not found: {image_path}") + orig_h, orig_w = img.shape + scale = 1.0 + if screen_width is not None and screen_height is not None: + scale_w = screen_width / orig_w + scale_h = screen_height / orig_h + if screen_mode == "fill": + scale = max(scale_w, scale_h) + else: + scale = min(scale_w, scale_h) + new_w = int(orig_w * scale) + new_h = int(orig_h * scale) + if verbose: + print(f"Scaling image from {orig_w}x{orig_h} to {new_w}x{new_h} (scale: {scale:.3f}, mode: {screen_mode})") + img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4) + else: + if verbose: + print(f"Using original image size: {orig_w}x{orig_h}") + arr = img.astype(np.float64) + h, w = arr.shape + # Use OpenCV's integral for fast computation + integral = cv2.integral(arr, sdepth=cv2.CV_64F)[1:,1:] + integral_sq = cv2.integral(arr**2, sdepth=cv2.CV_64F)[1:,1:] + def region_sum(ii, x1, y1, x2, y2): + total = ii[y2, x2] + if x1 > 0: + total -= ii[y2, x1-1] + if y1 > 0: + total -= ii[y1-1, x2] + if x1 > 0 and y1 > 0: + total += ii[y1-1, x1-1] + return total + min_size = 10 + max_size = min(h, int(w / aspect_ratio)) if aspect_ratio >= 1.0 else min(int(h * aspect_ratio), w) + best = None + best_size = min_size + while min_size <= max_size: + mid = (min_size + max_size) // 2 + if aspect_ratio >= 1.0: + region_h = mid + region_w = int(mid * aspect_ratio) + else: + region_w = mid + region_h = int(mid / aspect_ratio) + if region_w > w or region_h > h: + max_size = mid - 1 + continue + found = False + for y in range(0, h - region_h + 1, stride): + for x in range(0, w - region_w + 1, stride): + x1, y1 = x, y + x2, y2 = x + region_w - 1, y + region_h - 1 + s = region_sum(integral, x1, y1, x2, y2) + s2 = region_sum(integral_sq, x1, y1, x2, y2) + area = region_w * region_h + mean = s / area + var = (s2 / area) - (mean ** 2) + if var <= threshold: + found = True + best = (x, y, region_w, region_h, var) + break + if found: + break + if found: + best_size = mid + min_size = mid + 1 + else: + max_size = mid - 1 + if best: + x, y, region_w, region_h, var = best + center_x = x + region_w // 2 + center_y = y + region_h // 2 + return (center_x, center_y), (region_w, region_h), var + else: + return None, (0, 0), None + +def draw_region(image_path, coords, region_width=300, region_height=200, output_path='output.png', screen_width=None, screen_height=None, screen_mode="fill"): + img = cv2.imread(image_path) + if img is None: + raise FileNotFoundError(f"Image not found: {image_path}") + orig_h, orig_w = img.shape[:2] + if screen_width is not None and screen_height is not None: + scale_w = screen_width / orig_w + scale_h = screen_height / orig_h + if screen_mode == "fill": + scale = max(scale_w, scale_h) + else: + scale = min(scale_w, scale_h) + new_w = int(orig_w * scale) + new_h = int(orig_h * scale) + img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4) + x, y = coords + cv2.rectangle(img, (x, y), (x+region_width-1, y+region_height-1), (0,0,255), 3) + cv2.imwrite(output_path, img) + print(f"Saved output image with rectangle at {output_path}") + +def draw_largest_region(image_path, center, size, output_path='output.png', screen_width=None, screen_height=None, screen_mode="fill"): + img = cv2.imread(image_path) + if img is None: + raise FileNotFoundError(f"Image not found: {image_path}") + orig_h, orig_w = img.shape[:2] + if screen_width is not None and screen_height is not None: + scale_w = screen_width / orig_w + scale_h = screen_height / orig_h + if screen_mode == "fill": + scale = max(scale_w, scale_h) + else: + scale = min(scale_w, scale_h) + new_w = int(orig_w * scale) + new_h = int(orig_h * scale) + img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4) + cx, cy = center + region_w, region_h = size + x1 = cx - region_w // 2 + y1 = cy - region_h // 2 + x2 = cx + region_w // 2 - 1 + y2 = cy + region_h // 2 - 1 + cv2.rectangle(img, (x1, y1), (x2, y2), (255,0,0), 3) + cv2.imwrite(output_path, img) + print(f"Saved output image with largest region at {output_path}") + +def main(): + parser = argparse.ArgumentParser(description="Find least busy region in an image and output a JSON. Made for determining a suitable position for a wallpaper widget.") + parser.add_argument("image_path", help="Path to the input image") + parser.add_argument("--width", type=int, default=500, help="Region width") + parser.add_argument("--height", type=int, default=250, help="Region height") + parser.add_argument("-v", "--visual-output", action="store_true", help="Output image with rectangle") + parser.add_argument("--screen-width", type=int, default=1920, help="Screen width for wallpaper scaling") + parser.add_argument("--screen-height", type=int, default=1080, help="Screen height for wallpaper scaling") + parser.add_argument("--stride", type=int, default=4, help="Step size for sliding window (higher is faster, less precise)") + parser.add_argument("--screen-mode", choices=["fill", "fit"], default="fill", help="Wallpaper scaling mode: 'fill' (default) or 'fit'") + parser.add_argument("--verbose", action="store_true", help="Print verbose output") + parser.add_argument("-l", "--largest-region", action="store_true", help="Find the largest region under the variance threshold and output its center") + parser.add_argument("-t", "--variance-threshold", type=float, default=1000.0, help="Variance threshold for largest region mode") + parser.add_argument("--aspect-ratio", type=float, default=1.0, help="Aspect ratio (width/height) for largest region mode") + args = parser.parse_args() + + if args.largest_region: + center, size, var = find_largest_region( + args.image_path, + screen_width=args.screen_width, + screen_height=args.screen_height, + verbose=args.verbose, + stride=args.stride, + screen_mode=args.screen_mode, + threshold=args.variance_threshold, + aspect_ratio=args.aspect_ratio + ) + if center: + if args.visual_output: + draw_largest_region(args.image_path, center, size, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode) + # Output JSON + print(json.dumps({ + "center_x": center[0], + "center_y": center[1], + "width": size[0], + "height": size[1], + "variance": var + })) + else: + print(json.dumps({"error": "No region found under the threshold."})) + return + + coords, variance = find_least_busy_region( + args.image_path, + region_width=args.width, + region_height=args.height, + screen_width=args.screen_width, + screen_height=args.screen_height, + verbose=args.verbose, + stride=args.stride, + screen_mode=args.screen_mode + ) + if args.visual_output: + draw_region(args.image_path, coords, region_width=args.width, region_height=args.height, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode) + # Output JSON with center point + center_x = coords[0] + args.width // 2 + center_y = coords[1] + args.height // 2 + print(json.dumps({ + "center_x": center_x, + "center_y": center_y, + "width": args.width, + "height": args.height, + "variance": variance + })) + +if __name__ == "__main__": + main() + diff --git a/scriptdata/requirements.in b/scriptdata/requirements.in index 0b4ac323e..f312b7f15 100644 --- a/scriptdata/requirements.in +++ b/scriptdata/requirements.in @@ -8,3 +8,4 @@ materialyoucolor libsass material-color-utilities setproctitle +opencv-python diff --git a/scriptdata/requirements.txt b/scriptdata/requirements.txt index 802304132..a710f483a 100644 --- a/scriptdata/requirements.txt +++ b/scriptdata/requirements.txt @@ -11,7 +11,11 @@ material-color-utilities==0.2.1 materialyoucolor==2.0.10 # via -r scriptdata/requirements.in numpy==2.2.2 - # via material-color-utilities + # via + # material-color-utilities + # opencv-python +opencv-python==4.11.0.86 + # via -r scriptdata/requirements.in packaging==24.2 # via # build @@ -26,8 +30,6 @@ pycparser==2.22 # via cffi pyproject-hooks==1.2.0 # via build -# pywal==3.3.0 - # via -r scriptdata/requirements.in pywayland==0.4.18 # via -r scriptdata/requirements.in setproctitle==1.3.4 From d96abe7a4d6cf7de8389a116278241de261ec938 Mon Sep 17 00:00:00 2001 From: Bishoy Ehab Date: Sun, 8 Jun 2025 07:47:14 +0300 Subject: [PATCH 705/824] Add warining for users [the script is not fully tested] --- update.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/update.sh b/update.sh index 78202a833..8c54bb821 100755 --- a/update.sh +++ b/update.sh @@ -489,6 +489,14 @@ has_new_commits() { # Main script starts here log_header "Dotfiles Update Script" +log_warning "THIS SCRIPT IS NOT FULLY TESTED AND MAY CAUSE ISSUES!" +safe_read "BY CONTINUE YOU WILL USE IT AT YOUR OWN RISK (y/N): " response "N" + +if [[ ! "$response" =~ ^[Yy]$ ]]; then + log_error "Update aborted by user" + exit 1 +fi + # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in From 506fb857aa01574116c5a4797145677b4b8d905c Mon Sep 17 00:00:00 2001 From: Bishoy Ehab Date: Sun, 8 Jun 2025 07:58:04 +0300 Subject: [PATCH 706/824] Add --skip-notice argument and change the way to check the git repo --- update.sh | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/update.sh b/update.sh index 8c54bb821..098a6a0fa 100755 --- a/update.sh +++ b/update.sh @@ -13,7 +13,7 @@ set -uo pipefail # === Configuration === FORCE_CHECK=false CHECK_PACKAGES=false -REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +REPO_DIR="$(cd "$(dirname $0)" &>/dev/null && pwd)" ARCH_PACKAGES_DIR="${REPO_DIR}/arch-packages" UPDATE_IGNORE_FILE="${REPO_DIR}/.updateignore" HOME_UPDATE_IGNORE_FILE="${HOME}/.updateignore" @@ -489,13 +489,7 @@ has_new_commits() { # Main script starts here log_header "Dotfiles Update Script" -log_warning "THIS SCRIPT IS NOT FULLY TESTED AND MAY CAUSE ISSUES!" -safe_read "BY CONTINUE YOU WILL USE IT AT YOUR OWN RISK (y/N): " response "N" - -if [[ ! "$response" =~ ^[Yy]$ ]]; then - log_error "Update aborted by user" - exit 1 -fi +check=true # Parse command line arguments while [[ $# -gt 0 ]]; do @@ -530,6 +524,11 @@ while [[ $# -gt 0 ]]; do echo " - Interactive selection of packages to build" exit 0 ;; + --skip-notice) + log_warning "Skipping notice about script being untested" + check=false + shift + ;; *) log_error "Unknown option: $1" echo "Use --help for usage information" @@ -538,13 +537,26 @@ while [[ $# -gt 0 ]]; do esac done -# Check if we're in a git repository -if [[ ! -d "${REPO_DIR}/.git" ]]; then - die "Not in a git repository. Please run this script from your dotfiles repository." +if [[ "$check" == true ]]; then + log_warning "THIS SCRIPT IS NOT FULLY TESTED AND MAY CAUSE ISSUES!" + safe_read "BY CONTINUE YOU WILL USE IT AT YOUR OWN RISK (y/N): " response "N" + + if [[ ! "$response" =~ ^[Yy]$ ]]; then + log_error "Update aborted by user" + exit 1 + fi fi +# Check if we're in a git repository cd "$REPO_DIR" || die "Failed to change to repository directory" +if git rev-parse --is-inside-work-tree &>/dev/null; then + log_info "Running in git repository: $(git rev-parse --show-toplevel)" +else + log_error "Not in a git repository. Please run this script from your dotfiles repository." + exit 1 +fi + # Step 1: Pull latest commits log_header "Pulling Latest Changes" From f084cd7a23a9acc2c9942ec0b2374f6447fc07e9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 8 Jun 2025 08:19:15 +0200 Subject: [PATCH 707/824] material symbols: prevent memory spikes --- .config/quickshell/modules/common/widgets/MaterialSymbol.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml index dbbfff009..98584ce2d 100644 --- a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml +++ b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml @@ -6,6 +6,7 @@ Text { id: root property real iconSize: Appearance?.font.pixelSize.small ?? 16 property real fill: 0 + property real truncatedFill: Math.round(fill * 100) / 100 // Reduce memory consumption spikes from constant font remapping renderType: Text.NativeRendering font.hintingPreference: Font.PreferFullHinting verticalAlignment: Text.AlignVCenter @@ -22,7 +23,7 @@ Text { } font.variableAxes: { - "FILL": fill, + "FILL": truncatedFill, // "wght": font.weight, // "GRAD": 0, "opsz": iconSize, From 31c379cdfd4a7fb13d9f6771721b749e67f05881 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:27:38 +0200 Subject: [PATCH 708/824] dont enable dock by default --- .config/quickshell/modules/common/ConfigOptions.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 852a4b97c..77b21b49a 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -52,7 +52,7 @@ Singleton { } property QtObject dock: QtObject { - property bool enable: true + property bool enable: false property real height: 60 property real hoverRegionHeight: 3 property bool pinnedOnStartup: false From e18456d18e82db2e92d086129b865cb660e2b31e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:27:54 +0200 Subject: [PATCH 709/824] material symbol: use curverendering --- .../modules/common/widgets/MaterialSymbol.qml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml index 98584ce2d..07ec5025d 100644 --- a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml +++ b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml @@ -7,11 +7,13 @@ Text { property real iconSize: Appearance?.font.pixelSize.small ?? 16 property real fill: 0 property real truncatedFill: Math.round(fill * 100) / 100 // Reduce memory consumption spikes from constant font remapping - renderType: Text.NativeRendering - font.hintingPreference: Font.PreferFullHinting + renderType: Text.CurveRendering + font { + hintingPreference: Font.PreferFullHinting + family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded" + pixelSize: iconSize + } verticalAlignment: Text.AlignVCenter - font.family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded" - font.pixelSize: iconSize color: Appearance.m3colors.m3onBackground Behavior on fill { From be9b7f3629138067d07b491701ba64819578a836 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:28:54 +0200 Subject: [PATCH 710/824] least_busy_region.py: take files with spaces properly --- .config/matugen/scripts/least_busy_region.py | 115 +++++++++++++++--- .../illogical-impulse-python/PKGBUILD | 1 + scriptdata/requirements.in | 1 - scriptdata/requirements.txt | 6 +- 4 files changed, 102 insertions(+), 21 deletions(-) diff --git a/.config/matugen/scripts/least_busy_region.py b/.config/matugen/scripts/least_busy_region.py index 68e08ca12..7ae245dd4 100755 --- a/.config/matugen/scripts/least_busy_region.py +++ b/.config/matugen/scripts/least_busy_region.py @@ -1,4 +1,4 @@ -#!/usr/bin/env -S\_/bin/sh\_-c\_"source\_\$(eval\_echo\_\$ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate&&exec\_python\_-E\_"\$0"\_"\$@"" +#!/usr/bin/env python3 # Disclaimer: This script is vibe-coded. import os @@ -8,7 +8,17 @@ import numpy as np import argparse import json -def find_least_busy_region(image_path, region_width=300, region_height=200, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill"): +def center_crop(img, target_w, target_h): + h, w = img.shape[:2] + if w == target_w and h == target_h: + return img + x1 = max(0, (w - target_w) // 2) + y1 = max(0, (h - target_h) // 2) + x2 = x1 + target_w + y2 = y1 + target_h + return img[y1:y2, x1:x2] + +def find_least_busy_region(image_path, region_width=300, region_height=200, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", padding=50): img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) if img is None: raise FileNotFoundError(f"Image not found: {image_path}") @@ -26,6 +36,9 @@ def find_least_busy_region(image_path, region_width=300, region_height=200, scre if verbose: print(f"Scaling image from {orig_w}x{orig_h} to {new_w}x{new_h} (scale: {scale:.3f}, mode: {screen_mode})") img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4) + img = center_crop(img, screen_width, screen_height) + if verbose: + print(f"Cropped image to {screen_width}x{screen_height}") else: if verbose: print(f"Using original image size: {orig_w}x{orig_h}") @@ -46,8 +59,12 @@ def find_least_busy_region(image_path, region_width=300, region_height=200, scre min_var = None min_coords = (0, 0) area = region_width * region_height - for y in range(0, h - region_height + 1, stride): - for x in range(0, w - region_width + 1, stride): + x_start = padding + y_start = padding + x_end = w - region_width - padding + 1 + y_end = h - region_height - padding + 1 + for y in range(y_start, max(y_end, y_start+1), stride): + for x in range(x_start, max(x_end, x_start+1), stride): x1, y1 = x, y x2, y2 = x + region_width - 1, y + region_height - 1 s = region_sum(integral, x1, y1, x2, y2) @@ -59,7 +76,7 @@ def find_least_busy_region(image_path, region_width=300, region_height=200, scre min_coords = (x, y) return min_coords, min_var -def find_largest_region(image_path, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", threshold=100.0, aspect_ratio=1.0): +def find_largest_region(image_path, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", threshold=100.0, aspect_ratio=1.0, padding=50): img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) if img is None: raise FileNotFoundError(f"Image not found: {image_path}") @@ -77,6 +94,9 @@ def find_largest_region(image_path, screen_width=None, screen_height=None, verbo if verbose: print(f"Scaling image from {orig_w}x{orig_h} to {new_w}x{new_h} (scale: {scale:.3f}, mode: {screen_mode})") img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4) + img = center_crop(img, screen_width, screen_height) + if verbose: + print(f"Cropped image to {screen_width}x{screen_height}") else: if verbose: print(f"Using original image size: {orig_w}x{orig_h}") @@ -110,8 +130,12 @@ def find_largest_region(image_path, screen_width=None, screen_height=None, verbo max_size = mid - 1 continue found = False - for y in range(0, h - region_h + 1, stride): - for x in range(0, w - region_w + 1, stride): + x_start = padding + y_start = padding + x_end = w - region_w - padding + 1 + y_end = h - region_h - padding + 1 + for y in range(y_start, max(y_end, y_start+1), stride): + for x in range(x_start, max(x_end, x_start+1), stride): x1, y1 = x, y x2, y2 = x + region_w - 1, y + region_h - 1 s = region_sum(integral, x1, y1, x2, y2) @@ -153,6 +177,7 @@ def draw_region(image_path, coords, region_width=300, region_height=200, output_ new_w = int(orig_w * scale) new_h = int(orig_h * scale) img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4) + img = center_crop(img, screen_width, screen_height) x, y = coords cv2.rectangle(img, (x, y), (x+region_width-1, y+region_height-1), (0,0,255), 3) cv2.imwrite(output_path, img) @@ -173,6 +198,7 @@ def draw_largest_region(image_path, center, size, output_path='output.png', scre new_w = int(orig_w * scale) new_h = int(orig_h * scale) img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4) + img = center_crop(img, screen_width, screen_height) cx, cy = center region_w, region_h = size x1 = cx - region_w // 2 @@ -183,11 +209,51 @@ def draw_largest_region(image_path, center, size, output_path='output.png', scre cv2.imwrite(output_path, img) print(f"Saved output image with largest region at {output_path}") +def get_dominant_color(image_path, x, y, w, h, screen_width=None, screen_height=None, screen_mode="fill"): + img = cv2.imread(image_path) + if img is None: + raise FileNotFoundError(f"Image not found: {image_path}") + orig_h, orig_w = img.shape[:2] + if screen_width is not None and screen_height is not None: + scale_w = screen_width / orig_w + scale_h = screen_height / orig_h + if screen_mode == "fill": + scale = max(scale_w, scale_h) + else: + scale = min(scale_w, scale_h) + new_w = int(orig_w * scale) + new_h = int(orig_h * scale) + img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4) + img = center_crop(img, screen_width, screen_height) + # Ensure region is within bounds + x = max(0, x) + y = max(0, y) + w = max(1, min(w, img.shape[1] - x)) + h = max(1, min(h, img.shape[0] - y)) + region = img[y:y+h, x:x+w] + if region.size == 0 or region.shape[0] == 0 or region.shape[1] == 0: + return [0, 0, 0] + region = region.reshape((-1, 3)) + # Filter out black pixels (optional, improves accuracy for some images) + non_black = region[np.any(region > 10, axis=1)] + if non_black.shape[0] == 0: + non_black = region + region = np.float32(non_black) + if region.shape[0] < 3: + return [int(x) for x in np.mean(region, axis=0)] + # K-means to find dominant color + criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0) + K = min(3, region.shape[0]) + _, labels, centers = cv2.kmeans(region, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS) + counts = np.bincount(labels.flatten()) + dominant = centers[np.argmax(counts)] + return [int(x) for x in dominant] + def main(): parser = argparse.ArgumentParser(description="Find least busy region in an image and output a JSON. Made for determining a suitable position for a wallpaper widget.") parser.add_argument("image_path", help="Path to the input image") - parser.add_argument("--width", type=int, default=500, help="Region width") - parser.add_argument("--height", type=int, default=250, help="Region height") + parser.add_argument("--width", type=int, default=300, help="Region width") + parser.add_argument("--height", type=int, default=200, help="Region height") parser.add_argument("-v", "--visual-output", action="store_true", help="Output image with rectangle") parser.add_argument("--screen-width", type=int, default=1920, help="Screen width for wallpaper scaling") parser.add_argument("--screen-height", type=int, default=1080, help="Screen height for wallpaper scaling") @@ -196,7 +262,8 @@ def main(): parser.add_argument("--verbose", action="store_true", help="Print verbose output") parser.add_argument("-l", "--largest-region", action="store_true", help="Find the largest region under the variance threshold and output its center") parser.add_argument("-t", "--variance-threshold", type=float, default=1000.0, help="Variance threshold for largest region mode") - parser.add_argument("--aspect-ratio", type=float, default=1.0, help="Aspect ratio (width/height) for largest region mode") + parser.add_argument("--aspect-ratio", type=float, default=1.78, help="Aspect ratio (width/height) for largest region mode") + parser.add_argument("--padding", type=int, default=50, help="Minimum distance from region to image edge (default: 50)") args = parser.parse_args() if args.largest_region: @@ -208,18 +275,29 @@ def main(): stride=args.stride, screen_mode=args.screen_mode, threshold=args.variance_threshold, - aspect_ratio=args.aspect_ratio + aspect_ratio=args.aspect_ratio, + padding=args.padding ) if center: if args.visual_output: draw_largest_region(args.image_path, center, size, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode) - # Output JSON + # Extract dominant color + cx, cy = center + region_w, region_h = size + x1 = cx - region_w // 2 + y1 = cy - region_h // 2 + dominant_color = get_dominant_color( + args.image_path, x1, y1, region_w, region_h, + screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode + ) + dominant_color_hex = '#{:02x}{:02x}{:02x}'.format(*dominant_color) print(json.dumps({ "center_x": center[0], "center_y": center[1], "width": size[0], "height": size[1], - "variance": var + "variance": var, + "dominant_color": dominant_color_hex })) else: print(json.dumps({"error": "No region found under the threshold."})) @@ -233,19 +311,26 @@ def main(): screen_height=args.screen_height, verbose=args.verbose, stride=args.stride, - screen_mode=args.screen_mode + screen_mode=args.screen_mode, + padding=args.padding ) if args.visual_output: draw_region(args.image_path, coords, region_width=args.width, region_height=args.height, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode) # Output JSON with center point center_x = coords[0] + args.width // 2 center_y = coords[1] + args.height // 2 + dominant_color = get_dominant_color( + args.image_path, coords[0], coords[1], args.width, args.height, + screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode + ) + dominant_color_hex = '#{:02x}{:02x}{:02x}'.format(*dominant_color) print(json.dumps({ "center_x": center_x, "center_y": center_y, "width": args.width, "height": args.height, - "variance": variance + "variance": variance, + "dominant_color": dominant_color_hex })) if __name__ == "__main__": diff --git a/arch-packages/illogical-impulse-python/PKGBUILD b/arch-packages/illogical-impulse-python/PKGBUILD index fc1ac1e05..d9c6caa55 100644 --- a/arch-packages/illogical-impulse-python/PKGBUILD +++ b/arch-packages/illogical-impulse-python/PKGBUILD @@ -13,4 +13,5 @@ depends=( libportal-gtk4 gobject-introspection sassc + python-opencv ) diff --git a/scriptdata/requirements.in b/scriptdata/requirements.in index f312b7f15..0b4ac323e 100644 --- a/scriptdata/requirements.in +++ b/scriptdata/requirements.in @@ -8,4 +8,3 @@ materialyoucolor libsass material-color-utilities setproctitle -opencv-python diff --git a/scriptdata/requirements.txt b/scriptdata/requirements.txt index a710f483a..c2f380c4a 100644 --- a/scriptdata/requirements.txt +++ b/scriptdata/requirements.txt @@ -11,11 +11,7 @@ material-color-utilities==0.2.1 materialyoucolor==2.0.10 # via -r scriptdata/requirements.in numpy==2.2.2 - # via - # material-color-utilities - # opencv-python -opencv-python==4.11.0.86 - # via -r scriptdata/requirements.in + # via material-color-utilities packaging==24.2 # via # build From e8c294f43436b73d975703c8e70e8223cf1f7e04 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:29:16 +0200 Subject: [PATCH 711/824] remove redundant defaulted prop assignment --- .config/quickshell/services/ConfigLoader.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/.config/quickshell/services/ConfigLoader.qml b/.config/quickshell/services/ConfigLoader.qml index c1e040209..788fc728d 100644 --- a/.config/quickshell/services/ConfigLoader.qml +++ b/.config/quickshell/services/ConfigLoader.qml @@ -83,7 +83,6 @@ Singleton { Timer { id: delayedFileRead interval: ConfigOptions.hacks.arbitraryRaceConditionDelay - repeat: false running: false onTriggered: { root.applyConfig(configFileView.text()) From 8dab758d7847363454dab58b242976b189464d6e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:29:33 +0200 Subject: [PATCH 712/824] dock: hover region adjustment --- .config/quickshell/modules/dock/Dock.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/dock/Dock.qml b/.config/quickshell/modules/dock/Dock.qml index b89630228..bd0ac8ec7 100644 --- a/.config/quickshell/modules/dock/Dock.qml +++ b/.config/quickshell/modules/dock/Dock.qml @@ -55,7 +55,9 @@ Scope { // Scope id: dockMouseArea anchors.top: parent.top height: parent.height - anchors.topMargin: dockRoot.reveal ? 0 : dockRoot.implicitHeight - ConfigOptions.dock.hoverRegionHeight + anchors.topMargin: dockRoot.reveal ? 0 : + ConfigOptions?.dock.hoverToReveal ? (dockRoot.implicitHeight + 1) : + (dockRoot.implicitHeight - ConfigOptions.dock.hoverRegionHeight) anchors.left: parent.left anchors.right: parent.right hoverEnabled: true From 726558c0a09fe7cd34218463b5efedbb8c56b3f2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:30:12 +0200 Subject: [PATCH 713/824] background widgets --- .config/hypr/hyprland/execs.conf | 2 +- .config/hypr/hyprland/rules.conf | 2 + .config/matugen/config.toml | 2 +- .../backgroundWidgets/BackgroundWidgets.qml | 134 ++++++++++++++++++ .../modules/common/functions/color_utils.js | 26 +++- .config/quickshell/scripts/switchwall.sh | 22 ++- .config/quickshell/shell.qml | 31 ++-- 7 files changed, 199 insertions(+), 20 deletions(-) create mode 100644 .config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml diff --git a/.config/hypr/hyprland/execs.conf b/.config/hypr/hyprland/execs.conf index 32a25be4a..d4945605b 100644 --- a/.config/hypr/hyprland/execs.conf +++ b/.config/hypr/hyprland/execs.conf @@ -1,6 +1,6 @@ # Bar, wallpaper exec-once = swww-daemon --format xrgb --no-cache -exec-once = sleep 0.5; swww img "$(cat ~/.local/state/quickshell/user/generated/wallpaper.txt)" --transition-step 100 --transition-fps 120 --transition-type grow --transition-angle 30 --transition-duration 1 +exec-once = sleep 0.5; swww img "$(cat ~/.local/state/quickshell/user/generated/wallpaper/path.txt)" --transition-step 100 --transition-fps 120 --transition-type grow --transition-angle 30 --transition-duration 1 exec-once = /usr/lib/geoclue-2.0/demos/agent & gammastep exec-once = qs & diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index 19945f988..fe1f5da4c 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -119,6 +119,8 @@ layerrule = animation slide bottom, quickshell:osk layerrule = blur, quickshell:session layerrule = noanim, quickshell:session layerrule = animation fade, quickshell:notificationPopup +layerrule = blur, quickshell:backgroundWidgets +layerrule = ignorealpha 0.05, quickshell:backgroundWidgets # layerrule = blurpopups, quickshell:.* # layerrule = blur, quickshell:.* diff --git a/.config/matugen/config.toml b/.config/matugen/config.toml index ef7bf7639..66c17a37e 100644 --- a/.config/matugen/config.toml +++ b/.config/matugen/config.toml @@ -45,4 +45,4 @@ post_hook = '~/.config/matugen/templates/kde/kde-material-you-colors-wrapper.sh' [templates.wallpaper] input_path = '~/.config/matugen/templates/wallpaper.txt' -output_path = '~/.local/state/quickshell/user/generated/wallpaper.txt' +output_path = '~/.local/state/quickshell/user/generated/wallpaper/path.txt' diff --git a/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml b/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml new file mode 100644 index 000000000..dea41e08f --- /dev/null +++ b/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml @@ -0,0 +1,134 @@ +import "root:/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland +import Quickshell.Services.UPower + +Scope { + id: root + property string filePath: `${Directories.state}/user/generated/wallpaper/least_busy_region.json` + property real centerX: 0 + property real centerY: 0 + property color dominantColor: Appearance.m3colors.m3primary + property bool dominantColorIsDark: dominantColor.hslLightness < 0.5 + property color colBackground: ColorUtils.transparentize(ColorUtils.mix(Appearance.m3colors.m3primary, Appearance.m3colors.m3secondaryContainer), 1) + property color colText: ColorUtils.colorWithLightness(Appearance.m3colors.m3primary, (root.dominantColorIsDark ? 0.8 : 0.12)) + + function updateWidgetPosition(fileContent) { + console.log("[BackgroundWidgets] Updating widget position with content:", fileContent) + const parsedContent = JSON.parse(fileContent) + root.centerX = parsedContent.center_x + root.centerY = parsedContent.center_y + root.dominantColor = parsedContent.dominant_color || Appearance.m3colors.m3primary + } + + Timer { + id: delayedFileRead + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + running: false + onTriggered: { + root.updateWidgetPosition(leastBusyRegionFileView.text()) + } + } + + FileView { + id: leastBusyRegionFileView + path: Qt.resolvedUrl(root.filePath) + watchChanges: true + onFileChanged: { + this.reload() + delayedFileRead.start() + } + onLoadedChanged: { + const fileContent = leastBusyRegionFileView.text() + root.updateWidgetPosition(fileContent) + } + } + + Variants { // For each monitor + model: Quickshell.screens + + Loader { + required property var modelData + active: !ToplevelManager.activeToplevel?.activated + sourceComponent: PanelWindow { // Window + id: windowRoot + screen: modelData + property var textHorizontalAlignment: root.centerX < windowRoot.width / 3 ? Text.AlignLeft : + (root.centerX > windowRoot.width * 2 / 3 ? Text.AlignRight : Text.AlignHCenter) + + WlrLayershell.layer: WlrLayer.Bottom + WlrLayershell.namespace: "quickshell:backgroundWidgets" + + anchors { + top: true + bottom:true + left: true + right: true + } + color: "transparent" + HyprlandWindow.visibleMask: Region { + item: widgetBackground + } + + Rectangle { + id: widgetBackground + property real verticalPadding: 20 + property real horizontalPadding: 30 + radius: 40 + color: root.colBackground + implicitHeight: columnLayout.implicitHeight + verticalPadding * 2 + implicitWidth: columnLayout.implicitWidth + horizontalPadding * 2 + anchors { + left: parent.left + top: parent.top + leftMargin: root.centerX - implicitWidth / 2 + topMargin: root.centerY - implicitHeight / 2 + Behavior on leftMargin { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + Behavior on topMargin { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + } + + ColumnLayout { + id: columnLayout + anchors.centerIn: parent + spacing: -5 + + StyledText { + Layout.fillWidth: true + horizontalAlignment: windowRoot.textHorizontalAlignment + font.pixelSize: 95 + color: root.colText + style: Text.Raised + styleColor: Appearance.colors.colShadow + text: DateTime.time + } + StyledText { + Layout.fillWidth: true + horizontalAlignment: windowRoot.textHorizontalAlignment + font.pixelSize: 25 + color: root.colText + style: Text.Raised + styleColor: Appearance.colors.colShadow + text: DateTime.date + } + } + } + + } + } + + } + +} diff --git a/.config/quickshell/modules/common/functions/color_utils.js b/.config/quickshell/modules/common/functions/color_utils.js index c0ccfda9d..eb0fc0c2f 100644 --- a/.config/quickshell/modules/common/functions/color_utils.js +++ b/.config/quickshell/modules/common/functions/color_utils.js @@ -39,6 +39,30 @@ function colorWithSaturationOf(color1, color2) { return Qt.hsva(hue, sat, val, alpha); } +/** + * Returns a color with the given lightness and the hue, saturation, and alpha of the input color (using HSL). + * + * @param {string} color - The base color (any Qt.color-compatible string). + * @param {number} lightness - The lightness value to use (0-1). + * @returns {Qt.rgba} The resulting color. + */ +function colorWithLightness(color, lightness) { + var c = Qt.color(color); + return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a); +} + +/** + * Returns a color with the lightness of color2 and the hue, saturation, and alpha of color1 (using HSL). + * + * @param {string} color1 - The base color (any Qt.color-compatible string). + * @param {string} color2 - The color to take lightness from. + * @returns {Qt.rgba} The resulting color. + */ +function colorWithLightnessOf(color1, color2) { + var c2 = Qt.color(color2); + return colorWithLightness(color1, c2.hslLightness); +} + /** * Adapts color1 to the accent (hue and saturation) of color2 using HSL, keeping lightness and alpha from color1. * @@ -66,7 +90,7 @@ function adaptToAccent(color1, color2) { * @param {number} percentage - The mix ratio (0-1). 1 = all color1, 0 = all color2. * @returns {Qt.rgba} The resulting mixed color. */ -function mix(color1, color2, percentage) { +function mix(color1, color2, percentage = 0.5) { var c1 = Qt.color(color1); var c2 = Qt.color(color2); return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a); diff --git a/.config/quickshell/scripts/switchwall.sh b/.config/quickshell/scripts/switchwall.sh index 838bb8b9c..adc76201d 100755 --- a/.config/quickshell/scripts/switchwall.sh +++ b/.config/quickshell/scripts/switchwall.sh @@ -7,6 +7,7 @@ CONFIG_DIR="$XDG_CONFIG_HOME/quickshell" CACHE_DIR="$XDG_CACHE_HOME/quickshell" STATE_DIR="$XDG_STATE_HOME/quickshell" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MATUGEN_DIR="$XDG_CONFIG_HOME/matugen" terminalscheme="$XDG_CONFIG_HOME/quickshell/scripts/terminal/scheme-base.json" pre_process() { @@ -26,7 +27,19 @@ pre_process() { } post_process() { - true + local screen_width="$1" + local screen_height="$2" + local wallpaper_path="$3" + + # Determine the largest region on the wallpaper that's sufficiently un-busy to put widgets in + if [ ! -f "$MATUGEN_DIR/scripts/least_busy_region.py" ]; then + echo "Error: least_busy_region.py script not found in $MATUGEN_DIR/scripts/" + else + "$MATUGEN_DIR/scripts/least_busy_region.py" \ + --screen-width "$screen_width" --screen-height "$screen_height" \ + --width 300 --height 200 \ + "$wallpaper_path" > "$STATE_DIR"/user/generated/wallpaper/least_busy_region.json + fi } check_and_prompt_upscale() { @@ -219,7 +232,10 @@ switch() { "$SCRIPT_DIR"/applycolor.sh deactivate - post_process + # Pass screen width, height, and wallpaper path to post_process + min_width_desired="$(hyprctl monitors -j | jq '([.[].width] | max)' | xargs)" + min_height_desired="$(hyprctl monitors -j | jq '([.[].height] | max)' | xargs)" + post_process "$min_width_desired" "$min_height_desired" "$imgpath" } main() { @@ -273,4 +289,4 @@ main() { switch "$imgpath" "$mode_flag" "$type_flag" "$color_flag" "$color" } -main "$@" \ No newline at end of file +main "$@" diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index e6e5d566c..5d14061bf 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -3,6 +3,7 @@ //@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic import "./modules/common/" +import "./modules/backgroundWidgets/" import "./modules/bar/" import "./modules/cheatsheet/" import "./modules/dock/" @@ -26,6 +27,7 @@ ShellRoot { // Enable/disable modules here. False = not loaded at all, so rest assured // no unnecessary stuff will take up memory if you decide to only use, say, the overview. property bool enableBar: true + property bool enableBackgroundWidgets: true property bool enableCheatsheet: true property bool enableDock: true property bool enableMediaControls: true @@ -49,19 +51,20 @@ ShellRoot { FirstRunExperience.load() } - Loader { active: enableBar; sourceComponent: Bar {} } - Loader { active: enableCheatsheet; sourceComponent: Cheatsheet {} } - Loader { active: (enableDock && ConfigOptions?.dock.enable); sourceComponent: Dock {} } - Loader { active: enableMediaControls; sourceComponent: MediaControls {} } - Loader { active: enableNotificationPopup; sourceComponent: NotificationPopup {} } - Loader { active: enableOnScreenDisplayBrightness; sourceComponent: OnScreenDisplayBrightness {} } - Loader { active: enableOnScreenDisplayVolume; sourceComponent: OnScreenDisplayVolume {} } - Loader { active: enableOnScreenKeyboard; sourceComponent: OnScreenKeyboard {} } - Loader { active: enableOverview; sourceComponent: Overview {} } - Loader { active: enableReloadPopup; sourceComponent: ReloadPopup {} } - Loader { active: enableScreenCorners; sourceComponent: ScreenCorners {} } - Loader { active: enableSession; sourceComponent: Session {} } - Loader { active: enableSidebarLeft; sourceComponent: SidebarLeft {} } - Loader { active: enableSidebarRight; sourceComponent: SidebarRight {} } + LazyLoader { active: enableBar; component: Bar {} } + LazyLoader { active: enableBackgroundWidgets; component: BackgroundWidgets {} } + LazyLoader { active: enableCheatsheet; component: Cheatsheet {} } + LazyLoader { active: (enableDock && ConfigOptions?.dock.enable); component: Dock {} } + LazyLoader { active: enableMediaControls; component: MediaControls {} } + LazyLoader { active: enableNotificationPopup; component: NotificationPopup {} } + LazyLoader { active: enableOnScreenDisplayBrightness; component: OnScreenDisplayBrightness {} } + LazyLoader { active: enableOnScreenDisplayVolume; component: OnScreenDisplayVolume {} } + LazyLoader { active: enableOnScreenKeyboard; component: OnScreenKeyboard {} } + LazyLoader { active: enableOverview; component: Overview {} } + LazyLoader { active: enableReloadPopup; component: ReloadPopup {} } + LazyLoader { active: enableScreenCorners; component: ScreenCorners {} } + LazyLoader { active: enableSession; component: Session {} } + LazyLoader { active: enableSidebarLeft; component: SidebarLeft {} } + LazyLoader { active: enableSidebarRight; component: SidebarRight {} } } From f840af4652fb4a3c58aec4874812654da6f07e40 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:44:16 +0200 Subject: [PATCH 714/824] fix funny notice uhm some guys in discord said its not correct n stuff --- .config/matugen/scripts/least_busy_region.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/matugen/scripts/least_busy_region.py b/.config/matugen/scripts/least_busy_region.py index 7ae245dd4..a1f2f47b4 100755 --- a/.config/matugen/scripts/least_busy_region.py +++ b/.config/matugen/scripts/least_busy_region.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Disclaimer: This script is vibe-coded. +# Disclaimer: This script was ai-generated and went through minimal revision. import os os.environ["OPENCV_LOG_LEVEL"] = "SILENT" From cfb4f1a5e1ea43df95363b8c8caa09f759d8bc07 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 8 Jun 2025 17:05:20 +0200 Subject: [PATCH 715/824] gnome control center -> plasma systemsettings --- .config/hypr/hyprland/keybinds.conf | 2 +- .config/quickshell/modules/common/ConfigOptions.qml | 7 ++++--- .../modules/sidebarRight/quickToggles/NetworkToggle.qml | 2 +- arch-packages/illogical-impulse-kde/PKGBUILD | 1 - 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index f6e9d03b9..db554af57 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -208,7 +208,7 @@ bind = Super, W, exec, zen-browser # [hidden] bind = Super+Shift, W, exec, wps # WPS Office bind = Super, X, exec, kate # Kate (text editor) bind = Ctrl+Super, V, exec, pavucontrol-qt # Pavucontrol Qt (volume mixer) -bind = Super, I, exec, XDG_CURRENT_DESKTOP=gnome gnome-control-center # GNOME Control center (settings app) +bind = Super, I, exec, systemsettings # Plasma system settings bind = Ctrl+Shift, Escape, exec, plasma-systemmonitor --page-name Processes # Plasma system monitor # Cursed stuff diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 77b21b49a..514f21c23 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -21,10 +21,11 @@ Singleton { } property QtObject apps: QtObject { - property string bluetooth: "better-control --bluetooth" + property string bluetooth: "systemsettings kcm_bluetooth" property string imageViewer: "loupe" - property string network: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi" - property string settings: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center" + property string network: "better-control --wifi" + property string networkEthernet: "systemsettings kcm_networkmanagement" + property string settings: "systemsettings kcm_bluetooth" property string taskManager: "plasma-systemmonitor --page-name Processes" property string terminal: "kitty -1" // This is only for shell actions } diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml index 8f058fd53..5271e3769 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml @@ -15,7 +15,7 @@ QuickToggleButton { toggleNetwork.running = true } altAction: () => { - Hyprland.dispatch(`exec ${ConfigOptions.apps.network}`) + Hyprland.dispatch(`exec ${Network.ethernet ? ConfigOptions.apps.networkEthernet : ConfigOptions.apps.network}`) Hyprland.dispatch("global quickshell:sidebarRightClose") } Process { diff --git a/arch-packages/illogical-impulse-kde/PKGBUILD b/arch-packages/illogical-impulse-kde/PKGBUILD index 35f8d669f..d4f5e4058 100644 --- a/arch-packages/illogical-impulse-kde/PKGBUILD +++ b/arch-packages/illogical-impulse-kde/PKGBUILD @@ -7,6 +7,5 @@ license=(None) depends=( polkit-kde-agent gnome-keyring - gnome-control-center networkmanager better-control-git ) From dc8bfce62e03a884b318655d2fe4d21d95a4369d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 8 Jun 2025 18:36:40 +0200 Subject: [PATCH 716/824] ai: fix latex rendering --- .../quickshell/modules/common/Directories.qml | 2 +- .../quickshell/modules/sidebarLeft/AiChat.qml | 6 ++--- .../modules/sidebarLeft/aiChat/AiMessage.qml | 22 ++++++++++------- .../sidebarLeft/aiChat/MessageTextBlock.qml | 19 +++++++++++---- .config/quickshell/services/LatexRenderer.qml | 24 ++++++++++++------- 5 files changed, 47 insertions(+), 26 deletions(-) diff --git a/.config/quickshell/modules/common/Directories.qml b/.config/quickshell/modules/common/Directories.qml index 9e3e3000f..b256666fc 100644 --- a/.config/quickshell/modules/common/Directories.qml +++ b/.config/quickshell/modules/common/Directories.qml @@ -21,7 +21,7 @@ Singleton { property string booruPreviews: FileUtils.trimFileProtocol(`${Directories.cache}/media/boorus`) property string booruDownloads: FileUtils.trimFileProtocol(Directories.pictures + "/homework") property string booruDownloadsNsfw: FileUtils.trimFileProtocol(Directories.pictures + "/homework/🌶️") - property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/latex`) + property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/media/latex`) property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse`) property string shellConfigName: "config.json" property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}` diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index b78ff89ce..ecb8628be 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -125,9 +125,9 @@ int main(int argc, char* argv[]) { ### LaTeX -- Simple inline: $\\frac{1}{2} = \\frac{2}{4}$ -- Complex inline: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$ -- Another complex inline: \\\\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\\\] +- Inline w/ dollar signs: $\\frac{1}{2} = \\frac{2}{4}$ +- Inline w/ double dollar signs: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$ +- Inline w/ backslash and square brackets \\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\] `, Ai.interfaceRole); } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index e115338ad..775d12082 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -28,6 +28,8 @@ Rectangle { property bool renderMarkdown: true property bool editing: false + property list messageBlocks: StringUtils.splitMarkdownBlocks(root.messageData?.content) + anchors.left: parent?.left anchors.right: parent?.right implicitHeight: columnLayout.implicitHeight + root.messagePadding * 2 @@ -246,23 +248,25 @@ Rectangle { spacing: 0 Repeater { model: ScriptModel { - values: StringUtils.splitMarkdownBlocks(root.messageData?.content) + values: root.messageBlocks.map((block, index) => index) } delegate: Loader { + required property int index + property var thisBlock: root.messageBlocks[index] Layout.fillWidth: true - // property var segment: modelData - property var segmentContent: modelData.content - property var segmentLang: modelData.lang + // property var segment: thisBlock + property var segmentContent: thisBlock.content + property var segmentLang: thisBlock.lang property var messageData: root.messageData property var editing: root.editing property var renderMarkdown: root.renderMarkdown property var enableMouseSelection: root.enableMouseSelection property bool thinking: root.messageData?.thinking ?? true property bool done: root.messageData?.done ?? false - property bool completed: modelData.completed ?? false + property bool completed: thisBlock.completed ?? false - source: modelData.type === "code" ? "MessageCodeBlock.qml" : - modelData.type === "think" ? "MessageThinkBlock.qml" : + source: thisBlock.type === "code" ? "MessageCodeBlock.qml" : + thisBlock.type === "think" ? "MessageThinkBlock.qml" : "MessageTextBlock.qml" } @@ -277,7 +281,9 @@ Rectangle { Layout.alignment: Qt.AlignLeft Repeater { - model: root.messageData?.annotationSources + model: ScriptModel { + values: root.messageData?.annotationSources || [] + } delegate: AnnotationSourceButton { id: annotationButton displayText: modelData.text diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml index 87087c6b9..bc7f93df1 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml @@ -30,6 +30,18 @@ ColumnLayout { Layout.fillWidth: true + Timer { + id: renderTimer + interval: 1000 + repeat: true + onTriggered: { + renderLatex() + for (const hash of renderedLatexHashes) { + handleRenderedLatex(hash, true); + } + } + } + function renderLatex() { // Regex for $...$, $$...$$, \[...\] // Note: This is a simple approach and may need refinement for edge cases @@ -53,16 +65,13 @@ ColumnLayout { const imagePath = LatexRenderer.renderedImagePaths[hash]; const markdownImage = `![latex](${imagePath})`; - const expression = StringUtils.escapeBackslashes(LatexRenderer.processedExpressions[hash]); + const expression = LatexRenderer.processedExpressions[hash]; renderedSegmentContent = renderedSegmentContent.replace(expression, markdownImage); } } onDoneChanged: { - renderLatex() - for (const hash of renderedLatexHashes) { - handleRenderedLatex(hash, true); - } + renderTimer.restart(); } onEditingChanged: { if (!editing) { diff --git a/.config/quickshell/services/LatexRenderer.qml b/.config/quickshell/services/LatexRenderer.qml index d3b9ade1f..e7066fa4c 100644 --- a/.config/quickshell/services/LatexRenderer.qml +++ b/.config/quickshell/services/LatexRenderer.qml @@ -25,7 +25,8 @@ Singleton { property list processedHashes: [] property var processedExpressions: ({}) property var renderedImagePaths: ({}) - property string microtexBinaryPath: Qt.resolvedUrl("/opt/MicroTeX/LaTeX") + property string microtexBinaryDir: "/opt/MicroTeX" + property string microtexBinaryName: "LaTeX" property string latexOutputPath: Directories.latexOutput signal renderFinished(string hash, string imagePath) @@ -51,23 +52,28 @@ Singleton { } // 3. If not, render it with MicroTeX and mark as processed + // console.log(`[LatexRenderer] Rendering expression: ${expression} with hash: ${hash}`) + // console.log(` to file: ${imagePath}`) + // console.log(` with command: cd ${microtexBinaryDir} && ./${microtexBinaryName} -headless -input=${StringUtils.shellSingleQuoteEscape(expression)} -output=${imagePath} -textsize=${Appearance.font.pixelSize.normal} -padding=${renderPadding} -background=${Appearance.m3colors.m3tertiary} -foreground=${Appearance.m3colors.m3onTertiary} -maxwidth=0.85`) const processQml = ` import Quickshell.Io Process { id: microtexProcess${hash} running: true - command: [ "${microtexBinaryPath}", "-headless", - "-input=${StringUtils.escapeBackslashes(expression)}", - "-output=${imagePath}", - "-textsize=${Appearance.font.pixelSize.normal}", - "-padding=${renderPadding}", - "-background=${Appearance.m3colors.m3tertiary}", - "-foreground=${Appearance.m3colors.m3onTertiary}", - "-maxwidth=0.85" ] + command: [ "bash", "-c", + "cd ${root.microtexBinaryDir} && ./${root.microtexBinaryName} -headless '-input=${StringUtils.shellSingleQuoteEscape(StringUtils.escapeBackslashes(expression))}' " + + "'-output=${imagePath}' " + + "'-textsize=${Appearance.font.pixelSize.normal}' " + + "'-padding=${renderPadding}' " + // + "'-background=${Appearance.m3colors.m3tertiary}' " + + "'-foreground=${Appearance.colors.colOnLayer1}' " + + "-maxwidth=0.85 " + ] // stdout: SplitParser { // onRead: data => { console.log("MicroTeX: " + data) } // } onExited: (exitCode, exitStatus) => { + // console.log("[LatexRenderer] MicroTeX process exited with code: " + exitCode + ", status: " + exitStatus) renderedImagePaths["${hash}"] = "${imagePath}" root.renderFinished("${hash}", "${imagePath}") microtexProcess${hash}.destroy() From d521b2aae430cd8f0c96d91d6cb2793f22e38f3c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 8 Jun 2025 19:02:00 +0200 Subject: [PATCH 717/824] ai: latex: properly catch bracket-wrapped expressions --- .config/quickshell/modules/sidebarLeft/AiChat.qml | 11 ++++++++--- .../modules/sidebarLeft/aiChat/MessageTextBlock.qml | 10 +++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index ecb8628be..9ba1d3688 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -125,9 +125,14 @@ int main(int argc, char* argv[]) { ### LaTeX -- Inline w/ dollar signs: $\\frac{1}{2} = \\frac{2}{4}$ -- Inline w/ double dollar signs: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$ -- Inline w/ backslash and square brackets \\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\] + +Inline w/ dollar signs: $\\frac{1}{2} = \\frac{2}{4}$ + +Inline w/ double dollar signs: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$ + +Inline w/ backslash and square brackets \\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\] + +Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\) `, Ai.interfaceRole); } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml index bc7f93df1..25ecb05a6 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml @@ -33,7 +33,7 @@ ColumnLayout { Timer { id: renderTimer interval: 1000 - repeat: true + repeat: false onTriggered: { renderLatex() for (const hash of renderedLatexHashes) { @@ -45,17 +45,17 @@ ColumnLayout { function renderLatex() { // Regex for $...$, $$...$$, \[...\] // Note: This is a simple approach and may need refinement for edge cases - let regex = /(\$\$([\s\S]+?)\$\$)|(\$([^\$]+?)\$)|(\\\[((?:.|\n)+?)\\\])/g; + let regex = /(\$\$([\s\S]+?)\$\$)|(\$([^\$]+?)\$)|(\\\[((?:.|\n)+?)\\\])|(\\\(([\s\S]+?)\\\))/g; let match; while ((match = regex.exec(segmentContent)) !== null) { - let expression = match[1] || match[2] || match[3]; + let expression = match[1] || match[2] || match[3] || match[4] || match[5] || match[6] || match[7] || match[8]; if (expression) { - // Qt.callLater(() => { - // }); + Qt.callLater(() => { const [renderHash, isNew] = LatexRenderer.requestRender(expression.trim()); if (!renderedLatexHashes.includes(renderHash)) { renderedLatexHashes.push(renderHash); } + }); } } } From d4f223c89469e09dc189a35038294fa388853a81 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 8 Jun 2025 21:39:03 +0200 Subject: [PATCH 718/824] starship: a refresh --- .config/starship.toml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.config/starship.toml b/.config/starship.toml index 751f2fd2c..5ed04489e 100644 --- a/.config/starship.toml +++ b/.config/starship.toml @@ -10,25 +10,25 @@ add_newline = false # $character # """ format = """ -$cmd_duration$directory $git_branch +$cmd_duration 󰜥 $directory $git_branch $character """ # Replace the "❯" symbol in the prompt with "➜" [character] # The name of the module we are configuring is "character" -success_symbol = "[• ](bold fg:green) " -error_symbol = "[• 󰅙](bold fg:red) " +success_symbol = "[ 󰜥 ](bold fg:blue)" +error_symbol = "[ 󰜥 ](bold fg:red)" # Disable the package module, hiding it from the prompt completely [package] disabled = true [git_branch] -style = "bg: green" +style = "bg: cyan" symbol = "󰘬" -truncation_length = 4 +truncation_length = 12 truncation_symbol = "" -format = "• [](bold fg:green)[$symbol $branch(:$remote_branch)](fg:black bg:green)[ ](bold fg:green)" +format = "󰜥 [](bold fg:cyan)[$symbol $branch(:$remote_branch)](fg:black bg:cyan)[ ](bold fg:cyan)" [git_commit] commit_hash_length = 4 @@ -52,7 +52,7 @@ deleted = " 🗑 " [hostname] ssh_only = false -format = "[•$hostname](bg:cyan bold fg:black)[](bold fg:cyan )" +format = "[•$hostname](bg:cyan bold fg:black)[](bold fg:cyan)" trim_at = ".companyname.com" disabled = false @@ -82,8 +82,8 @@ home_symbol = "  " read_only = "  " style = "bg:green fg:black" truncation_length = 6 -truncation_symbol = "••/" -format = '[](bold fg:green)[$path ]($style)[](bold fg:green)' +truncation_symbol = " ••/" +format = '[](bold fg:green)[󰉋 $path]($style)[](bold fg:green)' [directory.substitutions] @@ -93,7 +93,8 @@ format = '[](bold fg:green)[$path ]($style)[](bold fg:green)' "Music" = " 󰎈 " "Pictures" = "  " "Videos" = "  " +"GitHub" = " 󰊤 " [cmd_duration] min_time = 0 -format = '[](bold fg:yellow)[ $duration](bold bg:yellow fg:black)[](bold fg:yellow) •• ' +format = '[](bold fg:yellow)[󰪢 $duration](bold bg:yellow fg:black)[](bold fg:yellow)' From 2663c46b7eb20ed3369f231256ee8fc0b8983ea3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 8 Jun 2025 21:50:24 +0200 Subject: [PATCH 719/824] terminal: nice dir listing --- .config/fish/config.fish | 4 +++- arch-packages/illogical-impulse-fonts-themes/PKGBUILD | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.config/fish/config.fish b/.config/fish/config.fish index 7f9114d72..3ddb607e5 100755 --- a/.config/fish/config.fish +++ b/.config/fish/config.fish @@ -17,7 +17,9 @@ if test -f ~/.local/state/quickshell/user/generated/terminal/sequences.txt cat ~/.local/state/quickshell/user/generated/terminal/sequences.txt end -alias pamcan=pacman +alias pamcan pacman +alias ls 'eza --icons' + # function fish_prompt # set_color cyan; echo (pwd) diff --git a/arch-packages/illogical-impulse-fonts-themes/PKGBUILD b/arch-packages/illogical-impulse-fonts-themes/PKGBUILD index c96790c0b..ed1a95070 100644 --- a/arch-packages/illogical-impulse-fonts-themes/PKGBUILD +++ b/arch-packages/illogical-impulse-fonts-themes/PKGBUILD @@ -7,6 +7,7 @@ license=(None) depends=( adw-gtk-theme-git breeze-plus + eza fish fontconfig kde-material-you-colors From 08e46c5af88842a707849cb30b4da75536a482b7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 09:20:51 +0200 Subject: [PATCH 720/824] make 0-size warnings stfu --- .config/quickshell/modules/bar/ActiveWindow.qml | 1 - .config/quickshell/modules/bar/Bar.qml | 2 +- .config/quickshell/modules/common/widgets/PrimaryTabButton.qml | 1 + .config/quickshell/modules/common/widgets/Revealer.qml | 1 + .config/quickshell/modules/common/widgets/RippleButton.qml | 1 + 5 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/bar/ActiveWindow.qml b/.config/quickshell/modules/bar/ActiveWindow.qml index 574cdb8c6..86018129b 100644 --- a/.config/quickshell/modules/bar/ActiveWindow.qml +++ b/.config/quickshell/modules/bar/ActiveWindow.qml @@ -12,7 +12,6 @@ Item { height: parent.height width: colLayout.width - ColumnLayout { id: colLayout diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index ca9b1e57e..88a605fe1 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -176,7 +176,7 @@ Scope { } ActiveWindow { - visible: barRoot.useShortenedForm === 0 + visible: barRoot.useShortenedForm === 0 && width > 0 && height > 0 Layout.rightMargin: Appearance.rounding.screenRounding Layout.fillWidth: true bar: barRoot diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml index b7c628712..a47f108b7 100644 --- a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml +++ b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml @@ -118,6 +118,7 @@ TabButton { property real implicitWidth: 0 property real implicitHeight: 0 + visible: width > 0 && height > 0 Behavior on opacity { animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) diff --git a/.config/quickshell/modules/common/widgets/Revealer.qml b/.config/quickshell/modules/common/widgets/Revealer.qml index 327d4aaa1..f3d438b55 100644 --- a/.config/quickshell/modules/common/widgets/Revealer.qml +++ b/.config/quickshell/modules/common/widgets/Revealer.qml @@ -13,6 +13,7 @@ Item { implicitWidth: (reveal || vertical) ? childrenRect.width : 0 implicitHeight: (reveal || !vertical) ? childrenRect.height : 0 + visible: width > 0 && height > 0 Behavior on implicitWidth { enabled: !vertical diff --git a/.config/quickshell/modules/common/widgets/RippleButton.qml b/.config/quickshell/modules/common/widgets/RippleButton.qml index cd7762b9d..9931cd02a 100644 --- a/.config/quickshell/modules/common/widgets/RippleButton.qml +++ b/.config/quickshell/modules/common/widgets/RippleButton.qml @@ -155,6 +155,7 @@ Button { width: ripple.implicitWidth height: ripple.implicitHeight opacity: 0 + visible: width > 0 && height > 0 property real implicitWidth: 0 property real implicitHeight: 0 From 8e1f28b11a68ff77b78b26c839b35795d3dcc6ac Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 09:25:22 +0200 Subject: [PATCH 721/824] less weird tooltip color for dark mode --- .config/quickshell/modules/common/Appearance.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 9f83ac5cf..a984b9e4f 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -133,7 +133,7 @@ Singleton { property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54) property color colSurfaceContainerHighestHover: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.95) property color colSurfaceContainerHighestActive: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.85) - property color colTooltip: "#3C4043" // m3colors.m3inverseSurface in the specs, but the m3 website actually uses this color + property color colTooltip: m3colors.darkmode ? ColorUtils.mix(m3colors.m3background, "#3C4043", 0.5) : "#3C4043" // m3colors.m3inverseSurface in the specs, but the m3 website actually uses #3C4043 property color colOnTooltip: "#F8F9FA" // m3colors.m3inverseOnSurface in the specs, but the m3 website actually uses this color property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5) property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7) From fa2723a54defdd53f6c69a2aaee68e9b24809291 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 12:23:58 +0200 Subject: [PATCH 722/824] dock: remove user config option because it doesn't work (just use enableDock in shell.qml) --- .config/quickshell/modules/common/ConfigOptions.qml | 1 - .config/quickshell/shell.qml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 514f21c23..6f1bc112a 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -53,7 +53,6 @@ Singleton { } property QtObject dock: QtObject { - property bool enable: false property real height: 60 property real hoverRegionHeight: 3 property bool pinnedOnStartup: false diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 5d14061bf..4acb7cccb 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -54,7 +54,7 @@ ShellRoot { LazyLoader { active: enableBar; component: Bar {} } LazyLoader { active: enableBackgroundWidgets; component: BackgroundWidgets {} } LazyLoader { active: enableCheatsheet; component: Cheatsheet {} } - LazyLoader { active: (enableDock && ConfigOptions?.dock.enable); component: Dock {} } + LazyLoader { active: enableDock; component: Dock {} } LazyLoader { active: enableMediaControls; component: MediaControls {} } LazyLoader { active: enableNotificationPopup; component: NotificationPopup {} } LazyLoader { active: enableOnScreenDisplayBrightness; component: OnScreenDisplayBrightness {} } From db8d51b931c5685b89768546d2c5e15a940e9cd2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 12:25:41 +0200 Subject: [PATCH 723/824] overview: window previews!!! --- .../modules/common/widgets/StyledLabel.qml | 15 +++++++++++++++ .../modules/overview/OverviewWidget.qml | 18 +++++++++++++----- .../modules/overview/OverviewWindow.qml | 19 ++++++++++++++++++- 3 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/StyledLabel.qml diff --git a/.config/quickshell/modules/common/widgets/StyledLabel.qml b/.config/quickshell/modules/common/widgets/StyledLabel.qml new file mode 100644 index 000000000..20a1cc9f0 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/StyledLabel.qml @@ -0,0 +1,15 @@ +import "root:/modules/common" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Label { + renderType: Text.NativeRendering + verticalAlignment: Text.AlignVCenter + font { + hintingPreference: Font.PreferFullHinting + family: Appearance?.font.family.main ?? "sans-serif" + pixelSize: Appearance?.font.pixelSize.small ?? 15 + } + color: Appearance?.m3colors.m3onBackground ?? "black" +} diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index a690536fe..f6f1f4840 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -145,14 +145,22 @@ Item { Repeater { // Window repeater model: ScriptModel { - values: windowAddresses.filter((address) => { - var win = windowByAddress[address] - return (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown) - }) + values: { + // console.log(JSON.stringify(ToplevelManager.toplevels.values.map(t => t), null, 2)) + return ToplevelManager.toplevels.values.filter((toplevel) => { + const address = `0x${toplevel.HyprlandToplevel.address}` + // console.log(`Checking window with address: ${address}`) + var win = windowByAddress[address] + return (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown) + }) + } } delegate: OverviewWindow { + required property var modelData + property var address: `0x${modelData.HyprlandToplevel.address}` id: window - windowData: windowByAddress[modelData] + windowData: windowByAddress[address] + toplevel: modelData monitorData: root.monitorData scale: root.scale availableWorkspaceWidth: root.workspaceImplicitWidth diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index d4617b542..15d919f80 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -1,17 +1,21 @@ +import "root:/" import "root:/services/" import "root:/modules/common" import "root:/modules/common/widgets" import "root:/modules/common/functions/color_utils.js" as ColorUtils import Qt5Compat.GraphicalEffects import QtQuick +import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Widgets import Quickshell.Io +import Quickshell.Wayland import Quickshell.Hyprland Rectangle { // Window id: root + property var toplevel property var windowData property var monitorData property var scale @@ -60,6 +64,12 @@ Rectangle { // Window animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } + ScreencopyView { + anchors.fill: parent + captureSource: GlobalStates.overviewOpen ? root.toplevel : null + live: false + } + ColumnLayout { anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left @@ -77,12 +87,19 @@ Rectangle { // Window } } - StyledText { + StyledLabel { Layout.leftMargin: 10 Layout.rightMargin: 10 visible: !compactMode Layout.fillWidth: true Layout.fillHeight: true + + background: Rectangle { + width: parent.width + color: Appearance.colors.colLayer2 + radius: Appearance.rounding.windowRounding * root.scale + } + horizontalAlignment: Text.AlignHCenter font.pixelSize: Appearance.font.pixelSize.smaller font.italic: indicateXWayland ? true : false From a81acb3dbea5a2752be139130d674c1a1573360f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 12:51:28 +0200 Subject: [PATCH 724/824] overview: cleaner, add back hover effect --- .../backgroundWidgets/BackgroundWidgets.qml | 2 +- .../modules/overview/OverviewWindow.qml | 69 +++++++++---------- .../modules/sidebarRight/SidebarRight.qml | 10 +++ 3 files changed, 42 insertions(+), 39 deletions(-) diff --git a/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml b/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml index dea41e08f..970dc8d17 100644 --- a/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml +++ b/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml @@ -23,7 +23,7 @@ Scope { property color colText: ColorUtils.colorWithLightness(Appearance.m3colors.m3primary, (root.dominantColorIsDark ? 0.8 : 0.12)) function updateWidgetPosition(fileContent) { - console.log("[BackgroundWidgets] Updating widget position with content:", fileContent) + // console.log("[BackgroundWidgets] Updating widget position with content:", fileContent) const parsedContent = JSON.parse(fileContent) root.centerX = parsedContent.center_x root.centerY = parsedContent.center_y diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index 15d919f80..1f350b7d4 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -45,11 +45,14 @@ Rectangle { // Window width: Math.min(windowData?.size[0] * root.scale, (restrictToWorkspace ? windowData?.size[0] : availableWorkspaceWidth - x + xOffset)) height: Math.min(windowData?.size[1] * root.scale, (restrictToWorkspace ? windowData?.size[1] : availableWorkspaceHeight - y + yOffset)) - radius: Appearance.rounding.windowRounding * root.scale - color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 - border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.9) - border.pixelAligned : false - border.width : 1 + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: root.width + height: root.height + radius: Appearance.rounding.windowRounding * root.scale + } + } Behavior on x { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) @@ -67,44 +70,34 @@ Rectangle { // Window ScreencopyView { anchors.fill: parent captureSource: GlobalStates.overviewOpen ? root.toplevel : null - live: false - } + live: true - ColumnLayout { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.font.pixelSize.smaller * 0.5 - - IconImage { - id: windowIcon - Layout.alignment: Qt.AlignHCenter - source: root.iconPath - implicitSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) - - Behavior on implicitSize { - animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) - } + Rectangle { + anchors.fill: parent + radius: Appearance.rounding.windowRounding * root.scale + color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 + opacity: pressed ? 0.3 : hovered ? 0.2 : 0 + border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.9) + border.pixelAligned : false + border.width : 1 } - StyledLabel { - Layout.leftMargin: 10 - Layout.rightMargin: 10 - visible: !compactMode - Layout.fillWidth: true - Layout.fillHeight: true + ColumnLayout { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.font.pixelSize.smaller * 0.5 - background: Rectangle { - width: parent.width - color: Appearance.colors.colLayer2 - radius: Appearance.rounding.windowRounding * root.scale + IconImage { + id: windowIcon + Layout.alignment: Qt.AlignHCenter + source: root.iconPath + implicitSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) + + Behavior on implicitSize { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } } - - horizontalAlignment: Text.AlignHCenter - font.pixelSize: Appearance.font.pixelSize.smaller - font.italic: indicateXWayland ? true : false - elide: Text.ElideRight - text: windowData?.title ?? "" } } } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 2bb92723d..8fcdca4c8 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -127,6 +127,16 @@ Scope { Layout.fillWidth: true } + QuickToggleButton { + toggled: false + buttonIcon: "restart_alt" + onClicked: { + Quickshell.reload(true) + } + StyledToolTip { + content: qsTr("Reload") + } + } QuickToggleButton { toggled: false buttonIcon: "power_settings_new" From 936ca85babffb4332a1b35830b9c7fb45db67f7e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 13:11:39 +0200 Subject: [PATCH 725/824] overview: fix blurry icons --- .../modules/overview/OverviewWidget.qml | 4 ++-- .../modules/overview/OverviewWindow.qml | 20 ++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index f6f1f4840..e1fbb3264 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -180,8 +180,8 @@ Item { repeat: false running: false onTriggered: { - window.x = Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset - window.y = Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset + window.x = Math.round(Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset) + window.y = Math.round(Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset) } } diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index 1f350b7d4..6436120e2 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -13,7 +13,7 @@ import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland -Rectangle { // Window +Item { // Window id: root property var toplevel property var windowData @@ -42,8 +42,8 @@ Rectangle { // Window x: initX y: initY - width: Math.min(windowData?.size[0] * root.scale, (restrictToWorkspace ? windowData?.size[0] : availableWorkspaceWidth - x + xOffset)) - height: Math.min(windowData?.size[1] * root.scale, (restrictToWorkspace ? windowData?.size[1] : availableWorkspaceHeight - y + yOffset)) + width: Math.round(Math.min(windowData?.size[0] * root.scale, (restrictToWorkspace ? windowData?.size[0] : availableWorkspaceWidth - x + xOffset))) + height: Math.round(Math.min(windowData?.size[1] * root.scale, (restrictToWorkspace ? windowData?.size[1] : availableWorkspaceHeight - y + yOffset))) layer.enabled: true layer.effect: OpacityMask { @@ -78,7 +78,6 @@ Rectangle { // Window color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 opacity: pressed ? 0.3 : hovered ? 0.2 : 0 border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.9) - border.pixelAligned : false border.width : 1 } @@ -88,13 +87,20 @@ Rectangle { // Window anchors.right: parent.right spacing: Appearance.font.pixelSize.smaller * 0.5 - IconImage { + Image { id: windowIcon + property var iconSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) + // mipmap: true Layout.alignment: Qt.AlignHCenter source: root.iconPath - implicitSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) + width: iconSize + height: iconSize + sourceSize: Qt.size(iconSize, iconSize) - Behavior on implicitSize { + Behavior on width { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + Behavior on height { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } } From 2a8725ec8d9266b38448cd87950dda24603e8a75 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 16:31:04 +0200 Subject: [PATCH 726/824] deps: remove xdg-user-dirs (#1369) --- arch-packages/illogical-impulse-toolkit/PKGBUILD | 1 - 1 file changed, 1 deletion(-) diff --git a/arch-packages/illogical-impulse-toolkit/PKGBUILD b/arch-packages/illogical-impulse-toolkit/PKGBUILD index 673e12f8b..6f12584a2 100644 --- a/arch-packages/illogical-impulse-toolkit/PKGBUILD +++ b/arch-packages/illogical-impulse-toolkit/PKGBUILD @@ -22,6 +22,5 @@ depends=( syntax-highlighting upower wtype - xdg-user-dirs-gtk ydotool ) From 3c82ce828b190f0aaafff0195e575a7a14b3b03f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 17:41:33 +0200 Subject: [PATCH 727/824] config options: correct settings app --- .config/quickshell/modules/common/ConfigOptions.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 6f1bc112a..ce6e45aa8 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -25,7 +25,7 @@ Singleton { property string imageViewer: "loupe" property string network: "better-control --wifi" property string networkEthernet: "systemsettings kcm_networkmanagement" - property string settings: "systemsettings kcm_bluetooth" + property string settings: "systemsettings" property string taskManager: "plasma-systemmonitor --page-name Processes" property string terminal: "kitty -1" // This is only for shell actions } From 4b4364d2a5cf63009dba90e38af7d932356f6b73 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 17:42:04 +0200 Subject: [PATCH 728/824] right sidebar: more buttons --- .../modules/sidebarRight/SidebarRight.qml | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 8fcdca4c8..a1e5a7478 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -127,24 +127,38 @@ Scope { Layout.fillWidth: true } - QuickToggleButton { - toggled: false - buttonIcon: "restart_alt" - onClicked: { - Quickshell.reload(true) + ButtonGroup { + QuickToggleButton { + toggled: false + buttonIcon: "restart_alt" + onClicked: { + Hyprland.dispatch("reload") + Quickshell.reload(true) + } + StyledToolTip { + content: qsTr("Reload Hyprland & Quickshell") + } } - StyledToolTip { - content: qsTr("Reload") + QuickToggleButton { + toggled: false + buttonIcon: "settings" + onClicked: { + Hyprland.dispatch(`exec ${ConfigOptions.apps.settings}`) + Hyprland.dispatch(`global quickshell:sidebarRightClose`) + } + StyledToolTip { + content: qsTr("Plasma Settings") + } } - } - QuickToggleButton { - toggled: false - buttonIcon: "power_settings_new" - onClicked: { - Hyprland.dispatch("global quickshell:sessionOpen") - } - StyledToolTip { - content: qsTr("Session") + QuickToggleButton { + toggled: false + buttonIcon: "power_settings_new" + onClicked: { + Hyprland.dispatch("global quickshell:sessionOpen") + } + StyledToolTip { + content: qsTr("Session") + } } } } From 5c2b12bf96af9ca0cafacf46bbc58c1cb2a5d348 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 17:43:58 +0200 Subject: [PATCH 729/824] dock: join pinned apps and open windows --- .config/quickshell/modules/dock/Dock.qml | 20 ---- .../quickshell/modules/dock/DockAppButton.qml | 112 +++++++++++------- .config/quickshell/modules/dock/DockApps.qml | 40 +++++-- 3 files changed, 99 insertions(+), 73 deletions(-) diff --git a/.config/quickshell/modules/dock/Dock.qml b/.config/quickshell/modules/dock/Dock.qml index bd0ac8ec7..25846ac5f 100644 --- a/.config/quickshell/modules/dock/Dock.qml +++ b/.config/quickshell/modules/dock/Dock.qml @@ -120,26 +120,6 @@ Scope { // Scope } } DockSeparator {} - // Pinned apps - Repeater { - model: ConfigOptions?.dock.pinnedApps ?? [] - - DockButton { - id: pinnedAppButton - required property string modelData - property DesktopEntry entry: DesktopEntries.byId(modelData) - onClicked: { - pinnedAppButton?.entry.execute(); - } - contentItem: IconImage { - anchors.centerIn: parent - source: Quickshell.iconPath(AppSearch.guessIcon(modelData), "image-missing") - } - } - } - - DockSeparator { visible: (ConfigOptions?.dock.pinnedApps ?? []).length > 0 } - DockApps { id: dockApps } DockSeparator {} DockButton { diff --git a/.config/quickshell/modules/dock/DockAppButton.qml b/.config/quickshell/modules/dock/DockAppButton.qml index 9df44c853..2304fe158 100644 --- a/.config/quickshell/modules/dock/DockAppButton.qml +++ b/.config/quickshell/modules/dock/DockAppButton.qml @@ -14,8 +14,8 @@ import Quickshell.Wayland import Quickshell.Hyprland DockButton { - id: appButton - required property var appToplevel + id: root + property var appToplevel property var appListRoot property int lastFocused: -1 property real iconSize: 35 @@ -23,56 +23,86 @@ DockButton { property real countDotHeight: 4 property bool appIsActive: appToplevel.toplevels.find(t => (t.activated == true)) !== undefined - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.NoButton - onEntered: { - appListRoot.lastHoveredButton = appButton - appListRoot.buttonHovered = true - lastFocused = appToplevel.toplevels.length - 1 + property bool isSeparator: appToplevel.appId === "SEPARATOR" + enabled: !isSeparator + implicitWidth: isSeparator ? 1 : implicitHeight - topInset - bottomInset + + Loader { + active: isSeparator + anchors { + fill: parent + topMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal + bottomMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal } - onExited: { - if (appListRoot.lastHoveredButton === appButton) { - appListRoot.buttonHovered = false + sourceComponent: DockSeparator {} + } + + Loader { + anchors.fill: parent + active: appToplevel.toplevels.length > 0 + sourceComponent: MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + onEntered: { + appListRoot.lastHoveredButton = root + appListRoot.buttonHovered = true + lastFocused = appToplevel.toplevels.length - 1 + } + onExited: { + if (appListRoot.lastHoveredButton === root) { + appListRoot.buttonHovered = false + } } } } + onClicked: { + if (appToplevel.toplevels.length === 0) { + DesktopEntries.byId(root.appToplevel.appId)?.execute(); + return; + } lastFocused = (lastFocused + 1) % appToplevel.toplevels.length appToplevel.toplevels[lastFocused].activate() } - contentItem: Item { - anchors.centerIn: parent - IconImage { - id: iconImage - anchors { - left: parent.left - right: parent.right - verticalCenter: parent.verticalCenter - } - source: Quickshell.iconPath(AppSearch.guessIcon(appToplevel.appId), "image-missing") - implicitSize: appButton.iconSize - } + contentItem: Loader { + active: !isSeparator + sourceComponent: Item { + anchors.centerIn: parent - RowLayout { - spacing: 3 - anchors { - top: iconImage.bottom - topMargin: 2 - horizontalCenter: parent.horizontalCenter + Loader { + id: iconImageLoader + anchors { + left: parent.left + right: parent.right + verticalCenter: parent.verticalCenter + } + active: !root.isSeparator + sourceComponent: IconImage { + source: Quickshell.iconPath(AppSearch.guessIcon(appToplevel.appId), "image-missing") + implicitSize: root.iconSize + } } - Repeater { - model: Math.min(appToplevel.toplevels.length, 3) - delegate: Rectangle { - required property int index - radius: Appearance.rounding.full - implicitWidth: (appToplevel.toplevels.length <= 3) ? - appButton.countDotWidth : appButton.countDotHeight // Circles when too many - implicitHeight: appButton.countDotHeight - color: appIsActive ? Appearance.m3colors.m3primary : ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.4) + + RowLayout { + spacing: 3 + anchors { + top: iconImageLoader.bottom + topMargin: 2 + horizontalCenter: parent.horizontalCenter + } + Repeater { + model: Math.min(appToplevel.toplevels.length, 3) + delegate: Rectangle { + required property int index + radius: Appearance.rounding.full + implicitWidth: (appToplevel.toplevels.length <= 3) ? + root.countDotWidth : root.countDotHeight // Circles when too many + implicitHeight: root.countDotHeight + color: appIsActive ? Appearance.m3colors.m3primary : ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.4) + } } } } diff --git a/.config/quickshell/modules/dock/DockApps.qml b/.config/quickshell/modules/dock/DockApps.qml index 9a8db93a9..900db0ca0 100644 --- a/.config/quickshell/modules/dock/DockApps.qml +++ b/.config/quickshell/modules/dock/DockApps.qml @@ -23,8 +23,9 @@ Item { property Item lastHoveredButton property bool buttonHovered: false property bool requestDockShow: previewPopup.show - property real popupX: parentWindow.mapFromItem(root.lastHoveredButton, root.lastHoveredButton.width / 2, root.lastHoveredButton.height / 2).x - implicitWidth / 2 - + property var parentWindow: root.QsWindow + property real popupX: parentWindow?.mapFromItem(root.lastHoveredButton, root.lastHoveredButton?.width / 2, root.lastHoveredButton?.height / 2).x - implicitWidth / 2 + ?? 0 implicitWidth: rowLayout.implicitWidth implicitHeight: rowLayout.implicitHeight @@ -38,15 +39,33 @@ Item { values: { var map = new Map(); + // Pinned apps + const pinnedApps = ConfigOptions?.dock.pinnedApps ?? []; + for (const appId of pinnedApps) { + if (!map.has(appId.toLowerCase())) map.set(appId.toLowerCase(), ({ + pinned: true, + toplevels: [] + })); + } + + // Separator + if (pinnedApps.length > 0) { + map.set("SEPARATOR", { pinned: false, toplevels: [] }); + } + + // Open windows for (const toplevel of ToplevelManager.toplevels.values) { - if (!map.has(toplevel.appId.toLowerCase())) map.set(toplevel.appId.toLowerCase(), []); - map.get(toplevel.appId.toLowerCase()).push(toplevel); + if (!map.has(toplevel.appId.toLowerCase())) map.set(toplevel.appId.toLowerCase(), ({ + pinned: false, + toplevels: [] + })); + map.get(toplevel.appId.toLowerCase()).toplevels.push(toplevel); } var values = []; for (const [key, value] of map) { - values.push({ appId: key, toplevels: value }); + values.push({ appId: key, toplevels: value.toplevels, pinned: value.pinned }); } return values; @@ -118,14 +137,9 @@ Item { anchors.bottom: parent.bottom implicitWidth: popupBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 implicitHeight: root.maxWindowPreviewHeight + root.windowControlsHeight + Appearance.sizes.elevationMargin * 2 - // anchors.horizontalCenter: parent.horizontalCenter hoverEnabled: true - // x: previewPopup.width / 2 + root.popupX - // Behavior on x { - // animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - // } x: { - const itemCenter = root.QsWindow.mapFromItem(root.lastHoveredButton, root.lastHoveredButton.width / 2, 0); + const itemCenter = root.QsWindow?.mapFromItem(root.lastHoveredButton, root.lastHoveredButton?.width / 2, 0); return itemCenter.x - width / 2 } StyledRectangularShadow { @@ -163,7 +177,9 @@ Item { id: previewRowLayout anchors.centerIn: parent Repeater { - model: previewPopup.appTopLevel?.toplevels ?? [] + model: ScriptModel { + values: previewPopup.appTopLevel?.toplevels ?? [] + } RippleButton { id: windowButton required property var modelData From 0beccd69e86f894403d276583ee60e76debedc78 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 17:47:57 +0200 Subject: [PATCH 730/824] overview: adjust hover/click colors --- .config/quickshell/modules/overview/OverviewWindow.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index 6436120e2..17a3b4bc2 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -68,6 +68,7 @@ Item { // Window } ScreencopyView { + id: windowPreview anchors.fill: parent captureSource: GlobalStates.overviewOpen ? root.toplevel : null live: true @@ -76,7 +77,7 @@ Item { // Window anchors.fill: parent radius: Appearance.rounding.windowRounding * root.scale color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 - opacity: pressed ? 0.3 : hovered ? 0.2 : 0 + opacity: pressed ? 0.5 : hovered ? 0.3 : 0 border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.9) border.width : 1 } From 6fffa86fc2a0d9f88342dc0f57ed097f3924649a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 18:08:10 +0200 Subject: [PATCH 731/824] dock: middle click to open new window --- .config/quickshell/modules/dock/DockAppButton.qml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/dock/DockAppButton.qml b/.config/quickshell/modules/dock/DockAppButton.qml index 2304fe158..5ddb6d150 100644 --- a/.config/quickshell/modules/dock/DockAppButton.qml +++ b/.config/quickshell/modules/dock/DockAppButton.qml @@ -24,6 +24,7 @@ DockButton { property bool appIsActive: appToplevel.toplevels.find(t => (t.activated == true)) !== undefined property bool isSeparator: appToplevel.appId === "SEPARATOR" + property var desktopEntry: DesktopEntries.byId(appToplevel.appId) enabled: !isSeparator implicitWidth: isSeparator ? 1 : implicitHeight - topInset - bottomInset @@ -60,13 +61,17 @@ DockButton { onClicked: { if (appToplevel.toplevels.length === 0) { - DesktopEntries.byId(root.appToplevel.appId)?.execute(); + root.desktopEntry?.execute(); return; } lastFocused = (lastFocused + 1) % appToplevel.toplevels.length appToplevel.toplevels[lastFocused].activate() } + middleClickAction: () => { + root.desktopEntry?.execute(); + } + contentItem: Loader { active: !isSeparator sourceComponent: Item { From cfb8a851e6bfa2028c0ca4dbbd5010d4416dbd06 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 22:34:04 +0200 Subject: [PATCH 732/824] readme: screenshots --- README.md | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index a845e4899..123b8cc6b 100644 --- a/README.md +++ b/README.md @@ -81,23 +81,21 @@ It's ready if you don't need localization... so quite likely

-## Main branch (*illogical-impulse*) +## illogical-impulseQuickshell -### AI -![image](https://github.com/user-attachments/assets/9d7af13f-89ef-470d-ba78-d2288b79cf60) -_Sidebar offers online and offline chat. Text selection summary is offline only for privacy._ +| AI | Common widgets | +|:---|:---------------| +| ![image](https://github.com/user-attachments/assets/08d26785-b54d-4ad1-875b-bb08cc6757f5) | ![image](https://github.com/user-attachments/assets/4fcd63d9-0943-4b21-8737-4bed97b71961) | +| Window management | Weeb power | +| ![image](https://github.com/user-attachments/assets/86cc511b-0d33-4c78-bcc0-3037d02a17da) | ![image](https://github.com/user-attachments/assets/a34a9664-326a-4e66-a643-480d6f7fcc19) | -### Notifications, music controls, system, calendar -![image](https://github.com/end-4/dots-hyprland/assets/97237370/406b72b6-fa38-4f0d-a6c4-4d7d5d5ddcb7) -_On the sidebar: flicking the notification_ +## illogical-impulseAGS -### Intuitive window management -![image](https://github.com/user-attachments/assets/02983b9b-79ba-4c25-8717-90bef2357ae5) -_You can also drag and drop windows across workspaces_ - -### Power to weebs -![image](https://github.com/user-attachments/assets/bbb332ec-962a-4e88-a95b-486d0bd8ce76) -_Get yande.re and konachan images from sidebar_ +| AI | Common widgets | +|:---|:---------------| +| ![image](https://github.com/user-attachments/assets/9d7af13f-89ef-470d-ba78-d2288b79cf60) | ![image](https://github.com/end-4/dots-hyprland/assets/97237370/406b72b6-fa38-4f0d-a6c4-4d7d5d5ddcb7) | +| Window management | Weeb power | +| ![image](https://github.com/user-attachments/assets/02983b9b-79ba-4c25-8717-90bef2357ae5) | ![image](https://github.com/user-attachments/assets/bbb332ec-962a-4e88-a95b-486d0bd8ce76) | ## Unsupported stuff From 2c4537bd024b012833451ee875388f4d5c9b2140 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 22:36:17 +0200 Subject: [PATCH 733/824] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 123b8cc6b..9403c73f2 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ It's ready if you don't need localization... so quite likely |:---|:---------------| | ![image](https://github.com/user-attachments/assets/08d26785-b54d-4ad1-875b-bb08cc6757f5) | ![image](https://github.com/user-attachments/assets/4fcd63d9-0943-4b21-8737-4bed97b71961) | | Window management | Weeb power | -| ![image](https://github.com/user-attachments/assets/86cc511b-0d33-4c78-bcc0-3037d02a17da) | ![image](https://github.com/user-attachments/assets/a34a9664-326a-4e66-a643-480d6f7fcc19) | +| ![image](https://github.com/user-attachments/assets/86cc511b-0d33-4c78-bcc0-3037d02a17da) | ![image](https://github.com/user-attachments/assets/e402f74a-6bd8-4ebe-bcf4-3a4a4846de10) | ## illogical-impulseAGS From 56958236e2f936961f50766d8589627083cdf1e2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 23:04:04 +0200 Subject: [PATCH 734/824] nicer link colors --- .config/quickshell/modules/common/widgets/StyledLabel.qml | 1 + .config/quickshell/modules/common/widgets/StyledText.qml | 1 + 2 files changed, 2 insertions(+) diff --git a/.config/quickshell/modules/common/widgets/StyledLabel.qml b/.config/quickshell/modules/common/widgets/StyledLabel.qml index 20a1cc9f0..f5201baea 100644 --- a/.config/quickshell/modules/common/widgets/StyledLabel.qml +++ b/.config/quickshell/modules/common/widgets/StyledLabel.qml @@ -12,4 +12,5 @@ Label { pixelSize: Appearance?.font.pixelSize.small ?? 15 } color: Appearance?.m3colors.m3onBackground ?? "black" + linkColor: Appearance?.m3colors.m3primary } diff --git a/.config/quickshell/modules/common/widgets/StyledText.qml b/.config/quickshell/modules/common/widgets/StyledText.qml index 6eef57852..7750456e0 100644 --- a/.config/quickshell/modules/common/widgets/StyledText.qml +++ b/.config/quickshell/modules/common/widgets/StyledText.qml @@ -11,4 +11,5 @@ Text { pixelSize: Appearance?.font.pixelSize.small ?? 15 } color: Appearance?.m3colors.m3onBackground ?? "black" + linkColor: Appearance?.m3colors.m3primary } From a6ecf107b3c1f61183cab9b3a65ed24b5147ba99 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 23:04:35 +0200 Subject: [PATCH 735/824] quickshell: fix some warnings --- .../modules/notificationPopup/NotificationPopup.qml | 2 +- .config/quickshell/modules/overview/Overview.qml | 4 ++-- .config/quickshell/modules/sidebarLeft/Anime.qml | 4 +++- .config/quickshell/services/Audio.qml | 3 --- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.config/quickshell/modules/notificationPopup/NotificationPopup.qml b/.config/quickshell/modules/notificationPopup/NotificationPopup.qml index 122489d88..fb046343d 100644 --- a/.config/quickshell/modules/notificationPopup/NotificationPopup.qml +++ b/.config/quickshell/modules/notificationPopup/NotificationPopup.qml @@ -15,7 +15,7 @@ Scope { PanelWindow { id: root visible: (Notifications.popupList.length > 0) - screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) + screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? null WlrLayershell.namespace: "quickshell:notificationPopup" WlrLayershell.layer: WlrLayer.Overlay diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 6fa85e891..d3e4e4567 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -92,8 +92,8 @@ Scope { visible: GlobalStates.overviewOpen anchors { horizontalCenter: parent.horizontalCenter - top: !ConfigOptions.bar.bottom ? parent.top : null - bottom: ConfigOptions.bar.bottom ? parent.bottom : null + top: !ConfigOptions.bar.bottom ? parent.top : undefined + bottom: ConfigOptions.bar.bottom ? parent.bottom : undefined } Keys.onPressed: (event) => { diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index acc1af4fe..444d3f052 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -562,8 +562,10 @@ Item { text: "•" } - Rectangle { // NSFW toggle + Item { // NSFW toggle + visible: width > 0 implicitWidth: switchesRow.implicitWidth + Layout.fillHeight: true RowLayout { id: switchesRow diff --git a/.config/quickshell/services/Audio.qml b/.config/quickshell/services/Audio.qml index dd46f0fbb..2fd5e0cac 100644 --- a/.config/quickshell/services/Audio.qml +++ b/.config/quickshell/services/Audio.qml @@ -19,9 +19,6 @@ Singleton { PwObjectTracker { objects: [sink, source] - Component.onCompleted: { - sink.audio.volume = sink.audio.volume; // Trigger initial volume change - } } Connections { // Protection against sudden volume changes From 93a14293aa3da453705c8c04d934ea44bcaf8dc3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 23:08:51 +0200 Subject: [PATCH 736/824] overview: clearer window borders --- .config/quickshell/modules/overview/OverviewWindow.qml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index 17a3b4bc2..3b376988b 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -76,9 +76,10 @@ Item { // Window Rectangle { anchors.fill: parent radius: Appearance.rounding.windowRounding * root.scale - color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 - opacity: pressed ? 0.5 : hovered ? 0.3 : 0 - border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.9) + color: pressed ? ColorUtils.transparentize(Appearance.colors.colLayer2Active, 0.5) : + hovered ? ColorUtils.transparentize(Appearance.colors.colLayer2Hover, 0.7) : + ColorUtils.transparentize(Appearance.colors.colLayer2) + border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.7) border.width : 1 } From b3a3975461e3ab4f3555a210595f3e98f173c566 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 23:29:52 +0200 Subject: [PATCH 737/824] dock: fix alignment --- .config/quickshell/modules/dock/Dock.qml | 8 +- .config/quickshell/modules/dock/DockApps.qml | 99 ++++++++++--------- .../quickshell/modules/dock/DockButton.qml | 5 +- .../quickshell/modules/dock/DockSeparator.qml | 4 +- 4 files changed, 63 insertions(+), 53 deletions(-) diff --git a/.config/quickshell/modules/dock/Dock.qml b/.config/quickshell/modules/dock/Dock.qml index 25846ac5f..a03a3f562 100644 --- a/.config/quickshell/modules/dock/Dock.qml +++ b/.config/quickshell/modules/dock/Dock.qml @@ -88,7 +88,7 @@ Scope { // Scope id: dockVisualBackground property real margin: Appearance.sizes.elevationMargin anchors.fill: parent - anchors.topMargin: margin + anchors.topMargin: Appearance.sizes.elevationMargin anchors.bottomMargin: Appearance.sizes.hyprlandGapsOut color: Appearance.colors.colLayer0 radius: Appearance.rounding.large @@ -103,6 +103,7 @@ Scope { // Scope property real padding: 5 VerticalButtonGroup { + Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work GroupButton { // Pin button baseWidth: 35 baseHeight: 35 @@ -120,12 +121,13 @@ Scope { // Scope } } DockSeparator {} - DockApps { id: dockApps } + DockApps { id: dockApps; } DockSeparator {} DockButton { + Layout.fillHeight: true onClicked: Hyprland.dispatch("global quickshell:overviewToggle") contentItem: MaterialSymbol { - anchors.centerIn: parent + anchors.fill: parent horizontalAlignment: Text.AlignHCenter font.pixelSize: parent.width / 2 text: "apps" diff --git a/.config/quickshell/modules/dock/DockApps.qml b/.config/quickshell/modules/dock/DockApps.qml index 900db0ca0..8b680c51a 100644 --- a/.config/quickshell/modules/dock/DockApps.qml +++ b/.config/quickshell/modules/dock/DockApps.qml @@ -23,59 +23,66 @@ Item { property Item lastHoveredButton property bool buttonHovered: false property bool requestDockShow: previewPopup.show - property var parentWindow: root.QsWindow - property real popupX: parentWindow?.mapFromItem(root.lastHoveredButton, root.lastHoveredButton?.width / 2, root.lastHoveredButton?.height / 2).x - implicitWidth / 2 - ?? 0 - implicitWidth: rowLayout.implicitWidth - implicitHeight: rowLayout.implicitHeight - RowLayout { - id: rowLayout + Layout.fillHeight: true + Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work + implicitWidth: listView.implicitWidth + + StyledListView { + id: listView spacing: 2 + orientation: ListView.Horizontal + anchors { + top: parent.top + bottom: parent.bottom + } + implicitWidth: contentWidth - Repeater { - model: ScriptModel { - objectProp: "appId" - values: { - var map = new Map(); + Behavior on implicitWidth { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } - // Pinned apps - const pinnedApps = ConfigOptions?.dock.pinnedApps ?? []; - for (const appId of pinnedApps) { - if (!map.has(appId.toLowerCase())) map.set(appId.toLowerCase(), ({ - pinned: true, - toplevels: [] - })); - } + model: ScriptModel { + objectProp: "appId" + values: { + var map = new Map(); - // Separator - if (pinnedApps.length > 0) { - map.set("SEPARATOR", { pinned: false, toplevels: [] }); - } - - // Open windows - for (const toplevel of ToplevelManager.toplevels.values) { - if (!map.has(toplevel.appId.toLowerCase())) map.set(toplevel.appId.toLowerCase(), ({ - pinned: false, - toplevels: [] - })); - map.get(toplevel.appId.toLowerCase()).toplevels.push(toplevel); - } - - var values = []; - - for (const [key, value] of map) { - values.push({ appId: key, toplevels: value.toplevels, pinned: value.pinned }); - } - - return values; + // Pinned apps + const pinnedApps = ConfigOptions?.dock.pinnedApps ?? []; + for (const appId of pinnedApps) { + if (!map.has(appId.toLowerCase())) map.set(appId.toLowerCase(), ({ + pinned: true, + toplevels: [] + })); } + + // Separator + if (pinnedApps.length > 0) { + map.set("SEPARATOR", { pinned: false, toplevels: [] }); + } + + // Open windows + for (const toplevel of ToplevelManager.toplevels.values) { + if (!map.has(toplevel.appId.toLowerCase())) map.set(toplevel.appId.toLowerCase(), ({ + pinned: false, + toplevels: [] + })); + map.get(toplevel.appId.toLowerCase()).toplevels.push(toplevel); + } + + var values = []; + + for (const [key, value] of map) { + values.push({ appId: key, toplevels: value.toplevels, pinned: value.pinned }); + } + + return values; } - delegate: DockAppButton { - required property var modelData - appToplevel: modelData - appListRoot: root - } + } + delegate: DockAppButton { + required property var modelData + appToplevel: modelData + appListRoot: root } } diff --git a/.config/quickshell/modules/dock/DockButton.qml b/.config/quickshell/modules/dock/DockButton.qml index 6c3010bf6..577cbcdc7 100644 --- a/.config/quickshell/modules/dock/DockButton.qml +++ b/.config/quickshell/modules/dock/DockButton.qml @@ -7,9 +7,10 @@ import QtQuick.Layouts RippleButton { Layout.fillHeight: true + Layout.topMargin: Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut implicitWidth: implicitHeight - topInset - bottomInset buttonRadius: Appearance.rounding.normal - topInset: dockVisualBackground.margin + dockRow.padding - bottomInset: dockVisualBackground.margin + dockRow.padding + topInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding + bottomInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding } diff --git a/.config/quickshell/modules/dock/DockSeparator.qml b/.config/quickshell/modules/dock/DockSeparator.qml index 2b27b0daf..29b77d492 100644 --- a/.config/quickshell/modules/dock/DockSeparator.qml +++ b/.config/quickshell/modules/dock/DockSeparator.qml @@ -5,8 +5,8 @@ import QtQuick.Controls import QtQuick.Layouts Rectangle { - Layout.topMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal - Layout.bottomMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal + Layout.topMargin: Appearance.sizes.elevationMargin + dockRow.padding + Appearance.rounding.normal + Layout.bottomMargin: Appearance.sizes.hyprlandGapsOut + dockRow.padding + Appearance.rounding.normal Layout.fillHeight: true implicitWidth: 1 color: Appearance.m3colors.m3outlineVariant From 49888f30f00142a93330bac74a890509d0930f12 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 9 Jun 2025 23:35:17 +0200 Subject: [PATCH 738/824] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9403c73f2..4cc7899e4 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,10 @@ It's ready if you don't need localization... so quite likely | Window management | Weeb power | | ![image](https://github.com/user-attachments/assets/86cc511b-0d33-4c78-bcc0-3037d02a17da) | ![image](https://github.com/user-attachments/assets/e402f74a-6bd8-4ebe-bcf4-3a4a4846de10) | +By the way... +- The funny notification positions are mimicking Android 16's dragging behavior +- The clock on the wallpaper is automatically placed at the "least busy" region of the image + ## illogical-impulseAGS | AI | Common widgets | From bec43ff8ed3bbe02057a03ef6ed73cccc0355570 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 10 Jun 2025 08:53:00 +0200 Subject: [PATCH 739/824] dock: fix hover to reveal behavior (#1375) --- .config/quickshell/modules/dock/Dock.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/dock/Dock.qml b/.config/quickshell/modules/dock/Dock.qml index a03a3f562..d5b3912e9 100644 --- a/.config/quickshell/modules/dock/Dock.qml +++ b/.config/quickshell/modules/dock/Dock.qml @@ -56,8 +56,9 @@ Scope { // Scope anchors.top: parent.top height: parent.height anchors.topMargin: dockRoot.reveal ? 0 : - ConfigOptions?.dock.hoverToReveal ? (dockRoot.implicitHeight + 1) : - (dockRoot.implicitHeight - ConfigOptions.dock.hoverRegionHeight) + ConfigOptions?.dock.hoverToReveal ? (dockRoot.implicitHeight - ConfigOptions.dock.hoverRegionHeight) : + (dockRoot.implicitHeight + 1) + anchors.left: parent.left anchors.right: parent.right hoverEnabled: true From cf652482c5b20794255984d31988d9761e12e87d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 10 Jun 2025 08:59:31 +0200 Subject: [PATCH 740/824] better control -> plasma wifi widget --- .config/quickshell/modules/common/ConfigOptions.qml | 6 +++--- arch-packages/illogical-impulse-kde/PKGBUILD | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index ce6e45aa8..eeba677fc 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -21,10 +21,10 @@ Singleton { } property QtObject apps: QtObject { - property string bluetooth: "systemsettings kcm_bluetooth" + property string bluetooth: "kcmshell6 kcm_bluetooth" property string imageViewer: "loupe" - property string network: "better-control --wifi" - property string networkEthernet: "systemsettings kcm_networkmanagement" + property string network: "plasmawindowed org.kde.plasma.networkmanagement" + property string networkEthernet: "kcmshell6 kcm_networkmanagement" property string settings: "systemsettings" property string taskManager: "plasma-systemmonitor --page-name Processes" property string terminal: "kitty -1" // This is only for shell actions diff --git a/arch-packages/illogical-impulse-kde/PKGBUILD b/arch-packages/illogical-impulse-kde/PKGBUILD index d4f5e4058..e699db846 100644 --- a/arch-packages/illogical-impulse-kde/PKGBUILD +++ b/arch-packages/illogical-impulse-kde/PKGBUILD @@ -7,5 +7,5 @@ license=(None) depends=( polkit-kde-agent gnome-keyring - networkmanager better-control-git + networkmanager ) From f7f4affe515b81fb37f02059211a09e4b3afa59e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 10 Jun 2025 09:14:54 +0200 Subject: [PATCH 741/824] dock: unload when not needed --- .config/quickshell/modules/dock/Dock.qml | 213 ++++++++++++----------- 1 file changed, 108 insertions(+), 105 deletions(-) diff --git a/.config/quickshell/modules/dock/Dock.qml b/.config/quickshell/modules/dock/Dock.qml index d5b3912e9..524fbc11f 100644 --- a/.config/quickshell/modules/dock/Dock.qml +++ b/.config/quickshell/modules/dock/Dock.qml @@ -18,127 +18,130 @@ Scope { // Scope Variants { // For each monitor model: Quickshell.screens - PanelWindow { // Window + + Loader { + id: dockLoader required property var modelData - id: dockRoot - screen: modelData - - property bool reveal: root.pinned - || (ConfigOptions?.dock.hoverToReveal && dockMouseArea.containsMouse) - || dockApps.requestDockShow - || (!ToplevelManager.activeToplevel?.activated) + active: ConfigOptions?.dock.hoverToReveal || (!ToplevelManager.activeToplevel?.activated) - anchors { - bottom: true - left: true - right: true - } + sourceComponent: PanelWindow { // Window + id: dockRoot + screen: dockLoader.modelData + + property bool reveal: root.pinned + || (ConfigOptions?.dock.hoverToReveal && dockMouseArea.containsMouse) + || dockApps.requestDockShow + || (!ToplevelManager.activeToplevel?.activated) - function hide() { - cheatsheetLoader.active = false - } - exclusiveZone: root.pinned ? implicitHeight - - (Appearance.sizes.hyprlandGapsOut) - - (Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut) : 0 - - implicitWidth: dockBackground.implicitWidth - WlrLayershell.namespace: "quickshell:dock" - color: "transparent" - - implicitHeight: (ConfigOptions?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut - - mask: Region { - item: dockMouseArea - } - - MouseArea { - id: dockMouseArea - anchors.top: parent.top - height: parent.height - anchors.topMargin: dockRoot.reveal ? 0 : - ConfigOptions?.dock.hoverToReveal ? (dockRoot.implicitHeight - ConfigOptions.dock.hoverRegionHeight) : - (dockRoot.implicitHeight + 1) - - anchors.left: parent.left - anchors.right: parent.right - hoverEnabled: true - - Behavior on anchors.topMargin { - animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + anchors { + bottom: true + left: true + right: true } - Item { - id: dockHoverRegion - anchors.fill: parent + exclusiveZone: root.pinned ? implicitHeight + - (Appearance.sizes.hyprlandGapsOut) + - (Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut) : 0 - Item { // Wrapper for the dock background - id: dockBackground - anchors { - top: parent.top - bottom: parent.bottom - horizontalCenter: parent.horizontalCenter - } + implicitWidth: dockBackground.implicitWidth + WlrLayershell.namespace: "quickshell:dock" + color: "transparent" - implicitWidth: dockRow.implicitWidth + 5 * 2 - height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut + implicitHeight: (ConfigOptions?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut - StyledRectangularShadow { - target: dockVisualBackground - } - Rectangle { // The real rectangle that is visible - id: dockVisualBackground - property real margin: Appearance.sizes.elevationMargin - anchors.fill: parent - anchors.topMargin: Appearance.sizes.elevationMargin - anchors.bottomMargin: Appearance.sizes.hyprlandGapsOut - color: Appearance.colors.colLayer0 - radius: Appearance.rounding.large - } + mask: Region { + item: dockMouseArea + } - RowLayout { - id: dockRow - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - spacing: 3 - property real padding: 5 + MouseArea { + id: dockMouseArea + anchors.top: parent.top + height: parent.height + anchors.topMargin: dockRoot.reveal ? 0 : + ConfigOptions?.dock.hoverToReveal ? (dockRoot.implicitHeight - ConfigOptions.dock.hoverRegionHeight) : + (dockRoot.implicitHeight + 1) + + anchors.left: parent.left + anchors.right: parent.right + hoverEnabled: true - VerticalButtonGroup { - Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work - GroupButton { // Pin button - baseWidth: 35 - baseHeight: 35 - clickedWidth: baseWidth - clickedHeight: baseHeight + 20 - buttonRadius: Appearance.rounding.normal - toggled: root.pinned - onClicked: root.pinned = !root.pinned + Behavior on anchors.topMargin { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + + Item { + id: dockHoverRegion + anchors.fill: parent + + Item { // Wrapper for the dock background + id: dockBackground + anchors { + top: parent.top + bottom: parent.bottom + horizontalCenter: parent.horizontalCenter + } + + implicitWidth: dockRow.implicitWidth + 5 * 2 + height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut + + StyledRectangularShadow { + target: dockVisualBackground + } + Rectangle { // The real rectangle that is visible + id: dockVisualBackground + property real margin: Appearance.sizes.elevationMargin + anchors.fill: parent + anchors.topMargin: Appearance.sizes.elevationMargin + anchors.bottomMargin: Appearance.sizes.hyprlandGapsOut + color: Appearance.colors.colLayer0 + radius: Appearance.rounding.large + } + + RowLayout { + id: dockRow + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + spacing: 3 + property real padding: 5 + + VerticalButtonGroup { + Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work + GroupButton { // Pin button + baseWidth: 35 + baseHeight: 35 + clickedWidth: baseWidth + clickedHeight: baseHeight + 20 + buttonRadius: Appearance.rounding.normal + toggled: root.pinned + onClicked: root.pinned = !root.pinned + contentItem: MaterialSymbol { + text: "keep" + horizontalAlignment: Text.AlignHCenter + iconSize: Appearance.font.pixelSize.larger + color: root.pinned ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0 + } + } + } + DockSeparator {} + DockApps { id: dockApps; } + DockSeparator {} + DockButton { + Layout.fillHeight: true + onClicked: Hyprland.dispatch("global quickshell:overviewToggle") contentItem: MaterialSymbol { - text: "keep" + anchors.fill: parent horizontalAlignment: Text.AlignHCenter - iconSize: Appearance.font.pixelSize.larger - color: root.pinned ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0 + font.pixelSize: parent.width / 2 + text: "apps" + color: Appearance.colors.colOnLayer0 } } } - DockSeparator {} - DockApps { id: dockApps; } - DockSeparator {} - DockButton { - Layout.fillHeight: true - onClicked: Hyprland.dispatch("global quickshell:overviewToggle") - contentItem: MaterialSymbol { - anchors.fill: parent - horizontalAlignment: Text.AlignHCenter - font.pixelSize: parent.width / 2 - text: "apps" - color: Appearance.colors.colOnLayer0 - } - } - } - } - } + } + } + } } } } From a1e052b0708a7f680bb411064b0041a04f94bcb9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 10 Jun 2025 09:15:48 +0200 Subject: [PATCH 742/824] Update rules.conf --- .config/hypr/hyprland/rules.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index fe1f5da4c..d7ce7a061 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -19,6 +19,8 @@ windowrulev2 = center, class:^(org.pulseaudio.pavucontrol)$ windowrulev2 = float, class:^(nm-connection-editor)$ windowrulev2 = size 45%, class:^(nm-connection-editor)$ windowrulev2 = center, class:^(nm-connection-editor)$ +windowrulev2 = center, class:.*plasmawindowed.* +windowrulev2 = center, class:kcm_bluetooth # No appearance # kde-material-you-colors spawns a window when changing dark/light theme. This is to make sure it doesn't interfere at all. @@ -116,6 +118,7 @@ layerrule = animation fade, quickshell:screenCorners layerrule = animation slide right, quickshell:sidebarRight layerrule = animation slide left, quickshell:sidebarLeft layerrule = animation slide bottom, quickshell:osk +layerrule = animation slide bottom, quickshell:dock layerrule = blur, quickshell:session layerrule = noanim, quickshell:session layerrule = animation fade, quickshell:notificationPopup From de941d8b9ede4c77826d942dfb5eb7e1ed987e8e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 10 Jun 2025 11:24:52 +0200 Subject: [PATCH 743/824] hyprland: fix float rules for plasma stuff --- .config/hypr/hyprland/rules.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index d7ce7a061..42897cafe 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -19,8 +19,8 @@ windowrulev2 = center, class:^(org.pulseaudio.pavucontrol)$ windowrulev2 = float, class:^(nm-connection-editor)$ windowrulev2 = size 45%, class:^(nm-connection-editor)$ windowrulev2 = center, class:^(nm-connection-editor)$ -windowrulev2 = center, class:.*plasmawindowed.* -windowrulev2 = center, class:kcm_bluetooth +windowrulev2 = float, class:.*plasmawindowed.* +windowrulev2 = float, class:kcm_.* # No appearance # kde-material-you-colors spawns a window when changing dark/light theme. This is to make sure it doesn't interfere at all. From 4f8574ef50e4cf40104cdb607d213a54c2db152e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 10 Jun 2025 13:24:58 +0200 Subject: [PATCH 744/824] wallpaper region limit: use min size of all instead of max --- .config/quickshell/scripts/switchwall.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/scripts/switchwall.sh b/.config/quickshell/scripts/switchwall.sh index adc76201d..dffd9bd61 100755 --- a/.config/quickshell/scripts/switchwall.sh +++ b/.config/quickshell/scripts/switchwall.sh @@ -233,9 +233,9 @@ switch() { deactivate # Pass screen width, height, and wallpaper path to post_process - min_width_desired="$(hyprctl monitors -j | jq '([.[].width] | max)' | xargs)" - min_height_desired="$(hyprctl monitors -j | jq '([.[].height] | max)' | xargs)" - post_process "$min_width_desired" "$min_height_desired" "$imgpath" + max_width_desired="$(hyprctl monitors -j | jq '([.[].width] | min)' | xargs)" + max_height_desired="$(hyprctl monitors -j | jq '([.[].height] | min)' | xargs)" + post_process "$max_width_desired" "$max_height_desired" "$imgpath" } main() { From bd0438be834c4200aefe8aec2d805f289932e22e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 10 Jun 2025 13:35:45 +0200 Subject: [PATCH 745/824] bg clock: fix positioning on scaled screens --- .../modules/backgroundWidgets/BackgroundWidgets.qml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml b/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml index 970dc8d17..3fb5471df 100644 --- a/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml +++ b/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml @@ -58,12 +58,13 @@ Scope { Loader { required property var modelData + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(modelData) active: !ToplevelManager.activeToplevel?.activated sourceComponent: PanelWindow { // Window id: windowRoot screen: modelData - property var textHorizontalAlignment: root.centerX < windowRoot.width / 3 ? Text.AlignLeft : - (root.centerX > windowRoot.width * 2 / 3 ? Text.AlignRight : Text.AlignHCenter) + property var textHorizontalAlignment: root.centerX / monitor.scale < windowRoot.width / 3 ? Text.AlignLeft : + (root.centerX / monitor.scale > windowRoot.width * 2 / 3 ? Text.AlignRight : Text.AlignHCenter) WlrLayershell.layer: WlrLayer.Bottom WlrLayershell.namespace: "quickshell:backgroundWidgets" @@ -90,8 +91,8 @@ Scope { anchors { left: parent.left top: parent.top - leftMargin: root.centerX - implicitWidth / 2 - topMargin: root.centerY - implicitHeight / 2 + leftMargin: (root.centerX / monitor.scale - implicitWidth / 2) + topMargin: (root.centerY / monitor.scale - implicitHeight / 2) Behavior on leftMargin { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } From bbee1e85ac2645d17b04e53e622aa2ca39edb886 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 00:33:53 +0200 Subject: [PATCH 746/824] deps: add bluedevil and plasma-nm --- arch-packages/illogical-impulse-kde/PKGBUILD | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch-packages/illogical-impulse-kde/PKGBUILD b/arch-packages/illogical-impulse-kde/PKGBUILD index e699db846..fd3a745f6 100644 --- a/arch-packages/illogical-impulse-kde/PKGBUILD +++ b/arch-packages/illogical-impulse-kde/PKGBUILD @@ -5,7 +5,9 @@ pkgdesc='Illogical Impulse KDE Dependencies' arch=(any) license=(None) depends=( - polkit-kde-agent + bluedevil gnome-keyring networkmanager + plasma-nm + polkit-kde-agent ) From 80f809b8b214dd9d9296fd765f0d93c752797440 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 00:36:09 +0200 Subject: [PATCH 747/824] config options: make fakescreenrounding default to 2 --- .config/quickshell/modules/common/ConfigOptions.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index eeba677fc..4aa54fa52 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -9,7 +9,7 @@ Singleton { } property QtObject appearance: QtObject { - property int fakeScreenRounding: 1 // 0: None | 1: Always | 2: When not fullscreen + property int fakeScreenRounding: 2 // 0: None | 1: Always | 2: When not fullscreen } property QtObject audio: QtObject { // Values in % From 74e049b7b472104f63465e5aa38c6e96d5cff2cc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 00:36:18 +0200 Subject: [PATCH 748/824] Directories: create shell config dir --- .config/quickshell/modules/common/Directories.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/modules/common/Directories.qml b/.config/quickshell/modules/common/Directories.qml index b256666fc..d2c570234 100644 --- a/.config/quickshell/modules/common/Directories.qml +++ b/.config/quickshell/modules/common/Directories.qml @@ -32,6 +32,7 @@ Singleton { property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/scripts/switchwall.sh`) // Cleanup on init Component.onCompleted: { + Hyprland.dispatch(`exec mkdir -p '${shellConfig}'`) Hyprland.dispatch(`exec mkdir -p '${favicons}'`) Hyprland.dispatch(`exec rm -rf '${coverArt}'; mkdir -p '${coverArt}'`) Hyprland.dispatch(`exec rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`) From fd5553a0ad89a8bbe15e8e97015948c9c434b916 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 00:36:26 +0200 Subject: [PATCH 749/824] format --- .config/quickshell/modules/screenCorners/ScreenCorners.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/screenCorners/ScreenCorners.qml b/.config/quickshell/modules/screenCorners/ScreenCorners.qml index 37f0ff23b..3988d73d8 100644 --- a/.config/quickshell/modules/screenCorners/ScreenCorners.qml +++ b/.config/quickshell/modules/screenCorners/ScreenCorners.qml @@ -15,7 +15,9 @@ Scope { model: Quickshell.screens PanelWindow { - visible: (ConfigOptions.appearance.fakeScreenRounding === 1 || (ConfigOptions.appearance.fakeScreenRounding === 2 && !activeWindow?.fullscreen)) + visible: (ConfigOptions.appearance.fakeScreenRounding === 1 + || (ConfigOptions.appearance.fakeScreenRounding === 2 + && !activeWindow?.fullscreen)) property var modelData From 0ad006eea1a59a049761be3a3068eeef21d313ba Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 00:40:07 +0200 Subject: [PATCH 750/824] config loader: write back on load to include new options in user config --- .config/quickshell/services/ConfigLoader.qml | 25 ++++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/services/ConfigLoader.qml b/.config/quickshell/services/ConfigLoader.qml index 788fc728d..61aa9b4ed 100644 --- a/.config/quickshell/services/ConfigLoader.qml +++ b/.config/quickshell/services/ConfigLoader.qml @@ -20,23 +20,30 @@ Singleton { id: root property string filePath: Directories.shellConfigPath property bool firstLoad: true + property bool preventNextLoad: false function loadConfig() { configFileView.reload() } function applyConfig(fileContent) { + console.log("[ConfigLoader] Applying config from file:", root.filePath); try { + if (fileContent.trim() === "") { + console.warn("[ConfigLoader] Config file is empty, skipping load."); + return; + } const json = JSON.parse(fileContent); ObjectUtils.applyToQtObject(ConfigOptions, json); if (root.firstLoad) { root.firstLoad = false; - } else { - Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`) + root.preventNextLoad = true; + root.saveConfig(); // Make sure new properties are added to the user's config file } } catch (e) { console.error("[ConfigLoader] Error reading file:", e); + console.log("[ConfigLoader] File content was:", fileContent); Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`) return; @@ -85,7 +92,16 @@ Singleton { interval: ConfigOptions.hacks.arbitraryRaceConditionDelay running: false onTriggered: { - root.applyConfig(configFileView.text()) + if (root.preventNextLoad) { + root.preventNextLoad = false; + return; + } + if (root.firstLoad) { + root.applyConfig(configFileView.text()) + } else { + root.applyConfig(configFileView.text()) + Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`) + } } } @@ -94,13 +110,12 @@ Singleton { path: Qt.resolvedUrl(root.filePath) watchChanges: true onFileChanged: { - console.log("[ConfigLoader] File changed, reloading...") this.reload() delayedFileRead.start() } onLoadedChanged: { const fileContent = configFileView.text() - root.applyConfig(fileContent) + delayedFileRead.start() } onLoadFailed: (error) => { if(error == FileViewError.FileNotFound) { From ee0df41deca609beb10246dd2c7b05dfec2e74ab Mon Sep 17 00:00:00 2001 From: Jx <103049321+JxJxxJxJ@users.noreply.github.com> Date: Tue, 10 Jun 2025 20:39:46 -0300 Subject: [PATCH 751/824] Add systemsettings as dependency (KDE desktop config) Otherwise the gear from the right pannel does nothing --- arch-packages/illogical-impulse-kde/PKGBUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/arch-packages/illogical-impulse-kde/PKGBUILD b/arch-packages/illogical-impulse-kde/PKGBUILD index fd3a745f6..b8b91c3f9 100644 --- a/arch-packages/illogical-impulse-kde/PKGBUILD +++ b/arch-packages/illogical-impulse-kde/PKGBUILD @@ -10,4 +10,5 @@ depends=( networkmanager plasma-nm polkit-kde-agent + systemsettings ) From ac6a64efaba8023b258dbdeada1a2d047a88ae21 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 08:25:05 +0200 Subject: [PATCH 752/824] right sidebar: fix idle inhibitor exiting on close (#1381) --- .../quickToggles/IdleInhibitor.qml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml b/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml index 4ac63d220..5080d99ad 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml @@ -3,16 +3,28 @@ import "root:/modules/common/widgets" import "../" import Quickshell.Io import Quickshell +import Quickshell.Hyprland QuickToggleButton { + id: root toggled: idleInhibitor.running buttonIcon: "coffee" onClicked: { - idleInhibitor.running = !idleInhibitor.running + if (toggled) { + root.toggled = false + Hyprland.dispatch("exec pkill wayland-idle") // pkill doesn't accept too long names + } else { + root.toggled = true + Hyprland.dispatch('exec ${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/scripts/wayland-idle-inhibitor.py') + } } Process { - id: idleInhibitor - command: ["bash", "-c", "${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/scripts/wayland-idle-inhibitor.py"] + id: fetchActiveState + running: true + command: ["bash", "-c", "pidof wayland-idle-inhibitor.py"] + onExited: (exitCode, exitStatus) => { + root.toggled = exitCode === 0 + } } StyledToolTip { content: qsTr("Keep system awake") From 039cb571675267c05bb906391c3fc3aef555fde5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 08:39:18 +0200 Subject: [PATCH 753/824] overview: partially fix window position for offset monitors (#1388) --- .../quickshell/modules/overview/OverviewWidget.qml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index e1fbb3264..1a9494938 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -156,9 +156,9 @@ Item { } } delegate: OverviewWindow { + id: window required property var modelData property var address: `0x${modelData.HyprlandToplevel.address}` - id: window windowData: windowByAddress[address] toplevel: modelData monitorData: root.monitorData @@ -166,13 +166,16 @@ Item { availableWorkspaceWidth: root.workspaceImplicitWidth availableWorkspaceHeight: root.workspaceImplicitHeight + property int monitorId: windowData?.monitor + property var monitor: HyprlandData.monitors[monitorId] + property bool atInitPosition: (initX == x && initY == y) restrictToWorkspace: Drag.active || atInitPosition property int workspaceColIndex: (windowData?.workspace.id - 1) % ConfigOptions.overview.numOfCols property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / ConfigOptions.overview.numOfCols) - xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex - yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex + xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex - (monitor?.x * root.scale) + yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex - (monitor?.y * root.scale) Timer { id: updateWindowPosition @@ -182,6 +185,7 @@ Item { onTriggered: { window.x = Math.round(Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset) window.y = Math.round(Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset) + console.log(`[OverviewWindow] Updated position for window ${windowData?.address} to (${window.x}, ${window.y})`) } } @@ -201,6 +205,7 @@ Item { window.pressed = true window.Drag.active = true window.Drag.source = window + console.log(`[OverviewWindow] Dragging window ${windowData?.address} from position (${window.x}, ${window.y})`) } onReleased: { const targetWorkspace = root.draggingTargetWorkspace From 8956752e85b3f88847c84b446e98563d05f71cdb Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 09:00:28 +0200 Subject: [PATCH 754/824] ripple button: default size based on contentitem --- .config/quickshell/modules/common/widgets/RippleButton.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.config/quickshell/modules/common/widgets/RippleButton.qml b/.config/quickshell/modules/common/widgets/RippleButton.qml index 9931cd02a..6e6e3c5fc 100644 --- a/.config/quickshell/modules/common/widgets/RippleButton.qml +++ b/.config/quickshell/modules/common/widgets/RippleButton.qml @@ -32,6 +32,9 @@ Button { property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2" property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2" + implicitWidth: contentItem.implicitWidth + horizontalPadding * 2 + implicitHeight: contentItem.implicitHeight + verticalPadding * 2 + property color buttonColor: root.enabled ? (root.toggled ? (root.hovered ? colBackgroundToggledHover : colBackgroundToggled) : From 244d3f6067e667d8ef81e4d4f69749dec85a773c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 09:01:24 +0200 Subject: [PATCH 755/824] idle inhibitor button: now wrong binding --- .../modules/sidebarRight/quickToggles/IdleInhibitor.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml b/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml index 5080d99ad..b48d3467f 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml @@ -7,7 +7,7 @@ import Quickshell.Hyprland QuickToggleButton { id: root - toggled: idleInhibitor.running + toggled: false buttonIcon: "coffee" onClicked: { if (toggled) { From 65983ade46c4a131b6534bc264969836745bf362 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:59:52 +0200 Subject: [PATCH 756/824] sidebar: translator: language selector --- .../modules/common/ConfigOptions.qml | 8 + .../modules/common/widgets/ButtonGroup.qml | 2 +- .../common/widgets/SelectionDialog.qml | 127 +++++++ .../modules/sidebarLeft/Translator.qml | 311 +++++++++--------- .../translator/LanguageSelectorButton.qml | 42 +++ .../sidebarLeft/translator/TextCanvas.qml | 92 ++++++ .../sidebarRight/volumeMixer/VolumeMixer.qml | 11 +- .config/quickshell/shell.qml | 2 +- 8 files changed, 439 insertions(+), 156 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/SelectionDialog.qml create mode 100644 .config/quickshell/modules/sidebarLeft/translator/LanguageSelectorButton.qml create mode 100644 .config/quickshell/modules/sidebarLeft/translator/TextCanvas.qml diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 4aa54fa52..902049729 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -63,6 +63,14 @@ Singleton { ] } + property QtObject language: QtObject { + property QtObject translator: QtObject { + property string engine: "auto" // Run `trans -list-engines` for available engines. auto should use google + property string sourceLanguage: "auto" + property string targetLanguage: "English" // Run `trans -list-all` for available languages + } + } + property QtObject networking: QtObject { property string userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" } diff --git a/.config/quickshell/modules/common/widgets/ButtonGroup.qml b/.config/quickshell/modules/common/widgets/ButtonGroup.qml index a1570c6af..5356535f4 100644 --- a/.config/quickshell/modules/common/widgets/ButtonGroup.qml +++ b/.config/quickshell/modules/common/widgets/ButtonGroup.qml @@ -11,7 +11,7 @@ import QtQuick.Layouts */ Rectangle { id: root - default property alias content: rowLayout.data + default property alias data: rowLayout.data property real spacing: 5 property real padding: 0 property int clickIndex: rowLayout.clickIndex diff --git a/.config/quickshell/modules/common/widgets/SelectionDialog.qml b/.config/quickshell/modules/common/widgets/SelectionDialog.qml new file mode 100644 index 000000000..1e1446b51 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/SelectionDialog.qml @@ -0,0 +1,127 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell + +Item { + id: root + property real dialogPadding: 15 + property real dialogMargin: 30 + property string titleText: "Selection Dialog" + property alias items: choiceModel.values + property int selectedId: -1 // -1 means no selection + + signal canceled(); + signal selected(var result); + + Rectangle { // Scrim + id: scrimOverlay + anchors.fill: parent + radius: Appearance.rounding.small + color: Appearance.colors.colScrim + MouseArea { + hoverEnabled: true + anchors.fill: parent + preventStealing: true + propagateComposedEvents: false + } + } + + Rectangle { // The dialog + id: dialog + color: Appearance.m3colors.m3surfaceContainerHigh + radius: Appearance.rounding.normal + anchors.fill: parent + anchors.margins: dialogMargin + implicitHeight: dialogColumnLayout.implicitHeight + + ColumnLayout { + id: dialogColumnLayout + anchors.fill: parent + spacing: 16 + + StyledText { + id: dialogTitle + Layout.topMargin: dialogPadding + Layout.leftMargin: dialogPadding + Layout.rightMargin: dialogPadding + Layout.alignment: Qt.AlignLeft + color: Appearance.m3colors.m3onSurface + font.pixelSize: Appearance.font.pixelSize.larger + text: root.titleText + } + + Rectangle { + color: Appearance.m3colors.m3outline + implicitHeight: 1 + Layout.fillWidth: true + Layout.leftMargin: dialogPadding + Layout.rightMargin: dialogPadding + } + + ListView { + id: choiceListView + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + + model: ScriptModel { + id: choiceModel + } + + delegate: StyledRadioButton { + id: radioButton + required property var modelData + required property int index + anchors { + left: parent?.left + right: parent?.right + leftMargin: root.dialogPadding + rightMargin: root.dialogPadding + } + + description: modelData.toString() + checked: index === root.selectedId + + onCheckedChanged: { + if (checked) { + root.selectedId = index; + } + } + } + } + + Rectangle { + color: Appearance.m3colors.m3outline + implicitHeight: 1 + Layout.fillWidth: true + Layout.leftMargin: dialogPadding + Layout.rightMargin: dialogPadding + } + + RowLayout { + id: dialogButtonsRowLayout + Layout.bottomMargin: dialogPadding + Layout.leftMargin: dialogPadding + Layout.rightMargin: dialogPadding + Layout.alignment: Qt.AlignRight + + DialogButton { + buttonText: qsTr("Cancel") + onClicked: root.canceled() + } + DialogButton { + buttonText: qsTr("OK") + onClicked: root.selected( + root.selectedId === -1 ? null : + root.items[root.selectedId] + ) + } + } + } + } +} diff --git a/.config/quickshell/modules/sidebarLeft/Translator.qml b/.config/quickshell/modules/sidebarLeft/Translator.qml index a853f30cf..92e0505db 100644 --- a/.config/quickshell/modules/sidebarLeft/Translator.qml +++ b/.config/quickshell/modules/sidebarLeft/Translator.qml @@ -3,6 +3,7 @@ import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" import "root:/modules/common/functions/string_utils.js" as StringUtils +import "./translator/" import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -15,11 +16,26 @@ import Quickshell.Hyprland */ Item { id: root - property var inputField: inputTextArea - property var outputField: outputTextArea - + // Widgets + property var inputField: inputCanvas.inputTextArea + // Widget variables property bool translationFor: false // Indicates if the translation is for an autocorrected text property string translatedText: "" + property list languages: [] + // Options + property string targetLanguage: ConfigOptions.language.translator.targetLanguage + property string sourceLanguage: ConfigOptions.language.translator.sourceLanguage + property string hostLanguage: targetLanguage + + property bool showLanguageSelector: false + property bool languageSelectorTarget: false // true for target language, false for source language + property string languageSelectorLanguage: "" + + function showLanguageSelectorDialog(isTargetLang: bool) { + root.showLanguageSelector = true + root.languageSelectorTarget = isTargetLang; + root.languageSelectorLanguage = isTargetLang ? root.targetLanguage : root.sourceLanguage; + } onFocusChanged: (focus) => { if (focus) { @@ -32,19 +48,23 @@ Item { interval: ConfigOptions.sidebar.translator.delay repeat: false onTriggered: () => { - if (inputTextArea.text.trim().length > 0) { + if (root.inputField.text.trim().length > 0) { + console.log("Translating with command:", translateProc.command); translateProc.running = false; translateProc.buffer = ""; // Clear the buffer translateProc.running = true; // Restart the process } else { - outputTextArea.text = ""; + root.translatedText = ""; } } } Process { id: translateProc - command: ["bash", "-c", `trans -no-theme -no-ansi '${StringUtils.shellSingleQuoteEscape(inputTextArea.text.trim())}'`] + command: ["bash", "-c", `trans -no-theme` + + ` -source '${StringUtils.shellSingleQuoteEscape(root.sourceLanguage)}'` + + ` -target '${StringUtils.shellSingleQuoteEscape(root.targetLanguage)}'` + + ` -no-ansi '${StringUtils.shellSingleQuoteEscape(root.inputField.text.trim())}'`] property string buffer: "" stdout: SplitParser { onRead: data => { @@ -54,12 +74,29 @@ Item { onExited: (exitCode, exitStatus) => { // 1. Split into sections by double newlines const sections = translateProc.buffer.trim().split(/\n\s*\n/); - // console.log("BUFFER:", translateProc.buffer); - // console.log("SECTIONS:", sections); + console.log("BUFFER:", translateProc.buffer); + console.log("SECTIONS:", sections); // 2. Extract relevant data root.translatedText = sections.length > 1 ? sections[1].trim() : ""; - root.outputField.text = root.translatedText; + } + } + + Process { + id: getLanguagesProc + command: ["trans", "-list-languages"] + property list bufferList: ["auto"] + running: true + stdout: SplitParser { + onRead: data => { + getLanguagesProc.bufferList.push(data.trim()); + } + } + onExited: (exitCode, exitStatus) => { + root.languages = getLanguagesProc.bufferList + .filter(lang => lang.trim().length > 0) // Filter out empty lines + .sort((a, b) => a.localeCompare(b)); // Sort alphabetically + getLanguagesProc.bufferList = []; // Clear the buffer } } @@ -71,159 +108,133 @@ Item { id: contentColumn anchors.fill: parent - Rectangle { // INPUT + LanguageSelectorButton { // Source language button + id: sourceLanguageButton + displayText: root.sourceLanguage + onClicked: { + root.showLanguageSelectorDialog(false); + } + } + + TextCanvas { // Content input id: inputCanvas - Layout.fillWidth: true - implicitHeight: Math.max(150, inputColumn.implicitHeight) - color: Appearance.colors.colLayer1 - radius: Appearance.rounding.normal - border.color: Appearance.m3colors.m3outlineVariant - border.width: 1 - - ColumnLayout { - id: inputColumn - anchors.fill: parent - spacing: 0 - - StyledTextArea { // Input area - id: inputTextArea - Layout.fillWidth: true - placeholderText: qsTr("Enter text to translate...") - wrapMode: TextEdit.Wrap - textFormat: TextEdit.PlainText - font.pixelSize: Appearance.font.pixelSize.small - color: Appearance.colors.colOnLayer1 - padding: 15 - background: null - onTextChanged: { - if (inputTextArea.text.trim().length > 0) { - translateTimer.restart(); - } else { - outputTextArea.text = ""; - } - } + isInput: true + placeholderText: qsTr("Enter text to translate...") + onInputTextChanged: { + translateTimer.restart(); + } + GroupButton { + id: pasteButton + baseWidth: height + buttonRadius: Appearance.rounding.small + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + iconSize: Appearance.font.pixelSize.larger + text: "content_paste" + color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext } - - Item { Layout.fillHeight: true } - - RowLayout { // Status row - Layout.fillWidth: true - Layout.margins: 10 - spacing: 10 - - Text { - Layout.leftMargin: 10 - text: qsTr("%1 characters").arg(inputTextArea.text.length) - color: Appearance.colors.colOnLayer1 - font.pixelSize: Appearance.font.pixelSize.smaller - } - Item { Layout.fillWidth: true } - ButtonGroup { - GroupButton { - id: pasteButton - baseWidth: height - buttonRadius: Appearance.rounding.small - contentItem: MaterialSymbol { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - iconSize: Appearance.font.pixelSize.larger - text: "content_paste" - color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext - } - onClicked: { - root.inputField.text = Quickshell.clipboardText - } - } - GroupButton { - id: deleteButton - baseWidth: height - buttonRadius: Appearance.rounding.small - enabled: inputTextArea.text.length > 0 - contentItem: MaterialSymbol { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - iconSize: Appearance.font.pixelSize.larger - text: "close" - color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext - } - onClicked: { - root.inputField.text = "" - } - } - } + onClicked: { + root.inputField.text = Quickshell.clipboardText + } + } + GroupButton { + id: deleteButton + baseWidth: height + buttonRadius: Appearance.rounding.small + enabled: inputCanvas.inputTextArea.text.length > 0 + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + iconSize: Appearance.font.pixelSize.larger + text: "close" + color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext + } + onClicked: { + root.inputField.text = "" } } } - Rectangle { // OUTPUT + LanguageSelectorButton { // Target language button + id: targetLanguageButton + displayText: root.targetLanguage + onClicked: { + root.showLanguageSelectorDialog(true); + } + } + + TextCanvas { // Content translation id: outputCanvas - Layout.fillWidth: true - implicitHeight: Math.max(150, outputColumn.implicitHeight) - color: Appearance.m3colors.m3surfaceContainer - radius: Appearance.rounding.normal - - ColumnLayout { // Output column - id: outputColumn - anchors.fill: parent - spacing: 0 - - StyledText { // Output area - id: outputTextArea - Layout.fillWidth: true - property bool hasTranslation: (root.translatedText.trim().length > 0) - wrapMode: TextEdit.Wrap - font.pixelSize: Appearance.font.pixelSize.small - color: hasTranslation ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext - padding: 15 - text: hasTranslation ? root.translatedText : "" + isInput: false + placeholderText: qsTr("Translation goes here...") + property bool hasTranslation: (root.translatedText.trim().length > 0) + text: hasTranslation ? root.translatedText : "" + GroupButton { + id: copyButton + baseWidth: height + buttonRadius: Appearance.rounding.small + enabled: outputCanvas.displayedText.trim().length > 0 + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + iconSize: Appearance.font.pixelSize.larger + text: "content_copy" + color: copyButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext } - Item { Layout.fillHeight: true } - RowLayout { // Status row - Layout.fillWidth: true - Layout.margins: 10 - spacing: 10 - Item { Layout.fillWidth: true } - ButtonGroup { - GroupButton { - id: copyButton - baseWidth: height - buttonRadius: Appearance.rounding.small - enabled: root.outputField.text.trim().length > 0 - contentItem: MaterialSymbol { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - iconSize: Appearance.font.pixelSize.larger - text: "content_copy" - color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext - } - onClicked: { - Quickshell.clipboardText = root.outputField.text - } - } - GroupButton { - id: searchButton - baseWidth: height - buttonRadius: Appearance.rounding.small - enabled: root.outputField.text.trim().length > 0 - contentItem: MaterialSymbol { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - iconSize: Appearance.font.pixelSize.larger - text: "travel_explore" - color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext - } - onClicked: { - let url = ConfigOptions.search.engineBaseUrl + root.outputField.text; - for (let site of ConfigOptions.search.excludedSites) { - url += ` -site:${site}`; - } - Qt.openUrlExternally(url); - } - } + onClicked: { + Quickshell.clipboardText = outputCanvas.displayedText + } + } + GroupButton { + id: searchButton + baseWidth: height + buttonRadius: Appearance.rounding.small + enabled: outputCanvas.displayedText.trim().length > 0 + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + iconSize: Appearance.font.pixelSize.larger + text: "travel_explore" + color: searchButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext + } + onClicked: { + let url = ConfigOptions.search.engineBaseUrl + outputCanvas.displayedText; + for (let site of ConfigOptions.search.excludedSites) { + url += ` -site:${site}`; } + Qt.openUrlExternally(url); } } } + } } + + Loader { + anchors.fill: parent + active: root.showLanguageSelector + visible: root.showLanguageSelector + z: 9999 + sourceComponent: SelectionDialog { + id: languageSelectorDialog + titleText: qsTr("Select Language") + items: root.languages + onCanceled: () => { + root.showLanguageSelector = false; + } + onSelected: (result) => { + root.showLanguageSelector = false; + if (!result || result.length === 0) return; // No selection made + + if (root.languageSelectorTarget) { + root.targetLanguage = result; + ConfigOptions.language.translator.targetLanguage = result; // Save to config + } else { + root.sourceLanguage = result; + ConfigOptions.language.translator.sourceLanguage = result; // Save to config + } + } + } + } } diff --git a/.config/quickshell/modules/sidebarLeft/translator/LanguageSelectorButton.qml b/.config/quickshell/modules/sidebarLeft/translator/LanguageSelectorButton.qml new file mode 100644 index 000000000..a78559517 --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/translator/LanguageSelectorButton.qml @@ -0,0 +1,42 @@ +import "root:/" +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/string_utils.js" as StringUtils +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland + +RippleButton { + id: root + property string displayText: "" + colBackground: Appearance.colors.colLayer2 + + contentItem: Item { + anchors.centerIn: parent + implicitWidth: languageRow.implicitWidth + implicitHeight: languageText.implicitHeight + RowLayout { + id: languageRow + anchors.centerIn: parent + spacing: 0 + StyledText { + id: languageText + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: 5 + text: root.displayText + color: Appearance.colors.colOnLayer2 + font.pixelSize: Appearance.font.pixelSize.small + } + MaterialSymbol { + Layout.alignment: Qt.AlignVCenter + iconSize: Appearance.font.pixelSize.hugeass + text: "arrow_drop_down" + color: Appearance.colors.colOnLayer2 + } + } + } +} diff --git a/.config/quickshell/modules/sidebarLeft/translator/TextCanvas.qml b/.config/quickshell/modules/sidebarLeft/translator/TextCanvas.qml new file mode 100644 index 000000000..7f32a63d9 --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/translator/TextCanvas.qml @@ -0,0 +1,92 @@ +import "root:/" +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/string_utils.js" as StringUtils +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland + +Rectangle { + id: root + property bool isInput: true // true for input, false for output + property string placeholderText + property string text: "" + property var inputTextArea: isInput ? inputLoader.item : undefined + readonly property string displayedText: isInput ? inputLoader.item.text : + root.text.length > 0 ? outputLoader.item.text : "" + default property alias actionButtons: actions.data + Layout.fillWidth: true + implicitHeight: Math.max(150, inputColumn.implicitHeight) + color: isInput ? Appearance.colors.colLayer1 : Appearance.m3colors.m3surfaceContainer + radius: Appearance.rounding.normal + border.color: isInput ? Appearance.m3colors.m3outlineVariant : "transparent" + border.width: isInput ? 1 : 0 + + signal inputTextChanged(); // Signal emitted when text changes + + ColumnLayout { + id: inputColumn + anchors.fill: parent + spacing: 0 + + Loader { + id: inputLoader + active: root.isInput + visible: root.isInput + Layout.fillWidth: true + sourceComponent: StyledTextArea { // Input area + id: inputTextArea + placeholderText: root.placeholderText + wrapMode: TextEdit.Wrap + textFormat: TextEdit.PlainText + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.colors.colOnLayer1 + padding: 15 + background: null + onTextChanged: root.inputTextChanged() + } + } + + Loader { + id: outputLoader + active: !root.isInput + visible: !root.isInput + Layout.fillWidth: true + sourceComponent: StyledText { // Output area + id: outputTextArea + padding: 15 + wrapMode: Text.Wrap + font.pixelSize: Appearance.font.pixelSize.small + color: root.text.length > 0 ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext + text: root.text.length > 0 ? root.text : root.placeholderText + } + } + + Item { Layout.fillHeight: true } + + RowLayout { // Status row + Layout.fillWidth: true + Layout.margins: 10 + spacing: 10 + + Loader { + active: root.isInput + visible: root.isInput + Layout.leftMargin: 10 + sourceComponent: Text { + text: qsTr("%1 characters").arg(inputLoader.item.text.length) + color: Appearance.colors.colOnLayer1 + font.pixelSize: Appearance.font.pixelSize.smaller + } + } + Item { Layout.fillWidth: true } + ButtonGroup { + id: actions + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml index a7a88a403..1799df556 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml @@ -5,6 +5,7 @@ import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Quickshell import Quickshell.Widgets import Quickshell.Services.Pipewire @@ -16,7 +17,7 @@ Item { property int dialogMargins: 16 property PwNode selectedDevice - function showDeviceSelectorDialog(input) { + function showDeviceSelectorDialog(input: bool) { root.selectedDevice = null root.showDeviceSelector = true root.deviceSelectorInput = input @@ -207,9 +208,11 @@ Item { spacing: 0 Repeater { - model: Pipewire.nodes.values.filter(node => { - return !node.isStream && node.isSink !== root.deviceSelectorInput && node.audio - }) + model: ScriptModel { + values: Pipewire.nodes.values.filter(node => { + return !node.isStream && node.isSink !== root.deviceSelectorInput && node.audio + }) + } // This could and should be refractored, but all data becomes null when passed wtf delegate: StyledRadioButton { diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 4acb7cccb..35a5008ea 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -29,7 +29,7 @@ ShellRoot { property bool enableBar: true property bool enableBackgroundWidgets: true property bool enableCheatsheet: true - property bool enableDock: true + property bool enableDock: false property bool enableMediaControls: true property bool enableNotificationPopup: true property bool enableOnScreenDisplayBrightness: true From 0fb28af3c7633ac35b502bee0094e6c941e78a5c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 11:06:22 +0200 Subject: [PATCH 757/824] sidebar: translator: change layout to reduce eye movement --- .../modules/sidebarLeft/Translator.qml | 204 +++++++++--------- 1 file changed, 105 insertions(+), 99 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/Translator.qml b/.config/quickshell/modules/sidebarLeft/Translator.qml index 92e0505db..aacc35868 100644 --- a/.config/quickshell/modules/sidebarLeft/Translator.qml +++ b/.config/quickshell/modules/sidebarLeft/Translator.qml @@ -99,116 +99,120 @@ Item { getLanguagesProc.bufferList = []; // Clear the buffer } } - - Flickable { + + ColumnLayout { anchors.fill: parent - contentHeight: contentColumn.implicitHeight + Flickable { + Layout.fillWidth: true + Layout.fillHeight: true + contentHeight: contentColumn.implicitHeight - ColumnLayout { - id: contentColumn - anchors.fill: parent + ColumnLayout { + id: contentColumn + anchors.fill: parent - LanguageSelectorButton { // Source language button - id: sourceLanguageButton - displayText: root.sourceLanguage - onClicked: { - root.showLanguageSelectorDialog(false); - } - } - - TextCanvas { // Content input - id: inputCanvas - isInput: true - placeholderText: qsTr("Enter text to translate...") - onInputTextChanged: { - translateTimer.restart(); - } - GroupButton { - id: pasteButton - baseWidth: height - buttonRadius: Appearance.rounding.small - contentItem: MaterialSymbol { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - iconSize: Appearance.font.pixelSize.larger - text: "content_paste" - color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext - } + LanguageSelectorButton { // Target language button + id: targetLanguageButton + displayText: root.targetLanguage onClicked: { - root.inputField.text = Quickshell.clipboardText + root.showLanguageSelectorDialog(true); } } - GroupButton { - id: deleteButton - baseWidth: height - buttonRadius: Appearance.rounding.small - enabled: inputCanvas.inputTextArea.text.length > 0 - contentItem: MaterialSymbol { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - iconSize: Appearance.font.pixelSize.larger - text: "close" - color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext - } - onClicked: { - root.inputField.text = "" - } - } - } - LanguageSelectorButton { // Target language button - id: targetLanguageButton - displayText: root.targetLanguage - onClicked: { - root.showLanguageSelectorDialog(true); - } - } - - TextCanvas { // Content translation - id: outputCanvas - isInput: false - placeholderText: qsTr("Translation goes here...") - property bool hasTranslation: (root.translatedText.trim().length > 0) - text: hasTranslation ? root.translatedText : "" - GroupButton { - id: copyButton - baseWidth: height - buttonRadius: Appearance.rounding.small - enabled: outputCanvas.displayedText.trim().length > 0 - contentItem: MaterialSymbol { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - iconSize: Appearance.font.pixelSize.larger - text: "content_copy" - color: copyButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext - } - onClicked: { - Quickshell.clipboardText = outputCanvas.displayedText - } - } - GroupButton { - id: searchButton - baseWidth: height - buttonRadius: Appearance.rounding.small - enabled: outputCanvas.displayedText.trim().length > 0 - contentItem: MaterialSymbol { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - iconSize: Appearance.font.pixelSize.larger - text: "travel_explore" - color: searchButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext - } - onClicked: { - let url = ConfigOptions.search.engineBaseUrl + outputCanvas.displayedText; - for (let site of ConfigOptions.search.excludedSites) { - url += ` -site:${site}`; + TextCanvas { // Content translation + id: outputCanvas + isInput: false + placeholderText: qsTr("Translation goes here...") + property bool hasTranslation: (root.translatedText.trim().length > 0) + text: hasTranslation ? root.translatedText : "" + GroupButton { + id: copyButton + baseWidth: height + buttonRadius: Appearance.rounding.small + enabled: outputCanvas.displayedText.trim().length > 0 + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + iconSize: Appearance.font.pixelSize.larger + text: "content_copy" + color: copyButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext + } + onClicked: { + Quickshell.clipboardText = outputCanvas.displayedText + } + } + GroupButton { + id: searchButton + baseWidth: height + buttonRadius: Appearance.rounding.small + enabled: outputCanvas.displayedText.trim().length > 0 + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + iconSize: Appearance.font.pixelSize.larger + text: "travel_explore" + color: searchButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext + } + onClicked: { + let url = ConfigOptions.search.engineBaseUrl + outputCanvas.displayedText; + for (let site of ConfigOptions.search.excludedSites) { + url += ` -site:${site}`; + } + Qt.openUrlExternally(url); } - Qt.openUrlExternally(url); } } - } - } + } + } + + LanguageSelectorButton { // Source language button + id: sourceLanguageButton + displayText: root.sourceLanguage + onClicked: { + root.showLanguageSelectorDialog(false); + } + } + + TextCanvas { // Content input + id: inputCanvas + isInput: true + placeholderText: qsTr("Enter text to translate...") + onInputTextChanged: { + translateTimer.restart(); + } + GroupButton { + id: pasteButton + baseWidth: height + buttonRadius: Appearance.rounding.small + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + iconSize: Appearance.font.pixelSize.larger + text: "content_paste" + color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext + } + onClicked: { + root.inputField.text = Quickshell.clipboardText + } + } + GroupButton { + id: deleteButton + baseWidth: height + buttonRadius: Appearance.rounding.small + enabled: inputCanvas.inputTextArea.text.length > 0 + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + iconSize: Appearance.font.pixelSize.larger + text: "close" + color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext + } + onClicked: { + root.inputField.text = "" + } + } + } } Loader { @@ -234,6 +238,8 @@ Item { root.sourceLanguage = result; ConfigOptions.language.translator.sourceLanguage = result; // Save to config } + + translateTimer.restart(); // Restart translation after language change } } } From 23f0e90a7bd447c05e174ed19c8d184624bd03ce Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 11:36:56 +0200 Subject: [PATCH 758/824] sidebar: translator: save selected language, cleaner selector --- .../modules/common/ConfigOptions.qml | 2 +- .../common/widgets/SelectionDialog.qml | 8 ++++--- .../modules/sidebarLeft/Translator.qml | 18 ++++++++------- .config/quickshell/services/ConfigLoader.qml | 22 +++++++++++++++++-- 4 files changed, 36 insertions(+), 14 deletions(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 902049729..470f438df 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -66,8 +66,8 @@ Singleton { property QtObject language: QtObject { property QtObject translator: QtObject { property string engine: "auto" // Run `trans -list-engines` for available engines. auto should use google + property string targetLanguage: "auto" // Run `trans -list-all` for available languages property string sourceLanguage: "auto" - property string targetLanguage: "English" // Run `trans -list-all` for available languages } } diff --git a/.config/quickshell/modules/common/widgets/SelectionDialog.qml b/.config/quickshell/modules/common/widgets/SelectionDialog.qml index 1e1446b51..affd13bc9 100644 --- a/.config/quickshell/modules/common/widgets/SelectionDialog.qml +++ b/.config/quickshell/modules/common/widgets/SelectionDialog.qml @@ -13,7 +13,8 @@ Item { property real dialogMargin: 30 property string titleText: "Selection Dialog" property alias items: choiceModel.values - property int selectedId: -1 // -1 means no selection + property int selectedId: choiceListView.currentIndex + property var defaultChoice signal canceled(); signal selected(var result); @@ -68,6 +69,7 @@ Item { Layout.fillWidth: true Layout.fillHeight: true clip: true + currentIndex: root.defaultChoice !== undefined ? root.items.indexOf(root.defaultChoice) : -1 model: ScriptModel { id: choiceModel @@ -85,11 +87,11 @@ Item { } description: modelData.toString() - checked: index === root.selectedId + checked: index === choiceListView.currentIndex onCheckedChanged: { if (checked) { - root.selectedId = index; + choiceListView.currentIndex = index; } } } diff --git a/.config/quickshell/modules/sidebarLeft/Translator.qml b/.config/quickshell/modules/sidebarLeft/Translator.qml index aacc35868..8f93b50d7 100644 --- a/.config/quickshell/modules/sidebarLeft/Translator.qml +++ b/.config/quickshell/modules/sidebarLeft/Translator.qml @@ -29,12 +29,10 @@ Item { property bool showLanguageSelector: false property bool languageSelectorTarget: false // true for target language, false for source language - property string languageSelectorLanguage: "" function showLanguageSelectorDialog(isTargetLang: bool) { - root.showLanguageSelector = true root.languageSelectorTarget = isTargetLang; - root.languageSelectorLanguage = isTargetLang ? root.targetLanguage : root.sourceLanguage; + root.showLanguageSelector = true } onFocusChanged: (focus) => { @@ -93,9 +91,12 @@ Item { } } onExited: (exitCode, exitStatus) => { - root.languages = getLanguagesProc.bufferList - .filter(lang => lang.trim().length > 0) // Filter out empty lines - .sort((a, b) => a.localeCompare(b)); // Sort alphabetically + // Ensure "auto" is always the first language + let langs = getLanguagesProc.bufferList + .filter(lang => lang.trim().length > 0 && lang !== "auto") + .sort((a, b) => a.localeCompare(b)); + langs.unshift("auto"); + root.languages = langs; getLanguagesProc.bufferList = []; // Clear the buffer } } @@ -224,6 +225,7 @@ Item { id: languageSelectorDialog titleText: qsTr("Select Language") items: root.languages + defaultChoice: root.languageSelectorTarget ? root.targetLanguage : root.sourceLanguage onCanceled: () => { root.showLanguageSelector = false; } @@ -233,10 +235,10 @@ Item { if (root.languageSelectorTarget) { root.targetLanguage = result; - ConfigOptions.language.translator.targetLanguage = result; // Save to config + ConfigLoader.setConfigValueAndSave("language.translator.targetLanguage", result); // Save to config } else { root.sourceLanguage = result; - ConfigOptions.language.translator.sourceLanguage = result; // Save to config + ConfigLoader.setConfigValueAndSave("language.translator.sourceLanguage", result); // Save to config } translateTimer.restart(); // Restart translation after language change diff --git a/.config/quickshell/services/ConfigLoader.qml b/.config/quickshell/services/ConfigLoader.qml index 61aa9b4ed..688bb47bb 100644 --- a/.config/quickshell/services/ConfigLoader.qml +++ b/.config/quickshell/services/ConfigLoader.qml @@ -14,13 +14,18 @@ import Qt.labs.platform /** * Loads and manages the shell configuration file. * The config file is by default at XDG_CONFIG_HOME/illogical-impulse/config.json. - * Automatically reloaded when the file changes, but does not provide a way to save changes. + * Automatically reloaded when the file changes. */ Singleton { id: root property string filePath: Directories.shellConfigPath property bool firstLoad: true property bool preventNextLoad: false + property var preventNextNotification: false + + onPreventNextNotificationChanged: { + console.log("HMM: preventNextNotification:", root.preventNextNotification); + } function loadConfig() { configFileView.reload() @@ -87,11 +92,19 @@ Singleton { Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(JSON.stringify(plainConfig, null, 2))}' > '${root.filePath}'`) } + function setConfigValueAndSave(nestedKey, value, preventNextNotification = true) { + setLiveConfigValue(nestedKey, value); + root.preventNextNotification = preventNextNotification; + console.log("SETTING: preventNextNotification:", root.preventNextNotification); + saveConfig(); + } + Timer { id: delayedFileRead interval: ConfigOptions.hacks.arbitraryRaceConditionDelay running: false onTriggered: { + console.log("GONNA APPLY KONFIG preventNextNotification:", root.preventNextNotification); if (root.preventNextLoad) { root.preventNextLoad = false; return; @@ -99,8 +112,13 @@ Singleton { if (root.firstLoad) { root.applyConfig(configFileView.text()) } else { + console.log("APPLYING: preventNextNotification:", root.preventNextNotification); root.applyConfig(configFileView.text()) - Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`) + if (!root.preventNextNotification) { + Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`) + } else { + // root.preventNextNotification = false; + } } } } From 447312959937ccb87dae37cca58cb6a0b9469b00 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 11:52:24 +0200 Subject: [PATCH 759/824] fix session screen button size --- .config/quickshell/modules/common/widgets/RippleButton.qml | 3 --- .../modules/sidebarLeft/translator/LanguageSelectorButton.qml | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/RippleButton.qml b/.config/quickshell/modules/common/widgets/RippleButton.qml index 6e6e3c5fc..9931cd02a 100644 --- a/.config/quickshell/modules/common/widgets/RippleButton.qml +++ b/.config/quickshell/modules/common/widgets/RippleButton.qml @@ -32,9 +32,6 @@ Button { property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2" property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2" - implicitWidth: contentItem.implicitWidth + horizontalPadding * 2 - implicitHeight: contentItem.implicitHeight + verticalPadding * 2 - property color buttonColor: root.enabled ? (root.toggled ? (root.hovered ? colBackgroundToggledHover : colBackgroundToggled) : diff --git a/.config/quickshell/modules/sidebarLeft/translator/LanguageSelectorButton.qml b/.config/quickshell/modules/sidebarLeft/translator/LanguageSelectorButton.qml index a78559517..37df25f83 100644 --- a/.config/quickshell/modules/sidebarLeft/translator/LanguageSelectorButton.qml +++ b/.config/quickshell/modules/sidebarLeft/translator/LanguageSelectorButton.qml @@ -15,6 +15,9 @@ RippleButton { property string displayText: "" colBackground: Appearance.colors.colLayer2 + implicitWidth: contentItem.implicitWidth + horizontalPadding * 2 + implicitHeight: contentItem.implicitHeight + verticalPadding * 2 + contentItem: Item { anchors.centerIn: parent implicitWidth: languageRow.implicitWidth From a3818322a675d57a0940b1bc3b64cdc07c8cec63 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 22:30:43 +0200 Subject: [PATCH 760/824] media controls: visualizer --- .../modules/mediaControls/MediaControls.qml | 23 ++++- .../modules/mediaControls/PlayerControl.qml | 83 +++++++++++++++++-- .../scripts/cava/raw_output_config.txt | 14 ++++ .../illogical-impulse-audio/PKGBUILD | 1 + 4 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 .config/quickshell/scripts/cava/raw_output_config.txt diff --git a/.config/quickshell/modules/mediaControls/MediaControls.qml b/.config/quickshell/modules/mediaControls/MediaControls.qml index 81b1d62d9..0a85bde0a 100644 --- a/.config/quickshell/modules/mediaControls/MediaControls.qml +++ b/.config/quickshell/modules/mediaControls/MediaControls.qml @@ -26,6 +26,7 @@ Scope { property real contentPadding: 13 property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 property real artRounding: Appearance.rounding.verysmall + property list visualizerPoints: [] property bool hasPlasmaIntegration: false function isRealPlayer(player) { @@ -68,13 +69,32 @@ Scope { return filtered; } + Process { + id: cavaProc + running: mediaControlsLoader.active + onRunningChanged: { + if (!cavaProc.running) { + root.visualizerPoints = []; + } + } + command: ["cava", "-p", `${FileUtils.trimFileProtocol(Directories.config)}/quickshell/scripts/cava/raw_output_config.txt`] + stdout: SplitParser { + onRead: data => { + // Parse `;`-separated values into the visualizerPoints array + let allPoints = data.split(";").map(p => parseFloat(p.trim())).filter(p => !isNaN(p)); + let points = allPoints.slice(Math.floor(allPoints.length / 2), allPoints.length); + root.visualizerPoints = points; + } + } + } + Loader { id: mediaControlsLoader active: false sourceComponent: PanelWindow { id: mediaControlsRoot - visible: mediaControlsLoader.active + visible: true exclusiveZone: 0 implicitWidth: ( @@ -112,6 +132,7 @@ Scope { delegate: PlayerControl { required property MprisPlayer modelData player: modelData + visualizerPoints: root.visualizerPoints } } } diff --git a/.config/quickshell/modules/mediaControls/PlayerControl.qml b/.config/quickshell/modules/mediaControls/PlayerControl.qml index 16be41b83..842b681fd 100644 --- a/.config/quickshell/modules/mediaControls/PlayerControl.qml +++ b/.config/quickshell/modules/mediaControls/PlayerControl.qml @@ -25,6 +25,8 @@ Item { // Player instance property string artFilePath: `${artDownloadLocation}/${artFileName}` property color artDominantColor: colorQuantizer?.colors[0] || Appearance.m3colors.m3secondaryContainer property bool downloaded: false + property list visualizerPoints: [] + property real maxVisualizerValue: 1000 // Max value in the data points implicitWidth: widgetWidth implicitHeight: widgetHeight @@ -150,6 +152,69 @@ Item { // Player instance } } + Canvas { // Visualizer + id: visualizerCanvas + anchors.fill: parent + onPaint: { + var ctx = getContext("2d"); + ctx.clearRect(0, 0, width, height); + + var points = playerController.visualizerPoints; + var maxVal = playerController.maxVisualizerValue || 1; + var h = height; + var w = width; + var n = points.length; + if (n < 2) return; + + // Smoothing: simple moving average (optional) + var smoothPoints = []; + var smoothWindow = 3; // adjust for more/less smoothing + for (var i = 0; i < n; ++i) { + var sum = 0, count = 0; + for (var j = -smoothWindow; j <= smoothWindow; ++j) { + var idx = Math.max(0, Math.min(n - 1, i + j)); + sum += points[idx]; + count++; + } + smoothPoints.push(sum / count); + } + + ctx.beginPath(); + ctx.moveTo(0, h); + for (var i = 0; i < n; ++i) { + var x = i * w / (n - 1); + var y = h - (smoothPoints[i] / maxVal) * h; + ctx.lineTo(x, y); + } + ctx.lineTo(w, h); + ctx.closePath(); + + ctx.fillStyle = Qt.rgba( + blendedColors.colPrimary.r, + blendedColors.colPrimary.g, + blendedColors.colPrimary.b, + 0.25 + ); + ctx.fill(); + } + Connections { + target: playerController + function onVisualizerPointsChanged() { + visualizerCanvas.requestPaint() + } + } + + layer.enabled: true + layer.effect: MultiEffect { // Blur a tiny bit to obscure away the points + source: visualizerCanvas + saturation: 0.2 + blurEnabled: true + blurMax: 6 + blur: 1 + } + } + + RowLayout { anchors.fill: parent anchors.margins: root.contentPadding @@ -160,7 +225,7 @@ Item { // Player instance Layout.fillHeight: true implicitWidth: height radius: root.artRounding - color: blendedColors.colLayer1 + color: ColorUtils.transparentize(blendedColors.colLayer1, 0.5) layer.enabled: true layer.effect: OpacityMask { @@ -235,12 +300,18 @@ Item { // Player instance iconName: "skip_previous" onClicked: playerController.player?.previous() } - StyledProgressBar { - id: slider + Item { + id: progressBarContainer Layout.fillWidth: true - highlightColor: blendedColors.colPrimary - trackColor: blendedColors.colSecondaryContainer - value: playerController.player?.position / playerController.player?.length + implicitHeight: progressBar.implicitHeight + + StyledProgressBar { + id: progressBar + anchors.fill: parent + highlightColor: blendedColors.colPrimary + trackColor: blendedColors.colSecondaryContainer + value: playerController.player?.position / playerController.player?.length + } } TrackChangeButton { iconName: "skip_next" diff --git a/.config/quickshell/scripts/cava/raw_output_config.txt b/.config/quickshell/scripts/cava/raw_output_config.txt new file mode 100644 index 000000000..a223a31cb --- /dev/null +++ b/.config/quickshell/scripts/cava/raw_output_config.txt @@ -0,0 +1,14 @@ +[general] +mode = waves +framerate = 60 +autosens = 1 +bars = 60 + +[output] +method = raw +raw_target = /dev/stdout +data_format = ascii + +[smoothing] +noise_reduction = 20 + diff --git a/arch-packages/illogical-impulse-audio/PKGBUILD b/arch-packages/illogical-impulse-audio/PKGBUILD index 81054e0b7..6cab7de53 100644 --- a/arch-packages/illogical-impulse-audio/PKGBUILD +++ b/arch-packages/illogical-impulse-audio/PKGBUILD @@ -5,6 +5,7 @@ pkgdesc='Illogical Impulse Audio Dependencies' arch=(any) license=(None) depends=( + cava pavucontrol-qt wireplumber libdbusmenu-gtk3 From c2c7078957a93dc4e6919cb93917d0568edd4584 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 22:30:55 +0200 Subject: [PATCH 761/824] translator: remove debug print --- .../quickshell/modules/sidebarLeft/Translator.qml | 6 +++--- .config/quickshell/services/ConfigLoader.qml | 14 ++------------ 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/.config/quickshell/modules/sidebarLeft/Translator.qml b/.config/quickshell/modules/sidebarLeft/Translator.qml index 8f93b50d7..999fe3e44 100644 --- a/.config/quickshell/modules/sidebarLeft/Translator.qml +++ b/.config/quickshell/modules/sidebarLeft/Translator.qml @@ -47,7 +47,7 @@ Item { repeat: false onTriggered: () => { if (root.inputField.text.trim().length > 0) { - console.log("Translating with command:", translateProc.command); + // console.log("Translating with command:", translateProc.command); translateProc.running = false; translateProc.buffer = ""; // Clear the buffer translateProc.running = true; // Restart the process @@ -72,8 +72,8 @@ Item { onExited: (exitCode, exitStatus) => { // 1. Split into sections by double newlines const sections = translateProc.buffer.trim().split(/\n\s*\n/); - console.log("BUFFER:", translateProc.buffer); - console.log("SECTIONS:", sections); + // console.log("BUFFER:", translateProc.buffer); + // console.log("SECTIONS:", sections); // 2. Extract relevant data root.translatedText = sections.length > 1 ? sections[1].trim() : ""; diff --git a/.config/quickshell/services/ConfigLoader.qml b/.config/quickshell/services/ConfigLoader.qml index 688bb47bb..347e8f400 100644 --- a/.config/quickshell/services/ConfigLoader.qml +++ b/.config/quickshell/services/ConfigLoader.qml @@ -23,16 +23,11 @@ Singleton { property bool preventNextLoad: false property var preventNextNotification: false - onPreventNextNotificationChanged: { - console.log("HMM: preventNextNotification:", root.preventNextNotification); - } - function loadConfig() { configFileView.reload() } function applyConfig(fileContent) { - console.log("[ConfigLoader] Applying config from file:", root.filePath); try { if (fileContent.trim() === "") { console.warn("[ConfigLoader] Config file is empty, skipping load."); @@ -82,8 +77,6 @@ Singleton { } } - console.log(parents.join(".")); - console.log(`[ConfigLoader] Setting live config value: ${nestedKey} = ${convertedValue}`); obj[keys[keys.length - 1]] = convertedValue; } @@ -95,7 +88,6 @@ Singleton { function setConfigValueAndSave(nestedKey, value, preventNextNotification = true) { setLiveConfigValue(nestedKey, value); root.preventNextNotification = preventNextNotification; - console.log("SETTING: preventNextNotification:", root.preventNextNotification); saveConfig(); } @@ -104,7 +96,6 @@ Singleton { interval: ConfigOptions.hacks.arbitraryRaceConditionDelay running: false onTriggered: { - console.log("GONNA APPLY KONFIG preventNextNotification:", root.preventNextNotification); if (root.preventNextLoad) { root.preventNextLoad = false; return; @@ -112,12 +103,11 @@ Singleton { if (root.firstLoad) { root.applyConfig(configFileView.text()) } else { - console.log("APPLYING: preventNextNotification:", root.preventNextNotification); root.applyConfig(configFileView.text()) if (!root.preventNextNotification) { - Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`) + // Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`) } else { - // root.preventNextNotification = false; + root.preventNextNotification = false; } } } From 5a867ea0616a2ba254844458c8f443528454be88 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 22:59:16 +0200 Subject: [PATCH 762/824] media controls: improve dupe entry filtering, correct cava config --- .../modules/mediaControls/MediaControls.qml | 5 ++--- .../modules/mediaControls/PlayerControl.qml | 11 ++++++----- .config/quickshell/scripts/cava/raw_output_config.txt | 5 ++++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.config/quickshell/modules/mediaControls/MediaControls.qml b/.config/quickshell/modules/mediaControls/MediaControls.qml index 0a85bde0a..4350658f6 100644 --- a/.config/quickshell/modules/mediaControls/MediaControls.qml +++ b/.config/quickshell/modules/mediaControls/MediaControls.qml @@ -54,7 +54,7 @@ Scope { for (let j = i + 1; j < players.length; ++j) { let p2 = players[j]; if (p1.trackTitle && p2.trackTitle && - (p1.trackTitle.startsWith(p2.trackTitle) || p2.trackTitle.startsWith(p1.trackTitle))) { + (p1.trackTitle.includes(p2.trackTitle) || p2.trackTitle.includes(p1.trackTitle))) { group.push(j); } } @@ -81,8 +81,7 @@ Scope { stdout: SplitParser { onRead: data => { // Parse `;`-separated values into the visualizerPoints array - let allPoints = data.split(";").map(p => parseFloat(p.trim())).filter(p => !isNaN(p)); - let points = allPoints.slice(Math.floor(allPoints.length / 2), allPoints.length); + let points = data.split(";").map(p => parseFloat(p.trim())).filter(p => !isNaN(p)); root.visualizerPoints = points; } } diff --git a/.config/quickshell/modules/mediaControls/PlayerControl.qml b/.config/quickshell/modules/mediaControls/PlayerControl.qml index 842b681fd..fb4799e17 100644 --- a/.config/quickshell/modules/mediaControls/PlayerControl.qml +++ b/.config/quickshell/modules/mediaControls/PlayerControl.qml @@ -27,6 +27,7 @@ Item { // Player instance property bool downloaded: false property list visualizerPoints: [] property real maxVisualizerValue: 1000 // Max value in the data points + property int visualizerSmoothing: 2 // Number of points to average for smoothing implicitWidth: widgetWidth implicitHeight: widgetHeight @@ -168,7 +169,7 @@ Item { // Player instance // Smoothing: simple moving average (optional) var smoothPoints = []; - var smoothWindow = 3; // adjust for more/less smoothing + var smoothWindow = playerController.visualizerSmoothing; // adjust for more/less smoothing for (var i = 0; i < n; ++i) { var sum = 0, count = 0; for (var j = -smoothWindow; j <= smoothWindow; ++j) { @@ -178,6 +179,7 @@ Item { // Player instance } smoothPoints.push(sum / count); } + if (!playerController.player?.isPlaying) smoothedPoints.fill(0); // If not playing, show no points ctx.beginPath(); ctx.moveTo(0, h); @@ -193,7 +195,7 @@ Item { // Player instance blendedColors.colPrimary.r, blendedColors.colPrimary.g, blendedColors.colPrimary.b, - 0.25 + 0.15 ); ctx.fill(); } @@ -205,15 +207,14 @@ Item { // Player instance } layer.enabled: true - layer.effect: MultiEffect { // Blur a tiny bit to obscure away the points + layer.effect: MultiEffect { // Blur a bit to obscure away the points source: visualizerCanvas saturation: 0.2 blurEnabled: true - blurMax: 6 + blurMax: 7 blur: 1 } } - RowLayout { anchors.fill: parent diff --git a/.config/quickshell/scripts/cava/raw_output_config.txt b/.config/quickshell/scripts/cava/raw_output_config.txt index a223a31cb..7760e4ea2 100644 --- a/.config/quickshell/scripts/cava/raw_output_config.txt +++ b/.config/quickshell/scripts/cava/raw_output_config.txt @@ -2,13 +2,16 @@ mode = waves framerate = 60 autosens = 1 -bars = 60 +bars = 50 [output] method = raw raw_target = /dev/stdout data_format = ascii +channels = mono +mono_option = average [smoothing] noise_reduction = 20 + From 24374efdce1850d8695d4eeb893577f949d0d71c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 23:16:11 +0200 Subject: [PATCH 763/824] bar: change music icon --- .config/quickshell/modules/bar/Media.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml index f4b8606b9..c59b6379d 100644 --- a/.config/quickshell/modules/bar/Media.qml +++ b/.config/quickshell/modules/bar/Media.qml @@ -68,7 +68,7 @@ Item { MaterialSymbol { anchors.centerIn: parent fill: 1 - text: activePlayer?.isPlaying ? "pause" : "play_arrow" + text: activePlayer?.isPlaying ? "pause" : "music_note" iconSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3onSecondaryContainer } From 1721c2bbb27f94c38090408a2806302a74b7d335 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 11 Jun 2025 23:16:38 +0200 Subject: [PATCH 764/824] material symbols: disable filll anim for now performance/memory concerns --- .../modules/common/widgets/MaterialSymbol.qml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml index 07ec5025d..450a856db 100644 --- a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml +++ b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml @@ -16,13 +16,13 @@ Text { verticalAlignment: Text.AlignVCenter color: Appearance.m3colors.m3onBackground - Behavior on fill { - NumberAnimation { - duration: Appearance?.animation.elementMoveFast.duration ?? 200 - easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline - easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1] - } - } + // Behavior on fill { + // NumberAnimation { + // duration: Appearance?.animation.elementMoveFast.duration ?? 200 + // easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline + // easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1] + // } + // } font.variableAxes: { "FILL": truncatedFill, From b7b183d2ca47a972e71f93487494a694d3bac3a2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 12 Jun 2025 00:12:24 +0200 Subject: [PATCH 765/824] quickshell: use more .colors (rather than .m3colors) --- .../backgroundWidgets/BackgroundWidgets.qml | 8 ++++---- .config/quickshell/modules/bar/Bar.qml | 6 +++--- .../quickshell/modules/bar/BatteryIndicator.qml | 2 +- .config/quickshell/modules/bar/Media.qml | 2 +- .config/quickshell/modules/bar/Resource.qml | 2 +- .config/quickshell/modules/bar/Workspaces.qml | 2 +- .../quickshell/modules/common/Appearance.qml | 17 ++++++++++++----- .../modules/common/widgets/CircularProgress.qml | 2 +- .../modules/common/widgets/KeyboardKey.qml | 2 +- .../modules/common/widgets/NavRailButton.qml | 2 +- .../common/widgets/NotificationActionButton.qml | 2 +- .../common/widgets/NotificationAppIcon.qml | 2 +- .../common/widgets/NotificationGroup.qml | 2 +- .../modules/common/widgets/NotificationItem.qml | 6 +++--- .../modules/common/widgets/SelectionDialog.qml | 2 +- .../modules/common/widgets/StyledSlider.qml | 2 +- .../modules/common/widgets/StyledSwitch.qml | 2 +- .../modules/common/widgets/StyledTextArea.qml | 2 +- .../quickshell/modules/dock/DockAppButton.qml | 2 +- .config/quickshell/modules/dock/DockApps.qml | 6 +++--- .../quickshell/modules/dock/DockSeparator.qml | 2 +- .../onScreenKeyboard/OnScreenKeyboard.qml | 2 +- .../modules/overview/OverviewWidget.qml | 2 +- .../quickshell/modules/overview/SearchItem.qml | 2 +- .../modules/overview/SearchWidget.qml | 4 ++-- .../modules/session/SessionActionButton.qml | 2 +- .../quickshell/modules/sidebarLeft/AiChat.qml | 2 +- .../quickshell/modules/sidebarLeft/Anime.qml | 2 +- .../modules/sidebarLeft/aiChat/AiMessage.qml | 2 +- .../aiChat/AnnotationSourceButton.qml | 2 +- .../sidebarLeft/aiChat/MessageCodeBlock.qml | 4 ++-- .../sidebarLeft/aiChat/MessageTextBlock.qml | 2 +- .../sidebarLeft/aiChat/MessageThinkBlock.qml | 2 +- .../modules/sidebarLeft/anime/BooruImage.qml | 2 +- .../modules/sidebarLeft/anime/BooruResponse.qml | 4 ++-- .../sidebarLeft/translator/TextCanvas.qml | 4 ++-- .../sidebarRight/calendar/CalendarDayButton.qml | 2 +- .../modules/sidebarRight/todo/TodoWidget.qml | 6 +++--- .../sidebarRight/volumeMixer/VolumeMixer.qml | 2 +- 39 files changed, 65 insertions(+), 58 deletions(-) diff --git a/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml b/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml index 3fb5471df..b6add8968 100644 --- a/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml +++ b/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml @@ -17,17 +17,17 @@ Scope { property string filePath: `${Directories.state}/user/generated/wallpaper/least_busy_region.json` property real centerX: 0 property real centerY: 0 - property color dominantColor: Appearance.m3colors.m3primary + property color dominantColor: Appearance.colors.colPrimary property bool dominantColorIsDark: dominantColor.hslLightness < 0.5 - property color colBackground: ColorUtils.transparentize(ColorUtils.mix(Appearance.m3colors.m3primary, Appearance.m3colors.m3secondaryContainer), 1) - property color colText: ColorUtils.colorWithLightness(Appearance.m3colors.m3primary, (root.dominantColorIsDark ? 0.8 : 0.12)) + property color colBackground: ColorUtils.transparentize(ColorUtils.mix(Appearance.colors.colPrimary, Appearance.colors.colSecondaryContainer), 1) + property color colText: ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (root.dominantColorIsDark ? 0.8 : 0.12)) function updateWidgetPosition(fileContent) { // console.log("[BackgroundWidgets] Updating widget position with content:", fileContent) const parsedContent = JSON.parse(fileContent) root.centerX = parsedContent.center_x root.centerY = parsedContent.center_y - root.dominantColor = parsedContent.dominant_color || Appearance.m3colors.m3primary + root.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary } Timer { diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 88a605fe1..e9efa4b1c 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -24,7 +24,7 @@ Scope { Layout.bottomMargin: barHeight / 3 Layout.fillHeight: true implicitWidth: 1 - color: Appearance.m3colors.m3outlineVariant + color: Appearance.colors.colOutlineVariant } Variants { // For each monitor @@ -149,7 +149,7 @@ Scope { colBackground: barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) colBackgroundHover: Appearance.colors.colLayer1Hover colRipple: Appearance.colors.colLayer1Active - colBackgroundToggled: Appearance.m3colors.m3secondaryContainer + colBackgroundToggled: Appearance.colors.colSecondaryContainer colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover colRippleToggled: Appearance.colors.colSecondaryContainerActive toggled: GlobalStates.sidebarLeftOpen @@ -346,7 +346,7 @@ Scope { colBackground: barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) colBackgroundHover: Appearance.colors.colLayer1Hover colRipple: Appearance.colors.colLayer1Active - colBackgroundToggled: Appearance.m3colors.m3secondaryContainer + colBackgroundToggled: Appearance.colors.colSecondaryContainer colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover colRippleToggled: Appearance.colors.colSecondaryContainerActive toggled: GlobalStates.sidebarRightOpen diff --git a/.config/quickshell/modules/bar/BatteryIndicator.qml b/.config/quickshell/modules/bar/BatteryIndicator.qml index 74cdf0169..271cd8c28 100644 --- a/.config/quickshell/modules/bar/BatteryIndicator.qml +++ b/.config/quickshell/modules/bar/BatteryIndicator.qml @@ -48,7 +48,7 @@ Rectangle { lineWidth: 2 value: percentage size: 26 - secondaryColor: (isLow && !isCharging) ? batteryLowBackground : Appearance.m3colors.m3secondaryContainer + secondaryColor: (isLow && !isCharging) ? batteryLowBackground : Appearance.colors.colSecondaryContainer primaryColor: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer fill: (isLow && !isCharging) diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml index c59b6379d..d105ea203 100644 --- a/.config/quickshell/modules/bar/Media.qml +++ b/.config/quickshell/modules/bar/Media.qml @@ -62,7 +62,7 @@ Item { lineWidth: 2 value: activePlayer?.position / activePlayer?.length size: 26 - secondaryColor: Appearance.m3colors.m3secondaryContainer + secondaryColor: Appearance.colors.colSecondaryContainer primaryColor: Appearance.m3colors.m3onSecondaryContainer MaterialSymbol { diff --git a/.config/quickshell/modules/bar/Resource.qml b/.config/quickshell/modules/bar/Resource.qml index 18fdcba42..2b2dd0b60 100644 --- a/.config/quickshell/modules/bar/Resource.qml +++ b/.config/quickshell/modules/bar/Resource.qml @@ -23,7 +23,7 @@ Item { lineWidth: 2 value: percentage size: 26 - secondaryColor: Appearance.m3colors.m3secondaryContainer + secondaryColor: Appearance.colors.colSecondaryContainer primaryColor: Appearance.m3colors.m3onSecondaryContainer MaterialSymbol { diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index cb3c6551a..b95526375 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -109,7 +109,7 @@ Item { topRightRadius: radiusRight bottomRightRadius: radiusRight - color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4) + color: ColorUtils.transparentize(Appearance.colors.colSecondaryContainer, 0.4) opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+1)) ? 1 : 0 Behavior on opacity { diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index a984b9e4f..97a8502ff 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -15,11 +15,11 @@ Singleton { property QtObject sizes property string syntaxHighlightingTheme - // [!] Enabling transparency can affect readability when using light theme. + // Extremely conservative transparency values for consistency and readability property real transparency: 0 property real contentTransparency: 0 - // property real transparency: 0.15 - // property real contentTransparency: 0.5 + // property real transparency: m3colors.darkmode ? 0.05 : 0 + // property real contentTransparency: m3colors.darkmode ? 0.18 : 0 m3colors: QtObject { property bool darkmode: false @@ -106,10 +106,10 @@ Singleton { property color colOnLayer0: m3colors.m3onBackground property color colLayer0Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.9, root.contentTransparency)) property color colLayer0Active: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.8, root.contentTransparency)) - property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.7), root.contentTransparency); + property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.8), root.contentTransparency); property color colOnLayer1: m3colors.m3onSurfaceVariant; property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45); - property color colLayer2: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.55), root.contentTransparency) + property color colLayer2: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.7), root.contentTransparency) property color colOnLayer2: m3colors.m3onSurface; property color colOnLayer2Disabled: ColorUtils.mix(colOnLayer2, m3colors.m3background, 0.4); property color colLayer3: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96), root.contentTransparency) @@ -127,16 +127,23 @@ Singleton { property color colPrimaryContainer: m3colors.m3primaryContainer property color colPrimaryContainerHover: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Hover, 0.7) property color colPrimaryContainerActive: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Active, 0.6) + property color colSecondary: m3colors.m3secondary property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85) property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4) + property color colSecondaryContainer: ColorUtils.transparentize(m3colors.m3secondaryContainer, root.contentTransparency) property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.6) property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54) + property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency) + property color colSurfaceContainer: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency) + property color colSurfaceContainerHigh: ColorUtils.transparentize(m3colors.m3surfaceContainerHigh, root.contentTransparency) + property color colSurfaceContainerHighest: ColorUtils.transparentize(m3colors.m3surfaceContainerHighest, root.contentTransparency) property color colSurfaceContainerHighestHover: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.95) property color colSurfaceContainerHighestActive: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.85) property color colTooltip: m3colors.darkmode ? ColorUtils.mix(m3colors.m3background, "#3C4043", 0.5) : "#3C4043" // m3colors.m3inverseSurface in the specs, but the m3 website actually uses #3C4043 property color colOnTooltip: "#F8F9FA" // m3colors.m3inverseOnSurface in the specs, but the m3 website actually uses this color property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5) property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7) + property color colOutlineVariant: m3colors.m3outlineVariant } rounding: QtObject { diff --git a/.config/quickshell/modules/common/widgets/CircularProgress.qml b/.config/quickshell/modules/common/widgets/CircularProgress.qml index d0afc6060..c3731e64e 100644 --- a/.config/quickshell/modules/common/widgets/CircularProgress.qml +++ b/.config/quickshell/modules/common/widgets/CircularProgress.qml @@ -14,7 +14,7 @@ Item { property int lineWidth: 2 property real value: 0 property color primaryColor: Appearance.m3colors.m3onSecondaryContainer - property color secondaryColor: Appearance.m3colors.m3secondaryContainer + property color secondaryColor: Appearance.colors.colSecondaryContainer property real gapAngle: Math.PI / 9 property bool fill: false property int fillOverflow: 2 diff --git a/.config/quickshell/modules/common/widgets/KeyboardKey.qml b/.config/quickshell/modules/common/widgets/KeyboardKey.qml index d6ba5ba08..0cc80429e 100644 --- a/.config/quickshell/modules/common/widgets/KeyboardKey.qml +++ b/.config/quickshell/modules/common/widgets/KeyboardKey.qml @@ -15,7 +15,7 @@ Rectangle { property real extraBottomBorderWidth: 2 property color borderColor: Appearance.colors.colOnLayer0 property real borderRadius: 5 - property color keyColor: Appearance.m3colors.m3surfaceContainerLow + property color keyColor: Appearance.colors.colSurfaceContainerLow implicitWidth: keyFace.implicitWidth + borderWidth * 2 implicitHeight: keyFace.implicitHeight + borderWidth * 2 + extraBottomBorderWidth radius: borderRadius diff --git a/.config/quickshell/modules/common/widgets/NavRailButton.qml b/.config/quickshell/modules/common/widgets/NavRailButton.qml index 1f55ad282..0a241553f 100644 --- a/.config/quickshell/modules/common/widgets/NavRailButton.qml +++ b/.config/quickshell/modules/common/widgets/NavRailButton.qml @@ -30,7 +30,7 @@ Button { Layout.alignment: Qt.AlignHCenter radius: Appearance.rounding.full color: toggled ? - (button.down ? Appearance.colors.colSecondaryContainerActive : button.hovered ? Appearance.colors.colSecondaryContainerHover : Appearance.m3colors.m3secondaryContainer) : + (button.down ? Appearance.colors.colSecondaryContainerActive : button.hovered ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer) : (button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)) Behavior on color { diff --git a/.config/quickshell/modules/common/widgets/NotificationActionButton.qml b/.config/quickshell/modules/common/widgets/NotificationActionButton.qml index 370814f91..e85735a71 100644 --- a/.config/quickshell/modules/common/widgets/NotificationActionButton.qml +++ b/.config/quickshell/modules/common/widgets/NotificationActionButton.qml @@ -15,7 +15,7 @@ RippleButton { leftPadding: 15 rightPadding: 15 buttonRadius: Appearance.rounding.small - colBackground: (urgency == NotificationUrgency.Critical) ? Appearance.m3colors.m3secondaryContainer : Appearance.m3colors.m3surfaceContainerHighest + colBackground: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainer : Appearance.colors.colSurfaceContainerHighest colBackgroundHover: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSurfaceContainerHighestHover colRipple: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colSurfaceContainerHighestActive diff --git a/.config/quickshell/modules/common/widgets/NotificationAppIcon.qml b/.config/quickshell/modules/common/widgets/NotificationAppIcon.qml index 371093b25..81505aff6 100644 --- a/.config/quickshell/modules/common/widgets/NotificationAppIcon.qml +++ b/.config/quickshell/modules/common/widgets/NotificationAppIcon.qml @@ -26,7 +26,7 @@ Rectangle { // App icon implicitWidth: size implicitHeight: size radius: Appearance.rounding.full - color: Appearance.m3colors.m3secondaryContainer + color: Appearance.colors.colSecondaryContainer Loader { id: materialSymbolLoader active: root.appIcon == "" diff --git a/.config/quickshell/modules/common/widgets/NotificationGroup.qml b/.config/quickshell/modules/common/widgets/NotificationGroup.qml index cfe93327c..e79f400ae 100644 --- a/.config/quickshell/modules/common/widgets/NotificationGroup.qml +++ b/.config/quickshell/modules/common/widgets/NotificationGroup.qml @@ -113,7 +113,7 @@ Item { // Notification group area id: background anchors.left: parent.left width: parent.width - color: Appearance.m3colors.m3surfaceContainer + color: Appearance.colors.colSurfaceContainer radius: Appearance.rounding.normal anchors.leftMargin: root.xOffset diff --git a/.config/quickshell/modules/common/widgets/NotificationItem.qml b/.config/quickshell/modules/common/widgets/NotificationItem.qml index 26286290c..9a2a7c31d 100644 --- a/.config/quickshell/modules/common/widgets/NotificationItem.qml +++ b/.config/quickshell/modules/common/widgets/NotificationItem.qml @@ -129,9 +129,9 @@ Item { // Notification item area color: (expanded && !onlyNotification) ? (notificationObject.urgency == NotificationUrgency.Critical) ? - ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, Appearance.colors.colLayer2, 0.35) : - (Appearance.m3colors.m3surfaceContainerHigh) : - ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHighest) + ColorUtils.mix(Appearance.colors.colSecondaryContainer, Appearance.colors.colLayer2, 0.35) : + (Appearance.colors.colSurfaceContainerHigh) : + ColorUtils.transparentize(Appearance.colors.colSurfaceContainerHighest) implicitHeight: expanded ? (contentColumn.implicitHeight + padding * 2) : summaryRow.implicitHeight Behavior on implicitHeight { diff --git a/.config/quickshell/modules/common/widgets/SelectionDialog.qml b/.config/quickshell/modules/common/widgets/SelectionDialog.qml index affd13bc9..9cf0940ed 100644 --- a/.config/quickshell/modules/common/widgets/SelectionDialog.qml +++ b/.config/quickshell/modules/common/widgets/SelectionDialog.qml @@ -34,7 +34,7 @@ Item { Rectangle { // The dialog id: dialog - color: Appearance.m3colors.m3surfaceContainerHigh + color: Appearance.colors.colSurfaceContainerHigh radius: Appearance.rounding.normal anchors.fill: parent anchors.margins: dialogMargin diff --git a/.config/quickshell/modules/common/widgets/StyledSlider.qml b/.config/quickshell/modules/common/widgets/StyledSlider.qml index 70491b8bb..ca0980030 100644 --- a/.config/quickshell/modules/common/widgets/StyledSlider.qml +++ b/.config/quickshell/modules/common/widgets/StyledSlider.qml @@ -19,7 +19,7 @@ Slider { property real handleLimit: root.backgroundDotMargins property real trackHeight: 30 * scale property color highlightColor: Appearance.colors.colPrimary - property color trackColor: Appearance.m3colors.m3secondaryContainer + property color trackColor: Appearance.colors.colSecondaryContainer property color handleColor: Appearance.m3colors.m3onSecondaryContainer property real trackRadius: Appearance.rounding.verysmall * scale property real unsharpenRadius: Appearance.rounding.unsharpen diff --git a/.config/quickshell/modules/common/widgets/StyledSwitch.qml b/.config/quickshell/modules/common/widgets/StyledSwitch.qml index bbc2ae513..217a2f7e4 100644 --- a/.config/quickshell/modules/common/widgets/StyledSwitch.qml +++ b/.config/quickshell/modules/common/widgets/StyledSwitch.qml @@ -13,7 +13,7 @@ Switch { implicitHeight: 32 * root.scale implicitWidth: 52 * root.scale property color activeColor: Appearance?.colors.colPrimary ?? "#685496" - property color inactiveColor: Appearance?.m3colors.m3surfaceContainerHighest ?? "#45464F" + property color inactiveColor: Appearance?.colors.colSurfaceContainerHighest ?? "#45464F" PointingHandInteraction {} diff --git a/.config/quickshell/modules/common/widgets/StyledTextArea.qml b/.config/quickshell/modules/common/widgets/StyledTextArea.qml index 1ea9a349a..67d417576 100644 --- a/.config/quickshell/modules/common/widgets/StyledTextArea.qml +++ b/.config/quickshell/modules/common/widgets/StyledTextArea.qml @@ -5,7 +5,7 @@ import QtQuick.Controls TextArea { renderType: Text.NativeRendering selectedTextColor: Appearance.m3colors.m3onSecondaryContainer - selectionColor: Appearance.m3colors.m3secondaryContainer + selectionColor: Appearance.colors.colSecondaryContainer placeholderTextColor: Appearance.m3colors.m3outline font { family: Appearance?.font.family.main ?? "sans-serif" diff --git a/.config/quickshell/modules/dock/DockAppButton.qml b/.config/quickshell/modules/dock/DockAppButton.qml index 5ddb6d150..f4623e625 100644 --- a/.config/quickshell/modules/dock/DockAppButton.qml +++ b/.config/quickshell/modules/dock/DockAppButton.qml @@ -106,7 +106,7 @@ DockButton { implicitWidth: (appToplevel.toplevels.length <= 3) ? root.countDotWidth : root.countDotHeight // Circles when too many implicitHeight: root.countDotHeight - color: appIsActive ? Appearance.m3colors.m3primary : ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.4) + color: appIsActive ? Appearance.colors.colPrimary : ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.4) } } } diff --git a/.config/quickshell/modules/dock/DockApps.qml b/.config/quickshell/modules/dock/DockApps.qml index 8b680c51a..ffda024c7 100644 --- a/.config/quickshell/modules/dock/DockApps.qml +++ b/.config/quickshell/modules/dock/DockApps.qml @@ -166,7 +166,7 @@ Item { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } clip: true - color: Appearance.m3colors.m3surfaceContainer + color: Appearance.colors.colSurfaceContainer radius: Appearance.rounding.normal anchors.bottom: parent.bottom anchors.bottomMargin: Appearance.sizes.elevationMargin @@ -205,7 +205,7 @@ Item { contentWidth: parent.width - anchors.margins * 2 WrapperRectangle { Layout.fillWidth: true - color: ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainer) + color: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer) radius: Appearance.rounding.small margin: 5 StyledText { @@ -218,7 +218,7 @@ Item { } GroupButton { id: closeButton - colBackground: ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainer) + colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer) baseWidth: windowControlsHeight baseHeight: windowControlsHeight buttonRadius: Appearance.rounding.full diff --git a/.config/quickshell/modules/dock/DockSeparator.qml b/.config/quickshell/modules/dock/DockSeparator.qml index 29b77d492..abb45d1da 100644 --- a/.config/quickshell/modules/dock/DockSeparator.qml +++ b/.config/quickshell/modules/dock/DockSeparator.qml @@ -9,5 +9,5 @@ Rectangle { Layout.bottomMargin: Appearance.sizes.hyprlandGapsOut + dockRow.padding + Appearance.rounding.normal Layout.fillHeight: true implicitWidth: 1 - color: Appearance.m3colors.m3outlineVariant + color: Appearance.colors.colOutlineVariant } diff --git a/.config/quickshell/modules/onScreenKeyboard/OnScreenKeyboard.qml b/.config/quickshell/modules/onScreenKeyboard/OnScreenKeyboard.qml index 39862c55f..e78f45b2e 100644 --- a/.config/quickshell/modules/onScreenKeyboard/OnScreenKeyboard.qml +++ b/.config/quickshell/modules/onScreenKeyboard/OnScreenKeyboard.qml @@ -111,7 +111,7 @@ Scope { // Scope Layout.bottomMargin: 20 Layout.fillHeight: true implicitWidth: 1 - color: Appearance.m3colors.m3outlineVariant + color: Appearance.colors.colOutlineVariant } OskContent { id: oskContent diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index 1a9494938..9ab39fd02 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -25,7 +25,7 @@ Item { property var windowAddresses: HyprlandData.addresses property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id) property real scale: ConfigOptions.overview.scale - property color activeBorderColor: Appearance.m3colors.m3secondary + property color activeBorderColor: Appearance.colors.colSecondary property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ? ((monitor.height - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) : diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index 00d5da743..d23cb4c09 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -80,7 +80,7 @@ RippleButton { buttonRadius: Appearance.rounding.normal colBackground: (root.down || root.keyboardDown) ? Appearance.colors.colLayer1Active : ((root.hovered || root.focus) ? Appearance.colors.colLayer1Hover : - ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHigh, 1)) + ColorUtils.transparentize(Appearance.colors.colSurfaceContainerHigh, 1)) colBackgroundHover: Appearance.colors.colLayer1Hover colRipple: Appearance.colors.colLayer1Active diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index ea8dcb3b9..9e6866240 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -219,7 +219,7 @@ Item { // Wrapper } color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant selectedTextColor: Appearance.m3colors.m3onSecondaryContainer - selectionColor: Appearance.m3colors.m3secondaryContainer + selectionColor: Appearance.colors.colSecondaryContainer placeholderText: qsTr("Search, calculate or run") placeholderTextColor: Appearance.m3colors.m3outline implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth @@ -260,7 +260,7 @@ Item { // Wrapper visible: root.showResults Layout.fillWidth: true height: 1 - color: Appearance.m3colors.m3outlineVariant + color: Appearance.colors.colOutlineVariant } ListView { // App results diff --git a/.config/quickshell/modules/session/SessionActionButton.qml b/.config/quickshell/modules/session/SessionActionButton.qml index 207305769..becda60c1 100644 --- a/.config/quickshell/modules/session/SessionActionButton.qml +++ b/.config/quickshell/modules/session/SessionActionButton.qml @@ -18,7 +18,7 @@ RippleButton { buttonRadius: (button.focus || button.down) ? size / 2 : Appearance.rounding.verylarge colBackground: button.keyboardDown ? Appearance.colors.colSecondaryContainerActive : button.focus ? Appearance.colors.colPrimary : - Appearance.m3colors.m3secondaryContainer + Appearance.colors.colSecondaryContainer colBackgroundHover: Appearance.colors.colPrimary colRipple: Appearance.colors.colPrimaryActive property color colText: (button.down || button.keyboardDown || button.focus || button.hovered) ? diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 9ba1d3688..1ce752c7b 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -345,7 +345,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\) implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin + commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45) clip: true - border.color: Appearance.m3colors.m3outlineVariant + border.color: Appearance.colors.colOutlineVariant border.width: 1 Behavior on implicitHeight { diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 444d3f052..1300d5482 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -359,7 +359,7 @@ Item { implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin + commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45) clip: true - border.color: Appearance.m3colors.m3outlineVariant + border.color: Appearance.colors.colOutlineVariant border.width: 1 Behavior on implicitHeight { diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index 775d12082..8d7952110 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -91,7 +91,7 @@ Rectangle { Rectangle { // Name id: nameWrapper - color: Appearance.m3colors.m3secondaryContainer + color: Appearance.colors.colSecondaryContainer // color: "transparent" radius: Appearance.rounding.small implicitHeight: Math.max(nameRowLayout.implicitHeight + 5 * 2, 30) diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml index f344308ca..a253a291e 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml @@ -21,7 +21,7 @@ RippleButton { leftPadding: (implicitHeight - faviconSize) / 2 rightPadding: 10 buttonRadius: Appearance.rounding.full - colBackground: Appearance.m3colors.m3surfaceContainerHighest + colBackground: Appearance.colors.colSurfaceContainerHighest colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover colRipple: Appearance.colors.colSurfaceContainerHighestActive diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml index ef0c1b5e9..ea7bb0ee3 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml @@ -40,7 +40,7 @@ ColumnLayout { topRightRadius: codeBlockBackgroundRounding bottomLeftRadius: Appearance.rounding.unsharpen bottomRightRadius: Appearance.rounding.unsharpen - color: Appearance.m3colors.m3surfaceContainerHighest + color: Appearance.colors.colSurfaceContainerHighest implicitHeight: codeBlockTitleBarRowLayout.implicitHeight + codeBlockHeaderPadding * 2 RowLayout { // Language and buttons @@ -209,7 +209,7 @@ ColumnLayout { font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text font.pixelSize: Appearance.font.pixelSize.small selectedTextColor: Appearance.m3colors.m3onSecondaryContainer - selectionColor: Appearance.m3colors.m3secondaryContainer + selectionColor: Appearance.colors.colSecondaryContainer // wrapMode: TextEdit.Wrap color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml index 25ecb05a6..faa6c5590 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml @@ -120,7 +120,7 @@ ColumnLayout { font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text font.pixelSize: Appearance.font.pixelSize.small selectedTextColor: Appearance.m3colors.m3onSecondaryContainer - selectionColor: Appearance.m3colors.m3secondaryContainer + selectionColor: Appearance.colors.colSecondaryContainer wrapMode: TextEdit.Wrap color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 textFormat: renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml index 27dae3bd3..1ae941b78 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml @@ -64,7 +64,7 @@ Item { Rectangle { // Header background id: header - color: Appearance.m3colors.m3surfaceContainerHighest + color: Appearance.colors.colSurfaceContainerHighest Layout.fillWidth: true implicitHeight: thinkBlockTitleBarRowLayout.implicitHeight + thinkBlockHeaderPaddingVertical * 2 diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index c8c89f72a..4e115bc76 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -133,7 +133,7 @@ Button { opacity: root.showActions ? 1 : 0 visible: opacity > 0 radius: Appearance.rounding.small - color: Appearance.m3colors.m3surfaceContainer + color: Appearance.colors.colSurfaceContainer implicitHeight: contextMenuColumnLayout.implicitHeight + radius * 2 implicitWidth: contextMenuColumnLayout.implicitWidth diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index c19c8bb98..7a207582f 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -67,7 +67,7 @@ Rectangle { RowLayout { // Header Rectangle { // Provider name id: providerNameWrapper - color: Appearance.m3colors.m3secondaryContainer + color: Appearance.colors.colSecondaryContainer radius: Appearance.rounding.small implicitWidth: providerName.implicitWidth + 10 * 2 implicitHeight: Math.max(providerName.implicitHeight + 5 * 2, 30) @@ -269,7 +269,7 @@ Rectangle { } buttonRadius: Appearance.rounding.small - colBackground: Appearance.m3colors.m3surfaceContainerHighest + colBackground: Appearance.colors.colSurfaceContainerHighest colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover colRipple: Appearance.colors.colSurfaceContainerHighestActive diff --git a/.config/quickshell/modules/sidebarLeft/translator/TextCanvas.qml b/.config/quickshell/modules/sidebarLeft/translator/TextCanvas.qml index 7f32a63d9..dad25020f 100644 --- a/.config/quickshell/modules/sidebarLeft/translator/TextCanvas.qml +++ b/.config/quickshell/modules/sidebarLeft/translator/TextCanvas.qml @@ -21,9 +21,9 @@ Rectangle { default property alias actionButtons: actions.data Layout.fillWidth: true implicitHeight: Math.max(150, inputColumn.implicitHeight) - color: isInput ? Appearance.colors.colLayer1 : Appearance.m3colors.m3surfaceContainer + color: isInput ? Appearance.colors.colLayer1 : Appearance.colors.colSurfaceContainer radius: Appearance.rounding.normal - border.color: isInput ? Appearance.m3colors.m3outlineVariant : "transparent" + border.color: isInput ? Appearance.colors.colOutlineVariant : "transparent" border.width: isInput ? 1 : 0 signal inputTextChanged(); // Signal emitted when text changes diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml index 88de9202c..7d1af447d 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml @@ -26,7 +26,7 @@ RippleButton { font.weight: bold ? Font.DemiBold : Font.Normal color: (isToday == 1) ? Appearance.m3colors.m3onPrimary : (isToday == 0) ? Appearance.colors.colOnLayer1 : - Appearance.m3colors.m3outlineVariant + Appearance.colors.colOutlineVariant Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index 527ed6978..9f5f4fd08 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -114,7 +114,7 @@ Item { id: tabBarBottomBorder Layout.fillWidth: true height: 1 - color: Appearance.m3colors.m3outlineVariant + color: Appearance.colors.colOutlineVariant } SwipeView { @@ -228,7 +228,7 @@ Item { anchors.margins: root.dialogMargins implicitHeight: dialogColumnLayout.implicitHeight - color: Appearance.m3colors.m3surfaceContainerHigh + color: Appearance.colors.colSurfaceContainerHigh radius: Appearance.rounding.normal function addTask() { @@ -264,7 +264,7 @@ Item { color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant renderType: Text.NativeRendering selectedTextColor: Appearance.m3colors.m3onSecondaryContainer - selectionColor: Appearance.m3colors.m3secondaryContainer + selectionColor: Appearance.colors.colSecondaryContainer placeholderText: qsTr("Task description") placeholderTextColor: Appearance.m3colors.m3outline focus: root.showAddDialog diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml index 1799df556..2e1570f30 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml @@ -161,7 +161,7 @@ Item { Rectangle { // The dialog id: dialog - color: Appearance.m3colors.m3surfaceContainerHigh + color: Appearance.colors.colSurfaceContainerHigh radius: Appearance.rounding.normal anchors.left: parent.left anchors.right: parent.right From 60c93fd2ee7dad44a584f335bb3fd04804fa78ff Mon Sep 17 00:00:00 2001 From: _xB <65196493+xBiei@users.noreply.github.com> Date: Thu, 12 Jun 2025 05:43:36 +0300 Subject: [PATCH 766/824] Add the utilButtons config options --- .config/quickshell/modules/common/ConfigOptions.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 91ba70408..842fe66ce 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -36,6 +36,12 @@ Singleton { property bool alwaysShowSwap: true property bool alwaysShowCpu: false } + property QtObject utilButtons: QtObject { + property bool showMicToggle: true + property bool showKeyboardToggle: true + property bool showScreenSnip: true + // property bool showColorPicker: true + } property QtObject workspaces: QtObject { property int shown: 10 property bool alwaysShowNumbers: false From c3fa00b5c651e520ee6086b7dc41ccfc64e563a3 Mon Sep 17 00:00:00 2001 From: _xB <65196493+xBiei@users.noreply.github.com> Date: Thu, 12 Jun 2025 05:45:22 +0300 Subject: [PATCH 767/824] bar: add visibility options to utilButtons --- .config/quickshell/modules/bar/UtilButtons.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/bar/UtilButtons.qml b/.config/quickshell/modules/bar/UtilButtons.qml index d2705865c..51f6e728c 100644 --- a/.config/quickshell/modules/bar/UtilButtons.qml +++ b/.config/quickshell/modules/bar/UtilButtons.qml @@ -25,6 +25,7 @@ Rectangle { CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Hyprland.dispatch("exec hyprshot --freeze --clipboard-only --mode region --silent") + visible: ConfigOptions.bar.utilButtons.showScreenSnip MaterialSymbol { horizontalAlignment: Qt.AlignHCenter @@ -39,6 +40,7 @@ Rectangle { // CircleUtilButton { // Layout.alignment: Qt.AlignVCenter // onClicked: Hyprland.dispatch("exec hyprpicker -a") + // visible: ConfigOptions.bar.utilButtons.showColorPicker // MaterialSymbol { // horizontalAlignment: Qt.AlignHCenter @@ -47,12 +49,12 @@ Rectangle { // iconSize: Appearance.font.pixelSize.large // color: Appearance.colors.colOnLayer2 // } - // } CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Hyprland.dispatch("global quickshell:oskToggle") + visible: ConfigOptions.bar.utilButtons.showKeyboardToggle MaterialSymbol { horizontalAlignment: Qt.AlignHCenter @@ -67,6 +69,7 @@ Rectangle { CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Hyprland.dispatch("exec wpctl set-mute @DEFAULT_SOURCE@ toggle") + visible: ConfigOptions.bar.utilButtons.showMicToggle && Pipewire.defaultAudioSource?.audio MaterialSymbol { horizontalAlignment: Qt.AlignHCenter From 89ff743c5dd916e13aa20eac13c0522bc4e7055e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 12 Jun 2025 07:43:52 +0200 Subject: [PATCH 768/824] Appearance: more m3colors in colors --- .config/quickshell/modules/bar/Workspaces.qml | 2 +- .config/quickshell/modules/common/Appearance.qml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index b95526375..cb3c6551a 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -109,7 +109,7 @@ Item { topRightRadius: radiusRight bottomRightRadius: radiusRight - color: ColorUtils.transparentize(Appearance.colors.colSecondaryContainer, 0.4) + color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4) opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+1)) ? 1 : 0 Behavior on opacity { diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 97a8502ff..49e9576bd 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -122,6 +122,7 @@ Singleton { property color colLayer3Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.90), root.contentTransparency) property color colLayer3Active: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.80), root.contentTransparency); property color colPrimary: m3colors.m3primary + property color colOnPrimary: m3colors.m3onPrimary property color colPrimaryHover: ColorUtils.mix(colors.colPrimary, colLayer1Hover, 0.87) property color colPrimaryActive: ColorUtils.mix(colors.colPrimary, colLayer1Active, 0.7) property color colPrimaryContainer: m3colors.m3primaryContainer @@ -133,6 +134,7 @@ Singleton { property color colSecondaryContainer: ColorUtils.transparentize(m3colors.m3secondaryContainer, root.contentTransparency) property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.6) property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54) + property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency) property color colSurfaceContainer: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency) property color colSurfaceContainerHigh: ColorUtils.transparentize(m3colors.m3surfaceContainerHigh, root.contentTransparency) From 13bad429bc5d5f9938164f62c4c2d0b9e2300f9c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 12 Jun 2025 07:44:03 +0200 Subject: [PATCH 769/824] refractor visualizer to its own file --- .../modules/common/widgets/WaveVisualizer.qml | 78 +++++++++++++++++++ .../modules/mediaControls/PlayerControl.qml | 65 ++-------------- 2 files changed, 84 insertions(+), 59 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/WaveVisualizer.qml diff --git a/.config/quickshell/modules/common/widgets/WaveVisualizer.qml b/.config/quickshell/modules/common/widgets/WaveVisualizer.qml new file mode 100644 index 000000000..eb579d7e1 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/WaveVisualizer.qml @@ -0,0 +1,78 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import Quickshell.Io + +Canvas { // Visualizer + id: root + property list points + property list smoothPoints + property real maxVisualizerValue: 1000 + property int smoothing: 2 + property bool live: true + property color color: Appearance.m3colors.m3primary + + onPointsChanged: () => { + root.requestPaint() + } + + anchors.fill: parent + onPaint: { + var ctx = getContext("2d"); + ctx.clearRect(0, 0, width, height); + + var points = root.points; + var maxVal = root.maxVisualizerValue || 1; + var h = height; + var w = width; + var n = points.length; + if (n < 2) return; + + // Smoothing: simple moving average (optional) + var smoothWindow = root.smoothing; // adjust for more/less smoothing + root.smoothPoints = []; + for (var i = 0; i < n; ++i) { + var sum = 0, count = 0; + for (var j = -smoothWindow; j <= smoothWindow; ++j) { + var idx = Math.max(0, Math.min(n - 1, i + j)); + sum += points[idx]; + count++; + } + root.smoothPoints.push(sum / count); + } + if (!root.live) smoothedPoints.fill(0); // If not playing, show no points + + ctx.beginPath(); + ctx.moveTo(0, h); + for (var i = 0; i < n; ++i) { + var x = i * w / (n - 1); + var y = h - (root.smoothPoints[i] / maxVal) * h; + ctx.lineTo(x, y); + } + ctx.lineTo(w, h); + ctx.closePath(); + + ctx.fillStyle = Qt.rgba( + root.color.r, + root.color.g, + root.color.b, + 0.15 + ); + ctx.fill(); + } + + layer.enabled: true + layer.effect: MultiEffect { // Blur a bit to obscure away the points + source: root + saturation: 0.2 + blurEnabled: true + blurMax: 7 + blur: 1 + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/mediaControls/PlayerControl.qml b/.config/quickshell/modules/mediaControls/PlayerControl.qml index fb4799e17..2100cc90c 100644 --- a/.config/quickshell/modules/mediaControls/PlayerControl.qml +++ b/.config/quickshell/modules/mediaControls/PlayerControl.qml @@ -153,67 +153,14 @@ Item { // Player instance } } - Canvas { // Visualizer + WaveVisualizer { id: visualizerCanvas anchors.fill: parent - onPaint: { - var ctx = getContext("2d"); - ctx.clearRect(0, 0, width, height); - - var points = playerController.visualizerPoints; - var maxVal = playerController.maxVisualizerValue || 1; - var h = height; - var w = width; - var n = points.length; - if (n < 2) return; - - // Smoothing: simple moving average (optional) - var smoothPoints = []; - var smoothWindow = playerController.visualizerSmoothing; // adjust for more/less smoothing - for (var i = 0; i < n; ++i) { - var sum = 0, count = 0; - for (var j = -smoothWindow; j <= smoothWindow; ++j) { - var idx = Math.max(0, Math.min(n - 1, i + j)); - sum += points[idx]; - count++; - } - smoothPoints.push(sum / count); - } - if (!playerController.player?.isPlaying) smoothedPoints.fill(0); // If not playing, show no points - - ctx.beginPath(); - ctx.moveTo(0, h); - for (var i = 0; i < n; ++i) { - var x = i * w / (n - 1); - var y = h - (smoothPoints[i] / maxVal) * h; - ctx.lineTo(x, y); - } - ctx.lineTo(w, h); - ctx.closePath(); - - ctx.fillStyle = Qt.rgba( - blendedColors.colPrimary.r, - blendedColors.colPrimary.g, - blendedColors.colPrimary.b, - 0.15 - ); - ctx.fill(); - } - Connections { - target: playerController - function onVisualizerPointsChanged() { - visualizerCanvas.requestPaint() - } - } - - layer.enabled: true - layer.effect: MultiEffect { // Blur a bit to obscure away the points - source: visualizerCanvas - saturation: 0.2 - blurEnabled: true - blurMax: 7 - blur: 1 - } + live: playerController.player?.isPlaying + points: playerController.visualizerPoints + maxVisualizerValue: playerController.maxVisualizerValue + smoothing: playerController.visualizerSmoothing + color: blendedColors.colPrimary } RowLayout { From af409dc0c9cbf7d5436fd342532d75f270b9b3bc Mon Sep 17 00:00:00 2001 From: _xB <65196493+xBiei@users.noreply.github.com> Date: Thu, 12 Jun 2025 09:01:25 +0300 Subject: [PATCH 770/824] bar: wrap buttons in Loaders --- .../quickshell/modules/bar/UtilButtons.qml | 97 +++++++++---------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/.config/quickshell/modules/bar/UtilButtons.qml b/.config/quickshell/modules/bar/UtilButtons.qml index 51f6e728c..73b44a58d 100644 --- a/.config/quickshell/modules/bar/UtilButtons.qml +++ b/.config/quickshell/modules/bar/UtilButtons.qml @@ -22,65 +22,64 @@ Rectangle { spacing: 4 anchors.centerIn: parent - CircleUtilButton { - Layout.alignment: Qt.AlignVCenter - onClicked: Hyprland.dispatch("exec hyprshot --freeze --clipboard-only --mode region --silent") - visible: ConfigOptions.bar.utilButtons.showScreenSnip - - MaterialSymbol { - horizontalAlignment: Qt.AlignHCenter - fill: 1 - text: "screenshot_region" - iconSize: Appearance.font.pixelSize.large - color: Appearance.colors.colOnLayer2 + Loader { + active: ConfigOptions.bar.utilButtons.showScreenSnip + sourceComponent: CircleUtilButton { + Layout.alignment: Qt.AlignVCenter + onClicked: Hyprland.dispatch("exec hyprshot --freeze --clipboard-only --mode region --silent") + MaterialSymbol { + horizontalAlignment: Qt.AlignHCenter + fill: 1 + text: "screenshot_region" + iconSize: Appearance.font.pixelSize.large + color: Appearance.colors.colOnLayer2 + } } - } - // CircleUtilButton { - // Layout.alignment: Qt.AlignVCenter - // onClicked: Hyprland.dispatch("exec hyprpicker -a") - // visible: ConfigOptions.bar.utilButtons.showColorPicker - - // MaterialSymbol { - // horizontalAlignment: Qt.AlignHCenter - // fill: 1 - // text: "colorize" - // iconSize: Appearance.font.pixelSize.large - // color: Appearance.colors.colOnLayer2 + // Loader { + // active: ConfigOptions.bar.utilButtons.showColorPicker + // sourceComponent: CircleUtilButton { + // Layout.alignment: Qt.AlignVCenter + // onClicked: Hyprland.dispatch("exec hyprpicker -a") + // MaterialSymbol { + // horizontalAlignment: Qt.AlignHCenter + // fill: 1 + // text: "colorize" + // iconSize: Appearance.font.pixelSize.large + // color: Appearance.colors.colOnLayer2 + // } // } // } - CircleUtilButton { - Layout.alignment: Qt.AlignVCenter - onClicked: Hyprland.dispatch("global quickshell:oskToggle") - visible: ConfigOptions.bar.utilButtons.showKeyboardToggle - - MaterialSymbol { - horizontalAlignment: Qt.AlignHCenter - fill: 0 - text: "keyboard" - iconSize: Appearance.font.pixelSize.large - color: Appearance.colors.colOnLayer2 + Loader { + active: ConfigOptions.bar.utilButtons.showKeyboardToggle + sourceComponent: CircleUtilButton { + Layout.alignment: Qt.AlignVCenter + onClicked: Hyprland.dispatch("global quickshell:oskToggle") + MaterialSymbol { + horizontalAlignment: Qt.AlignHCenter + fill: 0 + text: "keyboard" + iconSize: Appearance.font.pixelSize.large + color: Appearance.colors.colOnLayer2 + } } - } - CircleUtilButton { - Layout.alignment: Qt.AlignVCenter - onClicked: Hyprland.dispatch("exec wpctl set-mute @DEFAULT_SOURCE@ toggle") - visible: ConfigOptions.bar.utilButtons.showMicToggle && Pipewire.defaultAudioSource?.audio - - MaterialSymbol { - horizontalAlignment: Qt.AlignHCenter - fill: 0 - text: Pipewire.defaultAudioSource?.audio?.muted ? "mic_off" : "mic" - iconSize: Appearance.font.pixelSize.large - color: Appearance.colors.colOnLayer2 + Loader { + active: ConfigOptions.bar.utilButtons.showMicToggle + sourceComponent: CircleUtilButton { + Layout.alignment: Qt.AlignVCenter + onClicked: Hyprland.dispatch("exec wpctl set-mute @DEFAULT_SOURCE@ toggle") + MaterialSymbol { + horizontalAlignment: Qt.AlignHCenter + fill: 0 + text: Pipewire.defaultAudioSource?.audio?.muted ? "mic_off" : "mic" + iconSize: Appearance.font.pixelSize.large + color: Appearance.colors.colOnLayer2 + } } - } - } - } From fc157dd709b32560dac4a930991896a9cb38e1da Mon Sep 17 00:00:00 2001 From: _xB <65196493+xBiei@users.noreply.github.com> Date: Thu, 12 Jun 2025 09:22:22 +0300 Subject: [PATCH 771/824] bar: Filter Variants instead of hiding PanelWindow --- .config/quickshell/modules/bar/Bar.qml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 6c5e2a865..ed108c69e 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -19,16 +19,22 @@ Scope { readonly property int osdHideMouseMoveThreshold: 20 property bool showBarBackground: ConfigOptions.bar.showBackground + + // Check screensList from config, If no screens are specified, show on all screens + property var filteredScreens: { + const list = ConfigOptions.bar.screensList; + if (!list || list.length === 0) + return Quickshell.screens; + return Quickshell.screens.filter(screen => list.includes(screen.name)); + } + Variants { // For each monitor - model: Quickshell.screens + model: bar.filteredScreens PanelWindow { // Bar window id: barRoot screen: modelData - // Check screensList from config, If no screens are specified, show on all screens - visible: ConfigOptions.bar.screensList.includes(modelData.name) || ConfigOptions.bar.screensList.length === 0 - property ShellScreen modelData property var brightnessMonitor: Brightness.getMonitorForScreen(modelData) property real useShortenedForm: (Appearance.sizes.barHellaShortenScreenWidthThreshold >= screen.width) ? 2 : From a63dec24b8ec0b884c90652ee0f3d0c918ac0f49 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 12 Jun 2025 12:00:17 +0200 Subject: [PATCH 772/824] fix typo (#1393 is closed?) --- .config/quickshell/modules/common/widgets/WaveVisualizer.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/widgets/WaveVisualizer.qml b/.config/quickshell/modules/common/widgets/WaveVisualizer.qml index eb579d7e1..571e71838 100644 --- a/.config/quickshell/modules/common/widgets/WaveVisualizer.qml +++ b/.config/quickshell/modules/common/widgets/WaveVisualizer.qml @@ -46,7 +46,7 @@ Canvas { // Visualizer } root.smoothPoints.push(sum / count); } - if (!root.live) smoothedPoints.fill(0); // If not playing, show no points + if (!root.live) root.smoothPoints.fill(0); // If not playing, show no points ctx.beginPath(); ctx.moveTo(0, h); From 8f094e9a906002482328ad936c40a1f98c4a2662 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 12 Jun 2025 12:43:10 +0200 Subject: [PATCH 773/824] translator: add useless flag arabian guy requested --- .config/quickshell/modules/sidebarLeft/Translator.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/sidebarLeft/Translator.qml b/.config/quickshell/modules/sidebarLeft/Translator.qml index 999fe3e44..7e20360b2 100644 --- a/.config/quickshell/modules/sidebarLeft/Translator.qml +++ b/.config/quickshell/modules/sidebarLeft/Translator.qml @@ -59,7 +59,7 @@ Item { Process { id: translateProc - command: ["bash", "-c", `trans -no-theme` + command: ["bash", "-c", `trans -no-theme -no-bidi` + ` -source '${StringUtils.shellSingleQuoteEscape(root.sourceLanguage)}'` + ` -target '${StringUtils.shellSingleQuoteEscape(root.targetLanguage)}'` + ` -no-ansi '${StringUtils.shellSingleQuoteEscape(root.inputField.text.trim())}'`] From acc6ce644eeb07f1d21e653c2793b549b1a8185e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 12 Jun 2025 12:43:45 +0200 Subject: [PATCH 774/824] media controls: material 3 sperm --- .../common/widgets/StyledProgressBar.qml | 56 +++++++++++++++++-- .../modules/mediaControls/PlayerControl.qml | 1 + 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml index 5235357e3..26dea2db8 100644 --- a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml +++ b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml @@ -18,6 +18,14 @@ ProgressBar { property real valueBarGap: 4 property color highlightColor: Appearance?.colors.colPrimary ?? "#685496" property color trackColor: Appearance?.m3colors.m3secondaryContainer ?? "#F1D3F9" + property bool sperm: false // If true, the progress bar will have a wavy fill effect + property real waveAmplitude: sperm ? 3 : 0 + property real frequency: 8 + property real spermFps: 60 + + Behavior on waveAmplitude { + animation: Appearance?.animation.elementMoveEnter.numberAnimation.createObject(this) + } Behavior on value { animation: Appearance?.animation.elementMoveEnter.numberAnimation.createObject(this) @@ -35,11 +43,49 @@ ProgressBar { implicitWidth: parent.width implicitHeight: parent.height - Rectangle { // Left progress fill - width: root.visualPosition * parent.width - height: parent.height - radius: Appearance?.rounding.full ?? 9999 - color: root.highlightColor + Canvas { + id: wavyFill + anchors { + left: parent.left + right: parent.right + verticalCenter: parent.verticalCenter + } + height: parent.height * 6 + onPaint: { + var ctx = getContext("2d"); + ctx.clearRect(0, 0, width, height); + + var progress = root.visualPosition; + var fillWidth = progress * width; + var amplitude = root.waveAmplitude + var frequency = root.frequency; + var phase = Date.now() / 400.0; + var centerY = height / 2; + + ctx.beginPath(); + for (var x = 0; x <= fillWidth; x += 1) { + var waveY = centerY + amplitude * Math.sin(frequency * 2 * Math.PI * x / width + phase); + if (x === 0) + ctx.moveTo(x, waveY); + else + ctx.lineTo(x, waveY); + } + ctx.strokeStyle = root.highlightColor; + ctx.lineWidth = parent.height; + ctx.lineCap = "round"; + ctx.stroke(); + } + Connections { + target: root + function onValueChanged() { wavyFill.requestPaint(); } + function onHighlightColorChanged() { wavyFill.requestPaint(); } + } + Timer { + interval: 1000 / root.spermFps + running: root.sperm + repeat: root.sperm + onTriggered: wavyFill.requestPaint() + } } Rectangle { // Right remaining part fill anchors.right: parent.right diff --git a/.config/quickshell/modules/mediaControls/PlayerControl.qml b/.config/quickshell/modules/mediaControls/PlayerControl.qml index 2100cc90c..cd3e782c5 100644 --- a/.config/quickshell/modules/mediaControls/PlayerControl.qml +++ b/.config/quickshell/modules/mediaControls/PlayerControl.qml @@ -259,6 +259,7 @@ Item { // Player instance highlightColor: blendedColors.colPrimary trackColor: blendedColors.colSecondaryContainer value: playerController.player?.position / playerController.player?.length + sperm: playerController.player?.isPlaying } } TrackChangeButton { From f76fe3726914bf8f3c62cf4f904f858f5a16e53f Mon Sep 17 00:00:00 2001 From: _xB <65196493+xBiei@users.noreply.github.com> Date: Thu, 12 Jun 2025 13:51:03 +0300 Subject: [PATCH 775/824] bar: re-add color picker button --- .../quickshell/modules/bar/UtilButtons.qml | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.config/quickshell/modules/bar/UtilButtons.qml b/.config/quickshell/modules/bar/UtilButtons.qml index 73b44a58d..6bc6be794 100644 --- a/.config/quickshell/modules/bar/UtilButtons.qml +++ b/.config/quickshell/modules/bar/UtilButtons.qml @@ -37,20 +37,20 @@ Rectangle { } } - // Loader { - // active: ConfigOptions.bar.utilButtons.showColorPicker - // sourceComponent: CircleUtilButton { - // Layout.alignment: Qt.AlignVCenter - // onClicked: Hyprland.dispatch("exec hyprpicker -a") - // MaterialSymbol { - // horizontalAlignment: Qt.AlignHCenter - // fill: 1 - // text: "colorize" - // iconSize: Appearance.font.pixelSize.large - // color: Appearance.colors.colOnLayer2 - // } - // } - // } + Loader { + active: ConfigOptions.bar.utilButtons.showColorPicker + sourceComponent: CircleUtilButton { + Layout.alignment: Qt.AlignVCenter + onClicked: Hyprland.dispatch("exec hyprpicker -a") + MaterialSymbol { + horizontalAlignment: Qt.AlignHCenter + fill: 1 + text: "colorize" + iconSize: Appearance.font.pixelSize.large + color: Appearance.colors.colOnLayer2 + } + } + } Loader { active: ConfigOptions.bar.utilButtons.showKeyboardToggle From 22fb505af907220f488746699aaf94ffcdf27f69 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 12 Jun 2025 12:51:28 +0200 Subject: [PATCH 776/824] progress bar: change sperm morphing anim curve --- .config/quickshell/modules/common/widgets/StyledProgressBar.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml index 26dea2db8..45505c4c0 100644 --- a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml +++ b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml @@ -24,7 +24,7 @@ ProgressBar { property real spermFps: 60 Behavior on waveAmplitude { - animation: Appearance?.animation.elementMoveEnter.numberAnimation.createObject(this) + animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on value { From 3767542bac3829db865da57d68f39f5436dd1fb4 Mon Sep 17 00:00:00 2001 From: _xB <65196493+xBiei@users.noreply.github.com> Date: Thu, 12 Jun 2025 13:51:48 +0300 Subject: [PATCH 777/824] bar: re-add color picker button config --- .config/quickshell/modules/common/ConfigOptions.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 842fe66ce..8eded0387 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -40,7 +40,7 @@ Singleton { property bool showMicToggle: true property bool showKeyboardToggle: true property bool showScreenSnip: true - // property bool showColorPicker: true + property bool showColorPicker: true } property QtObject workspaces: QtObject { property int shown: 10 From 30f3825f1dda873d16d067cf7cacc712dc62d9d5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 12 Jun 2025 15:24:54 +0200 Subject: [PATCH 778/824] progressbar: sperm: fix left end cutoff --- .../modules/common/widgets/StyledProgressBar.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml index 45505c4c0..991ee489d 100644 --- a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml +++ b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml @@ -62,17 +62,17 @@ ProgressBar { var phase = Date.now() / 400.0; var centerY = height / 2; + ctx.strokeStyle = root.highlightColor; + ctx.lineWidth = parent.height; + ctx.lineCap = "round"; ctx.beginPath(); - for (var x = 0; x <= fillWidth; x += 1) { + for (var x = ctx.lineWidth / 2; x <= fillWidth; x += 1) { var waveY = centerY + amplitude * Math.sin(frequency * 2 * Math.PI * x / width + phase); if (x === 0) ctx.moveTo(x, waveY); else ctx.lineTo(x, waveY); } - ctx.strokeStyle = root.highlightColor; - ctx.lineWidth = parent.height; - ctx.lineCap = "round"; ctx.stroke(); } Connections { From 4a9af7e7bd90f755332e87eaaebf55e2e9773d1d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 12 Jun 2025 15:27:20 +0200 Subject: [PATCH 779/824] translator: add no skibidi flag at right place --- .config/quickshell/modules/sidebarLeft/Translator.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/sidebarLeft/Translator.qml b/.config/quickshell/modules/sidebarLeft/Translator.qml index 7e20360b2..37e5a37b1 100644 --- a/.config/quickshell/modules/sidebarLeft/Translator.qml +++ b/.config/quickshell/modules/sidebarLeft/Translator.qml @@ -82,7 +82,7 @@ Item { Process { id: getLanguagesProc - command: ["trans", "-list-languages"] + command: ["trans", "-list-languages", "-no-bidi"] property list bufferList: ["auto"] running: true stdout: SplitParser { From 4c97434ccaa1823de5197ca1d047cb9cfd2c2e86 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 12 Jun 2025 15:33:27 +0200 Subject: [PATCH 780/824] util buttons: make buttons hide properly --- .config/quickshell/modules/bar/UtilButtons.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.config/quickshell/modules/bar/UtilButtons.qml b/.config/quickshell/modules/bar/UtilButtons.qml index 6bc6be794..a893fe1ad 100644 --- a/.config/quickshell/modules/bar/UtilButtons.qml +++ b/.config/quickshell/modules/bar/UtilButtons.qml @@ -24,6 +24,7 @@ Rectangle { Loader { active: ConfigOptions.bar.utilButtons.showScreenSnip + visible: ConfigOptions.bar.utilButtons.showScreenSnip sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Hyprland.dispatch("exec hyprshot --freeze --clipboard-only --mode region --silent") @@ -39,6 +40,7 @@ Rectangle { Loader { active: ConfigOptions.bar.utilButtons.showColorPicker + visible: ConfigOptions.bar.utilButtons.showColorPicker sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Hyprland.dispatch("exec hyprpicker -a") @@ -54,6 +56,7 @@ Rectangle { Loader { active: ConfigOptions.bar.utilButtons.showKeyboardToggle + visible: ConfigOptions.bar.utilButtons.showKeyboardToggle sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Hyprland.dispatch("global quickshell:oskToggle") @@ -69,6 +72,7 @@ Rectangle { Loader { active: ConfigOptions.bar.utilButtons.showMicToggle + visible: ConfigOptions.bar.utilButtons.showMicToggle sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Hyprland.dispatch("exec wpctl set-mute @DEFAULT_SOURCE@ toggle") From da8a8aec4ed07231d403b366929c1ad9ff60be55 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 12 Jun 2025 15:35:31 +0200 Subject: [PATCH 781/824] adjust default util button visibility --- .config/quickshell/modules/common/ConfigOptions.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 18d3b5874..339ed1509 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -46,10 +46,10 @@ Singleton { property bool alwaysShowCpu: false } property QtObject utilButtons: QtObject { - property bool showMicToggle: true - property bool showKeyboardToggle: true property bool showScreenSnip: true - property bool showColorPicker: true + property bool showColorPicker: false + property bool showMicToggle: false + property bool showKeyboardToggle: true } property QtObject workspaces: QtObject { property int shown: 10 From 925f82301abede782e7caeb2fac6921e6c7d8b9b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 15:45:35 +0200 Subject: [PATCH 782/824] fix newline before prompt (proper solution for#1397) --- .config/quickshell/scripts/terminal/sequences.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/scripts/terminal/sequences.txt b/.config/quickshell/scripts/terminal/sequences.txt index 0308e0b34..97459582e 100644 --- a/.config/quickshell/scripts/terminal/sequences.txt +++ b/.config/quickshell/scripts/terminal/sequences.txt @@ -1 +1 @@ -]4;0;#$term0 #\]1;0;#$term0 #\]4;1;#$term1 #\]4;2;#$term2 #\]4;3;#$term3 #\]4;4;#$term4 #\]4;5;#$term5 #\]4;6;#$term6 #\]4;7;#$term7 #\]4;8;#$term8 #\]4;9;#$term9 #\]4;10;#$term10 #\]4;11;#$term11 #\]4;12;#$term12 #\]4;13;#$term13 #\]4;14;#$term14 #\]4;15;#$term15 #\]10;#$term7 #\]11;[100]#$term0 #\]12;#$term7 #\]13;#$term7 #\]17;#$term7 #\]19;#$term0 #\]4;232;#$term7 #\]4;256;#$term7 #\]708;[100]#$term0 #\]11;#$term0 #\ +]4;0;#$term0 #\]1;0;#$term0 #\]4;1;#$term1 #\]4;2;#$term2 #\]4;3;#$term3 #\]4;4;#$term4 #\]4;5;#$term5 #\]4;6;#$term6 #\]4;7;#$term7 #\]4;8;#$term8 #\]4;9;#$term9 #\]4;10;#$term10 #\]4;11;#$term11 #\]4;12;#$term12 #\]4;13;#$term13 #\]4;14;#$term14 #\]4;15;#$term15 #\]10;#$term7 #\]11;[100]#$term0 #\]12;#$term7 #\]13;#$term7 #\]17;#$term7 #\]19;#$term0 #\]4;232;#$term7 #\]4;256;#$term7 #\]708;[100]#$term0 #\]11;#$term0 #\ \ No newline at end of file From 89ed9e2b522b0a18cac997f9fd9cf2618f51fe13 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 15:46:05 +0200 Subject: [PATCH 783/824] progress bar: make sperm amplitude follow the m3 specs --- .../modules/common/widgets/StyledProgressBar.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml index 991ee489d..31bce5915 100644 --- a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml +++ b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml @@ -19,11 +19,11 @@ ProgressBar { property color highlightColor: Appearance?.colors.colPrimary ?? "#685496" property color trackColor: Appearance?.m3colors.m3secondaryContainer ?? "#F1D3F9" property bool sperm: false // If true, the progress bar will have a wavy fill effect - property real waveAmplitude: sperm ? 3 : 0 - property real frequency: 8 + property real spermAmplitudeMultiplier: sperm ? 0.5 : 0 + property real spermFrequency: 6 property real spermFps: 60 - Behavior on waveAmplitude { + Behavior on spermAmplitudeMultiplier { animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } @@ -57,8 +57,8 @@ ProgressBar { var progress = root.visualPosition; var fillWidth = progress * width; - var amplitude = root.waveAmplitude - var frequency = root.frequency; + var amplitude = parent.height * root.spermAmplitudeMultiplier; + var frequency = root.spermFrequency; var phase = Date.now() / 400.0; var centerY = height / 2; From dc24541f616c8655b5bf73cf9588eaea7d9c7521 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 17:04:53 +0200 Subject: [PATCH 784/824] add transparency option (#1398) --- .config/quickshell/modules/common/Appearance.qml | 6 ++---- .config/quickshell/modules/common/ConfigOptions.qml | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 49e9576bd..79b3c7895 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -16,10 +16,8 @@ Singleton { property string syntaxHighlightingTheme // Extremely conservative transparency values for consistency and readability - property real transparency: 0 - property real contentTransparency: 0 - // property real transparency: m3colors.darkmode ? 0.05 : 0 - // property real contentTransparency: m3colors.darkmode ? 0.18 : 0 + property real transparency: ConfigOptions?.appearance.transparency ? (m3colors.darkmode ? 0.1 : 0) : 0 + property real contentTransparency: ConfigOptions?.appearance.transparency ? (m3colors.darkmode ? 0.55 : 0) : 0 m3colors: QtObject { property bool darkmode: false diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 339ed1509..b936ec220 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -10,6 +10,7 @@ Singleton { property QtObject appearance: QtObject { property int fakeScreenRounding: 2 // 0: None | 1: Always | 2: When not fullscreen + property bool transparency: false } property QtObject audio: QtObject { // Values in % From c882b162d292c7baef6d5de6636c8117d7bfefe9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 17:36:48 +0200 Subject: [PATCH 785/824] fix active window title not showing --- .config/quickshell/modules/bar/ActiveWindow.qml | 8 ++++---- .config/quickshell/modules/bar/Bar.qml | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/modules/bar/ActiveWindow.qml b/.config/quickshell/modules/bar/ActiveWindow.qml index 86018129b..95e25c6f2 100644 --- a/.config/quickshell/modules/bar/ActiveWindow.qml +++ b/.config/quickshell/modules/bar/ActiveWindow.qml @@ -6,12 +6,12 @@ import Quickshell.Wayland import Quickshell.Hyprland Item { + id: root required property var bar readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) readonly property Toplevel activeWindow: ToplevelManager.activeToplevel - height: parent.height - width: colLayout.width + implicitWidth: colLayout.implicitWidth ColumnLayout { id: colLayout @@ -26,7 +26,7 @@ Item { font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colSubtext elide: Text.ElideRight - text: activeWindow?.activated ? activeWindow?.appId : qsTr("Desktop") + text: root.activeWindow?.activated ? root.activeWindow?.appId : qsTr("Desktop") } StyledText { @@ -34,7 +34,7 @@ Item { font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnLayer0 elide: Text.ElideRight - text: activeWindow?.activated ? activeWindow?.title : `${qsTr("Workspace")} ${monitor.activeWorkspace?.id}` + text: root.activeWindow?.activated ? root.activeWindow?.title : `${qsTr("Workspace")} ${monitor.activeWorkspace?.id}` } } diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index e9efa4b1c..a46c696a1 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -176,9 +176,10 @@ Scope { } ActiveWindow { - visible: barRoot.useShortenedForm === 0 && width > 0 && height > 0 + visible: barRoot.useShortenedForm === 0 Layout.rightMargin: Appearance.rounding.screenRounding Layout.fillWidth: true + Layout.fillHeight: true bar: barRoot } } From 3011f77e8877ab043536d6583a99a20663af301d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 17:37:07 +0200 Subject: [PATCH 786/824] media controls: adjust colors --- .config/quickshell/modules/mediaControls/PlayerControl.qml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/mediaControls/PlayerControl.qml b/.config/quickshell/modules/mediaControls/PlayerControl.qml index cd3e782c5..cce0788db 100644 --- a/.config/quickshell/modules/mediaControls/PlayerControl.qml +++ b/.config/quickshell/modules/mediaControls/PlayerControl.qml @@ -90,8 +90,9 @@ Item { // Player instance rescaleSize: 1 // Rescale to 1x1 pixel for faster processing } + property bool backgroundIsDark: artDominantColor.hslLightness < 0.5 property QtObject blendedColors: QtObject { - property color colLayer0: ColorUtils.mix(Appearance.colors.colLayer0, artDominantColor, 0.5) + property color colLayer0: ColorUtils.mix(Appearance.colors.colLayer0, artDominantColor, (backgroundIsDark && Appearance.m3colors.darkmode) ? 0.6 : 0.5) property color colLayer1: ColorUtils.mix(Appearance.colors.colLayer1, artDominantColor, 0.5) property color colOnLayer0: ColorUtils.mix(Appearance.colors.colOnLayer0, artDominantColor, 0.5) property color colOnLayer1: ColorUtils.mix(Appearance.colors.colOnLayer1, artDominantColor, 0.5) @@ -99,11 +100,11 @@ Item { // Player instance property color colPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimary, artDominantColor), artDominantColor, 0.5) property color colPrimaryHover: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryHover, artDominantColor), artDominantColor, 0.3) property color colPrimaryActive: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryActive, artDominantColor), artDominantColor, 0.3) - property color colSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, artDominantColor, 0.3) + property color colSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, artDominantColor, 0.15) property color colSecondaryContainerHover: ColorUtils.mix(Appearance.colors.colSecondaryContainerHover, artDominantColor, 0.3) property color colSecondaryContainerActive: ColorUtils.mix(Appearance.colors.colSecondaryContainerActive, artDominantColor, 0.5) property color colOnPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.m3colors.m3onPrimary, artDominantColor), artDominantColor, 0.5) - property color colOnSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3onSecondaryContainer, artDominantColor, 0.2) + property color colOnSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3onSecondaryContainer, artDominantColor, 0.5) } From 1cdb124fd4e11880aa98c37a96f050d975321fcb Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 18:19:59 +0200 Subject: [PATCH 787/824] keybinds: prevent multiple overlaying screen snips --- .config/hypr/hyprland/keybinds.conf | 4 ++-- .config/hypr/hyprland/rules.conf | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index db554af57..3c6040140 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -54,8 +54,8 @@ bindd = Ctrl+Shift+Alt+Super, Delete, Shutdown, exec, systemctl poweroff || logi # Screenshot, Record, OCR, Color picker, Clipboard history bindd = Super, V, Copy clipboard history entry, exec, qs ipc call TEST_ALIVE || pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # [hidden] Clipboard history >> clipboard (fallback) bindd = Super, Period, Copy an emoji, exec, qs ipc call TEST_ALIVE || pkill fuzzel || ~/.config/hypr/hyprland/scripts/fuzzel-emoji.sh copy # [hidden] Emoji >> clipboard (fallback) -bindd = Super+Shift, S, Screen snip, exec, hyprshot --freeze --clipboard-only --mode region --silent # Screen snip >> clipboard -bindd = Super+Shift+Alt, S, Screen snip and annotate, exec, grim -g "$(slurp)" - | swappy -f - # Screen snip and annotate +bindd = Super+Shift, S, Screen snip, exec, pidof slurp || hyprshot --freeze --clipboard-only --mode region --silent # Screen snip >> clipboard +bindd = Super+Shift+Alt, S, Screen snip and annotate, exec, pidof slurp || grim -g "$(slurp)" - | swappy -f - # Screen snip and annotate # OCR bindd = Super+Shift, T, Character recognition,exec,grim -g "$(slurp $SLURP_ARGS)" "tmp.png" && tesseract "tmp.png" - | wl-copy && rm "tmp.png" # [hidden] # Color picker diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index 42897cafe..be0abacc4 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -21,6 +21,7 @@ windowrulev2 = size 45%, class:^(nm-connection-editor)$ windowrulev2 = center, class:^(nm-connection-editor)$ windowrulev2 = float, class:.*plasmawindowed.* windowrulev2 = float, class:kcm_.* +windowrulev2 = float, class:.*bluedevil.* # No appearance # kde-material-you-colors spawns a window when changing dark/light theme. This is to make sure it doesn't interfere at all. @@ -112,7 +113,9 @@ layerrule = blur, osk[0-9]* layerrule = ignorealpha 0.6, osk[0-9]* # Quickshell -## My stuff +layerrule = blurpopups, quickshell:.* +layerrule = blur, quickshell:.* +layerrule = ignorealpha 0.79, quickshell:.* layerrule = animation slide, quickshell:bar layerrule = animation fade, quickshell:screenCorners layerrule = animation slide right, quickshell:sidebarRight @@ -121,14 +124,11 @@ layerrule = animation slide bottom, quickshell:osk layerrule = animation slide bottom, quickshell:dock layerrule = blur, quickshell:session layerrule = noanim, quickshell:session +layerrule = ignorealpha 0, quickshell:session layerrule = animation fade, quickshell:notificationPopup layerrule = blur, quickshell:backgroundWidgets layerrule = ignorealpha 0.05, quickshell:backgroundWidgets -# layerrule = blurpopups, quickshell:.* -# layerrule = blur, quickshell:.* -# layerrule = ignorealpha 0.79, quickshell:.* - # Launchers need to be FAST layerrule = noanim, quickshell:overview From cb6021602e0c49ae3db2b40228d41784e7f31af8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 18:20:19 +0200 Subject: [PATCH 788/824] keybinds: fix ctrl+super opening overview --- .config/hypr/hyprland/keybinds.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 3c6040140..b1ca5589a 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -7,6 +7,7 @@ bindid = Super, Super_L, Toggle overview, global, quickshell:overviewToggleRelease # Toggle overview/launcher bind = Super, Super_L, exec, qs ipc call TEST_ALIVE || pkill fuzzel || fuzzel # [hidden] Launcher (fallback) binditn = Super, catchall, global, quickshell:overviewToggleReleaseInterrupt # [hidden] +bind = Ctrl, Super_L, global, quickshell:overviewToggleReleaseInterrupt # [hidden] bind = Super, mouse:272, global, quickshell:overviewToggleReleaseInterrupt # [hidden] bind = Super, mouse:273, global, quickshell:overviewToggleReleaseInterrupt # [hidden] bind = Super, mouse:274, global, quickshell:overviewToggleReleaseInterrupt # [hidden] From b1729eed2774630c8464b42b75f369a641758934 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 18:22:47 +0200 Subject: [PATCH 789/824] fix weird material symbols --- .config/quickshell/modules/common/widgets/MaterialSymbol.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml index 450a856db..aac0b0315 100644 --- a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml +++ b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml @@ -7,7 +7,7 @@ Text { property real iconSize: Appearance?.font.pixelSize.small ?? 16 property real fill: 0 property real truncatedFill: Math.round(fill * 100) / 100 // Reduce memory consumption spikes from constant font remapping - renderType: Text.CurveRendering + renderType: Text.NativeRendering font { hintingPreference: Font.PreferFullHinting family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded" From be5b932beb6894e9dcb3f2169f4645732a07cc6f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 18:23:02 +0200 Subject: [PATCH 790/824] bar: workspaces: show dots only by default --- .config/quickshell/modules/bar/Workspaces.qml | 42 ++++++++++++++----- .../modules/common/ConfigOptions.qml | 1 + 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index cb3c6551a..96e3e4519 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -194,8 +194,11 @@ Item { } property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing") - StyledText { - opacity: (ConfigOptions.bar.workspaces.alwaysShowNumbers || GlobalStates.workspaceShowNumbers || !workspaceButtonBackground.biggestWindow) ? 1 : 0 + StyledText { // Workspace number text + opacity: GlobalStates.workspaceShowNumbers + || ((ConfigOptions?.bar.workspaces.alwaysShowNumbers && (!ConfigOptions?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || GlobalStates.workspaceShowNumbers)) + || (GlobalStates.workspaceShowNumbers && !ConfigOptions?.bar.workspaces.showAppIcons) + ) ? 1 : 0 z: 3 anchors.centerIn: parent @@ -212,26 +215,45 @@ Item { Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } - } - Item { + Rectangle { // Dot instead of ws number + opacity: (ConfigOptions?.bar.workspaces.alwaysShowNumbers + || GlobalStates.workspaceShowNumbers + || (ConfigOptions?.bar.workspaces.showAppIcons && workspaceButtonBackground.biggestWindow) + ) ? 0 : 1 + visible: opacity > 0 + anchors.centerIn: parent + width: workspaceButtonWidth * 0.15 + height: width + radius: width / 2 + color: (monitor.activeWorkspace?.id == button.workspaceValue) ? + Appearance.m3colors.m3onPrimary : + (workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer : + Appearance.colors.colOnLayer1Inactive) + + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + } + Item { // Main app icon anchors.centerIn: parent width: workspaceButtonWidth height: workspaceButtonWidth + opacity: !ConfigOptions?.bar.workspaces.showAppIcons ? 0 : + (workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ? + 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 + visible: opacity > 0 IconImage { id: mainAppIcon anchors.bottom: parent.bottom anchors.right: parent.right - anchors.bottomMargin: (!GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? + anchors.bottomMargin: (!GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ? (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked - anchors.rightMargin: (!GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? + anchors.rightMargin: (!GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ? (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked - opacity: (workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? - 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 - visible: opacity > 0 source: workspaceButtonBackground.mainAppIconSource - implicitSize: (!GlobalStates.workspaceShowNumbers && !ConfigOptions.bar.workspaces.alwaysShowNumbers) ? workspaceIconSize : workspaceIconSizeShrinked + implicitSize: (!GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index b936ec220..590677457 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -54,6 +54,7 @@ Singleton { } property QtObject workspaces: QtObject { property int shown: 10 + property bool showAppIcons: true property bool alwaysShowNumbers: false property int showNumberDelay: 150 // milliseconds } From 753df0b48c22a8f0680213acfee054ba3f6b2a3d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 20:33:36 +0200 Subject: [PATCH 791/824] fish: make clear actually clear in kitty --- .config/fish/config.fish | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/fish/config.fish b/.config/fish/config.fish index 3ddb607e5..4a86ee4f8 100755 --- a/.config/fish/config.fish +++ b/.config/fish/config.fish @@ -19,6 +19,7 @@ end alias pamcan pacman alias ls 'eza --icons' +alias clear "printf '\033[2J\033[3J\033[1;1H'" # function fish_prompt From 1a0df48ac3739a72c0ad8861410b49d4ea7b0b3d Mon Sep 17 00:00:00 2001 From: _xB <65196493+xBiei@users.noreply.github.com> Date: Fri, 13 Jun 2025 21:38:33 +0300 Subject: [PATCH 792/824] bar: Use ScriptModel to only update related screens --- .config/quickshell/modules/bar/Bar.qml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index eb171bbf7..9c98e26e7 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -28,15 +28,19 @@ Scope { // Check screensList from config, If no screens are specified, show on all screens - property var filteredScreens: { - const list = ConfigOptions.bar.screensList; - if (!list || list.length === 0) - return Quickshell.screens; - return Quickshell.screens.filter(screen => list.includes(screen.name)); + ScriptModel { + id: screensModel + values: { + const screens = Quickshell.screens; + const list = ConfigOptions.bar.screensList; + if (!list || list.length === 0) + return screens; + return screens.filter(screen => list.includes(screen.name)); + } } Variants { // For each monitor - model: bar.filteredScreens + model: screensModel.values PanelWindow { // Bar window id: barRoot From d70a117270f88358565acfe00ab8c944dfa7cabf Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 20:39:09 +0200 Subject: [PATCH 793/824] bar: make groups cleaner --- .config/quickshell/modules/bar/Bar.qml | 24 ++++++------ .config/quickshell/modules/bar/BarGroup.qml | 37 +++++++++++++++++++ .../modules/bar/BatteryIndicator.qml | 4 +- .../quickshell/modules/bar/ClockWidget.qml | 16 ++++---- .config/quickshell/modules/bar/Media.qml | 8 ---- .config/quickshell/modules/bar/Resource.qml | 1 + .config/quickshell/modules/bar/Resources.qml | 4 +- .../quickshell/modules/bar/UtilButtons.qml | 6 +-- .config/quickshell/modules/bar/Workspaces.qml | 13 +------ .../quickshell/modules/common/Appearance.qml | 2 +- .../modules/common/ConfigOptions.qml | 1 + .../modules/overview/OverviewWidget.qml | 4 +- 12 files changed, 64 insertions(+), 56 deletions(-) create mode 100644 .config/quickshell/modules/bar/BarGroup.qml diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index a46c696a1..5528f9b25 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -191,12 +191,10 @@ Scope { anchors.centerIn: parent spacing: ConfigOptions?.bar.borderless ? 4 : 8 - RowLayout { + BarGroup { id: leftCenterGroup Layout.preferredWidth: barRoot.centerSideModuleWidth - spacing: 4 Layout.fillHeight: true - implicitWidth: 350 Resources { alwaysShowAllResources: barRoot.useShortenedForm === 2 @@ -212,12 +210,15 @@ Scope { VerticalBarSeparator {visible: ConfigOptions?.bar.borderless} - RowLayout { + BarGroup { id: middleCenterGroup + padding: workspacesWidget.widgetPadding Layout.fillHeight: true - + Workspaces { + id: workspacesWidget bar: barRoot + Layout.fillHeight: true MouseArea { // Right-click to toggle overview anchors.fill: parent acceptedButtons: Qt.RightButton @@ -229,25 +230,23 @@ Scope { } } } - } VerticalBarSeparator {visible: ConfigOptions?.bar.borderless} - RowLayout { + BarGroup { id: rightCenterGroup - Layout.preferredWidth: leftCenterGroup.width + Layout.preferredWidth: barRoot.centerSideModuleWidth Layout.fillHeight: true - spacing: 4 - + ClockWidget { - showDate: barRoot.useShortenedForm < 2 + showDate: (ConfigOptions.bar.verbose && barRoot.useShortenedForm < 2) Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true } UtilButtons { - visible: barRoot.useShortenedForm === 0 + visible: (ConfigOptions.bar.verbosebarRoot.useShortenedForm === 0) Layout.alignment: Qt.AlignVCenter } @@ -255,7 +254,6 @@ Scope { visible: (barRoot.useShortenedForm < 2 && UPower.displayDevice.isLaptopBattery) Layout.alignment: Qt.AlignVCenter } - } } diff --git a/.config/quickshell/modules/bar/BarGroup.qml b/.config/quickshell/modules/bar/BarGroup.qml new file mode 100644 index 000000000..a71b67e46 --- /dev/null +++ b/.config/quickshell/modules/bar/BarGroup.qml @@ -0,0 +1,37 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import QtQuick +import QtQuick.Layouts + +Item { + id: root + property real padding: 5 + implicitHeight: 40 + implicitWidth: rowLayout.implicitWidth + padding * 2 + default property alias items: rowLayout.children + + Rectangle { + id: background + anchors { + fill: parent + topMargin: 4 + bottomMargin: 4 + } + color: ConfigOptions?.bar.borderless ? "transparent" : Appearance.colors.colLayer1 + radius: Appearance.rounding.small + } + + RowLayout { + id: rowLayout + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + right: parent.right + leftMargin: root.padding + rightMargin: root.padding + } + spacing: 4 + + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/bar/BatteryIndicator.qml b/.config/quickshell/modules/bar/BatteryIndicator.qml index 271cd8c28..61a981575 100644 --- a/.config/quickshell/modules/bar/BatteryIndicator.qml +++ b/.config/quickshell/modules/bar/BatteryIndicator.qml @@ -7,7 +7,7 @@ import Quickshell import Quickshell.Io import Quickshell.Services.UPower -Rectangle { +Item { id: root property bool borderless: ConfigOptions.bar.borderless readonly property var chargeState: Battery.chargeState @@ -20,8 +20,6 @@ Rectangle { implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitHeight: 32 - color: borderless ? "transparent" : Appearance.colors.colLayer1 - radius: Appearance.rounding.small RowLayout { id: rowLayout diff --git a/.config/quickshell/modules/bar/ClockWidget.qml b/.config/quickshell/modules/bar/ClockWidget.qml index 598122c0b..9e3403838 100644 --- a/.config/quickshell/modules/bar/ClockWidget.qml +++ b/.config/quickshell/modules/bar/ClockWidget.qml @@ -4,19 +4,17 @@ import "root:/services" import QtQuick import QtQuick.Layouts -Rectangle { +Item { + id: root property bool borderless: ConfigOptions.bar.borderless - property bool showDate: true - implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 6 // idk, text seems nicer w/ more padding + property bool showDate: ConfigOptions.bar.verbose + implicitWidth: rowLayout.implicitWidth implicitHeight: 32 - color: borderless ? "transparent" : Appearance.colors.colLayer1 - radius: Appearance.rounding.small RowLayout { id: rowLayout - - spacing: 4 anchors.centerIn: parent + spacing: 4 StyledText { font.pixelSize: Appearance.font.pixelSize.large @@ -25,14 +23,14 @@ Rectangle { } StyledText { - visible: showDate + visible: root.showDate font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnLayer1 text: "•" } StyledText { - visible: showDate + visible: root.showDate font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnLayer1 text: DateTime.date diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml index d105ea203..3bd8a78a4 100644 --- a/.config/quickshell/modules/bar/Media.qml +++ b/.config/quickshell/modules/bar/Media.qml @@ -42,14 +42,6 @@ Item { } } - Rectangle { // Background - anchors.centerIn: parent - width: parent.width - implicitHeight: 32 - color: borderless ? "transparent" : Appearance.colors.colLayer1 - radius: Appearance.rounding.small - } - RowLayout { // Real content id: rowLayout diff --git a/.config/quickshell/modules/bar/Resource.qml b/.config/quickshell/modules/bar/Resource.qml index 2b2dd0b60..fed7a153c 100644 --- a/.config/quickshell/modules/bar/Resource.qml +++ b/.config/quickshell/modules/bar/Resource.qml @@ -10,6 +10,7 @@ Item { required property double percentage property bool shown: true clip: true + visible: width > 0 && height > 0 implicitWidth: resourceRowLayout.x < 0 ? 0 : childrenRect.width implicitHeight: childrenRect.height diff --git a/.config/quickshell/modules/bar/Resources.qml b/.config/quickshell/modules/bar/Resources.qml index 537a06c09..9f9b969ca 100644 --- a/.config/quickshell/modules/bar/Resources.qml +++ b/.config/quickshell/modules/bar/Resources.qml @@ -7,14 +7,12 @@ import Quickshell import Quickshell.Io import Quickshell.Services.Mpris -Rectangle { +Item { id: root property bool borderless: ConfigOptions.bar.borderless property bool alwaysShowAllResources: false implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin implicitHeight: 32 - color: borderless ? "transparent" : Appearance.colors.colLayer1 - radius: Appearance.rounding.small RowLayout { id: rowLayout diff --git a/.config/quickshell/modules/bar/UtilButtons.qml b/.config/quickshell/modules/bar/UtilButtons.qml index a893fe1ad..9d9832a0a 100644 --- a/.config/quickshell/modules/bar/UtilButtons.qml +++ b/.config/quickshell/modules/bar/UtilButtons.qml @@ -7,14 +7,10 @@ import Quickshell.Io import Quickshell.Hyprland import Quickshell.Services.Pipewire -Rectangle { +Item { id: root property bool borderless: ConfigOptions.bar.borderless - Layout.alignment: Qt.AlignVCenter implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 - implicitHeight: 32 - color: borderless ? "transparent" : Appearance.colors.colLayer1 - radius: Appearance.rounding.small RowLayout { id: rowLayout diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 96e3e4519..93de99c0f 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -47,20 +47,9 @@ Item { } } - Layout.fillHeight: true implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitHeight: 40 - // Background - Rectangle { - z: 0 - anchors.centerIn: parent - implicitHeight: 32 - implicitWidth: rowLayout.implicitWidth + widgetPadding * 2 - radius: Appearance.rounding.small - color: borderless ? "transparent" : Appearance.colors.colLayer1 - } - // Scroll to switch workspaces WheelHandler { onWheel: (event) => { @@ -223,7 +212,7 @@ Item { ) ? 0 : 1 visible: opacity > 0 anchors.centerIn: parent - width: workspaceButtonWidth * 0.15 + width: workspaceButtonWidth * 0.18 height: width radius: width / 2 color: (monitor.activeWorkspace?.id == button.workspaceValue) ? diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 79b3c7895..7622b29a4 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -281,7 +281,7 @@ Singleton { sizes: QtObject { property real barHeight: 40 - property real barCenterSideModuleWidth: 360 + property real barCenterSideModuleWidth: ConfigOptions?.bar.verbose ? 360 : 140 property real barCenterSideModuleWidthShortened: 280 property real barCenterSideModuleWidthHellaShortened: 190 property real barShortenScreenWidthThreshold: 1200 // Shorten if screen width is at most this value diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 590677457..ee402428e 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -42,6 +42,7 @@ Singleton { property bool borderless: false // true for no grouping of items property string topLeftIcon: "spark" // Options: distro, spark property bool showBackground: true + property bool verbose: true property QtObject resources: QtObject { property bool alwaysShowSwap: true property bool alwaysShowCpu: false diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index 9ab39fd02..e0999d6e3 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -185,7 +185,7 @@ Item { onTriggered: { window.x = Math.round(Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset) window.y = Math.round(Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset) - console.log(`[OverviewWindow] Updated position for window ${windowData?.address} to (${window.x}, ${window.y})`) + // console.log(`[OverviewWindow] Updated position for window ${windowData?.address} to (${window.x}, ${window.y})`) } } @@ -205,7 +205,7 @@ Item { window.pressed = true window.Drag.active = true window.Drag.source = window - console.log(`[OverviewWindow] Dragging window ${windowData?.address} from position (${window.x}, ${window.y})`) + // console.log(`[OverviewWindow] Dragging window ${windowData?.address} from position (${window.x}, ${window.y})`) } onReleased: { const targetWorkspace = root.draggingTargetWorkspace From efb2f3fee503f3c8e73c6cc3cb81b302fa0d922d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 21:36:05 +0200 Subject: [PATCH 794/824] install script: notify about ags version --- install.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/install.sh b/install.sh index 7b47af0d4..afc8b516b 100755 --- a/install.sh +++ b/install.sh @@ -15,6 +15,10 @@ prevent_sudo_or_root startask () { printf "\e[34m[$0]: Hi there! Before we start:\n" + printf '\n' + printf '[NEW] illogical-impulse is now powered by Quickshell. If you were using the AGS version and would like to keep it, do not run this script.\n' + printf ' For the AGS version, run the script in its branch instead: git checkout ii-ags && ./install.sh\n' + printf '\n' printf 'This script 1. only works for ArchLinux and Arch-based distros.\n' printf ' 2. does not handle system-level/hardware stuff like Nvidia drivers\n' printf "\e[31m" From 4d6deffd888644b3d41a41f885cf31213b961bd9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 21:45:08 +0200 Subject: [PATCH 795/824] install script: update ags version notice --- install.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index afc8b516b..5d0588810 100755 --- a/install.sh +++ b/install.sh @@ -16,8 +16,9 @@ prevent_sudo_or_root startask () { printf "\e[34m[$0]: Hi there! Before we start:\n" printf '\n' - printf '[NEW] illogical-impulse is now powered by Quickshell. If you were using the AGS version and would like to keep it, do not run this script.\n' - printf ' For the AGS version, run the script in its branch instead: git checkout ii-ags && ./install.sh\n' + printf '[NEW] illogical-impulse is now powered by Quickshell. If you were using the old AGS version and would like to keep it, do not run this script.\n' + printf ' The AGS version, although uses less memory, has much worse performance. If you do not need (inconsistent) translations, the Quickshell version is recommended.\n' + printf ' If you would like it anyway, run the script in its branch instead: git checkout ii-ags && ./install.sh\n' printf '\n' printf 'This script 1. only works for ArchLinux and Arch-based distros.\n' printf ' 2. does not handle system-level/hardware stuff like Nvidia drivers\n' From 8a0204a279f7b645f80dbb3a2ba22aabd2766f2a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 22:27:39 +0200 Subject: [PATCH 796/824] bar: add the missing '&&' --- .config/quickshell/modules/bar/Bar.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 5528f9b25..869c0bd54 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -246,7 +246,7 @@ Scope { } UtilButtons { - visible: (ConfigOptions.bar.verbosebarRoot.useShortenedForm === 0) + visible: (ConfigOptions.bar.verbose && barRoot.useShortenedForm === 0) Layout.alignment: Qt.AlignVCenter } From 2b384330fcd91b6397b7aeab8765c8e58c10d354 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 22:27:58 +0200 Subject: [PATCH 797/824] bar: increase ws number show delay --- .config/quickshell/modules/common/ConfigOptions.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index ee402428e..747a1376b 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -57,7 +57,7 @@ Singleton { property int shown: 10 property bool showAppIcons: true property bool alwaysShowNumbers: false - property int showNumberDelay: 150 // milliseconds + property int showNumberDelay: 300 // milliseconds } } From e72405c754a34d623afe4532664dec7b2a2950dd Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 22:28:08 +0200 Subject: [PATCH 798/824] revealer: fix visibility --- .config/quickshell/modules/common/widgets/Revealer.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/widgets/Revealer.qml b/.config/quickshell/modules/common/widgets/Revealer.qml index f3d438b55..2fb5dc3a8 100644 --- a/.config/quickshell/modules/common/widgets/Revealer.qml +++ b/.config/quickshell/modules/common/widgets/Revealer.qml @@ -13,7 +13,7 @@ Item { implicitWidth: (reveal || vertical) ? childrenRect.width : 0 implicitHeight: (reveal || !vertical) ? childrenRect.height : 0 - visible: width > 0 && height > 0 + visible: reveal && width > 0 && height > 0 Behavior on implicitWidth { enabled: !vertical From 975c469e1a371b78d580174c3e0c948ad448f0c9 Mon Sep 17 00:00:00 2001 From: _xB <65196493+xBiei@users.noreply.github.com> Date: Fri, 13 Jun 2025 23:29:17 +0300 Subject: [PATCH 799/824] notifications: re-add support for HTML tags --- .../modules/common/widgets/NotificationItem.qml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationItem.qml b/.config/quickshell/modules/common/widgets/NotificationItem.qml index 33e901f62..47e7cdee2 100644 --- a/.config/quickshell/modules/common/widgets/NotificationItem.qml +++ b/.config/quickshell/modules/common/widgets/NotificationItem.qml @@ -41,13 +41,11 @@ Item { // Notification item area // Handle Brave/Chrome notifications - remove first line if (appName && appName.toLowerCase().includes('brave')) { const lines = body.split('\n\n') - if (lines.length > 1) { + if (lines.length > 1 && lines[0].startsWith(']*>/g, '') return processedBody } @@ -190,8 +188,7 @@ Item { // Notification item area elide: Text.ElideRight textFormat: Text.StyledText text: { - const processedBody = processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary) - return processedBody.replace(/img{max-width:${notificationBodyText.width}px;}` + - `${processedBody.replace(/\n/g, "
")}` + `${processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\n/g, "
")}` } onLinkActivated: (link) => { From 2e472450c588f033962aa50f7384677bfe69a48e Mon Sep 17 00:00:00 2001 From: _xB <65196493+xBiei@users.noreply.github.com> Date: Fri, 13 Jun 2025 23:45:18 +0300 Subject: [PATCH 800/824] notifications: support popular chromium-based browsers --- .../common/widgets/NotificationItem.qml | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationItem.qml b/.config/quickshell/modules/common/widgets/NotificationItem.qml index 47e7cdee2..4622fcde3 100644 --- a/.config/quickshell/modules/common/widgets/NotificationItem.qml +++ b/.config/quickshell/modules/common/widgets/NotificationItem.qml @@ -38,11 +38,19 @@ Item { // Notification item area function processNotificationBody(body, appName) { let processedBody = body - // Handle Brave/Chrome notifications - remove first line - if (appName && appName.toLowerCase().includes('brave')) { - const lines = body.split('\n\n') - if (lines.length > 1 && lines[0].startsWith(' lowerApp.includes(name))) { + const lines = body.split('\n\n') + + if (lines.length > 1 && lines[0].startsWith('") } } } From 50e69c2fa536bc76a7874078e75aa1b98190d566 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 22:51:50 +0200 Subject: [PATCH 801/824] bar: fix screen filtering --- .config/quickshell/modules/bar/Bar.qml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 9c98e26e7..fa66de5b2 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -26,21 +26,14 @@ Scope { implicitWidth: 1 color: Appearance.colors.colOutlineVariant - - // Check screensList from config, If no screens are specified, show on all screens - ScriptModel { - id: screensModel - values: { + Variants { // For each monitor + model: { const screens = Quickshell.screens; const list = ConfigOptions.bar.screensList; if (!list || list.length === 0) return screens; return screens.filter(screen => list.includes(screen.name)); } - } - - Variants { // For each monitor - model: screensModel.values PanelWindow { // Bar window id: barRoot From 62909dedfab203a4ec2c1740d1c028fb10dcb458 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 22:52:18 +0200 Subject: [PATCH 802/824] configoptions: bar: empty screen list by default --- .config/quickshell/modules/common/ConfigOptions.qml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 527412737..e5bd45413 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -45,10 +45,7 @@ Singleton { property bool alwaysShowSwap: true property bool alwaysShowCpu: false } - property list screensList: [ - "HDMI-A-1", - "DP-1" - ] + property list screensList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command property QtObject utilButtons: QtObject { property bool showScreenSnip: true property bool showColorPicker: false From 0b49b731520427fa63e13db27557c8f3b8cc08e7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 22:54:00 +0200 Subject: [PATCH 803/824] config options: bar: rename screensList -> screenList --- .config/quickshell/modules/common/ConfigOptions.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index e5bd45413..ca73f1f42 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -45,7 +45,7 @@ Singleton { property bool alwaysShowSwap: true property bool alwaysShowCpu: false } - property list screensList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command + property list screenList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command property QtObject utilButtons: QtObject { property bool showScreenSnip: true property bool showColorPicker: false From 0ad98583792f176fd13bfff1e27d487617bfe704 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 22:54:24 +0200 Subject: [PATCH 804/824] bar: screensList -> screenList --- .config/quickshell/modules/bar/Bar.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index fa66de5b2..8615c4cf3 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -29,7 +29,7 @@ Scope { Variants { // For each monitor model: { const screens = Quickshell.screens; - const list = ConfigOptions.bar.screensList; + const list = ConfigOptions.bar.screenList; if (!list || list.length === 0) return screens; return screens.filter(screen => list.includes(screen.name)); From c96f2f5eb4244dcf8621297d6c4fcf7408a976e9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 23:00:59 +0200 Subject: [PATCH 805/824] notif filter: remove redundant "edge" --- .config/quickshell/modules/common/widgets/NotificationItem.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/widgets/NotificationItem.qml b/.config/quickshell/modules/common/widgets/NotificationItem.qml index 4622fcde3..d1cf51351 100644 --- a/.config/quickshell/modules/common/widgets/NotificationItem.qml +++ b/.config/quickshell/modules/common/widgets/NotificationItem.qml @@ -42,7 +42,7 @@ Item { // Notification item area if (appName) { const lowerApp = appName.toLowerCase() const chromiumBrowsers = [ - "brave", "chrome", "chromium", "vivaldi", "opera", "microsoft edge", "edge" + "brave", "chrome", "chromium", "vivaldi", "opera", "microsoft edge" ] if (chromiumBrowsers.some(name => lowerApp.includes(name))) { From 5fb1efa8bb51411fe6a50b0e4a3818591e773fea Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 23:13:24 +0200 Subject: [PATCH 806/824] bar: fix bracket --- .config/quickshell/modules/bar/Bar.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 263106a42..9fb9b32ed 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -25,6 +25,7 @@ Scope { Layout.fillHeight: true implicitWidth: 1 color: Appearance.colors.colOutlineVariant + } Variants { // For each monitor model: { From 46692d4bbb6419baafa294317d8c6666a220902a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 23:16:26 +0200 Subject: [PATCH 807/824] Update README.md --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index 7b8035e72..1f40d4a1b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,3 @@ -# Quickshell-powered illogical-impulse -## Current status - -It's ready if you don't need localization... so quite likely - -## Instructions - -- **Installation**: Clone the repo, checkout this branch and run `install.sh` -- **Dolphin fix** so it won't ask which program to open file with every time: `sudo pacman -S archlinux-xdg-menu && XDG_MENU_PREFIX=arch- kbuildsycoca6; sudo ln -s /etc/xdg/menus/plasma-applications.menu /etc/xdg/menus/applications.menu` -- TODO: Update install script to include the above fix -

【 end_4's Hyprland dotfiles 】

From 1af3f6f4853c3cd6ee3b9a69cdd6dfc38fbb16d8 Mon Sep 17 00:00:00 2001 From: _xB <65196493+xBiei@users.noreply.github.com> Date: Sat, 14 Jun 2025 00:27:06 +0300 Subject: [PATCH 808/824] unneeded stuff --- README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.md b/README.md index 1f40d4a1b..4c2062447 100644 --- a/README.md +++ b/README.md @@ -64,13 +64,6 @@ - For a more comprehensive list of dependencies, see [scriptdata/dependencies.conf](https://github.com/end-4/dots-hyprland/blob/main/scriptdata/dependencies.conf) -
- Help improve these dotfiles - - - You can give feedback/suggestions for the [`ii-qs` branch](https://github.com/end-4/dots-hyprland/tree/ii-qs) in [#1276](https://github.com/end-4/dots-hyprland/pull/1276) - -
-

• screenshots •

From fba79bdd21ab7fdd1f32fe74c70f32a1886a39d3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 23:44:48 +0200 Subject: [PATCH 809/824] readme: no more fuzzel in main stuff list --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c2062447..edfefa863 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ | ------------- | ------------- | | [Hyprland](https://github.com/hyprwm/hyprland) | The compositor (for noobs, you can just call it a window manager) | | [Quickshell](https://quickshell.outfoxxed.me/) | A QtQuick-based widget system, responsible for the status bar, sidebars, etc. | - | [Fuzzel](https://mark.stosberg.com/fuzzel-a-great-dmenu-and-rofi-altenrative-for-wayland/) | For clipboard and emoji picker | + - For a more comprehensive list of dependencies, see [scriptdata/dependencies.conf](https://github.com/end-4/dots-hyprland/blob/main/scriptdata/dependencies.conf) From 590f25aeff505afe4a1d7cf2708b3266ffcaea37 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 13 Jun 2025 23:50:16 +0200 Subject: [PATCH 810/824] readme: add thanks fox --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index edfefa863..a5d437d0d 100644 --- a/README.md +++ b/README.md @@ -120,8 +120,9 @@ By the way...

- - [@clsty](https://github.com/clsty) for making an actually good install script + many other stuff that I neglect + - [@clsty](https://github.com/clsty) for making my work accessible by taking care of the install script and many other things - [@midn8hustlr](https://github.com/midn8hustlr) for greatly improving the color generation system + - [@outfoxxed](https://github.com/outfoxxed/) for being extremely supportive in my Quickshell journey - Quickshell: [Soramane](https://github.com/caelestia-dots/shell/), [FridayFaerie](https://github.com/FridayFaerie/quickshell), [nydragon](https://github.com/nydragon/nysh) - AGS: [Aylur's config](https://github.com/Aylur/dotfiles/tree/ags-pre-ts), [kotontrion's config](https://github.com/kotontrion/dotfiles) - EWW: [fufexan's config](https://github.com/fufexan/dotfiles) (he thanks more people there btw) From ec14e36be3a15750a2ecc8335c1f7c4540ae38f3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 14 Jun 2025 00:03:58 +0200 Subject: [PATCH 811/824] installation: include dolphin app nag fix --- install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/install.sh b/install.sh index 5d0588810..4d9742ffe 100755 --- a/install.sh +++ b/install.sh @@ -148,6 +148,7 @@ v systemctl --user enable ydotool --now v sudo systemctl enable bluetooth --now v gsettings set org.gnome.desktop.interface font-name 'Rubik 11' v gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' +v sudo pacman -S archlinux-xdg-menu && XDG_MENU_PREFIX=arch- kbuildsycoca6; sudo ln -s /etc/xdg/menus/plasma-applications.menu /etc/xdg/menus/applications.menu ##################################################################################### From 5cb087e650b797e7a811579343015a00460b10f2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 14 Jun 2025 00:26:02 +0200 Subject: [PATCH 812/824] installation: set qt app style --- install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/install.sh b/install.sh index 4d9742ffe..e22f912b4 100755 --- a/install.sh +++ b/install.sh @@ -144,11 +144,12 @@ esac v sudo usermod -aG video,i2c,input "$(whoami)" v bash -c "echo i2c-dev | sudo tee /etc/modules-load.d/i2c-dev.conf" +v sudo pacman -S archlinux-xdg-menu && XDG_MENU_PREFIX=arch- kbuildsycoca6; sudo ln -s /etc/xdg/menus/plasma-applications.menu /etc/xdg/menus/applications.menu v systemctl --user enable ydotool --now v sudo systemctl enable bluetooth --now v gsettings set org.gnome.desktop.interface font-name 'Rubik 11' v gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' -v sudo pacman -S archlinux-xdg-menu && XDG_MENU_PREFIX=arch- kbuildsycoca6; sudo ln -s /etc/xdg/menus/plasma-applications.menu /etc/xdg/menus/applications.menu +v kwriteconfig6 --file kdeglobals --group KDE --key widgetStyle Darkly ##################################################################################### From 389246b180182994cffd1dea052c65a92218542f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 14 Jun 2025 08:16:59 +0200 Subject: [PATCH 813/824] background clock: hide in default position --- .../modules/backgroundWidgets/BackgroundWidgets.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml b/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml index b6add8968..9df0a9d35 100644 --- a/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml +++ b/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml @@ -15,8 +15,8 @@ import Quickshell.Services.UPower Scope { id: root property string filePath: `${Directories.state}/user/generated/wallpaper/least_busy_region.json` - property real centerX: 0 - property real centerY: 0 + property real centerX: -500 + property real centerY: -500 property color dominantColor: Appearance.colors.colPrimary property bool dominantColorIsDark: dominantColor.hslLightness < 0.5 property color colBackground: ColorUtils.transparentize(ColorUtils.mix(Appearance.colors.colPrimary, Appearance.colors.colSecondaryContainer), 1) From 468709c645c7d3115e2e9163032576f9ca3f3085 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 14 Jun 2025 08:48:45 +0200 Subject: [PATCH 814/824] readme: deprecation notice for ags version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a5d437d0d..8c50118eb 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ By the way... - The funny notification positions are mimicking Android 16's dragging behavior - The clock on the wallpaper is automatically placed at the "least busy" region of the image -## illogical-impulseAGS +## illogical-impulseAGS (Deprecated) | AI | Common widgets | |:---|:---------------| From f17437b4199a1a30fc665e97d2ab51db4c73e41a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 14 Jun 2025 10:01:40 +0200 Subject: [PATCH 815/824] fix right sidebar esc to close --- .../modules/sidebarRight/SidebarRight.qml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index a1e5a7478..ed9788f0e 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -64,7 +64,14 @@ Scope { } width: sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 - + + focus: GlobalStates.sidebarRightOpen + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Escape) { + sidebarRoot.hide(); + } + } + sourceComponent: Item { implicitHeight: sidebarRightBackground.implicitHeight implicitWidth: sidebarRightBackground.implicitWidth @@ -81,13 +88,6 @@ Scope { color: Appearance.colors.colLayer0 radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1 - Keys.onPressed: (event) => { - if (event.key === Qt.Key_Escape) { - sidebarRoot.hide(); - } - } - - ColumnLayout { spacing: sidebarPadding anchors.fill: parent @@ -194,7 +194,7 @@ Scope { } } } - + } From 9a360a3009db5b86024eed1dc0b54e05c5f0c926 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 14 Jun 2025 10:08:04 +0200 Subject: [PATCH 816/824] overview: left/right to switch ws --- .config/quickshell/modules/overview/Overview.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index d3e4e4567..a7817e6e9 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -99,6 +99,10 @@ Scope { Keys.onPressed: (event) => { if (event.key === Qt.Key_Escape) { GlobalStates.overviewOpen = false; + } else if (event.key === Qt.Key_Left) { + if (!root.searchingText) Hyprland.dispatch("workspace r-1"); + } else if (event.key === Qt.Key_Right) { + if (!root.searchingText) Hyprland.dispatch("workspace r+1"); } } From 67edf4e822d0f26facd8e75dd5eb657c8f558f29 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 14 Jun 2025 10:55:53 +0200 Subject: [PATCH 817/824] keybinds: apps: more flexibility --- .config/hypr/hyprland/keybinds.conf | 24 +++++++++---------- .../scripts/launch_first_available.sh | 7 ++++++ 2 files changed, 18 insertions(+), 13 deletions(-) create mode 100755 .config/hypr/hyprland/scripts/launch_first_available.sh diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index b1ca5589a..31194d693 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -198,19 +198,17 @@ bindl= ,XF86AudioPlay, exec, playerctl play-pause # [hidden] bindl= ,XF86AudioPause, exec, playerctl play-pause # [hidden] ##! Apps -bind = Super, T, exec, # [hidden] -bind = Super, Return, exec, kitty -1 # Kitty (terminal) -bind = Super, T, exec, kitty -1 # [hidden] Kitty (terminal) (alt) -bind = Ctrl+Alt, T, exec, kitty -1 # [hidden] Kitty (for Ubuntu people) -bind = Super, E, exec, dolphin --new-window # Dolphin (file manager) -bind = Ctrl+Super, W, exec, firefox # Firefox (browser) -bind = Super, C, exec, code # VSCode (code editor) -bind = Super, W, exec, zen-browser # [hidden] -bind = Super+Shift, W, exec, wps # WPS Office -bind = Super, X, exec, kate # Kate (text editor) -bind = Ctrl+Super, V, exec, pavucontrol-qt # Pavucontrol Qt (volume mixer) -bind = Super, I, exec, systemsettings # Plasma system settings -bind = Ctrl+Shift, Escape, exec, plasma-systemmonitor --page-name Processes # Plasma system monitor +bind = Super, Return, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # Terminal +bind = Super, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # [hidden] Kitty (terminal) (alt) +bind = Ctrl+Alt, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # [hidden] Kitty (for Ubuntu people) +bind = Super, E, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "dolphin" "nautilus" "nemo" "thunar" # File manager +bind = Super, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "zen-browser" "firefox" "brave" "chromium" "google-chrome-stable" "microsoft-edge-stable" "opera" # Browser +bind = Super, C, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "code" "codium" "zed" "kate" "gnome-text-editor" "emacs" "command -v nvim && kitty -1 nvim" # Code editor +bind = Super+Shift, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "wps" "onlyoffice-desktopeditors" # Launch office +bind = Super, X, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kate" "gnome-text-editor" "emacs" # Text editor +bind = Ctrl+Super, V, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "pavucontrol-qt" "pavucontrol" # Volume mixer +bind = Super, I, exec, XDG_CURRENT_DESKTOP=gnome ~/.config/hypr/hyprland/scripts/launch_first_available.sh "systemsettings" "gnome-control-center" "better-control" # Settings app +bind = Ctrl+Shift, Escape, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "gnome-system-monitor" "plasma-systemmonitzor --page-name Processes" "command -v btop && kitty -1 fish -c btop" # System monitor # Cursed stuff ## Make window not amogus large diff --git a/.config/hypr/hyprland/scripts/launch_first_available.sh b/.config/hypr/hyprland/scripts/launch_first_available.sh new file mode 100755 index 000000000..499947a9d --- /dev/null +++ b/.config/hypr/hyprland/scripts/launch_first_available.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +for cmd in "$@"; do + eval "command -v ${cmd%% *}" >/dev/null 2>&1 || continue + eval "$cmd" & + exit +done +exit 1 From e5e4f4086715ea35807149d03477dab153aa823d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 14 Jun 2025 10:56:12 +0200 Subject: [PATCH 818/824] window rules: more precise catch for kde's bluetooth connect --- .config/hypr/hyprland/rules.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index be0abacc4..c73303086 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -21,7 +21,7 @@ windowrulev2 = size 45%, class:^(nm-connection-editor)$ windowrulev2 = center, class:^(nm-connection-editor)$ windowrulev2 = float, class:.*plasmawindowed.* windowrulev2 = float, class:kcm_.* -windowrulev2 = float, class:.*bluedevil.* +windowrulev2 = float, class:.*bluedevilwizard # No appearance # kde-material-you-colors spawns a window when changing dark/light theme. This is to make sure it doesn't interfere at all. From 7ef91639e83d6ede08d0a99cf0b7720c1c901d1f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 14 Jun 2025 11:08:28 +0200 Subject: [PATCH 819/824] keybinds: make stuff more nicely packed on the cheatsheet --- .config/hypr/hyprland/keybinds.conf | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 31194d693..37f4d482b 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -45,12 +45,6 @@ bindld = Super+Alt,M, Toggle mic, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # bindd = Ctrl+Super, T, Change wallpaper, exec, ~/.config/quickshell/scripts/switchwall.sh # Change wallpaper bind = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool qs quickshell; qs & # Restart widgets -##! Session -bindd = Super, L, Lock, exec, loginctl lock-session # Lock -bind = Super+Shift, L, exec, loginctl lock-session # [hidden] -bindld = Super+Shift, L, Suspend system, exec, sleep 0.1 && systemctl suspend || loginctl suspend # Sleep -bindd = Ctrl+Shift+Alt+Super, Delete, Shutdown, exec, systemctl poweroff || loginctl poweroff # [hidden] Power off - ##! Utilities # Screenshot, Record, OCR, Color picker, Clipboard history bindd = Super, V, Copy clipboard history entry, exec, qs ipc call TEST_ALIVE || pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # [hidden] Clipboard history >> clipboard (fallback) @@ -175,17 +169,23 @@ bind = Ctrl+Super, BracketRight, workspace, +1 # [hidden] bind = Ctrl+Super, Up, workspace, r-5 # [hidden] bind = Ctrl+Super, Down, workspace, r+5 # [hidden] -##! Screen -# Zoom -binde = Super, Minus, exec, ~/.config/hypr/hyprland/scripts/zoom.sh decrease 0.1 # Zoom out -binde = Super, Equal, exec, ~/.config/hypr/hyprland/scripts/zoom.sh increase 0.1 # Zoom in - #! # Testing bind = Super+Alt, f11, exec, bash -c 'RANDOM_IMAGE=$(find ~/Pictures -type f | grep -v -i "nipple" | grep -v -i "pussy" | shuf -n 1); ACTION=$(notify-send "Test notification with body image" "This notification should contain your user account image and
Discord icon. Oh and here is a random image in your Pictures folder: \"Testing" -a "Hyprland keybind" -p -h "string:image-path:/var/lib/AccountsService/icons/$USER" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Open the random image" -A "action3=Useless button"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"' # [hidden] bind = Super+Alt, f12, exec, bash -c 'RANDOM_IMAGE=$(find ~/Pictures -type f | grep -v -i "nipple" | grep -v -i "pussy" | shuf -n 1); ACTION=$(notify-send "Test notification" "This notification should contain a random image in your Pictures folder and Discord icon.\nFlick right to dismiss!" -a "Discord (fake)" -p -h "string:image-path:$RANDOM_IMAGE" -t 6000 -i "discord" -A "openImage=Open profile image" -A "action2=Useless button" -A "action3=Cry more"); [[ $ACTION == *openImage ]] && xdg-open "/var/lib/AccountsService/icons/$USER"' # [hidden] bind = Super+Alt, Equal, exec, notify-send "Urgent notification" "Ah hell no" -u critical -a 'Hyprland keybind' # [hidden] +##! Session +bindd = Super, L, Lock, exec, loginctl lock-session # Lock +bind = Super+Shift, L, exec, loginctl lock-session # [hidden] +bindld = Super+Shift, L, Suspend system, exec, sleep 0.1 && systemctl suspend || loginctl suspend # Sleep +bindd = Ctrl+Shift+Alt+Super, Delete, Shutdown, exec, systemctl poweroff || loginctl poweroff # [hidden] Power off + +##! Screen +# Zoom +binde = Super, Minus, exec, ~/.config/hypr/hyprland/scripts/zoom.sh decrease 0.1 # Zoom out +binde = Super, Equal, exec, ~/.config/hypr/hyprland/scripts/zoom.sh increase 0.1 # Zoom in + ##! Media bindl= Super+Shift, N, exec, playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` # Next track bindl= ,XF86AudioNext, exec, playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` # [hidden] @@ -204,7 +204,7 @@ bind = Ctrl+Alt, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available bind = Super, E, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "dolphin" "nautilus" "nemo" "thunar" # File manager bind = Super, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "zen-browser" "firefox" "brave" "chromium" "google-chrome-stable" "microsoft-edge-stable" "opera" # Browser bind = Super, C, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "code" "codium" "zed" "kate" "gnome-text-editor" "emacs" "command -v nvim && kitty -1 nvim" # Code editor -bind = Super+Shift, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "wps" "onlyoffice-desktopeditors" # Launch office +bind = Super+Shift, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "wps" "onlyoffice-desktopeditors" # Office software bind = Super, X, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kate" "gnome-text-editor" "emacs" # Text editor bind = Ctrl+Super, V, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "pavucontrol-qt" "pavucontrol" # Volume mixer bind = Super, I, exec, XDG_CURRENT_DESKTOP=gnome ~/.config/hypr/hyprland/scripts/launch_first_available.sh "systemsettings" "gnome-control-center" "better-control" # Settings app From bc993bb7e2585cb2c0f0af111f6c8625d715d0b7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 14 Jun 2025 11:11:36 +0200 Subject: [PATCH 820/824] quickshell: add scale factor envvar --- .config/quickshell/shell.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 35a5008ea..4edfde51f 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -2,6 +2,9 @@ //@ pragma Env QS_NO_RELOAD_POPUP=1 //@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic +// Adjust this to make the shell smaller or larger +//@ pragma Env QT_SCALE_FACTOR=1 + import "./modules/common/" import "./modules/backgroundWidgets/" import "./modules/bar/" From 859c79010c3d5fddcf4eff677c9761eab8bc2ce4 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 14 Jun 2025 12:46:21 +0200 Subject: [PATCH 821/824] readme: installation: point to wiki page of new version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c50118eb..4ec7b63ce 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ ``` - **Manual** installation, other distros and more: - - See the [Wiki](https://end-4.github.io/dots-hyprland-wiki/en/i-i/01setup/) + - See the [Wiki](https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/01setup/) - **Default keybinds**: Should be somewhat familiar if you've used Windows or GNOME. - For a list, hit `Super`+`/` From f33fb45b898012eb0494b720db961027a15790fe Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 14 Jun 2025 15:59:01 +0200 Subject: [PATCH 822/824] no sussy trust me bro --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ec7b63ce..33ceb2cd4 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ |:---|:---------------| | ![image](https://github.com/user-attachments/assets/08d26785-b54d-4ad1-875b-bb08cc6757f5) | ![image](https://github.com/user-attachments/assets/4fcd63d9-0943-4b21-8737-4bed97b71961) | | Window management | Weeb power | -| ![image](https://github.com/user-attachments/assets/86cc511b-0d33-4c78-bcc0-3037d02a17da) | ![image](https://github.com/user-attachments/assets/e402f74a-6bd8-4ebe-bcf4-3a4a4846de10) | +| ![image](https://github.com/user-attachments/assets/86cc511b-0d33-4c78-bcc0-3037d02a17da) | ![image](https://github.com/user-attachments/assets/292259fc-57d3-4663-a583-2ce2faad13fb) | By the way... - The funny notification positions are mimicking Android 16's dragging behavior From 22a796deae31657d5aa4bb22ab7f7c0b92276ab3 Mon Sep 17 00:00:00 2001 From: "Celestial.y" Date: Sun, 15 Jun 2025 09:08:08 +0800 Subject: [PATCH 823/824] Not using recursive env var P.S. I have had done it in ii-ags --- .config/hypr/hyprland/env.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/hypr/hyprland/env.conf b/.config/hypr/hyprland/env.conf index 4d1fa8f7d..524af62a2 100644 --- a/.config/hypr/hyprland/env.conf +++ b/.config/hypr/hyprland/env.conf @@ -18,8 +18,8 @@ env = QT_QPA_PLATFORMTHEME, kde # env = WLR_DRM_NO_ATOMIC, 1 # ######## Virtual envrionment ######### -env = XDG_STATE_HOME, $HOME/.local/state -env = ILLOGICAL_IMPULSE_VIRTUAL_ENV, $XDG_STATE_HOME/quickshell/.venv +env = ILLOGICAL_IMPULSE_VIRTUAL_ENV, ~/ +.local/state/quickshell/.venv # ############ Others ############# From 75d70d5e87cfe25b8b04f6eb1f36b9d67a71a9b8 Mon Sep 17 00:00:00 2001 From: Bishoy Ehab Date: Sun, 15 Jun 2025 05:37:44 +0300 Subject: [PATCH 824/824] Fix an error in env file --- .config/hypr/hyprland/env.conf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.config/hypr/hyprland/env.conf b/.config/hypr/hyprland/env.conf index 524af62a2..b3d843cdf 100644 --- a/.config/hypr/hyprland/env.conf +++ b/.config/hypr/hyprland/env.conf @@ -18,8 +18,7 @@ env = QT_QPA_PLATFORMTHEME, kde # env = WLR_DRM_NO_ATOMIC, 1 # ######## Virtual envrionment ######### -env = ILLOGICAL_IMPULSE_VIRTUAL_ENV, ~/ -.local/state/quickshell/.venv +env = ILLOGICAL_IMPULSE_VIRTUAL_ENV, ~/.local/state/quickshell/.venv # ############ Others #############