forked from Shinonome/dots-hyprland
Merge branch 'main' into main
This commit is contained in:
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
+4
-3
@@ -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
-2
@@ -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 {
|
||||
|
||||
+10
-5
@@ -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();
|
||||
|
||||
Submodule dots/.config/quickshell/ii/modules/common/widgets/shapes added at 8369a081ba
@@ -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)
|
||||
|
||||
+102
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
+1
@@ -25,4 +25,5 @@ RDEPEND="
|
||||
dev-python/jq
|
||||
dev-build/meson
|
||||
x11-misc/xdg-user-dirs
|
||||
app-misc/yq-go
|
||||
"
|
||||
@@ -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}
|
||||
|
||||
@@ -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
@@ -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/"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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 ;;
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user