forked from Shinonome/dots-hyprland
Merge branch 'main' into main
This commit is contained in:
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user