Merge branch 'main' into main

This commit is contained in:
end-4
2025-10-30 10:00:39 +01:00
committed by GitHub
81 changed files with 1909 additions and 420 deletions
+3
View File
@@ -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
+1 -1
View File
@@ -28,7 +28,7 @@
<details> <details>
<summary>Installation (illogical-impulse Quickshell)</summary> <summary>Installation (illogical-impulse Quickshell)</summary>
- 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` - Or, clone this repo and run `./setup install`
- See [document](https://ii.clsty.link/en/ii-qs/01setup/) for details. - 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: - **Default keybinds**: Should be somewhat familiar to Windows or GNOME users. Important ones:
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
<fontconfig>
<match target="font">
<edit name="rgba" mode="assign">
<const>none</const>
</edit>
</match>
<!-- Fix for: arabic fonts rendering in Noto Nastaliq Urdu | affects Chromium, Discord (maybe all chromium based apps, but not Spotify somehow) -->
<match target="pattern">
<test compare="eq" name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="prepend" binding="strong">
<string>Noto Sans Arabic</string>
</edit>
</match>
</fontconfig>
+10 -6
View File
@@ -70,14 +70,18 @@ bind = Super+Shift, T,exec, qs -c $qsConfig ipc call TEST_ALIVE || pidof slurp |
# Color picker # Color picker
bindd = Super+Shift, C, Color picker, exec, hyprpicker -a # Pick color (Hex) >> clipboard bindd = Super+Shift, C, Color picker, exec, hyprpicker -a # Pick color (Hex) >> clipboard
# Fullscreen screenshot # Fullscreen screenshot
bindld = ,Print, Screenshot >> clipboard ,exec,grim - | wl-copy # Screenshot >> clipboard bindl = ,Print,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 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 # Recording stuff
bindl = Super+Alt, R, exec, ~/.config/hypr/hyprland/scripts/record.sh # Record region (no sound) bindl = Super+Shift, R, global, quickshell:regionRecord # Record region (no sound)
bindl = Ctrl+Alt, R, exec, ~/.config/hypr/hyprland/scripts/record.sh --fullscreen # [hidden] Record screen (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+Shift+Alt, R, exec, ~/.config/hypr/hyprland/scripts/record.sh --fullscreen-sound # Record screen (with sound) 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 # 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 ##! Window
@@ -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
@@ -96,7 +96,10 @@ Variants {
left: true left: true
right: 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 { Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
} }
@@ -442,7 +445,7 @@ Variants {
color: bgRoot.colText color: bgRoot.colText
style: Text.Raised style: Text.Raised
styleColor: Appearance.colors.colShadow styleColor: Appearance.colors.colShadow
animateChange: true animateChange: Config.options.background.clock.digital.animateChange
} }
component ClockStatusText: Row { component ClockStatusText: Row {
id: statusTextRow id: statusTextRow
@@ -13,13 +13,14 @@ Item {
text: Qt.locale().toString(DateTime.clock.date, root.isMonth ? "MM" : "d") text: Qt.locale().toString(DateTime.clock.date, root.isMonth ? "MM" : "d")
MaterialCookie { MaterialShape {
id: bubble
z: 5 z: 5
sides: root.isMonth ? 1 : 4 // sides: root.isMonth ? 1 : 4
shape: root.isMonth ? MaterialShape.Shape.Pill : MaterialShape.Shape.Pentagon
anchors.centerIn: parent anchors.centerIn: parent
color: root.isMonth ? Appearance.colors.colPrimaryContainer : Appearance.colors.colTertiaryContainer color: root.isMonth ? Appearance.colors.colPrimaryContainer : Appearance.colors.colTertiaryContainer
implicitSize: targetSize implicitSize: targetSize
constantlyRotate: Config.options.background.clock.cookie.constantlyRotate
} }
StyledText { StyledText {
@@ -27,7 +27,7 @@ Item { // Bar content region
// Background shadow // Background shadow
Loader { 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 anchors.fill: barBackground
sourceComponent: StyledRectangularShadow { sourceComponent: StyledRectangularShadow {
anchors.fill: undefined // The loader's anchors act on this, and this should not have any anchor anchors.fill: undefined // The loader's anchors act on this, and this should not have any anchor
@@ -41,7 +41,7 @@ Item {
visible: Config.options.bar.utilButtons.showScreenRecord visible: Config.options.bar.utilButtons.showScreenRecord
sourceComponent: CircleUtilButton { sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
onClicked: Quickshell.execDetached(["bash", "-c", "~/.config/hypr/hyprland/scripts/record.sh"]) onClicked: Quickshell.execDetached([Directories.recordScriptPath])
MaterialSymbol { MaterialSymbol {
horizontalAlignment: Qt.AlignHCenter horizontalAlignment: Qt.AlignHCenter
fill: 1 fill: 1
@@ -5,6 +5,7 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects import Qt5Compat.GraphicalEffects
import Qt.labs.synchronizer
import Quickshell.Io import Quickshell.Io
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
@@ -22,7 +23,6 @@ Scope { // Scope
"name": Translation.tr("Elements") "name": Translation.tr("Elements")
}, },
] ]
property int selectedTab: 0
Loader { Loader {
id: cheatsheetLoader id: cheatsheetLoader
@@ -31,6 +31,7 @@ Scope { // Scope
sourceComponent: PanelWindow { // Window sourceComponent: PanelWindow { // Window
id: cheatsheetRoot id: cheatsheetRoot
visible: cheatsheetLoader.active visible: cheatsheetLoader.active
property int selectedTab: 0
anchors { anchors {
top: true top: true
@@ -85,16 +86,16 @@ Scope { // Scope
} }
if (event.modifiers === Qt.ControlModifier) { if (event.modifiers === Qt.ControlModifier) {
if (event.key === Qt.Key_PageDown) { if (event.key === Qt.Key_PageDown) {
root.selectedTab = Math.min(root.selectedTab + 1, root.tabButtonList.length - 1); cheatsheetRoot.selectedTab = Math.min(cheatsheetRoot.selectedTab + 1, root.tabButtonList.length - 1);
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_PageUp) { } else if (event.key === Qt.Key_PageUp) {
root.selectedTab = Math.max(root.selectedTab - 1, 0); cheatsheetRoot.selectedTab = Math.max(cheatsheetRoot.selectedTab - 1, 0);
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Tab) { } else if (event.key === Qt.Key_Tab) {
root.selectedTab = (root.selectedTab + 1) % root.tabButtonList.length; cheatsheetRoot.selectedTab = (cheatsheetRoot.selectedTab + 1) % root.tabButtonList.length;
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Backtab) { } else if (event.key === Qt.Key_Backtab) {
root.selectedTab = (root.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length; cheatsheetRoot.selectedTab = (cheatsheetRoot.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length;
event.accepted = true; event.accepted = true;
} }
} }
@@ -140,9 +141,8 @@ Scope { // Scope
PrimaryTabBar { // Tab strip PrimaryTabBar { // Tab strip
id: tabBar id: tabBar
tabButtonList: root.tabButtonList tabButtonList: root.tabButtonList
externalTrackedTab: root.selectedTab Synchronizer on currentIndex {
function onCurrentIndexChanged(currentIndex) { property alias source: cheatsheetRoot.selectedTab
root.selectedTab = currentIndex;
} }
} }
@@ -164,12 +164,12 @@ Scope { // Scope
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
} }
currentIndex: tabBar.externalTrackedTab currentIndex: cheatsheetRoot.selectedTab
onCurrentIndexChanged: { onCurrentIndexChanged: {
contentWidthBehavior.enabled = true; contentWidthBehavior.enabled = true;
contentHeightBehavior.enabled = true; contentHeightBehavior.enabled = true;
tabBar.enableIndicatorAnimation = true; tabBar.enableIndicatorAnimation = true;
root.selectedTab = currentIndex; cheatsheetRoot.selectedTab = currentIndex;
} }
clip: true clip: true
@@ -159,6 +159,8 @@ Singleton {
property color colTertiaryContainer: m3colors.m3tertiaryContainer property color colTertiaryContainer: m3colors.m3tertiaryContainer
property color colTertiaryContainerHover: ColorUtils.mix(m3colors.m3tertiaryContainer, m3colors.m3onTertiaryContainer, 0.90) property color colTertiaryContainerHover: ColorUtils.mix(m3colors.m3tertiaryContainer, m3colors.m3onTertiaryContainer, 0.90)
property color colTertiaryContainerActive: ColorUtils.mix(m3colors.m3tertiaryContainer, colLayer1Active, 0.54) 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 colOnSecondaryContainer: m3colors.m3onSecondaryContainer
property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency) property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency)
property color colSurfaceContainer: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency) property color colSurfaceContainer: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency)
@@ -315,9 +317,9 @@ Singleton {
} }
property QtObject clickBounce: QtObject { property QtObject clickBounce: QtObject {
property int duration: 200 property int duration: 400
property int type: Easing.BezierSpline property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.expressiveFastSpatial property list<real> bezierCurve: animationCurves.expressiveDefaultSpatial
property int velocity: 850 property int velocity: 850
property Component numberAnimation: Component { NumberAnimation { property Component numberAnimation: Component { NumberAnimation {
duration: root.animation.clickBounce.duration duration: root.animation.clickBounce.duration
@@ -165,6 +165,9 @@ Singleton {
property bool dateInClock: true property bool dateInClock: true
property bool constantlyRotate: false property bool constantlyRotate: false
} }
property JsonObject digital: JsonObject {
property bool animateChange: true
}
} }
property string wallpaperPath: "" property string wallpaperPath: ""
@@ -194,6 +197,7 @@ Singleton {
} }
property bool bottom: false // Instead of top property bool bottom: false // Instead of top
property int cornerStyle: 0 // 0: Hug | 1: Float | 2: Plain rectangle 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 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 string topLeftIcon: "spark" // Options: "distro" or any icon name in ~/.config/quickshell/ii/assets/icons
property bool showBackground: true property bool showBackground: true
@@ -376,6 +380,11 @@ Singleton {
property int updateInterval: 3000 property int updateInterval: 3000
} }
property JsonObject musicRecognition: JsonObject {
property int timeout: 16
property int interval: 4
}
property JsonObject search: JsonObject { property JsonObject search: JsonObject {
property int nonAppResultDelay: 30 // This prevents lagging when typing property int nonAppResultDelay: 30 // This prevents lagging when typing
property string engineBaseUrl: "https://www.google.com/search?q=" property string engineBaseUrl: "https://www.google.com/search?q="
@@ -419,10 +428,11 @@ Singleton {
property bool bottom: false property bool bottom: false
property bool valueScroll: true property bool valueScroll: true
property bool clickless: false property bool clickless: false
property real cornerRegionWidth: 250 property int cornerRegionWidth: 250
property real cornerRegionHeight: 2 property int cornerRegionHeight: 5
property bool visualize: false property bool visualize: false
property bool clicklessCornerEnd: true property bool clicklessCornerEnd: true
property int clicklessCornerVerticalOffset: 1
} }
property JsonObject quickToggles: JsonObject { property JsonObject quickToggles: JsonObject {
@@ -42,6 +42,7 @@ Singleton {
property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/ai/prompts`) property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/ai/prompts`)
property string aiChats: FileUtils.trimFileProtocol(`${Directories.state}/user/ai/chats`) property string aiChats: FileUtils.trimFileProtocol(`${Directories.state}/user/ai/chats`)
property string aiTranslationScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/ai/gemini-translate.sh`) property string aiTranslationScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/ai/gemini-translate.sh`)
property string recordScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/videos/record.sh`)
// Cleanup on init // Cleanup on init
Component.onCompleted: { Component.onCompleted: {
Quickshell.execDetached(["mkdir", "-p", `${shellConfig}`]) Quickshell.execDetached(["mkdir", "-p", `${shellConfig}`])
@@ -13,8 +13,8 @@ Rectangle {
property alias uniformCellSizes: rowLayout.uniformCellSizes property alias uniformCellSizes: rowLayout.uniformCellSizes
property real spacing: 5 property real spacing: 5
property real padding: 0 property real padding: 0
property int clickIndex: rowLayout.clickIndex property alias clickIndex: rowLayout.clickIndex
property int childrenCount: rowLayout.children.length property alias childrenCount: rowLayout.childrenCount
property real contentWidth: { property real contentWidth: {
let total = 0; let total = 0;
@@ -44,5 +44,6 @@ Rectangle {
anchors.margins: root.padding anchors.margins: root.padding
spacing: root.spacing spacing: root.spacing
property int clickIndex: -1 property int clickIndex: -1
property int childrenCount: children.length
}] }]
} }
@@ -3,7 +3,6 @@ import QtQuick.Shapes
import Quickshell import Quickshell
import qs.modules.common import qs.modules.common
Item { Item {
id: root id: root
@@ -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<var> 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)
}
}
@@ -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();
}
}
}
@@ -2,7 +2,7 @@ import QtQuick
import qs.modules.common import qs.modules.common
import qs.modules.common.widgets import qs.modules.common.widgets
MaterialCookie { MaterialShape {
id: root id: root
property alias text: symbol.text property alias text: symbol.text
property alias iconSize: symbol.iconSize property alias iconSize: symbol.iconSize
@@ -13,7 +13,7 @@ MaterialCookie {
color: Appearance.colors.colSecondaryContainer color: Appearance.colors.colSecondaryContainer
colSymbol: Appearance.colors.colOnSecondaryContainer colSymbol: Appearance.colors.colOnSecondaryContainer
sides: 5 shape: MaterialShape.Shape.Clover4Leaf
implicitSize: Math.max(symbol.implicitWidth, symbol.implicitHeight) + padding * 2 implicitSize: Math.max(symbol.implicitWidth, symbol.implicitHeight) + padding * 2
@@ -7,6 +7,7 @@ Rectangle {
id: root id: root
property alias materialIcon: icon.text property alias materialIcon: icon.text
property alias text: noticeText.text property alias text: noticeText.text
default property alias data: buttonRow.data
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
color: Appearance.colors.colPrimaryContainer color: Appearance.colors.colPrimaryContainer
@@ -28,13 +29,23 @@ Rectangle {
color: Appearance.colors.colOnPrimaryContainer color: Appearance.colors.colOnPrimaryContainer
} }
StyledText { ColumnLayout {
id: noticeText
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter spacing: 4
text: "Notice message"
color: Appearance.colors.colOnPrimaryContainer StyledText {
wrapMode: Text.WordWrap 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
}
} }
} }
} }
@@ -6,7 +6,7 @@ import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import Quickshell.Services.Notifications import Quickshell.Services.Notifications
MaterialCookie { // App icon MaterialShape { // App icon
id: root id: root
property var appIcon: "" property var appIcon: ""
property var summary: "" property var summary: ""
@@ -21,8 +21,11 @@ MaterialCookie { // App icon
property real smallAppIconSize: implicitSize * smallAppIconScale property real smallAppIconSize: implicitSize * smallAppIconScale
implicitSize: 38 * scale implicitSize: 38 * scale
sides: isUrgent ? 10 : 0 property list<var> urgentShapes: [
amplitude: implicitSize / 24 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 color: isUrgent ? Appearance.colors.colPrimary : Appearance.colors.colSecondaryContainer
Loader { Loader {
@@ -7,9 +7,11 @@ Item {
id: root id: root
property bool shown: true property bool shown: true
property alias icon: cookieWrappedMaterialSymbol.text property alias icon: shapeWidget.text
property alias title: widgetNameText.text property alias title: widgetNameText.text
property alias description: widgetDescriptionText.text property alias description: widgetDescriptionText.text
property alias shape: shapeWidget.shape
property alias descriptionHorizontalAlignment: widgetDescriptionText.horizontalAlignment
opacity: shown ? 1 : 0 opacity: shown ? 1 : 0
visible: opacity > 0 visible: opacity > 0
@@ -27,14 +29,16 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
spacing: 5 spacing: 5
CookieWrappedMaterialSymbol { MaterialShapeWrappedMaterialSymbol {
id: cookieWrappedMaterialSymbol id: shapeWidget
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
iconSize: 60 padding: 12
rotation: -60 * (1 - root.opacity) iconSize: 56
rotation: -30 * (1 - root.opacity)
} }
StyledText { StyledText {
id: widgetNameText id: widgetNameText
visible: title !== ""
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
font.pixelSize: Appearance.font.pixelSize.larger font.pixelSize: Appearance.font.pixelSize.larger
font.family: Appearance.font.family.title font.family: Appearance.font.family.title
@@ -43,6 +47,7 @@ Item {
} }
StyledText { StyledText {
id: widgetDescriptionText id: widgetDescriptionText
visible: description !== ""
Layout.fillWidth: true Layout.fillWidth: true
font.pixelSize: Appearance.font.pixelSize.small font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.m3colors.m3outline color: Appearance.m3colors.m3outline
@@ -3,16 +3,20 @@ import qs.services
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Qt.labs.synchronizer
ColumnLayout { ColumnLayout {
id: root id: root
spacing: 0 spacing: 0
required property var tabButtonList // Something like [{"icon": "notifications", "name": Translation.tr("Notifications")}, {"icon": "volume_up", "name": Translation.tr("Volume mixer")}] required property var tabButtonList // Something like [{"icon": "notifications", "name": Translation.tr("Notifications")}, {"icon": "volume_up", "name": Translation.tr("Volume mixer")}]
required property var externalTrackedTab property int currentIndex
property bool enableIndicatorAnimation: false property bool enableIndicatorAnimation: false
property color colIndicator: Appearance?.colors.colPrimary ?? "#65558F" property color colIndicator: Appearance?.colors.colPrimary ?? "#65558F"
property color colBorder: Appearance?.m3colors.m3outlineVariant ?? "#C6C6D0" property color colBorder: Appearance?.m3colors.m3outlineVariant ?? "#C6C6D0"
signal currentIndexChanged(int index)
onCurrentIndexChanged: {
enableIndicatorAnimation = true
}
property bool centerTabBar: parent.width > 500 property bool centerTabBar: parent.width > 500
Layout.fillWidth: !centerTabBar Layout.fillWidth: !centerTabBar
@@ -22,9 +26,8 @@ ColumnLayout {
TabBar { TabBar {
id: tabBar id: tabBar
Layout.fillWidth: true Layout.fillWidth: true
currentIndex: root.externalTrackedTab Synchronizer on currentIndex {
onCurrentIndexChanged: { property alias source: root.currentIndex
root.onCurrentIndexChanged(currentIndex)
} }
background: Item { background: Item {
@@ -42,10 +45,11 @@ ColumnLayout {
Repeater { Repeater {
model: root.tabButtonList model: root.tabButtonList
delegate: PrimaryTabButton { delegate: PrimaryTabButton {
selected: (index == root.externalTrackedTab) selected: (index == root.currentIndex)
buttonText: modelData.name buttonText: modelData.name
buttonIcon: modelData.icon buttonIcon: modelData.icon
minimumWidth: 160 minimumWidth: 160
onClicked: root.currentIndex = index
} }
} }
} }
@@ -54,12 +58,6 @@ ColumnLayout {
id: tabIndicator id: tabIndicator
Layout.fillWidth: true Layout.fillWidth: true
height: 3 height: 3
Connections {
target: root
function onExternalTrackedTabChanged() {
root.enableIndicatorAnimation = true
}
}
Rectangle { Rectangle {
id: indicator id: indicator
@@ -91,7 +91,7 @@ TabButton {
background: Rectangle { background: Rectangle {
id: buttonBackground id: buttonBackground
radius: Appearance?.rounding.small ?? 7 radius: Appearance?.rounding.normal
implicitHeight: 37 implicitHeight: 37
color: (root.hovered ? root.colBackgroundHover : root.colBackground) color: (root.hovered ? root.colBackgroundHover : root.colBackground)
layer.enabled: true layer.enabled: true
@@ -45,13 +45,25 @@ Switch {
anchors.leftMargin: root.checked ? ((root.pressed || root.down) ? (22 * root.scale) : 24 * root.scale) : ((root.pressed || root.down) ? (2 * root.scale) : 8 * root.scale) anchors.leftMargin: root.checked ? ((root.pressed || root.down) ? (22 * root.scale) : 24 * root.scale) : ((root.pressed || root.down) ? (2 * root.scale) : 8 * root.scale)
Behavior on anchors.leftMargin { Behavior on anchors.leftMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this) NumberAnimation {
duration: Appearance.animationCurves.expressiveFastSpatialDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
} }
Behavior on width { Behavior on width {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this) NumberAnimation {
duration: Appearance.animationCurves.expressiveFastSpatialDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
} }
Behavior on height { Behavior on height {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this) NumberAnimation {
duration: Appearance.animationCurves.expressiveFastSpatialDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
} }
Behavior on color { Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
@@ -17,13 +17,17 @@ function findSuitableMaterialSymbol(summary = "") {
'time': 'scheduleb', 'time': 'scheduleb',
'installed': 'download', 'installed': 'download',
'configuration reloaded': 'reset_wrench', 'configuration reloaded': 'reset_wrench',
'unable': 'question_mark',
"couldn't": 'question_mark',
'config': 'reset_wrench', 'config': 'reset_wrench',
'update': 'update', 'update': 'update',
'ai response': 'neurology', 'ai response': 'neurology',
'control': 'settings', 'control': 'settings',
'upsca': 'compare', 'upsca': 'compare',
'music': 'queue_music',
'install': 'deployed_code_update', 'install': 'deployed_code_update',
'startswith:file': 'folder_copy', // Declarative startsWith check 'startswith:file': 'folder_copy', // Declarative startsWith check
}; };
const lowerSummary = summary.toLowerCase(); const lowerSummary = summary.toLowerCase();
@@ -28,7 +28,10 @@ Scope {
Connections { Connections {
target: GlobalStates target: GlobalStates
function onScreenLockedChanged() { function onScreenLockedChanged() {
if (GlobalStates.screenLocked) lockContext.reset(); if (GlobalStates.screenLocked) {
lockContext.reset();
lockContext.tryFingerUnlock();
}
} }
} }
@@ -113,7 +116,7 @@ Scope {
onPressed: { onPressed: {
if (Config.options.lock.useHyprlock) { if (Config.options.lock.useHyprlock) {
Quickshell.execDetached(["hyprlock"]) Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]);
return; return;
} }
GlobalStates.screenLocked = true; GlobalStates.screenLocked = true;
@@ -2,6 +2,7 @@ import qs
import qs.modules.common import qs.modules.common
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io
import Quickshell.Services.Pam import Quickshell.Services.Pam
Scope { Scope {
@@ -18,6 +19,7 @@ Scope {
property string currentText: "" property string currentText: ""
property bool unlockInProgress: false property bool unlockInProgress: false
property bool showFailure: false property bool showFailure: false
property bool fingerprintsConfigured: false
property var targetAction: LockContext.ActionEnum.Unlock property var targetAction: LockContext.ActionEnum.Unlock
function resetTargetAction() { function resetTargetAction() {
@@ -60,6 +62,34 @@ Scope {
pam.start(); pam.start();
} }
function tryFingerUnlock() {
if (root.fingerprintsConfigured) {
fingerPam.start();
}
}
function stopFingerPam() {
fingerPam.abort();
}
Process {
id: fingerprintCheckProc
running: true
command: ["bash", "-c", "fprintd-list $(whoami)"]
stdout: StdioCollector {
id: fingerprintOutputCollector
onStreamFinished: {
root.fingerprintsConfigured = fingerprintOutputCollector.text.includes("Fingerprints for user");
}
}
onExited: (exitCode, exitStatus) => {
if (exitCode !== 0) {
console.warn("fprintd-list command exited with error:", exitCode, exitStatus);
root.fingerprintsConfigured = false;
}
}
}
PamContext { PamContext {
id: pam id: pam
@@ -74,6 +104,7 @@ Scope {
onCompleted: result => { onCompleted: result => {
if (result == PamResult.Success) { if (result == PamResult.Success) {
root.unlocked(root.targetAction); root.unlocked(root.targetAction);
stopFingerPam();
} else { } else {
root.clearText(); root.clearText();
root.unlockInProgress = false; root.unlockInProgress = false;
@@ -83,4 +114,19 @@ Scope {
} }
} }
PamContext {
id: fingerPam
configDirectory: "pam"
config: "fprintd.conf"
onCompleted: result => {
if (result == PamResult.Success) {
root.unlocked(root.targetAction);
stopFingerPam();
} else if (result == PamResult.Error){ // if timeout or etc..
tryFingerUnlock()
}
}
}
} }
@@ -1,5 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell.Services.UPower import Quickshell.Services.UPower
import qs import qs
import qs.services import qs.services
@@ -7,6 +8,7 @@ import qs.modules.common
import qs.modules.common.widgets import qs.modules.common.widgets
import qs.modules.common.functions import qs.modules.common.functions
import qs.modules.bar as Bar import qs.modules.bar as Bar
import Quickshell
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
MouseArea { MouseArea {
@@ -98,6 +100,23 @@ MouseArea {
scale: root.toolbarScale scale: root.toolbarScale
opacity: root.toolbarOpacity opacity: root.toolbarOpacity
// Fingerprint
Loader {
Layout.leftMargin: 10
Layout.rightMargin: 6
Layout.alignment: Qt.AlignVCenter
active: root.context.fingerprintsConfigured
visible: active
sourceComponent: MaterialSymbol {
id: fingerprintIcon
fill: 1
text: "fingerprint"
iconSize: Appearance.font.pixelSize.hugeass
color: Appearance.colors.colOnSurfaceVariant
}
}
ToolbarTextField { ToolbarTextField {
id: passwordBox id: passwordBox
placeholderText: GlobalStates.screenUnlockFailed ? Translation.tr("Incorrect password") : Translation.tr("Enter password") placeholderText: GlobalStates.screenUnlockFailed ? Translation.tr("Incorrect password") : Translation.tr("Enter password")
@@ -125,6 +144,16 @@ MouseArea {
root.context.resetClearTimer(); 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 { SequentialAnimation {
id: wrongPasswordShakeAnim id: wrongPasswordShakeAnim
NumberAnimation { target: passwordBox; property: "x"; to: -30; duration: 50 } NumberAnimation { target: passwordBox; property: "x"; to: -30; duration: 50 }
@@ -139,6 +168,17 @@ MouseArea {
if (GlobalStates.screenUnlockFailed) wrongPasswordShakeAnim.restart(); 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 { ToolbarButton {
@@ -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<var> 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
}
}
}
}
}
}
}
@@ -0,0 +1 @@
auth sufficient pam_fprintd.so
@@ -21,7 +21,7 @@ Scope {
readonly property real osdWidth: Appearance.sizes.osdWidth readonly property real osdWidth: Appearance.sizes.osdWidth
readonly property real widgetWidth: Appearance.sizes.mediaControlsWidth readonly property real widgetWidth: Appearance.sizes.mediaControlsWidth
readonly property real widgetHeight: Appearance.sizes.mediaControlsHeight readonly property real widgetHeight: Appearance.sizes.mediaControlsHeight
property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1
property list<real> visualizerPoints: [] property list<real> visualizerPoints: []
property bool hasPlasmaIntegration: false property bool hasPlasmaIntegration: false
@@ -44,6 +44,9 @@ Toolbar {
return "image_search"; return "image_search";
case RegionSelection.SnipAction.CharRecognition: case RegionSelection.SnipAction.CharRecognition:
return "document_scanner"; return "document_scanner";
case RegionSelection.SnipAction.Record:
case RegionSelection.SnipAction.RecordWithSound:
return "videocam";
default: default:
return ""; return "";
} }
@@ -5,15 +5,16 @@ import qs.modules.common.widgets
import qs.services import qs.services
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Qt.labs.synchronizer
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
import Qt.labs.synchronizer
PanelWindow { PanelWindow {
id: root id: root
visible: false visible: false
color: "transparent"
WlrLayershell.namespace: "quickshell:regionSelector" WlrLayershell.namespace: "quickshell:regionSelector"
WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
@@ -26,7 +27,7 @@ PanelWindow {
} }
// TODO: Ask: sidebar AI; Ocr: tesseract // TODO: Ask: sidebar AI; Ocr: tesseract
enum SnipAction { Copy, Edit, Search, CharRecognition } enum SnipAction { Copy, Edit, Search, CharRecognition, Record, RecordWithSound }
enum SelectionMode { RectCorners, Circle } enum SelectionMode { RectCorners, Circle }
property var action: RegionSelection.SnipAction.Copy property var action: RegionSelection.SnipAction.Copy
property var selectionMode: RegionSelection.SelectionMode.RectCorners property var selectionMode: RegionSelection.SelectionMode.RectCorners
@@ -175,14 +176,35 @@ PanelWindow {
property real regionY: Math.min(dragStartY, draggingY) property real regionY: Math.min(dragStartY, draggingY)
Process { Process {
id: screenshotProcess id: screenshotProc
running: true running: true
command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(root.screen.name)}' '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`] command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(root.screen.name)}' '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`]
onExited: (exitCode, exitStatus) => { onExited: (exitCode, exitStatus) => {
root.visible = true;
if (root.enableContentRegions) imageDetectionProcess.running = 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 { Process {
id: imageDetectionProcess id: imageDetectionProcess
@@ -221,11 +243,16 @@ PanelWindow {
} }
// Set command for action // 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)} ` 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 cropToStdout = `${cropBase} -`
const cropInPlace = `${cropBase} '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'` const cropInPlace = `${cropBase} '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`
const cleanup = `rm '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'` const cleanup = `rm '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`
const slurpRegion = `${rx},${ry} ${rw}x${rh}`
const uploadAndGetUrl = (filePath) => { const uploadAndGetUrl = (filePath) => {
return `curl -sF files[]=@'${StringUtils.shellSingleQuoteEscape(filePath)}' ${root.fileUploadApiEndpoint} | jq -r '.files[0].url'` return `curl -sF files[]=@'${StringUtils.shellSingleQuoteEscape(filePath)}' ${root.fileUploadApiEndpoint} | jq -r '.files[0].url'`
} }
@@ -242,6 +269,12 @@ PanelWindow {
case RegionSelection.SnipAction.CharRecognition: 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}`] 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; 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: default:
console.warn("[Region Selector] Unknown snip action, skipping snip."); console.warn("[Region Selector] Unknown snip action, skipping snip.");
root.dismiss(); root.dismiss();
@@ -62,6 +62,18 @@ Scope {
GlobalStates.regionSelectorOpen = true 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 { IpcHandler {
target: "region" target: "region"
@@ -71,10 +83,15 @@ Scope {
function search() { function search() {
root.search() root.search()
} }
function ocr() { function ocr() {
root.ocr() root.ocr()
} }
function record() {
root.record()
}
function recordWithSound() {
root.recordWithSound()
}
} }
GlobalShortcut { GlobalShortcut {
@@ -92,4 +109,14 @@ Scope {
description: "Recognizes text in the selected region" description: "Recognizes text in the selected region"
onPressed: root.ocr() 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()
}
} }
@@ -79,10 +79,12 @@ Scope {
implicitWidth: Config.options.sidebar.cornerOpen.cornerRegionWidth implicitWidth: Config.options.sidebar.cornerOpen.cornerRegionWidth
implicitHeight: Config.options.sidebar.cornerOpen.cornerRegionHeight implicitHeight: Config.options.sidebar.cornerOpen.cornerRegionHeight
hoverEnabled: true hoverEnabled: true
onMouseXChanged: { onPositionChanged: {
if (!Config.options.sidebar.cornerOpen.clicklessCornerEnd) return; if (!Config.options.sidebar.cornerOpen.clicklessCornerEnd) return;
if ((cornerWidget.isRight && mouseArea.mouseX >= mouseArea.width - 2) const verticalOffset = Config.options.sidebar.cornerOpen.clicklessCornerVerticalOffset;
|| (cornerWidget.isLeft && mouseArea.mouseX <= 2)) 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](); screenCorners.actionForCorner[cornerPanelWindow.corner]();
} }
onEntered: { onEntered: {
@@ -55,6 +55,20 @@ ContentPage {
} }
} }
ContentSubsection {
visible: Config.options.background.clock.style === "digital"
title: Translation.tr("Digital clock settings")
ConfigSwitch {
buttonIcon: "animation"
text: Translation.tr("Animate time change")
checked: Config.options.background.clock.digital.animateChange
onCheckedChanged: {
Config.options.background.clock.digital.animateChange = checked;
}
}
}
ContentSubsection { ContentSubsection {
visible: Config.options.background.clock.style === "cookie" visible: Config.options.background.clock.style === "cookie"
title: Translation.tr("Cookie clock settings") title: Translation.tr("Cookie clock settings")
@@ -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 { 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 { ConfigSwitch {
enabled: !Config.options.sidebar.cornerOpen.clickless 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 checked: Config.options.sidebar.cornerOpen.clicklessCornerEnd
onCheckedChanged: { onCheckedChanged: {
Config.options.sidebar.cornerOpen.clicklessCornerEnd = checked; 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") 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 { ConfigRow {
uniform: true uniform: true
ConfigSwitch { ConfigSwitch {
@@ -332,5 +332,33 @@ ContentPage {
NoticeBox { NoticeBox {
Layout.fillWidth: true 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) 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
}
}
}
} }
} }
@@ -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 { ContentSection {
icon: "cell_tower" icon: "cell_tower"
title: Translation.tr("Networking") title: Translation.tr("Networking")
@@ -54,6 +82,7 @@ ContentPage {
Config.options.resources.updateInterval = value; Config.options.resources.updateInterval = value;
} }
} }
} }
ContentSection { ContentSection {
@@ -369,6 +369,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
icon: "neurology" icon: "neurology"
title: Translation.tr("Large language models") 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") 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 { ScrollToBottomButton {
@@ -23,12 +23,21 @@ Item {
property var suggestionQuery: "" property var suggestionQuery: ""
property var suggestionList: [] 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 { Connections {
target: Booru target: Booru
function onTagSuggestion(query, suggestions) { function onTagSuggestion(query, suggestions) {
root.suggestionQuery = query; root.suggestionQuery = query;
root.suggestionList = suggestions; root.suggestionList = suggestions;
} }
function onRunningRequestsChanged() {
if (Booru.runningRequests === 0) {
root.pullLoading = false;
}
}
} }
property var allCommands: [ property var allCommands: [
@@ -53,6 +62,8 @@ Item {
if (root.responses.length > 0) { if (root.responses.length > 0) {
const lastResponse = root.responses[root.responses.length - 1]; const lastResponse = root.responses[root.responses.length - 1];
root.handleInput(`${lastResponse.tags.join(" ")} ${parseInt(lastResponse.page) + 1}`); root.handleInput(`${lastResponse.tags.join(" ")} ${parseInt(lastResponse.page) + 1}`);
} else {
root.handleInput("");
} }
} }
}, },
@@ -85,10 +96,7 @@ Item {
} }
} }
else if (inputText.trim() == "+") { else if (inputText.trim() == "+") {
if (root.responses.length > 0) { root.handleInput(`${root.commandPrefix}next`);
const lastResponse = root.responses[root.responses.length - 1]
root.handleInput(lastResponse.tags.join(" ") + ` ${parseInt(lastResponse.page) + 1}`);
}
} }
else { else {
// Create tag list // Create tag list
@@ -111,17 +119,23 @@ Item {
} }
} }
property real pageKeyScrollAmount: booruResponseListView.height / 2
Keys.onPressed: (event) => { Keys.onPressed: (event) => {
tagInputField.forceActiveFocus() tagInputField.forceActiveFocus()
if (event.modifiers === Qt.NoModifier) { if (event.modifiers === Qt.NoModifier) {
if (event.key === Qt.Key_PageUp) { if (event.key === Qt.Key_PageUp) {
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 event.accepted = true
} else if (event.key === Qt.Key_PageDown) { } 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 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 mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4
property int lastResponseLength: 0 property int lastResponseLength: 0
Connections {
model: ScriptModel { target: root
values: { function onResponsesChanged() {
if(root.responses.length > booruResponseListView.lastResponseLength) { if (root.responses.length > booruResponseListView.lastResponseLength) {
if (booruResponseListView.lastResponseLength > 0 && root.responses[booruResponseListView.lastResponseLength].provider != "system") if (booruResponseListView.lastResponseLength > 0 && root.responses[booruResponseListView.lastResponseLength].provider != "system")
booruResponseListView.contentY = booruResponseListView.contentY + root.scrollOnNewResponse booruResponseListView.contentY = booruResponseListView.contentY + root.scrollOnNewResponse
booruResponseListView.lastResponseLength = root.responses.length booruResponseListView.lastResponseLength = root.responses.length
} }
return root.responses
} }
} }
model: ScriptModel {
values: root.responses
}
delegate: BooruResponse { delegate: BooruResponse {
responseData: modelData responseData: modelData
tagInputField: root.inputField tagInputField: root.inputField
@@ -176,6 +193,14 @@ Item {
downloadPath: root.downloadPath downloadPath: root.downloadPath
nsfwPath: root.nsfwPath 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 { PagePlaceholder {
@@ -185,6 +210,7 @@ Item {
icon: "bookmark_heart" icon: "bookmark_heart"
title: Translation.tr("Anime boorus") title: Translation.tr("Anime boorus")
description: "" description: ""
shape: MaterialShape.Shape.Bun
} }
ScrollToBottomButton { ScrollToBottomButton {
@@ -192,42 +218,24 @@ Item {
target: booruResponseListView target: booruResponseListView
} }
Item { // Queries awaiting response MaterialLoadingIndicator {
id: loadingIndicator
z: 4 z: 4
anchors.left: parent.left anchors {
anchors.right: parent.right horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom bottom: parent.bottom
anchors.margins: 10 bottomMargin: 20 + (root.pullLoading ? 0 : Math.max(0, (root.normalizedPullDistance - 0.5) * 50))
implicitHeight: pendingBackground.implicitHeight Behavior on bottomMargin {
opacity: Booru.runningRequests > 0 ? 1 : 0 NumberAnimation {
visible: opacity > 0 duration: 200
easing.type: Easing.BezierSpline
Behavior on opacity { easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
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)
} }
} }
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)
} }
} }
@@ -5,6 +5,7 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects import Qt5Compat.GraphicalEffects
import Qt.labs.synchronizer
Item { Item {
id: root id: root
@@ -58,9 +59,8 @@ Item {
id: tabBar id: tabBar
visible: root.tabButtonList.length > 1 visible: root.tabButtonList.length > 1
tabButtonList: root.tabButtonList tabButtonList: root.tabButtonList
externalTrackedTab: root.selectedTab Synchronizer on currentIndex {
function onCurrentIndexChanged(currentIndex) { property alias source: root.selectedTab
root.selectedTab = currentIndex
} }
} }
@@ -71,7 +71,7 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
spacing: 10 spacing: 10
currentIndex: tabBar.externalTrackedTab currentIndex: root.selectedTab
onCurrentIndexChanged: { onCurrentIndexChanged: {
tabBar.enableIndicatorAnimation = true tabBar.enableIndicatorAnimation = true
root.selectedTab = currentIndex root.selectedTab = currentIndex
@@ -105,7 +105,7 @@ Rectangle {
anchors.right: parent.right anchors.right: parent.right
anchors.leftMargin: 10 anchors.leftMargin: 10
anchors.rightMargin: 10 anchors.rightMargin: 10
spacing: 7 spacing: 12
Item { Item {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
@@ -177,6 +177,20 @@ Rectangle {
ButtonGroup { ButtonGroup {
spacing: 5 spacing: 5
AiMessageControlButton {
id: regenButton
buttonIcon: "refresh"
visible: messageData?.role === 'assistant'
onClicked: {
Ai.regenerate(root.messageIndex)
}
StyledToolTip {
text: Translation.tr("Regenerate")
}
}
AiMessageControlButton { AiMessageControlButton {
id: copyButton id: copyButton
buttonIcon: activated ? "inventory" : "content_copy" buttonIcon: activated ? "inventory" : "content_copy"
@@ -254,28 +268,50 @@ Rectangle {
spacing: 0 spacing: 0
Repeater { Repeater {
model: root.messageBlocks.length model: ScriptModel {
delegate: Loader { values: Array.from({ length: root.messageBlocks.length }, (msg, i) => {
required property int index return ({
property var thisBlock: root.messageBlocks[index] type: root.messageBlocks[i].type
Layout.fillWidth: true })
// property var segment: thisBlock });
property var segmentContent: thisBlock.content }
property var segmentLang: thisBlock.lang
property var messageData: root.messageData
property var editing: root.editing
property var renderMarkdown: root.renderMarkdown
property var enableMouseSelection: root.enableMouseSelection
property bool thinking: root.messageData?.thinking ?? true
property bool done: root.messageData?.done ?? false
property bool completed: thisBlock.completed ?? false
property bool forceDisableChunkSplitting: root.messageData.content.includes("```") delegate: DelegateChooser {
id: messageDelegate
source: thisBlock.type === "code" ? "MessageCodeBlock.qml" : role: "type"
thisBlock.type === "think" ? "MessageThinkBlock.qml" :
"MessageTextBlock.qml"
DelegateChoice { roleValue: "code"; MessageCodeBlock {
required property int index
property var thisBlock: root.messageBlocks[index]
editing: root.editing
renderMarkdown: root.renderMarkdown
enableMouseSelection: root.enableMouseSelection
segmentContent: thisBlock.content
segmentLang: thisBlock.lang
messageData: root.messageData
} }
DelegateChoice { roleValue: "think"; MessageThinkBlock {
required property int index
property var thisBlock: root.messageBlocks[index]
editing: root.editing
renderMarkdown: root.renderMarkdown
enableMouseSelection: root.enableMouseSelection
segmentContent: thisBlock.content
messageData: root.messageData
done: root.messageData?.done ?? false
completed: thisBlock.completed ?? false
} }
DelegateChoice { roleValue: "text"; MessageTextBlock {
required property int index
property var thisBlock: root.messageBlocks[index]
editing: root.editing
renderMarkdown: root.renderMarkdown
enableMouseSelection: root.enableMouseSelection
segmentContent: thisBlock.content
messageData: root.messageData
done: root.messageData?.done ?? false
forceDisableChunkSplitting: root.messageData.content.includes("```")
} }
} }
} }
} }
@@ -13,22 +13,20 @@ import org.kde.syntaxhighlighting
ColumnLayout { ColumnLayout {
id: root id: root
// These are needed on the parent loader // These are needed on the parent loader
property bool editing: parent?.editing ?? false property bool editing: false
property bool renderMarkdown: parent?.renderMarkdown ?? true property bool renderMarkdown: true
property bool enableMouseSelection: parent?.enableMouseSelection ?? false property bool enableMouseSelection: false
property var segmentContent: parent?.segmentContent ?? ({}) property var segmentContent: ({})
property var segmentLang: parent?.segmentLang ?? "txt" property var segmentLang: "txt"
property var messageData: {}
property bool isCommandRequest: segmentLang === "command" property bool isCommandRequest: segmentLang === "command"
property var displayLang: (isCommandRequest ? "bash" : segmentLang) property var displayLang: (isCommandRequest ? "bash" : segmentLang)
property var messageData: parent?.messageData ?? {}
property real codeBlockBackgroundRounding: Appearance.rounding.small property real codeBlockBackgroundRounding: Appearance.rounding.small
property real codeBlockHeaderPadding: 3 property real codeBlockHeaderPadding: 3
property real codeBlockComponentSpacing: 2 property real codeBlockComponentSpacing: 2
spacing: codeBlockComponentSpacing spacing: codeBlockComponentSpacing
anchors.left: parent.left
anchors.right: parent.right
Rectangle { // Code background Rectangle { // Code background
Layout.fillWidth: true Layout.fillWidth: true
@@ -14,17 +14,17 @@ import Quickshell.Hyprland
ColumnLayout { ColumnLayout {
id: root id: root
// These are needed on the parent loader // These are needed on the parent loader
property bool editing: parent?.editing ?? false property bool editing: false
property bool renderMarkdown: parent?.renderMarkdown ?? true property bool renderMarkdown: true
property bool enableMouseSelection: parent?.enableMouseSelection ?? false property bool enableMouseSelection: false
property string segmentContent: parent?.segmentContent ?? ({}) property var segmentContent: ({})
property var messageData: parent?.messageData ?? {} property var messageData: {}
property bool done: parent?.done ?? true property bool done: true
property list<string> renderedLatexHashes: [] property bool forceDisableChunkSplitting: false
property list<string> renderedLatexHashes: []
property string renderedSegmentContent: "" property string renderedSegmentContent: ""
property string shownText: "" property string shownText: ""
property bool forceDisableChunkSplitting: parent?.forceDisableChunkSplitting ?? false
property bool fadeChunkSplitting: !forceDisableChunkSplitting && !editing && !/\n\|/.test(shownText) && Config.options.sidebar.ai.textFadeIn property bool fadeChunkSplitting: !forceDisableChunkSplitting && !editing && !/\n\|/.test(shownText) && Config.options.sidebar.ai.textFadeIn
Layout.fillWidth: true Layout.fillWidth: true
@@ -11,13 +11,13 @@ import Qt5Compat.GraphicalEffects
Item { Item {
id: root id: root
// These are needed on the parent loader // These are needed on the parent loader
property bool editing: parent?.editing ?? false property bool editing: false
property bool renderMarkdown: parent?.renderMarkdown ?? true property bool renderMarkdown: true
property bool enableMouseSelection: parent?.enableMouseSelection ?? false property bool enableMouseSelection: false
property string segmentContent: parent?.segmentContent ?? ({}) property var segmentContent: ({})
property var messageData: parent?.messageData ?? {} property var messageData: {}
property bool done: parent?.done ?? true property bool done: true
property bool completed: parent?.completed ?? false property bool completed: false
property real thinkBlockBackgroundRounding: Appearance.rounding.small property real thinkBlockBackgroundRounding: Appearance.rounding.small
property real thinkBlockHeaderPaddingVertical: 3 property real thinkBlockHeaderPaddingVertical: 3
@@ -100,9 +100,7 @@ Rectangle {
id: tagsFlickable id: tagsFlickable
visible: root.responseData.tags.length > 0 visible: root.responseData.tags.length > 0
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.fillWidth: { Layout.fillWidth: true
return true
}
implicitHeight: tagRowLayout.implicitHeight implicitHeight: tagRowLayout.implicitHeight
contentWidth: tagRowLayout.implicitWidth contentWidth: tagRowLayout.implicitWidth
@@ -31,37 +31,12 @@ Item {
} }
// Placeholder when list is empty // Placeholder when list is empty
Item { PagePlaceholder {
anchors.fill: listview shown: Notifications.list.length === 0
icon: "notifications_active"
visible: opacity > 0 description: Translation.tr("Nothing")
opacity: (Notifications.list.length === 0) ? 1 : 0 shape: MaterialShape.Shape.Ghostish
descriptionHorizontalAlignment: Text.AlignHCenter
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")
}
}
} }
ButtonGroup { ButtonGroup {
@@ -29,7 +29,7 @@ AbstractQuickPanel {
readonly property real baseCellHeight: 56 readonly property real baseCellHeight: 56
// Toggles // Toggles
readonly property list<string> availableToggleTypes: ["network", "bluetooth", "idleInhibitor", "easyEffects", "nightLight", "darkMode", "cloudflareWarp", "gameMode", "screenSnip", "colorPicker", "onScreenKeyboard", "mic", "audio", "notifications", "powerProfile"] readonly property list<string> 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 int columns: Config.options.sidebar.quickToggles.android.columns
readonly property list<var> toggles: Config.ready ? Config.options.sidebar.quickToggles.android.toggles : [] readonly property list<var> toggles: Config.ready ? Config.options.sidebar.quickToggles.android.toggles : []
readonly property list<var> toggleRows: toggleRowsForList(toggles) readonly property list<var> toggleRows: toggleRowsForList(toggles)
@@ -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);
}
}
}
}
}
@@ -232,4 +232,17 @@ DelegateChooser {
cellSize: modelData.size 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
} }
} }
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
COLOR_FILE_PATH="${XDG_STATE_HOME:-$HOME/.local/state}/quickshell/user/generated/color.txt"
# Define an array of possible VSCode settings file paths for various forks
settings_paths=(
"${XDG_CONFIG_HOME:-$HOME/.config}/Code/User/settings.json"
"${XDG_CONFIG_HOME:-$HOME/.config}/VSCodium/User/settings.json"
"${XDG_CONFIG_HOME:-$HOME/.config}/Code - OSS/User/settings.json"
"${XDG_CONFIG_HOME:-$HOME/.config}/Code - Insiders/User/settings.json"
"${XDG_CONFIG_HOME:-$HOME/.config}/Cursor/User/settings.json"
# Add more paths as needed for other forks
)
new_color=$(cat "$COLOR_FILE_PATH")
# Loop through each settings file path
for CODE_SETTINGS_PATH in "${settings_paths[@]}"; do
if [[ -f "$CODE_SETTINGS_PATH" ]]; then
# Try to update the key if it exists
if grep -q '"material-code.primaryColor"' "$CODE_SETTINGS_PATH"; then
sed -i -E \
"s/(\"material-code.primaryColor\"\s*:\s*\")[^\"]*(\")/\1${new_color}\2/" \
"$CODE_SETTINGS_PATH"
else # If the key is not already there, add it
sed -i '$ s/}/,\n "material-code.primaryColor": "'${new_color}'"\n}/' "$CODE_SETTINGS_PATH"
sed -i '$ s/,\n,/,/' "$CODE_SETTINGS_PATH"
fi
fi
done
@@ -55,18 +55,8 @@ post_process() {
local screen_height="$2" local screen_height="$2"
local wallpaper_path="$3" local wallpaper_path="$3"
handle_kde_material_you_colors & handle_kde_material_you_colors &
"$SCRIPT_DIR/code/material-code-set-color.sh" &
# Determine the largest region on the wallpaper that's sufficiently un-busy to put widgets in
# if [ ! -f "$MATUGEN_DIR/scripts/least_busy_region.py" ]; then
# echo "Error: least_busy_region.py script not found in $MATUGEN_DIR/scripts/"
# else
# "$MATUGEN_DIR/scripts/least_busy_region.py" \
# --screen-width "$screen_width" --screen-height "$screen_height" \
# --width 300 --height 200 \
# "$wallpaper_path" > "$STATE_DIR"/user/generated/wallpaper/least_busy_region.json
# fi
} }
check_and_prompt_upscale() { check_and_prompt_upscale() {
@@ -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
+69
View File
@@ -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 <value> 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
@@ -769,6 +769,18 @@ Singleton {
root.pendingFilePath = CF.FileUtils.trimFileProtocol(filePath); root.pendingFilePath = CF.FileUtils.trimFileProtocol(filePath);
} }
function regenerate(messageIndex) {
if (messageIndex < 0 || messageIndex >= messageIDs.length) return;
const id = root.messageIDs[messageIndex];
const message = root.messageByID[id];
if (message.role !== "assistant") return;
// Remove all messages after this one
for (let i = root.messageIDs.length - 1; i >= messageIndex; i--) {
root.removeMessage(i);
}
requester.makeRequest();
}
function createFunctionOutputMessage(name, output, includeOutputInChat = true) { function createFunctionOutputMessage(name, output, includeOutputInChat = true) {
return aiMessageComponent.createObject(root, { return aiMessageComponent.createObject(root, {
"role": "user", "role": "user",
@@ -29,12 +29,18 @@ Singleton {
property real lastVolume: 0 property real lastVolume: 0
function onVolumeChanged() { function onVolumeChanged() {
if (!Config.options.audio.protection.enable) return; if (!Config.options.audio.protection.enable) return;
const newVolume = sink.audio.volume;
// when resuming from suspend, we should not write volume to avoid pipewire volume reset issues
if (isNaN(newVolume) || newVolume === undefined || newVolume === null) {
lastReady = false;
lastVolume = 0;
return;
}
if (!lastReady) { if (!lastReady) {
lastVolume = sink.audio.volume; lastVolume = newVolume;
lastReady = true; lastReady = true;
return; return;
} }
const newVolume = sink.audio.volume;
const maxAllowedIncrease = Config.options.audio.protection.maxAllowedIncrease / 100; const maxAllowedIncrease = Config.options.audio.protection.maxAllowedIncrease / 100;
const maxAllowed = Config.options.audio.protection.maxAllowed / 100; const maxAllowed = Config.options.audio.protection.maxAllowed / 100;
@@ -45,9 +51,6 @@ Singleton {
root.sinkProtectionTriggered(Translation.tr("Exceeded max allowed")); root.sinkProtectionTriggered(Translation.tr("Exceeded max allowed"));
sink.audio.volume = Math.min(lastVolume, maxAllowed); sink.audio.volume = Math.min(lastVolume, maxAllowed);
} }
if (sink.ready && (isNaN(sink.audio.volume) || sink.audio.volume === undefined || sink.audio.volume === null)) {
sink.audio.volume = 0;
}
lastVolume = sink.audio.volume; lastVolume = sink.audio.volume;
} }
} }
@@ -16,8 +16,6 @@ import QtQuick
*/ */
Singleton { Singleton {
id: root id: root
property real minimumBrightnessAllowed: 0.00001 // Setting to 0 would kind of turn off the screen. We don't want that.
signal brightnessChanged() signal brightnessChanged()
property var ddcMonitors: [] property var ddcMonitors: []
@@ -137,14 +135,14 @@ Singleton {
} }
function syncBrightness() { function syncBrightness() {
const brightnessValue = Math.max(monitor.multipliedBrightness, root.minimumBrightnessAllowed) const brightnessValue = Math.max(monitor.multipliedBrightness, 0)
const rounded = Math.round(brightnessValue * monitor.rawMaxBrightness); const rawValueRounded = Math.max(Math.floor(brightnessValue * monitor.rawMaxBrightness), 1);
setProc.command = isDdc ? ["ddcutil", "-b", busNum, "setvcp", "10", rounded] : ["brightnessctl", "--class", "backlight", "s", rounded, "--quiet"]; setProc.command = isDdc ? ["ddcutil", "-b", busNum, "setvcp", "10", rawValueRounded] : ["brightnessctl", "--class", "backlight", "s", rawValueRounded, "--quiet"];
setProc.startDetached(); setProc.startDetached();
} }
function setBrightness(value: real): void { 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; monitor.brightness = value;
} }
+18 -3
View File
@@ -10,6 +10,7 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Window import QtQuick.Window
import Quickshell
import qs.services import qs.services
import qs.modules.common import qs.modules.common
import qs.modules.common.widgets import qs.modules.common.widgets
@@ -169,15 +170,29 @@ ApplicationWindow {
FloatingActionButton { FloatingActionButton {
id: fab id: fab
iconText: "edit" property bool justCopied: false
buttonText: Translation.tr("Config file") iconText: justCopied ? "check" : "edit"
buttonText: justCopied ? Translation.tr("Path copied") : Translation.tr("Config file")
expanded: navRail.expanded expanded: navRail.expanded
downAction: () => { downAction: () => {
Qt.openUrlExternally(`${Directories.config}/illogical-impulse/config.json`); 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 { 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")
} }
} }
@@ -1,20 +1,22 @@
pkgname=illogical-impulse-basic pkgname=illogical-impulse-basic
pkgver=1.0 pkgver=1.0
pkgrel=1 pkgrel=2
pkgdesc='Illogical Impulse Basic Dependencies' pkgdesc='Illogical Impulse Basic Dependencies'
arch=(any) arch=(any)
license=(None) license=(None)
depends=( depends=(
axel axel
bc bc
coreutils coreutils
cliphist cliphist
cmake cmake
curl curl
rsync wget
wget ripgrep
ripgrep jq
jq meson
meson xdg-user-dirs
xdg-user-dirs # Used in install script
rsync
go-yq # https://github.com/mikefarah/yq
) )
@@ -25,4 +25,5 @@ RDEPEND="
dev-python/jq dev-python/jq
dev-build/meson dev-build/meson
x11-misc/xdg-user-dirs x11-misc/xdg-user-dirs
app-misc/yq-go
" "
+2 -1
View File
@@ -24,6 +24,7 @@ printf "${STY_RST}"
pause pause
x sudo emerge --noreplace --quiet app-eselect/eselect-repository 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 if [[ -z $(eselect repository list | grep localrepo) ]]; then
v sudo eselect repository create localrepo 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 # Update system
v sudo emerge --sync v sudo emerge --sync
v sudo emerge --quiet --newuse --update --deep @world v sudo emerge --quiet --newuse --update --deep @world
v sudo emerge --quiet @smart-live-rebuild
v sudo emerge --depclean v sudo emerge --depclean
# Remove old ebuilds (if this isn't done the wildcard will fuck upon a version change) # 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 v sudo ebuild ${ebuild_dir}/dev-util/hyprwayland-scanner/hyprwayland-scanner*9999.ebuild digest
###### LIVE EBUILDS END ###### LIVE EBUILDS END
# Install dependencies # Install dependencies
for i in "${metapkgs[@]}"; do for i in "${metapkgs[@]}"; do
x sudo mkdir -p ${ebuild_dir}/app-misc/${i} x sudo mkdir -p ${ebuild_dir}/app-misc/${i}
+127
View File
@@ -2,3 +2,130 @@
# It's not for directly running. # It's not for directly running.
# This file is currently WIP. # This file is currently WIP.
function install_home-manager(){
# https://nix-community.github.io/home-manager/index.xhtml#sec-install-standalone
local cmd=home-manager
# Maybe installed already, just not sourced yet
try source $HOME/.nix-profile/etc/profile.d/hm-session-vars.sh
command -v $cmd && return
x nix-channel --add https://nixos.org/channels/nixos-25.05 nixpkgs-home
x nix-channel --add https://github.com/nix-community/home-manager/archive/release-25.05.tar.gz home-manager
x nix-channel --update
x env NIX_PATH="nixpkgs=$HOME/.nix-defexpr/channels/nixpkgs-home" nix-shell '<home-manager>' -A install
command -v $cmd && return
echo "Failed in installing $cmd."
echo "Please install it by yourself and then retry."
return 1
}
function install_nix(){
# https://github.com/NixOS/experimental-nix-installer
local cmd=nix
x mkdir -p ${REPO_ROOT}/cache
x curl -JLo ${REPO_ROOT}/cache/nix-installer https://artifacts.nixos.org/experimental-installer
x sh ${REPO_ROOT}/cache/nix-installer install
try source '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'
command -v $cmd && return
echo "Failed in installing $cmd."
echo "Please install it by yourself and then retry."
return 1
}
function install_curl(){
local cmd=curl
if [[ "$OS_DISTRO_ID" == "arch" || "$OS_DISTRO_ID_LIKE" == "arch" || "$OS_DISTRO_ID" == "cachyos" ]]; then
x sudo pacman -Syu
x sudo pacman -S --noconfirm $cmd
elif [[ "$OS_DISTRO_ID" == "debian" || "$OS_DISTRO_ID_LIKE" == "debian" ]]; then
x sudo apt update
x sudo apt install $cmd
fi
command -v $cmd && return
echo "Failed in installing $cmd."
echo "Please install it by yourself and then retry."
return 1
}
function install_zsh(){
local cmd=zsh
if [[ "$OS_DISTRO_ID" == "arch" || "$OS_DISTRO_ID_LIKE" == "arch" || "$OS_DISTRO_ID" == "cachyos" ]]; then
x sudo pacman -Syu
x sudo pacman -S --noconfirm $cmd
elif [[ "$OS_DISTRO_ID" == "debian" || "$OS_DISTRO_ID_LIKE" == "debian" ]]; then
x sudo apt update
x sudo apt install $cmd
fi
command -v $cmd && return
echo "Failed in installing $cmd."
echo "Please install it by yourself and then retry."
return 1
}
function install_swaylock(){
local cmd=swaylock
echo "Detecting command \"$cmd\"..."
command -v $cmd && return
echo "Command \"$cmd\" not found, try to install..."
if [[ "$OS_DISTRO_ID" == "arch" || "$OS_DISTRO_ID_LIKE" == "arch" || "$OS_DISTRO_ID" == "cachyos" ]]; then
x sudo pacman -Syu
x sudo pacman -S --noconfirm $cmd
elif [[ "$OS_DISTRO_ID" == "debian" || "$OS_DISTRO_ID_LIKE" == "debian" ]]; then
x sudo apt update
x sudo apt install $cmd
fi
command -v $cmd && return
echo "Failed in installing $cmd."
echo "Please install it by yourself and then retry."
return 1
}
function hm_deps(){
SETUP_HM_DIR="${REPO_ROOT}/sdata/dist-nix/home-manager"
SETUP_USERNAME_NIXFILE="${SETUP_HM_DIR}/username.nix"
echo "\"$(whoami)\"" > "${SETUP_USERNAME_NIXFILE}"
x git add "${SETUP_USERNAME_NIXFILE}"
cd $SETUP_HM_DIR
x home-manager switch --flake .#illogical_impulse \
--extra-experimental-features nix-command \
--extra-experimental-features flakes
cd $REPO_ROOT
x git 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
+73 -1
View File
@@ -69,7 +69,11 @@ function pause(){
fi fi
} }
function remove_bashcomments_emptylines(){ function remove_bashcomments_emptylines(){
mkdir -p "$(dirname "$2")" && cat "$1" | sed -e 's/#.*//' -e '/^[[:space:]]*$/d' > "$2" echo "pwd=$(pwd)"
echo "input=$1"
echo "output=$2"
mkdir -p "$(dirname "$2")"
cat "$1" | sed -e 's/#.*//' -e '/^[[:space:]]*$/d' > "$2"
} }
function prevent_sudo_or_root(){ function prevent_sudo_or_root(){
case $(whoami) in case $(whoami) in
@@ -287,3 +291,71 @@ function check_disk_space() {
return 0 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/"
}
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Check whether pkgs exist in AUR or repos of Arch.
#
# Do NOT abuse this since it consumes extra bandwidth from AUR server.
pkglistfile=$(mktemp)
pkglistfile_orig=${LIST_FILE_PATH}
pkglistfile_orig_s=${REPO_ROOT}/cache/dependencies_stripped.conf
#if ! "$(command -v curl)";then
# echo "Please install curl first.";exit 1
#fi
#if ! "$(command -v gzip)";then
# echo "Please install gzip first.";exit 1
#fi
#if ! "$(command -v pacman)";then
# echo "pacman not found, aborting...";exit 1
#fi
remove_bashcomments_emptylines $pkglistfile_orig $pkglistfile_orig_s
cat $pkglistfile_orig_s | sed "s_\ _\n_g" > $pkglistfile
echo "The non-existent pkgs in $pkglistfile_orig are listed as follows."
# Borrowed from https://bbs.archlinux.org/viewtopic.php?pid=1490795#p1490795
comm -23 <(sort -u $pkglistfile) <(sort -u <(curl https://aur.archlinux.org/packages.gz | gzip -cd | sort) <(pacman -Ssq))
echo "End of list. If nothing appears, then all pkgs exist."
rm $pkglistfile
+33
View File
@@ -0,0 +1,33 @@
# Handle args for subcmd: checkdeps
# shellcheck shell=bash
showhelp(){
echo -e "Syntax: $0 checkdeps [OPTIONS] <LIST_FILE_PATH>...
Check whether pkgs listed in <LIST_FILE_PATH> exist in AUR or repos of Arch.
Options:
-h, --help Show this help message
"
}
# `man getopt` to see more
para=$(getopt \
-o h \
-l help \
-n "$0" -- "$@")
[ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1
#####################################################################################
eval set -- "$para"
while true ; do
case "$1" in
-h|--help) showhelp;exit;;
--) shift;break ;;
*) sleep 0 ;;
esac
done
if [[ -f "$1" ]]; then
echo "Using list file \"$1\".";LIST_FILE_PATH="$1";shift 1
else
echo "Wrong path \"$1\" of list file.";exit 1
fi
@@ -541,6 +541,7 @@ if git remote get-url origin &>/dev/null; then
log_info "Pulling changes from origin/$current_branch..." log_info "Pulling changes from origin/$current_branch..."
if git pull; then if git pull; then
log_success "Successfully pulled latest changes" log_success "Successfully pulled latest changes"
git submodule update --init --recursive
else else
log_warning "Failed to pull changes from remote. Continuing with local repository..." log_warning "Failed to pull changes from remote. Continuing with local repository..."
log_info "You may need to resolve conflicts manually later." log_info "You may need to resolve conflicts manually later."
@@ -3,6 +3,16 @@
# shellcheck shell=bash # 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 # exp-update.sh - Enhanced dotfiles update script
@@ -834,6 +844,7 @@ if git remote get-url origin &>/dev/null; then
else else
if git pull --ff-only; then if git pull --ff-only; then
log_success "Successfully pulled latest changes" log_success "Successfully pulled latest changes"
git submodule update --init --recursive
# Verify we actually got new commits # Verify we actually got new commits
if git rev-parse --verify HEAD@{1} &>/dev/null; then if git rev-parse --verify HEAD@{1} &>/dev/null; then
if [[ "$(git rev-parse HEAD)" == "$(git rev-parse HEAD@{1})" ]]; then if [[ "$(git rev-parse HEAD)" == "$(git rev-parse HEAD@{1})" ]]; then
@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/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 set -euo pipefail
@@ -129,7 +129,7 @@ log_header() { :; }
log_die() { echo "ERROR: \$1"; exit 1; } log_die() { echo "ERROR: \$1"; exit 1; }
STY_CYAN="" STY_RST="" STY_YELLOW="" 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 SKIP_NOTICE=true
REPO_ROOT="\$1" REPO_ROOT="\$1"
CHECK_PACKAGES=false CHECK_PACKAGES=false
@@ -139,7 +139,7 @@ VERBOSE=false
NON_INTERACTIVE=true NON_INTERACTIVE=true
SOURCE_ONLY=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) detected_dirs=\$(detect_repo_structure)
if [[ -n "\$detected_dirs" ]]; then if [[ -n "\$detected_dirs" ]]; then
read -ra MONITOR_DIRS <<<"\$detected_dirs" read -ra MONITOR_DIRS <<<"\$detected_dirs"
@@ -190,7 +190,7 @@ log_success() { :; }
log_header() { :; } log_header() { :; }
log_die() { echo "ERROR: \$1"; exit 1; } 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 SKIP_NOTICE=true
REPO_ROOT="\$1" REPO_ROOT="\$1"
CHECK_PACKAGES=false CHECK_PACKAGES=false
@@ -200,7 +200,7 @@ VERBOSE=false
NON_INTERACTIVE=true NON_INTERACTIVE=true
SOURCE_ONLY=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) detected_dirs=\$(detect_repo_structure)
if [[ -n "\$detected_dirs" ]]; then if [[ -n "\$detected_dirs" ]]; then
read -ra MONITOR_DIRS <<<"\$detected_dirs" read -ra MONITOR_DIRS <<<"\$detected_dirs"
@@ -276,7 +276,7 @@ log_success() { :; }
log_header() { :; } log_header() { :; }
log_die() { echo "ERROR: \$1" >&2; exit 1; } 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" REPO_ROOT="\$1"
export REPO_ROOT export REPO_ROOT
@@ -293,7 +293,7 @@ HOME_UPDATE_IGNORE_FILE="/dev/null"
# Source the production script to use the real should_ignore function # Source the production script to use the real should_ignore function
# Redirect all unwanted output to stderr, then to /dev/null # 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=( test_cases=(
"\$REPO_ROOT/app.log:0" "\$REPO_ROOT/app.log:0"
@@ -348,7 +348,7 @@ test_safe_read_security() {
log_test "Testing safe_read uses secure assignment (printf -v)" log_test "Testing safe_read uses secure assignment (printf -v)"
local safe_read_function 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 if [[ -z "$safe_read_function" ]]; then
log_fail "Could not find safe_read function" log_fail "Could not find safe_read function"
@@ -547,7 +547,7 @@ log_success() { :; }
log_header() { :; } log_header() { :; }
log_die() { echo "ERROR: \$1" >&2; exit 1; } 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" REPO_ROOT="\$1"
export REPO_ROOT export REPO_ROOT
@@ -563,7 +563,7 @@ UPDATE_IGNORE_FILE="\${REPO_ROOT}/.updateignore"
HOME_UPDATE_IGNORE_FILE="/dev/null" HOME_UPDATE_IGNORE_FILE="/dev/null"
# Source the production script to use the real should_ignore function # 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 patterns into cache
load_ignore_patterns load_ignore_patterns
@@ -649,7 +649,7 @@ VERBOSE=false
NON_INTERACTIVE=true NON_INTERACTIVE=true
SOURCE_ONLY=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-\$\$" test_dir="/tmp/test-ensure-dir-\$\$"
@@ -9,10 +9,8 @@ printf "${STY_CYAN}[$0]: Hi there! Before we start:${STY_RST}\n"
printf "\n" printf "\n"
printf "${STY_PURPLE}${STY_BOLD}[NEW] illogical-impulse is now powered by Quickshell.${STY_RST}\n" printf "${STY_PURPLE}${STY_BOLD}[NEW] illogical-impulse is now powered by Quickshell.${STY_RST}\n"
printf "${STY_PURPLE}" printf "${STY_PURPLE}"
printf '# NOTE: illogical-impulse on AGS is no longer supported.\n'
printf '# If you were using the old version with AGS and would like to keep it, do not run this script.\n' printf '# If you were using the old version with AGS and would like to keep it, do not run this script.\n'
printf '# The AGS version, although uses less memory, has much worse performance (it uses Gtk3). \n'
printf '# If you aren'\''t running on ewaste, the Quickshell version is recommended. \n'
printf "# If you would like the AGS version anyway, run the following to switch to its branch first:\n ${STY_INVERT} git checkout ii-ags && ./install.sh ${STY_RST}\n"
printf "\n" printf "\n"
pause pause
printf "${STY_CYAN}${STY_BOLD}Quick overview about what this script does:${STY_RST}\n" printf "${STY_CYAN}${STY_BOLD}Quick overview about what this script does:${STY_RST}\n"
@@ -1,9 +1,10 @@
# This script is meant to be sourced. # This script is meant to be sourced.
# It's not for directly running. # It's not for directly running.
printf "${STY_CYAN}[$0]: 1. Install dependencies\n${STY_RST}"
function outdate_detect(){ function outdate_detect(){
# Shallow clone prevent latest_commit_timestamp() from working. # Shallow clone prevent latest_commit_timestamp() from working.
v git_auto_unshallow x git_auto_unshallow
local source_path="$1" local source_path="$1"
local target_path="$2" local target_path="$2"
@@ -1,5 +1,6 @@
# This script is meant to be sourced. # This script is meant to be sourced.
# It's not for directly running. # It's not for directly running.
printf "${STY_CYAN}[$0]: 2. Setup for permissions/services etc\n${STY_RST}"
# shellcheck shell=bash # shellcheck shell=bash
+240
View File
@@ -0,0 +1,240 @@
# This script is meant to be sourced.
# It's not for directly running.
# TODO: https://github.com/end-4/dots-hyprland/issues/2137
printf "${STY_CYAN}[$0]: 3. Copying config files (experimental YAML-based)${STY_RST}\n"
# Configuration file
CONFIG_FILE="sdata/subcmd-install/3.files.yaml"
# =============================================================================
# ORIGINAL FUNCTIONS
# =============================================================================
function warning_rsync_delete(){
printf "${STY_YELLOW}"
printf "The command below uses --delete for rsync which overwrites the destination folder.\n"
printf "${STY_RST}"
}
function warning_rsync_normal(){
printf "${STY_YELLOW}"
printf "The command below uses rsync which overwrites the destination.\n"
printf "${STY_RST}"
}
function backup_configs(){
backup_clashing_targets dots/.config $XDG_CONFIG_HOME "${BACKUP_DIR}/.config"
backup_clashing_targets dots/.local/share $XDG_DATA_HOME "${BACKUP_DIR}/.local/share"
printf "${STY_BLUE}Backup into \"${BACKUP_DIR}\" finished.${STY_RST}\n"
}
function ask_backup_configs(){
showfun backup_clashing_targets
printf "${STY_RED}"
printf "Would you like to backup clashing dirs/files under \"$XDG_CONFIG_HOME\" and \"$XDG_DATA_HOME\" to \"$BACKUP_DIR\"?"
printf "${STY_RST}"
while true;do
echo " y = Yes, backup"
echo " n/s = No, skip to next"
local p; read -p "====> " p
case $p in
[yY]) echo -e "${STY_BLUE}OK, doing backup...${STY_RST}" ;local backup=true;break ;;
[nNsS]) echo -e "${STY_BLUE}Alright, skipping...${STY_RST}" ;local backup=false;break ;;
*) echo -e "${STY_RED}Please enter [y/n].${STY_RST}";;
esac
done
if $backup;then backup_configs;fi
}
function auto_backup_configs(){
# Backup when $BACKUP_DIR does not exist
if [[ ! -d "$BACKUP_DIR" ]]; then backup_configs;fi
}
#####################################################################################
showfun auto_get_git_submodule
v auto_get_git_submodule
# In case some dirs does not exists
v mkdir -p $XDG_BIN_HOME $XDG_CACHE_HOME $XDG_CONFIG_HOME $XDG_DATA_HOME/icons
if [[ ! "${SKIP_BACKUP}" == true ]]; then
case $ask in
false) auto_backup_configs ;;
*) ask_backup_configs ;;
esac
fi
# Run user preference wizard
case $ask in
false) sleep 0 ;;
*) wizard_update_preferences ;;
esac
# Read patterns from YAML file
readarray patterns < <(yq -o=j -I=0 '.patterns[]' "$CONFIG_FILE")
# Process each pattern
for pattern in "${patterns[@]}"; do
from=$(echo "$pattern" | yq '.from' - | envsubst)
to=$(echo "$pattern" | yq '.to' - | envsubst)
mode=$(echo "$pattern" | yq '.mode' - | envsubst)
condition=$(echo "$pattern" | yq '.condition // "true"')
# Handle fontconfig fontset override
# If 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
@@ -1,84 +1,64 @@
# This script is meant to be sourced. # This script is meant to be sourced.
# It's not for directly running. # It's not for directly running.
printf "${STY_CYAN}[$0]: 3. Copying config files\n${STY_RST}"
# shellcheck shell=bash # shellcheck shell=bash
# TODO: https://github.com/end-4/dots-hyprland/issues/2137 # TODO: https://github.com/end-4/dots-hyprland/issues/2137
function warning_rsync(){ function warning_rsync_delete(){
printf "${STY_YELLOW}" 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}" printf "${STY_RST}"
} }
function backup_clashing_targets(){ function warning_rsync_normal(){
# For dirs/files under target_dir, only backup those which clashes with the ones under source_dir printf "${STY_YELLOW}"
printf "The command below uses rsync which overwrites the destination.\n"
printf "${STY_RST}"
}
# Deal with arguments function backup_configs(){
local source_dir="$1" backup_clashing_targets dots/.config $XDG_CONFIG_HOME "${BACKUP_DIR}/.config"
local target_dir="$2" backup_clashing_targets dots/.local/share $XDG_DATA_HOME "${BACKUP_DIR}/.local/share"
local backup_dir="$3" printf "${STY_BLUE}Backup into \"${BACKUP_DIR}\" finished.${STY_RST}\n"
# Find clash dirs/files, save as clash_list
local clash_list=()
local source_list=($(ls -A "$source_dir"))
local target_list=($(ls -A "$target_dir"))
declare -A target_map
for i in "${target_list[@]}"; do
target_map["$i"]=1
done
for i in "${source_list[@]}"; do
if [[ -n "${target_map[$i]}" ]]; then
clash_list+=("$i")
fi
done
# Construct args_includes for rsync
local args_includes=()
for i in "${clash_list[@]}"; do
if [[ -d "$target_dir/$i" ]]; then
args_includes+=(--include="/$i/")
args_includes+=(--include="/$i/**")
else
args_includes+=(--include="/$i")
fi
done
args_includes+=(--exclude='*')
x mkdir -p $backup_dir
x rsync -av --progress "${args_includes[@]}" "$target_dir/" "$backup_dir/"
} }
function ask_backup_configs(){ function ask_backup_configs(){
showfun backup_clashing_targets showfun backup_clashing_targets
printf "${STY_RED}" printf "${STY_RED}"
printf "Would you like to backup clashing dirs/files under \"$XDG_CONFIG_HOME\" and \"$XDG_DATA_HOME\" to \"$BACKUP_DIR\"?" printf "Would you like to backup clashing dirs/files under \"$XDG_CONFIG_HOME\" and \"$XDG_DATA_HOME\" to \"$BACKUP_DIR\"?\n"
printf "${STY_RST}" printf "${STY_RST}"
while true;do while true;do
echo " y = Yes, backup" echo " y = Yes, backup"
echo " n = No, skip to next" echo " n/s = No, skip to next"
local p; read -p "====> " p local p; read -p "====> " p
case $p in case $p in
[yY]) echo -e "${STY_BLUE}OK, doing backup...${STY_RST}" ;local backup=true;break ;; [yY]) echo -e "${STY_BLUE}OK, doing backup...${STY_RST}" ;local backup=true;break ;;
[nN]) echo -e "${STY_BLUE}Alright, skipping...${STY_RST}" ;local backup=false;break ;; [nNsS]) echo -e "${STY_BLUE}Alright, skipping...${STY_RST}" ;local backup=false;break ;;
*) echo -e "${STY_RED}Please enter [y/n].${STY_RST}";; *) echo -e "${STY_RED}Please enter [y/n].${STY_RST}";;
esac esac
done done
if $backup;then if $backup;then backup_configs;fi
backup_clashing_targets dots/.config $XDG_CONFIG_HOME "${BACKUP_DIR}/.config" }
backup_clashing_targets dots/.local/share $XDG_DATA_HOME "${BACKUP_DIR}/.local/share" function auto_backup_configs(){
fi # 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 # 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 if [[ ! "${SKIP_BACKUP}" == true ]]; then
false) sleep 0 ;; case $ask in
*) ask_backup_configs ;; false) auto_backup_configs ;;
esac *) ask_backup_configs ;;
esac
fi
# TODO: A better method for users to choose their customization, # TODO: A better method for users to choose their customization,
# for example some users may prefer ZSH over FISH, and foot over kitty. # 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 # original dotfiles and new ones in the SAME DIRECTORY
# (eg. in ~/.config/hypr) won't be mixed together # (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 case $SKIP_MISCCONF in
true) sleep 0;; 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" # i="dots/.config/$i"
echo "[$0]: Found target: 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/" 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; v rsync -av "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 fi
done done
warning_rsync_delete; v rsync -av "dots/.local/share/konsole/" "${XDG_DATA_HOME:-$HOME/.local/share}"/konsole/
;; ;;
esac esac
case $SKIP_QUICKSHELL in case $SKIP_QUICKSHELL in
true) sleep 0;; 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 esac
case $SKIP_FISH in case $SKIP_FISH in
true) sleep 0;; 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 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 # For Hyprland
declare -a arg_excludes=() declare -a arg_excludes=()
arg_excludes+=(--exclude '/custom') arg_excludes+=(--exclude '/custom')
@@ -127,17 +118,25 @@ arg_excludes+=(--exclude '/hyprland.conf')
case $SKIP_HYPRLAND in case $SKIP_HYPRLAND in
true) sleep 0;; 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" t="$XDG_CONFIG_HOME/hypr/hyprland.conf"
if [ -f $t ];then if [ -f $t ];then
echo -e "${STY_BLUE}[$0]: \"$t\" already exists.${STY_RST}" echo -e "${STY_BLUE}[$0]: \"$t\" already exists.${STY_RST}"
v mv $t $t.old if $ii_firstrun;then
v cp -f dots/.config/hypr/hyprland.conf $t echo -e "${STY_BLUE}[$0]: It seems to be the firstrun.${STY_RST}"
existed_hypr_conf_firstrun=y v mv $t $t.old
v cp -f dots/.config/hypr/hyprland.conf $t
existed_hypr_conf_firstrun=y
else
echo -e "${STY_BLUE}[$0]: It seems not a firstrun.${STY_RST}"
v cp -f dots/.config/hypr/hyprland.conf $t.new
existed_hypr_conf=y
fi
else else
echo -e "${STY_YELLOW}[$0]: \"$t\" does not exist yet.${STY_RST}" echo -e "${STY_YELLOW}[$0]: \"$t\" does not exist yet.${STY_RST}"
v cp dots/.config/hypr/hyprland.conf $t v cp dots/.config/hypr/hyprland.conf $t
existed_hypr_conf=n
fi fi
t="$XDG_CONFIG_HOME/hypr/hypridle.conf" t="$XDG_CONFIG_HOME/hypr/hypridle.conf"
if [ -f $t ];then if [ -f $t ];then
@@ -164,7 +163,7 @@ case $SKIP_HYPRLAND in
echo -e "${STY_BLUE}[$0]: \"$t\" already exists, will not do anything.${STY_RST}" echo -e "${STY_BLUE}[$0]: \"$t\" already exists, will not do anything.${STY_RST}"
else else
echo -e "${STY_YELLOW}[$0]: \"$t\" does not exist yet.${STY_RST}" echo -e "${STY_YELLOW}[$0]: \"$t\" does not exist yet.${STY_RST}"
warning_rsync; v rsync -av --delete dots/.config/hypr/custom/ $t/ v rsync -av --delete dots/.config/hypr/custom/ $t/
fi fi
;; ;;
esac esac
@@ -174,8 +173,7 @@ declare -a arg_excludes=()
# some foldes (eg. .local/bin) should be processed separately to avoid `--delete' for rsync, # 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. # 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 # 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/ v cp -f "dots/.local/share/icons/illogical-impulse.svg" "${XDG_DATA_HOME}"/icons/illogical-impulse.svg
warning_rsync; v rsync -av "dots/.local/share/konsole/" "${XDG_DATA_HOME:-$HOME/.local/share}"/konsole/
# Prevent hyprland from not fully loaded # Prevent hyprland from not fully loaded
sleep 1 sleep 1
+116
View File
@@ -0,0 +1,116 @@
version: "1.0"
user_preferences:
shell: "fish" # fish | zsh
terminal: "foot" # kitty | foot
keybindings: "default" # default | vim
patterns:
# Always install these files
- from: "dots/.config/quickshell"
to: "$XDG_CONFIG_HOME/quickshell"
mode: "sync"
# Conditionally install these files
- from: "dots/.config/fish"
to: "$XDG_CONFIG_HOME/fish"
mode: "sync"
condition:
type: "shell"
value: "fish"
- from: "dots/.config/zshrc.d"
to: "$XDG_CONFIG_HOME/zshrc.d"
mode: "sync"
condition:
type: "shell"
value: "zsh"
- from: "dots/.config/foot"
to: "$XDG_CONFIG_HOME/foot"
mode: "sync"
condition:
type: "terminal"
value: "foot"
- from: "dots/.config/kitty"
to: "$XDG_CONFIG_HOME/kitty"
mode: "sync"
condition:
type: "terminal"
value: "kitty"
# Hyprland
- from: "dots/.config/hypr"
to: "$XDG_CONFIG_HOME/hypr"
mode: "sync"
excludes: ["custom", "hyprlock.conf", "hypridle.conf", "hyprland.conf"]
# Hyprland special files
- from: "dots/.config/hypr/hyprland.conf"
to: "$XDG_CONFIG_HOME/hypr/hyprland.conf"
mode: "hard-backup"
- from: "dots/.config/hypr/hypridle.conf"
to: "$XDG_CONFIG_HOME/hypr/hypridle.conf"
mode: "soft-backup"
- from: "dots/.config/hypr/hyprlock.conf"
to: "$XDG_CONFIG_HOME/hypr/hyprlock.conf"
mode: "soft-backup"
- from: "dots/.config/hypr/custom"
to: "$XDG_CONFIG_HOME/hypr/custom"
mode: "skip-if-exists"
- from: "dots/.local/share/icons"
to: "$XDG_DATA_HOME/icons"
mode: "soft"
- from: "dots/.local/share/konsole"
to: "$XDG_DATA_HOME/konsole"
mode: "soft"
# Fontconfig (default - fontsets handled separately if 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"
@@ -14,14 +14,18 @@ Options for install:
--skip-allsetups Skip the whole process setting up permissions/services etc --skip-allsetups Skip the whole process setting up permissions/services etc
--skip-allfiles Skip the whole process copying configuration files --skip-allfiles Skip the whole process copying configuration files
-s, --skip-sysupdate Skip system package upgrade e.g. \"sudo pacman -Syu\" -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-quickshell Skip installing the config for Quickshell
--skip-hyprland Skip installing the config for Hyprland --skip-hyprland Skip installing the config for Hyprland
--skip-fish Skip installing the config for Fish --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 --skip-miscconf Skip copying the dirs and files to \".configs\" except for
Quickshell, Fish and Hyprland Quickshell, Fish and Hyprland
--core Alias of --skip-{plasmaintg,fish,miscconf,fontconfig}
--exp-files Use experimental script for the third step copying files --exp-files Use experimental script for the third step copying files
--fontset <set> (Unavailable yet) Use a set of pre-defined font and config --fontset <set> Use a set of pre-defined font and config (currently only fontconfig).
Possible values of <set>: $(ls -A ${REPO_ROOT}/dots-extra/fontsets)
--via-nix (Unavailable yet) Use Nix to install dependencies --via-nix (Unavailable yet) Use Nix to install dependencies
" "
} }
@@ -33,7 +37,7 @@ cleancache(){
# `man getopt` to see more # `man getopt` to see more
para=$(getopt \ para=$(getopt \
-o hfk:cs \ -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" -- "$@") -n "$0" -- "$@")
[ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1 [ $? != 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-allsetups) SKIP_ALLSETUPS=true;shift;;
--skip-allfiles) SKIP_ALLFILES=true;shift;; --skip-allfiles) SKIP_ALLFILES=true;shift;;
-s|--skip-sysupdate) SKIP_SYSUPDATE=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-hyprland) SKIP_HYPRLAND=true;shift;;
--skip-fish) SKIP_FISH=true;shift;; --skip-fish) SKIP_FISH=true;shift;;
--skip-quickshell) SKIP_QUICKSHELL=true;shift;; --skip-quickshell) SKIP_QUICKSHELL=true;shift;;
--skip-fontconfig) SKIP_FONTCONFIG=true;shift;;
--skip-miscconf) SKIP_MISCCONF=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;; --exp-files) EXPERIMENTAL_FILES_SCRIPT=true;shift;;
--via-nix) INSTALL_VIA_NIX=true;shift;; --via-nix) INSTALL_VIA_NIX=true;shift;;
## Ones with parameter ## Ones with parameter
--fontset) --fontset)
case $2 in if [[ -d "${REPO_ROOT}/dots-extra/fontsets/$2" ]];
"default"|"zh-CN"|"vi") fontset="$2";; then echo "Using fontset \"$2\".";II_FONTSET_NAME="$2";shift 2
*) echo -e "Wrong argument for $1.";exit 1;; else echo "Wrong argument for $1.";exit 1
esac;echo "The fontset is ${fontset}.";shift 2;; fi;;
## Ending ## Ending
--) break ;; --) break ;;
+30 -37
View File
@@ -24,6 +24,7 @@ Subcommands:
exp-uninstall (Experimental) Uninstall illogical-impulse. exp-uninstall (Experimental) Uninstall illogical-impulse.
exp-update (Experimental) Update illogical-impulse without fully reinstall. exp-update (Experimental) Update illogical-impulse without fully reinstall.
exp-update-old (Experimental) exp-update but use behaves like old version. exp-update-old (Experimental) exp-update but use behaves like old version.
checkdeps (For dev only) Check whether pkgs exist in AUR or repos of Arch.
help Show this help message. help Show this help message.
For each <subcommand>, use -h for details: For each <subcommand>, use -h for details:
@@ -34,75 +35,67 @@ case $1 in
# Global help # Global help
help|--help|-h)showhelp_global;exit;; help|--help|-h)showhelp_global;exit;;
# Correct subcommand # Correct subcommand
install|install-deps|install-setups|install-files|exp-uninstall|exp-update|exp-update-old) install|exp-uninstall|exp-update|exp-update-old|checkdeps)
SCRIPT_SUBCOMMAND=$1;shift;; SUBCMD_NAME=$1
# No subcommand SUBCMD_DIR=./sdata/subcmd-$1
-*|"")SCRIPT_SUBCOMMAND=install;; 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 # Wrong subcommand
*)printf "${STY_RED}Unknown subcommand \"$1\".${STY_RST}\n";showhelp_global;exit 1;; *)printf "${STY_RED}Unknown subcommand \"$1\".${STY_RST}\n";showhelp_global;exit 1;;
esac esac
##################################################################################### #####################################################################################
case ${SCRIPT_SUBCOMMAND} in if [[ -f "${SUBCMD_DIR}/options.sh" ]];
then source "${SUBCMD_DIR}/options.sh"
fi
case ${SUBCMD_NAME} in
install) install)
source ./sdata/options/install.sh
if [[ "${SKIP_ALLGREETING}" != true ]]; then if [[ "${SKIP_ALLGREETING}" != true ]]; then
source ./sdata/step/0.install-greeting.sh source ${SUBCMD_DIR}/0.greeting.sh
fi fi
if [[ "${SKIP_ALLDEPS}" != true ]]; then if [[ "${SKIP_ALLDEPS}" != true ]]; then
printf "${STY_CYAN}[$0]: 1. Install dependencies\n${STY_RST}" source ${SUBCMD_DIR}/1.deps-selector.sh
source ./sdata/step/1.install-deps-selector.sh
fi fi
if [[ "${SKIP_ALLSETUPS}" != true ]]; then if [[ "${SKIP_ALLSETUPS}" != true ]]; then
printf "${STY_CYAN}[$0]: 2. Setup for permissions/services etc\n${STY_RST}" source ${SUBCMD_DIR}/2.setups-selector.sh
source ./sdata/step/2.install-setups-selector.sh
fi fi
if [[ "${SKIP_ALLFILES}" != true ]]; then if [[ "${SKIP_ALLFILES}" != true ]]; then
printf "${STY_CYAN}[$0]: 3. Copying config files\n${STY_RST}"
if [[ "${EXPERIMENTAL_FILES_SCRIPT}" == true ]]; then if [[ "${EXPERIMENTAL_FILES_SCRIPT}" == true ]]; then
source ./sdata/step/3.install-files.experimental.sh source ${SUBCMD_DIR}/3.files-exp.sh
else else
source ./sdata/step/3.install-files.sh source ${SUBCMD_DIR}/3.files.sh
fi fi
fi fi
;; ;;
install-deps) install-deps)
source ./sdata/options/install.sh
if [[ "${SKIP_ALLDEPS}" != true ]]; then if [[ "${SKIP_ALLDEPS}" != true ]]; then
printf "${STY_CYAN}[$0]: 1. Install dependencies\n${STY_RST}" source ${SUBCMD_DIR}/1.deps-selector.sh
source ./sdata/step/1.install-deps-selector.sh
fi fi
;; ;;
install-setups) install-setups)
source ./sdata/options/install.sh
if [[ "${SKIP_ALLSETUPS}" != true ]]; then if [[ "${SKIP_ALLSETUPS}" != true ]]; then
printf "${STY_CYAN}[$0]: 2. Setup for permissions/services etc\n${STY_RST}" source ${SUBCMD_DIR}/2.setups-selector.sh
source ./sdata/step/2.install-setups-selector.sh
fi fi
;; ;;
install-files) install-files)
source ./sdata/options/install.sh
if [[ "${SKIP_ALLFILES}" != true ]]; then if [[ "${SKIP_ALLFILES}" != true ]]; then
printf "${STY_CYAN}[$0]: 3. Copying config files\n${STY_RST}"
if [[ "${EXPERIMENTAL_FILES_SCRIPT}" == true ]]; then if [[ "${EXPERIMENTAL_FILES_SCRIPT}" == true ]]; then
source ./sdata/step/3.install-files.experimental.sh source ${SUBCMD_DIR}/3.files-exp.sh
else else
source ./sdata/step/3.install-files.sh source ${SUBCMD_DIR}/3.files.sh
fi fi
fi fi
;; ;;
exp-uninstall) *)
source ./sdata/options/exp-uninstall.sh source ${SUBCMD_DIR}/0.run.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
exit exit
;; ;;
esac esac