diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..6daa3761e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "dots/.config/quickshell/ii/modules/common/widgets/shapes"] + path = dots/.config/quickshell/ii/modules/common/widgets/shapes + url = https://github.com/end-4/rounded-polygon-qmljs.git diff --git a/README.md b/README.md index 85a0330c7..ffedf84a6 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@
Installation (illogical-impulse Quickshell) - - Just run `bash <(curl -s https://ii.clsty.link/setup)` + - Just run `bash <(curl -s https://ii.clsty.link/get)` - Or, clone this repo and run `./setup install` - See [document](https://ii.clsty.link/en/ii-qs/01setup/) for details. - **Default keybinds**: Should be somewhat familiar to Windows or GNOME users. Important ones: diff --git a/dots-extra/fontsets/ar/conf.d b/dots-extra/fontsets/ar/conf.d new file mode 100644 index 000000000..016af78bc --- /dev/null +++ b/dots-extra/fontsets/ar/conf.d @@ -0,0 +1,19 @@ + + + + + + none + + + + + + + sans-serif + + + Noto Sans Arabic + + + diff --git a/dots/.config/hypr/hyprland/keybinds.conf b/dots/.config/hypr/hyprland/keybinds.conf index 60bca72af..263d5048d 100644 --- a/dots/.config/hypr/hyprland/keybinds.conf +++ b/dots/.config/hypr/hyprland/keybinds.conf @@ -70,14 +70,18 @@ bind = Super+Shift, T,exec, qs -c $qsConfig ipc call TEST_ALIVE || pidof slurp | # Color picker bindd = Super+Shift, C, Color picker, exec, hyprpicker -a # Pick color (Hex) >> clipboard # Fullscreen screenshot -bindld = ,Print, Screenshot >> clipboard ,exec,grim - | wl-copy # Screenshot >> clipboard -bindld = Ctrl,Print, Screenshot >> clipboard & save, exec, mkdir -p $(xdg-user-dir PICTURES)/Screenshots && grim $(xdg-user-dir PICTURES)/Screenshots/Screenshot_"$(date '+%Y-%m-%d_%H.%M.%S')".png # Screenshot >> clipboard & file +bindl = ,Print,exec,grim - | wl-copy # Screenshot >> clipboard +bindln = Ctrl,Print, exec, mkdir -p $(xdg-user-dir PICTURES)/Screenshots && grim $(xdg-user-dir PICTURES)/Screenshots/Screenshot_"$(date '+%Y-%m-%d_%H.%M.%S')".png # Screenshot >> clipboard & file (file) +bindln = Ctrl,Print,exec,grim - | wl-copy # [hidden] Screenshot >> clipboard & file (clipboard) # Recording stuff -bindl = Super+Alt, R, exec, ~/.config/hypr/hyprland/scripts/record.sh # Record region (no sound) -bindl = Ctrl+Alt, R, exec, ~/.config/hypr/hyprland/scripts/record.sh --fullscreen # [hidden] Record screen (no sound) -bindl = Super+Shift+Alt, R, exec, ~/.config/hypr/hyprland/scripts/record.sh --fullscreen-sound # Record screen (with sound) +bindl = Super+Shift, R, global, quickshell:regionRecord # Record region (no sound) +bindl = Super+Shift, R, exec, qs -c $qsConfig ipc call TEST_ALIVE || ~/.config/quickshell/$qsConfig/scripts/videos/record.sh # [hidden] Record region (no sound) (fallback) +bindl = Super+Alt, R, global, quickshell:regionRecord # [hidden] Record region (no sound) +bindl = Super+Alt, R, exec, qs -c $qsConfig ipc call TEST_ALIVE || ~/.config/quickshell/$qsConfig/scripts/videos/record.sh # [hidden] Record region (no sound) (fallback) +bindl = Ctrl+Alt, R, exec, ~/.config/quickshell/$qsConfig/scripts/videos/record.sh --fullscreen # [hidden] Record screen (no sound) +bindl = Super+Shift+Alt, R, exec, ~/.config/quickshell/$qsConfig/scripts/videos/record.sh --fullscreen --sound # Record screen (with sound) # AI -bindd = Super+Shift+Alt, mouse:273, Generate AI summary for selected text, exec, ~/.config/hypr/hyprland/scripts/ai/primary-buffer-query.sh # AI summary for selected text +bindd = Super+Shift+Alt, mouse:273, Generate AI summary for selected text, exec, ~/.config/hypr/hyprland/scripts/ai/primary-buffer-query.sh # [hidden] AI summary for selected text #! ##! Window diff --git a/dots/.config/hypr/hyprland/scripts/record.sh b/dots/.config/hypr/hyprland/scripts/record.sh deleted file mode 100755 index 37435ac15..000000000 --- a/dots/.config/hypr/hyprland/scripts/record.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash - -getdate() { - date '+%Y-%m-%d_%H.%M.%S' -} -getaudiooutput() { - pactl list sources | grep 'Name' | grep 'monitor' | cut -d ' ' -f2 -} -getactivemonitor() { - hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name' -} - -xdgvideo="$(xdg-user-dir VIDEOS)" -if [[ $xdgvideo = "$HOME" ]]; then - unset xdgvideo -fi -mkdir -p "${xdgvideo:-$HOME/Videos}" -cd "${xdgvideo:-$HOME/Videos}" || exit - -if pgrep wf-recorder > /dev/null; then - notify-send "Recording Stopped" "Stopped" -a 'Recorder' & - pkill wf-recorder & -else - if [[ "$1" == "--fullscreen-sound" ]]; then - notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown - wf-recorder -o "$(getactivemonitor)" --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --audio="$(getaudiooutput)" - elif [[ "$1" == "--fullscreen" ]]; then - notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown - wf-recorder -o "$(getactivemonitor)" --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t - else - if ! region="$(slurp 2>&1)"; then - notify-send "Recording cancelled" "Selection was cancelled" -a 'Recorder' & disown - exit 1 - fi - notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown - if [[ "$1" == "--sound" ]]; then - wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" --audio="$(getaudiooutput)" - else - wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" - fi - fi -fi diff --git a/dots/.config/quickshell/ii/modules/background/Background.qml b/dots/.config/quickshell/ii/modules/background/Background.qml index 4aed1c08c..fc0bb0cfb 100644 --- a/dots/.config/quickshell/ii/modules/background/Background.qml +++ b/dots/.config/quickshell/ii/modules/background/Background.qml @@ -96,7 +96,10 @@ Variants { left: true right: true } - color: CF.ColorUtils.transparentize(CF.ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colPrimary, 0.75), (bgRoot.wallpaperIsVideo ? 1 : 0)) + color: { + if (!bgRoot.wallpaperSafetyTriggered || bgRoot.wallpaperIsVideo) return "transparent"; + return CF.ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colPrimary, 0.75) + } Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } @@ -442,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/dateIndicator/BubbleDate.qml b/dots/.config/quickshell/ii/modules/background/cookieClock/dateIndicator/BubbleDate.qml index 35659e4fc..3a7313600 100644 --- a/dots/.config/quickshell/ii/modules/background/cookieClock/dateIndicator/BubbleDate.qml +++ b/dots/.config/quickshell/ii/modules/background/cookieClock/dateIndicator/BubbleDate.qml @@ -13,13 +13,14 @@ Item { text: Qt.locale().toString(DateTime.clock.date, root.isMonth ? "MM" : "d") - MaterialCookie { + MaterialShape { + id: bubble z: 5 - sides: root.isMonth ? 1 : 4 + // sides: root.isMonth ? 1 : 4 + shape: root.isMonth ? MaterialShape.Shape.Pill : MaterialShape.Shape.Pentagon anchors.centerIn: parent color: root.isMonth ? Appearance.colors.colPrimaryContainer : Appearance.colors.colTertiaryContainer implicitSize: targetSize - constantlyRotate: Config.options.background.clock.cookie.constantlyRotate } StyledText { diff --git a/dots/.config/quickshell/ii/modules/bar/BarContent.qml b/dots/.config/quickshell/ii/modules/bar/BarContent.qml index 77ed8db69..03e01aa99 100644 --- a/dots/.config/quickshell/ii/modules/bar/BarContent.qml +++ b/dots/.config/quickshell/ii/modules/bar/BarContent.qml @@ -27,7 +27,7 @@ Item { // Bar content region // Background shadow Loader { - active: Config.options.bar.showBackground && Config.options.bar.cornerStyle === 1 + active: Config.options.bar.showBackground && Config.options.bar.cornerStyle === 1 && Config.options.bar.floatStyleShadow anchors.fill: barBackground sourceComponent: StyledRectangularShadow { anchors.fill: undefined // The loader's anchors act on this, and this should not have any anchor diff --git a/dots/.config/quickshell/ii/modules/bar/UtilButtons.qml b/dots/.config/quickshell/ii/modules/bar/UtilButtons.qml index d7f73a4e3..cc3eb64a2 100644 --- a/dots/.config/quickshell/ii/modules/bar/UtilButtons.qml +++ b/dots/.config/quickshell/ii/modules/bar/UtilButtons.qml @@ -41,7 +41,7 @@ Item { visible: Config.options.bar.utilButtons.showScreenRecord sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter - onClicked: Quickshell.execDetached(["bash", "-c", "~/.config/hypr/hyprland/scripts/record.sh"]) + onClicked: Quickshell.execDetached([Directories.recordScriptPath]) MaterialSymbol { horizontalAlignment: Qt.AlignHCenter fill: 1 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/Appearance.qml b/dots/.config/quickshell/ii/modules/common/Appearance.qml index f4b42cac0..38589b161 100644 --- a/dots/.config/quickshell/ii/modules/common/Appearance.qml +++ b/dots/.config/quickshell/ii/modules/common/Appearance.qml @@ -159,6 +159,8 @@ Singleton { property color colTertiaryContainer: m3colors.m3tertiaryContainer property color colTertiaryContainerHover: ColorUtils.mix(m3colors.m3tertiaryContainer, m3colors.m3onTertiaryContainer, 0.90) property color colTertiaryContainerActive: ColorUtils.mix(m3colors.m3tertiaryContainer, colLayer1Active, 0.54) + property color colOnTertiary: m3colors.m3onTertiary + property color colOnTertiaryContainer: m3colors.m3onTertiaryContainer property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency) property color colSurfaceContainer: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency) @@ -315,9 +317,9 @@ Singleton { } property QtObject clickBounce: QtObject { - property int duration: 200 + property int duration: 400 property int type: Easing.BezierSpline - property list bezierCurve: animationCurves.expressiveFastSpatial + property list bezierCurve: animationCurves.expressiveDefaultSpatial property int velocity: 850 property Component numberAnimation: Component { NumberAnimation { duration: root.animation.clickBounce.duration diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index f496f7bab..f43b86187 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -165,6 +165,9 @@ Singleton { property bool dateInClock: true property bool constantlyRotate: false } + property JsonObject digital: JsonObject { + property bool animateChange: true + } } property string wallpaperPath: "" @@ -194,6 +197,7 @@ Singleton { } property bool bottom: false // Instead of top property int cornerStyle: 0 // 0: Hug | 1: Float | 2: Plain rectangle + property bool floatStyleShadow: true // Show shadow behind bar when cornerStyle == 1 (Float) property bool borderless: false // true for no grouping of items property string topLeftIcon: "spark" // Options: "distro" or any icon name in ~/.config/quickshell/ii/assets/icons property bool showBackground: true @@ -376,6 +380,11 @@ Singleton { property int updateInterval: 3000 } + property JsonObject musicRecognition: JsonObject { + property int timeout: 16 + property int interval: 4 + } + property JsonObject search: JsonObject { property int nonAppResultDelay: 30 // This prevents lagging when typing property string engineBaseUrl: "https://www.google.com/search?q=" @@ -419,10 +428,11 @@ Singleton { property bool bottom: false property bool valueScroll: true property bool clickless: false - property real cornerRegionWidth: 250 - property real cornerRegionHeight: 2 + property int cornerRegionWidth: 250 + property int cornerRegionHeight: 5 property bool visualize: false property bool clicklessCornerEnd: true + property int clicklessCornerVerticalOffset: 1 } property JsonObject quickToggles: JsonObject { diff --git a/dots/.config/quickshell/ii/modules/common/Directories.qml b/dots/.config/quickshell/ii/modules/common/Directories.qml index 94821e857..411ed6c7f 100644 --- a/dots/.config/quickshell/ii/modules/common/Directories.qml +++ b/dots/.config/quickshell/ii/modules/common/Directories.qml @@ -42,6 +42,7 @@ Singleton { property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/ai/prompts`) property string aiChats: FileUtils.trimFileProtocol(`${Directories.state}/user/ai/chats`) property string aiTranslationScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/ai/gemini-translate.sh`) + property string recordScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/videos/record.sh`) // Cleanup on init Component.onCompleted: { Quickshell.execDetached(["mkdir", "-p", `${shellConfig}`]) diff --git a/dots/.config/quickshell/ii/modules/common/widgets/ButtonGroup.qml b/dots/.config/quickshell/ii/modules/common/widgets/ButtonGroup.qml index 32cd5e04f..d3c9114eb 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/ButtonGroup.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/ButtonGroup.qml @@ -13,8 +13,8 @@ Rectangle { property alias uniformCellSizes: rowLayout.uniformCellSizes property real spacing: 5 property real padding: 0 - property int clickIndex: rowLayout.clickIndex - property int childrenCount: rowLayout.children.length + property alias clickIndex: rowLayout.clickIndex + property alias childrenCount: rowLayout.childrenCount property real contentWidth: { let total = 0; @@ -44,5 +44,6 @@ Rectangle { anchors.margins: root.padding spacing: root.spacing property int clickIndex: -1 + property int childrenCount: children.length }] } diff --git a/dots/.config/quickshell/ii/modules/common/widgets/MaterialCookie.qml b/dots/.config/quickshell/ii/modules/common/widgets/MaterialCookie.qml index a0cdfeafc..79048ca63 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/MaterialCookie.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/MaterialCookie.qml @@ -3,7 +3,6 @@ import QtQuick.Shapes import Quickshell import qs.modules.common - Item { id: root diff --git a/dots/.config/quickshell/ii/modules/common/widgets/MaterialLoadingIndicator.qml b/dots/.config/quickshell/ii/modules/common/widgets/MaterialLoadingIndicator.qml new file mode 100644 index 000000000..502cd1a3f --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/MaterialLoadingIndicator.qml @@ -0,0 +1,89 @@ +pragma ComponentBehavior: Bound +import QtQuick +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets + +Rectangle { + id: root + + property bool loading: true + property double pullProgress: 0 + + // Size, color + property double implicitSize: 48 + implicitWidth: implicitSize + implicitHeight: implicitSize + radius: Math.min(width, height) / 2 + color: Appearance.colors.colPrimaryContainer + property double baseShapeSize: root.implicitSize * 0.7 + property double leapZoomSize: root.baseShapeSize * 1.2 + property double leapZoomProgress: 0 + + // Shape + property list shapes: [ + MaterialShape.Shape.SoftBurst, + MaterialShape.Shape.Cookie9Sided, + MaterialShape.Shape.Pentagon, + MaterialShape.Shape.Pill, + MaterialShape.Shape.Sunny, + MaterialShape.Shape.Cookie4Sided, + MaterialShape.Shape.Oval, + ] + property int shapeIndex: 0 + property double pullRotation: root.loading ? 0 : -(root.pullProgress * 360) + property double continuousRotation: 0 + property double leapRotation: 0 + rotation: pullRotation + continuousRotation + leapRotation + + RotationAnimation on continuousRotation { + running: root.loading + duration: 12000 + easing.type: Easing.Linear + loops: Animation.Infinite + from: 0 + to: 360 + } + Timer { + interval: 800 + running: root.loading + repeat: true + onTriggered: leapAnimation.start() + } + ParallelAnimation { + id: leapAnimation + PropertyAction { target: root; property: "shapeIndex"; value: (root.shapeIndex + 1) % root.shapes.length } + RotationAnimation { + target: root + direction: RotationAnimation.Shortest + property: "leapRotation" + to: (root.leapRotation + 90) % 360 + duration: 350 + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: root + property: "leapZoomProgress" + from: 0 + to: 1 + duration: 750 + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.animationCurves.standard + } + } + + MaterialShape { + id: shape + anchors.centerIn: parent + shape: root.shapes[root.shapeIndex] + implicitSize: { + const leapZoomDiff = root.leapZoomSize - root.baseShapeSize + const progressFirstHalf = Math.min(root.leapZoomProgress, 0.5) * 2; + const progressSecondHalf = Math.max(root.leapZoomProgress - 0.5, 0) * 2; + return root.baseShapeSize + leapZoomDiff * progressFirstHalf - leapZoomDiff * progressSecondHalf; + } + color: Appearance.colors.colOnPrimaryContainer + + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/MaterialShape.qml b/dots/.config/quickshell/ii/modules/common/widgets/MaterialShape.qml new file mode 100644 index 000000000..30d953478 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/MaterialShape.qml @@ -0,0 +1,87 @@ +import qs.modules.common.widgets.shapes +import "shapes/material-shapes.js" as MaterialShapes + +ShapeCanvas { + id: root + enum Shape { + Circle, + Square, + Slanted, + Arch, + Fan, + Arrow, + SemiCircle, + Oval, + Pill, + Triangle, + Diamond, + ClamShell, + Pentagon, + Gem, + Sunny, + VerySunny, + Cookie4Sided, + Cookie6Sided, + Cookie7Sided, + Cookie9Sided, + Cookie12Sided, + Ghostish, + Clover4Leaf, + Clover8Leaf, + Burst, + SoftBurst, + Boom, + SoftBoom, + Flower, + Puffy, + PuffyDiamond, + PixelCircle, + PixelTriangle, + Bun, + Heart + } + required property var shape + property double implicitSize + implicitHeight: implicitSize + implicitWidth: implicitSize + roundedPolygon: { + switch (root.shape) { + case MaterialShape.Shape.Circle: return MaterialShapes.getCircle(); + case MaterialShape.Shape.Square: return MaterialShapes.getSquare(); + case MaterialShape.Shape.Slanted: return MaterialShapes.getSlanted(); + case MaterialShape.Shape.Arch: return MaterialShapes.getArch(); + case MaterialShape.Shape.Fan: return MaterialShapes.getFan(); + case MaterialShape.Shape.Arrow: return MaterialShapes.getArrow(); + case MaterialShape.Shape.SemiCircle: return MaterialShapes.getSemiCircle(); + case MaterialShape.Shape.Oval: return MaterialShapes.getOval(); + case MaterialShape.Shape.Pill: return MaterialShapes.getPill(); + case MaterialShape.Shape.Triangle: return MaterialShapes.getTriangle(); + case MaterialShape.Shape.Diamond: return MaterialShapes.getDiamond(); + case MaterialShape.Shape.ClamShell: return MaterialShapes.getClamShell(); + case MaterialShape.Shape.Pentagon: return MaterialShapes.getPentagon(); + case MaterialShape.Shape.Gem: return MaterialShapes.getGem(); + case MaterialShape.Shape.Sunny: return MaterialShapes.getSunny(); + case MaterialShape.Shape.VerySunny: return MaterialShapes.getVerySunny(); + case MaterialShape.Shape.Cookie4Sided: return MaterialShapes.getCookie4Sided(); + case MaterialShape.Shape.Cookie6Sided: return MaterialShapes.getCookie6Sided(); + case MaterialShape.Shape.Cookie7Sided: return MaterialShapes.getCookie7Sided(); + case MaterialShape.Shape.Cookie9Sided: return MaterialShapes.getCookie9Sided(); + case MaterialShape.Shape.Cookie12Sided: return MaterialShapes.getCookie12Sided(); + case MaterialShape.Shape.Ghostish: return MaterialShapes.getGhostish(); + case MaterialShape.Shape.Clover4Leaf: return MaterialShapes.getClover4Leaf(); + case MaterialShape.Shape.Clover8Leaf: return MaterialShapes.getClover8Leaf(); + case MaterialShape.Shape.Burst: return MaterialShapes.getBurst(); + case MaterialShape.Shape.SoftBurst: return MaterialShapes.getSoftBurst(); + case MaterialShape.Shape.Boom: return MaterialShapes.getBoom(); + case MaterialShape.Shape.SoftBoom: return MaterialShapes.getSoftBoom(); + case MaterialShape.Shape.Flower: return MaterialShapes.getFlower(); + case MaterialShape.Shape.Puffy: return MaterialShapes.getPuffy(); + case MaterialShape.Shape.PuffyDiamond: return MaterialShapes.getPuffyDiamond(); + case MaterialShape.Shape.PixelCircle: return MaterialShapes.getPixelCircle(); + case MaterialShape.Shape.PixelTriangle: return MaterialShapes.getPixelTriangle(); + case MaterialShape.Shape.Bun: return MaterialShapes.getBun(); + case MaterialShape.Shape.Heart: return MaterialShapes.getHeart(); + default: return MaterialShapes.getCircle(); + } + } +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/CookieWrappedMaterialSymbol.qml b/dots/.config/quickshell/ii/modules/common/widgets/MaterialShapeWrappedMaterialSymbol.qml similarity index 90% rename from dots/.config/quickshell/ii/modules/common/widgets/CookieWrappedMaterialSymbol.qml rename to dots/.config/quickshell/ii/modules/common/widgets/MaterialShapeWrappedMaterialSymbol.qml index 996388a3c..b8c3469d6 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/CookieWrappedMaterialSymbol.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/MaterialShapeWrappedMaterialSymbol.qml @@ -2,7 +2,7 @@ import QtQuick import qs.modules.common import qs.modules.common.widgets -MaterialCookie { +MaterialShape { id: root property alias text: symbol.text property alias iconSize: symbol.iconSize @@ -13,7 +13,7 @@ MaterialCookie { color: Appearance.colors.colSecondaryContainer colSymbol: Appearance.colors.colOnSecondaryContainer - sides: 5 + shape: MaterialShape.Shape.Clover4Leaf implicitSize: Math.max(symbol.implicitWidth, symbol.implicitHeight) + padding * 2 diff --git a/dots/.config/quickshell/ii/modules/common/widgets/NoticeBox.qml b/dots/.config/quickshell/ii/modules/common/widgets/NoticeBox.qml index 6b110a9ae..c6a1d29c6 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/NoticeBox.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/NoticeBox.qml @@ -7,6 +7,7 @@ Rectangle { id: root property alias materialIcon: icon.text property alias text: noticeText.text + default property alias data: buttonRow.data radius: Appearance.rounding.normal color: Appearance.colors.colPrimaryContainer @@ -28,13 +29,23 @@ Rectangle { color: Appearance.colors.colOnPrimaryContainer } - StyledText { - id: noticeText + ColumnLayout { Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - text: "Notice message" - color: Appearance.colors.colOnPrimaryContainer - wrapMode: Text.WordWrap + spacing: 4 + + StyledText { + id: noticeText + Layout.fillWidth: true + text: "Notice message" + color: Appearance.colors.colOnPrimaryContainer + wrapMode: Text.WordWrap + } + + RowLayout { + id: buttonRow + visible: children.length > 0 + Layout.fillWidth: true + } } } } diff --git a/dots/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml b/dots/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml index 8635f4f78..795a708be 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml @@ -6,7 +6,7 @@ import Quickshell import Quickshell.Widgets import Quickshell.Services.Notifications -MaterialCookie { // App icon +MaterialShape { // App icon id: root property var appIcon: "" property var summary: "" @@ -21,8 +21,11 @@ MaterialCookie { // App icon property real smallAppIconSize: implicitSize * smallAppIconScale implicitSize: 38 * scale - sides: isUrgent ? 10 : 0 - amplitude: implicitSize / 24 + property list urgentShapes: [ + MaterialShape.Shape.VerySunny, + MaterialShape.Shape.SoftBurst, + ] + shape: isUrgent ? urgentShapes[Math.floor(Math.random() * urgentShapes.length)] : MaterialShape.Shape.Circle color: isUrgent ? Appearance.colors.colPrimary : Appearance.colors.colSecondaryContainer Loader { diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/PagePlaceholder.qml b/dots/.config/quickshell/ii/modules/common/widgets/PagePlaceholder.qml similarity index 75% rename from dots/.config/quickshell/ii/modules/sidebarLeft/PagePlaceholder.qml rename to dots/.config/quickshell/ii/modules/common/widgets/PagePlaceholder.qml index 9c41e64d0..36378b53b 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/PagePlaceholder.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/PagePlaceholder.qml @@ -7,9 +7,11 @@ Item { id: root property bool shown: true - property alias icon: cookieWrappedMaterialSymbol.text + property alias icon: shapeWidget.text property alias title: widgetNameText.text property alias description: widgetDescriptionText.text + property alias shape: shapeWidget.shape + property alias descriptionHorizontalAlignment: widgetDescriptionText.horizontalAlignment opacity: shown ? 1 : 0 visible: opacity > 0 @@ -27,14 +29,16 @@ Item { anchors.centerIn: parent spacing: 5 - CookieWrappedMaterialSymbol { - id: cookieWrappedMaterialSymbol + MaterialShapeWrappedMaterialSymbol { + id: shapeWidget Layout.alignment: Qt.AlignHCenter - iconSize: 60 - rotation: -60 * (1 - root.opacity) + padding: 12 + iconSize: 56 + rotation: -30 * (1 - root.opacity) } StyledText { id: widgetNameText + visible: title !== "" Layout.alignment: Qt.AlignHCenter font.pixelSize: Appearance.font.pixelSize.larger font.family: Appearance.font.family.title @@ -43,6 +47,7 @@ Item { } StyledText { id: widgetDescriptionText + visible: description !== "" Layout.fillWidth: true font.pixelSize: Appearance.font.pixelSize.small color: Appearance.m3colors.m3outline 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/SecondaryTabButton.qml b/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml index 347774cff..b1036c777 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml @@ -91,7 +91,7 @@ TabButton { background: Rectangle { id: buttonBackground - radius: Appearance?.rounding.small ?? 7 + radius: Appearance?.rounding.normal implicitHeight: 37 color: (root.hovered ? root.colBackgroundHover : root.colBackground) layer.enabled: true 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/notification_utils.js b/dots/.config/quickshell/ii/modules/common/widgets/notification_utils.js index 7ab21c3bb..f817a460b 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/notification_utils.js +++ b/dots/.config/quickshell/ii/modules/common/widgets/notification_utils.js @@ -17,13 +17,17 @@ function findSuitableMaterialSymbol(summary = "") { 'time': 'scheduleb', 'installed': 'download', 'configuration reloaded': 'reset_wrench', + 'unable': 'question_mark', + "couldn't": 'question_mark', 'config': 'reset_wrench', 'update': 'update', 'ai response': 'neurology', 'control': 'settings', 'upsca': 'compare', + 'music': 'queue_music', 'install': 'deployed_code_update', 'startswith:file': 'folder_copy', // Declarative startsWith check + }; const lowerSummary = summary.toLowerCase(); diff --git a/dots/.config/quickshell/ii/modules/common/widgets/shapes b/dots/.config/quickshell/ii/modules/common/widgets/shapes new file mode 160000 index 000000000..8369a081b --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/shapes @@ -0,0 +1 @@ +Subproject commit 8369a081ba4694bb3433c6e25fe7329522a2187e diff --git a/dots/.config/quickshell/ii/modules/lock/Lock.qml b/dots/.config/quickshell/ii/modules/lock/Lock.qml index 15950093c..458f7c63c 100644 --- a/dots/.config/quickshell/ii/modules/lock/Lock.qml +++ b/dots/.config/quickshell/ii/modules/lock/Lock.qml @@ -28,7 +28,10 @@ Scope { Connections { target: GlobalStates function onScreenLockedChanged() { - if (GlobalStates.screenLocked) lockContext.reset(); + if (GlobalStates.screenLocked) { + lockContext.reset(); + lockContext.tryFingerUnlock(); + } } } @@ -113,7 +116,7 @@ Scope { onPressed: { if (Config.options.lock.useHyprlock) { - Quickshell.execDetached(["hyprlock"]) + Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]); return; } GlobalStates.screenLocked = true; 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 f31595825..628f4d2fa 100644 --- a/dots/.config/quickshell/ii/modules/lock/LockSurface.qml +++ b/dots/.config/quickshell/ii/modules/lock/LockSurface.qml @@ -1,5 +1,6 @@ import QtQuick import QtQuick.Layouts +import Qt5Compat.GraphicalEffects import Quickshell.Services.UPower import qs import qs.services @@ -7,6 +8,7 @@ import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.bar as Bar +import Quickshell import Quickshell.Services.SystemTray MouseArea { @@ -98,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") @@ -124,7 +143,17 @@ MouseArea { Keys.onPressed: event => { root.context.resetClearTimer(); } + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: passwordBox.width - 8 + height: passwordBox.height + radius: height / 2 + } + } + // Shake when wrong password SequentialAnimation { id: wrongPasswordShakeAnim NumberAnimation { target: passwordBox; property: "x"; to: -30; duration: 50 } @@ -139,6 +168,17 @@ MouseArea { if (GlobalStates.screenUnlockFailed) wrongPasswordShakeAnim.restart(); } } + + // We're drawing dots manually + color: ColorUtils.transparentize(Appearance.colors.colOnLayer1) + PasswordChars { + anchors { + fill: parent + leftMargin: passwordBox.padding + rightMargin: passwordBox.padding + } + length: root.context.currentText.length + } } ToolbarButton { diff --git a/dots/.config/quickshell/ii/modules/lock/PasswordChars.qml b/dots/.config/quickshell/ii/modules/lock/PasswordChars.qml new file mode 100644 index 000000000..79ffc9464 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/lock/PasswordChars.qml @@ -0,0 +1,96 @@ +pragma ComponentBehavior: Bound +import QtQuick +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.common.functions +import Quickshell + +StyledFlickable { + id: root + required property int length + contentWidth: dotsRow.implicitWidth + contentX: (Math.max(contentWidth - width, 0)) + Behavior on contentX { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + rightMargin: 14 + Row { + id: dotsRow + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + leftMargin: 4 + } + spacing: 10 + Repeater { + model: ScriptModel { + values: Array(root.length) + } + delegate: Item { + id: charItem + required property int index + implicitWidth: 10 + implicitHeight: 10 + MaterialShape { + id: materialShape + anchors.centerIn: parent + property list charShapes: [ + MaterialShape.Shape.Clover4Leaf, + MaterialShape.Shape.Arrow, + MaterialShape.Shape.Pill, + MaterialShape.Shape.SoftBurst, + MaterialShape.Shape.Diamond, + MaterialShape.Shape.ClamShell, + MaterialShape.Shape.Pentagon, + ] + shape: charShapes[charItem.index % charShapes.length] + // Animate on appearance + color: Appearance.colors.colPrimary + implicitSize: 0 + opacity: 0 + scale: 0.5 + Component.onCompleted: { + appearAnim.start(); + } + ParallelAnimation { + id: appearAnim + NumberAnimation { + target: materialShape + properties: "opacity" + to: 1 + duration: 50 + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + NumberAnimation { + target: materialShape + properties: "scale" + to: 1 + duration: 200 + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial + } + NumberAnimation { + target: materialShape + properties: "implicitSize" + to: 18 + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial + } + ColorAnimation { + target: materialShape + properties: "color" + from: Appearance.colors.colPrimary + to: Appearance.colors.colOnLayer1 + duration: 1000 + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } + } + } + } + } +} 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 cc314fbd3..e737e9cac 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/OptionsToolbar.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/OptionsToolbar.qml @@ -44,6 +44,9 @@ Toolbar { return "image_search"; case RegionSelection.SnipAction.CharRecognition: return "document_scanner"; + case RegionSelection.SnipAction.Record: + case RegionSelection.SnipAction.RecordWithSound: + return "videocam"; default: return ""; } diff --git a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml index 39c45d965..5ecce47ba 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml @@ -5,15 +5,16 @@ 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 visible: false + color: "transparent" WlrLayershell.namespace: "quickshell:regionSelector" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive @@ -26,7 +27,7 @@ PanelWindow { } // TODO: Ask: sidebar AI; Ocr: tesseract - enum SnipAction { Copy, Edit, Search, CharRecognition } + enum SnipAction { Copy, Edit, Search, CharRecognition, Record, RecordWithSound } enum SelectionMode { RectCorners, Circle } property var action: RegionSelection.SnipAction.Copy property var selectionMode: RegionSelection.SelectionMode.RectCorners @@ -175,14 +176,35 @@ PanelWindow { property real regionY: Math.min(dragStartY, draggingY) Process { - id: screenshotProcess + id: screenshotProc running: true command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(root.screen.name)}' '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`] onExited: (exitCode, exitStatus) => { - root.visible = true; if (root.enableContentRegions) imageDetectionProcess.running = true; + root.preparationDone = !checkRecordingProc.running; } } + property bool isRecording: root.action === RegionSelection.SnipAction.Record || root.action === RegionSelection.SnipAction.RecordWithSound + property bool recordingShouldStop: false + Process { + id: checkRecordingProc + running: isRecording + command: ["pidof", "wf-recorder"] + onExited: (exitCode, exitStatus) => { + root.preparationDone = !screenshotProc.running + root.recordingShouldStop = (exitCode === 0); + } + } + property bool preparationDone: false + onPreparationDoneChanged: { + if (!preparationDone) return; + if (root.isRecording && root.recordingShouldStop) { + Quickshell.execDetached([Directories.recordScriptPath]); + root.dismiss(); + return; + } + root.visible = true; + } Process { id: imageDetectionProcess @@ -221,11 +243,16 @@ PanelWindow { } // Set command for action + const rx = Math.round(root.regionX * root.monitorScale); + const ry = Math.round(root.regionY * root.monitorScale); + const rw = Math.round(root.regionWidth * root.monitorScale); + const rh = Math.round(root.regionHeight * root.monitorScale); const cropBase = `magick ${StringUtils.shellSingleQuoteEscape(root.screenshotPath)} ` - + `-crop ${root.regionWidth * root.monitorScale}x${root.regionHeight * root.monitorScale}+${root.regionX * root.monitorScale}+${root.regionY * root.monitorScale}` + + `-crop ${rw}x${rh}+${rx}+${ry}` const cropToStdout = `${cropBase} -` const cropInPlace = `${cropBase} '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'` const cleanup = `rm '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'` + const slurpRegion = `${rx},${ry} ${rw}x${rh}` const uploadAndGetUrl = (filePath) => { return `curl -sF files[]=@'${StringUtils.shellSingleQuoteEscape(filePath)}' ${root.fileUploadApiEndpoint} | jq -r '.files[0].url'` } @@ -242,6 +269,12 @@ PanelWindow { case RegionSelection.SnipAction.CharRecognition: snipProc.command = ["bash", "-c", `${cropInPlace} && tesseract '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}' stdout -l $(tesseract --list-langs | awk 'NR>1{print $1}' | tr '\\n' '+' | sed 's/\\+$/\\n/') | wl-copy && ${cleanup}`] break; + case RegionSelection.SnipAction.Record: + snipProc.command = ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}'`] + break; + case RegionSelection.SnipAction.RecordWithSound: + snipProc.command = ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}' --sound`] + break; default: console.warn("[Region Selector] Unknown snip action, skipping snip."); root.dismiss(); diff --git a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelector.qml b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelector.qml index 0ccb26eb0..40440366a 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelector.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelector.qml @@ -62,6 +62,18 @@ Scope { GlobalStates.regionSelectorOpen = true } + function record() { + root.action = RegionSelection.SnipAction.Record + root.selectionMode = RegionSelection.SelectionMode.RectCorners + GlobalStates.regionSelectorOpen = true + } + + function recordWithSound() { + root.action = RegionSelection.SnipAction.RecordWithSound + root.selectionMode = RegionSelection.SelectionMode.RectCorners + GlobalStates.regionSelectorOpen = true + } + IpcHandler { target: "region" @@ -71,10 +83,15 @@ Scope { function search() { root.search() } - function ocr() { root.ocr() } + function record() { + root.record() + } + function recordWithSound() { + root.recordWithSound() + } } GlobalShortcut { @@ -92,4 +109,14 @@ Scope { description: "Recognizes text in the selected region" onPressed: root.ocr() } + GlobalShortcut { + name: "regionRecord" + description: "Records the selected region" + onPressed: root.record() + } + GlobalShortcut { + name: "regionRecordWithSound" + description: "Records the selected region with sound" + onPressed: root.recordWithSound() + } } diff --git a/dots/.config/quickshell/ii/modules/screenCorners/ScreenCorners.qml b/dots/.config/quickshell/ii/modules/screenCorners/ScreenCorners.qml index 55173b664..cbb8087ae 100644 --- a/dots/.config/quickshell/ii/modules/screenCorners/ScreenCorners.qml +++ b/dots/.config/quickshell/ii/modules/screenCorners/ScreenCorners.qml @@ -79,10 +79,12 @@ Scope { implicitWidth: Config.options.sidebar.cornerOpen.cornerRegionWidth implicitHeight: Config.options.sidebar.cornerOpen.cornerRegionHeight hoverEnabled: true - onMouseXChanged: { + onPositionChanged: { if (!Config.options.sidebar.cornerOpen.clicklessCornerEnd) return; - if ((cornerWidget.isRight && mouseArea.mouseX >= mouseArea.width - 2) - || (cornerWidget.isLeft && mouseArea.mouseX <= 2)) + const verticalOffset = Config.options.sidebar.cornerOpen.clicklessCornerVerticalOffset; + const correctX = (cornerWidget.isRight && mouseArea.mouseX >= mouseArea.width - 2) || (cornerWidget.isLeft && mouseArea.mouseX <= 2); + const correctY = (cornerWidget.isTop && mouseArea.mouseY > verticalOffset || cornerWidget.isBottom && mouseArea.mouseY < mouseArea.height - verticalOffset); + if (correctX && correctY) screenCorners.actionForCorner[cornerPanelWindow.corner](); } onEntered: { diff --git a/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml b/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml index bd08cfffd..cf7e98171 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") @@ -787,22 +801,22 @@ ContentPage { } } } + ConfigSwitch { + buttonIcon: "highlight_mouse_cursor" + text: Translation.tr("Hover to trigger") + checked: Config.options.sidebar.cornerOpen.clickless + onCheckedChanged: { + Config.options.sidebar.cornerOpen.clickless = checked; + } + + StyledToolTip { + text: Translation.tr("When this is off you'll have to click") + } + } Row { - ConfigSwitch { - buttonIcon: "highlight_mouse_cursor" - text: Translation.tr("Hover to trigger") - checked: Config.options.sidebar.cornerOpen.clickless - onCheckedChanged: { - Config.options.sidebar.cornerOpen.clickless = checked; - } - - StyledToolTip { - text: Translation.tr("When this is off you'll have to click") - } - } ConfigSwitch { enabled: !Config.options.sidebar.cornerOpen.clickless - text: Translation.tr("but force at absolute corner") + text: Translation.tr("Force hover open at absolute corner") checked: Config.options.sidebar.cornerOpen.clicklessCornerEnd onCheckedChanged: { Config.options.sidebar.cornerOpen.clicklessCornerEnd = checked; @@ -812,7 +826,29 @@ ContentPage { text: Translation.tr("When the previous option is off and this is on,\nyou can still hover the corner's end to open sidebar,\nand the remaining area can be used for volume/brightness scroll") } } + ConfigSpinBox { + icon: "arrow_cool_down" + text: Translation.tr("with vertical offset") + value: Config.options.sidebar.cornerOpen.clicklessCornerVerticalOffset + from: 0 + to: 20 + stepSize: 1 + onValueChanged: { + Config.options.sidebar.cornerOpen.clicklessCornerVerticalOffset = value; + } + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + StyledToolTip { + extraVisibleCondition: mouseArea.containsMouse + text: Translation.tr("Why this is cool:\nFor non-0 values, it won't trigger when you reach the\nscreen corner along the horizontal edge, but it will when\nyou do along the vertical edge") + } + } + } } + ConfigRow { uniform: true ConfigSwitch { diff --git a/dots/.config/quickshell/ii/modules/settings/QuickConfig.qml b/dots/.config/quickshell/ii/modules/settings/QuickConfig.qml index aff06a384..c9c540afa 100644 --- a/dots/.config/quickshell/ii/modules/settings/QuickConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/QuickConfig.qml @@ -332,5 +332,33 @@ ContentPage { NoticeBox { Layout.fillWidth: true text: Translation.tr('Not all options are available in this app. You should also check the config file by hitting the "Config file" button on the topleft corner or opening %1 manually.').arg(Directories.shellConfigPath) + + Item { + Layout.fillWidth: true + } + RippleButtonWithIcon { + id: copyPathButton + property bool justCopied: false + Layout.fillWidth: false + buttonRadius: Appearance.rounding.small + materialIcon: justCopied ? "check" : "content_copy" + mainText: justCopied ? Translation.tr("Path copied") : Translation.tr("Copy path") + onClicked: { + copyPathButton.justCopied = true + Quickshell.clipboardText = FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse/config.json`); + revertTextTimer.restart(); + } + colBackground: ColorUtils.transparentize(Appearance.colors.colPrimaryContainer) + colBackgroundHover: Appearance.colors.colPrimaryContainerHover + colRipple: Appearance.colors.colPrimaryContainerActive + + Timer { + id: revertTextTimer + interval: 1500 + onTriggered: { + copyPathButton.justCopied = false + } + } + } } } diff --git a/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml b/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml index fae5bf4de..4636d666b 100644 --- a/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml @@ -24,6 +24,34 @@ ContentPage { } } + ContentSection { + icon: "music_cast" + title: Translation.tr("Music Recognition") + + ConfigSpinBox { + icon: "timer_off" + text: Translation.tr("Total duration timeout (s)") + value: Config.options.musicRecognition.timeout + from: 10 + to: 100 + stepSize: 2 + onValueChanged: { + Config.options.musicRecognition.timeout = value; + } + } + ConfigSpinBox { + icon: "av_timer" + text: Translation.tr("Polling interval (s)") + value: Config.options.musicRecognition.interval + from: 2 + to: 10 + stepSize: 1 + onValueChanged: { + Config.options.musicRecognition.interval = value; + } + } + } + ContentSection { icon: "cell_tower" title: Translation.tr("Networking") @@ -54,6 +82,7 @@ ContentPage { Config.options.resources.updateInterval = value; } } + } ContentSection { diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml index 6d00752bc..eff0483d3 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml @@ -369,6 +369,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\) icon: "neurology" title: Translation.tr("Large language models") description: Translation.tr("Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window") + shape: MaterialShape.Shape.PixelCircle } ScrollToBottomButton { diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/Anime.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/Anime.qml index c673f6304..1e1d483c9 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/Anime.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/Anime.qml @@ -23,12 +23,21 @@ Item { property var suggestionQuery: "" property var suggestionList: [] + property bool pullLoading: false + property int pullLoadingGap: 80 + property real normalizedPullDistance: Math.max(0, (1 - Math.exp(-booruResponseListView.verticalOvershoot / 50)) * booruResponseListView.dragging) + Connections { target: Booru function onTagSuggestion(query, suggestions) { root.suggestionQuery = query; root.suggestionList = suggestions; } + function onRunningRequestsChanged() { + if (Booru.runningRequests === 0) { + root.pullLoading = false; + } + } } property var allCommands: [ @@ -53,6 +62,8 @@ Item { if (root.responses.length > 0) { const lastResponse = root.responses[root.responses.length - 1]; root.handleInput(`${lastResponse.tags.join(" ")} ${parseInt(lastResponse.page) + 1}`); + } else { + root.handleInput(""); } } }, @@ -85,10 +96,7 @@ Item { } } else if (inputText.trim() == "+") { - if (root.responses.length > 0) { - const lastResponse = root.responses[root.responses.length - 1] - root.handleInput(lastResponse.tags.join(" ") + ` ${parseInt(lastResponse.page) + 1}`); - } + root.handleInput(`${root.commandPrefix}next`); } else { // Create tag list @@ -111,17 +119,23 @@ Item { } } + property real pageKeyScrollAmount: booruResponseListView.height / 2 Keys.onPressed: (event) => { tagInputField.forceActiveFocus() if (event.modifiers === Qt.NoModifier) { if (event.key === Qt.Key_PageUp) { - booruResponseListView.contentY = Math.max(0, booruResponseListView.contentY - booruResponseListView.height / 2) + if (booruResponseListView.atYBeginning) return; + booruResponseListView.contentY = Math.max(0, booruResponseListView.contentY - root.pageKeyScrollAmount) event.accepted = true } else if (event.key === Qt.Key_PageDown) { - booruResponseListView.contentY = Math.min(booruResponseListView.contentHeight - booruResponseListView.height / 2, booruResponseListView.contentY + booruResponseListView.height / 2) + if (booruResponseListView.atYEnd) return; + booruResponseListView.contentY = Math.min(booruResponseListView.contentHeight, booruResponseListView.contentY + root.pageKeyScrollAmount) event.accepted = true } } + if ((event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier) && event.key === Qt.Key_O) { + Booru.clearResponses() + } } @@ -158,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 @@ -176,6 +193,14 @@ Item { downloadPath: root.downloadPath nsfwPath: root.nsfwPath } + + onDragEnded: { // Pull to load more + const gap = booruResponseListView.verticalOvershoot + if (gap > root.pullLoadingGap) { + root.pullLoading = true + root.handleInput(`${root.commandPrefix}next`) + } + } } PagePlaceholder { @@ -185,6 +210,7 @@ Item { icon: "bookmark_heart" title: Translation.tr("Anime boorus") description: "" + shape: MaterialShape.Shape.Bun } ScrollToBottomButton { @@ -192,42 +218,24 @@ Item { target: booruResponseListView } - Item { // Queries awaiting response + MaterialLoadingIndicator { + id: loadingIndicator z: 4 - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.margins: 10 - implicitHeight: pendingBackground.implicitHeight - opacity: Booru.runningRequests > 0 ? 1 : 0 - visible: opacity > 0 - - Behavior on opacity { - animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) - } - - Rectangle { - id: pendingBackground - color: Appearance.m3colors.m3inverseSurface - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - implicitHeight: pendingText.implicitHeight + 12 * 2 - radius: Appearance.rounding.verysmall - - StyledText { - id: pendingText - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 12 - anchors.rightMargin: 12 - anchors.verticalCenter: parent.verticalCenter - font.pixelSize: Appearance.font.pixelSize.smaller - color: Appearance.m3colors.m3inverseOnSurface - wrapMode: Text.Wrap - text: Translation.tr("%1 queries pending").arg(Booru.runningRequests) + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: 20 + (root.pullLoading ? 0 : Math.max(0, (root.normalizedPullDistance - 0.5) * 50)) + Behavior on bottomMargin { + NumberAnimation { + duration: 200 + easing.type: Easing.BezierSpline + easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial + } } } + loading: root.pullLoading || Booru.runningRequests > 0 + pullProgress: Math.min(1, booruResponseListView.verticalOvershoot / root.pullLoadingGap * booruResponseListView.dragging) + scale: root.pullLoading ? 1 : Math.min(1, root.normalizedPullDistance * 2) } } 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/sidebarLeft/anime/BooruResponse.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml index 9258dbc9d..6b1711a40 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml @@ -100,9 +100,7 @@ Rectangle { id: tagsFlickable visible: root.responseData.tags.length > 0 Layout.alignment: Qt.AlignLeft - Layout.fillWidth: { - return true - } + Layout.fillWidth: true implicitHeight: tagRowLayout.implicitHeight contentWidth: tagRowLayout.implicitWidth diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/notifications/NotificationList.qml b/dots/.config/quickshell/ii/modules/sidebarRight/notifications/NotificationList.qml index a1748a67d..99c1fb9a5 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/notifications/NotificationList.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/notifications/NotificationList.qml @@ -31,37 +31,12 @@ Item { } // Placeholder when list is empty - Item { - anchors.fill: listview - - visible: opacity > 0 - opacity: (Notifications.list.length === 0) ? 1 : 0 - - Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.menuDecel.duration - easing.type: Appearance.animation.menuDecel.type - } - } - - ColumnLayout { - anchors.centerIn: parent - spacing: 5 - - MaterialSymbol { - Layout.alignment: Qt.AlignHCenter - iconSize: 55 - color: Appearance.m3colors.m3outline - text: "notifications_active" - } - StyledText { - Layout.alignment: Qt.AlignHCenter - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.m3colors.m3outline - horizontalAlignment: Text.AlignHCenter - text: Translation.tr("No notifications") - } - } + PagePlaceholder { + shown: Notifications.list.length === 0 + icon: "notifications_active" + description: Translation.tr("Nothing") + shape: MaterialShape.Shape.Ghostish + descriptionHorizontalAlignment: Text.AlignHCenter } ButtonGroup { diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml index a7d743585..b5e49d3b0 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml @@ -29,7 +29,7 @@ AbstractQuickPanel { readonly property real baseCellHeight: 56 // Toggles - readonly property list availableToggleTypes: ["network", "bluetooth", "idleInhibitor", "easyEffects", "nightLight", "darkMode", "cloudflareWarp", "gameMode", "screenSnip", "colorPicker", "onScreenKeyboard", "mic", "audio", "notifications", "powerProfile"] + readonly property list availableToggleTypes: ["network", "bluetooth", "idleInhibitor", "easyEffects", "nightLight", "darkMode", "cloudflareWarp", "gameMode", "screenSnip", "colorPicker", "onScreenKeyboard", "mic", "audio", "notifications", "powerProfile","musicRecognition"] readonly property int columns: Config.options.sidebar.quickToggles.android.columns readonly property list toggles: Config.ready ? Config.options.sidebar.quickToggles.android.toggles : [] readonly property list toggleRows: toggleRowsForList(toggles) diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidMusicRecognition.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidMusicRecognition.qml new file mode 100644 index 000000000..a1b9c6738 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidMusicRecognition.qml @@ -0,0 +1,102 @@ +import qs +import qs.modules.common +import qs.modules.common.widgets +import QtQuick +import Quickshell +import Quickshell.Io +import qs.services + + +AndroidQuickToggleButton { + id: root + + property int timeoutInterval: Config.options.musicRecognition.interval + property int timeoutDuration: Config.options.musicRecognition.timeout + + + property string monitorSource: "monitor" // "monitor" (system sound) , "input" (microphone) + + name: Translation.tr("Identify Music") + statusText: toggled ? Translation.tr("Listening...") : monitorSource === "monitor" ? Translation.tr("System sound") : Translation.tr("Microphone") + toggled: false + buttonIcon: toggled ? "music_cast" : (monitorSource === "monitor" ? "music_note" : "frame_person_mic") + + property var recognizedTrack: ({ title:"", subtitle:"", url:""}) + + function handleRecognition(jsonText) { + try { + var obj = JSON.parse(jsonText) + root.recognizedTrack = { + title: obj.track.title, + subtitle: obj.track.subtitle, + url: obj.track.url + } + musicReconizedProc.running = true + } catch(e) { + Quickshell.execDetached(["notify-send", Translation.tr("Couldn't recognize music"), Translation.tr("Perhaps what you're listening to is too niche"), "-a", "Shell"]) + } finally { + root.toggled = false + } + } + + + StyledToolTip { + text: Translation.tr("Recognize music | Right-click to toggle source") + } + + onClicked: { + root.toggled = !root.toggled + recognizeMusicProc.running = root.toggled + musicReconizedProc.running = false + } + + altAction: () => { + if (root.monitorSource === "monitor"){ + root.monitorSource = "input" + return + }else { + root.monitorSource = "monitor" + } + + } + + Process { + id: recognizeMusicProc + running: false + command: [`${Directories.scriptPath}/musicRecognition/recognize-music.sh`, "-i", root.timeoutInterval, "-t", root.timeoutDuration, "-s", root.monitorSource] + stdout: StdioCollector { + onStreamFinished: { + handleRecognition(this.text) + } + } + onExited: (exitCode, exitStatus) => { + if (exitCode === 1) { + Quickshell.execDetached(["notify-send", Translation.tr("Couldn't recognize music"), Translation.tr("Make sure you have songrec installed"), "-a", "Shell"]) + root.toggled = false + } + } + } + + Process { + id: musicReconizedProc + running: false + command: [ + "notify-send", + Translation.tr("Music Recognized"), + root.recognizedTrack.title + " - " + root.recognizedTrack.subtitle, + "-A", "Shazam", + "-A", "YouTube", + "-a", "Shell" + ] + stdout: StdioCollector { + onStreamFinished: { + if (this.text === "") return + if (this.text == 0){ + Qt.openUrlExternally(root.recognizedTrack.url); + } else { + Qt.openUrlExternally("https://www.youtube.com/results?search_query=" + root.recognizedTrack.title + " - " + root.recognizedTrack.subtitle); + } + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidToggleDelegateChooser.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidToggleDelegateChooser.qml index a8f79c843..aa5356e2e 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidToggleDelegateChooser.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidToggleDelegateChooser.qml @@ -232,4 +232,17 @@ DelegateChooser { cellSize: modelData.size } } + DelegateChoice { roleValue: "musicRecognition"; AndroidMusicRecognition { + required property int index + required property var modelData + buttonIndex: root.startingIndex + index + buttonData: modelData + editMode: root.editMode + expandedSize: modelData.size > 1 + baseCellWidth: root.baseCellWidth + baseCellHeight: root.baseCellHeight + cellSpacing: root.spacing + cellSize: modelData.size + } } + } 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/scripts/musicRecognition/recognize-music.sh b/dots/.config/quickshell/ii/scripts/musicRecognition/recognize-music.sh new file mode 100755 index 000000000..09b0361af --- /dev/null +++ b/dots/.config/quickshell/ii/scripts/musicRecognition/recognize-music.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +INTERVAL=2 +TOTAL_DURATION=30 +MIN_VALID_RESULT_LENGTH=300 +SOURCE_TYPE="monitor" # monitor | input +TMP_PATH="/tmp/quickshell/media/songrec" +TMP_RAW="$TMP_PATH/recording.raw" +TMP_MP3="$TMP_PATH/recording.mp3" + +while getopts "i:t:s:" opt; do + case $opt in + i) INTERVAL=$OPTARG ;; + t) TOTAL_DURATION=$OPTARG ;; + s) SOURCE_TYPE=$OPTARG ;; + *) exit 1 ;; + esac +done +if [ "$SOURCE_TYPE" = "monitor" ]; then + MONITOR_SOURCE=$(pactl list short sources 2>/dev/null | grep -m1 monitor | awk '{print $2}' || true) +elif [ "$SOURCE_TYPE" = "input" ]; then + MONITOR_SOURCE=$(pactl info | grep "Default Source:" | awk '{print $3}' || true) +else + echo "Invalid source type" + exit 1 +fi + +if [ -z "$MONITOR_SOURCE" ] || ! command -v songrec >/dev/null 2>&1; then + exit 1 +fi + +cleanup() { + rm -f "$TMP_RAW" "$TMP_MP3" + pkill -P $$ parec >/dev/null 2>&1 || true +} +trap cleanup EXIT + +mkdir -p "$TMP_PATH" +parec --device="$MONITOR_SOURCE" --format=s16le --rate=44100 --channels=2 > "$TMP_RAW" & +START_TIME=$(date +%s) + +while true; do + sleep "$INTERVAL" + CURRENT_TIME=$(date +%s) + ELAPSED=$((CURRENT_TIME - START_TIME)) + + if (( ELAPSED >= TOTAL_DURATION )); then + exit 0 + fi + + ffmpeg -f s16le -ar 44100 -ac 2 -i "$TMP_RAW" -acodec libmp3lame -y -hide_banner -loglevel error "$TMP_MP3" 2>/dev/null + RESULT=$(songrec audio-file-to-recognized-song "$TMP_MP3" 2>/dev/null || true) + + if echo "$RESULT" | grep -q '"matches": \[' && [ ${#RESULT} -gt $MIN_VALID_RESULT_LENGTH ]; then + echo "$RESULT" + exit 0 + fi +done diff --git a/dots/.config/quickshell/ii/scripts/videos/record.sh b/dots/.config/quickshell/ii/scripts/videos/record.sh new file mode 100755 index 000000000..794bcf1ba --- /dev/null +++ b/dots/.config/quickshell/ii/scripts/videos/record.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +getdate() { + date '+%Y-%m-%d_%H.%M.%S' +} +getaudiooutput() { + pactl list sources | grep 'Name' | grep 'monitor' | cut -d ' ' -f2 +} +getactivemonitor() { + hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name' +} + +xdgvideo="$(xdg-user-dir VIDEOS)" +if [[ $xdgvideo = "$HOME" ]]; then + unset xdgvideo +fi +mkdir -p "${xdgvideo:-$HOME/Videos}" +cd "${xdgvideo:-$HOME/Videos}" || exit + +# parse --region without modifying $@ so other flags like --fullscreen still work +ARGS=("$@") +MANUAL_REGION="" +SOUND_FLAG=0 +FULLSCREEN_FLAG=0 +for ((i=0;i<${#ARGS[@]};i++)); do + if [[ "${ARGS[i]}" == "--region" ]]; then + if (( i+1 < ${#ARGS[@]} )); then + MANUAL_REGION="${ARGS[i+1]}" + else + notify-send "Recording cancelled" "No region specified for --region" -a 'Recorder' & disown + exit 1 + fi + elif [[ "${ARGS[i]}" == "--sound" ]]; then + SOUND_FLAG=1 + elif [[ "${ARGS[i]}" == "--fullscreen" ]]; then + FULLSCREEN_FLAG=1 + fi +done + +if pgrep wf-recorder > /dev/null; then + notify-send "Recording Stopped" "Stopped" -a 'Recorder' & + pkill wf-recorder & +else + if [[ $FULLSCREEN_FLAG -eq 1 ]]; then + notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown + if [[ $SOUND_FLAG -eq 1 ]]; then + wf-recorder -o "$(getactivemonitor)" --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --audio="$(getaudiooutput)" + else + wf-recorder -o "$(getactivemonitor)" --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t + fi + else + # If a manual region was provided via --region, use it; otherwise run slurp as before. + if [[ -n "$MANUAL_REGION" ]]; then + region="$MANUAL_REGION" + else + if ! region="$(slurp 2>&1)"; then + notify-send "Recording cancelled" "Selection was cancelled" -a 'Recorder' & disown + exit 1 + fi + fi + + notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown + if [[ $SOUND_FLAG -eq 1 ]]; then + wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" --audio="$(getaudiooutput)" + else + wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" + fi + fi +fi 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..9bb096208 100644 --- a/dots/.config/quickshell/ii/services/Audio.qml +++ b/dots/.config/quickshell/ii/services/Audio.qml @@ -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/dots/.config/quickshell/ii/services/Brightness.qml b/dots/.config/quickshell/ii/services/Brightness.qml index e2d164814..5232f76f9 100644 --- a/dots/.config/quickshell/ii/services/Brightness.qml +++ b/dots/.config/quickshell/ii/services/Brightness.qml @@ -16,8 +16,6 @@ import QtQuick */ Singleton { id: root - property real minimumBrightnessAllowed: 0.00001 // Setting to 0 would kind of turn off the screen. We don't want that. - signal brightnessChanged() property var ddcMonitors: [] @@ -137,14 +135,14 @@ Singleton { } function syncBrightness() { - const brightnessValue = Math.max(monitor.multipliedBrightness, root.minimumBrightnessAllowed) - const rounded = Math.round(brightnessValue * monitor.rawMaxBrightness); - setProc.command = isDdc ? ["ddcutil", "-b", busNum, "setvcp", "10", rounded] : ["brightnessctl", "--class", "backlight", "s", rounded, "--quiet"]; + const brightnessValue = Math.max(monitor.multipliedBrightness, 0) + const rawValueRounded = Math.max(Math.floor(brightnessValue * monitor.rawMaxBrightness), 1); + setProc.command = isDdc ? ["ddcutil", "-b", busNum, "setvcp", "10", rawValueRounded] : ["brightnessctl", "--class", "backlight", "s", rawValueRounded, "--quiet"]; setProc.startDetached(); } function setBrightness(value: real): void { - value = Math.max(root.minimumBrightnessAllowed, Math.min(1, value)); + value = Math.max(0, Math.min(1, value)); monitor.brightness = value; } diff --git a/dots/.config/quickshell/ii/settings.qml b/dots/.config/quickshell/ii/settings.qml index 6ab96c937..5d07c0502 100644 --- a/dots/.config/quickshell/ii/settings.qml +++ b/dots/.config/quickshell/ii/settings.qml @@ -10,6 +10,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Window +import Quickshell import qs.services import qs.modules.common import qs.modules.common.widgets @@ -169,15 +170,29 @@ ApplicationWindow { FloatingActionButton { id: fab - iconText: "edit" - buttonText: Translation.tr("Config file") + property bool justCopied: false + iconText: justCopied ? "check" : "edit" + buttonText: justCopied ? Translation.tr("Path copied") : Translation.tr("Config file") expanded: navRail.expanded downAction: () => { Qt.openUrlExternally(`${Directories.config}/illogical-impulse/config.json`); } + altAction: () => { + Quickshell.clipboardText = CF.FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse/config.json`); + fab.justCopied = true; + revertTextTimer.restart() + } + + Timer { + id: revertTextTimer + interval: 1500 + onTriggered: { + fab.justCopied = false; + } + } StyledToolTip { - text: Translation.tr("Open the shell config file.\nIf the button doesn't work or doesn't open in your favorite editor,\nyou can manually open ~/.config/illogical-impulse/config.json") + text: Translation.tr("Open the shell config file\nAlternatively right-click to copy path") } } 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-gentoo/install-deps.sh b/sdata/dist-gentoo/install-deps.sh index 911512ddf..7c7dcd58d 100644 --- a/sdata/dist-gentoo/install-deps.sh +++ b/sdata/dist-gentoo/install-deps.sh @@ -24,6 +24,7 @@ printf "${STY_RST}" pause x sudo emerge --noreplace --quiet app-eselect/eselect-repository +x sudo emerge --noreplace --quiet app-portage/smart-live-rebuild if [[ -z $(eselect repository list | grep localrepo) ]]; then v sudo eselect repository create localrepo @@ -53,6 +54,7 @@ v sudo sh -c 'cat ./sdata/dist-gentoo/additional-useflags >> /etc/portage/packag # Update system v sudo emerge --sync v sudo emerge --quiet --newuse --update --deep @world +v sudo emerge --quiet @smart-live-rebuild v sudo emerge --depclean # Remove old ebuilds (if this isn't done the wildcard will fuck upon a version change) @@ -80,7 +82,6 @@ v sudo ebuild ${ebuild_dir}/dev-libs/hyprlang/hyprlang*9999.ebuild digest v sudo ebuild ${ebuild_dir}/dev-util/hyprwayland-scanner/hyprwayland-scanner*9999.ebuild digest ###### LIVE EBUILDS END - # Install dependencies for i in "${metapkgs[@]}"; do x sudo mkdir -p ${ebuild_dir}/app-misc/${i} diff --git a/sdata/dist-nix/install-deps.sh b/sdata/dist-nix/install-deps.sh index 77697e501..59ffec887 100644 --- a/sdata/dist-nix/install-deps.sh +++ b/sdata/dist-nix/install-deps.sh @@ -2,3 +2,130 @@ # 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 reset "${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 \ No newline at end of file diff --git a/sdata/lib/functions.sh b/sdata/lib/functions.sh index 496397f83..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 @@ -287,3 +291,71 @@ function check_disk_space() { return 0 } + +function auto_get_git_submodule(){ + local git_submodules_list=() + + while IFS= read -r path; do + [ -z "$path" ] && continue + git_submodules_list+=("$path") + done < <(git submodule status --recursive 2>/dev/null | awk '{print $2}') + + local missing=0 + + for p in "${git_submodules_list[@]}"; do + if [ ! -d "$p" ] || [ -z "$(ls -A "$p" 2>/dev/null)" ]; then + missing=1 + break + fi + done + + if [ "$missing" -eq 1 ]; then + 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/step/exp-uninstall.sh b/sdata/subcmd-exp-uninstall/0.run.sh similarity index 100% rename from sdata/step/exp-uninstall.sh rename to sdata/subcmd-exp-uninstall/0.run.sh diff --git a/sdata/options/exp-uninstall.sh b/sdata/subcmd-exp-uninstall/options.sh similarity index 100% rename from sdata/options/exp-uninstall.sh rename to sdata/subcmd-exp-uninstall/options.sh diff --git a/sdata/step/exp-update-old.sh b/sdata/subcmd-exp-update-old/0.run.sh similarity index 99% rename from sdata/step/exp-update-old.sh rename to sdata/subcmd-exp-update-old/0.run.sh index 76ddd9ed9..dc4b97075 100644 --- a/sdata/step/exp-update-old.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/options/exp-update-old.sh b/sdata/subcmd-exp-update-old/options.sh similarity index 100% rename from sdata/options/exp-update-old.sh rename to sdata/subcmd-exp-update-old/options.sh diff --git a/sdata/step/exp-update.sh b/sdata/subcmd-exp-update/0.run.sh similarity index 97% rename from sdata/step/exp-update.sh rename to sdata/subcmd-exp-update/0.run.sh index 32d90ebec..2ab21379c 100644 --- a/sdata/step/exp-update.sh +++ b/sdata/subcmd-exp-update/0.run.sh @@ -3,6 +3,16 @@ # shellcheck shell=bash +##################################################################################### +# Notes by @clsty: +# +# I'm not the one who developed this script (see issue#2284 which discussed about the history). +# However it contains many unnecessary logics. This is typically what AI will do. +# I don't really care if it's AI-generated or not, it's just an extra option in addition to ./setup install, so as long as the users say it works, it should be fine. +# However, it's not easy to maintain something like this. +# The redundant logic should be cleaned up someday. +# +# This also applies for exp-update.tester.sh, TBH I don't think that file is really needed, and it also looks like AI-generated. Just guessing though. ##################################################################################### # # exp-update.sh - Enhanced dotfiles update script @@ -834,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/step/exp-update-tester.sh b/sdata/subcmd-exp-update/exp-update-tester.sh similarity index 96% rename from sdata/step/exp-update-tester.sh rename to sdata/subcmd-exp-update/exp-update-tester.sh index 9b467d7a1..15e93f150 100755 --- a/sdata/step/exp-update-tester.sh +++ b/sdata/subcmd-exp-update/exp-update-tester.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# exp-update-tester.sh - Test suite for exp-update.sh +# exp-update-tester.sh - Test suite for exp-update # set -euo pipefail @@ -129,7 +129,7 @@ log_header() { :; } log_die() { echo "ERROR: \$1"; exit 1; } STY_CYAN="" STY_RST="" STY_YELLOW="" -# Set required environment variables for exp-update.sh +# Set required environment variables for exp-update/0.run.sh SKIP_NOTICE=true REPO_ROOT="\$1" CHECK_PACKAGES=false @@ -139,7 +139,7 @@ VERBOSE=false NON_INTERACTIVE=true SOURCE_ONLY=true -source "$ORIGINAL_DIR/sdata/step/exp-update.sh" +source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh" detected_dirs=\$(detect_repo_structure) if [[ -n "\$detected_dirs" ]]; then read -ra MONITOR_DIRS <<<"\$detected_dirs" @@ -190,7 +190,7 @@ log_success() { :; } log_header() { :; } log_die() { echo "ERROR: \$1"; exit 1; } -# Set required environment variables for exp-update.sh +# Set required environment variables for exp-update SKIP_NOTICE=true REPO_ROOT="\$1" CHECK_PACKAGES=false @@ -200,7 +200,7 @@ VERBOSE=false NON_INTERACTIVE=true SOURCE_ONLY=true -source "$ORIGINAL_DIR/sdata/step/exp-update.sh" +source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh" detected_dirs=\$(detect_repo_structure) if [[ -n "\$detected_dirs" ]]; then read -ra MONITOR_DIRS <<<"\$detected_dirs" @@ -276,7 +276,7 @@ log_success() { :; } log_header() { :; } log_die() { echo "ERROR: \$1" >&2; exit 1; } -# FIXED: Set REPO_ROOT before sourcing exp-update.sh +# FIXED: Set REPO_ROOT before sourcing exp-update REPO_ROOT="\$1" export REPO_ROOT @@ -293,7 +293,7 @@ HOME_UPDATE_IGNORE_FILE="/dev/null" # Source the production script to use the real should_ignore function # Redirect all unwanted output to stderr, then to /dev/null -source "$ORIGINAL_DIR/sdata/step/exp-update.sh" 2>/dev/null +source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh" 2>/dev/null test_cases=( "\$REPO_ROOT/app.log:0" @@ -348,7 +348,7 @@ test_safe_read_security() { log_test "Testing safe_read uses secure assignment (printf -v)" local safe_read_function - safe_read_function=$(awk '/^safe_read\(\) \{/,/^\}/' "$ORIGINAL_DIR/sdata/step/exp-update.sh") + safe_read_function=$(awk '/^safe_read\(\) \{/,/^\}/' "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh") if [[ -z "$safe_read_function" ]]; then log_fail "Could not find safe_read function" @@ -547,7 +547,7 @@ log_success() { :; } log_header() { :; } log_die() { echo "ERROR: \$1" >&2; exit 1; } -# FIXED: Set REPO_ROOT before sourcing exp-update.sh +# FIXED: Set REPO_ROOT before sourcing exp-update REPO_ROOT="\$1" export REPO_ROOT @@ -563,7 +563,7 @@ UPDATE_IGNORE_FILE="\${REPO_ROOT}/.updateignore" HOME_UPDATE_IGNORE_FILE="/dev/null" # Source the production script to use the real should_ignore function -source "$ORIGINAL_DIR/sdata/step/exp-update.sh" 2>/dev/null +source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh" 2>/dev/null # Load patterns into cache load_ignore_patterns @@ -649,7 +649,7 @@ VERBOSE=false NON_INTERACTIVE=true SOURCE_ONLY=true -source "$ORIGINAL_DIR/sdata/step/exp-update.sh" 2>/dev/null +source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh" 2>/dev/null test_dir="/tmp/test-ensure-dir-\$\$" diff --git a/sdata/options/exp-update.sh b/sdata/subcmd-exp-update/options.sh similarity index 100% rename from sdata/options/exp-update.sh rename to sdata/subcmd-exp-update/options.sh diff --git a/sdata/step/0.install-greeting.sh b/sdata/subcmd-install/0.greeting.sh similarity index 83% rename from sdata/step/0.install-greeting.sh rename to sdata/subcmd-install/0.greeting.sh index 413aacf64..a948e5f3c 100644 --- a/sdata/step/0.install-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/step/1.install-deps-selector.sh b/sdata/subcmd-install/1.deps-selector.sh similarity index 98% rename from sdata/step/1.install-deps-selector.sh rename to sdata/subcmd-install/1.deps-selector.sh index c1f4bea0c..71f8cf066 100644 --- a/sdata/step/1.install-deps-selector.sh +++ b/sdata/subcmd-install/1.deps-selector.sh @@ -1,9 +1,10 @@ # This script is meant to be sourced. # It's not for directly running. +printf "${STY_CYAN}[$0]: 1. Install dependencies\n${STY_RST}" function outdate_detect(){ # Shallow clone prevent latest_commit_timestamp() from working. - v git_auto_unshallow + x git_auto_unshallow local source_path="$1" local target_path="$2" diff --git a/sdata/step/2.install-setups-selector.sh b/sdata/subcmd-install/2.setups-selector.sh similarity index 97% rename from sdata/step/2.install-setups-selector.sh rename to sdata/subcmd-install/2.setups-selector.sh index c279d7085..e4c8e612d 100644 --- a/sdata/step/2.install-setups-selector.sh +++ b/sdata/subcmd-install/2.setups-selector.sh @@ -1,5 +1,6 @@ # This script is meant to be sourced. # It's not for directly running. +printf "${STY_CYAN}[$0]: 2. Setup for permissions/services etc\n${STY_RST}" # shellcheck shell=bash diff --git a/sdata/subcmd-install/3.files-exp.sh b/sdata/subcmd-install/3.files-exp.sh new file mode 100644 index 000000000..f35229535 --- /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 II_FONTSET_NAME is set and this is the fontconfig pattern, use the fontset instead + if [[ "$from" == "dots/.config/fontconfig" ]] && [[ -n "${II_FONTSET_NAME:-}" ]]; then + from="dots-extra/fontsets/${II_FONTSET_NAME}" + echo "Using fontset \"${II_FONTSET_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/step/3.install-files.sh b/sdata/subcmd-install/3.files.sh similarity index 70% rename from sdata/step/3.install-files.sh rename to sdata/subcmd-install/3.files.sh index eaea5017a..ead48593f 100644 --- a/sdata/step/3.install-files.sh +++ b/sdata/subcmd-install/3.files.sh @@ -1,84 +1,64 @@ # This script is meant to be sourced. # It's not for directly running. +printf "${STY_CYAN}[$0]: 3. Copying config files\n${STY_RST}" # shellcheck shell=bash # TODO: https://github.com/end-4/dots-hyprland/issues/2137 -function warning_rsync(){ +function warning_rsync_delete(){ printf "${STY_YELLOW}" - printf "The commands using rsync will overwrite the destination when it exists already.\n" + printf "The command below uses --delete for rsync which overwrites the destination folder.\n" printf "${STY_RST}" } -function backup_clashing_targets(){ - # For dirs/files under target_dir, only backup those which clashes with the ones under source_dir +function warning_rsync_normal(){ + printf "${STY_YELLOW}" + printf "The command below uses rsync which overwrites the destination.\n" + printf "${STY_RST}" +} - # 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" - fi + 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/quickshell $XDG_DATA_HOME +v mkdir -p $XDG_BIN_HOME $XDG_CACHE_HOME $XDG_CONFIG_HOME $XDG_DATA_HOME/icons -case $ask in - false) sleep 0 ;; - *) ask_backup_configs ;; -esac +if [[ ! "${SKIP_BACKUP}" == true ]]; then + case $ask in + false) auto_backup_configs ;; + *) ask_backup_configs ;; + esac +fi # TODO: A better method for users to choose their customization, # for example some users may prefer ZSH over FISH, and foot over kitty. @@ -90,34 +70,45 @@ esac # original dotfiles and new ones in the SAME DIRECTORY # (eg. in ~/.config/hypr) won't be mixed together -# MISC (For dots/.config/* but not quickshell, not fish, not Hyprland) +# MISC (For dots/.config/* but not quickshell, not fish, not Hyprland, not fontconfig) case $SKIP_MISCCONF in true) sleep 0;; *) - for i in $(find dots/.config/ -mindepth 1 -maxdepth 1 ! -name 'quickshell' ! -name 'fish' ! -name 'hypr' -exec basename {} \;); do + for i in $(find dots/.config/ -mindepth 1 -maxdepth 1 ! -name 'quickshell' ! -name 'fish' ! -name 'hypr' ! -name 'fontconfig' -exec basename {} \;); do # i="dots/.config/$i" echo "[$0]: Found target: dots/.config/$i" - if [ -d "dots/.config/$i" ];then warning_rsync; v rsync -av --delete "dots/.config/$i/" "$XDG_CONFIG_HOME/$i/" - elif [ -f "dots/.config/$i" ];then warning_rsync; v rsync -av "dots/.config/$i" "$XDG_CONFIG_HOME/$i" + if [ -d "dots/.config/$i" ];then warning_rsync_delete; v rsync -av --delete "dots/.config/$i/" "$XDG_CONFIG_HOME/$i/" + elif [ -f "dots/.config/$i" ];then warning_rsync_normal; v rsync -av "dots/.config/$i" "$XDG_CONFIG_HOME/$i" fi done + warning_rsync_delete; v rsync -av "dots/.local/share/konsole/" "${XDG_DATA_HOME:-$HOME/.local/share}"/konsole/ ;; esac case $SKIP_QUICKSHELL in true) sleep 0;; *) - warning_rsync; v rsync -av --delete dots/.config/quickshell/ii/ "$XDG_CONFIG_HOME"/quickshell/ii/ + # Should overwriting the whole directory not only ~/.config/quickshell/ii/ cuz https://github.com/end-4/dots-hyprland/issues/2294#issuecomment-3448671064 + warning_rsync_delete; v rsync -av --delete dots/.config/quickshell/ "$XDG_CONFIG_HOME"/quickshell/ ;; esac case $SKIP_FISH in true) sleep 0;; *) - warning_rsync; v rsync -av --delete dots/.config/fish/ "$XDG_CONFIG_HOME"/fish/ + warning_rsync_delete; v rsync -av --delete dots/.config/fish/ "$XDG_CONFIG_HOME"/fish/ ;; esac +case $SKIP_FONTCONFIG in + true) sleep 0;; + *) + case "$II_FONTSET_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/ ;; + esac;; +esac + # For Hyprland declare -a arg_excludes=() arg_excludes+=(--exclude '/custom') @@ -127,17 +118,25 @@ arg_excludes+=(--exclude '/hyprland.conf') case $SKIP_HYPRLAND in true) sleep 0;; *) - warning_rsync; v rsync -av --delete "${arg_excludes[@]}" dots/.config/hypr/ "$XDG_CONFIG_HOME"/hypr/ + warning_rsync_delete; v rsync -av --delete "${arg_excludes[@]}" dots/.config/hypr/ "$XDG_CONFIG_HOME"/hypr/ + # When hypr/custom does not exist, we assume that it's the firstrun. + if [ -d "$XDG_CONFIG_HOME/hypr/custom" ];then ii_firstrun=false;else ii_firstrun=true;fi t="$XDG_CONFIG_HOME/hypr/hyprland.conf" 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 @@ -164,7 +163,7 @@ case $SKIP_HYPRLAND in echo -e "${STY_BLUE}[$0]: \"$t\" already exists, will not do anything.${STY_RST}" else echo -e "${STY_YELLOW}[$0]: \"$t\" does not exist yet.${STY_RST}" - warning_rsync; v rsync -av --delete dots/.config/hypr/custom/ $t/ + v rsync -av --delete dots/.config/hypr/custom/ $t/ fi ;; esac @@ -174,8 +173,7 @@ declare -a arg_excludes=() # some foldes (eg. .local/bin) should be processed separately to avoid `--delete' for rsync, # since the files here come from different places, not only about one program. # v rsync -av "dots/.local/bin/" "$XDG_BIN_HOME" # No longer needed since scripts are no longer in ~/.local/bin -warning_rsync; v rsync -av "dots/.local/share/icons/" "${XDG_DATA_HOME:-$HOME/.local/share}"/icons/ -warning_rsync; v rsync -av "dots/.local/share/konsole/" "${XDG_DATA_HOME:-$HOME/.local/share}"/konsole/ +v cp -f "dots/.local/share/icons/illogical-impulse.svg" "${XDG_DATA_HOME}"/icons/illogical-impulse.svg # Prevent hyprland from not fully loaded sleep 1 diff --git a/sdata/subcmd-install/3.files.yaml b/sdata/subcmd-install/3.files.yaml new file mode 100644 index 000000000..a9f2985f2 --- /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 II_FONTSET_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/options/install.sh b/sdata/subcmd-install/options.sh similarity index 75% rename from sdata/options/install.sh rename to sdata/subcmd-install/options.sh index fa3f2b4e3..dba8a4430 100644 --- a/sdata/options/install.sh +++ b/sdata/subcmd-install/options.sh @@ -14,14 +14,18 @@ Options for install: --skip-allsetups Skip the whole process setting up permissions/services etc --skip-allfiles Skip the whole process copying configuration files -s, --skip-sysupdate Skip system package upgrade e.g. \"sudo pacman -Syu\" + --skip-plasmaintg Skip installing plasma-browser-integration + --skip-backup Skip backup conflicting files --skip-quickshell Skip installing the config for Quickshell --skip-hyprland Skip installing the config for Hyprland --skip-fish Skip installing the config for Fish - --skip-plasmaintg Skip installing plasma-browser-integration + --skip-fontconfig Skip installing the config for fontconfig --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 (Unavailable yet) Use a set of pre-defined font and config + --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 " } @@ -33,7 +37,7 @@ cleancache(){ # `man getopt` to see more para=$(getopt \ -o hfk:cs \ - -l help,force,fontset:,clean,skip-allgreeting,skip-alldeps,skip-allsetups,skip-allfiles,skip-sysupdate,skip-quickshell,skip-fish,skip-hyprland,skip-plasmaintg,skip-miscconf,exp-files,via-nix \ + -l help,force,fontset:,clean,skip-allgreeting,skip-alldeps,skip-allsetups,skip-allfiles,skip-sysupdate,skip-plasmaintg,skip-backup,skip-quickshell,skip-fish,skip-hyprland,skip-fontconfig,skip-miscconf,core,exp-files,via-nix \ -n "$0" -- "$@") [ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1 ##################################################################################### @@ -63,20 +67,23 @@ while true ; do --skip-allsetups) SKIP_ALLSETUPS=true;shift;; --skip-allfiles) SKIP_ALLFILES=true;shift;; -s|--skip-sysupdate) SKIP_SYSUPDATE=true;shift;; + --skip-plasmaintg) SKIP_PLASMAINTG=true;shift;; + --skip-backup) SKIP_BACKUP=true;shift;; --skip-hyprland) SKIP_HYPRLAND=true;shift;; --skip-fish) SKIP_FISH=true;shift;; --skip-quickshell) SKIP_QUICKSHELL=true;shift;; + --skip-fontconfig) SKIP_FONTCONFIG=true;shift;; --skip-miscconf) SKIP_MISCCONF=true;shift;; - --skip-plasmaintg) SKIP_PLASMAINTG=true;shift;; + --core) SKIP_PLASMAINTG=true;SKIP_FISH=true;SKIP_FONTCONFIG=true;SKIP_MISCCONF=true;shift;; --exp-files) EXPERIMENTAL_FILES_SCRIPT=true;shift;; --via-nix) INSTALL_VIA_NIX=true;shift;; ## Ones with parameter --fontset) - case $2 in - "default"|"zh-CN"|"vi") fontset="$2";; - *) echo -e "Wrong argument for $1.";exit 1;; - esac;echo "The fontset is ${fontset}.";shift 2;; + if [[ -d "${REPO_ROOT}/dots-extra/fontsets/$2" ]]; + then echo "Using fontset \"$2\".";II_FONTSET_NAME="$2";shift 2 + else echo "Wrong argument for $1.";exit 1 + fi;; ## Ending --) break ;; diff --git a/setup b/setup index 8fe456ff9..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,75 +35,67 @@ case $1 in # Global help help|--help|-h)showhelp_global;exit;; # Correct subcommand - install|install-deps|install-setups|install-files|exp-uninstall|exp-update|exp-update-old) - SCRIPT_SUBCOMMAND=$1;shift;; - # No subcommand - -*|"")SCRIPT_SUBCOMMAND=install;; + install|exp-uninstall|exp-update|exp-update-old|checkdeps) + SUBCMD_NAME=$1 + SUBCMD_DIR=./sdata/subcmd-$1 + shift;; + # Correct subcommand but not using ./sdata/subcmd-$1 + install-deps|install-setups|install-files) + SUBCMD_NAME=$1 + SUBCMD_DIR=./sdata/subcmd-install + shift;; + # No subcommand, default to install + -*|"") + SUBCMD_NAME=install + SUBCMD_DIR=./sdata/subcmd-install + ;; # Wrong subcommand *)printf "${STY_RED}Unknown subcommand \"$1\".${STY_RST}\n";showhelp_global;exit 1;; esac ##################################################################################### -case ${SCRIPT_SUBCOMMAND} in +if [[ -f "${SUBCMD_DIR}/options.sh" ]]; + then source "${SUBCMD_DIR}/options.sh" +fi +case ${SUBCMD_NAME} in install) - source ./sdata/options/install.sh if [[ "${SKIP_ALLGREETING}" != true ]]; then - source ./sdata/step/0.install-greeting.sh + source ${SUBCMD_DIR}/0.greeting.sh fi if [[ "${SKIP_ALLDEPS}" != true ]]; then - printf "${STY_CYAN}[$0]: 1. Install dependencies\n${STY_RST}" - source ./sdata/step/1.install-deps-selector.sh + source ${SUBCMD_DIR}/1.deps-selector.sh fi if [[ "${SKIP_ALLSETUPS}" != true ]]; then - printf "${STY_CYAN}[$0]: 2. Setup for permissions/services etc\n${STY_RST}" - source ./sdata/step/2.install-setups-selector.sh + source ${SUBCMD_DIR}/2.setups-selector.sh fi if [[ "${SKIP_ALLFILES}" != true ]]; then - printf "${STY_CYAN}[$0]: 3. Copying config files\n${STY_RST}" if [[ "${EXPERIMENTAL_FILES_SCRIPT}" == true ]]; then - source ./sdata/step/3.install-files.experimental.sh + source ${SUBCMD_DIR}/3.files-exp.sh else - source ./sdata/step/3.install-files.sh + source ${SUBCMD_DIR}/3.files.sh fi fi ;; install-deps) - source ./sdata/options/install.sh if [[ "${SKIP_ALLDEPS}" != true ]]; then - printf "${STY_CYAN}[$0]: 1. Install dependencies\n${STY_RST}" - source ./sdata/step/1.install-deps-selector.sh + source ${SUBCMD_DIR}/1.deps-selector.sh fi ;; install-setups) - source ./sdata/options/install.sh if [[ "${SKIP_ALLSETUPS}" != true ]]; then - printf "${STY_CYAN}[$0]: 2. Setup for permissions/services etc\n${STY_RST}" - source ./sdata/step/2.install-setups-selector.sh + source ${SUBCMD_DIR}/2.setups-selector.sh fi ;; install-files) - source ./sdata/options/install.sh if [[ "${SKIP_ALLFILES}" != true ]]; then - printf "${STY_CYAN}[$0]: 3. Copying config files\n${STY_RST}" if [[ "${EXPERIMENTAL_FILES_SCRIPT}" == true ]]; then - source ./sdata/step/3.install-files.experimental.sh + source ${SUBCMD_DIR}/3.files-exp.sh else - source ./sdata/step/3.install-files.sh + source ${SUBCMD_DIR}/3.files.sh fi fi ;; - exp-uninstall) - source ./sdata/options/exp-uninstall.sh - source ./sdata/step/exp-uninstall.sh - exit - ;; - exp-update) - source ./sdata/options/exp-update.sh - source ./sdata/step/exp-update.sh - exit - ;; - exp-update-old) - source ./sdata/options/exp-update-old.sh - source ./sdata/step/exp-update-old.sh + *) + source ${SUBCMD_DIR}/0.run.sh exit ;; esac