diff --git a/dots/.config/quickshell/ii/modules/background/Background.qml b/dots/.config/quickshell/ii/modules/background/Background.qml index 71bae0d77..fc0bb0cfb 100644 --- a/dots/.config/quickshell/ii/modules/background/Background.qml +++ b/dots/.config/quickshell/ii/modules/background/Background.qml @@ -445,7 +445,7 @@ Variants { color: bgRoot.colText style: Text.Raised styleColor: Appearance.colors.colShadow - animateChange: true + animateChange: Config.options.background.clock.digital.animateChange } component ClockStatusText: Row { id: statusTextRow diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/CookieClock.qml b/dots/.config/quickshell/ii/modules/background/cookieClock/CookieClock.qml index d08056909..07f83401d 100644 --- a/dots/.config/quickshell/ii/modules/background/cookieClock/CookieClock.qml +++ b/dots/.config/quickshell/ii/modules/background/cookieClock/CookieClock.qml @@ -50,9 +50,9 @@ Item { print("[Cookie clock] Setting clock preset for category: " + category) // "abstract", "anime", "city", "minimalist", "landscape", "plants", "person", "space" if (category == "abstract") { - applyStyle(10, "dots", "fill", "medium", "dot", "bubble") + applyStyle(9, "none", "fill", "medium", "dot", "bubble") } else if (category == "anime") { - applyStyle(12, "dots", "fill", "bold", "dot", "bubble") + applyStyle(7, "none", "fill", "bold", "dot", "bubble") } else if (category == "city" || category == "space") { applyStyle(23, "full", "hollow", "thin", "classic", "bubble") } else if (category == "minimalist") { @@ -83,121 +83,141 @@ Item { } } + property bool useSineCookie: Config.options.background.clock.cookie.useSineCookie DropShadow { - source: cookie + source: useSineCookie ? sineCookieLoader : roundedPolygonCookieLoader anchors.fill: source - horizontalOffset: 0 - verticalOffset: 1 radius: 8 samples: radius * 2 + 1 color: root.colShadow transparentBorder: true + + RotationAnimation on rotation { + running: Config.options.background.clock.cookie.constantlyRotate + duration: 30000 + easing.type: Easing.Linear + loops: Animation.Infinite + from: 360 + to: 0 + } + } + Loader { + id: sineCookieLoader + z: 0 + visible: false // The DropShadow already draws it + active: useSineCookie + sourceComponent: SineCookie { + implicitSize: root.implicitSize + sides: Config.options.background.clock.cookie.sides + color: root.colBackground + } + } + Loader { + id: roundedPolygonCookieLoader + z: 0 + visible: false // The DropShadow already draws it + active: !useSineCookie + sourceComponent: MaterialCookie { + implicitSize: root.implicitSize + sides: Config.options.background.clock.cookie.sides + color: root.colBackground + } } - MaterialCookie { - id: cookie - z: 0 - implicitSize: root.implicitSize - amplitude: implicitSize / 70 - sides: Config.options.background.clock.cookie.sides - color: root.colBackground - constantlyRotate: Config.options.background.clock.cookie.constantlyRotate - - // Hour/minutes numbers/dots/lines - MinuteMarks { - anchors.fill: parent + // Hour/minutes numbers/dots/lines + MinuteMarks { + anchors.fill: parent + color: root.colOnBackground + } + + // Stupid extra hour marks in the middle + FadeLoader { + id: hourMarksLoader + anchors.centerIn: parent + shown: Config.options.background.clock.cookie.hourMarks + sourceComponent: HourMarks { + implicitSize: 135 * (1.75 - 0.75 * hourMarksLoader.opacity) color: root.colOnBackground + colOnBackground: ColorUtils.mix(root.colBackgroundInfo, root.colOnBackground, 0.5) + } + } + + // Number column in the middle + FadeLoader { + id: timeColumnLoader + anchors.centerIn: parent + shown: Config.options.background.clock.cookie.timeIndicators + scale: 1.4 - 0.4 * timeColumnLoader.shown + Behavior on scale { + animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } - // Stupid extra hour marks in the middle - FadeLoader { - id: hourMarksLoader - anchors.centerIn: parent - shown: Config.options.background.clock.cookie.hourMarks - sourceComponent: HourMarks { - implicitSize: 135 * (1.75 - 0.75 * hourMarksLoader.opacity) - color: root.colOnBackground - colOnBackground: ColorUtils.mix(root.colBackgroundInfo, root.colOnBackground, 0.5) - } + sourceComponent: TimeColumn { + color: root.colBackgroundInfo } + } - // Number column in the middle - FadeLoader { - id: timeColumnLoader - anchors.centerIn: parent - shown: Config.options.background.clock.cookie.timeIndicators - scale: 1.4 - 0.4 * timeColumnLoader.shown - Behavior on scale { - animation: Appearance.animation.elementResize.numberAnimation.createObject(this) - } - - sourceComponent: TimeColumn { - color: root.colBackgroundInfo - } + // Hour hand + FadeLoader { + anchors.fill: parent + z: 1 + shown: Config.options.background.clock.cookie.hourHandStyle !== "hide" + sourceComponent: HourHand { + clockHour: root.clockHour + clockMinute: root.clockMinute + style: Config.options.background.clock.cookie.hourHandStyle + color: root.colHourHand } + } - // Hour hand - FadeLoader { + // Minute hand + FadeLoader { + anchors.fill: parent + z: 2 + shown: Config.options.background.clock.cookie.minuteHandStyle !== "hide" + sourceComponent: MinuteHand { anchors.fill: parent - z: 1 - shown: Config.options.background.clock.cookie.hourHandStyle !== "hide" - sourceComponent: HourHand { - clockHour: root.clockHour - clockMinute: root.clockMinute - style: Config.options.background.clock.cookie.hourHandStyle - color: root.colHourHand - } + clockMinute: root.clockMinute + style: Config.options.background.clock.cookie.minuteHandStyle + color: root.colMinuteHand } + } - // Minute hand - FadeLoader { - anchors.fill: parent - z: 2 - shown: Config.options.background.clock.cookie.minuteHandStyle !== "hide" - sourceComponent: MinuteHand { - anchors.fill: parent - clockMinute: root.clockMinute - style: Config.options.background.clock.cookie.minuteHandStyle - color: root.colMinuteHand - } + // Second hand + FadeLoader { + id: secondHandLoader + z: (Config.options.background.clock.cookie.secondHandStyle === "line") ? 2 : 3 + shown: Config.options.time.secondPrecision && Config.options.background.clock.cookie.secondHandStyle !== "hide" + anchors.fill: parent + sourceComponent: SecondHand { + id: secondHand + clockSecond: root.clockSecond + style: Config.options.background.clock.cookie.secondHandStyle + color: root.colSecondHand } + } - // Second hand - FadeLoader { - id: secondHandLoader - z: (Config.options.background.clock.cookie.secondHandStyle === "line") ? 2 : 3 - shown: Config.options.time.secondPrecision && Config.options.background.clock.cookie.secondHandStyle !== "hide" - anchors.fill: parent - sourceComponent: SecondHand { - id: secondHand - clockSecond: root.clockSecond - style: Config.options.background.clock.cookie.secondHandStyle - color: root.colSecondHand - } + // Center dot + FadeLoader { + z: 4 + anchors.centerIn: parent + shown: Config.options.background.clock.cookie.minuteHandStyle !== "bold" + sourceComponent: Rectangle { + color: Config.options.background.clock.cookie.minuteHandStyle === "medium" ? root.colBackground : root.colMinuteHand + implicitWidth: 6 + implicitHeight: implicitWidth + radius: width / 2 } + } - // Center dot - FadeLoader { - z: 4 - anchors.centerIn: parent - shown: Config.options.background.clock.cookie.minuteHandStyle !== "bold" - sourceComponent: Rectangle { - color: Config.options.background.clock.cookie.minuteHandStyle === "medium" ? root.colBackground : root.colMinuteHand - implicitWidth: 6 - implicitHeight: implicitWidth - radius: width / 2 - } - } + // Date + FadeLoader { + anchors.fill: parent + shown: Config.options.background.clock.cookie.dateStyle !== "hide" - // Date - FadeLoader { - anchors.fill: parent - shown: Config.options.background.clock.cookie.dateStyle !== "hide" - - sourceComponent: DateIndicator { - color: root.colBackgroundInfo - style: Config.options.background.clock.cookie.dateStyle - } + sourceComponent: DateIndicator { + color: root.colBackgroundInfo + style: Config.options.background.clock.cookie.dateStyle } } } diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/HourHand.qml b/dots/.config/quickshell/ii/modules/background/cookieClock/HourHand.qml index c38960256..5c355d345 100644 --- a/dots/.config/quickshell/ii/modules/background/cookieClock/HourHand.qml +++ b/dots/.config/quickshell/ii/modules/background/cookieClock/HourHand.qml @@ -9,7 +9,7 @@ Item { required property int clockHour required property int clockMinute property real handLength: 72 - property real handWidth: 18 + property real handWidth: 20 property string style: "fill" property color color: Appearance.colors.colPrimary @@ -19,6 +19,14 @@ Item { } rotation: -90 + (360 / 12) * (root.clockHour + root.clockMinute / 60) + Behavior on rotation { + animation: RotationAnimation { + direction: RotationAnimation.Clockwise + duration: 300 + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.animationCurves.emphasized + } + } Rectangle { anchors.verticalCenter: parent.verticalCenter diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/MinuteHand.qml b/dots/.config/quickshell/ii/modules/background/cookieClock/MinuteHand.qml index 0b8a78d16..764c14f2b 100644 --- a/dots/.config/quickshell/ii/modules/background/cookieClock/MinuteHand.qml +++ b/dots/.config/quickshell/ii/modules/background/cookieClock/MinuteHand.qml @@ -10,13 +10,17 @@ Item { required property int clockMinute property string style: "medium" property real handLength: 95 - property real handWidth: style === "bold" ? 18 : style === "medium" ? 12 : 5 + property real handWidth: style === "bold" ? 20 : style === "medium" ? 12 : 5 property color color: Appearance.colors.colSecondary rotation: -90 + (360 / 60) * root.clockMinute - Behavior on rotation { - animation: Appearance.animation.elementResize.numberAnimation.createObject(this) + animation: RotationAnimation { + direction: RotationAnimation.Clockwise + duration: 300 + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.animationCurves.emphasized + } } Rectangle { diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/SecondHand.qml b/dots/.config/quickshell/ii/modules/background/cookieClock/SecondHand.qml index a4643a259..93ca1cf48 100644 --- a/dots/.config/quickshell/ii/modules/background/cookieClock/SecondHand.qml +++ b/dots/.config/quickshell/ii/modules/background/cookieClock/SecondHand.qml @@ -10,7 +10,7 @@ Item { required property int clockSecond property real handWidth: 2 - property real handLength: 100 + property real handLength: 95 property real dotSize: 20 property string style: "hide" property color color: Appearance.colors.colSecondary @@ -19,7 +19,8 @@ Item { Behavior on rotation { enabled: Config.options.background.clock.cookie.constantlyRotate // Animating every second is expensive... - animation: NumberAnimation { + animation: RotationAnimation { + direction: RotationAnimation.Clockwise duration: 1000 // 1 second easing.type: Easing.InOutQuad } @@ -29,7 +30,7 @@ Item { anchors { left: parent.left verticalCenter: parent.verticalCenter - leftMargin: 10 + leftMargin: 10 + (root.style === "dot" ? root.dotSize : 0) } implicitWidth: root.style === "dot" ? root.dotSize : root.handLength implicitHeight: root.style === "dot" ? root.dotSize : root.handWidth diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/minuteMarks/MinuteMarks.qml b/dots/.config/quickshell/ii/modules/background/cookieClock/minuteMarks/MinuteMarks.qml index be0389677..07cf0dc5e 100644 --- a/dots/.config/quickshell/ii/modules/background/cookieClock/minuteMarks/MinuteMarks.qml +++ b/dots/.config/quickshell/ii/modules/background/cookieClock/minuteMarks/MinuteMarks.qml @@ -14,7 +14,10 @@ Item { // 12 Dots FadeLoader { id: dotsLoader - anchors.fill: parent + anchors { + fill: parent + margins: 10 + } shown: root.style === "dots" sourceComponent: Dots { color: root.color @@ -37,7 +40,10 @@ Item { // Lines FadeLoader { id: linesLoader - anchors.fill: parent + anchors { + fill: parent + margins: 10 + } shown: root.style === "full" sourceComponent: Lines { color: root.color diff --git a/dots/.config/quickshell/ii/modules/cheatsheet/Cheatsheet.qml b/dots/.config/quickshell/ii/modules/cheatsheet/Cheatsheet.qml index eaa59ee3d..00dac8b89 100644 --- a/dots/.config/quickshell/ii/modules/cheatsheet/Cheatsheet.qml +++ b/dots/.config/quickshell/ii/modules/cheatsheet/Cheatsheet.qml @@ -5,6 +5,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects +import Qt.labs.synchronizer import Quickshell.Io import Quickshell import Quickshell.Wayland @@ -22,7 +23,6 @@ Scope { // Scope "name": Translation.tr("Elements") }, ] - property int selectedTab: 0 Loader { id: cheatsheetLoader @@ -31,6 +31,7 @@ Scope { // Scope sourceComponent: PanelWindow { // Window id: cheatsheetRoot visible: cheatsheetLoader.active + property int selectedTab: 0 anchors { top: true @@ -85,16 +86,16 @@ Scope { // Scope } if (event.modifiers === Qt.ControlModifier) { if (event.key === Qt.Key_PageDown) { - root.selectedTab = Math.min(root.selectedTab + 1, root.tabButtonList.length - 1); + cheatsheetRoot.selectedTab = Math.min(cheatsheetRoot.selectedTab + 1, root.tabButtonList.length - 1); event.accepted = true; } else if (event.key === Qt.Key_PageUp) { - root.selectedTab = Math.max(root.selectedTab - 1, 0); + cheatsheetRoot.selectedTab = Math.max(cheatsheetRoot.selectedTab - 1, 0); event.accepted = true; } else if (event.key === Qt.Key_Tab) { - root.selectedTab = (root.selectedTab + 1) % root.tabButtonList.length; + cheatsheetRoot.selectedTab = (cheatsheetRoot.selectedTab + 1) % root.tabButtonList.length; event.accepted = true; } else if (event.key === Qt.Key_Backtab) { - root.selectedTab = (root.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length; + cheatsheetRoot.selectedTab = (cheatsheetRoot.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length; event.accepted = true; } } @@ -140,9 +141,8 @@ Scope { // Scope PrimaryTabBar { // Tab strip id: tabBar tabButtonList: root.tabButtonList - externalTrackedTab: root.selectedTab - function onCurrentIndexChanged(currentIndex) { - root.selectedTab = currentIndex; + Synchronizer on currentIndex { + property alias source: cheatsheetRoot.selectedTab } } @@ -164,12 +164,12 @@ Scope { // Scope animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } - currentIndex: tabBar.externalTrackedTab + currentIndex: cheatsheetRoot.selectedTab onCurrentIndexChanged: { contentWidthBehavior.enabled = true; contentHeightBehavior.enabled = true; tabBar.enableIndicatorAnimation = true; - root.selectedTab = currentIndex; + cheatsheetRoot.selectedTab = currentIndex; } clip: true diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index 0bca2b820..18cd92fc3 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -164,6 +164,10 @@ Singleton { property bool hourMarks: false property bool dateInClock: true property bool constantlyRotate: false + property bool useSineCookie: false + } + property JsonObject digital: JsonObject { + property bool animateChange: true } } @@ -324,6 +328,7 @@ Singleton { property bool unlockKeyring: true property bool requirePasswordToPower: false } + property bool materialShapeChars: true } property JsonObject media: JsonObject { diff --git a/dots/.config/quickshell/ii/modules/common/Directories.qml b/dots/.config/quickshell/ii/modules/common/Directories.qml index 411ed6c7f..59f3dab53 100644 --- a/dots/.config/quickshell/ii/modules/common/Directories.qml +++ b/dots/.config/quickshell/ii/modules/common/Directories.qml @@ -2,7 +2,7 @@ pragma Singleton pragma ComponentBehavior: Bound import qs.modules.common.functions -import Qt.labs.platform +import QtCore import QtQuick import Quickshell diff --git a/dots/.config/quickshell/ii/modules/common/widgets/MaterialCookie.qml b/dots/.config/quickshell/ii/modules/common/widgets/MaterialCookie.qml index 79048ca63..a1f4be235 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/MaterialCookie.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/MaterialCookie.qml @@ -2,69 +2,38 @@ import QtQuick import QtQuick.Shapes import Quickshell import qs.modules.common +import qs.modules.common.widgets.shapes +import "shapes/geometry/offset.js" as Offset +import "shapes/shapes/corner-rounding.js" as CornerRounding +import "shapes/shapes/rounded-polygon.js" as RoundedPolygon +import "shapes/material-shapes.js" as MaterialShapes Item { id: root - - property real sides: 12 + property int sides: 12 property int implicitSize: 100 - property real amplitude: implicitSize / 50 - property int renderPoints: 360 - property color color: "#605790" - property alias strokeWidth: shapePath.strokeWidth - property bool constantlyRotate: false + property alias color: shapeCanvas.color implicitWidth: implicitSize implicitHeight: implicitSize - property real shapeRotation: 0 + property var cornerRounding: new CornerRounding.CornerRounding((sides < 17 ? 1.5 : 1.1) / Math.max(sides, 1)) - Loader { - active: constantlyRotate - sourceComponent: FrameAnimation { - running: true - onTriggered: { - shapeRotation += 0.05 - } - } - } - - Behavior on sides { - animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) - } - - Shape { - id: shape + ShapeCanvas { + id: shapeCanvas anchors.fill: parent - preferredRendererType: Shape.CurveRenderer - - ShapePath { - id: shapePath - strokeWidth: 0 - fillColor: root.color - pathHints: ShapePath.PathSolid & ShapePath.PathNonIntersecting - - PathPolyline { - property var pointsList: { - var points = [] - var cx = shape.width / 2 // center x - var cy = shape.height / 2 // center y - var steps = root.renderPoints - var radius = root.implicitSize / 2 - root.amplitude - for (var i = 0; i <= steps; i++) { - var angle = (i / steps) * 2 * Math.PI - var rotatedAngle = angle * root.sides + Math.PI/2 + (root.shapeRotation * root.constantlyRotate) - var wave = Math.sin(rotatedAngle) * root.amplitude - var x = Math.cos(angle) * (radius + wave) + cx - var y = Math.sin(angle) * (radius + wave) + cy - points.push(Qt.point(x, y)) - } - return points - } - - path: pointsList - } - + roundedPolygon: switch(sides) { + case 0: return MaterialShapes.getCircle(); + case 1: return MaterialShapes.getCircle(); + case 4: return MaterialShapes.getCookie4Sided(); + case 6: return MaterialShapes.getCookie6Sided(); + case 7: return MaterialShapes.getCookie7Sided(); + case 9: return MaterialShapes.getCookie9Sided(); + case 12: return MaterialShapes.getCookie12Sided(); + default: return RoundedPolygon.RoundedPolygon.star(sides, 1, 0.8, root.cornerRounding) + .transformed((x, y) => MaterialShapes.rotate30.map(new Offset.Offset(x, y))) + .normalized(); } } } + diff --git a/dots/.config/quickshell/ii/modules/common/widgets/MaterialShape.qml b/dots/.config/quickshell/ii/modules/common/widgets/MaterialShape.qml index 30d953478..225fc4139 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/MaterialShape.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/MaterialShape.qml @@ -44,6 +44,7 @@ ShapeCanvas { property double implicitSize implicitHeight: implicitSize implicitWidth: implicitSize + polygonIsNormalized: true roundedPolygon: { switch (root.shape) { case MaterialShape.Shape.Circle: return MaterialShapes.getCircle(); diff --git a/dots/.config/quickshell/ii/modules/common/widgets/PrimaryTabBar.qml b/dots/.config/quickshell/ii/modules/common/widgets/PrimaryTabBar.qml index 7d792de56..474bdc591 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/PrimaryTabBar.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/PrimaryTabBar.qml @@ -3,16 +3,20 @@ import qs.services import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Qt.labs.synchronizer ColumnLayout { id: root spacing: 0 required property var tabButtonList // Something like [{"icon": "notifications", "name": Translation.tr("Notifications")}, {"icon": "volume_up", "name": Translation.tr("Volume mixer")}] - required property var externalTrackedTab + property int currentIndex property bool enableIndicatorAnimation: false property color colIndicator: Appearance?.colors.colPrimary ?? "#65558F" property color colBorder: Appearance?.m3colors.m3outlineVariant ?? "#C6C6D0" - signal currentIndexChanged(int index) + + onCurrentIndexChanged: { + enableIndicatorAnimation = true + } property bool centerTabBar: parent.width > 500 Layout.fillWidth: !centerTabBar @@ -22,9 +26,8 @@ ColumnLayout { TabBar { id: tabBar Layout.fillWidth: true - currentIndex: root.externalTrackedTab - onCurrentIndexChanged: { - root.onCurrentIndexChanged(currentIndex) + Synchronizer on currentIndex { + property alias source: root.currentIndex } background: Item { @@ -42,10 +45,11 @@ ColumnLayout { Repeater { model: root.tabButtonList delegate: PrimaryTabButton { - selected: (index == root.externalTrackedTab) + selected: (index == root.currentIndex) buttonText: modelData.name buttonIcon: modelData.icon minimumWidth: 160 + onClicked: root.currentIndex = index } } } @@ -54,12 +58,6 @@ ColumnLayout { id: tabIndicator Layout.fillWidth: true height: 3 - Connections { - target: root - function onExternalTrackedTabChanged() { - root.enableIndicatorAnimation = true - } - } Rectangle { id: indicator diff --git a/dots/.config/quickshell/ii/modules/common/widgets/SineCookie.qml b/dots/.config/quickshell/ii/modules/common/widgets/SineCookie.qml new file mode 100644 index 000000000..79048ca63 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/SineCookie.qml @@ -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 + } + + } + } +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/StyledSwitch.qml b/dots/.config/quickshell/ii/modules/common/widgets/StyledSwitch.qml index f16e213fd..56c1425d9 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/StyledSwitch.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/StyledSwitch.qml @@ -45,13 +45,25 @@ Switch { anchors.leftMargin: root.checked ? ((root.pressed || root.down) ? (22 * root.scale) : 24 * root.scale) : ((root.pressed || root.down) ? (2 * root.scale) : 8 * root.scale) Behavior on anchors.leftMargin { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + NumberAnimation { + duration: Appearance.animationCurves.expressiveFastSpatialDuration + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial + } } Behavior on width { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + NumberAnimation { + duration: Appearance.animationCurves.expressiveFastSpatialDuration + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial + } } Behavior on height { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + NumberAnimation { + duration: Appearance.animationCurves.expressiveFastSpatialDuration + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial + } } Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) diff --git a/dots/.config/quickshell/ii/modules/common/widgets/shapes b/dots/.config/quickshell/ii/modules/common/widgets/shapes index 2e9263e01..8aa62a41b 160000 --- a/dots/.config/quickshell/ii/modules/common/widgets/shapes +++ b/dots/.config/quickshell/ii/modules/common/widgets/shapes @@ -1 +1 @@ -Subproject commit 2e9263e011e8dcf00a66da5a2e3fe4df160b41b9 +Subproject commit 8aa62a41bd4cdc4899bdfdc0d9cf103ac34c51f6 diff --git a/dots/.config/quickshell/ii/modules/lock/Lock.qml b/dots/.config/quickshell/ii/modules/lock/Lock.qml index f090350c6..ead478bdc 100644 --- a/dots/.config/quickshell/ii/modules/lock/Lock.qml +++ b/dots/.config/quickshell/ii/modules/lock/Lock.qml @@ -1,4 +1,6 @@ +pragma ComponentBehavior: Bound import qs +import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.lock @@ -9,9 +11,9 @@ import Quickshell.Wayland import Quickshell.Hyprland Scope { - id: root + id: root - function unlockKeyring() { + function unlockKeyring() { Quickshell.execDetached({ environment: ({ UNLOCK_PASSWORD: root.currentText @@ -20,117 +22,141 @@ Scope { }) } - // This stores all the information shared between the lock surfaces on each screen. - // https://github.com/quickshell-mirror/quickshell-examples/tree/master/lockscreen - LockContext { - id: lockContext + property var windowData: [] + function saveWindowPositionAndTile() { + Hyprland.dispatch(`keyword dwindle:pseudotile true`) + root.windowData = HyprlandData.windowList.filter(w => (w.floating && w.workspace.id === HyprlandData.activeWorkspace.id)) + root.windowData.forEach(w => { + Hyprland.dispatch(`pseudo address:${w.address}`) + Hyprland.dispatch(`settiled address:${w.address}`) + Hyprland.dispatch(`movetoworkspacesilent ${w.workspace.id},address:${w.address}`) + }) + } + function restoreWindowPositionAndTile() { + root.windowData.forEach(w => { + Hyprland.dispatch(`setfloating address:${w.address}`) + Hyprland.dispatch(`movewindowpixel exact ${w.at[0]} ${w.at[1]}, address:${w.address}`) + Hyprland.dispatch(`pseudo address:${w.address}`) + }) + Hyprland.dispatch(`keyword dwindle:pseudotile false`) + } - Connections { - target: GlobalStates - function onScreenLockedChanged() { - if (GlobalStates.screenLocked) lockContext.reset(); - } - } + // This stores all the information shared between the lock surfaces on each screen. + // https://github.com/quickshell-mirror/quickshell-examples/tree/master/lockscreen + LockContext { + id: lockContext - onUnlocked: (targetAction) => { - // Perform the target action if it's not just unlocking - if (targetAction == LockContext.ActionEnum.Poweroff) { - Session.poweroff(); - return; - } else if (targetAction == LockContext.ActionEnum.Reboot) { - Session.reboot(); - return; - } + Connections { + target: GlobalStates + function onScreenLockedChanged() { + if (GlobalStates.screenLocked) { + lockContext.reset(); + lockContext.tryFingerUnlock(); + } + } + } - // Unlock the keyring if configured to do so - if (Config.options.lock.security.unlockKeyring) root.unlockKeyring(); + onUnlocked: (targetAction) => { + // Perform the target action if it's not just unlocking + if (targetAction == LockContext.ActionEnum.Poweroff) { + Session.poweroff(); + return; + } else if (targetAction == LockContext.ActionEnum.Reboot) { + Session.reboot(); + return; + } - // Unlock the screen before exiting, or the compositor will display a - // fallback lock you can't interact with. - GlobalStates.screenLocked = false; - - // Refocus last focused window on unlock (hack) - Quickshell.execDetached(["bash", "-c", `sleep 0.2; hyprctl --batch "dispatch togglespecialworkspace; dispatch togglespecialworkspace"`]) + // Unlock the keyring if configured to do so + if (Config.options.lock.security.unlockKeyring) root.unlockKeyring(); + + // Unlock the screen before exiting, or the compositor will display a + // fallback lock you can't interact with. + GlobalStates.screenLocked = false; + + // Refocus last focused window on unlock (hack) + Quickshell.execDetached(["bash", "-c", `sleep 0.2; hyprctl --batch "dispatch togglespecialworkspace; dispatch togglespecialworkspace"`]) // Reset lockContext.reset(); - } - } + } + } - WlSessionLock { - id: lock - locked: GlobalStates.screenLocked + WlSessionLock { + id: lock + locked: GlobalStates.screenLocked - WlSessionLockSurface { - color: "transparent" - Loader { - active: GlobalStates.screenLocked - anchors.fill: parent - opacity: active ? 1 : 0 - Behavior on opacity { - animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) - } - sourceComponent: LockSurface { - context: lockContext - } - } - } - } + WlSessionLockSurface { + color: "transparent" + Loader { + active: GlobalStates.screenLocked + anchors.fill: parent + opacity: active ? 1 : 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + } + sourceComponent: LockSurface { + context: lockContext + } + } + } + } - // Blur layer hack - Variants { + // Blur layer hack + Variants { model: Quickshell.screens - delegate: Scope { - required property ShellScreen modelData - property bool shouldPush: GlobalStates.screenLocked - property string targetMonitorName: modelData.name - property int verticalMovementDistance: modelData.height - property int horizontalSqueeze: modelData.width * 0.2 - onShouldPushChanged: { - if (shouldPush) { - Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, ${verticalMovementDistance}, ${-verticalMovementDistance}, ${horizontalSqueeze}, ${horizontalSqueeze}`]) - } else { - Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, 0, 0, 0, 0`]) - } - } - } - } + delegate: Scope { + required property ShellScreen modelData + property bool shouldPush: GlobalStates.screenLocked + property string targetMonitorName: modelData.name + property int verticalMovementDistance: modelData.height + property int horizontalSqueeze: modelData.width * 0.2 + onShouldPushChanged: { + if (shouldPush) { + root.saveWindowPositionAndTile(); + Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, ${verticalMovementDistance}, ${-verticalMovementDistance}, ${horizontalSqueeze}, ${horizontalSqueeze}`]) + } else { + Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, 0, 0, 0, 0`]) + root.restoreWindowPositionAndTile(); + } + } + } + } - IpcHandler { + IpcHandler { target: "lock" function activate(): void { GlobalStates.screenLocked = true; } - function focus(): void { - lockContext.shouldReFocus(); - } + function focus(): void { + lockContext.shouldReFocus(); + } } - GlobalShortcut { + GlobalShortcut { name: "lock" description: "Locks the screen" onPressed: { - if (Config.options.lock.useHyprlock) { - Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]); - return; - } + if (Config.options.lock.useHyprlock) { + Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]); + return; + } GlobalStates.screenLocked = true; } } - GlobalShortcut { + GlobalShortcut { name: "lockFocus" description: "Re-focuses the lock screen. This is because Hyprland after waking up for whatever reason" - + "decides to keyboard-unfocus the lock screen" + + "decides to keyboard-unfocus the lock screen" onPressed: { lockContext.shouldReFocus(); } } - Connections { + Connections { target: Config function onReadyChanged() { if (Config.options.lock.launchOnStartup && Config.ready && Persistent.ready && Persistent.isNewHyprlandInstance) { diff --git a/dots/.config/quickshell/ii/modules/lock/LockContext.qml b/dots/.config/quickshell/ii/modules/lock/LockContext.qml index 7d030fc42..dcfd9a85d 100644 --- a/dots/.config/quickshell/ii/modules/lock/LockContext.qml +++ b/dots/.config/quickshell/ii/modules/lock/LockContext.qml @@ -2,6 +2,7 @@ import qs import qs.modules.common import QtQuick import Quickshell +import Quickshell.Io import Quickshell.Services.Pam Scope { @@ -18,6 +19,7 @@ Scope { property string currentText: "" property bool unlockInProgress: false property bool showFailure: false + property bool fingerprintsConfigured: false property var targetAction: LockContext.ActionEnum.Unlock function resetTargetAction() { @@ -60,6 +62,34 @@ Scope { pam.start(); } + function tryFingerUnlock() { + if (root.fingerprintsConfigured) { + fingerPam.start(); + } + } + + function stopFingerPam() { + fingerPam.abort(); + } + + Process { + id: fingerprintCheckProc + running: true + command: ["bash", "-c", "fprintd-list $(whoami)"] + stdout: StdioCollector { + id: fingerprintOutputCollector + onStreamFinished: { + root.fingerprintsConfigured = fingerprintOutputCollector.text.includes("Fingerprints for user"); + } + } + onExited: (exitCode, exitStatus) => { + if (exitCode !== 0) { + console.warn("fprintd-list command exited with error:", exitCode, exitStatus); + root.fingerprintsConfigured = false; + } + } + } + PamContext { id: pam @@ -74,6 +104,7 @@ Scope { onCompleted: result => { if (result == PamResult.Success) { root.unlocked(root.targetAction); + stopFingerPam(); } else { root.clearText(); root.unlockInProgress = false; @@ -83,4 +114,19 @@ Scope { } } + PamContext { + id: fingerPam + + configDirectory: "pam" + config: "fprintd.conf" + + onCompleted: result => { + if (result == PamResult.Success) { + root.unlocked(root.targetAction); + stopFingerPam(); + } else if (result == PamResult.Error){ // if timeout or etc.. + tryFingerUnlock() + } + } + } } diff --git a/dots/.config/quickshell/ii/modules/lock/LockSurface.qml b/dots/.config/quickshell/ii/modules/lock/LockSurface.qml index bf5895e8a..4fc147de4 100644 --- a/dots/.config/quickshell/ii/modules/lock/LockSurface.qml +++ b/dots/.config/quickshell/ii/modules/lock/LockSurface.qml @@ -100,6 +100,23 @@ MouseArea { scale: root.toolbarScale opacity: root.toolbarOpacity + // Fingerprint + Loader { + Layout.leftMargin: 10 + Layout.rightMargin: 6 + Layout.alignment: Qt.AlignVCenter + active: root.context.fingerprintsConfigured + visible: active + + sourceComponent: MaterialSymbol { + id: fingerprintIcon + fill: 1 + text: "fingerprint" + iconSize: Appearance.font.pixelSize.hugeass + color: Appearance.colors.colOnSurfaceVariant + } + } + ToolbarTextField { id: passwordBox placeholderText: GlobalStates.screenUnlockFailed ? Translation.tr("Incorrect password") : Translation.tr("Enter password") @@ -126,7 +143,7 @@ MouseArea { Keys.onPressed: event => { root.context.resetClearTimer(); } - + layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { @@ -136,15 +153,35 @@ MouseArea { } } + // Shake when wrong password + SequentialAnimation { + id: wrongPasswordShakeAnim + NumberAnimation { target: passwordBox; property: "x"; to: -30; duration: 50 } + NumberAnimation { target: passwordBox; property: "x"; to: 30; duration: 50 } + NumberAnimation { target: passwordBox; property: "x"; to: -15; duration: 40 } + NumberAnimation { target: passwordBox; property: "x"; to: 15; duration: 40 } + NumberAnimation { target: passwordBox; property: "x"; to: 0; duration: 30 } + } + Connections { + target: GlobalStates + function onScreenUnlockFailedChanged() { + if (GlobalStates.screenUnlockFailed) wrongPasswordShakeAnim.restart(); + } + } + // We're drawing dots manually - color: ColorUtils.transparentize(Appearance.colors.colOnLayer1) - PasswordChars { + property bool materialShapeChars: Config.options.lock.materialShapeChars + color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, materialShapeChars ? 1 : 0) + Loader { + active: passwordBox.materialShapeChars anchors { fill: parent leftMargin: passwordBox.padding rightMargin: passwordBox.padding } - length: root.context.currentText.length + sourceComponent: PasswordChars { + length: root.context.currentText.length + } } } diff --git a/dots/.config/quickshell/ii/modules/lock/PasswordChars.qml b/dots/.config/quickshell/ii/modules/lock/PasswordChars.qml index 79ffc9464..400c2495a 100644 --- a/dots/.config/quickshell/ii/modules/lock/PasswordChars.qml +++ b/dots/.config/quickshell/ii/modules/lock/PasswordChars.qml @@ -15,7 +15,6 @@ StyledFlickable { Behavior on contentX { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } - rightMargin: 14 Row { id: dotsRow anchors { diff --git a/dots/.config/quickshell/ii/modules/lock/pam/fprintd.conf b/dots/.config/quickshell/ii/modules/lock/pam/fprintd.conf new file mode 100644 index 000000000..73d9cc725 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/lock/pam/fprintd.conf @@ -0,0 +1 @@ +auth sufficient pam_fprintd.so \ No newline at end of file diff --git a/dots/.config/quickshell/ii/modules/mediaControls/MediaControls.qml b/dots/.config/quickshell/ii/modules/mediaControls/MediaControls.qml index 62426a36f..75dca5926 100644 --- a/dots/.config/quickshell/ii/modules/mediaControls/MediaControls.qml +++ b/dots/.config/quickshell/ii/modules/mediaControls/MediaControls.qml @@ -21,7 +21,7 @@ Scope { readonly property real osdWidth: Appearance.sizes.osdWidth readonly property real widgetWidth: Appearance.sizes.mediaControlsWidth readonly property real widgetHeight: Appearance.sizes.mediaControlsHeight - property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 + property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1 property list visualizerPoints: [] property bool hasPlasmaIntegration: false diff --git a/dots/.config/quickshell/ii/modules/regionSelector/OptionsToolbar.qml b/dots/.config/quickshell/ii/modules/regionSelector/OptionsToolbar.qml index e737e9cac..d4e54ddda 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/OptionsToolbar.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/OptionsToolbar.qml @@ -23,13 +23,25 @@ Toolbar { // Signals signal dismiss() - MaterialCookie { + MaterialShape { Layout.fillHeight: true Layout.leftMargin: 2 Layout.rightMargin: 2 implicitSize: 36 // Intentionally smaller because this one is brighter than others - sides: 10 - amplitude: implicitSize / 44 + shape: switch (root.action) { + case RegionSelection.SnipAction.Copy: + case RegionSelection.SnipAction.Edit: + return MaterialShape.Shape.Cookie4Sided; + case RegionSelection.SnipAction.Search: + return MaterialShape.Shape.Pentagon; + case RegionSelection.SnipAction.CharRecognition: + return MaterialShape.Shape.Sunny; + case RegionSelection.SnipAction.Record: + case RegionSelection.SnipAction.RecordWithSound: + return MaterialShape.Shape.Gem; + default: + return MaterialShape.Shape.Cookie12Sided; + } color: Appearance.colors.colPrimary MaterialSymbol { anchors.centerIn: parent diff --git a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml index 05610f9dd..5ecce47ba 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml @@ -5,11 +5,11 @@ import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Controls +import Qt.labs.synchronizer import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland -import Qt.labs.synchronizer PanelWindow { id: root diff --git a/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml b/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml index 662cfd389..13a909cbb 100644 --- a/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml @@ -55,6 +55,20 @@ ContentPage { } } + ContentSubsection { + visible: Config.options.background.clock.style === "digital" + title: Translation.tr("Digital clock settings") + + ConfigSwitch { + buttonIcon: "animation" + text: Translation.tr("Animate time change") + checked: Config.options.background.clock.digital.animateChange + onCheckedChanged: { + Config.options.background.clock.digital.animateChange = checked; + } + } + } + ContentSubsection { visible: Config.options.background.clock.style === "cookie" title: Translation.tr("Cookie clock settings") @@ -71,6 +85,18 @@ ContentPage { } } + ConfigSwitch { + buttonIcon: "airwave" + text: Translation.tr("Use old sine wave cookie implementation") + checked: Config.options.background.clock.cookie.useSineCookie + onCheckedChanged: { + Config.options.background.clock.cookie.useSineCookie = checked; + } + StyledToolTip { + text: "Looks a bit softer and more consistent with different number of sides,\nbut has less impressive morphing" + } + } + ConfigSpinBox { icon: "add_triangle" text: Translation.tr("Sides") @@ -530,6 +556,15 @@ ContentPage { Config.options.lock.showLockedText = checked; } } + + ConfigSwitch { + buttonIcon: "shapes" + text: Translation.tr('Use varying shapes for password characters') + checked: Config.options.lock.materialShapeChars + onCheckedChanged: { + Config.options.lock.materialShapeChars = checked; + } + } } ContentSubsection { title: Translation.tr("Style: Blurred") diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/Anime.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/Anime.qml index c7a8c19a3..1e1d483c9 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/Anime.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/Anime.qml @@ -119,16 +119,17 @@ Item { } } + property real pageKeyScrollAmount: booruResponseListView.height / 2 Keys.onPressed: (event) => { tagInputField.forceActiveFocus() if (event.modifiers === Qt.NoModifier) { if (event.key === Qt.Key_PageUp) { if (booruResponseListView.atYBeginning) return; - booruResponseListView.contentY = Math.max(0, booruResponseListView.contentY - booruResponseListView.height / 2) + booruResponseListView.contentY = Math.max(0, booruResponseListView.contentY - root.pageKeyScrollAmount) event.accepted = true } else if (event.key === Qt.Key_PageDown) { if (booruResponseListView.atYEnd) return; - booruResponseListView.contentY = Math.min(booruResponseListView.contentHeight - booruResponseListView.height / 2, booruResponseListView.contentY + booruResponseListView.height / 2) + booruResponseListView.contentY = Math.min(booruResponseListView.contentHeight, booruResponseListView.contentY + root.pageKeyScrollAmount) event.accepted = true } } @@ -171,17 +172,20 @@ Item { mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4 property int lastResponseLength: 0 - - model: ScriptModel { - values: { - if(root.responses.length > booruResponseListView.lastResponseLength) { + Connections { + target: root + function onResponsesChanged() { + if (root.responses.length > booruResponseListView.lastResponseLength) { if (booruResponseListView.lastResponseLength > 0 && root.responses[booruResponseListView.lastResponseLength].provider != "system") booruResponseListView.contentY = booruResponseListView.contentY + root.scrollOnNewResponse booruResponseListView.lastResponseLength = root.responses.length } - return root.responses } } + + model: ScriptModel { + values: root.responses + } delegate: BooruResponse { responseData: modelData tagInputField: root.inputField diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/SidebarLeftContent.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/SidebarLeftContent.qml index ae19f07a8..ff1c34509 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/SidebarLeftContent.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/SidebarLeftContent.qml @@ -5,6 +5,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects +import Qt.labs.synchronizer Item { id: root @@ -58,9 +59,8 @@ Item { id: tabBar visible: root.tabButtonList.length > 1 tabButtonList: root.tabButtonList - externalTrackedTab: root.selectedTab - function onCurrentIndexChanged(currentIndex) { - root.selectedTab = currentIndex + Synchronizer on currentIndex { + property alias source: root.selectedTab } } @@ -71,7 +71,7 @@ Item { Layout.fillHeight: true spacing: 10 - currentIndex: tabBar.externalTrackedTab + currentIndex: root.selectedTab onCurrentIndexChanged: { tabBar.enableIndicatorAnimation = true root.selectedTab = currentIndex diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/AiMessage.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/AiMessage.qml index 62f1dc794..3cccb9d8f 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/AiMessage.qml @@ -105,7 +105,7 @@ Rectangle { anchors.right: parent.right anchors.leftMargin: 10 anchors.rightMargin: 10 - spacing: 7 + spacing: 12 Item { Layout.alignment: Qt.AlignVCenter @@ -177,6 +177,20 @@ Rectangle { ButtonGroup { spacing: 5 + AiMessageControlButton { + id: regenButton + buttonIcon: "refresh" + visible: messageData?.role === 'assistant' + + onClicked: { + Ai.regenerate(root.messageIndex) + } + + StyledToolTip { + text: Translation.tr("Regenerate") + } + } + AiMessageControlButton { id: copyButton buttonIcon: activated ? "inventory" : "content_copy" @@ -254,28 +268,50 @@ Rectangle { spacing: 0 Repeater { - model: root.messageBlocks.length - delegate: Loader { - required property int index - property var thisBlock: root.messageBlocks[index] - Layout.fillWidth: true - // property var segment: thisBlock - property var segmentContent: thisBlock.content - property var segmentLang: thisBlock.lang - property var messageData: root.messageData - property var editing: root.editing - property var renderMarkdown: root.renderMarkdown - property var enableMouseSelection: root.enableMouseSelection - property bool thinking: root.messageData?.thinking ?? true - property bool done: root.messageData?.done ?? false - property bool completed: thisBlock.completed ?? false + model: ScriptModel { + values: Array.from({ length: root.messageBlocks.length }, (msg, i) => { + return ({ + type: root.messageBlocks[i].type + }) + }); + } - property bool forceDisableChunkSplitting: root.messageData.content.includes("```") - - source: thisBlock.type === "code" ? "MessageCodeBlock.qml" : - thisBlock.type === "think" ? "MessageThinkBlock.qml" : - "MessageTextBlock.qml" + delegate: DelegateChooser { + id: messageDelegate + role: "type" + DelegateChoice { roleValue: "code"; MessageCodeBlock { + required property int index + property var thisBlock: root.messageBlocks[index] + editing: root.editing + renderMarkdown: root.renderMarkdown + enableMouseSelection: root.enableMouseSelection + segmentContent: thisBlock.content + segmentLang: thisBlock.lang + messageData: root.messageData + } } + DelegateChoice { roleValue: "think"; MessageThinkBlock { + required property int index + property var thisBlock: root.messageBlocks[index] + editing: root.editing + renderMarkdown: root.renderMarkdown + enableMouseSelection: root.enableMouseSelection + segmentContent: thisBlock.content + messageData: root.messageData + done: root.messageData?.done ?? false + completed: thisBlock.completed ?? false + } } + DelegateChoice { roleValue: "text"; MessageTextBlock { + required property int index + property var thisBlock: root.messageBlocks[index] + editing: root.editing + renderMarkdown: root.renderMarkdown + enableMouseSelection: root.enableMouseSelection + segmentContent: thisBlock.content + messageData: root.messageData + done: root.messageData?.done ?? false + forceDisableChunkSplitting: root.messageData.content.includes("```") + } } } } } diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/MessageCodeBlock.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/MessageCodeBlock.qml index c72905688..a6f69063d 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/MessageCodeBlock.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/MessageCodeBlock.qml @@ -13,22 +13,20 @@ import org.kde.syntaxhighlighting ColumnLayout { id: root // These are needed on the parent loader - property bool editing: parent?.editing ?? false - property bool renderMarkdown: parent?.renderMarkdown ?? true - property bool enableMouseSelection: parent?.enableMouseSelection ?? false - property var segmentContent: parent?.segmentContent ?? ({}) - property var segmentLang: parent?.segmentLang ?? "txt" + property bool editing: false + property bool renderMarkdown: true + property bool enableMouseSelection: false + property var segmentContent: ({}) + property var segmentLang: "txt" + property var messageData: {} property bool isCommandRequest: segmentLang === "command" property var displayLang: (isCommandRequest ? "bash" : segmentLang) - property var messageData: parent?.messageData ?? {} property real codeBlockBackgroundRounding: Appearance.rounding.small property real codeBlockHeaderPadding: 3 property real codeBlockComponentSpacing: 2 spacing: codeBlockComponentSpacing - anchors.left: parent.left - anchors.right: parent.right Rectangle { // Code background Layout.fillWidth: true diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/MessageTextBlock.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/MessageTextBlock.qml index a245b6979..2c471d3f8 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/MessageTextBlock.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/MessageTextBlock.qml @@ -14,17 +14,17 @@ import Quickshell.Hyprland ColumnLayout { id: root // These are needed on the parent loader - property bool editing: parent?.editing ?? false - property bool renderMarkdown: parent?.renderMarkdown ?? true - property bool enableMouseSelection: parent?.enableMouseSelection ?? false - property string segmentContent: parent?.segmentContent ?? ({}) - property var messageData: parent?.messageData ?? {} - property bool done: parent?.done ?? true - property list renderedLatexHashes: [] + property bool editing: false + property bool renderMarkdown: true + property bool enableMouseSelection: false + property var segmentContent: ({}) + property var messageData: {} + property bool done: true + property bool forceDisableChunkSplitting: false + property list renderedLatexHashes: [] property string renderedSegmentContent: "" property string shownText: "" - property bool forceDisableChunkSplitting: parent?.forceDisableChunkSplitting ?? false property bool fadeChunkSplitting: !forceDisableChunkSplitting && !editing && !/\n\|/.test(shownText) && Config.options.sidebar.ai.textFadeIn Layout.fillWidth: true diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/MessageThinkBlock.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/MessageThinkBlock.qml index 8407a0b56..1463c6e60 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/MessageThinkBlock.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/MessageThinkBlock.qml @@ -11,13 +11,13 @@ import Qt5Compat.GraphicalEffects Item { id: root // These are needed on the parent loader - property bool editing: parent?.editing ?? false - property bool renderMarkdown: parent?.renderMarkdown ?? true - property bool enableMouseSelection: parent?.enableMouseSelection ?? false - property string segmentContent: parent?.segmentContent ?? ({}) - property var messageData: parent?.messageData ?? {} - property bool done: parent?.done ?? true - property bool completed: parent?.completed ?? false + property bool editing: false + property bool renderMarkdown: true + property bool enableMouseSelection: false + property var segmentContent: ({}) + property var messageData: {} + property bool done: true + property bool completed: false property real thinkBlockBackgroundRounding: Appearance.rounding.small property real thinkBlockHeaderPaddingVertical: 3 diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidPowerProfileToggle.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidPowerProfileToggle.qml index 916e9be51..064180ae5 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidPowerProfileToggle.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidPowerProfileToggle.qml @@ -17,10 +17,10 @@ AndroidQuickToggleButton { case PowerProfile.Performance: return "local_fire_department" } statusText: switch(PowerProfiles.profile) { - case PowerProfile.PowerSaver: return "Power Saver" - case PowerProfile.Balanced: return "Balanced" - case PowerProfile.Performance: return "Performance" - } + case PowerProfile.PowerSaver: return "Power Saver" + case PowerProfile.Balanced: return "Balanced" + case PowerProfile.Performance: return "Performance" + } onClicked: (event) => { if (PowerProfiles.hasPerformanceProfile) { diff --git a/dots/.config/quickshell/ii/scripts/colors/code/material-code-set-color.sh b/dots/.config/quickshell/ii/scripts/colors/code/material-code-set-color.sh new file mode 100755 index 000000000..b29fa7775 --- /dev/null +++ b/dots/.config/quickshell/ii/scripts/colors/code/material-code-set-color.sh @@ -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 + diff --git a/dots/.config/quickshell/ii/scripts/colors/switchwall.sh b/dots/.config/quickshell/ii/scripts/colors/switchwall.sh index 5d1a57c7a..31f260760 100755 --- a/dots/.config/quickshell/ii/scripts/colors/switchwall.sh +++ b/dots/.config/quickshell/ii/scripts/colors/switchwall.sh @@ -55,18 +55,8 @@ post_process() { local screen_height="$2" local wallpaper_path="$3" - handle_kde_material_you_colors & - - # Determine the largest region on the wallpaper that's sufficiently un-busy to put widgets in - # if [ ! -f "$MATUGEN_DIR/scripts/least_busy_region.py" ]; then - # echo "Error: least_busy_region.py script not found in $MATUGEN_DIR/scripts/" - # else - # "$MATUGEN_DIR/scripts/least_busy_region.py" \ - # --screen-width "$screen_width" --screen-height "$screen_height" \ - # --width 300 --height 200 \ - # "$wallpaper_path" > "$STATE_DIR"/user/generated/wallpaper/least_busy_region.json - # fi + "$SCRIPT_DIR/code/material-code-set-color.sh" & } check_and_prompt_upscale() { diff --git a/dots/.config/quickshell/ii/services/Ai.qml b/dots/.config/quickshell/ii/services/Ai.qml index 26657b0d1..47b1d151f 100644 --- a/dots/.config/quickshell/ii/services/Ai.qml +++ b/dots/.config/quickshell/ii/services/Ai.qml @@ -769,6 +769,18 @@ Singleton { root.pendingFilePath = CF.FileUtils.trimFileProtocol(filePath); } + function regenerate(messageIndex) { + if (messageIndex < 0 || messageIndex >= messageIDs.length) return; + const id = root.messageIDs[messageIndex]; + const message = root.messageByID[id]; + if (message.role !== "assistant") return; + // Remove all messages after this one + for (let i = root.messageIDs.length - 1; i >= messageIndex; i--) { + root.removeMessage(i); + } + requester.makeRequest(); + } + function createFunctionOutputMessage(name, output, includeOutputInChat = true) { return aiMessageComponent.createObject(root, { "role": "user", diff --git a/dots/.config/quickshell/ii/services/Audio.qml b/dots/.config/quickshell/ii/services/Audio.qml index 4ce6521c6..5fca1a51a 100644 --- a/dots/.config/quickshell/ii/services/Audio.qml +++ b/dots/.config/quickshell/ii/services/Audio.qml @@ -1,9 +1,9 @@ +pragma Singleton +pragma ComponentBehavior: Bound import qs.modules.common import QtQuick import Quickshell import Quickshell.Services.Pipewire -pragma Singleton -pragma ComponentBehavior: Bound /** * A nice wrapper for default Pipewire audio sink and source. @@ -29,12 +29,18 @@ Singleton { property real lastVolume: 0 function onVolumeChanged() { if (!Config.options.audio.protection.enable) return; + const newVolume = sink.audio.volume; + // when resuming from suspend, we should not write volume to avoid pipewire volume reset issues + if (isNaN(newVolume) || newVolume === undefined || newVolume === null) { + lastReady = false; + lastVolume = 0; + return; + } if (!lastReady) { - lastVolume = sink.audio.volume; + lastVolume = newVolume; lastReady = true; return; } - const newVolume = sink.audio.volume; const maxAllowedIncrease = Config.options.audio.protection.maxAllowedIncrease / 100; const maxAllowed = Config.options.audio.protection.maxAllowed / 100; @@ -45,9 +51,6 @@ Singleton { root.sinkProtectionTriggered(Translation.tr("Exceeded max allowed")); sink.audio.volume = Math.min(lastVolume, maxAllowed); } - if (sink.ready && (isNaN(sink.audio.volume) || sink.audio.volume === undefined || sink.audio.volume === null)) { - sink.audio.volume = 0; - } lastVolume = sink.audio.volume; } } diff --git a/sdata/dist-arch/illogical-impulse-audio/PKGBUILD b/sdata/dist-arch/illogical-impulse-audio/PKGBUILD index 6cab7de53..4a9294122 100644 --- a/sdata/dist-arch/illogical-impulse-audio/PKGBUILD +++ b/sdata/dist-arch/illogical-impulse-audio/PKGBUILD @@ -1,6 +1,6 @@ pkgname=illogical-impulse-audio pkgver=1.0 -pkgrel=1 +pkgrel=2 pkgdesc='Illogical Impulse Audio Dependencies' arch=(any) license=(None) @@ -8,6 +8,7 @@ depends=( cava pavucontrol-qt wireplumber + pipewire-pulse libdbusmenu-gtk3 playerctl ) diff --git a/sdata/dist-arch/illogical-impulse-basic/PKGBUILD b/sdata/dist-arch/illogical-impulse-basic/PKGBUILD index c338727f9..a403480e8 100644 --- a/sdata/dist-arch/illogical-impulse-basic/PKGBUILD +++ b/sdata/dist-arch/illogical-impulse-basic/PKGBUILD @@ -1,20 +1,22 @@ pkgname=illogical-impulse-basic pkgver=1.0 -pkgrel=1 +pkgrel=2 pkgdesc='Illogical Impulse Basic Dependencies' arch=(any) license=(None) depends=( - axel - bc - coreutils - cliphist - cmake - curl - rsync - wget - ripgrep - jq - meson - xdg-user-dirs + axel + bc + coreutils + cliphist + cmake + curl + wget + ripgrep + jq + meson + xdg-user-dirs + # Used in install script + rsync + go-yq # https://github.com/mikefarah/yq ) diff --git a/sdata/dist-gentoo/illogical-impulse-basic/illogical-impulse-basic-1.0-r1.ebuild b/sdata/dist-gentoo/illogical-impulse-basic/illogical-impulse-basic-1.0-r2.ebuild similarity index 96% rename from sdata/dist-gentoo/illogical-impulse-basic/illogical-impulse-basic-1.0-r1.ebuild rename to sdata/dist-gentoo/illogical-impulse-basic/illogical-impulse-basic-1.0-r2.ebuild index 535e80725..eeae5c57b 100644 --- a/sdata/dist-gentoo/illogical-impulse-basic/illogical-impulse-basic-1.0-r1.ebuild +++ b/sdata/dist-gentoo/illogical-impulse-basic/illogical-impulse-basic-1.0-r2.ebuild @@ -25,4 +25,5 @@ RDEPEND=" dev-python/jq dev-build/meson x11-misc/xdg-user-dirs + app-misc/yq-go " diff --git a/sdata/dist-nix/README.md b/sdata/dist-nix/README.md index 458556d72..20f80025d 100644 --- a/sdata/dist-nix/README.md +++ b/sdata/dist-nix/README.md @@ -6,24 +6,10 @@ **NOTE: The sdata/dist-nix is not for NixOS but every distro, using Nix and home-manager.** ## 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. 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 ### 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 ### 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`: -``` -{ 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 ]; - }; - }; - }; -} -``` +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. diff --git a/sdata/dist-nix/home-manager/flake.lock b/sdata/dist-nix/home-manager/flake.lock new file mode 100644 index 000000000..3c2f0f3f0 --- /dev/null +++ b/sdata/dist-nix/home-manager/flake.lock @@ -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 +} diff --git a/sdata/dist-nix/home-manager/flake.nix b/sdata/dist-nix/home-manager/flake.nix new file mode 100644 index 000000000..3422f5c4b --- /dev/null +++ b/sdata/dist-nix/home-manager/flake.nix @@ -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 + ]; + }; + }; + }; +} diff --git a/sdata/dist-nix/home-manager/home.nix b/sdata/dist-nix/home-manager/home.nix new file mode 100644 index 000000000..6f17bd4f0 --- /dev/null +++ b/sdata/dist-nix/home-manager/home.nix @@ -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; +} diff --git a/sdata/dist-nix/install-deps.sh b/sdata/dist-nix/install-deps.sh index 77697e501..726a51259 100644 --- a/sdata/dist-nix/install-deps.sh +++ b/sdata/dist-nix/install-deps.sh @@ -1,4 +1,129 @@ # This script is meant to be sourced. # 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 '' -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 diff --git a/sdata/lib/functions.sh b/sdata/lib/functions.sh index 3282637bb..b236b8324 100644 --- a/sdata/lib/functions.sh +++ b/sdata/lib/functions.sh @@ -69,7 +69,11 @@ function pause(){ fi } 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(){ case $(whoami) in @@ -309,3 +313,49 @@ function auto_get_git_submodule(){ x git submodule update --init --recursive 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/" +} diff --git a/sdata/subcmd-checkdeps/0.run.sh b/sdata/subcmd-checkdeps/0.run.sh new file mode 100644 index 000000000..3c95097b5 --- /dev/null +++ b/sdata/subcmd-checkdeps/0.run.sh @@ -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 diff --git a/sdata/subcmd-checkdeps/options.sh b/sdata/subcmd-checkdeps/options.sh new file mode 100644 index 000000000..562b2e364 --- /dev/null +++ b/sdata/subcmd-checkdeps/options.sh @@ -0,0 +1,33 @@ +# Handle args for subcmd: checkdeps +# shellcheck shell=bash + +showhelp(){ +echo -e "Syntax: $0 checkdeps [OPTIONS] ... + +Check whether pkgs listed in 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 diff --git a/sdata/subcmd-exp-update-old/0.run.sh b/sdata/subcmd-exp-update-old/0.run.sh index 76ddd9ed9..dc4b97075 100644 --- a/sdata/subcmd-exp-update-old/0.run.sh +++ b/sdata/subcmd-exp-update-old/0.run.sh @@ -541,6 +541,7 @@ if git remote get-url origin &>/dev/null; then log_info "Pulling changes from origin/$current_branch..." if git pull; then log_success "Successfully pulled latest changes" + git submodule update --init --recursive else log_warning "Failed to pull changes from remote. Continuing with local repository..." log_info "You may need to resolve conflicts manually later." diff --git a/sdata/subcmd-exp-update/0.run.sh b/sdata/subcmd-exp-update/0.run.sh index 632d63082..2ab21379c 100644 --- a/sdata/subcmd-exp-update/0.run.sh +++ b/sdata/subcmd-exp-update/0.run.sh @@ -844,6 +844,7 @@ if git remote get-url origin &>/dev/null; then else if git pull --ff-only; then log_success "Successfully pulled latest changes" + git submodule update --init --recursive # Verify we actually got new commits if git rev-parse --verify HEAD@{1} &>/dev/null; then if [[ "$(git rev-parse HEAD)" == "$(git rev-parse HEAD@{1})" ]]; then diff --git a/sdata/subcmd-install/0.greeting.sh b/sdata/subcmd-install/0.greeting.sh index 413aacf64..a948e5f3c 100644 --- a/sdata/subcmd-install/0.greeting.sh +++ b/sdata/subcmd-install/0.greeting.sh @@ -9,10 +9,8 @@ printf "${STY_CYAN}[$0]: Hi there! Before we start:${STY_RST}\n" printf "\n" printf "${STY_PURPLE}${STY_BOLD}[NEW] illogical-impulse is now powered by Quickshell.${STY_RST}\n" 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 '# 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" pause printf "${STY_CYAN}${STY_BOLD}Quick overview about what this script does:${STY_RST}\n" diff --git a/sdata/subcmd-install/3.files-exp.sh b/sdata/subcmd-install/3.files-exp.sh new file mode 100644 index 000000000..778fd3ba7 --- /dev/null +++ b/sdata/subcmd-install/3.files-exp.sh @@ -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 diff --git a/sdata/subcmd-install/3.files.sh b/sdata/subcmd-install/3.files.sh index 71c04e163..8e92fed2c 100644 --- a/sdata/subcmd-install/3.files.sh +++ b/sdata/subcmd-install/3.files.sh @@ -18,72 +18,32 @@ function warning_rsync_normal(){ printf "${STY_RST}" } -function backup_clashing_targets(){ - # For dirs/files under target_dir, only backup those which clashes with the ones under source_dir - - # Deal with arguments - 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 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 "Would you like to backup clashing dirs/files under \"$XDG_CONFIG_HOME\" and \"$XDG_DATA_HOME\" to \"$BACKUP_DIR\"?\n" printf "${STY_RST}" while true;do echo " y = Yes, backup" - echo " n = No, skip to next" + 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 ;; - [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}";; esac done - if $backup;then - 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 + if $backup;then backup_configs;fi } function auto_backup_configs(){ # Backup when $BACKUP_DIR does not exist - if [[ ! -d "$BACKUP_DIR" ]]; then - 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 + if [[ ! -d "$BACKUP_DIR" ]]; then backup_configs;fi } ##################################################################################### @@ -143,9 +103,9 @@ esac case $SKIP_FONTCONFIG in 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-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 @@ -159,16 +119,24 @@ case $SKIP_HYPRLAND in true) sleep 0;; *) 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" if [ -f $t ];then echo -e "${STY_BLUE}[$0]: \"$t\" already exists.${STY_RST}" - v mv $t $t.old - v cp -f dots/.config/hypr/hyprland.conf $t - existed_hypr_conf_firstrun=y + if $ii_firstrun;then + echo -e "${STY_BLUE}[$0]: It seems to be the firstrun.${STY_RST}" + 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 echo -e "${STY_YELLOW}[$0]: \"$t\" does not exist yet.${STY_RST}" v cp dots/.config/hypr/hyprland.conf $t - existed_hypr_conf=n fi t="$XDG_CONFIG_HOME/hypr/hypridle.conf" if [ -f $t ];then diff --git a/sdata/subcmd-install/3.files.yaml b/sdata/subcmd-install/3.files.yaml new file mode 100644 index 000000000..59ed69ebc --- /dev/null +++ b/sdata/subcmd-install/3.files.yaml @@ -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" diff --git a/sdata/subcmd-install/options.sh b/sdata/subcmd-install/options.sh index dba8a4430..d60705354 100644 --- a/sdata/subcmd-install/options.sh +++ b/sdata/subcmd-install/options.sh @@ -1,7 +1,7 @@ # Handle args for subcmd: install # shellcheck shell=bash showhelp(){ -echo -e "Syntax: $0 install [OPTIONS]... +printf "Syntax: $0 install [OPTIONS]... Idempotent installation for dotfiles. @@ -23,11 +23,17 @@ Options for install: --skip-miscconf Skip copying the dirs and files to \".configs\" except for Quickshell, Fish and Hyprland --core Alias of --skip-{plasmaintg,fish,miscconf,fontconfig} - --exp-files Use experimental script for the third step copying files --fontset Use a set of pre-defined font and config (currently only fontconfig). Possible values of : $(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(){ @@ -81,7 +87,7 @@ while true ; do ## Ones with parameter --fontset) 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 fi;; diff --git a/setup b/setup index 170e43464..cae0a1928 100755 --- a/setup +++ b/setup @@ -24,6 +24,7 @@ Subcommands: exp-uninstall (Experimental) Uninstall illogical-impulse. exp-update (Experimental) Update illogical-impulse without fully reinstall. 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. For each , use -h for details: @@ -34,7 +35,7 @@ case $1 in # Global help help|--help|-h)showhelp_global;exit;; # Correct subcommand - install|exp-uninstall|exp-update|exp-update-old) + install|exp-uninstall|exp-update|exp-update-old|checkdeps) SUBCMD_NAME=$1 SUBCMD_DIR=./sdata/subcmd-$1 shift;;