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>
<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`
- 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:
+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
bindd = Super+Shift, C, Color picker, exec, hyprpicker -a # Pick color (Hex) >> clipboard
# Fullscreen screenshot
bindld = ,Print, Screenshot >> clipboard ,exec,grim - | wl-copy # Screenshot >> clipboard
bindld = Ctrl,Print, Screenshot >> clipboard & save, exec, mkdir -p $(xdg-user-dir PICTURES)/Screenshots && grim $(xdg-user-dir PICTURES)/Screenshots/Screenshot_"$(date '+%Y-%m-%d_%H.%M.%S')".png # Screenshot >> clipboard & file
bindl = ,Print,exec,grim - | wl-copy # Screenshot >> clipboard
bindln = Ctrl,Print, exec, mkdir -p $(xdg-user-dir PICTURES)/Screenshots && grim $(xdg-user-dir PICTURES)/Screenshots/Screenshot_"$(date '+%Y-%m-%d_%H.%M.%S')".png # Screenshot >> clipboard & file (file)
bindln = Ctrl,Print,exec,grim - | wl-copy # [hidden] Screenshot >> clipboard & file (clipboard)
# Recording stuff
bindl = Super+Alt, R, exec, ~/.config/hypr/hyprland/scripts/record.sh # Record region (no sound)
bindl = Ctrl+Alt, R, exec, ~/.config/hypr/hyprland/scripts/record.sh --fullscreen # [hidden] Record screen (no sound)
bindl = Super+Shift+Alt, R, exec, ~/.config/hypr/hyprland/scripts/record.sh --fullscreen-sound # Record screen (with sound)
bindl = Super+Shift, R, global, quickshell:regionRecord # Record region (no sound)
bindl = Super+Shift, R, exec, qs -c $qsConfig ipc call TEST_ALIVE || ~/.config/quickshell/$qsConfig/scripts/videos/record.sh # [hidden] Record region (no sound) (fallback)
bindl = Super+Alt, R, global, quickshell:regionRecord # [hidden] Record region (no sound)
bindl = Super+Alt, R, exec, qs -c $qsConfig ipc call TEST_ALIVE || ~/.config/quickshell/$qsConfig/scripts/videos/record.sh # [hidden] Record region (no sound) (fallback)
bindl = Ctrl+Alt, R, exec, ~/.config/quickshell/$qsConfig/scripts/videos/record.sh --fullscreen # [hidden] Record screen (no sound)
bindl = Super+Shift+Alt, R, exec, ~/.config/quickshell/$qsConfig/scripts/videos/record.sh --fullscreen --sound # Record screen (with sound)
# AI
bindd = Super+Shift+Alt, mouse:273, Generate AI summary for selected text, exec, ~/.config/hypr/hyprland/scripts/ai/primary-buffer-query.sh # AI summary for selected text
bindd = Super+Shift+Alt, mouse:273, Generate AI summary for selected text, exec, ~/.config/hypr/hyprland/scripts/ai/primary-buffer-query.sh # [hidden] AI summary for selected text
#!
##! Window
@@ -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
right: true
}
color: CF.ColorUtils.transparentize(CF.ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colPrimary, 0.75), (bgRoot.wallpaperIsVideo ? 1 : 0))
color: {
if (!bgRoot.wallpaperSafetyTriggered || bgRoot.wallpaperIsVideo) return "transparent";
return CF.ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colPrimary, 0.75)
}
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
@@ -442,7 +445,7 @@ Variants {
color: bgRoot.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
animateChange: true
animateChange: Config.options.background.clock.digital.animateChange
}
component ClockStatusText: Row {
id: statusTextRow
@@ -13,13 +13,14 @@ Item {
text: Qt.locale().toString(DateTime.clock.date, root.isMonth ? "MM" : "d")
MaterialCookie {
MaterialShape {
id: bubble
z: 5
sides: root.isMonth ? 1 : 4
// sides: root.isMonth ? 1 : 4
shape: root.isMonth ? MaterialShape.Shape.Pill : MaterialShape.Shape.Pentagon
anchors.centerIn: parent
color: root.isMonth ? Appearance.colors.colPrimaryContainer : Appearance.colors.colTertiaryContainer
implicitSize: targetSize
constantlyRotate: Config.options.background.clock.cookie.constantlyRotate
}
StyledText {
@@ -27,7 +27,7 @@ Item { // Bar content region
// Background shadow
Loader {
active: Config.options.bar.showBackground && Config.options.bar.cornerStyle === 1
active: Config.options.bar.showBackground && Config.options.bar.cornerStyle === 1 && Config.options.bar.floatStyleShadow
anchors.fill: barBackground
sourceComponent: StyledRectangularShadow {
anchors.fill: undefined // The loader's anchors act on this, and this should not have any anchor
@@ -41,7 +41,7 @@ Item {
visible: Config.options.bar.utilButtons.showScreenRecord
sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter
onClicked: Quickshell.execDetached(["bash", "-c", "~/.config/hypr/hyprland/scripts/record.sh"])
onClicked: Quickshell.execDetached([Directories.recordScriptPath])
MaterialSymbol {
horizontalAlignment: Qt.AlignHCenter
fill: 1
@@ -5,6 +5,7 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Qt.labs.synchronizer
import Quickshell.Io
import Quickshell
import Quickshell.Wayland
@@ -22,7 +23,6 @@ Scope { // Scope
"name": Translation.tr("Elements")
},
]
property int selectedTab: 0
Loader {
id: cheatsheetLoader
@@ -31,6 +31,7 @@ Scope { // Scope
sourceComponent: PanelWindow { // Window
id: cheatsheetRoot
visible: cheatsheetLoader.active
property int selectedTab: 0
anchors {
top: true
@@ -85,16 +86,16 @@ Scope { // Scope
}
if (event.modifiers === Qt.ControlModifier) {
if (event.key === Qt.Key_PageDown) {
root.selectedTab = Math.min(root.selectedTab + 1, root.tabButtonList.length - 1);
cheatsheetRoot.selectedTab = Math.min(cheatsheetRoot.selectedTab + 1, root.tabButtonList.length - 1);
event.accepted = true;
} else if (event.key === Qt.Key_PageUp) {
root.selectedTab = Math.max(root.selectedTab - 1, 0);
cheatsheetRoot.selectedTab = Math.max(cheatsheetRoot.selectedTab - 1, 0);
event.accepted = true;
} else if (event.key === Qt.Key_Tab) {
root.selectedTab = (root.selectedTab + 1) % root.tabButtonList.length;
cheatsheetRoot.selectedTab = (cheatsheetRoot.selectedTab + 1) % root.tabButtonList.length;
event.accepted = true;
} else if (event.key === Qt.Key_Backtab) {
root.selectedTab = (root.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length;
cheatsheetRoot.selectedTab = (cheatsheetRoot.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length;
event.accepted = true;
}
}
@@ -140,9 +141,8 @@ Scope { // Scope
PrimaryTabBar { // Tab strip
id: tabBar
tabButtonList: root.tabButtonList
externalTrackedTab: root.selectedTab
function onCurrentIndexChanged(currentIndex) {
root.selectedTab = currentIndex;
Synchronizer on currentIndex {
property alias source: cheatsheetRoot.selectedTab
}
}
@@ -164,12 +164,12 @@ Scope { // Scope
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
currentIndex: tabBar.externalTrackedTab
currentIndex: cheatsheetRoot.selectedTab
onCurrentIndexChanged: {
contentWidthBehavior.enabled = true;
contentHeightBehavior.enabled = true;
tabBar.enableIndicatorAnimation = true;
root.selectedTab = currentIndex;
cheatsheetRoot.selectedTab = currentIndex;
}
clip: true
@@ -159,6 +159,8 @@ Singleton {
property color colTertiaryContainer: m3colors.m3tertiaryContainer
property color colTertiaryContainerHover: ColorUtils.mix(m3colors.m3tertiaryContainer, m3colors.m3onTertiaryContainer, 0.90)
property color colTertiaryContainerActive: ColorUtils.mix(m3colors.m3tertiaryContainer, colLayer1Active, 0.54)
property color colOnTertiary: m3colors.m3onTertiary
property color colOnTertiaryContainer: m3colors.m3onTertiaryContainer
property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer
property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency)
property color colSurfaceContainer: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency)
@@ -315,9 +317,9 @@ Singleton {
}
property QtObject clickBounce: QtObject {
property int duration: 200
property int duration: 400
property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.expressiveFastSpatial
property list<real> bezierCurve: animationCurves.expressiveDefaultSpatial
property int velocity: 850
property Component numberAnimation: Component { NumberAnimation {
duration: root.animation.clickBounce.duration
@@ -165,6 +165,9 @@ Singleton {
property bool dateInClock: true
property bool constantlyRotate: false
}
property JsonObject digital: JsonObject {
property bool animateChange: true
}
}
property string wallpaperPath: ""
@@ -194,6 +197,7 @@ Singleton {
}
property bool bottom: false // Instead of top
property int cornerStyle: 0 // 0: Hug | 1: Float | 2: Plain rectangle
property bool floatStyleShadow: true // Show shadow behind bar when cornerStyle == 1 (Float)
property bool borderless: false // true for no grouping of items
property string topLeftIcon: "spark" // Options: "distro" or any icon name in ~/.config/quickshell/ii/assets/icons
property bool showBackground: true
@@ -376,6 +380,11 @@ Singleton {
property int updateInterval: 3000
}
property JsonObject musicRecognition: JsonObject {
property int timeout: 16
property int interval: 4
}
property JsonObject search: JsonObject {
property int nonAppResultDelay: 30 // This prevents lagging when typing
property string engineBaseUrl: "https://www.google.com/search?q="
@@ -419,10 +428,11 @@ Singleton {
property bool bottom: false
property bool valueScroll: true
property bool clickless: false
property real cornerRegionWidth: 250
property real cornerRegionHeight: 2
property int cornerRegionWidth: 250
property int cornerRegionHeight: 5
property bool visualize: false
property bool clicklessCornerEnd: true
property int clicklessCornerVerticalOffset: 1
}
property JsonObject quickToggles: JsonObject {
@@ -42,6 +42,7 @@ Singleton {
property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/ai/prompts`)
property string aiChats: FileUtils.trimFileProtocol(`${Directories.state}/user/ai/chats`)
property string aiTranslationScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/ai/gemini-translate.sh`)
property string recordScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/videos/record.sh`)
// Cleanup on init
Component.onCompleted: {
Quickshell.execDetached(["mkdir", "-p", `${shellConfig}`])
@@ -13,8 +13,8 @@ Rectangle {
property alias uniformCellSizes: rowLayout.uniformCellSizes
property real spacing: 5
property real padding: 0
property int clickIndex: rowLayout.clickIndex
property int childrenCount: rowLayout.children.length
property alias clickIndex: rowLayout.clickIndex
property alias childrenCount: rowLayout.childrenCount
property real contentWidth: {
let total = 0;
@@ -44,5 +44,6 @@ Rectangle {
anchors.margins: root.padding
spacing: root.spacing
property int clickIndex: -1
property int childrenCount: children.length
}]
}
@@ -3,7 +3,6 @@ import QtQuick.Shapes
import Quickshell
import qs.modules.common
Item {
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.widgets
MaterialCookie {
MaterialShape {
id: root
property alias text: symbol.text
property alias iconSize: symbol.iconSize
@@ -13,7 +13,7 @@ MaterialCookie {
color: Appearance.colors.colSecondaryContainer
colSymbol: Appearance.colors.colOnSecondaryContainer
sides: 5
shape: MaterialShape.Shape.Clover4Leaf
implicitSize: Math.max(symbol.implicitWidth, symbol.implicitHeight) + padding * 2
@@ -7,6 +7,7 @@ Rectangle {
id: root
property alias materialIcon: icon.text
property alias text: noticeText.text
default property alias data: buttonRow.data
radius: Appearance.rounding.normal
color: Appearance.colors.colPrimaryContainer
@@ -28,13 +29,23 @@ Rectangle {
color: Appearance.colors.colOnPrimaryContainer
}
StyledText {
id: noticeText
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
text: "Notice message"
color: Appearance.colors.colOnPrimaryContainer
wrapMode: Text.WordWrap
spacing: 4
StyledText {
id: noticeText
Layout.fillWidth: true
text: "Notice message"
color: Appearance.colors.colOnPrimaryContainer
wrapMode: Text.WordWrap
}
RowLayout {
id: buttonRow
visible: children.length > 0
Layout.fillWidth: true
}
}
}
}
@@ -6,7 +6,7 @@ import Quickshell
import Quickshell.Widgets
import Quickshell.Services.Notifications
MaterialCookie { // App icon
MaterialShape { // App icon
id: root
property var appIcon: ""
property var summary: ""
@@ -21,8 +21,11 @@ MaterialCookie { // App icon
property real smallAppIconSize: implicitSize * smallAppIconScale
implicitSize: 38 * scale
sides: isUrgent ? 10 : 0
amplitude: implicitSize / 24
property list<var> urgentShapes: [
MaterialShape.Shape.VerySunny,
MaterialShape.Shape.SoftBurst,
]
shape: isUrgent ? urgentShapes[Math.floor(Math.random() * urgentShapes.length)] : MaterialShape.Shape.Circle
color: isUrgent ? Appearance.colors.colPrimary : Appearance.colors.colSecondaryContainer
Loader {
@@ -7,9 +7,11 @@ Item {
id: root
property bool shown: true
property alias icon: cookieWrappedMaterialSymbol.text
property alias icon: shapeWidget.text
property alias title: widgetNameText.text
property alias description: widgetDescriptionText.text
property alias shape: shapeWidget.shape
property alias descriptionHorizontalAlignment: widgetDescriptionText.horizontalAlignment
opacity: shown ? 1 : 0
visible: opacity > 0
@@ -27,14 +29,16 @@ Item {
anchors.centerIn: parent
spacing: 5
CookieWrappedMaterialSymbol {
id: cookieWrappedMaterialSymbol
MaterialShapeWrappedMaterialSymbol {
id: shapeWidget
Layout.alignment: Qt.AlignHCenter
iconSize: 60
rotation: -60 * (1 - root.opacity)
padding: 12
iconSize: 56
rotation: -30 * (1 - root.opacity)
}
StyledText {
id: widgetNameText
visible: title !== ""
Layout.alignment: Qt.AlignHCenter
font.pixelSize: Appearance.font.pixelSize.larger
font.family: Appearance.font.family.title
@@ -43,6 +47,7 @@ Item {
}
StyledText {
id: widgetDescriptionText
visible: description !== ""
Layout.fillWidth: true
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.m3colors.m3outline
@@ -3,16 +3,20 @@ import qs.services
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt.labs.synchronizer
ColumnLayout {
id: root
spacing: 0
required property var tabButtonList // Something like [{"icon": "notifications", "name": Translation.tr("Notifications")}, {"icon": "volume_up", "name": Translation.tr("Volume mixer")}]
required property var externalTrackedTab
property int currentIndex
property bool enableIndicatorAnimation: false
property color colIndicator: Appearance?.colors.colPrimary ?? "#65558F"
property color colBorder: Appearance?.m3colors.m3outlineVariant ?? "#C6C6D0"
signal currentIndexChanged(int index)
onCurrentIndexChanged: {
enableIndicatorAnimation = true
}
property bool centerTabBar: parent.width > 500
Layout.fillWidth: !centerTabBar
@@ -22,9 +26,8 @@ ColumnLayout {
TabBar {
id: tabBar
Layout.fillWidth: true
currentIndex: root.externalTrackedTab
onCurrentIndexChanged: {
root.onCurrentIndexChanged(currentIndex)
Synchronizer on currentIndex {
property alias source: root.currentIndex
}
background: Item {
@@ -42,10 +45,11 @@ ColumnLayout {
Repeater {
model: root.tabButtonList
delegate: PrimaryTabButton {
selected: (index == root.externalTrackedTab)
selected: (index == root.currentIndex)
buttonText: modelData.name
buttonIcon: modelData.icon
minimumWidth: 160
onClicked: root.currentIndex = index
}
}
}
@@ -54,12 +58,6 @@ ColumnLayout {
id: tabIndicator
Layout.fillWidth: true
height: 3
Connections {
target: root
function onExternalTrackedTabChanged() {
root.enableIndicatorAnimation = true
}
}
Rectangle {
id: indicator
@@ -91,7 +91,7 @@ TabButton {
background: Rectangle {
id: buttonBackground
radius: Appearance?.rounding.small ?? 7
radius: Appearance?.rounding.normal
implicitHeight: 37
color: (root.hovered ? root.colBackgroundHover : root.colBackground)
layer.enabled: true
@@ -45,13 +45,25 @@ Switch {
anchors.leftMargin: root.checked ? ((root.pressed || root.down) ? (22 * root.scale) : 24 * root.scale) : ((root.pressed || root.down) ? (2 * root.scale) : 8 * root.scale)
Behavior on anchors.leftMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
NumberAnimation {
duration: Appearance.animationCurves.expressiveFastSpatialDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
}
Behavior on width {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
NumberAnimation {
duration: Appearance.animationCurves.expressiveFastSpatialDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
}
Behavior on height {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
NumberAnimation {
duration: Appearance.animationCurves.expressiveFastSpatialDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
}
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
@@ -17,13 +17,17 @@ function findSuitableMaterialSymbol(summary = "") {
'time': 'scheduleb',
'installed': 'download',
'configuration reloaded': 'reset_wrench',
'unable': 'question_mark',
"couldn't": 'question_mark',
'config': 'reset_wrench',
'update': 'update',
'ai response': 'neurology',
'control': 'settings',
'upsca': 'compare',
'music': 'queue_music',
'install': 'deployed_code_update',
'startswith:file': 'folder_copy', // Declarative startsWith check
};
const lowerSummary = summary.toLowerCase();
@@ -28,7 +28,10 @@ Scope {
Connections {
target: GlobalStates
function onScreenLockedChanged() {
if (GlobalStates.screenLocked) lockContext.reset();
if (GlobalStates.screenLocked) {
lockContext.reset();
lockContext.tryFingerUnlock();
}
}
}
@@ -113,7 +116,7 @@ Scope {
onPressed: {
if (Config.options.lock.useHyprlock) {
Quickshell.execDetached(["hyprlock"])
Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]);
return;
}
GlobalStates.screenLocked = true;
@@ -2,6 +2,7 @@ import qs
import qs.modules.common
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.Pam
Scope {
@@ -18,6 +19,7 @@ Scope {
property string currentText: ""
property bool unlockInProgress: false
property bool showFailure: false
property bool fingerprintsConfigured: false
property var targetAction: LockContext.ActionEnum.Unlock
function resetTargetAction() {
@@ -60,6 +62,34 @@ Scope {
pam.start();
}
function tryFingerUnlock() {
if (root.fingerprintsConfigured) {
fingerPam.start();
}
}
function stopFingerPam() {
fingerPam.abort();
}
Process {
id: fingerprintCheckProc
running: true
command: ["bash", "-c", "fprintd-list $(whoami)"]
stdout: StdioCollector {
id: fingerprintOutputCollector
onStreamFinished: {
root.fingerprintsConfigured = fingerprintOutputCollector.text.includes("Fingerprints for user");
}
}
onExited: (exitCode, exitStatus) => {
if (exitCode !== 0) {
console.warn("fprintd-list command exited with error:", exitCode, exitStatus);
root.fingerprintsConfigured = false;
}
}
}
PamContext {
id: pam
@@ -74,6 +104,7 @@ Scope {
onCompleted: result => {
if (result == PamResult.Success) {
root.unlocked(root.targetAction);
stopFingerPam();
} else {
root.clearText();
root.unlockInProgress = false;
@@ -83,4 +114,19 @@ Scope {
}
}
PamContext {
id: fingerPam
configDirectory: "pam"
config: "fprintd.conf"
onCompleted: result => {
if (result == PamResult.Success) {
root.unlocked(root.targetAction);
stopFingerPam();
} else if (result == PamResult.Error){ // if timeout or etc..
tryFingerUnlock()
}
}
}
}
@@ -1,5 +1,6 @@
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell.Services.UPower
import qs
import qs.services
@@ -7,6 +8,7 @@ import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import qs.modules.bar as Bar
import Quickshell
import Quickshell.Services.SystemTray
MouseArea {
@@ -98,6 +100,23 @@ MouseArea {
scale: root.toolbarScale
opacity: root.toolbarOpacity
// Fingerprint
Loader {
Layout.leftMargin: 10
Layout.rightMargin: 6
Layout.alignment: Qt.AlignVCenter
active: root.context.fingerprintsConfigured
visible: active
sourceComponent: MaterialSymbol {
id: fingerprintIcon
fill: 1
text: "fingerprint"
iconSize: Appearance.font.pixelSize.hugeass
color: Appearance.colors.colOnSurfaceVariant
}
}
ToolbarTextField {
id: passwordBox
placeholderText: GlobalStates.screenUnlockFailed ? Translation.tr("Incorrect password") : Translation.tr("Enter password")
@@ -124,7 +143,17 @@ MouseArea {
Keys.onPressed: event => {
root.context.resetClearTimer();
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: passwordBox.width - 8
height: passwordBox.height
radius: height / 2
}
}
// Shake when wrong password
SequentialAnimation {
id: wrongPasswordShakeAnim
NumberAnimation { target: passwordBox; property: "x"; to: -30; duration: 50 }
@@ -139,6 +168,17 @@ MouseArea {
if (GlobalStates.screenUnlockFailed) wrongPasswordShakeAnim.restart();
}
}
// We're drawing dots manually
color: ColorUtils.transparentize(Appearance.colors.colOnLayer1)
PasswordChars {
anchors {
fill: parent
leftMargin: passwordBox.padding
rightMargin: passwordBox.padding
}
length: root.context.currentText.length
}
}
ToolbarButton {
@@ -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 widgetWidth: Appearance.sizes.mediaControlsWidth
readonly property real widgetHeight: Appearance.sizes.mediaControlsHeight
property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1
property list<real> visualizerPoints: []
property bool hasPlasmaIntegration: false
@@ -44,6 +44,9 @@ Toolbar {
return "image_search";
case RegionSelection.SnipAction.CharRecognition:
return "document_scanner";
case RegionSelection.SnipAction.Record:
case RegionSelection.SnipAction.RecordWithSound:
return "videocam";
default:
return "";
}
@@ -5,15 +5,16 @@ import qs.modules.common.widgets
import qs.services
import QtQuick
import QtQuick.Controls
import Qt.labs.synchronizer
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
import Qt.labs.synchronizer
PanelWindow {
id: root
visible: false
color: "transparent"
WlrLayershell.namespace: "quickshell:regionSelector"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
@@ -26,7 +27,7 @@ PanelWindow {
}
// TODO: Ask: sidebar AI; Ocr: tesseract
enum SnipAction { Copy, Edit, Search, CharRecognition }
enum SnipAction { Copy, Edit, Search, CharRecognition, Record, RecordWithSound }
enum SelectionMode { RectCorners, Circle }
property var action: RegionSelection.SnipAction.Copy
property var selectionMode: RegionSelection.SelectionMode.RectCorners
@@ -175,14 +176,35 @@ PanelWindow {
property real regionY: Math.min(dragStartY, draggingY)
Process {
id: screenshotProcess
id: screenshotProc
running: true
command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(root.screen.name)}' '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`]
onExited: (exitCode, exitStatus) => {
root.visible = true;
if (root.enableContentRegions) imageDetectionProcess.running = true;
root.preparationDone = !checkRecordingProc.running;
}
}
property bool isRecording: root.action === RegionSelection.SnipAction.Record || root.action === RegionSelection.SnipAction.RecordWithSound
property bool recordingShouldStop: false
Process {
id: checkRecordingProc
running: isRecording
command: ["pidof", "wf-recorder"]
onExited: (exitCode, exitStatus) => {
root.preparationDone = !screenshotProc.running
root.recordingShouldStop = (exitCode === 0);
}
}
property bool preparationDone: false
onPreparationDoneChanged: {
if (!preparationDone) return;
if (root.isRecording && root.recordingShouldStop) {
Quickshell.execDetached([Directories.recordScriptPath]);
root.dismiss();
return;
}
root.visible = true;
}
Process {
id: imageDetectionProcess
@@ -221,11 +243,16 @@ PanelWindow {
}
// Set command for action
const rx = Math.round(root.regionX * root.monitorScale);
const ry = Math.round(root.regionY * root.monitorScale);
const rw = Math.round(root.regionWidth * root.monitorScale);
const rh = Math.round(root.regionHeight * root.monitorScale);
const cropBase = `magick ${StringUtils.shellSingleQuoteEscape(root.screenshotPath)} `
+ `-crop ${root.regionWidth * root.monitorScale}x${root.regionHeight * root.monitorScale}+${root.regionX * root.monitorScale}+${root.regionY * root.monitorScale}`
+ `-crop ${rw}x${rh}+${rx}+${ry}`
const cropToStdout = `${cropBase} -`
const cropInPlace = `${cropBase} '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`
const cleanup = `rm '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`
const slurpRegion = `${rx},${ry} ${rw}x${rh}`
const uploadAndGetUrl = (filePath) => {
return `curl -sF files[]=@'${StringUtils.shellSingleQuoteEscape(filePath)}' ${root.fileUploadApiEndpoint} | jq -r '.files[0].url'`
}
@@ -242,6 +269,12 @@ PanelWindow {
case RegionSelection.SnipAction.CharRecognition:
snipProc.command = ["bash", "-c", `${cropInPlace} && tesseract '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}' stdout -l $(tesseract --list-langs | awk 'NR>1{print $1}' | tr '\\n' '+' | sed 's/\\+$/\\n/') | wl-copy && ${cleanup}`]
break;
case RegionSelection.SnipAction.Record:
snipProc.command = ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}'`]
break;
case RegionSelection.SnipAction.RecordWithSound:
snipProc.command = ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}' --sound`]
break;
default:
console.warn("[Region Selector] Unknown snip action, skipping snip.");
root.dismiss();
@@ -62,6 +62,18 @@ Scope {
GlobalStates.regionSelectorOpen = true
}
function record() {
root.action = RegionSelection.SnipAction.Record
root.selectionMode = RegionSelection.SelectionMode.RectCorners
GlobalStates.regionSelectorOpen = true
}
function recordWithSound() {
root.action = RegionSelection.SnipAction.RecordWithSound
root.selectionMode = RegionSelection.SelectionMode.RectCorners
GlobalStates.regionSelectorOpen = true
}
IpcHandler {
target: "region"
@@ -71,10 +83,15 @@ Scope {
function search() {
root.search()
}
function ocr() {
root.ocr()
}
function record() {
root.record()
}
function recordWithSound() {
root.recordWithSound()
}
}
GlobalShortcut {
@@ -92,4 +109,14 @@ Scope {
description: "Recognizes text in the selected region"
onPressed: root.ocr()
}
GlobalShortcut {
name: "regionRecord"
description: "Records the selected region"
onPressed: root.record()
}
GlobalShortcut {
name: "regionRecordWithSound"
description: "Records the selected region with sound"
onPressed: root.recordWithSound()
}
}
@@ -79,10 +79,12 @@ Scope {
implicitWidth: Config.options.sidebar.cornerOpen.cornerRegionWidth
implicitHeight: Config.options.sidebar.cornerOpen.cornerRegionHeight
hoverEnabled: true
onMouseXChanged: {
onPositionChanged: {
if (!Config.options.sidebar.cornerOpen.clicklessCornerEnd) return;
if ((cornerWidget.isRight && mouseArea.mouseX >= mouseArea.width - 2)
|| (cornerWidget.isLeft && mouseArea.mouseX <= 2))
const verticalOffset = Config.options.sidebar.cornerOpen.clicklessCornerVerticalOffset;
const correctX = (cornerWidget.isRight && mouseArea.mouseX >= mouseArea.width - 2) || (cornerWidget.isLeft && mouseArea.mouseX <= 2);
const correctY = (cornerWidget.isTop && mouseArea.mouseY > verticalOffset || cornerWidget.isBottom && mouseArea.mouseY < mouseArea.height - verticalOffset);
if (correctX && correctY)
screenCorners.actionForCorner[cornerPanelWindow.corner]();
}
onEntered: {
@@ -55,6 +55,20 @@ ContentPage {
}
}
ContentSubsection {
visible: Config.options.background.clock.style === "digital"
title: Translation.tr("Digital clock settings")
ConfigSwitch {
buttonIcon: "animation"
text: Translation.tr("Animate time change")
checked: Config.options.background.clock.digital.animateChange
onCheckedChanged: {
Config.options.background.clock.digital.animateChange = checked;
}
}
}
ContentSubsection {
visible: Config.options.background.clock.style === "cookie"
title: Translation.tr("Cookie clock settings")
@@ -787,22 +801,22 @@ ContentPage {
}
}
}
ConfigSwitch {
buttonIcon: "highlight_mouse_cursor"
text: Translation.tr("Hover to trigger")
checked: Config.options.sidebar.cornerOpen.clickless
onCheckedChanged: {
Config.options.sidebar.cornerOpen.clickless = checked;
}
StyledToolTip {
text: Translation.tr("When this is off you'll have to click")
}
}
Row {
ConfigSwitch {
buttonIcon: "highlight_mouse_cursor"
text: Translation.tr("Hover to trigger")
checked: Config.options.sidebar.cornerOpen.clickless
onCheckedChanged: {
Config.options.sidebar.cornerOpen.clickless = checked;
}
StyledToolTip {
text: Translation.tr("When this is off you'll have to click")
}
}
ConfigSwitch {
enabled: !Config.options.sidebar.cornerOpen.clickless
text: Translation.tr("but force at absolute corner")
text: Translation.tr("Force hover open at absolute corner")
checked: Config.options.sidebar.cornerOpen.clicklessCornerEnd
onCheckedChanged: {
Config.options.sidebar.cornerOpen.clicklessCornerEnd = checked;
@@ -812,7 +826,29 @@ ContentPage {
text: Translation.tr("When the previous option is off and this is on,\nyou can still hover the corner's end to open sidebar,\nand the remaining area can be used for volume/brightness scroll")
}
}
ConfigSpinBox {
icon: "arrow_cool_down"
text: Translation.tr("with vertical offset")
value: Config.options.sidebar.cornerOpen.clicklessCornerVerticalOffset
from: 0
to: 20
stepSize: 1
onValueChanged: {
Config.options.sidebar.cornerOpen.clicklessCornerVerticalOffset = value;
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
StyledToolTip {
extraVisibleCondition: mouseArea.containsMouse
text: Translation.tr("Why this is cool:\nFor non-0 values, it won't trigger when you reach the\nscreen corner along the horizontal edge, but it will when\nyou do along the vertical edge")
}
}
}
}
ConfigRow {
uniform: true
ConfigSwitch {
@@ -332,5 +332,33 @@ ContentPage {
NoticeBox {
Layout.fillWidth: true
text: Translation.tr('Not all options are available in this app. You should also check the config file by hitting the "Config file" button on the topleft corner or opening %1 manually.').arg(Directories.shellConfigPath)
Item {
Layout.fillWidth: true
}
RippleButtonWithIcon {
id: copyPathButton
property bool justCopied: false
Layout.fillWidth: false
buttonRadius: Appearance.rounding.small
materialIcon: justCopied ? "check" : "content_copy"
mainText: justCopied ? Translation.tr("Path copied") : Translation.tr("Copy path")
onClicked: {
copyPathButton.justCopied = true
Quickshell.clipboardText = FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse/config.json`);
revertTextTimer.restart();
}
colBackground: ColorUtils.transparentize(Appearance.colors.colPrimaryContainer)
colBackgroundHover: Appearance.colors.colPrimaryContainerHover
colRipple: Appearance.colors.colPrimaryContainerActive
Timer {
id: revertTextTimer
interval: 1500
onTriggered: {
copyPathButton.justCopied = false
}
}
}
}
}
@@ -24,6 +24,34 @@ ContentPage {
}
}
ContentSection {
icon: "music_cast"
title: Translation.tr("Music Recognition")
ConfigSpinBox {
icon: "timer_off"
text: Translation.tr("Total duration timeout (s)")
value: Config.options.musicRecognition.timeout
from: 10
to: 100
stepSize: 2
onValueChanged: {
Config.options.musicRecognition.timeout = value;
}
}
ConfigSpinBox {
icon: "av_timer"
text: Translation.tr("Polling interval (s)")
value: Config.options.musicRecognition.interval
from: 2
to: 10
stepSize: 1
onValueChanged: {
Config.options.musicRecognition.interval = value;
}
}
}
ContentSection {
icon: "cell_tower"
title: Translation.tr("Networking")
@@ -54,6 +82,7 @@ ContentPage {
Config.options.resources.updateInterval = value;
}
}
}
ContentSection {
@@ -369,6 +369,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
icon: "neurology"
title: Translation.tr("Large language models")
description: Translation.tr("Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window")
shape: MaterialShape.Shape.PixelCircle
}
ScrollToBottomButton {
@@ -23,12 +23,21 @@ Item {
property var suggestionQuery: ""
property var suggestionList: []
property bool pullLoading: false
property int pullLoadingGap: 80
property real normalizedPullDistance: Math.max(0, (1 - Math.exp(-booruResponseListView.verticalOvershoot / 50)) * booruResponseListView.dragging)
Connections {
target: Booru
function onTagSuggestion(query, suggestions) {
root.suggestionQuery = query;
root.suggestionList = suggestions;
}
function onRunningRequestsChanged() {
if (Booru.runningRequests === 0) {
root.pullLoading = false;
}
}
}
property var allCommands: [
@@ -53,6 +62,8 @@ Item {
if (root.responses.length > 0) {
const lastResponse = root.responses[root.responses.length - 1];
root.handleInput(`${lastResponse.tags.join(" ")} ${parseInt(lastResponse.page) + 1}`);
} else {
root.handleInput("");
}
}
},
@@ -85,10 +96,7 @@ Item {
}
}
else if (inputText.trim() == "+") {
if (root.responses.length > 0) {
const lastResponse = root.responses[root.responses.length - 1]
root.handleInput(lastResponse.tags.join(" ") + ` ${parseInt(lastResponse.page) + 1}`);
}
root.handleInput(`${root.commandPrefix}next`);
}
else {
// Create tag list
@@ -111,17 +119,23 @@ Item {
}
}
property real pageKeyScrollAmount: booruResponseListView.height / 2
Keys.onPressed: (event) => {
tagInputField.forceActiveFocus()
if (event.modifiers === Qt.NoModifier) {
if (event.key === Qt.Key_PageUp) {
booruResponseListView.contentY = Math.max(0, booruResponseListView.contentY - booruResponseListView.height / 2)
if (booruResponseListView.atYBeginning) return;
booruResponseListView.contentY = Math.max(0, booruResponseListView.contentY - root.pageKeyScrollAmount)
event.accepted = true
} else if (event.key === Qt.Key_PageDown) {
booruResponseListView.contentY = Math.min(booruResponseListView.contentHeight - booruResponseListView.height / 2, booruResponseListView.contentY + booruResponseListView.height / 2)
if (booruResponseListView.atYEnd) return;
booruResponseListView.contentY = Math.min(booruResponseListView.contentHeight, booruResponseListView.contentY + root.pageKeyScrollAmount)
event.accepted = true
}
}
if ((event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier) && event.key === Qt.Key_O) {
Booru.clearResponses()
}
}
@@ -158,17 +172,20 @@ Item {
mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4
property int lastResponseLength: 0
model: ScriptModel {
values: {
if(root.responses.length > booruResponseListView.lastResponseLength) {
Connections {
target: root
function onResponsesChanged() {
if (root.responses.length > booruResponseListView.lastResponseLength) {
if (booruResponseListView.lastResponseLength > 0 && root.responses[booruResponseListView.lastResponseLength].provider != "system")
booruResponseListView.contentY = booruResponseListView.contentY + root.scrollOnNewResponse
booruResponseListView.lastResponseLength = root.responses.length
}
return root.responses
}
}
model: ScriptModel {
values: root.responses
}
delegate: BooruResponse {
responseData: modelData
tagInputField: root.inputField
@@ -176,6 +193,14 @@ Item {
downloadPath: root.downloadPath
nsfwPath: root.nsfwPath
}
onDragEnded: { // Pull to load more
const gap = booruResponseListView.verticalOvershoot
if (gap > root.pullLoadingGap) {
root.pullLoading = true
root.handleInput(`${root.commandPrefix}next`)
}
}
}
PagePlaceholder {
@@ -185,6 +210,7 @@ Item {
icon: "bookmark_heart"
title: Translation.tr("Anime boorus")
description: ""
shape: MaterialShape.Shape.Bun
}
ScrollToBottomButton {
@@ -192,42 +218,24 @@ Item {
target: booruResponseListView
}
Item { // Queries awaiting response
MaterialLoadingIndicator {
id: loadingIndicator
z: 4
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 10
implicitHeight: pendingBackground.implicitHeight
opacity: Booru.runningRequests > 0 ? 1 : 0
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
Rectangle {
id: pendingBackground
color: Appearance.m3colors.m3inverseSurface
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
implicitHeight: pendingText.implicitHeight + 12 * 2
radius: Appearance.rounding.verysmall
StyledText {
id: pendingText
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 12
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: Appearance.font.pixelSize.smaller
color: Appearance.m3colors.m3inverseOnSurface
wrapMode: Text.Wrap
text: Translation.tr("%1 queries pending").arg(Booru.runningRequests)
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: 20 + (root.pullLoading ? 0 : Math.max(0, (root.normalizedPullDistance - 0.5) * 50))
Behavior on bottomMargin {
NumberAnimation {
duration: 200
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
}
}
loading: root.pullLoading || Booru.runningRequests > 0
pullProgress: Math.min(1, booruResponseListView.verticalOvershoot / root.pullLoadingGap * booruResponseListView.dragging)
scale: root.pullLoading ? 1 : Math.min(1, root.normalizedPullDistance * 2)
}
}
@@ -5,6 +5,7 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Qt.labs.synchronizer
Item {
id: root
@@ -58,9 +59,8 @@ Item {
id: tabBar
visible: root.tabButtonList.length > 1
tabButtonList: root.tabButtonList
externalTrackedTab: root.selectedTab
function onCurrentIndexChanged(currentIndex) {
root.selectedTab = currentIndex
Synchronizer on currentIndex {
property alias source: root.selectedTab
}
}
@@ -71,7 +71,7 @@ Item {
Layout.fillHeight: true
spacing: 10
currentIndex: tabBar.externalTrackedTab
currentIndex: root.selectedTab
onCurrentIndexChanged: {
tabBar.enableIndicatorAnimation = true
root.selectedTab = currentIndex
@@ -105,7 +105,7 @@ Rectangle {
anchors.right: parent.right
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 7
spacing: 12
Item {
Layout.alignment: Qt.AlignVCenter
@@ -177,6 +177,20 @@ Rectangle {
ButtonGroup {
spacing: 5
AiMessageControlButton {
id: regenButton
buttonIcon: "refresh"
visible: messageData?.role === 'assistant'
onClicked: {
Ai.regenerate(root.messageIndex)
}
StyledToolTip {
text: Translation.tr("Regenerate")
}
}
AiMessageControlButton {
id: copyButton
buttonIcon: activated ? "inventory" : "content_copy"
@@ -254,28 +268,50 @@ Rectangle {
spacing: 0
Repeater {
model: root.messageBlocks.length
delegate: Loader {
required property int index
property var thisBlock: root.messageBlocks[index]
Layout.fillWidth: true
// property var segment: thisBlock
property var segmentContent: thisBlock.content
property var segmentLang: thisBlock.lang
property var messageData: root.messageData
property var editing: root.editing
property var renderMarkdown: root.renderMarkdown
property var enableMouseSelection: root.enableMouseSelection
property bool thinking: root.messageData?.thinking ?? true
property bool done: root.messageData?.done ?? false
property bool completed: thisBlock.completed ?? false
model: ScriptModel {
values: Array.from({ length: root.messageBlocks.length }, (msg, i) => {
return ({
type: root.messageBlocks[i].type
})
});
}
property bool forceDisableChunkSplitting: root.messageData.content.includes("```")
source: thisBlock.type === "code" ? "MessageCodeBlock.qml" :
thisBlock.type === "think" ? "MessageThinkBlock.qml" :
"MessageTextBlock.qml"
delegate: DelegateChooser {
id: messageDelegate
role: "type"
DelegateChoice { roleValue: "code"; MessageCodeBlock {
required property int index
property var thisBlock: root.messageBlocks[index]
editing: root.editing
renderMarkdown: root.renderMarkdown
enableMouseSelection: root.enableMouseSelection
segmentContent: thisBlock.content
segmentLang: thisBlock.lang
messageData: root.messageData
} }
DelegateChoice { roleValue: "think"; MessageThinkBlock {
required property int index
property var thisBlock: root.messageBlocks[index]
editing: root.editing
renderMarkdown: root.renderMarkdown
enableMouseSelection: root.enableMouseSelection
segmentContent: thisBlock.content
messageData: root.messageData
done: root.messageData?.done ?? false
completed: thisBlock.completed ?? false
} }
DelegateChoice { roleValue: "text"; MessageTextBlock {
required property int index
property var thisBlock: root.messageBlocks[index]
editing: root.editing
renderMarkdown: root.renderMarkdown
enableMouseSelection: root.enableMouseSelection
segmentContent: thisBlock.content
messageData: root.messageData
done: root.messageData?.done ?? false
forceDisableChunkSplitting: root.messageData.content.includes("```")
} }
}
}
}
@@ -13,22 +13,20 @@ import org.kde.syntaxhighlighting
ColumnLayout {
id: root
// These are needed on the parent loader
property bool editing: parent?.editing ?? false
property bool renderMarkdown: parent?.renderMarkdown ?? true
property bool enableMouseSelection: parent?.enableMouseSelection ?? false
property var segmentContent: parent?.segmentContent ?? ({})
property var segmentLang: parent?.segmentLang ?? "txt"
property bool editing: false
property bool renderMarkdown: true
property bool enableMouseSelection: false
property var segmentContent: ({})
property var segmentLang: "txt"
property var messageData: {}
property bool isCommandRequest: segmentLang === "command"
property var displayLang: (isCommandRequest ? "bash" : segmentLang)
property var messageData: parent?.messageData ?? {}
property real codeBlockBackgroundRounding: Appearance.rounding.small
property real codeBlockHeaderPadding: 3
property real codeBlockComponentSpacing: 2
spacing: codeBlockComponentSpacing
anchors.left: parent.left
anchors.right: parent.right
Rectangle { // Code background
Layout.fillWidth: true
@@ -14,17 +14,17 @@ import Quickshell.Hyprland
ColumnLayout {
id: root
// These are needed on the parent loader
property bool editing: parent?.editing ?? false
property bool renderMarkdown: parent?.renderMarkdown ?? true
property bool enableMouseSelection: parent?.enableMouseSelection ?? false
property string segmentContent: parent?.segmentContent ?? ({})
property var messageData: parent?.messageData ?? {}
property bool done: parent?.done ?? true
property list<string> renderedLatexHashes: []
property bool editing: false
property bool renderMarkdown: true
property bool enableMouseSelection: false
property var segmentContent: ({})
property var messageData: {}
property bool done: true
property bool forceDisableChunkSplitting: false
property list<string> renderedLatexHashes: []
property string renderedSegmentContent: ""
property string shownText: ""
property bool forceDisableChunkSplitting: parent?.forceDisableChunkSplitting ?? false
property bool fadeChunkSplitting: !forceDisableChunkSplitting && !editing && !/\n\|/.test(shownText) && Config.options.sidebar.ai.textFadeIn
Layout.fillWidth: true
@@ -11,13 +11,13 @@ import Qt5Compat.GraphicalEffects
Item {
id: root
// These are needed on the parent loader
property bool editing: parent?.editing ?? false
property bool renderMarkdown: parent?.renderMarkdown ?? true
property bool enableMouseSelection: parent?.enableMouseSelection ?? false
property string segmentContent: parent?.segmentContent ?? ({})
property var messageData: parent?.messageData ?? {}
property bool done: parent?.done ?? true
property bool completed: parent?.completed ?? false
property bool editing: false
property bool renderMarkdown: true
property bool enableMouseSelection: false
property var segmentContent: ({})
property var messageData: {}
property bool done: true
property bool completed: false
property real thinkBlockBackgroundRounding: Appearance.rounding.small
property real thinkBlockHeaderPaddingVertical: 3
@@ -100,9 +100,7 @@ Rectangle {
id: tagsFlickable
visible: root.responseData.tags.length > 0
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: {
return true
}
Layout.fillWidth: true
implicitHeight: tagRowLayout.implicitHeight
contentWidth: tagRowLayout.implicitWidth
@@ -31,37 +31,12 @@ Item {
}
// Placeholder when list is empty
Item {
anchors.fill: listview
visible: opacity > 0
opacity: (Notifications.list.length === 0) ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: Appearance.animation.menuDecel.duration
easing.type: Appearance.animation.menuDecel.type
}
}
ColumnLayout {
anchors.centerIn: parent
spacing: 5
MaterialSymbol {
Layout.alignment: Qt.AlignHCenter
iconSize: 55
color: Appearance.m3colors.m3outline
text: "notifications_active"
}
StyledText {
Layout.alignment: Qt.AlignHCenter
font.pixelSize: Appearance.font.pixelSize.normal
color: Appearance.m3colors.m3outline
horizontalAlignment: Text.AlignHCenter
text: Translation.tr("No notifications")
}
}
PagePlaceholder {
shown: Notifications.list.length === 0
icon: "notifications_active"
description: Translation.tr("Nothing")
shape: MaterialShape.Shape.Ghostish
descriptionHorizontalAlignment: Text.AlignHCenter
}
ButtonGroup {
@@ -29,7 +29,7 @@ AbstractQuickPanel {
readonly property real baseCellHeight: 56
// 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 list<var> toggles: Config.ready ? Config.options.sidebar.quickToggles.android.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
} }
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 wallpaper_path="$3"
handle_kde_material_you_colors &
# Determine the largest region on the wallpaper that's sufficiently un-busy to put widgets in
# if [ ! -f "$MATUGEN_DIR/scripts/least_busy_region.py" ]; then
# echo "Error: least_busy_region.py script not found in $MATUGEN_DIR/scripts/"
# else
# "$MATUGEN_DIR/scripts/least_busy_region.py" \
# --screen-width "$screen_width" --screen-height "$screen_height" \
# --width 300 --height 200 \
# "$wallpaper_path" > "$STATE_DIR"/user/generated/wallpaper/least_busy_region.json
# fi
"$SCRIPT_DIR/code/material-code-set-color.sh" &
}
check_and_prompt_upscale() {
@@ -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);
}
function regenerate(messageIndex) {
if (messageIndex < 0 || messageIndex >= messageIDs.length) return;
const id = root.messageIDs[messageIndex];
const message = root.messageByID[id];
if (message.role !== "assistant") return;
// Remove all messages after this one
for (let i = root.messageIDs.length - 1; i >= messageIndex; i--) {
root.removeMessage(i);
}
requester.makeRequest();
}
function createFunctionOutputMessage(name, output, includeOutputInChat = true) {
return aiMessageComponent.createObject(root, {
"role": "user",
@@ -29,12 +29,18 @@ Singleton {
property real lastVolume: 0
function onVolumeChanged() {
if (!Config.options.audio.protection.enable) return;
const newVolume = sink.audio.volume;
// when resuming from suspend, we should not write volume to avoid pipewire volume reset issues
if (isNaN(newVolume) || newVolume === undefined || newVolume === null) {
lastReady = false;
lastVolume = 0;
return;
}
if (!lastReady) {
lastVolume = sink.audio.volume;
lastVolume = newVolume;
lastReady = true;
return;
}
const newVolume = sink.audio.volume;
const maxAllowedIncrease = Config.options.audio.protection.maxAllowedIncrease / 100;
const maxAllowed = Config.options.audio.protection.maxAllowed / 100;
@@ -45,9 +51,6 @@ Singleton {
root.sinkProtectionTriggered(Translation.tr("Exceeded max allowed"));
sink.audio.volume = Math.min(lastVolume, maxAllowed);
}
if (sink.ready && (isNaN(sink.audio.volume) || sink.audio.volume === undefined || sink.audio.volume === null)) {
sink.audio.volume = 0;
}
lastVolume = sink.audio.volume;
}
}
@@ -16,8 +16,6 @@ import QtQuick
*/
Singleton {
id: root
property real minimumBrightnessAllowed: 0.00001 // Setting to 0 would kind of turn off the screen. We don't want that.
signal brightnessChanged()
property var ddcMonitors: []
@@ -137,14 +135,14 @@ Singleton {
}
function syncBrightness() {
const brightnessValue = Math.max(monitor.multipliedBrightness, root.minimumBrightnessAllowed)
const rounded = Math.round(brightnessValue * monitor.rawMaxBrightness);
setProc.command = isDdc ? ["ddcutil", "-b", busNum, "setvcp", "10", rounded] : ["brightnessctl", "--class", "backlight", "s", rounded, "--quiet"];
const brightnessValue = Math.max(monitor.multipliedBrightness, 0)
const rawValueRounded = Math.max(Math.floor(brightnessValue * monitor.rawMaxBrightness), 1);
setProc.command = isDdc ? ["ddcutil", "-b", busNum, "setvcp", "10", rawValueRounded] : ["brightnessctl", "--class", "backlight", "s", rawValueRounded, "--quiet"];
setProc.startDetached();
}
function setBrightness(value: real): void {
value = Math.max(root.minimumBrightnessAllowed, Math.min(1, value));
value = Math.max(0, Math.min(1, value));
monitor.brightness = value;
}
+18 -3
View File
@@ -10,6 +10,7 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Window
import Quickshell
import qs.services
import qs.modules.common
import qs.modules.common.widgets
@@ -169,15 +170,29 @@ ApplicationWindow {
FloatingActionButton {
id: fab
iconText: "edit"
buttonText: Translation.tr("Config file")
property bool justCopied: false
iconText: justCopied ? "check" : "edit"
buttonText: justCopied ? Translation.tr("Path copied") : Translation.tr("Config file")
expanded: navRail.expanded
downAction: () => {
Qt.openUrlExternally(`${Directories.config}/illogical-impulse/config.json`);
}
altAction: () => {
Quickshell.clipboardText = CF.FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse/config.json`);
fab.justCopied = true;
revertTextTimer.restart()
}
Timer {
id: revertTextTimer
interval: 1500
onTriggered: {
fab.justCopied = false;
}
}
StyledToolTip {
text: Translation.tr("Open the shell config file.\nIf the button doesn't work or doesn't open in your favorite editor,\nyou can manually open ~/.config/illogical-impulse/config.json")
text: Translation.tr("Open the shell config file\nAlternatively right-click to copy path")
}
}
@@ -1,20 +1,22 @@
pkgname=illogical-impulse-basic
pkgver=1.0
pkgrel=1
pkgrel=2
pkgdesc='Illogical Impulse Basic Dependencies'
arch=(any)
license=(None)
depends=(
axel
bc
coreutils
cliphist
cmake
curl
rsync
wget
ripgrep
jq
meson
xdg-user-dirs
axel
bc
coreutils
cliphist
cmake
curl
wget
ripgrep
jq
meson
xdg-user-dirs
# Used in install script
rsync
go-yq # https://github.com/mikefarah/yq
)
@@ -25,4 +25,5 @@ RDEPEND="
dev-python/jq
dev-build/meson
x11-misc/xdg-user-dirs
app-misc/yq-go
"
+2 -1
View File
@@ -24,6 +24,7 @@ printf "${STY_RST}"
pause
x sudo emerge --noreplace --quiet app-eselect/eselect-repository
x sudo emerge --noreplace --quiet app-portage/smart-live-rebuild
if [[ -z $(eselect repository list | grep localrepo) ]]; then
v sudo eselect repository create localrepo
@@ -53,6 +54,7 @@ v sudo sh -c 'cat ./sdata/dist-gentoo/additional-useflags >> /etc/portage/packag
# Update system
v sudo emerge --sync
v sudo emerge --quiet --newuse --update --deep @world
v sudo emerge --quiet @smart-live-rebuild
v sudo emerge --depclean
# Remove old ebuilds (if this isn't done the wildcard will fuck upon a version change)
@@ -80,7 +82,6 @@ v sudo ebuild ${ebuild_dir}/dev-libs/hyprlang/hyprlang*9999.ebuild digest
v sudo ebuild ${ebuild_dir}/dev-util/hyprwayland-scanner/hyprwayland-scanner*9999.ebuild digest
###### LIVE EBUILDS END
# Install dependencies
for i in "${metapkgs[@]}"; do
x sudo mkdir -p ${ebuild_dir}/app-misc/${i}
+127
View File
@@ -2,3 +2,130 @@
# It's not for directly running.
# This file is currently WIP.
function install_home-manager(){
# https://nix-community.github.io/home-manager/index.xhtml#sec-install-standalone
local cmd=home-manager
# Maybe installed already, just not sourced yet
try source $HOME/.nix-profile/etc/profile.d/hm-session-vars.sh
command -v $cmd && return
x nix-channel --add https://nixos.org/channels/nixos-25.05 nixpkgs-home
x nix-channel --add https://github.com/nix-community/home-manager/archive/release-25.05.tar.gz home-manager
x nix-channel --update
x env NIX_PATH="nixpkgs=$HOME/.nix-defexpr/channels/nixpkgs-home" nix-shell '<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
}
function remove_bashcomments_emptylines(){
mkdir -p "$(dirname "$2")" && cat "$1" | sed -e 's/#.*//' -e '/^[[:space:]]*$/d' > "$2"
echo "pwd=$(pwd)"
echo "input=$1"
echo "output=$2"
mkdir -p "$(dirname "$2")"
cat "$1" | sed -e 's/#.*//' -e '/^[[:space:]]*$/d' > "$2"
}
function prevent_sudo_or_root(){
case $(whoami) in
@@ -287,3 +291,71 @@ function check_disk_space() {
return 0
}
function auto_get_git_submodule(){
local git_submodules_list=()
while IFS= read -r path; do
[ -z "$path" ] && continue
git_submodules_list+=("$path")
done < <(git submodule status --recursive 2>/dev/null | awk '{print $2}')
local missing=0
for p in "${git_submodules_list[@]}"; do
if [ ! -d "$p" ] || [ -z "$(ls -A "$p" 2>/dev/null)" ]; then
missing=1
break
fi
done
if [ "$missing" -eq 1 ]; then
x git submodule update --init --recursive
fi
}
function backup_clashing_targets(){
# For non-recursive dirs/files under target_dir, only backup those which clashes with the ones under source_dir
# However, ignore the ones listed in ignored_list
# Deal with arguments
local source_dir="$1"
local target_dir="$2"
local backup_dir="$3"
local -a ignored_list=("${@:4}")
# Find clash dirs/files, save as clash_list
local clash_list=()
local source_list=($(ls -A "$source_dir"))
local target_list=($(ls -A "$target_dir"))
local -A target_map
for i in "${target_list[@]}"; do
target_map["$i"]=1
done
for i in "${source_list[@]}"; do
if [[ -n "${target_map[$i]}" ]]; then
clash_list+=("$i")
fi
done
local -A delk
for del in "${ignored_list[@]}" ; do delk[$del]=1 ; done
for k in "${!clash_list[@]}" ; do
[ "${delk[${clash_list[$k]}]-}" ] && unset 'clash_list[k]'
done
clash_list=("${clash_list[@]}")
# Construct args_includes for rsync
local args_includes=()
for i in "${clash_list[@]}"; do
if [[ -d "$target_dir/$i" ]]; then
args_includes+=(--include="/$i/")
args_includes+=(--include="/$i/**")
else
args_includes+=(--include="/$i")
fi
done
args_includes+=(--exclude='*')
x mkdir -p $backup_dir
x rsync -av --progress "${args_includes[@]}" "$target_dir/" "$backup_dir/"
}
+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..."
if git pull; then
log_success "Successfully pulled latest changes"
git submodule update --init --recursive
else
log_warning "Failed to pull changes from remote. Continuing with local repository..."
log_info "You may need to resolve conflicts manually later."
@@ -3,6 +3,16 @@
# shellcheck shell=bash
#####################################################################################
# Notes by @clsty:
#
# I'm not the one who developed this script (see issue#2284 which discussed about the history).
# However it contains many unnecessary logics. This is typically what AI will do.
# I don't really care if it's AI-generated or not, it's just an extra option in addition to ./setup install, so as long as the users say it works, it should be fine.
# However, it's not easy to maintain something like this.
# The redundant logic should be cleaned up someday.
#
# This also applies for exp-update.tester.sh, TBH I don't think that file is really needed, and it also looks like AI-generated. Just guessing though.
#####################################################################################
#
# exp-update.sh - Enhanced dotfiles update script
@@ -834,6 +844,7 @@ if git remote get-url origin &>/dev/null; then
else
if git pull --ff-only; then
log_success "Successfully pulled latest changes"
git submodule update --init --recursive
# Verify we actually got new commits
if git rev-parse --verify HEAD@{1} &>/dev/null; then
if [[ "$(git rev-parse HEAD)" == "$(git rev-parse HEAD@{1})" ]]; then
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
#
# exp-update-tester.sh - Test suite for exp-update.sh
# exp-update-tester.sh - Test suite for exp-update
#
set -euo pipefail
@@ -129,7 +129,7 @@ log_header() { :; }
log_die() { echo "ERROR: \$1"; exit 1; }
STY_CYAN="" STY_RST="" STY_YELLOW=""
# Set required environment variables for exp-update.sh
# Set required environment variables for exp-update/0.run.sh
SKIP_NOTICE=true
REPO_ROOT="\$1"
CHECK_PACKAGES=false
@@ -139,7 +139,7 @@ VERBOSE=false
NON_INTERACTIVE=true
SOURCE_ONLY=true
source "$ORIGINAL_DIR/sdata/step/exp-update.sh"
source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh"
detected_dirs=\$(detect_repo_structure)
if [[ -n "\$detected_dirs" ]]; then
read -ra MONITOR_DIRS <<<"\$detected_dirs"
@@ -190,7 +190,7 @@ log_success() { :; }
log_header() { :; }
log_die() { echo "ERROR: \$1"; exit 1; }
# Set required environment variables for exp-update.sh
# Set required environment variables for exp-update
SKIP_NOTICE=true
REPO_ROOT="\$1"
CHECK_PACKAGES=false
@@ -200,7 +200,7 @@ VERBOSE=false
NON_INTERACTIVE=true
SOURCE_ONLY=true
source "$ORIGINAL_DIR/sdata/step/exp-update.sh"
source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh"
detected_dirs=\$(detect_repo_structure)
if [[ -n "\$detected_dirs" ]]; then
read -ra MONITOR_DIRS <<<"\$detected_dirs"
@@ -276,7 +276,7 @@ log_success() { :; }
log_header() { :; }
log_die() { echo "ERROR: \$1" >&2; exit 1; }
# FIXED: Set REPO_ROOT before sourcing exp-update.sh
# FIXED: Set REPO_ROOT before sourcing exp-update
REPO_ROOT="\$1"
export REPO_ROOT
@@ -293,7 +293,7 @@ HOME_UPDATE_IGNORE_FILE="/dev/null"
# Source the production script to use the real should_ignore function
# Redirect all unwanted output to stderr, then to /dev/null
source "$ORIGINAL_DIR/sdata/step/exp-update.sh" 2>/dev/null
source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh" 2>/dev/null
test_cases=(
"\$REPO_ROOT/app.log:0"
@@ -348,7 +348,7 @@ test_safe_read_security() {
log_test "Testing safe_read uses secure assignment (printf -v)"
local safe_read_function
safe_read_function=$(awk '/^safe_read\(\) \{/,/^\}/' "$ORIGINAL_DIR/sdata/step/exp-update.sh")
safe_read_function=$(awk '/^safe_read\(\) \{/,/^\}/' "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh")
if [[ -z "$safe_read_function" ]]; then
log_fail "Could not find safe_read function"
@@ -547,7 +547,7 @@ log_success() { :; }
log_header() { :; }
log_die() { echo "ERROR: \$1" >&2; exit 1; }
# FIXED: Set REPO_ROOT before sourcing exp-update.sh
# FIXED: Set REPO_ROOT before sourcing exp-update
REPO_ROOT="\$1"
export REPO_ROOT
@@ -563,7 +563,7 @@ UPDATE_IGNORE_FILE="\${REPO_ROOT}/.updateignore"
HOME_UPDATE_IGNORE_FILE="/dev/null"
# Source the production script to use the real should_ignore function
source "$ORIGINAL_DIR/sdata/step/exp-update.sh" 2>/dev/null
source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh" 2>/dev/null
# Load patterns into cache
load_ignore_patterns
@@ -649,7 +649,7 @@ VERBOSE=false
NON_INTERACTIVE=true
SOURCE_ONLY=true
source "$ORIGINAL_DIR/sdata/step/exp-update.sh" 2>/dev/null
source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh" 2>/dev/null
test_dir="/tmp/test-ensure-dir-\$\$"
@@ -9,10 +9,8 @@ printf "${STY_CYAN}[$0]: Hi there! Before we start:${STY_RST}\n"
printf "\n"
printf "${STY_PURPLE}${STY_BOLD}[NEW] illogical-impulse is now powered by Quickshell.${STY_RST}\n"
printf "${STY_PURPLE}"
printf '# NOTE: illogical-impulse on AGS is no longer supported.\n'
printf '# If you were using the old version with AGS and would like to keep it, do not run this script.\n'
printf '# The AGS version, although uses less memory, has much worse performance (it uses Gtk3). \n'
printf '# If you aren'\''t running on ewaste, the Quickshell version is recommended. \n'
printf "# If you would like the AGS version anyway, run the following to switch to its branch first:\n ${STY_INVERT} git checkout ii-ags && ./install.sh ${STY_RST}\n"
printf "\n"
pause
printf "${STY_CYAN}${STY_BOLD}Quick overview about what this script does:${STY_RST}\n"
@@ -1,9 +1,10 @@
# This script is meant to be sourced.
# It's not for directly running.
printf "${STY_CYAN}[$0]: 1. Install dependencies\n${STY_RST}"
function outdate_detect(){
# Shallow clone prevent latest_commit_timestamp() from working.
v git_auto_unshallow
x git_auto_unshallow
local source_path="$1"
local target_path="$2"
@@ -1,5 +1,6 @@
# This script is meant to be sourced.
# It's not for directly running.
printf "${STY_CYAN}[$0]: 2. Setup for permissions/services etc\n${STY_RST}"
# shellcheck shell=bash
+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.
# It's not for directly running.
printf "${STY_CYAN}[$0]: 3. Copying config files\n${STY_RST}"
# shellcheck shell=bash
# TODO: https://github.com/end-4/dots-hyprland/issues/2137
function warning_rsync(){
function warning_rsync_delete(){
printf "${STY_YELLOW}"
printf "The commands using rsync will overwrite the destination when it exists already.\n"
printf "The command below uses --delete for rsync which overwrites the destination folder.\n"
printf "${STY_RST}"
}
function backup_clashing_targets(){
# For dirs/files under target_dir, only backup those which clashes with the ones under source_dir
function warning_rsync_normal(){
printf "${STY_YELLOW}"
printf "The command below uses rsync which overwrites the destination.\n"
printf "${STY_RST}"
}
# Deal with arguments
local source_dir="$1"
local target_dir="$2"
local backup_dir="$3"
# Find clash dirs/files, save as clash_list
local clash_list=()
local source_list=($(ls -A "$source_dir"))
local target_list=($(ls -A "$target_dir"))
declare -A target_map
for i in "${target_list[@]}"; do
target_map["$i"]=1
done
for i in "${source_list[@]}"; do
if [[ -n "${target_map[$i]}" ]]; then
clash_list+=("$i")
fi
done
# Construct args_includes for rsync
local args_includes=()
for i in "${clash_list[@]}"; do
if [[ -d "$target_dir/$i" ]]; then
args_includes+=(--include="/$i/")
args_includes+=(--include="/$i/**")
else
args_includes+=(--include="/$i")
fi
done
args_includes+=(--exclude='*')
x mkdir -p $backup_dir
x rsync -av --progress "${args_includes[@]}" "$target_dir/" "$backup_dir/"
function backup_configs(){
backup_clashing_targets dots/.config $XDG_CONFIG_HOME "${BACKUP_DIR}/.config"
backup_clashing_targets dots/.local/share $XDG_DATA_HOME "${BACKUP_DIR}/.local/share"
printf "${STY_BLUE}Backup into \"${BACKUP_DIR}\" finished.${STY_RST}\n"
}
function ask_backup_configs(){
showfun backup_clashing_targets
printf "${STY_RED}"
printf "Would you like to backup clashing dirs/files under \"$XDG_CONFIG_HOME\" and \"$XDG_DATA_HOME\" to \"$BACKUP_DIR\"?"
printf "Would you like to backup clashing dirs/files under \"$XDG_CONFIG_HOME\" and \"$XDG_DATA_HOME\" to \"$BACKUP_DIR\"?\n"
printf "${STY_RST}"
while true;do
echo " y = Yes, backup"
echo " n = No, skip to next"
echo " n/s = No, skip to next"
local p; read -p "====> " p
case $p in
[yY]) echo -e "${STY_BLUE}OK, doing backup...${STY_RST}" ;local backup=true;break ;;
[nN]) echo -e "${STY_BLUE}Alright, skipping...${STY_RST}" ;local backup=false;break ;;
[nNsS]) echo -e "${STY_BLUE}Alright, skipping...${STY_RST}" ;local backup=false;break ;;
*) echo -e "${STY_RED}Please enter [y/n].${STY_RST}";;
esac
done
if $backup;then
backup_clashing_targets dots/.config $XDG_CONFIG_HOME "${BACKUP_DIR}/.config"
backup_clashing_targets dots/.local/share $XDG_DATA_HOME "${BACKUP_DIR}/.local/share"
fi
if $backup;then backup_configs;fi
}
function auto_backup_configs(){
# Backup when $BACKUP_DIR does not exist
if [[ ! -d "$BACKUP_DIR" ]]; then backup_configs;fi
}
#####################################################################################
showfun auto_get_git_submodule
v auto_get_git_submodule
# In case some dirs does not exists
v mkdir -p $XDG_BIN_HOME $XDG_CACHE_HOME $XDG_CONFIG_HOME/quickshell $XDG_DATA_HOME
v mkdir -p $XDG_BIN_HOME $XDG_CACHE_HOME $XDG_CONFIG_HOME $XDG_DATA_HOME/icons
case $ask in
false) sleep 0 ;;
*) ask_backup_configs ;;
esac
if [[ ! "${SKIP_BACKUP}" == true ]]; then
case $ask in
false) auto_backup_configs ;;
*) ask_backup_configs ;;
esac
fi
# TODO: A better method for users to choose their customization,
# for example some users may prefer ZSH over FISH, and foot over kitty.
@@ -90,34 +70,45 @@ esac
# original dotfiles and new ones in the SAME DIRECTORY
# (eg. in ~/.config/hypr) won't be mixed together
# MISC (For dots/.config/* but not quickshell, not fish, not Hyprland)
# MISC (For dots/.config/* but not quickshell, not fish, not Hyprland, not fontconfig)
case $SKIP_MISCCONF in
true) sleep 0;;
*)
for i in $(find dots/.config/ -mindepth 1 -maxdepth 1 ! -name 'quickshell' ! -name 'fish' ! -name 'hypr' -exec basename {} \;); do
for i in $(find dots/.config/ -mindepth 1 -maxdepth 1 ! -name 'quickshell' ! -name 'fish' ! -name 'hypr' ! -name 'fontconfig' -exec basename {} \;); do
# i="dots/.config/$i"
echo "[$0]: Found target: dots/.config/$i"
if [ -d "dots/.config/$i" ];then warning_rsync; v rsync -av --delete "dots/.config/$i/" "$XDG_CONFIG_HOME/$i/"
elif [ -f "dots/.config/$i" ];then warning_rsync; v rsync -av "dots/.config/$i" "$XDG_CONFIG_HOME/$i"
if [ -d "dots/.config/$i" ];then warning_rsync_delete; v rsync -av --delete "dots/.config/$i/" "$XDG_CONFIG_HOME/$i/"
elif [ -f "dots/.config/$i" ];then warning_rsync_normal; v rsync -av "dots/.config/$i" "$XDG_CONFIG_HOME/$i"
fi
done
warning_rsync_delete; v rsync -av "dots/.local/share/konsole/" "${XDG_DATA_HOME:-$HOME/.local/share}"/konsole/
;;
esac
case $SKIP_QUICKSHELL in
true) sleep 0;;
*)
warning_rsync; v rsync -av --delete dots/.config/quickshell/ii/ "$XDG_CONFIG_HOME"/quickshell/ii/
# Should overwriting the whole directory not only ~/.config/quickshell/ii/ cuz https://github.com/end-4/dots-hyprland/issues/2294#issuecomment-3448671064
warning_rsync_delete; v rsync -av --delete dots/.config/quickshell/ "$XDG_CONFIG_HOME"/quickshell/
;;
esac
case $SKIP_FISH in
true) sleep 0;;
*)
warning_rsync; v rsync -av --delete dots/.config/fish/ "$XDG_CONFIG_HOME"/fish/
warning_rsync_delete; v rsync -av --delete dots/.config/fish/ "$XDG_CONFIG_HOME"/fish/
;;
esac
case $SKIP_FONTCONFIG in
true) sleep 0;;
*)
case "$II_FONTSET_NAME" in
"") warning_rsync_delete; v rsync -av --delete dots/.config/fontconfig/ "$XDG_CONFIG_HOME"/fontconfig/ ;;
*) warning_rsync_delete; v rsync -av --delete dots-extra/fontsets/$II_FONTSET_NAME/ "$XDG_CONFIG_HOME"/fontconfig/ ;;
esac;;
esac
# For Hyprland
declare -a arg_excludes=()
arg_excludes+=(--exclude '/custom')
@@ -127,17 +118,25 @@ arg_excludes+=(--exclude '/hyprland.conf')
case $SKIP_HYPRLAND in
true) sleep 0;;
*)
warning_rsync; v rsync -av --delete "${arg_excludes[@]}" dots/.config/hypr/ "$XDG_CONFIG_HOME"/hypr/
warning_rsync_delete; v rsync -av --delete "${arg_excludes[@]}" dots/.config/hypr/ "$XDG_CONFIG_HOME"/hypr/
# When hypr/custom does not exist, we assume that it's the firstrun.
if [ -d "$XDG_CONFIG_HOME/hypr/custom" ];then ii_firstrun=false;else ii_firstrun=true;fi
t="$XDG_CONFIG_HOME/hypr/hyprland.conf"
if [ -f $t ];then
echo -e "${STY_BLUE}[$0]: \"$t\" already exists.${STY_RST}"
v mv $t $t.old
v cp -f dots/.config/hypr/hyprland.conf $t
existed_hypr_conf_firstrun=y
if $ii_firstrun;then
echo -e "${STY_BLUE}[$0]: It seems to be the firstrun.${STY_RST}"
v mv $t $t.old
v cp -f dots/.config/hypr/hyprland.conf $t
existed_hypr_conf_firstrun=y
else
echo -e "${STY_BLUE}[$0]: It seems not a firstrun.${STY_RST}"
v cp -f dots/.config/hypr/hyprland.conf $t.new
existed_hypr_conf=y
fi
else
echo -e "${STY_YELLOW}[$0]: \"$t\" does not exist yet.${STY_RST}"
v cp dots/.config/hypr/hyprland.conf $t
existed_hypr_conf=n
fi
t="$XDG_CONFIG_HOME/hypr/hypridle.conf"
if [ -f $t ];then
@@ -164,7 +163,7 @@ case $SKIP_HYPRLAND in
echo -e "${STY_BLUE}[$0]: \"$t\" already exists, will not do anything.${STY_RST}"
else
echo -e "${STY_YELLOW}[$0]: \"$t\" does not exist yet.${STY_RST}"
warning_rsync; v rsync -av --delete dots/.config/hypr/custom/ $t/
v rsync -av --delete dots/.config/hypr/custom/ $t/
fi
;;
esac
@@ -174,8 +173,7 @@ declare -a arg_excludes=()
# some foldes (eg. .local/bin) should be processed separately to avoid `--delete' for rsync,
# since the files here come from different places, not only about one program.
# v rsync -av "dots/.local/bin/" "$XDG_BIN_HOME" # No longer needed since scripts are no longer in ~/.local/bin
warning_rsync; v rsync -av "dots/.local/share/icons/" "${XDG_DATA_HOME:-$HOME/.local/share}"/icons/
warning_rsync; v rsync -av "dots/.local/share/konsole/" "${XDG_DATA_HOME:-$HOME/.local/share}"/konsole/
v cp -f "dots/.local/share/icons/illogical-impulse.svg" "${XDG_DATA_HOME}"/icons/illogical-impulse.svg
# Prevent hyprland from not fully loaded
sleep 1
+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-allfiles Skip the whole process copying configuration files
-s, --skip-sysupdate Skip system package upgrade e.g. \"sudo pacman -Syu\"
--skip-plasmaintg Skip installing plasma-browser-integration
--skip-backup Skip backup conflicting files
--skip-quickshell Skip installing the config for Quickshell
--skip-hyprland Skip installing the config for Hyprland
--skip-fish Skip installing the config for Fish
--skip-plasmaintg Skip installing plasma-browser-integration
--skip-fontconfig Skip installing the config for fontconfig
--skip-miscconf Skip copying the dirs and files to \".configs\" except for
Quickshell, Fish and Hyprland
--core Alias of --skip-{plasmaintg,fish,miscconf,fontconfig}
--exp-files Use experimental script for the third step copying files
--fontset <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
"
}
@@ -33,7 +37,7 @@ cleancache(){
# `man getopt` to see more
para=$(getopt \
-o hfk:cs \
-l help,force,fontset:,clean,skip-allgreeting,skip-alldeps,skip-allsetups,skip-allfiles,skip-sysupdate,skip-quickshell,skip-fish,skip-hyprland,skip-plasmaintg,skip-miscconf,exp-files,via-nix \
-l help,force,fontset:,clean,skip-allgreeting,skip-alldeps,skip-allsetups,skip-allfiles,skip-sysupdate,skip-plasmaintg,skip-backup,skip-quickshell,skip-fish,skip-hyprland,skip-fontconfig,skip-miscconf,core,exp-files,via-nix \
-n "$0" -- "$@")
[ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1
#####################################################################################
@@ -63,20 +67,23 @@ while true ; do
--skip-allsetups) SKIP_ALLSETUPS=true;shift;;
--skip-allfiles) SKIP_ALLFILES=true;shift;;
-s|--skip-sysupdate) SKIP_SYSUPDATE=true;shift;;
--skip-plasmaintg) SKIP_PLASMAINTG=true;shift;;
--skip-backup) SKIP_BACKUP=true;shift;;
--skip-hyprland) SKIP_HYPRLAND=true;shift;;
--skip-fish) SKIP_FISH=true;shift;;
--skip-quickshell) SKIP_QUICKSHELL=true;shift;;
--skip-fontconfig) SKIP_FONTCONFIG=true;shift;;
--skip-miscconf) SKIP_MISCCONF=true;shift;;
--skip-plasmaintg) SKIP_PLASMAINTG=true;shift;;
--core) SKIP_PLASMAINTG=true;SKIP_FISH=true;SKIP_FONTCONFIG=true;SKIP_MISCCONF=true;shift;;
--exp-files) EXPERIMENTAL_FILES_SCRIPT=true;shift;;
--via-nix) INSTALL_VIA_NIX=true;shift;;
## Ones with parameter
--fontset)
case $2 in
"default"|"zh-CN"|"vi") fontset="$2";;
*) echo -e "Wrong argument for $1.";exit 1;;
esac;echo "The fontset is ${fontset}.";shift 2;;
if [[ -d "${REPO_ROOT}/dots-extra/fontsets/$2" ]];
then echo "Using fontset \"$2\".";II_FONTSET_NAME="$2";shift 2
else echo "Wrong argument for $1.";exit 1
fi;;
## Ending
--) break ;;
+30 -37
View File
@@ -24,6 +24,7 @@ Subcommands:
exp-uninstall (Experimental) Uninstall illogical-impulse.
exp-update (Experimental) Update illogical-impulse without fully reinstall.
exp-update-old (Experimental) exp-update but use behaves like old version.
checkdeps (For dev only) Check whether pkgs exist in AUR or repos of Arch.
help Show this help message.
For each <subcommand>, use -h for details:
@@ -34,75 +35,67 @@ case $1 in
# Global help
help|--help|-h)showhelp_global;exit;;
# Correct subcommand
install|install-deps|install-setups|install-files|exp-uninstall|exp-update|exp-update-old)
SCRIPT_SUBCOMMAND=$1;shift;;
# No subcommand
-*|"")SCRIPT_SUBCOMMAND=install;;
install|exp-uninstall|exp-update|exp-update-old|checkdeps)
SUBCMD_NAME=$1
SUBCMD_DIR=./sdata/subcmd-$1
shift;;
# Correct subcommand but not using ./sdata/subcmd-$1
install-deps|install-setups|install-files)
SUBCMD_NAME=$1
SUBCMD_DIR=./sdata/subcmd-install
shift;;
# No subcommand, default to install
-*|"")
SUBCMD_NAME=install
SUBCMD_DIR=./sdata/subcmd-install
;;
# Wrong subcommand
*)printf "${STY_RED}Unknown subcommand \"$1\".${STY_RST}\n";showhelp_global;exit 1;;
esac
#####################################################################################
case ${SCRIPT_SUBCOMMAND} in
if [[ -f "${SUBCMD_DIR}/options.sh" ]];
then source "${SUBCMD_DIR}/options.sh"
fi
case ${SUBCMD_NAME} in
install)
source ./sdata/options/install.sh
if [[ "${SKIP_ALLGREETING}" != true ]]; then
source ./sdata/step/0.install-greeting.sh
source ${SUBCMD_DIR}/0.greeting.sh
fi
if [[ "${SKIP_ALLDEPS}" != true ]]; then
printf "${STY_CYAN}[$0]: 1. Install dependencies\n${STY_RST}"
source ./sdata/step/1.install-deps-selector.sh
source ${SUBCMD_DIR}/1.deps-selector.sh
fi
if [[ "${SKIP_ALLSETUPS}" != true ]]; then
printf "${STY_CYAN}[$0]: 2. Setup for permissions/services etc\n${STY_RST}"
source ./sdata/step/2.install-setups-selector.sh
source ${SUBCMD_DIR}/2.setups-selector.sh
fi
if [[ "${SKIP_ALLFILES}" != true ]]; then
printf "${STY_CYAN}[$0]: 3. Copying config files\n${STY_RST}"
if [[ "${EXPERIMENTAL_FILES_SCRIPT}" == true ]]; then
source ./sdata/step/3.install-files.experimental.sh
source ${SUBCMD_DIR}/3.files-exp.sh
else
source ./sdata/step/3.install-files.sh
source ${SUBCMD_DIR}/3.files.sh
fi
fi
;;
install-deps)
source ./sdata/options/install.sh
if [[ "${SKIP_ALLDEPS}" != true ]]; then
printf "${STY_CYAN}[$0]: 1. Install dependencies\n${STY_RST}"
source ./sdata/step/1.install-deps-selector.sh
source ${SUBCMD_DIR}/1.deps-selector.sh
fi
;;
install-setups)
source ./sdata/options/install.sh
if [[ "${SKIP_ALLSETUPS}" != true ]]; then
printf "${STY_CYAN}[$0]: 2. Setup for permissions/services etc\n${STY_RST}"
source ./sdata/step/2.install-setups-selector.sh
source ${SUBCMD_DIR}/2.setups-selector.sh
fi
;;
install-files)
source ./sdata/options/install.sh
if [[ "${SKIP_ALLFILES}" != true ]]; then
printf "${STY_CYAN}[$0]: 3. Copying config files\n${STY_RST}"
if [[ "${EXPERIMENTAL_FILES_SCRIPT}" == true ]]; then
source ./sdata/step/3.install-files.experimental.sh
source ${SUBCMD_DIR}/3.files-exp.sh
else
source ./sdata/step/3.install-files.sh
source ${SUBCMD_DIR}/3.files.sh
fi
fi
;;
exp-uninstall)
source ./sdata/options/exp-uninstall.sh
source ./sdata/step/exp-uninstall.sh
exit
;;
exp-update)
source ./sdata/options/exp-update.sh
source ./sdata/step/exp-update.sh
exit
;;
exp-update-old)
source ./sdata/options/exp-update-old.sh
source ./sdata/step/exp-update-old.sh
*)
source ${SUBCMD_DIR}/0.run.sh
exit
;;
esac