Merge branch 'main' into weather-settings

This commit is contained in:
vaguesyntax
2025-10-30 18:13:49 +03:00
54 changed files with 1963 additions and 502 deletions
@@ -445,7 +445,7 @@ Variants {
color: bgRoot.colText color: bgRoot.colText
style: Text.Raised style: Text.Raised
styleColor: Appearance.colors.colShadow styleColor: Appearance.colors.colShadow
animateChange: true animateChange: Config.options.background.clock.digital.animateChange
} }
component ClockStatusText: Row { component ClockStatusText: Row {
id: statusTextRow id: statusTextRow
@@ -50,9 +50,9 @@ Item {
print("[Cookie clock] Setting clock preset for category: " + category) print("[Cookie clock] Setting clock preset for category: " + category)
// "abstract", "anime", "city", "minimalist", "landscape", "plants", "person", "space" // "abstract", "anime", "city", "minimalist", "landscape", "plants", "person", "space"
if (category == "abstract") { if (category == "abstract") {
applyStyle(10, "dots", "fill", "medium", "dot", "bubble") applyStyle(9, "none", "fill", "medium", "dot", "bubble")
} else if (category == "anime") { } else if (category == "anime") {
applyStyle(12, "dots", "fill", "bold", "dot", "bubble") applyStyle(7, "none", "fill", "bold", "dot", "bubble")
} else if (category == "city" || category == "space") { } else if (category == "city" || category == "space") {
applyStyle(23, "full", "hollow", "thin", "classic", "bubble") applyStyle(23, "full", "hollow", "thin", "classic", "bubble")
} else if (category == "minimalist") { } else if (category == "minimalist") {
@@ -83,121 +83,141 @@ Item {
} }
} }
property bool useSineCookie: Config.options.background.clock.cookie.useSineCookie
DropShadow { DropShadow {
source: cookie source: useSineCookie ? sineCookieLoader : roundedPolygonCookieLoader
anchors.fill: source anchors.fill: source
horizontalOffset: 0
verticalOffset: 1
radius: 8 radius: 8
samples: radius * 2 + 1 samples: radius * 2 + 1
color: root.colShadow color: root.colShadow
transparentBorder: true transparentBorder: true
RotationAnimation on rotation {
running: Config.options.background.clock.cookie.constantlyRotate
duration: 30000
easing.type: Easing.Linear
loops: Animation.Infinite
from: 360
to: 0
}
}
Loader {
id: sineCookieLoader
z: 0
visible: false // The DropShadow already draws it
active: useSineCookie
sourceComponent: SineCookie {
implicitSize: root.implicitSize
sides: Config.options.background.clock.cookie.sides
color: root.colBackground
}
}
Loader {
id: roundedPolygonCookieLoader
z: 0
visible: false // The DropShadow already draws it
active: !useSineCookie
sourceComponent: MaterialCookie {
implicitSize: root.implicitSize
sides: Config.options.background.clock.cookie.sides
color: root.colBackground
}
} }
MaterialCookie { // Hour/minutes numbers/dots/lines
id: cookie MinuteMarks {
z: 0 anchors.fill: parent
implicitSize: root.implicitSize color: root.colOnBackground
amplitude: implicitSize / 70 }
sides: Config.options.background.clock.cookie.sides
color: root.colBackground // Stupid extra hour marks in the middle
constantlyRotate: Config.options.background.clock.cookie.constantlyRotate FadeLoader {
id: hourMarksLoader
// Hour/minutes numbers/dots/lines anchors.centerIn: parent
MinuteMarks { shown: Config.options.background.clock.cookie.hourMarks
anchors.fill: parent sourceComponent: HourMarks {
implicitSize: 135 * (1.75 - 0.75 * hourMarksLoader.opacity)
color: root.colOnBackground color: root.colOnBackground
colOnBackground: ColorUtils.mix(root.colBackgroundInfo, root.colOnBackground, 0.5)
}
}
// Number column in the middle
FadeLoader {
id: timeColumnLoader
anchors.centerIn: parent
shown: Config.options.background.clock.cookie.timeIndicators
scale: 1.4 - 0.4 * timeColumnLoader.shown
Behavior on scale {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
} }
// Stupid extra hour marks in the middle sourceComponent: TimeColumn {
FadeLoader { color: root.colBackgroundInfo
id: hourMarksLoader
anchors.centerIn: parent
shown: Config.options.background.clock.cookie.hourMarks
sourceComponent: HourMarks {
implicitSize: 135 * (1.75 - 0.75 * hourMarksLoader.opacity)
color: root.colOnBackground
colOnBackground: ColorUtils.mix(root.colBackgroundInfo, root.colOnBackground, 0.5)
}
} }
}
// Number column in the middle // Hour hand
FadeLoader { FadeLoader {
id: timeColumnLoader anchors.fill: parent
anchors.centerIn: parent z: 1
shown: Config.options.background.clock.cookie.timeIndicators shown: Config.options.background.clock.cookie.hourHandStyle !== "hide"
scale: 1.4 - 0.4 * timeColumnLoader.shown sourceComponent: HourHand {
Behavior on scale { clockHour: root.clockHour
animation: Appearance.animation.elementResize.numberAnimation.createObject(this) clockMinute: root.clockMinute
} style: Config.options.background.clock.cookie.hourHandStyle
color: root.colHourHand
sourceComponent: TimeColumn {
color: root.colBackgroundInfo
}
} }
}
// Hour hand // Minute hand
FadeLoader { FadeLoader {
anchors.fill: parent
z: 2
shown: Config.options.background.clock.cookie.minuteHandStyle !== "hide"
sourceComponent: MinuteHand {
anchors.fill: parent anchors.fill: parent
z: 1 clockMinute: root.clockMinute
shown: Config.options.background.clock.cookie.hourHandStyle !== "hide" style: Config.options.background.clock.cookie.minuteHandStyle
sourceComponent: HourHand { color: root.colMinuteHand
clockHour: root.clockHour
clockMinute: root.clockMinute
style: Config.options.background.clock.cookie.hourHandStyle
color: root.colHourHand
}
} }
}
// Minute hand // Second hand
FadeLoader { FadeLoader {
anchors.fill: parent id: secondHandLoader
z: 2 z: (Config.options.background.clock.cookie.secondHandStyle === "line") ? 2 : 3
shown: Config.options.background.clock.cookie.minuteHandStyle !== "hide" shown: Config.options.time.secondPrecision && Config.options.background.clock.cookie.secondHandStyle !== "hide"
sourceComponent: MinuteHand { anchors.fill: parent
anchors.fill: parent sourceComponent: SecondHand {
clockMinute: root.clockMinute id: secondHand
style: Config.options.background.clock.cookie.minuteHandStyle clockSecond: root.clockSecond
color: root.colMinuteHand style: Config.options.background.clock.cookie.secondHandStyle
} color: root.colSecondHand
} }
}
// Second hand // Center dot
FadeLoader { FadeLoader {
id: secondHandLoader z: 4
z: (Config.options.background.clock.cookie.secondHandStyle === "line") ? 2 : 3 anchors.centerIn: parent
shown: Config.options.time.secondPrecision && Config.options.background.clock.cookie.secondHandStyle !== "hide" shown: Config.options.background.clock.cookie.minuteHandStyle !== "bold"
anchors.fill: parent sourceComponent: Rectangle {
sourceComponent: SecondHand { color: Config.options.background.clock.cookie.minuteHandStyle === "medium" ? root.colBackground : root.colMinuteHand
id: secondHand implicitWidth: 6
clockSecond: root.clockSecond implicitHeight: implicitWidth
style: Config.options.background.clock.cookie.secondHandStyle radius: width / 2
color: root.colSecondHand
}
} }
}
// Center dot // Date
FadeLoader { FadeLoader {
z: 4 anchors.fill: parent
anchors.centerIn: parent shown: Config.options.background.clock.cookie.dateStyle !== "hide"
shown: Config.options.background.clock.cookie.minuteHandStyle !== "bold"
sourceComponent: Rectangle {
color: Config.options.background.clock.cookie.minuteHandStyle === "medium" ? root.colBackground : root.colMinuteHand
implicitWidth: 6
implicitHeight: implicitWidth
radius: width / 2
}
}
// Date sourceComponent: DateIndicator {
FadeLoader { color: root.colBackgroundInfo
anchors.fill: parent style: Config.options.background.clock.cookie.dateStyle
shown: Config.options.background.clock.cookie.dateStyle !== "hide"
sourceComponent: DateIndicator {
color: root.colBackgroundInfo
style: Config.options.background.clock.cookie.dateStyle
}
} }
} }
} }
@@ -9,7 +9,7 @@ Item {
required property int clockHour required property int clockHour
required property int clockMinute required property int clockMinute
property real handLength: 72 property real handLength: 72
property real handWidth: 18 property real handWidth: 20
property string style: "fill" property string style: "fill"
property color color: Appearance.colors.colPrimary property color color: Appearance.colors.colPrimary
@@ -19,6 +19,14 @@ Item {
} }
rotation: -90 + (360 / 12) * (root.clockHour + root.clockMinute / 60) rotation: -90 + (360 / 12) * (root.clockHour + root.clockMinute / 60)
Behavior on rotation {
animation: RotationAnimation {
direction: RotationAnimation.Clockwise
duration: 300
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.emphasized
}
}
Rectangle { Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -10,13 +10,17 @@ Item {
required property int clockMinute required property int clockMinute
property string style: "medium" property string style: "medium"
property real handLength: 95 property real handLength: 95
property real handWidth: style === "bold" ? 18 : style === "medium" ? 12 : 5 property real handWidth: style === "bold" ? 20 : style === "medium" ? 12 : 5
property color color: Appearance.colors.colSecondary property color color: Appearance.colors.colSecondary
rotation: -90 + (360 / 60) * root.clockMinute rotation: -90 + (360 / 60) * root.clockMinute
Behavior on rotation { Behavior on rotation {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this) animation: RotationAnimation {
direction: RotationAnimation.Clockwise
duration: 300
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.emphasized
}
} }
Rectangle { Rectangle {
@@ -10,7 +10,7 @@ Item {
required property int clockSecond required property int clockSecond
property real handWidth: 2 property real handWidth: 2
property real handLength: 100 property real handLength: 95
property real dotSize: 20 property real dotSize: 20
property string style: "hide" property string style: "hide"
property color color: Appearance.colors.colSecondary property color color: Appearance.colors.colSecondary
@@ -19,7 +19,8 @@ Item {
Behavior on rotation { Behavior on rotation {
enabled: Config.options.background.clock.cookie.constantlyRotate // Animating every second is expensive... enabled: Config.options.background.clock.cookie.constantlyRotate // Animating every second is expensive...
animation: NumberAnimation { animation: RotationAnimation {
direction: RotationAnimation.Clockwise
duration: 1000 // 1 second duration: 1000 // 1 second
easing.type: Easing.InOutQuad easing.type: Easing.InOutQuad
} }
@@ -29,7 +30,7 @@ Item {
anchors { anchors {
left: parent.left left: parent.left
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
leftMargin: 10 leftMargin: 10 + (root.style === "dot" ? root.dotSize : 0)
} }
implicitWidth: root.style === "dot" ? root.dotSize : root.handLength implicitWidth: root.style === "dot" ? root.dotSize : root.handLength
implicitHeight: root.style === "dot" ? root.dotSize : root.handWidth implicitHeight: root.style === "dot" ? root.dotSize : root.handWidth
@@ -14,7 +14,10 @@ Item {
// 12 Dots // 12 Dots
FadeLoader { FadeLoader {
id: dotsLoader id: dotsLoader
anchors.fill: parent anchors {
fill: parent
margins: 10
}
shown: root.style === "dots" shown: root.style === "dots"
sourceComponent: Dots { sourceComponent: Dots {
color: root.color color: root.color
@@ -37,7 +40,10 @@ Item {
// Lines // Lines
FadeLoader { FadeLoader {
id: linesLoader id: linesLoader
anchors.fill: parent anchors {
fill: parent
margins: 10
}
shown: root.style === "full" shown: root.style === "full"
sourceComponent: Lines { sourceComponent: Lines {
color: root.color color: root.color
@@ -5,6 +5,7 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects import Qt5Compat.GraphicalEffects
import Qt.labs.synchronizer
import Quickshell.Io import Quickshell.Io
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
@@ -22,7 +23,6 @@ Scope { // Scope
"name": Translation.tr("Elements") "name": Translation.tr("Elements")
}, },
] ]
property int selectedTab: 0
Loader { Loader {
id: cheatsheetLoader id: cheatsheetLoader
@@ -31,6 +31,7 @@ Scope { // Scope
sourceComponent: PanelWindow { // Window sourceComponent: PanelWindow { // Window
id: cheatsheetRoot id: cheatsheetRoot
visible: cheatsheetLoader.active visible: cheatsheetLoader.active
property int selectedTab: 0
anchors { anchors {
top: true top: true
@@ -85,16 +86,16 @@ Scope { // Scope
} }
if (event.modifiers === Qt.ControlModifier) { if (event.modifiers === Qt.ControlModifier) {
if (event.key === Qt.Key_PageDown) { if (event.key === Qt.Key_PageDown) {
root.selectedTab = Math.min(root.selectedTab + 1, root.tabButtonList.length - 1); cheatsheetRoot.selectedTab = Math.min(cheatsheetRoot.selectedTab + 1, root.tabButtonList.length - 1);
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_PageUp) { } else if (event.key === Qt.Key_PageUp) {
root.selectedTab = Math.max(root.selectedTab - 1, 0); cheatsheetRoot.selectedTab = Math.max(cheatsheetRoot.selectedTab - 1, 0);
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Tab) { } else if (event.key === Qt.Key_Tab) {
root.selectedTab = (root.selectedTab + 1) % root.tabButtonList.length; cheatsheetRoot.selectedTab = (cheatsheetRoot.selectedTab + 1) % root.tabButtonList.length;
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Backtab) { } else if (event.key === Qt.Key_Backtab) {
root.selectedTab = (root.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length; cheatsheetRoot.selectedTab = (cheatsheetRoot.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length;
event.accepted = true; event.accepted = true;
} }
} }
@@ -140,9 +141,8 @@ Scope { // Scope
PrimaryTabBar { // Tab strip PrimaryTabBar { // Tab strip
id: tabBar id: tabBar
tabButtonList: root.tabButtonList tabButtonList: root.tabButtonList
externalTrackedTab: root.selectedTab Synchronizer on currentIndex {
function onCurrentIndexChanged(currentIndex) { property alias source: cheatsheetRoot.selectedTab
root.selectedTab = currentIndex;
} }
} }
@@ -164,12 +164,12 @@ Scope { // Scope
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
} }
currentIndex: tabBar.externalTrackedTab currentIndex: cheatsheetRoot.selectedTab
onCurrentIndexChanged: { onCurrentIndexChanged: {
contentWidthBehavior.enabled = true; contentWidthBehavior.enabled = true;
contentHeightBehavior.enabled = true; contentHeightBehavior.enabled = true;
tabBar.enableIndicatorAnimation = true; tabBar.enableIndicatorAnimation = true;
root.selectedTab = currentIndex; cheatsheetRoot.selectedTab = currentIndex;
} }
clip: true clip: true
@@ -164,6 +164,10 @@ Singleton {
property bool hourMarks: false property bool hourMarks: false
property bool dateInClock: true property bool dateInClock: true
property bool constantlyRotate: false property bool constantlyRotate: false
property bool useSineCookie: false
}
property JsonObject digital: JsonObject {
property bool animateChange: true
} }
} }
@@ -324,6 +328,7 @@ Singleton {
property bool unlockKeyring: true property bool unlockKeyring: true
property bool requirePasswordToPower: false property bool requirePasswordToPower: false
} }
property bool materialShapeChars: true
} }
property JsonObject media: JsonObject { property JsonObject media: JsonObject {
@@ -2,7 +2,7 @@ pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import qs.modules.common.functions import qs.modules.common.functions
import Qt.labs.platform import QtCore
import QtQuick import QtQuick
import Quickshell import Quickshell
@@ -2,69 +2,38 @@ import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import Quickshell import Quickshell
import qs.modules.common import qs.modules.common
import qs.modules.common.widgets.shapes
import "shapes/geometry/offset.js" as Offset
import "shapes/shapes/corner-rounding.js" as CornerRounding
import "shapes/shapes/rounded-polygon.js" as RoundedPolygon
import "shapes/material-shapes.js" as MaterialShapes
Item { Item {
id: root id: root
property int sides: 12
property real sides: 12
property int implicitSize: 100 property int implicitSize: 100
property real amplitude: implicitSize / 50 property alias color: shapeCanvas.color
property int renderPoints: 360
property color color: "#605790"
property alias strokeWidth: shapePath.strokeWidth
property bool constantlyRotate: false
implicitWidth: implicitSize implicitWidth: implicitSize
implicitHeight: implicitSize implicitHeight: implicitSize
property real shapeRotation: 0 property var cornerRounding: new CornerRounding.CornerRounding((sides < 17 ? 1.5 : 1.1) / Math.max(sides, 1))
Loader { ShapeCanvas {
active: constantlyRotate id: shapeCanvas
sourceComponent: FrameAnimation {
running: true
onTriggered: {
shapeRotation += 0.05
}
}
}
Behavior on sides {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Shape {
id: shape
anchors.fill: parent anchors.fill: parent
preferredRendererType: Shape.CurveRenderer roundedPolygon: switch(sides) {
case 0: return MaterialShapes.getCircle();
ShapePath { case 1: return MaterialShapes.getCircle();
id: shapePath case 4: return MaterialShapes.getCookie4Sided();
strokeWidth: 0 case 6: return MaterialShapes.getCookie6Sided();
fillColor: root.color case 7: return MaterialShapes.getCookie7Sided();
pathHints: ShapePath.PathSolid & ShapePath.PathNonIntersecting case 9: return MaterialShapes.getCookie9Sided();
case 12: return MaterialShapes.getCookie12Sided();
PathPolyline { default: return RoundedPolygon.RoundedPolygon.star(sides, 1, 0.8, root.cornerRounding)
property var pointsList: { .transformed((x, y) => MaterialShapes.rotate30.map(new Offset.Offset(x, y)))
var points = [] .normalized();
var cx = shape.width / 2 // center x
var cy = shape.height / 2 // center y
var steps = root.renderPoints
var radius = root.implicitSize / 2 - root.amplitude
for (var i = 0; i <= steps; i++) {
var angle = (i / steps) * 2 * Math.PI
var rotatedAngle = angle * root.sides + Math.PI/2 + (root.shapeRotation * root.constantlyRotate)
var wave = Math.sin(rotatedAngle) * root.amplitude
var x = Math.cos(angle) * (radius + wave) + cx
var y = Math.sin(angle) * (radius + wave) + cy
points.push(Qt.point(x, y))
}
return points
}
path: pointsList
}
} }
} }
} }
@@ -44,6 +44,7 @@ ShapeCanvas {
property double implicitSize property double implicitSize
implicitHeight: implicitSize implicitHeight: implicitSize
implicitWidth: implicitSize implicitWidth: implicitSize
polygonIsNormalized: true
roundedPolygon: { roundedPolygon: {
switch (root.shape) { switch (root.shape) {
case MaterialShape.Shape.Circle: return MaterialShapes.getCircle(); case MaterialShape.Shape.Circle: return MaterialShapes.getCircle();
@@ -3,16 +3,20 @@ import qs.services
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Qt.labs.synchronizer
ColumnLayout { ColumnLayout {
id: root id: root
spacing: 0 spacing: 0
required property var tabButtonList // Something like [{"icon": "notifications", "name": Translation.tr("Notifications")}, {"icon": "volume_up", "name": Translation.tr("Volume mixer")}] required property var tabButtonList // Something like [{"icon": "notifications", "name": Translation.tr("Notifications")}, {"icon": "volume_up", "name": Translation.tr("Volume mixer")}]
required property var externalTrackedTab property int currentIndex
property bool enableIndicatorAnimation: false property bool enableIndicatorAnimation: false
property color colIndicator: Appearance?.colors.colPrimary ?? "#65558F" property color colIndicator: Appearance?.colors.colPrimary ?? "#65558F"
property color colBorder: Appearance?.m3colors.m3outlineVariant ?? "#C6C6D0" property color colBorder: Appearance?.m3colors.m3outlineVariant ?? "#C6C6D0"
signal currentIndexChanged(int index)
onCurrentIndexChanged: {
enableIndicatorAnimation = true
}
property bool centerTabBar: parent.width > 500 property bool centerTabBar: parent.width > 500
Layout.fillWidth: !centerTabBar Layout.fillWidth: !centerTabBar
@@ -22,9 +26,8 @@ ColumnLayout {
TabBar { TabBar {
id: tabBar id: tabBar
Layout.fillWidth: true Layout.fillWidth: true
currentIndex: root.externalTrackedTab Synchronizer on currentIndex {
onCurrentIndexChanged: { property alias source: root.currentIndex
root.onCurrentIndexChanged(currentIndex)
} }
background: Item { background: Item {
@@ -42,10 +45,11 @@ ColumnLayout {
Repeater { Repeater {
model: root.tabButtonList model: root.tabButtonList
delegate: PrimaryTabButton { delegate: PrimaryTabButton {
selected: (index == root.externalTrackedTab) selected: (index == root.currentIndex)
buttonText: modelData.name buttonText: modelData.name
buttonIcon: modelData.icon buttonIcon: modelData.icon
minimumWidth: 160 minimumWidth: 160
onClicked: root.currentIndex = index
} }
} }
} }
@@ -54,12 +58,6 @@ ColumnLayout {
id: tabIndicator id: tabIndicator
Layout.fillWidth: true Layout.fillWidth: true
height: 3 height: 3
Connections {
target: root
function onExternalTrackedTabChanged() {
root.enableIndicatorAnimation = true
}
}
Rectangle { Rectangle {
id: indicator id: indicator
@@ -0,0 +1,70 @@
import QtQuick
import QtQuick.Shapes
import Quickshell
import qs.modules.common
Item {
id: root
property real sides: 12
property int implicitSize: 100
property real amplitude: implicitSize / 50
property int renderPoints: 360
property color color: "#605790"
property alias strokeWidth: shapePath.strokeWidth
property bool constantlyRotate: false
implicitWidth: implicitSize
implicitHeight: implicitSize
property real shapeRotation: 0
Loader {
active: constantlyRotate
sourceComponent: FrameAnimation {
running: true
onTriggered: {
shapeRotation += 0.05
}
}
}
Behavior on sides {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Shape {
id: shape
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
ShapePath {
id: shapePath
strokeWidth: 0
fillColor: root.color
pathHints: ShapePath.PathSolid & ShapePath.PathNonIntersecting
PathPolyline {
property var pointsList: {
var points = []
var cx = shape.width / 2 // center x
var cy = shape.height / 2 // center y
var steps = root.renderPoints
var radius = root.implicitSize / 2 - root.amplitude
for (var i = 0; i <= steps; i++) {
var angle = (i / steps) * 2 * Math.PI
var rotatedAngle = angle * root.sides + Math.PI/2 + (root.shapeRotation * root.constantlyRotate)
var wave = Math.sin(rotatedAngle) * root.amplitude
var x = Math.cos(angle) * (radius + wave) + cx
var y = Math.sin(angle) * (radius + wave) + cy
points.push(Qt.point(x, y))
}
return points
}
path: pointsList
}
}
}
}
@@ -45,13 +45,25 @@ Switch {
anchors.leftMargin: root.checked ? ((root.pressed || root.down) ? (22 * root.scale) : 24 * root.scale) : ((root.pressed || root.down) ? (2 * root.scale) : 8 * root.scale) anchors.leftMargin: root.checked ? ((root.pressed || root.down) ? (22 * root.scale) : 24 * root.scale) : ((root.pressed || root.down) ? (2 * root.scale) : 8 * root.scale)
Behavior on anchors.leftMargin { Behavior on anchors.leftMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this) NumberAnimation {
duration: Appearance.animationCurves.expressiveFastSpatialDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
} }
Behavior on width { Behavior on width {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this) NumberAnimation {
duration: Appearance.animationCurves.expressiveFastSpatialDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
} }
Behavior on height { Behavior on height {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this) NumberAnimation {
duration: Appearance.animationCurves.expressiveFastSpatialDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
} }
Behavior on color { Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
+104 -78
View File
@@ -1,4 +1,6 @@
pragma ComponentBehavior: Bound
import qs import qs
import qs.services
import qs.modules.common import qs.modules.common
import qs.modules.common.functions import qs.modules.common.functions
import qs.modules.lock import qs.modules.lock
@@ -9,9 +11,9 @@ import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
Scope { Scope {
id: root id: root
function unlockKeyring() { function unlockKeyring() {
Quickshell.execDetached({ Quickshell.execDetached({
environment: ({ environment: ({
UNLOCK_PASSWORD: root.currentText UNLOCK_PASSWORD: root.currentText
@@ -20,117 +22,141 @@ Scope {
}) })
} }
// This stores all the information shared between the lock surfaces on each screen. property var windowData: []
// https://github.com/quickshell-mirror/quickshell-examples/tree/master/lockscreen function saveWindowPositionAndTile() {
LockContext { Hyprland.dispatch(`keyword dwindle:pseudotile true`)
id: lockContext root.windowData = HyprlandData.windowList.filter(w => (w.floating && w.workspace.id === HyprlandData.activeWorkspace.id))
root.windowData.forEach(w => {
Hyprland.dispatch(`pseudo address:${w.address}`)
Hyprland.dispatch(`settiled address:${w.address}`)
Hyprland.dispatch(`movetoworkspacesilent ${w.workspace.id},address:${w.address}`)
})
}
function restoreWindowPositionAndTile() {
root.windowData.forEach(w => {
Hyprland.dispatch(`setfloating address:${w.address}`)
Hyprland.dispatch(`movewindowpixel exact ${w.at[0]} ${w.at[1]}, address:${w.address}`)
Hyprland.dispatch(`pseudo address:${w.address}`)
})
Hyprland.dispatch(`keyword dwindle:pseudotile false`)
}
Connections { // This stores all the information shared between the lock surfaces on each screen.
target: GlobalStates // https://github.com/quickshell-mirror/quickshell-examples/tree/master/lockscreen
function onScreenLockedChanged() { LockContext {
if (GlobalStates.screenLocked) lockContext.reset(); id: lockContext
}
}
onUnlocked: (targetAction) => { Connections {
// Perform the target action if it's not just unlocking target: GlobalStates
if (targetAction == LockContext.ActionEnum.Poweroff) { function onScreenLockedChanged() {
Session.poweroff(); if (GlobalStates.screenLocked) {
return; lockContext.reset();
} else if (targetAction == LockContext.ActionEnum.Reboot) { lockContext.tryFingerUnlock();
Session.reboot(); }
return; }
} }
// Unlock the keyring if configured to do so onUnlocked: (targetAction) => {
if (Config.options.lock.security.unlockKeyring) root.unlockKeyring(); // Perform the target action if it's not just unlocking
if (targetAction == LockContext.ActionEnum.Poweroff) {
Session.poweroff();
return;
} else if (targetAction == LockContext.ActionEnum.Reboot) {
Session.reboot();
return;
}
// Unlock the screen before exiting, or the compositor will display a // Unlock the keyring if configured to do so
// fallback lock you can't interact with. if (Config.options.lock.security.unlockKeyring) root.unlockKeyring();
GlobalStates.screenLocked = false;
// Unlock the screen before exiting, or the compositor will display a
// Refocus last focused window on unlock (hack) // fallback lock you can't interact with.
Quickshell.execDetached(["bash", "-c", `sleep 0.2; hyprctl --batch "dispatch togglespecialworkspace; dispatch togglespecialworkspace"`]) GlobalStates.screenLocked = false;
// Refocus last focused window on unlock (hack)
Quickshell.execDetached(["bash", "-c", `sleep 0.2; hyprctl --batch "dispatch togglespecialworkspace; dispatch togglespecialworkspace"`])
// Reset // Reset
lockContext.reset(); lockContext.reset();
} }
} }
WlSessionLock { WlSessionLock {
id: lock id: lock
locked: GlobalStates.screenLocked locked: GlobalStates.screenLocked
WlSessionLockSurface { WlSessionLockSurface {
color: "transparent" color: "transparent"
Loader { Loader {
active: GlobalStates.screenLocked active: GlobalStates.screenLocked
anchors.fill: parent anchors.fill: parent
opacity: active ? 1 : 0 opacity: active ? 1 : 0
Behavior on opacity { Behavior on opacity {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
} }
sourceComponent: LockSurface { sourceComponent: LockSurface {
context: lockContext context: lockContext
} }
} }
} }
} }
// Blur layer hack // Blur layer hack
Variants { Variants {
model: Quickshell.screens model: Quickshell.screens
delegate: Scope { delegate: Scope {
required property ShellScreen modelData required property ShellScreen modelData
property bool shouldPush: GlobalStates.screenLocked property bool shouldPush: GlobalStates.screenLocked
property string targetMonitorName: modelData.name property string targetMonitorName: modelData.name
property int verticalMovementDistance: modelData.height property int verticalMovementDistance: modelData.height
property int horizontalSqueeze: modelData.width * 0.2 property int horizontalSqueeze: modelData.width * 0.2
onShouldPushChanged: { onShouldPushChanged: {
if (shouldPush) { if (shouldPush) {
Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, ${verticalMovementDistance}, ${-verticalMovementDistance}, ${horizontalSqueeze}, ${horizontalSqueeze}`]) root.saveWindowPositionAndTile();
} else { Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, ${verticalMovementDistance}, ${-verticalMovementDistance}, ${horizontalSqueeze}, ${horizontalSqueeze}`])
Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, 0, 0, 0, 0`]) } else {
} Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, 0, 0, 0, 0`])
} root.restoreWindowPositionAndTile();
} }
} }
}
}
IpcHandler { IpcHandler {
target: "lock" target: "lock"
function activate(): void { function activate(): void {
GlobalStates.screenLocked = true; GlobalStates.screenLocked = true;
} }
function focus(): void { function focus(): void {
lockContext.shouldReFocus(); lockContext.shouldReFocus();
} }
} }
GlobalShortcut { GlobalShortcut {
name: "lock" name: "lock"
description: "Locks the screen" description: "Locks the screen"
onPressed: { onPressed: {
if (Config.options.lock.useHyprlock) { if (Config.options.lock.useHyprlock) {
Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]); Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]);
return; return;
} }
GlobalStates.screenLocked = true; GlobalStates.screenLocked = true;
} }
} }
GlobalShortcut { GlobalShortcut {
name: "lockFocus" name: "lockFocus"
description: "Re-focuses the lock screen. This is because Hyprland after waking up for whatever reason" description: "Re-focuses the lock screen. This is because Hyprland after waking up for whatever reason"
+ "decides to keyboard-unfocus the lock screen" + "decides to keyboard-unfocus the lock screen"
onPressed: { onPressed: {
lockContext.shouldReFocus(); lockContext.shouldReFocus();
} }
} }
Connections { Connections {
target: Config target: Config
function onReadyChanged() { function onReadyChanged() {
if (Config.options.lock.launchOnStartup && Config.ready && Persistent.ready && Persistent.isNewHyprlandInstance) { if (Config.options.lock.launchOnStartup && Config.ready && Persistent.ready && Persistent.isNewHyprlandInstance) {
@@ -2,6 +2,7 @@ import qs
import qs.modules.common import qs.modules.common
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io
import Quickshell.Services.Pam import Quickshell.Services.Pam
Scope { Scope {
@@ -18,6 +19,7 @@ Scope {
property string currentText: "" property string currentText: ""
property bool unlockInProgress: false property bool unlockInProgress: false
property bool showFailure: false property bool showFailure: false
property bool fingerprintsConfigured: false
property var targetAction: LockContext.ActionEnum.Unlock property var targetAction: LockContext.ActionEnum.Unlock
function resetTargetAction() { function resetTargetAction() {
@@ -60,6 +62,34 @@ Scope {
pam.start(); pam.start();
} }
function tryFingerUnlock() {
if (root.fingerprintsConfigured) {
fingerPam.start();
}
}
function stopFingerPam() {
fingerPam.abort();
}
Process {
id: fingerprintCheckProc
running: true
command: ["bash", "-c", "fprintd-list $(whoami)"]
stdout: StdioCollector {
id: fingerprintOutputCollector
onStreamFinished: {
root.fingerprintsConfigured = fingerprintOutputCollector.text.includes("Fingerprints for user");
}
}
onExited: (exitCode, exitStatus) => {
if (exitCode !== 0) {
console.warn("fprintd-list command exited with error:", exitCode, exitStatus);
root.fingerprintsConfigured = false;
}
}
}
PamContext { PamContext {
id: pam id: pam
@@ -74,6 +104,7 @@ Scope {
onCompleted: result => { onCompleted: result => {
if (result == PamResult.Success) { if (result == PamResult.Success) {
root.unlocked(root.targetAction); root.unlocked(root.targetAction);
stopFingerPam();
} else { } else {
root.clearText(); root.clearText();
root.unlockInProgress = false; root.unlockInProgress = false;
@@ -83,4 +114,19 @@ Scope {
} }
} }
PamContext {
id: fingerPam
configDirectory: "pam"
config: "fprintd.conf"
onCompleted: result => {
if (result == PamResult.Success) {
root.unlocked(root.targetAction);
stopFingerPam();
} else if (result == PamResult.Error){ // if timeout or etc..
tryFingerUnlock()
}
}
}
} }
@@ -100,6 +100,23 @@ MouseArea {
scale: root.toolbarScale scale: root.toolbarScale
opacity: root.toolbarOpacity opacity: root.toolbarOpacity
// Fingerprint
Loader {
Layout.leftMargin: 10
Layout.rightMargin: 6
Layout.alignment: Qt.AlignVCenter
active: root.context.fingerprintsConfigured
visible: active
sourceComponent: MaterialSymbol {
id: fingerprintIcon
fill: 1
text: "fingerprint"
iconSize: Appearance.font.pixelSize.hugeass
color: Appearance.colors.colOnSurfaceVariant
}
}
ToolbarTextField { ToolbarTextField {
id: passwordBox id: passwordBox
placeholderText: GlobalStates.screenUnlockFailed ? Translation.tr("Incorrect password") : Translation.tr("Enter password") placeholderText: GlobalStates.screenUnlockFailed ? Translation.tr("Incorrect password") : Translation.tr("Enter password")
@@ -126,7 +143,7 @@ MouseArea {
Keys.onPressed: event => { Keys.onPressed: event => {
root.context.resetClearTimer(); root.context.resetClearTimer();
} }
layer.enabled: true layer.enabled: true
layer.effect: OpacityMask { layer.effect: OpacityMask {
maskSource: Rectangle { maskSource: Rectangle {
@@ -136,15 +153,35 @@ MouseArea {
} }
} }
// Shake when wrong password
SequentialAnimation {
id: wrongPasswordShakeAnim
NumberAnimation { target: passwordBox; property: "x"; to: -30; duration: 50 }
NumberAnimation { target: passwordBox; property: "x"; to: 30; duration: 50 }
NumberAnimation { target: passwordBox; property: "x"; to: -15; duration: 40 }
NumberAnimation { target: passwordBox; property: "x"; to: 15; duration: 40 }
NumberAnimation { target: passwordBox; property: "x"; to: 0; duration: 30 }
}
Connections {
target: GlobalStates
function onScreenUnlockFailedChanged() {
if (GlobalStates.screenUnlockFailed) wrongPasswordShakeAnim.restart();
}
}
// We're drawing dots manually // We're drawing dots manually
color: ColorUtils.transparentize(Appearance.colors.colOnLayer1) property bool materialShapeChars: Config.options.lock.materialShapeChars
PasswordChars { color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, materialShapeChars ? 1 : 0)
Loader {
active: passwordBox.materialShapeChars
anchors { anchors {
fill: parent fill: parent
leftMargin: passwordBox.padding leftMargin: passwordBox.padding
rightMargin: passwordBox.padding rightMargin: passwordBox.padding
} }
length: root.context.currentText.length sourceComponent: PasswordChars {
length: root.context.currentText.length
}
} }
} }
@@ -15,7 +15,6 @@ StyledFlickable {
Behavior on contentX { Behavior on contentX {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
} }
rightMargin: 14
Row { Row {
id: dotsRow id: dotsRow
anchors { anchors {
@@ -0,0 +1 @@
auth sufficient pam_fprintd.so
@@ -21,7 +21,7 @@ Scope {
readonly property real osdWidth: Appearance.sizes.osdWidth readonly property real osdWidth: Appearance.sizes.osdWidth
readonly property real widgetWidth: Appearance.sizes.mediaControlsWidth readonly property real widgetWidth: Appearance.sizes.mediaControlsWidth
readonly property real widgetHeight: Appearance.sizes.mediaControlsHeight readonly property real widgetHeight: Appearance.sizes.mediaControlsHeight
property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1
property list<real> visualizerPoints: [] property list<real> visualizerPoints: []
property bool hasPlasmaIntegration: false property bool hasPlasmaIntegration: false
@@ -23,13 +23,25 @@ Toolbar {
// Signals // Signals
signal dismiss() signal dismiss()
MaterialCookie { MaterialShape {
Layout.fillHeight: true Layout.fillHeight: true
Layout.leftMargin: 2 Layout.leftMargin: 2
Layout.rightMargin: 2 Layout.rightMargin: 2
implicitSize: 36 // Intentionally smaller because this one is brighter than others implicitSize: 36 // Intentionally smaller because this one is brighter than others
sides: 10 shape: switch (root.action) {
amplitude: implicitSize / 44 case RegionSelection.SnipAction.Copy:
case RegionSelection.SnipAction.Edit:
return MaterialShape.Shape.Cookie4Sided;
case RegionSelection.SnipAction.Search:
return MaterialShape.Shape.Pentagon;
case RegionSelection.SnipAction.CharRecognition:
return MaterialShape.Shape.Sunny;
case RegionSelection.SnipAction.Record:
case RegionSelection.SnipAction.RecordWithSound:
return MaterialShape.Shape.Gem;
default:
return MaterialShape.Shape.Cookie12Sided;
}
color: Appearance.colors.colPrimary color: Appearance.colors.colPrimary
MaterialSymbol { MaterialSymbol {
anchors.centerIn: parent anchors.centerIn: parent
@@ -5,11 +5,11 @@ import qs.modules.common.widgets
import qs.services import qs.services
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Qt.labs.synchronizer
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
import Qt.labs.synchronizer
PanelWindow { PanelWindow {
id: root id: root
@@ -55,6 +55,20 @@ ContentPage {
} }
} }
ContentSubsection {
visible: Config.options.background.clock.style === "digital"
title: Translation.tr("Digital clock settings")
ConfigSwitch {
buttonIcon: "animation"
text: Translation.tr("Animate time change")
checked: Config.options.background.clock.digital.animateChange
onCheckedChanged: {
Config.options.background.clock.digital.animateChange = checked;
}
}
}
ContentSubsection { ContentSubsection {
visible: Config.options.background.clock.style === "cookie" visible: Config.options.background.clock.style === "cookie"
title: Translation.tr("Cookie clock settings") title: Translation.tr("Cookie clock settings")
@@ -71,6 +85,18 @@ ContentPage {
} }
} }
ConfigSwitch {
buttonIcon: "airwave"
text: Translation.tr("Use old sine wave cookie implementation")
checked: Config.options.background.clock.cookie.useSineCookie
onCheckedChanged: {
Config.options.background.clock.cookie.useSineCookie = checked;
}
StyledToolTip {
text: "Looks a bit softer and more consistent with different number of sides,\nbut has less impressive morphing"
}
}
ConfigSpinBox { ConfigSpinBox {
icon: "add_triangle" icon: "add_triangle"
text: Translation.tr("Sides") text: Translation.tr("Sides")
@@ -530,6 +556,15 @@ ContentPage {
Config.options.lock.showLockedText = checked; Config.options.lock.showLockedText = checked;
} }
} }
ConfigSwitch {
buttonIcon: "shapes"
text: Translation.tr('Use varying shapes for password characters')
checked: Config.options.lock.materialShapeChars
onCheckedChanged: {
Config.options.lock.materialShapeChars = checked;
}
}
} }
ContentSubsection { ContentSubsection {
title: Translation.tr("Style: Blurred") title: Translation.tr("Style: Blurred")
@@ -119,16 +119,17 @@ Item {
} }
} }
property real pageKeyScrollAmount: booruResponseListView.height / 2
Keys.onPressed: (event) => { Keys.onPressed: (event) => {
tagInputField.forceActiveFocus() tagInputField.forceActiveFocus()
if (event.modifiers === Qt.NoModifier) { if (event.modifiers === Qt.NoModifier) {
if (event.key === Qt.Key_PageUp) { if (event.key === Qt.Key_PageUp) {
if (booruResponseListView.atYBeginning) return; if (booruResponseListView.atYBeginning) return;
booruResponseListView.contentY = Math.max(0, booruResponseListView.contentY - booruResponseListView.height / 2) booruResponseListView.contentY = Math.max(0, booruResponseListView.contentY - root.pageKeyScrollAmount)
event.accepted = true event.accepted = true
} else if (event.key === Qt.Key_PageDown) { } else if (event.key === Qt.Key_PageDown) {
if (booruResponseListView.atYEnd) return; if (booruResponseListView.atYEnd) return;
booruResponseListView.contentY = Math.min(booruResponseListView.contentHeight - booruResponseListView.height / 2, booruResponseListView.contentY + booruResponseListView.height / 2) booruResponseListView.contentY = Math.min(booruResponseListView.contentHeight, booruResponseListView.contentY + root.pageKeyScrollAmount)
event.accepted = true event.accepted = true
} }
} }
@@ -171,17 +172,20 @@ Item {
mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4 mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4
property int lastResponseLength: 0 property int lastResponseLength: 0
Connections {
model: ScriptModel { target: root
values: { function onResponsesChanged() {
if(root.responses.length > booruResponseListView.lastResponseLength) { if (root.responses.length > booruResponseListView.lastResponseLength) {
if (booruResponseListView.lastResponseLength > 0 && root.responses[booruResponseListView.lastResponseLength].provider != "system") if (booruResponseListView.lastResponseLength > 0 && root.responses[booruResponseListView.lastResponseLength].provider != "system")
booruResponseListView.contentY = booruResponseListView.contentY + root.scrollOnNewResponse booruResponseListView.contentY = booruResponseListView.contentY + root.scrollOnNewResponse
booruResponseListView.lastResponseLength = root.responses.length booruResponseListView.lastResponseLength = root.responses.length
} }
return root.responses
} }
} }
model: ScriptModel {
values: root.responses
}
delegate: BooruResponse { delegate: BooruResponse {
responseData: modelData responseData: modelData
tagInputField: root.inputField tagInputField: root.inputField
@@ -5,6 +5,7 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects import Qt5Compat.GraphicalEffects
import Qt.labs.synchronizer
Item { Item {
id: root id: root
@@ -58,9 +59,8 @@ Item {
id: tabBar id: tabBar
visible: root.tabButtonList.length > 1 visible: root.tabButtonList.length > 1
tabButtonList: root.tabButtonList tabButtonList: root.tabButtonList
externalTrackedTab: root.selectedTab Synchronizer on currentIndex {
function onCurrentIndexChanged(currentIndex) { property alias source: root.selectedTab
root.selectedTab = currentIndex
} }
} }
@@ -71,7 +71,7 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
spacing: 10 spacing: 10
currentIndex: tabBar.externalTrackedTab currentIndex: root.selectedTab
onCurrentIndexChanged: { onCurrentIndexChanged: {
tabBar.enableIndicatorAnimation = true tabBar.enableIndicatorAnimation = true
root.selectedTab = currentIndex root.selectedTab = currentIndex
@@ -105,7 +105,7 @@ Rectangle {
anchors.right: parent.right anchors.right: parent.right
anchors.leftMargin: 10 anchors.leftMargin: 10
anchors.rightMargin: 10 anchors.rightMargin: 10
spacing: 7 spacing: 12
Item { Item {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
@@ -177,6 +177,20 @@ Rectangle {
ButtonGroup { ButtonGroup {
spacing: 5 spacing: 5
AiMessageControlButton {
id: regenButton
buttonIcon: "refresh"
visible: messageData?.role === 'assistant'
onClicked: {
Ai.regenerate(root.messageIndex)
}
StyledToolTip {
text: Translation.tr("Regenerate")
}
}
AiMessageControlButton { AiMessageControlButton {
id: copyButton id: copyButton
buttonIcon: activated ? "inventory" : "content_copy" buttonIcon: activated ? "inventory" : "content_copy"
@@ -254,28 +268,50 @@ Rectangle {
spacing: 0 spacing: 0
Repeater { Repeater {
model: root.messageBlocks.length model: ScriptModel {
delegate: Loader { values: Array.from({ length: root.messageBlocks.length }, (msg, i) => {
required property int index return ({
property var thisBlock: root.messageBlocks[index] type: root.messageBlocks[i].type
Layout.fillWidth: true })
// property var segment: thisBlock });
property var segmentContent: thisBlock.content }
property var segmentLang: thisBlock.lang
property var messageData: root.messageData
property var editing: root.editing
property var renderMarkdown: root.renderMarkdown
property var enableMouseSelection: root.enableMouseSelection
property bool thinking: root.messageData?.thinking ?? true
property bool done: root.messageData?.done ?? false
property bool completed: thisBlock.completed ?? false
property bool forceDisableChunkSplitting: root.messageData.content.includes("```") delegate: DelegateChooser {
id: messageDelegate
source: thisBlock.type === "code" ? "MessageCodeBlock.qml" : role: "type"
thisBlock.type === "think" ? "MessageThinkBlock.qml" :
"MessageTextBlock.qml"
DelegateChoice { roleValue: "code"; MessageCodeBlock {
required property int index
property var thisBlock: root.messageBlocks[index]
editing: root.editing
renderMarkdown: root.renderMarkdown
enableMouseSelection: root.enableMouseSelection
segmentContent: thisBlock.content
segmentLang: thisBlock.lang
messageData: root.messageData
} }
DelegateChoice { roleValue: "think"; MessageThinkBlock {
required property int index
property var thisBlock: root.messageBlocks[index]
editing: root.editing
renderMarkdown: root.renderMarkdown
enableMouseSelection: root.enableMouseSelection
segmentContent: thisBlock.content
messageData: root.messageData
done: root.messageData?.done ?? false
completed: thisBlock.completed ?? false
} }
DelegateChoice { roleValue: "text"; MessageTextBlock {
required property int index
property var thisBlock: root.messageBlocks[index]
editing: root.editing
renderMarkdown: root.renderMarkdown
enableMouseSelection: root.enableMouseSelection
segmentContent: thisBlock.content
messageData: root.messageData
done: root.messageData?.done ?? false
forceDisableChunkSplitting: root.messageData.content.includes("```")
} }
} }
} }
} }
@@ -13,22 +13,20 @@ import org.kde.syntaxhighlighting
ColumnLayout { ColumnLayout {
id: root id: root
// These are needed on the parent loader // These are needed on the parent loader
property bool editing: parent?.editing ?? false property bool editing: false
property bool renderMarkdown: parent?.renderMarkdown ?? true property bool renderMarkdown: true
property bool enableMouseSelection: parent?.enableMouseSelection ?? false property bool enableMouseSelection: false
property var segmentContent: parent?.segmentContent ?? ({}) property var segmentContent: ({})
property var segmentLang: parent?.segmentLang ?? "txt" property var segmentLang: "txt"
property var messageData: {}
property bool isCommandRequest: segmentLang === "command" property bool isCommandRequest: segmentLang === "command"
property var displayLang: (isCommandRequest ? "bash" : segmentLang) property var displayLang: (isCommandRequest ? "bash" : segmentLang)
property var messageData: parent?.messageData ?? {}
property real codeBlockBackgroundRounding: Appearance.rounding.small property real codeBlockBackgroundRounding: Appearance.rounding.small
property real codeBlockHeaderPadding: 3 property real codeBlockHeaderPadding: 3
property real codeBlockComponentSpacing: 2 property real codeBlockComponentSpacing: 2
spacing: codeBlockComponentSpacing spacing: codeBlockComponentSpacing
anchors.left: parent.left
anchors.right: parent.right
Rectangle { // Code background Rectangle { // Code background
Layout.fillWidth: true Layout.fillWidth: true
@@ -14,17 +14,17 @@ import Quickshell.Hyprland
ColumnLayout { ColumnLayout {
id: root id: root
// These are needed on the parent loader // These are needed on the parent loader
property bool editing: parent?.editing ?? false property bool editing: false
property bool renderMarkdown: parent?.renderMarkdown ?? true property bool renderMarkdown: true
property bool enableMouseSelection: parent?.enableMouseSelection ?? false property bool enableMouseSelection: false
property string segmentContent: parent?.segmentContent ?? ({}) property var segmentContent: ({})
property var messageData: parent?.messageData ?? {} property var messageData: {}
property bool done: parent?.done ?? true property bool done: true
property list<string> renderedLatexHashes: [] property bool forceDisableChunkSplitting: false
property list<string> renderedLatexHashes: []
property string renderedSegmentContent: "" property string renderedSegmentContent: ""
property string shownText: "" property string shownText: ""
property bool forceDisableChunkSplitting: parent?.forceDisableChunkSplitting ?? false
property bool fadeChunkSplitting: !forceDisableChunkSplitting && !editing && !/\n\|/.test(shownText) && Config.options.sidebar.ai.textFadeIn property bool fadeChunkSplitting: !forceDisableChunkSplitting && !editing && !/\n\|/.test(shownText) && Config.options.sidebar.ai.textFadeIn
Layout.fillWidth: true Layout.fillWidth: true
@@ -11,13 +11,13 @@ import Qt5Compat.GraphicalEffects
Item { Item {
id: root id: root
// These are needed on the parent loader // These are needed on the parent loader
property bool editing: parent?.editing ?? false property bool editing: false
property bool renderMarkdown: parent?.renderMarkdown ?? true property bool renderMarkdown: true
property bool enableMouseSelection: parent?.enableMouseSelection ?? false property bool enableMouseSelection: false
property string segmentContent: parent?.segmentContent ?? ({}) property var segmentContent: ({})
property var messageData: parent?.messageData ?? {} property var messageData: {}
property bool done: parent?.done ?? true property bool done: true
property bool completed: parent?.completed ?? false property bool completed: false
property real thinkBlockBackgroundRounding: Appearance.rounding.small property real thinkBlockBackgroundRounding: Appearance.rounding.small
property real thinkBlockHeaderPaddingVertical: 3 property real thinkBlockHeaderPaddingVertical: 3
@@ -17,10 +17,10 @@ AndroidQuickToggleButton {
case PowerProfile.Performance: return "local_fire_department" case PowerProfile.Performance: return "local_fire_department"
} }
statusText: switch(PowerProfiles.profile) { statusText: switch(PowerProfiles.profile) {
case PowerProfile.PowerSaver: return "Power Saver" case PowerProfile.PowerSaver: return "Power Saver"
case PowerProfile.Balanced: return "Balanced" case PowerProfile.Balanced: return "Balanced"
case PowerProfile.Performance: return "Performance" case PowerProfile.Performance: return "Performance"
} }
onClicked: (event) => { onClicked: (event) => {
if (PowerProfiles.hasPerformanceProfile) { if (PowerProfiles.hasPerformanceProfile) {
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
COLOR_FILE_PATH="${XDG_STATE_HOME:-$HOME/.local/state}/quickshell/user/generated/color.txt"
# Define an array of possible VSCode settings file paths for various forks
settings_paths=(
"${XDG_CONFIG_HOME:-$HOME/.config}/Code/User/settings.json"
"${XDG_CONFIG_HOME:-$HOME/.config}/VSCodium/User/settings.json"
"${XDG_CONFIG_HOME:-$HOME/.config}/Code - OSS/User/settings.json"
"${XDG_CONFIG_HOME:-$HOME/.config}/Code - Insiders/User/settings.json"
"${XDG_CONFIG_HOME:-$HOME/.config}/Cursor/User/settings.json"
# Add more paths as needed for other forks
)
new_color=$(cat "$COLOR_FILE_PATH")
# Loop through each settings file path
for CODE_SETTINGS_PATH in "${settings_paths[@]}"; do
if [[ -f "$CODE_SETTINGS_PATH" ]]; then
# Try to update the key if it exists
if grep -q '"material-code.primaryColor"' "$CODE_SETTINGS_PATH"; then
sed -i -E \
"s/(\"material-code.primaryColor\"\s*:\s*\")[^\"]*(\")/\1${new_color}\2/" \
"$CODE_SETTINGS_PATH"
else # If the key is not already there, add it
sed -i '$ s/}/,\n "material-code.primaryColor": "'${new_color}'"\n}/' "$CODE_SETTINGS_PATH"
sed -i '$ s/,\n,/,/' "$CODE_SETTINGS_PATH"
fi
fi
done
@@ -55,18 +55,8 @@ post_process() {
local screen_height="$2" local screen_height="$2"
local wallpaper_path="$3" local wallpaper_path="$3"
handle_kde_material_you_colors & handle_kde_material_you_colors &
"$SCRIPT_DIR/code/material-code-set-color.sh" &
# 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() { check_and_prompt_upscale() {
@@ -769,6 +769,18 @@ Singleton {
root.pendingFilePath = CF.FileUtils.trimFileProtocol(filePath); root.pendingFilePath = CF.FileUtils.trimFileProtocol(filePath);
} }
function regenerate(messageIndex) {
if (messageIndex < 0 || messageIndex >= messageIDs.length) return;
const id = root.messageIDs[messageIndex];
const message = root.messageByID[id];
if (message.role !== "assistant") return;
// Remove all messages after this one
for (let i = root.messageIDs.length - 1; i >= messageIndex; i--) {
root.removeMessage(i);
}
requester.makeRequest();
}
function createFunctionOutputMessage(name, output, includeOutputInChat = true) { function createFunctionOutputMessage(name, output, includeOutputInChat = true) {
return aiMessageComponent.createObject(root, { return aiMessageComponent.createObject(root, {
"role": "user", "role": "user",
+10 -7
View File
@@ -1,9 +1,9 @@
pragma Singleton
pragma ComponentBehavior: Bound
import qs.modules.common import qs.modules.common
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Services.Pipewire import Quickshell.Services.Pipewire
pragma Singleton
pragma ComponentBehavior: Bound
/** /**
* A nice wrapper for default Pipewire audio sink and source. * A nice wrapper for default Pipewire audio sink and source.
@@ -29,12 +29,18 @@ Singleton {
property real lastVolume: 0 property real lastVolume: 0
function onVolumeChanged() { function onVolumeChanged() {
if (!Config.options.audio.protection.enable) return; if (!Config.options.audio.protection.enable) return;
const newVolume = sink.audio.volume;
// when resuming from suspend, we should not write volume to avoid pipewire volume reset issues
if (isNaN(newVolume) || newVolume === undefined || newVolume === null) {
lastReady = false;
lastVolume = 0;
return;
}
if (!lastReady) { if (!lastReady) {
lastVolume = sink.audio.volume; lastVolume = newVolume;
lastReady = true; lastReady = true;
return; return;
} }
const newVolume = sink.audio.volume;
const maxAllowedIncrease = Config.options.audio.protection.maxAllowedIncrease / 100; const maxAllowedIncrease = Config.options.audio.protection.maxAllowedIncrease / 100;
const maxAllowed = Config.options.audio.protection.maxAllowed / 100; const maxAllowed = Config.options.audio.protection.maxAllowed / 100;
@@ -45,9 +51,6 @@ Singleton {
root.sinkProtectionTriggered(Translation.tr("Exceeded max allowed")); root.sinkProtectionTriggered(Translation.tr("Exceeded max allowed"));
sink.audio.volume = Math.min(lastVolume, maxAllowed); sink.audio.volume = Math.min(lastVolume, maxAllowed);
} }
if (sink.ready && (isNaN(sink.audio.volume) || sink.audio.volume === undefined || sink.audio.volume === null)) {
sink.audio.volume = 0;
}
lastVolume = sink.audio.volume; lastVolume = sink.audio.volume;
} }
} }
@@ -1,6 +1,6 @@
pkgname=illogical-impulse-audio pkgname=illogical-impulse-audio
pkgver=1.0 pkgver=1.0
pkgrel=1 pkgrel=2
pkgdesc='Illogical Impulse Audio Dependencies' pkgdesc='Illogical Impulse Audio Dependencies'
arch=(any) arch=(any)
license=(None) license=(None)
@@ -8,6 +8,7 @@ depends=(
cava cava
pavucontrol-qt pavucontrol-qt
wireplumber wireplumber
pipewire-pulse
libdbusmenu-gtk3 libdbusmenu-gtk3
playerctl playerctl
) )
@@ -1,20 +1,22 @@
pkgname=illogical-impulse-basic pkgname=illogical-impulse-basic
pkgver=1.0 pkgver=1.0
pkgrel=1 pkgrel=2
pkgdesc='Illogical Impulse Basic Dependencies' pkgdesc='Illogical Impulse Basic Dependencies'
arch=(any) arch=(any)
license=(None) license=(None)
depends=( depends=(
axel axel
bc bc
coreutils coreutils
cliphist cliphist
cmake cmake
curl curl
rsync wget
wget ripgrep
ripgrep jq
jq meson
meson xdg-user-dirs
xdg-user-dirs # Used in install script
rsync
go-yq # https://github.com/mikefarah/yq
) )
@@ -25,4 +25,5 @@ RDEPEND="
dev-python/jq dev-python/jq
dev-build/meson dev-build/meson
x11-misc/xdg-user-dirs x11-misc/xdg-user-dirs
app-misc/yq-go
" "
+2 -73
View File
@@ -6,24 +6,10 @@
**NOTE: The sdata/dist-nix is not for NixOS but every distro, using Nix and home-manager.** **NOTE: The sdata/dist-nix is not for NixOS but every distro, using Nix and home-manager.**
## plan ## plan
TODO:
Write a proper `flake.nix` and optionally `home.nix` and other files under `./sdata/dist-nix/iiqs-hm/` to install all dependencies that `./sdata/dist-arch/install-deps.sh` does. (**excluding** the screenlock)
TODO:
In this script, implement the process below:
1. Warning user about "this script is only experimental and must only use it at your own risks.", and prompt `y/N` (default N) before proceeding.
2. If nix not installed:
1. install nix via [NixOS/experimental-nix-installer](https://github.com/NixOS/experimental-nix-installer)
2. Enable nix for shell
- Update: Skip this step cuz the nix-installer will handle it automatically e.g. in `/etc/zsh/zshrc`.
3. Ensure the experimental feature, Nix Flake, is enabled.
3. cd to `iiqs-hm` and use something like `home-manager switch --flake .#iiqs` to install the dependencies.
4. Install screen lock using system package manager of the current distro.
Note that this script must be idempotent. Note that this script must be idempotent.
TODO: TODO:
Write guide for people already use nix, so they can manually grab things from this repo to their own Nix/home-manager configurations to install the dependencies. - [ ] Write a proper `flake.nix` and `home.nix` and other files under `dist-nix/home-manager/` to install all dependencies that `dist-arch/` does. (**excluding** the screenlock)
## Attentions ## Attentions
### PAM ### PAM
@@ -37,61 +23,4 @@ The problem could be solved by using the system-provided libpam instead.
See also https://github.com/caelestia-dots/shell/issues/668 See also https://github.com/caelestia-dots/shell/issues/668
### NixGL ### NixGL
On non-NixOS distros, packages installed via home-manager have problem accessing GPU, especially Hyprland because it requires GPU acceleration to launch. `nixGL` should be used to address the problem. Example code in `home.nix`: On non-NixOS distros, packages installed via home-manager have problem accessing GPU, especially Hyprland because it requires GPU acceleration to launch. `nixGL` should be used to address the problem.
```
{ config, lib, pkgs, nixgl, ... }:
{
nixGL.packages = nixgl.packages;
nixGL.defaultWrapper = "mesa";
# other lines not showed here ...
home = {
packages = with pkgs; [
cowsay # normal packages that does not need nixGL
lolcat
# other lines not showed here ...
]
++ [
(config.lib.nixGL.wrap pkgs.firefox-bin)
(config.lib.nixGL.wrap pkgs.hyprland)
# other lines not showed here ...
];
# other lines not showed here ...
};
}
```
And in `flake.nix`:
```nix
{
inputs = {
nixpkgs.url = "nixpkgs/nixos-25.05";
home-manager = {
url = "github:nix-community/home-manager/release-25.05";
inputs.nixpkgs.follows = "nixpkgs";
};
hyprland = {
url = "github:hyprwm/Hyprland";
};
nixgl.url = "github:nix-community/nixGL";
};
outputs = { nixpkgs, home-manager, nixgl, ... }:
let
lib = nixpkgs.lib;
system = "x86_64-linux";
pkgs = import nixpkgs {
inherit system;
overlays = [ nixgl.overlay ];
};
in {
homeConfigurations = {
mydot = home-manager.lib.homeManagerConfiguration {
inherit pkgs;
extraSpecialArgs = { inherit nixgl; };
modules = [ ./home.nix ];
};
};
};
}
```
+542
View File
@@ -0,0 +1,542 @@
{
"nodes": {
"aquamarine": {
"inputs": {
"hyprutils": [
"hyprland",
"hyprutils"
],
"hyprwayland-scanner": [
"hyprland",
"hyprwayland-scanner"
],
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1760101617,
"narHash": "sha256-8jf/3ZCi+B7zYpIyV04+3wm72BD7Z801IlOzsOACR7I=",
"owner": "hyprwm",
"repo": "aquamarine",
"rev": "1826a9923881320306231b1c2090379ebf9fa4f8",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "aquamarine",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"hyprland",
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1758463745,
"narHash": "sha256-uhzsV0Q0I9j2y/rfweWeGif5AWe0MGrgZ/3TjpDYdGA=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "3b955f5f0a942f9f60cdc9cacb7844335d0f21c3",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "release-25.05",
"repo": "home-manager",
"type": "github"
}
},
"hyprcursor": {
"inputs": {
"hyprlang": [
"hyprland",
"hyprlang"
],
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1753964049,
"narHash": "sha256-lIqabfBY7z/OANxHoPeIrDJrFyYy9jAM4GQLzZ2feCM=",
"owner": "hyprwm",
"repo": "hyprcursor",
"rev": "44e91d467bdad8dcf8bbd2ac7cf49972540980a5",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprcursor",
"type": "github"
}
},
"hyprgraphics": {
"inputs": {
"hyprutils": [
"hyprland",
"hyprutils"
],
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1760445448,
"narHash": "sha256-fXGjL6dw31FPFRrmIemzGiNSlfvEJTJNsmadZi+qNhI=",
"owner": "hyprwm",
"repo": "hyprgraphics",
"rev": "50fb9f069219f338a11cf0bcccb9e58357d67757",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprgraphics",
"type": "github"
}
},
"hyprland": {
"inputs": {
"aquamarine": "aquamarine",
"hyprcursor": "hyprcursor",
"hyprgraphics": "hyprgraphics",
"hyprland-protocols": "hyprland-protocols",
"hyprland-qtutils": "hyprland-qtutils",
"hyprlang": "hyprlang",
"hyprutils": "hyprutils",
"hyprwayland-scanner": "hyprwayland-scanner",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks",
"systems": "systems",
"xdph": "xdph"
},
"locked": {
"lastModified": 1761780088,
"narHash": "sha256-ylKrWQeIAGyysfHbgZpcWUs9UsbiOBIVXTPqaiV3lf0=",
"owner": "hyprwm",
"repo": "Hyprland",
"rev": "6ade4d58cab67e18aa758ef664e36421cab4d8b2",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "Hyprland",
"type": "github"
}
},
"hyprland-protocols": {
"inputs": {
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1759610243,
"narHash": "sha256-+KEVnKBe8wz+a6dTLq8YDcF3UrhQElwsYJaVaHXJtoI=",
"owner": "hyprwm",
"repo": "hyprland-protocols",
"rev": "bd153e76f751f150a09328dbdeb5e4fab9d23622",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprland-protocols",
"type": "github"
}
},
"hyprland-qt-support": {
"inputs": {
"hyprlang": [
"hyprland",
"hyprland-qtutils",
"hyprlang"
],
"nixpkgs": [
"hyprland",
"hyprland-qtutils",
"nixpkgs"
],
"systems": [
"hyprland",
"hyprland-qtutils",
"systems"
]
},
"locked": {
"lastModified": 1749154592,
"narHash": "sha256-DO7z5CeT/ddSGDEnK9mAXm1qlGL47L3VAHLlLXoCjhE=",
"owner": "hyprwm",
"repo": "hyprland-qt-support",
"rev": "4c8053c3c888138a30c3a6c45c2e45f5484f2074",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprland-qt-support",
"type": "github"
}
},
"hyprland-qtutils": {
"inputs": {
"hyprland-qt-support": "hyprland-qt-support",
"hyprlang": [
"hyprland",
"hyprlang"
],
"hyprutils": [
"hyprland",
"hyprland-qtutils",
"hyprlang",
"hyprutils"
],
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1759080228,
"narHash": "sha256-RgDoAja0T1hnF0pTc56xPfLfFOO8Utol2iITwYbUhTk=",
"owner": "hyprwm",
"repo": "hyprland-qtutils",
"rev": "629b15c19fa4082e4ce6be09fdb89e8c3312aed7",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprland-qtutils",
"type": "github"
}
},
"hyprlang": {
"inputs": {
"hyprutils": [
"hyprland",
"hyprutils"
],
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1758927902,
"narHash": "sha256-LZgMds7M94+vuMql2bERQ6LiFFdhgsEFezE4Vn+Ys3A=",
"owner": "hyprwm",
"repo": "hyprlang",
"rev": "4dafa28d4f79877d67a7d1a654cddccf8ebf15da",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprlang",
"type": "github"
}
},
"hyprutils": {
"inputs": {
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1759619523,
"narHash": "sha256-r1ed7AR2ZEb2U8gy321/Xcp1ho2tzn+gG1te/Wxsj1A=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "3df7bde01efb3a3e8e678d1155f2aa3f19e177ef",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprutils",
"type": "github"
}
},
"hyprwayland-scanner": {
"inputs": {
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1755184602,
"narHash": "sha256-RCBQN8xuADB0LEgaKbfRqwm6CdyopE1xIEhNc67FAbw=",
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"rev": "b3b0f1f40ae09d4447c20608e5a4faf8bf3c492d",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"type": "github"
}
},
"nixgl": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1752054764,
"narHash": "sha256-Ob/HuUhANoDs+nvYqyTKrkcPXf4ZgXoqMTQoCK0RFgQ=",
"owner": "nix-community",
"repo": "nixGL",
"rev": "a8e1ce7d49a149ed70df676785b07f63288f53c5",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixGL",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1761114652,
"narHash": "sha256-f/QCJM/YhrV/lavyCVz8iU3rlZun6d+dAiC3H+CDle4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "01f116e4df6a15f4ccdffb1bcd41096869fb385c",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1746378225,
"narHash": "sha256-OeRSuL8PUjIfL3Q0fTbNJD/fmv1R+K2JAOqWJd3Oceg=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "93e8cdce7afc64297cfec447c311470788131cd9",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1761597516,
"narHash": "sha256-wxX7u6D2rpkJLWkZ2E932SIvDJW8+ON/0Yy8+a5vsDU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "daf6dc47aa4b44791372d6139ab7b25269184d55",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-25.05",
"type": "indirect"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"hyprland",
"nixpkgs"
]
},
"locked": {
"lastModified": 1760663237,
"narHash": "sha256-BflA6U4AM1bzuRMR8QqzPXqh8sWVCNDzOdsxXEguJIc=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "ca5b894d3e3e151ffc1db040b6ce4dcc75d31c37",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"home-manager": "home-manager",
"hyprland": "hyprland",
"nixgl": "nixgl",
"nixpkgs": "nixpkgs_3"
}
},
"systems": {
"locked": {
"lastModified": 1689347949,
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
"owner": "nix-systems",
"repo": "default-linux",
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default-linux",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"xdph": {
"inputs": {
"hyprland-protocols": [
"hyprland",
"hyprland-protocols"
],
"hyprlang": [
"hyprland",
"hyprlang"
],
"hyprutils": [
"hyprland",
"hyprutils"
],
"hyprwayland-scanner": [
"hyprland",
"hyprwayland-scanner"
],
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1760713634,
"narHash": "sha256-5HXelmz2x/uO26lvW7MudnadbAfoBnve4tRBiDVLtOM=",
"owner": "hyprwm",
"repo": "xdg-desktop-portal-hyprland",
"rev": "753bbbdf6a052994da94062e5b753288cef28dfb",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "xdg-desktop-portal-hyprland",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+41
View File
@@ -0,0 +1,41 @@
# flake.nix
{
description = "illogical-impulse";
inputs = {
nixpkgs.url = "nixpkgs/nixos-25.05";
home-manager = {
url = "github:nix-community/home-manager/release-25.05";
inputs.nixpkgs.follows = "nixpkgs";
};
hyprland = {
url = "github:hyprwm/Hyprland";
};
nixgl.url = "github:nix-community/nixGL";
};
outputs = { nixpkgs, home-manager, nixgl, ... }:
let
home_attrs = rec {
username = import ./username.nix;
homeDirectory = "/home/${username}";
stateVersion = "25.05";
};
system = "x86_64-linux";
lib = nixpkgs.lib;
pkgs = import nixpkgs {
inherit system;
};
in {
homeConfigurations = {
illogical_impulse = home-manager.lib.homeManagerConfiguration {
inherit pkgs;
extraSpecialArgs = { inherit nixgl home_attrs; };
modules = [
./home.nix
];
};
};
};
}
+57
View File
@@ -0,0 +1,57 @@
{ config, lib, pkgs, nixgl, home_attrs, ... }:
{
programs.home-manager.enable = true;
nixGL.packages = nixgl.packages;
nixGL.defaultWrapper = "mesa";
xdg.portal = {
enable = true;
extraPortals = with pkgs; [
xdg-desktop-portal-gnome
xdg-desktop-portal-gtk
xdg-desktop-portal-wlr
];
config.hyprland = {
default = [ "hyprland" "gtk" ];
"org.freedesktop.impl.portal.ScreenCast" = [
"gnome"
];
};
};
## Allow fontconfig to discover fonts in home.packages
fonts.fontconfig.enable = true;
# home.sessionVariables.NIXOS_OZONE_WL = "1";
wayland.windowManager.hyprland = {
## Make sure home-manager not generate ~/.config/hypr/hyprland.conf
systemd.enable = false; plugins = []; settings = {}; extraConfig = "";
enable = true;
## Use NixGL
package = config.lib.nixGL.wrap pkgs.hyprland;
};
home = {
packages = with pkgs; [
##### Sure #####
## Basic cli tool
## inetutils: provides hostname, ifconfig, ping, etc.
## libnotify: provides notify-send
jq rsync inetutils libnotify
## Media related
brightnessctl playerctl pavucontrol
## Clipboard/Emoji
wl-clipboard cliphist
## Terminal and shell
foot cowsay lolcat
##### Fonts/Icons/Cursors/Decoration #####
fontconfig
##### Other basic things #####
dbus xorg.xlsclients networkmanager
##### Not work, to be solved #####
# swaylock pamtester
];
}//home_attrs;
}
+126 -1
View File
@@ -1,4 +1,129 @@
# This script is meant to be sourced. # This script is meant to be sourced.
# It's not for directly running. # It's not for directly running.
# This file is currently WIP. function install_home-manager(){
# https://nix-community.github.io/home-manager/index.xhtml#sec-install-standalone
local cmd=home-manager
# Maybe installed already, just not sourced yet
try source $HOME/.nix-profile/etc/profile.d/hm-session-vars.sh
command -v $cmd && return
x nix-channel --add https://nixos.org/channels/nixos-25.05 nixpkgs-home
x nix-channel --add https://github.com/nix-community/home-manager/archive/release-25.05.tar.gz home-manager
x nix-channel --update
x env NIX_PATH="nixpkgs=$HOME/.nix-defexpr/channels/nixpkgs-home" nix-shell '<home-manager>' -A install
command -v $cmd && return
echo "Failed in installing $cmd."
echo "Please install it by yourself and then retry."
return 1
}
function install_nix(){
# https://github.com/NixOS/experimental-nix-installer
local cmd=nix
x mkdir -p ${REPO_ROOT}/cache
x curl -JLo ${REPO_ROOT}/cache/nix-installer https://artifacts.nixos.org/experimental-installer
x sh ${REPO_ROOT}/cache/nix-installer install
try source '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'
command -v $cmd && return
echo "Failed in installing $cmd."
echo "Please install it by yourself and then retry."
return 1
}
function install_curl(){
local cmd=curl
if [[ "$OS_DISTRO_ID" == "arch" || "$OS_DISTRO_ID_LIKE" == "arch" || "$OS_DISTRO_ID" == "cachyos" ]]; then
x sudo pacman -Syu
x sudo pacman -S --noconfirm $cmd
elif [[ "$OS_DISTRO_ID" == "debian" || "$OS_DISTRO_ID_LIKE" == "debian" ]]; then
x sudo apt update
x sudo apt install $cmd
fi
command -v $cmd && return
echo "Failed in installing $cmd."
echo "Please install it by yourself and then retry."
return 1
}
function install_zsh(){
local cmd=zsh
if [[ "$OS_DISTRO_ID" == "arch" || "$OS_DISTRO_ID_LIKE" == "arch" || "$OS_DISTRO_ID" == "cachyos" ]]; then
x sudo pacman -Syu
x sudo pacman -S --noconfirm $cmd
elif [[ "$OS_DISTRO_ID" == "debian" || "$OS_DISTRO_ID_LIKE" == "debian" ]]; then
x sudo apt update
x sudo apt install $cmd
fi
command -v $cmd && return
echo "Failed in installing $cmd."
echo "Please install it by yourself and then retry."
return 1
}
function install_swaylock(){
local cmd=swaylock
echo "Detecting command \"$cmd\"..."
command -v $cmd && return
echo "Command \"$cmd\" not found, try to install..."
if [[ "$OS_DISTRO_ID" == "arch" || "$OS_DISTRO_ID_LIKE" == "arch" || "$OS_DISTRO_ID" == "cachyos" ]]; then
x sudo pacman -Syu
x sudo pacman -S --noconfirm $cmd
elif [[ "$OS_DISTRO_ID" == "debian" || "$OS_DISTRO_ID_LIKE" == "debian" ]]; then
x sudo apt update
x sudo apt install $cmd
fi
command -v $cmd && return
echo "Failed in installing $cmd."
echo "Please install it by yourself and then retry."
return 1
}
function hm_deps(){
SETUP_HM_DIR="${REPO_ROOT}/sdata/dist-nix/home-manager"
SETUP_USERNAME_NIXFILE="${SETUP_HM_DIR}/username.nix"
echo "\"$(whoami)\"" > "${SETUP_USERNAME_NIXFILE}"
x git add "${SETUP_USERNAME_NIXFILE}"
cd $SETUP_HM_DIR
x home-manager switch --flake .#illogical_impulse \
--extra-experimental-features nix-command \
--extra-experimental-features flakes
cd $REPO_ROOT
x git rm -f "${SETUP_USERNAME_NIXFILE}"
}
##################################################
##################################################
if ! command -v curl >/dev/null 2>&1;then
echo -e "${STY_YELLOW}[$0]: \"curl\" not found.${STY_RST}"
showfun install_curl
v install_curl
fi
if ! command -v zsh >/dev/null 2>&1;then
echo -e "${STY_YELLOW}[$0]: \"zsh\" not found.${STY_RST}"
showfun install_zsh
v install_zsh
fi
if ! command -v swaylock >/dev/null 2>&1;then
echo -e "${STY_YELLOW}[$0]: \"swaylock\" not found.${STY_RST}"
showfun install_swaylock
v install_swaylock
fi
if ! command -v nix >/dev/null 2>&1;then
echo -e "${STY_YELLOW}[$0]: \"nix\" not found.${STY_RST}"
showfun install_nix
v install_nix
fi
if ! command -v home-manager >/dev/null 2>&1;then
echo -e "${STY_YELLOW}[$0]: \"home-manager\" not found.${STY_RST}"
showfun install_home-manager
v install_home-manager
fi
showfun hm_deps
v hm_deps
+51 -1
View File
@@ -69,7 +69,11 @@ function pause(){
fi fi
} }
function remove_bashcomments_emptylines(){ function remove_bashcomments_emptylines(){
mkdir -p "$(dirname "$2")" && cat "$1" | sed -e 's/#.*//' -e '/^[[:space:]]*$/d' > "$2" echo "pwd=$(pwd)"
echo "input=$1"
echo "output=$2"
mkdir -p "$(dirname "$2")"
cat "$1" | sed -e 's/#.*//' -e '/^[[:space:]]*$/d' > "$2"
} }
function prevent_sudo_or_root(){ function prevent_sudo_or_root(){
case $(whoami) in case $(whoami) in
@@ -309,3 +313,49 @@ function auto_get_git_submodule(){
x git submodule update --init --recursive x git submodule update --init --recursive
fi fi
} }
function backup_clashing_targets(){
# For non-recursive dirs/files under target_dir, only backup those which clashes with the ones under source_dir
# However, ignore the ones listed in ignored_list
# Deal with arguments
local source_dir="$1"
local target_dir="$2"
local backup_dir="$3"
local -a ignored_list=("${@:4}")
# Find clash dirs/files, save as clash_list
local clash_list=()
local source_list=($(ls -A "$source_dir"))
local target_list=($(ls -A "$target_dir"))
local -A target_map
for i in "${target_list[@]}"; do
target_map["$i"]=1
done
for i in "${source_list[@]}"; do
if [[ -n "${target_map[$i]}" ]]; then
clash_list+=("$i")
fi
done
local -A delk
for del in "${ignored_list[@]}" ; do delk[$del]=1 ; done
for k in "${!clash_list[@]}" ; do
[ "${delk[${clash_list[$k]}]-}" ] && unset 'clash_list[k]'
done
clash_list=("${clash_list[@]}")
# Construct args_includes for rsync
local args_includes=()
for i in "${clash_list[@]}"; do
if [[ -d "$target_dir/$i" ]]; then
args_includes+=(--include="/$i/")
args_includes+=(--include="/$i/**")
else
args_includes+=(--include="/$i")
fi
done
args_includes+=(--exclude='*')
x mkdir -p $backup_dir
x rsync -av --progress "${args_includes[@]}" "$target_dir/" "$backup_dir/"
}
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Check whether pkgs exist in AUR or repos of Arch.
#
# Do NOT abuse this since it consumes extra bandwidth from AUR server.
pkglistfile=$(mktemp)
pkglistfile_orig=${LIST_FILE_PATH}
pkglistfile_orig_s=${REPO_ROOT}/cache/dependencies_stripped.conf
#if ! "$(command -v curl)";then
# echo "Please install curl first.";exit 1
#fi
#if ! "$(command -v gzip)";then
# echo "Please install gzip first.";exit 1
#fi
#if ! "$(command -v pacman)";then
# echo "pacman not found, aborting...";exit 1
#fi
remove_bashcomments_emptylines $pkglistfile_orig $pkglistfile_orig_s
cat $pkglistfile_orig_s | sed "s_\ _\n_g" > $pkglistfile
echo "The non-existent pkgs in $pkglistfile_orig are listed as follows."
# Borrowed from https://bbs.archlinux.org/viewtopic.php?pid=1490795#p1490795
comm -23 <(sort -u $pkglistfile) <(sort -u <(curl https://aur.archlinux.org/packages.gz | gzip -cd | sort) <(pacman -Ssq))
echo "End of list. If nothing appears, then all pkgs exist."
rm $pkglistfile
+33
View File
@@ -0,0 +1,33 @@
# Handle args for subcmd: checkdeps
# shellcheck shell=bash
showhelp(){
echo -e "Syntax: $0 checkdeps [OPTIONS] <LIST_FILE_PATH>...
Check whether pkgs listed in <LIST_FILE_PATH> exist in AUR or repos of Arch.
Options:
-h, --help Show this help message
"
}
# `man getopt` to see more
para=$(getopt \
-o h \
-l help \
-n "$0" -- "$@")
[ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1
#####################################################################################
eval set -- "$para"
while true ; do
case "$1" in
-h|--help) showhelp;exit;;
--) shift;break ;;
*) sleep 0 ;;
esac
done
if [[ -f "$1" ]]; then
echo "Using list file \"$1\".";LIST_FILE_PATH="$1";shift 1
else
echo "Wrong path \"$1\" of list file.";exit 1
fi
+1
View File
@@ -541,6 +541,7 @@ if git remote get-url origin &>/dev/null; then
log_info "Pulling changes from origin/$current_branch..." log_info "Pulling changes from origin/$current_branch..."
if git pull; then if git pull; then
log_success "Successfully pulled latest changes" log_success "Successfully pulled latest changes"
git submodule update --init --recursive
else else
log_warning "Failed to pull changes from remote. Continuing with local repository..." log_warning "Failed to pull changes from remote. Continuing with local repository..."
log_info "You may need to resolve conflicts manually later." log_info "You may need to resolve conflicts manually later."
+1
View File
@@ -844,6 +844,7 @@ if git remote get-url origin &>/dev/null; then
else else
if git pull --ff-only; then if git pull --ff-only; then
log_success "Successfully pulled latest changes" log_success "Successfully pulled latest changes"
git submodule update --init --recursive
# Verify we actually got new commits # Verify we actually got new commits
if git rev-parse --verify HEAD@{1} &>/dev/null; then if git rev-parse --verify HEAD@{1} &>/dev/null; then
if [[ "$(git rev-parse HEAD)" == "$(git rev-parse HEAD@{1})" ]]; then if [[ "$(git rev-parse HEAD)" == "$(git rev-parse HEAD@{1})" ]]; then
+1 -3
View File
@@ -9,10 +9,8 @@ printf "${STY_CYAN}[$0]: Hi there! Before we start:${STY_RST}\n"
printf "\n" printf "\n"
printf "${STY_PURPLE}${STY_BOLD}[NEW] illogical-impulse is now powered by Quickshell.${STY_RST}\n" printf "${STY_PURPLE}${STY_BOLD}[NEW] illogical-impulse is now powered by Quickshell.${STY_RST}\n"
printf "${STY_PURPLE}" printf "${STY_PURPLE}"
printf '# NOTE: illogical-impulse on AGS is no longer supported.\n'
printf '# If you were using the old version with AGS and would like to keep it, do not run this script.\n' printf '# If you were using the old version with AGS and would like to keep it, do not run this script.\n'
printf '# The AGS version, although uses less memory, has much worse performance (it uses Gtk3). \n'
printf '# If you aren'\''t running on ewaste, the Quickshell version is recommended. \n'
printf "# If you would like the AGS version anyway, run the following to switch to its branch first:\n ${STY_INVERT} git checkout ii-ags && ./install.sh ${STY_RST}\n"
printf "\n" printf "\n"
pause pause
printf "${STY_CYAN}${STY_BOLD}Quick overview about what this script does:${STY_RST}\n" printf "${STY_CYAN}${STY_BOLD}Quick overview about what this script does:${STY_RST}\n"
+240
View File
@@ -0,0 +1,240 @@
# This script is meant to be sourced.
# It's not for directly running.
# TODO: https://github.com/end-4/dots-hyprland/issues/2137
printf "${STY_CYAN}[$0]: 3. Copying config files (experimental YAML-based)${STY_RST}\n"
# Configuration file
CONFIG_FILE="sdata/subcmd-install/3.files.yaml"
# =============================================================================
# ORIGINAL FUNCTIONS
# =============================================================================
function warning_rsync_delete(){
printf "${STY_YELLOW}"
printf "The command below uses --delete for rsync which overwrites the destination folder.\n"
printf "${STY_RST}"
}
function warning_rsync_normal(){
printf "${STY_YELLOW}"
printf "The command below uses rsync which overwrites the destination.\n"
printf "${STY_RST}"
}
function backup_configs(){
backup_clashing_targets dots/.config $XDG_CONFIG_HOME "${BACKUP_DIR}/.config"
backup_clashing_targets dots/.local/share $XDG_DATA_HOME "${BACKUP_DIR}/.local/share"
printf "${STY_BLUE}Backup into \"${BACKUP_DIR}\" finished.${STY_RST}\n"
}
function ask_backup_configs(){
showfun backup_clashing_targets
printf "${STY_RED}"
printf "Would you like to backup clashing dirs/files under \"$XDG_CONFIG_HOME\" and \"$XDG_DATA_HOME\" to \"$BACKUP_DIR\"?"
printf "${STY_RST}"
while true;do
echo " y = Yes, backup"
echo " n/s = No, skip to next"
local p; read -p "====> " p
case $p in
[yY]) echo -e "${STY_BLUE}OK, doing backup...${STY_RST}" ;local backup=true;break ;;
[nNsS]) echo -e "${STY_BLUE}Alright, skipping...${STY_RST}" ;local backup=false;break ;;
*) echo -e "${STY_RED}Please enter [y/n].${STY_RST}";;
esac
done
if $backup;then backup_configs;fi
}
function auto_backup_configs(){
# Backup when $BACKUP_DIR does not exist
if [[ ! -d "$BACKUP_DIR" ]]; then backup_configs;fi
}
#####################################################################################
showfun auto_get_git_submodule
v auto_get_git_submodule
# In case some dirs does not exists
v mkdir -p $XDG_BIN_HOME $XDG_CACHE_HOME $XDG_CONFIG_HOME $XDG_DATA_HOME/icons
if [[ ! "${SKIP_BACKUP}" == true ]]; then
case $ask in
false) auto_backup_configs ;;
*) ask_backup_configs ;;
esac
fi
# Run user preference wizard
case $ask in
false) sleep 0 ;;
*) wizard_update_preferences ;;
esac
# Read patterns from YAML file
readarray patterns < <(yq -o=j -I=0 '.patterns[]' "$CONFIG_FILE")
# Process each pattern
for pattern in "${patterns[@]}"; do
from=$(echo "$pattern" | yq '.from' - | envsubst)
to=$(echo "$pattern" | yq '.to' - | envsubst)
mode=$(echo "$pattern" | yq '.mode' - | envsubst)
condition=$(echo "$pattern" | yq '.condition // "true"')
# Handle fontconfig fontset override
# If FONTSET_DIR_NAME is set and this is the fontconfig pattern, use the fontset instead
if [[ "$from" == "dots/.config/fontconfig" ]] && [[ -n "${FONTSET_DIR_NAME:-}" ]]; then
from="dots-extra/fontsets/${FONTSET_DIR_NAME}"
echo "Using fontset \"${FONTSET_DIR_NAME}\" for fontconfig"
fi
# Check if pattern should be processed
if ! should_process_pattern "$pattern"; then
# Format condition message nicely
if [[ "$condition" != "true" ]]; then
cond_type=$(echo "$condition" | yq -r '.type // ""')
cond_value=$(echo "$condition" | yq -r '.value // ""')
if [[ -n "$cond_type" && -n "$cond_value" ]]; then
echo "Skipping $from -> $to (condition not met: $cond_type == '$cond_value')"
else
echo "Skipping $from -> $to (condition not met)"
fi
else
echo "Skipping $from -> $to (condition not met)"
fi
continue
fi
echo "Processing: $from -> $to (mode: $mode)"
# Build exclude arguments for rsync
excludes=()
if echo "$pattern" | yq -e '.excludes' >/dev/null 2>&1; then
while IFS= read -r exclude; do
excludes+=(--exclude "$exclude")
done < <(echo "$pattern" | yq -r '.excludes[]')
fi
# Check if source exists
if [[ ! -e "$from" ]]; then
echo "Warning: Source does not exist: $from (skipping)"
continue
fi
# Ensure destination directory exists for files
if [[ -f "$from" ]]; then
v mkdir -p "$(dirname "$to")"
fi
# Execute based on mode
case $mode in
"sync")
if [[ -d "$from" ]]; then
warning_rsync_delete
v rsync -av --delete "${excludes[@]}" "$from/" "$to/"
else
warning_rsync_normal
# For files, don't use trailing slash and don't use --delete
v rsync -av "${excludes[@]}" "$from" "$to"
fi
;;
"soft")
warning_rsync_normal
if [[ -d "$from" ]]; then
v rsync -av "${excludes[@]}" "$from/" "$to/"
else
# For files, don't use trailing slash
v rsync -av "${excludes[@]}" "$from" "$to"
fi
;;
"hard")
v cp -r "$from" "$to"
;;
"hard-backup")
if [[ -e "$to" ]]; then
if files_are_same "$from" "$to"; then
echo "Files are identical, skipping backup"
else
backup_number=$(get_next_backup_number "$to")
v mv "$to" "$to.old.$backup_number"
v cp -r "$from" "$to"
fi
else
v cp -r "$from" "$to"
fi
;;
"soft-backup")
if [[ -e "$to" ]]; then
if files_are_same "$from" "$to"; then
echo "Files are identical, skipping backup"
else
v cp -r "$from" "$to.new"
fi
else
v cp -r "$from" "$to"
fi
;;
"skip")
echo "Skipping $from"
;;
"skip-if-exists")
if [[ -e "$to" ]]; then
echo "Skipping $from (destination exists)"
else
v cp -r "$from" "$to"
fi
;;
*)
echo "Unknown mode: $mode"
;;
esac
done
# Prevent hyprland from not fully loaded
sleep 1
try hyprctl reload
# Rest of original script logic...
# (Keep the existing warning messages and file checks)
warn_files=()
warn_files_tests=()
warn_files_tests+=(/usr/local/lib/{GUtils-1.0.typelib,Gvc-1.0.typelib,libgutils.so,libgvc.so})
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)
warn_files_tests+=(/usr/local/share/licenses/ttf-gabarito)
warn_files_tests+=(/usr/local/share/icons/OneUI{,-dark,-light})
warn_files_tests+=(/usr/local/share/icons/Bibata-Modern-Classic)
warn_files_tests+=(/usr/local/bin/{LaTeX,res})
for i in ${warn_files_tests[@]}; do
echo $i
test -f $i && warn_files+=($i)
test -d $i && warn_files+=($i)
done
#####################################################################################
# TODO: output the logs below to a temp file and cat that file, also show the path of the file so users will be able to read it again.
printf "\n"
printf "\n"
printf "\n"
printf "${STY_CYAN}[$0]: Finished${STY_RESET}\n"
printf "\n"
printf "${STY_CYAN}When starting Hyprland from your display manager (login screen) ${STY_RED} DO NOT SELECT UWSM ${STY_RESET}\n"
printf "\n"
printf "${STY_CYAN}If you are already running Hyprland,${STY_RESET}\n"
printf "${STY_CYAN}Press ${STY_BG_CYAN} Ctrl+Super+T ${STY_BG_CYAN} to select a wallpaper${STY_RESET}\n"
printf "${STY_CYAN}Press ${STY_BG_CYAN} Super+/ ${STY_CYAN} for a list of keybinds${STY_RESET}\n"
printf "\n"
printf "${STY_CYAN}For suggestions/hints after installation:${STY_RESET}\n"
printf "${STY_CYAN}${STY_UNDERLINE} https://ii.clsty.link/en/ii-qs/01setup/#post-installation ${STY_RESET}\n"
printf "\n"
if [[ -z "${ILLOGICAL_IMPULSE_VIRTUAL_ENV}" ]]; then
printf "\n${STY_RED}[$0]: \!! Important \!! : Please ensure environment variable ${STY_RESET} \$ILLOGICAL_IMPULSE_VIRTUAL_ENV ${STY_RED} 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.${STY_RESET}\n"
fi
if [[ ! -z "${warn_files[@]}" ]]; then
printf "\n${STY_RED}[$0]: \!! Important \!! : Please delete ${STY_RESET} ${warn_files[*]} ${STY_RED} manually as soon as possible, since we\'re now using AUR package or local PKGBUILD to install them for Arch(based) Linux distros, and they'll take precedence over our installation, or at least take up more space.${STY_RESET}\n"
fi
+23 -55
View File
@@ -18,72 +18,32 @@ function warning_rsync_normal(){
printf "${STY_RST}" printf "${STY_RST}"
} }
function backup_clashing_targets(){ function backup_configs(){
# For dirs/files under target_dir, only backup those which clashes with the ones under source_dir backup_clashing_targets dots/.config $XDG_CONFIG_HOME "${BACKUP_DIR}/.config"
backup_clashing_targets dots/.local/share $XDG_DATA_HOME "${BACKUP_DIR}/.local/share"
# Deal with arguments printf "${STY_BLUE}Backup into \"${BACKUP_DIR}\" finished.${STY_RST}\n"
local source_dir="$1"
local target_dir="$2"
local backup_dir="$3"
# Find clash dirs/files, save as clash_list
local clash_list=()
local source_list=($(ls -A "$source_dir"))
local target_list=($(ls -A "$target_dir"))
declare -A target_map
for i in "${target_list[@]}"; do
target_map["$i"]=1
done
for i in "${source_list[@]}"; do
if [[ -n "${target_map[$i]}" ]]; then
clash_list+=("$i")
fi
done
# Construct args_includes for rsync
local args_includes=()
for i in "${clash_list[@]}"; do
if [[ -d "$target_dir/$i" ]]; then
args_includes+=(--include="/$i/")
args_includes+=(--include="/$i/**")
else
args_includes+=(--include="/$i")
fi
done
args_includes+=(--exclude='*')
x mkdir -p $backup_dir
x rsync -av --progress "${args_includes[@]}" "$target_dir/" "$backup_dir/"
} }
function ask_backup_configs(){ function ask_backup_configs(){
showfun backup_clashing_targets showfun backup_clashing_targets
printf "${STY_RED}" printf "${STY_RED}"
printf "Would you like to backup clashing dirs/files under \"$XDG_CONFIG_HOME\" and \"$XDG_DATA_HOME\" to \"$BACKUP_DIR\"?" printf "Would you like to backup clashing dirs/files under \"$XDG_CONFIG_HOME\" and \"$XDG_DATA_HOME\" to \"$BACKUP_DIR\"?\n"
printf "${STY_RST}" printf "${STY_RST}"
while true;do while true;do
echo " y = Yes, backup" echo " y = Yes, backup"
echo " n = No, skip to next" echo " n/s = No, skip to next"
local p; read -p "====> " p local p; read -p "====> " p
case $p in case $p in
[yY]) echo -e "${STY_BLUE}OK, doing backup...${STY_RST}" ;local backup=true;break ;; [yY]) echo -e "${STY_BLUE}OK, doing backup...${STY_RST}" ;local backup=true;break ;;
[nN]) echo -e "${STY_BLUE}Alright, skipping...${STY_RST}" ;local backup=false;break ;; [nNsS]) echo -e "${STY_BLUE}Alright, skipping...${STY_RST}" ;local backup=false;break ;;
*) echo -e "${STY_RED}Please enter [y/n].${STY_RST}";; *) echo -e "${STY_RED}Please enter [y/n].${STY_RST}";;
esac esac
done done
if $backup;then if $backup;then backup_configs;fi
backup_clashing_targets dots/.config $XDG_CONFIG_HOME "${BACKUP_DIR}/.config"
backup_clashing_targets dots/.local/share $XDG_DATA_HOME "${BACKUP_DIR}/.local/share"
printf "${STY_BLUE}Backup into \"${BACKUP_DIR}\" finished.${STY_RST}\n"
fi
} }
function auto_backup_configs(){ function auto_backup_configs(){
# Backup when $BACKUP_DIR does not exist # Backup when $BACKUP_DIR does not exist
if [[ ! -d "$BACKUP_DIR" ]]; then if [[ ! -d "$BACKUP_DIR" ]]; then backup_configs;fi
backup_clashing_targets dots/.config $XDG_CONFIG_HOME "${BACKUP_DIR}/.config"
backup_clashing_targets dots/.local/share $XDG_DATA_HOME "${BACKUP_DIR}/.local/share"
printf "${STY_BLUE}Backup into \"${BACKUP_DIR}\" finished.${STY_RST}\n"
fi
} }
##################################################################################### #####################################################################################
@@ -143,9 +103,9 @@ esac
case $SKIP_FONTCONFIG in case $SKIP_FONTCONFIG in
true) sleep 0;; true) sleep 0;;
*) *)
case "$II_FONTSET_NAME" in case "$FONTSET_DIR_NAME" in
"") warning_rsync_delete; v rsync -av --delete dots/.config/fontconfig/ "$XDG_CONFIG_HOME"/fontconfig/ ;; "") warning_rsync_delete; v rsync -av --delete dots/.config/fontconfig/ "$XDG_CONFIG_HOME"/fontconfig/ ;;
*) warning_rsync_delete; v rsync -av --delete dots-extra/fontsets/$II_FONTSET_NAME/ "$XDG_CONFIG_HOME"/fontconfig/ ;; *) warning_rsync_delete; v rsync -av --delete dots-extra/fontsets/$FONTSET_DIR_NAME/ "$XDG_CONFIG_HOME"/fontconfig/ ;;
esac;; esac;;
esac esac
@@ -159,16 +119,24 @@ case $SKIP_HYPRLAND in
true) sleep 0;; true) sleep 0;;
*) *)
warning_rsync_delete; v rsync -av --delete "${arg_excludes[@]}" dots/.config/hypr/ "$XDG_CONFIG_HOME"/hypr/ warning_rsync_delete; v rsync -av --delete "${arg_excludes[@]}" dots/.config/hypr/ "$XDG_CONFIG_HOME"/hypr/
# When hypr/custom does not exist, we assume that it's the firstrun.
if [ -d "$XDG_CONFIG_HOME/hypr/custom" ];then ii_firstrun=false;else ii_firstrun=true;fi
t="$XDG_CONFIG_HOME/hypr/hyprland.conf" t="$XDG_CONFIG_HOME/hypr/hyprland.conf"
if [ -f $t ];then if [ -f $t ];then
echo -e "${STY_BLUE}[$0]: \"$t\" already exists.${STY_RST}" echo -e "${STY_BLUE}[$0]: \"$t\" already exists.${STY_RST}"
v mv $t $t.old if $ii_firstrun;then
v cp -f dots/.config/hypr/hyprland.conf $t echo -e "${STY_BLUE}[$0]: It seems to be the firstrun.${STY_RST}"
existed_hypr_conf_firstrun=y v mv $t $t.old
v cp -f dots/.config/hypr/hyprland.conf $t
existed_hypr_conf_firstrun=y
else
echo -e "${STY_BLUE}[$0]: It seems not a firstrun.${STY_RST}"
v cp -f dots/.config/hypr/hyprland.conf $t.new
existed_hypr_conf=y
fi
else else
echo -e "${STY_YELLOW}[$0]: \"$t\" does not exist yet.${STY_RST}" echo -e "${STY_YELLOW}[$0]: \"$t\" does not exist yet.${STY_RST}"
v cp dots/.config/hypr/hyprland.conf $t v cp dots/.config/hypr/hyprland.conf $t
existed_hypr_conf=n
fi fi
t="$XDG_CONFIG_HOME/hypr/hypridle.conf" t="$XDG_CONFIG_HOME/hypr/hypridle.conf"
if [ -f $t ];then if [ -f $t ];then
+116
View File
@@ -0,0 +1,116 @@
version: "1.0"
user_preferences:
shell: "fish" # fish | zsh
terminal: "foot" # kitty | foot
keybindings: "default" # default | vim
patterns:
# Always install these files
- from: "dots/.config/quickshell"
to: "$XDG_CONFIG_HOME/quickshell"
mode: "sync"
# Conditionally install these files
- from: "dots/.config/fish"
to: "$XDG_CONFIG_HOME/fish"
mode: "sync"
condition:
type: "shell"
value: "fish"
- from: "dots/.config/zshrc.d"
to: "$XDG_CONFIG_HOME/zshrc.d"
mode: "sync"
condition:
type: "shell"
value: "zsh"
- from: "dots/.config/foot"
to: "$XDG_CONFIG_HOME/foot"
mode: "sync"
condition:
type: "terminal"
value: "foot"
- from: "dots/.config/kitty"
to: "$XDG_CONFIG_HOME/kitty"
mode: "sync"
condition:
type: "terminal"
value: "kitty"
# Hyprland
- from: "dots/.config/hypr"
to: "$XDG_CONFIG_HOME/hypr"
mode: "sync"
excludes: ["custom", "hyprlock.conf", "hypridle.conf", "hyprland.conf"]
# Hyprland special files
- from: "dots/.config/hypr/hyprland.conf"
to: "$XDG_CONFIG_HOME/hypr/hyprland.conf"
mode: "hard-backup"
- from: "dots/.config/hypr/hypridle.conf"
to: "$XDG_CONFIG_HOME/hypr/hypridle.conf"
mode: "soft-backup"
- from: "dots/.config/hypr/hyprlock.conf"
to: "$XDG_CONFIG_HOME/hypr/hyprlock.conf"
mode: "soft-backup"
- from: "dots/.config/hypr/custom"
to: "$XDG_CONFIG_HOME/hypr/custom"
mode: "skip-if-exists"
- from: "dots/.local/share/icons"
to: "$XDG_DATA_HOME/icons"
mode: "soft"
- from: "dots/.local/share/konsole"
to: "$XDG_DATA_HOME/konsole"
mode: "soft"
# Fontconfig (default - fontsets handled separately if FONTSET_DIR_NAME is set)
- from: "dots/.config/fontconfig"
to: "$XDG_CONFIG_HOME/fontconfig"
mode: "sync"
# MISC config directories (other .config directories)
- from: "dots/.config/fuzzel"
to: "$XDG_CONFIG_HOME/fuzzel"
mode: "sync"
- from: "dots/.config/kde-material-you-colors"
to: "$XDG_CONFIG_HOME/kde-material-you-colors"
mode: "sync"
- from: "dots/.config/Kvantum"
to: "$XDG_CONFIG_HOME/Kvantum"
mode: "sync"
- from: "dots/.config/matugen"
to: "$XDG_CONFIG_HOME/matugen"
mode: "sync"
- from: "dots/.config/mpv"
to: "$XDG_CONFIG_HOME/mpv"
mode: "sync"
- from: "dots/.config/qt5ct"
to: "$XDG_CONFIG_HOME/qt5ct"
mode: "sync"
- from: "dots/.config/qt6ct"
to: "$XDG_CONFIG_HOME/qt6ct"
mode: "sync"
- from: "dots/.config/wlogout"
to: "$XDG_CONFIG_HOME/wlogout"
mode: "sync"
- from: "dots/.config/xdg-desktop-portal"
to: "$XDG_CONFIG_HOME/xdg-desktop-portal"
mode: "sync"
# MISC config files (individual files in .config)
- from: "dots/.config/chrome-flags.conf"
to: "$XDG_CONFIG_HOME/chrome-flags.conf"
mode: "soft"
- from: "dots/.config/code-flags.conf"
to: "$XDG_CONFIG_HOME/code-flags.conf"
mode: "soft"
- from: "dots/.config/darklyrc"
to: "$XDG_CONFIG_HOME/darklyrc"
mode: "soft"
- from: "dots/.config/dolphinrc"
to: "$XDG_CONFIG_HOME/dolphinrc"
mode: "soft"
- from: "dots/.config/kdeglobals"
to: "$XDG_CONFIG_HOME/kdeglobals"
mode: "soft"
- from: "dots/.config/konsolerc"
to: "$XDG_CONFIG_HOME/konsolerc"
mode: "soft"
- from: "dots/.config/starship.toml"
to: "$XDG_CONFIG_HOME/starship.toml"
mode: "soft"
- from: "dots/.config/thorium-flags.conf"
to: "$XDG_CONFIG_HOME/thorium-flags.conf"
mode: "soft"
+11 -5
View File
@@ -1,7 +1,7 @@
# Handle args for subcmd: install # Handle args for subcmd: install
# shellcheck shell=bash # shellcheck shell=bash
showhelp(){ showhelp(){
echo -e "Syntax: $0 install [OPTIONS]... printf "Syntax: $0 install [OPTIONS]...
Idempotent installation for dotfiles. Idempotent installation for dotfiles.
@@ -23,11 +23,17 @@ Options for install:
--skip-miscconf Skip copying the dirs and files to \".configs\" except for --skip-miscconf Skip copying the dirs and files to \".configs\" except for
Quickshell, Fish and Hyprland Quickshell, Fish and Hyprland
--core Alias of --skip-{plasmaintg,fish,miscconf,fontconfig} --core Alias of --skip-{plasmaintg,fish,miscconf,fontconfig}
--exp-files Use experimental script for the third step copying files
--fontset <set> Use a set of pre-defined font and config (currently only fontconfig). --fontset <set> Use a set of pre-defined font and config (currently only fontconfig).
Possible values of <set>: $(ls -A ${REPO_ROOT}/dots-extra/fontsets) Possible values of <set>: $(ls -A ${REPO_ROOT}/dots-extra/fontsets)
--via-nix (Unavailable yet) Use Nix to install dependencies ${STY_CYAN}
" New features (experimental):
--exp-files Use yaml-based config for the third step copying files.
This feature is ${STY_YELLOW}still on early stage${STY_CYAN}, feedback and contribution welcomed,
see https://github.com/end-4/dots-hyprland/issues/2137 for details.
--via-nix Use Nix and Home-manager to install dependencies.
This feature is ${STY_RED}working in progress${STY_CYAN}. Contribution is welcomed,
see https://github.com/end-4/dots-hyprland/issues/1061 for details.
${STY_RST}"
} }
cleancache(){ cleancache(){
@@ -81,7 +87,7 @@ while true ; do
## Ones with parameter ## Ones with parameter
--fontset) --fontset)
if [[ -d "${REPO_ROOT}/dots-extra/fontsets/$2" ]]; if [[ -d "${REPO_ROOT}/dots-extra/fontsets/$2" ]];
then echo "Using fontset \"$2\".";II_FONTSET_NAME="$2";shift 2 then echo "Using fontset \"$2\".";FONTSET_DIR_NAME="$2";shift 2
else echo "Wrong argument for $1.";exit 1 else echo "Wrong argument for $1.";exit 1
fi;; fi;;
+2 -1
View File
@@ -24,6 +24,7 @@ Subcommands:
exp-uninstall (Experimental) Uninstall illogical-impulse. exp-uninstall (Experimental) Uninstall illogical-impulse.
exp-update (Experimental) Update illogical-impulse without fully reinstall. exp-update (Experimental) Update illogical-impulse without fully reinstall.
exp-update-old (Experimental) exp-update but use behaves like old version. exp-update-old (Experimental) exp-update but use behaves like old version.
checkdeps (For dev only) Check whether pkgs exist in AUR or repos of Arch.
help Show this help message. help Show this help message.
For each <subcommand>, use -h for details: For each <subcommand>, use -h for details:
@@ -34,7 +35,7 @@ case $1 in
# Global help # Global help
help|--help|-h)showhelp_global;exit;; help|--help|-h)showhelp_global;exit;;
# Correct subcommand # Correct subcommand
install|exp-uninstall|exp-update|exp-update-old) install|exp-uninstall|exp-update|exp-update-old|checkdeps)
SUBCMD_NAME=$1 SUBCMD_NAME=$1
SUBCMD_DIR=./sdata/subcmd-$1 SUBCMD_DIR=./sdata/subcmd-$1
shift;; shift;;