Merge branch 'end-4:main' into keybinds-settings

This commit is contained in:
Madjid Taha
2025-11-04 17:21:27 +01:00
committed by GitHub
89 changed files with 1061 additions and 1212 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ bind = Shift+Super+Alt, Slash, exec, qs -p ~/.config/quickshell/$qsConfig/welcom
bindle=, XF86MonBrightnessUp, exec, qs -c $qsConfig ipc call brightness increment || brightnessctl s 5%+ # [hidden]
bindle=, XF86MonBrightnessDown, exec, qs -c $qsConfig ipc call brightness decrement || brightnessctl s 5%- # [hidden]
bindle=, XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%+ # [hidden]
bindle=, XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%+ -l 1.5# [hidden]
bindle=, XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%- # [hidden]
bindl = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_SINK@ toggle # [hidden]
+1
View File
@@ -144,6 +144,7 @@ layerrule = animation slide bottom, quickshell:osk
layerrule = noanim, quickshell:polkit
layerrule = xray 0, quickshell:popup # No weird color for bar tooltips (this in theory should suffice)
layerrule = ignorealpha 1, quickshell:popup # No weird color for bar tooltips (but somehow this is necessary)
layerrule = ignorealpha 1, quickshell:mediaControls # Same as above
layerrule = noanim, quickshell:regionSelector
layerrule = noanim, quickshell:screenshot
layerrule = blur, quickshell:session
@@ -7,13 +7,16 @@ if pgrep -f 'geoclue-2.0/demos/agent' > /dev/null; then
fi
# List of known possible GeoClue agent paths
AGENT_PATHS="
/usr/libexec/geoclue-2.0/demos/agent
/usr/lib/geoclue-2.0/demos/agent
"
AGENT_PATHS=(
/usr/libexec/geoclue-2.0/demos/agent
/usr/lib/geoclue-2.0/demos/agent
"$HOME/.nix-profile/libexec/geoclue-2.0/demos/agent"
"$HOME/.nix-profile/lib/geoclue-2.0/demos/agent"
/run/current-system/sw/libexec/geoclue-2.0/demos/agent
)
# Find the first valid agent path
for path in $AGENT_PATHS; do
for path in "${AGENT_PATHS[@]}"; do
if [ -x "$path" ]; then
echo "Starting GeoClue agent from: $path"
"$path" & # starts in the background
@@ -334,7 +334,6 @@ Variants {
font {
pixelSize: Appearance.font.pixelSize.normal
weight: 350
italic: true
}
color: bgRoot.colText
style: Text.Raised
@@ -1,6 +1,7 @@
import qs
import qs.services
import qs.modules.common
import qs.modules.common.models
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
@@ -163,12 +164,12 @@ Item {
horizontalCenter: vertical ? parent.horizontalCenter : undefined
}
// idx1 is the "leading" indicator position, idx2 is the "following" one
// The former animates faster than the latter, see the NumberAnimations below
property real idx1: workspaceIndexInGroup
property real idx2: workspaceIndexInGroup
property real indicatorPosition: Math.min(idx1, idx2) * workspaceButtonWidth + root.activeWorkspaceMargin
property real indicatorLength: Math.abs(idx1 - idx2) * workspaceButtonWidth + workspaceButtonWidth - root.activeWorkspaceMargin * 2
AnimatedTabIndexPair {
id: idxPair
index: root.workspaceIndexInGroup
}
property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * workspaceButtonWidth + root.activeWorkspaceMargin
property real indicatorLength: Math.abs(idxPair.idx1 - idxPair.idx2) * workspaceButtonWidth + workspaceButtonWidth - root.activeWorkspaceMargin * 2
property real indicatorThickness: workspaceButtonWidth - root.activeWorkspaceMargin * 2
x: root.vertical ? null : indicatorPosition
@@ -176,18 +177,6 @@ Item {
y: root.vertical ? indicatorPosition : null
implicitHeight: root.vertical ? indicatorLength : indicatorThickness
Behavior on idx1 {
NumberAnimation {
duration: 100
easing.type: Easing.OutSine
}
}
Behavior on idx2 {
NumberAnimation {
duration: 300
easing.type: Easing.OutSine
}
}
}
// Workspaces - numbers
@@ -31,7 +31,6 @@ Scope { // Scope
sourceComponent: PanelWindow { // Window
id: cheatsheetRoot
visible: cheatsheetLoader.active
property int selectedTab: 0
anchors {
top: true
@@ -76,7 +75,7 @@ Scope { // Scope
border.width: 1
border.color: Appearance.colors.colLayer0Border
radius: Appearance.rounding.windowRounding
property real padding: 30
property real padding: 20
implicitWidth: cheatsheetColumnLayout.implicitWidth + padding * 2
implicitHeight: cheatsheetColumnLayout.implicitHeight + padding * 2
@@ -86,16 +85,16 @@ Scope { // Scope
}
if (event.modifiers === Qt.ControlModifier) {
if (event.key === Qt.Key_PageDown) {
cheatsheetRoot.selectedTab = Math.min(cheatsheetRoot.selectedTab + 1, root.tabButtonList.length - 1);
tabBar.incrementCurrentIndex();
event.accepted = true;
} else if (event.key === Qt.Key_PageUp) {
cheatsheetRoot.selectedTab = Math.max(cheatsheetRoot.selectedTab - 1, 0);
tabBar.decrementCurrentIndex();
event.accepted = true;
} else if (event.key === Qt.Key_Tab) {
cheatsheetRoot.selectedTab = (cheatsheetRoot.selectedTab + 1) % root.tabButtonList.length;
tabBar.setCurrentIndex((tabBar.currentIndex + 1) % root.tabButtonList.length);
event.accepted = true;
} else if (event.key === Qt.Key_Backtab) {
cheatsheetRoot.selectedTab = (cheatsheetRoot.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length;
tabBar.setCurrentIndex((tabBar.currentIndex - 1 + root.tabButtonList.length) % root.tabButtonList.length);
event.accepted = true;
}
}
@@ -129,23 +128,15 @@ Scope { // Scope
ColumnLayout { // Real content
id: cheatsheetColumnLayout
anchors.centerIn: parent
spacing: 20
spacing: 10
StyledText {
id: cheatsheetTitle
Toolbar {
Layout.alignment: Qt.AlignHCenter
font {
family: Appearance.font.family.title
pixelSize: Appearance.font.pixelSize.title
variableAxes: Appearance.font.variableAxes.title
}
text: Translation.tr("Cheat sheet")
}
PrimaryTabBar { // Tab strip
id: tabBar
tabButtonList: root.tabButtonList
Synchronizer on currentIndex {
property alias source: cheatsheetRoot.selectedTab
enableShadow: false
ToolbarTabBar {
id: tabBar
tabButtonList: root.tabButtonList
currentIndex: swipeView.currentIndex
}
}
@@ -154,26 +145,11 @@ Scope { // Scope
Layout.topMargin: 5
Layout.fillWidth: true
Layout.fillHeight: true
currentIndex: tabBar.currentIndex
spacing: 10
Behavior on implicitWidth {
id: contentWidthBehavior
enabled: false
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on implicitHeight {
id: contentHeightBehavior
enabled: false
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
currentIndex: cheatsheetRoot.selectedTab
onCurrentIndexChanged: {
contentWidthBehavior.enabled = true;
contentHeightBehavior.enabled = true;
tabBar.enableIndicatorAnimation = true;
cheatsheetRoot.selectedTab = currentIndex;
}
implicitWidth: Math.max.apply(null, contentChildren.map(child => child.implicitWidth || 0))
implicitHeight: Math.max.apply(null, contentChildren.map(child => child.implicitHeight || 0))
clip: true
layer.enabled: true
@@ -11,6 +11,7 @@ Item {
Column {
id: mainLayout
anchors.centerIn: parent
spacing: root.spacing
Repeater { // Main table rows
@@ -7,8 +7,8 @@ RippleButton {
id: root
required property var element
opacity: element.type != "empty" ? 1 : 0
implicitHeight: 60
implicitWidth: 60
implicitHeight: 70
implicitWidth: 70
colBackground: Appearance.colors.colLayer2
buttonRadius: Appearance.rounding.small
@@ -373,6 +373,7 @@ Singleton {
property real scale: 0.18 // Relative to screen size
property real rows: 2
property real columns: 5
property bool centerIcons: true
}
property JsonObject regionSelector: JsonObject {
@@ -0,0 +1,26 @@
import QtQuick
// idx1 is the "leading" indicator position, idx2 is the "following" one
// The former animates faster than the latter, see the NumberAnimations below
QtObject {
id: root
required property int index
property real idx1: index
property real idx2: index
property int idx1Duration: 100
property int idx2Duration: 300
Behavior on idx1 {
NumberAnimation {
duration: root.idx1Duration
easing.type: Easing.OutSine
}
}
Behavior on idx2 {
NumberAnimation {
duration: root.idx2Duration
easing.type: Easing.OutSine
}
}
}
@@ -13,7 +13,7 @@ ToolbarButton {
contentItem: Row {
anchors.centerIn: parent
spacing: 6
spacing: 4
MaterialSymbol {
anchors.verticalCenter: parent.verticalCenter
@@ -1,95 +0,0 @@
import qs.modules.common
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")}]
property int currentIndex
property bool enableIndicatorAnimation: false
property color colIndicator: Appearance?.colors.colPrimary ?? "#65558F"
property color colBorder: Appearance?.m3colors.m3outlineVariant ?? "#C6C6D0"
onCurrentIndexChanged: {
enableIndicatorAnimation = true
}
property bool centerTabBar: parent.width > 500
Layout.fillWidth: !centerTabBar
Layout.alignment: Qt.AlignHCenter
implicitWidth: Math.max(tabBar.implicitWidth, 600)
TabBar {
id: tabBar
Layout.fillWidth: true
Synchronizer on currentIndex {
property alias source: root.currentIndex
}
background: Item {
WheelHandler {
onWheel: (event) => {
if (event.angleDelta.y < 0)
tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1)
else if (event.angleDelta.y > 0)
tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0)
}
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
}
}
Repeater {
model: root.tabButtonList
delegate: PrimaryTabButton {
selected: (index == root.currentIndex)
buttonText: modelData.name
buttonIcon: modelData.icon
minimumWidth: 160
onClicked: root.currentIndex = index
}
}
}
Item { // Tab indicator
id: tabIndicator
Layout.fillWidth: true
height: 3
Rectangle {
id: indicator
property int tabCount: root.tabButtonList.length
property real fullTabSize: root.width / tabCount;
property real targetWidth: tabBar.contentItem?.children[0]?.children[tabBar.currentIndex]?.tabContentWidth ?? 0
implicitWidth: targetWidth
anchors {
top: parent.top
bottom: parent.bottom
}
x: tabBar.currentIndex * fullTabSize + (fullTabSize - targetWidth) / 2
color: root.colIndicator
radius: Appearance?.rounding.full ?? 9999
Behavior on x {
animation: Appearance?.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on implicitWidth {
animation: Appearance?.animation.elementMove.numberAnimation.createObject(this)
}
}
}
Rectangle { // Tabbar bottom border
id: tabBarBottomBorder
Layout.fillWidth: true
implicitHeight: 1
color: root.colBorder
}
}
@@ -1,172 +0,0 @@
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
TabButton {
id: button
property string buttonText
property string buttonIcon
property real minimumWidth: 110
property bool selected: false
property int tabContentWidth: contentItem.children[0].implicitWidth
property int rippleDuration: 1200
height: buttonBackground.height
implicitWidth: Math.max(tabContentWidth, buttonBackground.implicitWidth, minimumWidth)
property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent"
property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED"
property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2"
property color colActive: Appearance?.colors.colPrimary ?? "#65558F"
property color colInactive: Appearance?.colors.colOnLayer1 ?? "#45464F"
component RippleAnim: NumberAnimation {
duration: rippleDuration
easing.type: Appearance?.animation.elementMoveEnter.type
easing.bezierCurve: Appearance?.animationCurves.standardDecel
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onPressed: (event) => {
button.click() // Because the MouseArea already consumed the event
const {x,y} = event
const stateY = buttonBackground.y;
rippleAnim.x = x;
rippleAnim.y = y - stateY;
const dist = (ox,oy) => ox*ox + oy*oy
const stateEndY = stateY + buttonBackground.height
rippleAnim.radius = Math.sqrt(Math.max(dist(0, stateY), dist(0, stateEndY), dist(width, stateY), dist(width, stateEndY)))
rippleFadeAnim.complete();
rippleAnim.restart();
}
onReleased: (event) => {
rippleFadeAnim.restart();
}
}
RippleAnim {
id: rippleFadeAnim
duration: rippleDuration * 2
target: ripple
property: "opacity"
to: 0
}
SequentialAnimation {
id: rippleAnim
property real x
property real y
property real radius
PropertyAction {
target: ripple
property: "x"
value: rippleAnim.x
}
PropertyAction {
target: ripple
property: "y"
value: rippleAnim.y
}
PropertyAction {
target: ripple
property: "opacity"
value: 1
}
ParallelAnimation {
RippleAnim {
target: ripple
properties: "implicitWidth,implicitHeight"
from: 0
to: rippleAnim.radius * 2
}
}
}
background: Rectangle {
id: buttonBackground
radius: Appearance?.rounding.small
implicitHeight: 50
color: (button.hovered ? button.colBackgroundHover : button.colBackground)
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: buttonBackground.width
height: buttonBackground.height
radius: buttonBackground.radius
}
}
Behavior on color {
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
}
Item {
id: ripple
width: ripple.implicitWidth
height: ripple.implicitHeight
opacity: 0
property real implicitWidth: 0
property real implicitHeight: 0
visible: width > 0 && height > 0
Behavior on opacity {
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
}
RadialGradient {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: button.colRipple }
GradientStop { position: 0.3; color: button.colRipple }
GradientStop { position: 0.5 ; color: Qt.rgba(button.colRipple.r, button.colRipple.g, button.colRipple.b, 0) }
}
}
transform: Translate {
x: -ripple.width / 2
y: -ripple.height / 2
}
}
}
contentItem: Item {
anchors.centerIn: buttonBackground
ColumnLayout {
anchors.centerIn: parent
spacing: 0
MaterialSymbol {
visible: buttonIcon?.length > 0
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
text: buttonIcon
iconSize: Appearance?.font.pixelSize.hugeass ?? 25
fill: selected ? 1 : 0
color: selected ? button.colActive : button.colInactive
Behavior on color {
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
StyledText {
id: buttonTextWidget
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Appearance?.font.pixelSize.small
color: selected ? button.colActive : button.colInactive
text: buttonText
Behavior on color {
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
}
}
}
@@ -10,6 +10,7 @@ import qs.modules.common.widgets
Item {
id: root
property bool enableShadow: true
property real padding: 8
property alias colBackground: background.color
property alias spacing: toolbarLayout.spacing
@@ -18,15 +19,20 @@ Item {
implicitHeight: background.implicitHeight
property alias radius: background.radius
StyledRectangularShadow {
target: background
Loader {
active: root.enableShadow
anchors.fill: background
sourceComponent: StyledRectangularShadow {
target: background
anchors.fill: undefined
}
}
Rectangle {
id: background
anchors.fill: parent
color: Appearance.m3colors.m3surfaceContainer
implicitHeight: Math.max(toolbarLayout.implicitHeight + root.padding * 2, 56)
implicitHeight: 56
implicitWidth: toolbarLayout.implicitWidth + root.padding * 2
radius: height / 2
@@ -0,0 +1,103 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.models
import qs.services
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt.labs.synchronizer
Item {
id: root
property alias currentIndex: tabBar.currentIndex
required property var tabButtonList
function incrementCurrentIndex() {
tabBar.incrementCurrentIndex()
}
function decrementCurrentIndex() {
tabBar.decrementCurrentIndex()
}
function setCurrentIndex(index) {
tabBar.setCurrentIndex(index)
}
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
implicitWidth: contentItem.implicitWidth
implicitHeight: 40
Row {
id: contentItem
z: 1
anchors.centerIn: parent
spacing: 4
Repeater {
model: root.tabButtonList
delegate: ToolbarTabButton {
required property int index
required property var modelData
current: index == root.currentIndex
text: modelData.name
materialSymbol: modelData.icon
onClicked: {
root.setCurrentIndex(index)
}
}
}
}
Rectangle {
id: activeIndicator
z: 0
color: Appearance.colors.colSecondaryContainer
implicitWidth: contentItem.children[root.currentIndex]?.implicitWidth ?? 0
implicitHeight: contentItem.children[root.currentIndex]?.implicitHeight ?? 0
radius: height / 2
// Animation
property Item targetItem: contentItem.children[root.currentIndex]
AnimatedTabIndexPair {
id: leftBound
idx1Duration: 50
idx2Duration: 200
index: activeIndicator.targetItem.x
}
AnimatedTabIndexPair {
id: rightBound
idx1Duration: 50
idx2Duration: 200
index: activeIndicator.targetItem.x + activeIndicator.targetItem.width
}
x: Math.min(leftBound.idx1, leftBound.idx2)
width: Math.max(rightBound.idx1, rightBound.idx2) - x
}
MouseArea {
anchors.fill: parent
z: 2
acceptedButtons: Qt.NoButton
cursorShape: Qt.PointingHandCursor
onWheel: (event) => {
if (event.angleDelta.y < 0) {
root.incrementCurrentIndex();
}
else {
root.decrementCurrentIndex();
}
}
}
// TabBar doesn't allow tabs to be of different sizes. Literally unusable.
// We use it only for the logic and draw stuff manually
TabBar {
id: tabBar
z: -1
background: null
Repeater { // This is to fool the TabBar that it has tabs so it does the indices properly
model: root.tabButtonList.length
delegate: TabButton {
background: null
}
}
}
}
@@ -0,0 +1,40 @@
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
RippleButton {
id: root
required property string materialSymbol
required property bool current
horizontalPadding: 10
implicitHeight: 40
implicitWidth: implicitContentWidth + horizontalPadding * 2
buttonRadius: height / 2
colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)
colBackgroundHover: ColorUtils.transparentize(Appearance.colors.colOnSurface, current ? 1 : 0.95)
colRipple: ColorUtils.transparentize(Appearance.colors.colOnSurface, 0.95)
contentItem: Row {
id: contentRow
anchors.centerIn: parent
spacing: 6
MaterialSymbol {
id: icon
anchors.verticalCenter: parent.verticalCenter
iconSize: 22
text: root.materialSymbol
}
StyledText {
id: label
anchors.verticalCenter: parent.verticalCenter
text: root.text
}
}
}
@@ -13,10 +13,16 @@ import Quickshell.Hyprland
Scope {
id: root
Process {
id: unlockKeyringProc
onExited: (exitCode, exitStatus) => {
KeyringStorage.fetchKeyringData();
}
}
function unlockKeyring() {
Quickshell.execDetached({
unlockKeyringProc.exec({
environment: ({
UNLOCK_PASSWORD: root.currentText
"UNLOCK_PASSWORD": lockContext.currentText
}),
command: ["bash", "-c", Quickshell.shellPath("scripts/keyring/unlock.sh")]
})
@@ -24,7 +30,7 @@ Scope {
property var windowData: []
function saveWindowPositionAndTile() {
Hyprland.dispatch(`keyword dwindle:pseudotile true`)
Quickshell.execDetached(["hyprctl", "keyword", "dwindle:pseudotile", "true"])
root.windowData = HyprlandData.windowList.filter(w => (w.floating && w.workspace.id === HyprlandData.activeWorkspace.id))
root.windowData.forEach(w => {
Hyprland.dispatch(`pseudo address:${w.address}`)
@@ -38,7 +44,7 @@ Scope {
Hyprland.dispatch(`movewindowpixel exact ${w.at[0]} ${w.at[1]}, address:${w.address}`)
Hyprland.dispatch(`pseudo address:${w.address}`)
})
Hyprland.dispatch(`keyword dwindle:pseudotile false`)
Quickshell.execDetached(["hyprctl", "keyword", "dwindle:pseudotile", "false"])
}
// This stores all the information shared between the lock surfaces on each screen.
@@ -156,20 +162,24 @@ Scope {
}
}
function initIfReady() {
if (!Config.ready || !Persistent.ready) return;
if (Config.options.lock.launchOnStartup && Persistent.isNewHyprlandInstance) {
Hyprland.dispatch("global quickshell:lock")
} else {
KeyringStorage.fetchKeyringData();
}
}
Connections {
target: Config
function onReadyChanged() {
if (Config.options.lock.launchOnStartup && Config.ready && Persistent.ready && Persistent.isNewHyprlandInstance) {
Hyprland.dispatch("global quickshell:lock")
}
root.initIfReady();
}
}
Connections {
target: Persistent
function onReadyChanged() {
if (Config.options.lock.launchOnStartup && Config.ready && Persistent.ready && Persistent.isNewHyprlandInstance) {
Hyprland.dispatch("global quickshell:lock")
}
root.initIfReady();
}
}
}
@@ -38,6 +38,7 @@ Scope {
root.resetTargetAction();
root.clearText();
root.unlockInProgress = false;
stopFingerPam();
}
Timer {
@@ -69,7 +70,9 @@ Scope {
}
function stopFingerPam() {
fingerPam.abort();
if (fingerPam.running) {
fingerPam.abort();
}
}
Process {
@@ -119,6 +119,7 @@ MouseArea {
ToolbarTextField {
id: passwordBox
Layout.rightMargin: -Layout.leftMargin
placeholderText: GlobalStates.screenUnlockFailed ? Translation.tr("Incorrect password") : Translation.tr("Enter password")
// Style
@@ -156,11 +157,11 @@ MouseArea {
// Shake when wrong password
SequentialAnimation {
id: wrongPasswordShakeAnim
NumberAnimation { target: passwordBox; property: "x"; to: -30; duration: 50 }
NumberAnimation { target: passwordBox; property: "x"; to: 30; duration: 50 }
NumberAnimation { target: passwordBox; property: "x"; to: -15; duration: 40 }
NumberAnimation { target: passwordBox; property: "x"; to: 15; duration: 40 }
NumberAnimation { target: passwordBox; property: "x"; to: 0; duration: 30 }
NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: -30; duration: 50 }
NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: 30; duration: 50 }
NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: -15; duration: 40 }
NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: 15; duration: 40 }
NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: 0; duration: 30 }
}
Connections {
target: GlobalStates
@@ -101,7 +101,7 @@ Item { // Player instance
id: background
anchors.fill: parent
anchors.margins: Appearance.sizes.elevationMargin
color: blendedColors.colLayer0
color: ColorUtils.applyAlpha(blendedColors.colLayer0, 1)
radius: root.radius
layer.enabled: true
@@ -43,10 +43,12 @@ Item { // Window
property bool hovered: false
property bool pressed: false
property var iconToWindowRatio: 0.35
property var xwaylandIndicatorToIconRatio: 0.35
property var iconToWindowRatioCompact: 0.6
property var iconPath: Quickshell.iconPath(AppSearch.guessIcon(windowData?.class), "image-missing")
property bool centerIcons: Config.options.overview.centerIcons
property real iconGapRatio: 0.06
property real iconToWindowRatio: centerIcons ? 0.35 : 0.15
property real xwaylandIndicatorToIconRatio: 0.35
property real iconToWindowRatioCompact: 0.6
property string iconPath: Quickshell.iconPath(AppSearch.guessIcon(windowData?.class), "image-missing")
property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth
property bool indicateXWayland: windowData?.xwayland ?? false
@@ -109,14 +111,20 @@ Item { // Window
Image {
id: windowIcon
anchors.centerIn: parent
property real baseSize: Math.min(root.targetWindowWidth, root.targetWindowHeight)
anchors {
top: root.centerIcons ? undefined : parent.top
left: root.centerIcons ? undefined : parent.left
centerIn: root.centerIcons ? parent : undefined
margins: baseSize * root.iconGapRatio
}
property var iconSize: {
// console.log("-=-=-", root.toplevel.title, "-=-=-")
// console.log("Target window size:", targetWindowWidth, targetWindowHeight)
// console.log("Icon ratio:", root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio)
// console.log("Scale:", root.monitorData.scale)
// console.log("Final:", Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / root.monitorData.scale)
return Math.min(root.targetWindowWidth, root.targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio);
return baseSize * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio);
}
// mipmap: true
Layout.alignment: Qt.AlignHCenter
@@ -65,18 +65,16 @@ Toolbar {
}
}
IconAndTextToolbarButton {
iconText: "activity_zone"
text: Translation.tr("Rect")
toggled: root.selectionMode === RegionSelection.SelectionMode.RectCorners
onClicked: root.selectionMode = RegionSelection.SelectionMode.RectCorners
}
IconAndTextToolbarButton {
iconText: "gesture"
text: Translation.tr("Circle")
toggled: root.selectionMode === RegionSelection.SelectionMode.Circle
onClicked: root.selectionMode = RegionSelection.SelectionMode.Circle
ToolbarTabBar {
id: tabBar
tabButtonList: [
{"icon": "activity_zone", "name": Translation.tr("Rect")},
{"icon": "gesture", "name": Translation.tr("Circle")}
]
currentIndex: root.selectionMode === RegionSelection.SelectionMode.RectCorners ? 0 : 1
onCurrentIndexChanged: {
root.selectionMode = currentIndex === 0 ? RegionSelection.SelectionMode.RectCorners : RegionSelection.SelectionMode.Circle;
}
}
}
@@ -17,7 +17,7 @@ PanelWindow {
color: "transparent"
WlrLayershell.namespace: "quickshell:regionSelector"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
exclusionMode: ExclusionMode.Ignore
anchors {
left: true
@@ -961,6 +961,14 @@ ContentPage {
Config.options.overview.enable = checked;
}
}
ConfigSwitch {
buttonIcon: "center_focus_strong"
text: Translation.tr("Center icons")
checked: Config.options.overview.centerIcons
onCheckedChanged: {
Config.options.overview.centerIcons = checked;
}
}
ConfigSpinBox {
icon: "loupe"
text: Translation.tr("Scale (%)")
@@ -13,27 +13,28 @@ import Quickshell.Io
Item {
id: root
property real padding: 4
property var inputField: messageInputField
property string commandPrefix: "/"
property var suggestionQuery: ""
property var suggestionList: []
onFocusChanged: (focus) => {
onFocusChanged: focus => {
if (focus) {
root.inputField.forceActiveFocus()
root.inputField.forceActiveFocus();
}
}
Keys.onPressed: (event) => {
messageInputField.forceActiveFocus()
Keys.onPressed: event => {
messageInputField.forceActiveFocus();
if (event.modifiers === Qt.NoModifier) {
if (event.key === Qt.Key_PageUp) {
messageListView.contentY = Math.max(0, messageListView.contentY - messageListView.height / 2)
event.accepted = true
messageListView.contentY = Math.max(0, messageListView.contentY - messageListView.height / 2);
event.accepted = true;
} else if (event.key === Qt.Key_PageDown) {
messageListView.contentY = Math.min(messageListView.contentHeight - messageListView.height / 2, messageListView.contentY + messageListView.height / 2)
event.accepted = true
messageListView.contentY = Math.min(messageListView.contentHeight - messageListView.height / 2, messageListView.contentY + messageListView.height / 2);
event.accepted = true;
}
}
if ((event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier) && event.key === Qt.Key_O) {
@@ -45,21 +46,21 @@ Item {
{
name: "attach",
description: Translation.tr("Attach a file. Only works with Gemini."),
execute: (args) => {
execute: args => {
Ai.attachFile(args.join(" ").trim());
}
},
{
name: "model",
description: Translation.tr("Choose model"),
execute: (args) => {
execute: args => {
Ai.setModel(args[0]);
}
},
{
name: "tool",
description: Translation.tr("Set the tool to use for the model."),
execute: (args) => {
execute: args => {
// console.log(args)
if (args.length == 0 || args[0] == "get") {
Ai.addMessage(Translation.tr("Usage: %1tool TOOL_NAME").arg(root.commandPrefix), Ai.interfaceRole);
@@ -75,7 +76,7 @@ Item {
{
name: "prompt",
description: Translation.tr("Set the system prompt for the model."),
execute: (args) => {
execute: args => {
if (args.length === 0 || args[0] === "get") {
Ai.printPrompt();
return;
@@ -86,9 +87,9 @@ Item {
{
name: "key",
description: Translation.tr("Set API key"),
execute: (args) => {
execute: args => {
if (args[0] == "get") {
Ai.printApiKey()
Ai.printApiKey();
} else {
Ai.setApiKey(args[0]);
}
@@ -97,25 +98,25 @@ Item {
{
name: "save",
description: Translation.tr("Save chat"),
execute: (args) => {
const joinedArgs = args.join(" ")
execute: args => {
const joinedArgs = args.join(" ");
if (joinedArgs.trim().length == 0) {
Ai.addMessage(Translation.tr("Usage: %1save CHAT_NAME").arg(root.commandPrefix), Ai.interfaceRole);
return;
}
Ai.saveChat(joinedArgs)
Ai.saveChat(joinedArgs);
}
},
{
name: "load",
description: Translation.tr("Load chat"),
execute: (args) => {
const joinedArgs = args.join(" ")
execute: args => {
const joinedArgs = args.join(" ");
if (joinedArgs.trim().length == 0) {
Ai.addMessage(Translation.tr("Usage: %1load CHAT_NAME").arg(root.commandPrefix), Ai.interfaceRole);
return;
}
Ai.loadChat(joinedArgs)
Ai.loadChat(joinedArgs);
}
},
{
@@ -128,10 +129,10 @@ Item {
{
name: "temp",
description: Translation.tr("Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5."),
execute: (args) => {
execute: args => {
// console.log(args)
if (args.length == 0 || args[0] == "get") {
Ai.printTemperature()
Ai.printTemperature();
} else {
const temp = parseFloat(args[0]);
Ai.setTemperature(temp);
@@ -191,8 +192,7 @@ Inline w/ double dollar signs: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\p
Inline w/ backslash and square brackets \\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\]
Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
`,
Ai.interfaceRole);
`, Ai.interfaceRole);
}
},
]
@@ -208,13 +208,12 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
} else {
Ai.addMessage(Translation.tr("Unknown command: ") + command, Ai.interfaceRole);
}
}
else {
} else {
Ai.sendUserMessage(inputText);
}
// Always scroll to bottom when user sends a message
messageListView.positionViewAtEnd()
messageListView.positionViewAtEnd();
}
Process {
@@ -223,16 +222,14 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
property string imageDecodeFileName: "image"
property string imageDecodeFilePath: `${imageDecodePath}/${imageDecodeFileName}`
function handleEntry(entry: string) {
imageDecodeFileName = parseInt(entry.match(/^(\d+)\t/)[1])
decodeImageAndAttachProc.exec(["bash", "-c",
`[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(entry)}' | ${Cliphist.cliphistBinary} decode > '${imageDecodeFilePath}'`
])
imageDecodeFileName = parseInt(entry.match(/^(\d+)\t/)[1]);
decodeImageAndAttachProc.exec(["bash", "-c", `[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(entry)}' | ${Cliphist.cliphistBinary} decode > '${imageDecodeFilePath}'`]);
}
onExited: (exitCode, exitStatus) => {
if (exitCode === 0) {
Ai.attachFile(imageDecodeFilePath);
} else {
console.error("[AiChat] Failed to decode image in clipboard content")
console.error("[AiChat] Failed to decode image in clipboard content");
}
}
}
@@ -278,37 +275,14 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
ColumnLayout {
id: columnLayout
anchors.fill: parent
RowLayout { // Status
Layout.alignment: Qt.AlignHCenter
spacing: 10
StatusItem {
icon: Ai.currentModelHasApiKey ? "key" : "key_off"
statusText: ""
description: Ai.currentModelHasApiKey ? Translation.tr("API key is set\nChange with /key YOUR_API_KEY") : Translation.tr("No API key\nSet it with /key YOUR_API_KEY")
}
StatusSeparator {}
StatusItem {
icon: "device_thermostat"
statusText: Ai.temperature.toFixed(1)
description: Translation.tr("Temperature\nChange with /temp VALUE")
}
StatusSeparator {
visible: Ai.tokenCount.total > 0
}
StatusItem {
visible: Ai.tokenCount.total > 0
icon: "token"
statusText: Ai.tokenCount.total
description: Translation.tr("Total token count\nInput: %1\nOutput: %2")
.arg(Ai.tokenCount.input)
.arg(Ai.tokenCount.output)
}
anchors {
fill: parent
margins: root.padding
}
spacing: root.padding
Item { // Messages
Item {
// Messages
Layout.fillWidth: true
Layout.fillHeight: true
layer.enabled: true
@@ -320,6 +294,55 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
}
}
StyledRectangularShadow {
z: 1
target: statusBg
opacity: messageListView.atYBeginning ? 0 : 1
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
Rectangle {
id: statusBg
z: 2
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
topMargin: 4
}
implicitWidth: statusRowLayout.implicitWidth + 10 * 2
implicitHeight: Math.max(statusRowLayout.implicitHeight, 38)
radius: Appearance.rounding.normal - root.padding
color: Appearance.colors.colLayer2
RowLayout {
id: statusRowLayout
anchors.centerIn: parent
spacing: 10
StatusItem {
icon: Ai.currentModelHasApiKey ? "key" : "key_off"
statusText: ""
description: Ai.currentModelHasApiKey ? Translation.tr("API key is set\nChange with /key YOUR_API_KEY") : Translation.tr("No API key\nSet it with /key YOUR_API_KEY")
}
StatusSeparator {}
StatusItem {
icon: "device_thermostat"
statusText: Ai.temperature.toFixed(1)
description: Translation.tr("Temperature\nChange with /temp VALUE")
}
StatusSeparator {
visible: Ai.tokenCount.total > 0
}
StatusItem {
visible: Ai.tokenCount.total > 0
icon: "token"
statusText: Ai.tokenCount.total
description: Translation.tr("Total token count\nInput: %1\nOutput: %2").arg(Ai.tokenCount.input).arg(Ai.tokenCount.output)
}
}
}
ScrollEdgeFade {
z: 1
target: messageListView
@@ -332,16 +355,20 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
anchors.fill: parent
spacing: 10
popin: false
topMargin: statusBg.implicitHeight + statusBg.anchors.topMargin * 2
touchpadScrollFactor: Config.options.interactions.scrolling.touchpadScrollFactor * 1.4
mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4
property int lastResponseLength: 0
onContentHeightChanged: {
if (atYEnd) Qt.callLater(positionViewAtEnd);
if (atYEnd)
Qt.callLater(positionViewAtEnd);
}
onCountChanged: { // Auto-scroll when new messages are added
if (atYEnd) Qt.callLater(positionViewAtEnd);
onCountChanged: {
// Auto-scroll when new messages are added
if (atYEnd)
Qt.callLater(positionViewAtEnd);
}
add: null // Prevent function calls from being janky
@@ -357,7 +384,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
required property int index
messageIndex: index
messageData: {
Ai.messageByID[modelData]
Ai.messageByID[modelData];
}
messageInputField: root.inputField
}
@@ -393,8 +420,8 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
Repeater {
id: suggestionRepeater
model: {
suggestions.selectedIndex = 0
return root.suggestionList.slice(0, 10)
suggestions.selectedIndex = 0;
return root.suggestionList.slice(0, 10);
}
delegate: ApiCommandButton {
id: commandButton
@@ -413,7 +440,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
}
}
onClicked: {
suggestions.acceptSuggestion(modelData.name)
suggestions.acceptSuggestion(modelData.name);
}
}
}
@@ -443,14 +470,10 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
id: inputWrapper
property real spacing: 5
Layout.fillWidth: true
radius: Appearance.rounding.small
color: Appearance.colors.colLayer1
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin
+ commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + spacing, 45)
+ (attachedFileIndicator.implicitHeight + spacing + attachedFileIndicator.anchors.topMargin)
radius: Appearance.rounding.normal - root.padding
color: Appearance.colors.colLayer2
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin + commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + spacing, 45) + (attachedFileIndicator.implicitHeight + spacing + attachedFileIndicator.anchors.topMargin)
clip: true
border.color: Appearance.colors.colOutlineVariant
border.width: 1
Behavior on implicitHeight {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
@@ -488,121 +511,122 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
background: null
onTextChanged: { // Handle suggestions
onTextChanged: {
// Handle suggestions
if (messageInputField.text.length === 0) {
root.suggestionQuery = ""
root.suggestionList = []
return
root.suggestionQuery = "";
root.suggestionList = [];
return;
} else if (messageInputField.text.startsWith(`${root.commandPrefix}model`)) {
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? ""
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
const modelResults = Fuzzy.go(root.suggestionQuery, Ai.modelList.map(model => {
return {
name: Fuzzy.prepare(model),
obj: model,
}
obj: model
};
}), {
all: true,
key: "name"
})
});
root.suggestionList = modelResults.map(model => {
return {
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "model ") : ""}${model.target}`,
displayName: `${Ai.models[model.target].name}`,
description: `${Ai.models[model.target].description}`,
}
})
description: `${Ai.models[model.target].description}`
};
});
} else if (messageInputField.text.startsWith(`${root.commandPrefix}prompt`)) {
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? ""
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.promptFiles.map(file => {
return {
name: Fuzzy.prepare(file),
obj: file,
}
obj: file
};
}), {
all: true,
key: "name"
})
});
root.suggestionList = promptFileResults.map(file => {
return {
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "prompt ") : ""}${file.target}`,
displayName: `${FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target))}`,
description: Translation.tr("Load prompt from %1").arg(file.target),
}
})
description: Translation.tr("Load prompt from %1").arg(file.target)
};
});
} else if (messageInputField.text.startsWith(`${root.commandPrefix}save`)) {
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? ""
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.savedChats.map(file => {
return {
name: Fuzzy.prepare(file),
obj: file,
}
obj: file
};
}), {
all: true,
key: "name"
})
});
root.suggestionList = promptFileResults.map(file => {
const chatName = FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target)).trim()
const chatName = FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target)).trim();
return {
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "save ") : ""}${chatName}`,
displayName: `${chatName}`,
description: Translation.tr("Save chat to %1").arg(chatName),
}
})
description: Translation.tr("Save chat to %1").arg(chatName)
};
});
} else if (messageInputField.text.startsWith(`${root.commandPrefix}load`)) {
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? ""
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.savedChats.map(file => {
return {
name: Fuzzy.prepare(file),
obj: file,
}
obj: file
};
}), {
all: true,
key: "name"
})
});
root.suggestionList = promptFileResults.map(file => {
const chatName = FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target)).trim()
const chatName = FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target)).trim();
return {
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "load ") : ""}${chatName}`,
displayName: `${chatName}`,
description: Translation.tr(`Load chat from %1`).arg(file.target),
}
})
description: Translation.tr(`Load chat from %1`).arg(file.target)
};
});
} else if (messageInputField.text.startsWith(`${root.commandPrefix}tool`)) {
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? ""
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
const toolResults = Fuzzy.go(root.suggestionQuery, Ai.availableTools.map(tool => {
return {
name: Fuzzy.prepare(tool),
obj: tool,
}
obj: tool
};
}), {
all: true,
key: "name"
})
});
root.suggestionList = toolResults.map(tool => {
const toolName = tool.target
const toolName = tool.target;
return {
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "tool ") : ""}${tool.target}`,
displayName: toolName,
description: Ai.toolDescriptions[toolName],
}
})
} else if(messageInputField.text.startsWith(root.commandPrefix)) {
root.suggestionQuery = messageInputField.text
description: Ai.toolDescriptions[toolName]
};
});
} else if (messageInputField.text.startsWith(root.commandPrefix)) {
root.suggestionQuery = messageInputField.text;
root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(messageInputField.text.substring(1))).map(cmd => {
return {
name: `${root.commandPrefix}${cmd.name}`,
description: `${cmd.description}`,
}
})
description: `${cmd.description}`
};
});
}
}
function accept() {
root.handleInput(text)
text = ""
root.handleInput(text);
text = "";
}
Keys.onPressed: (event) => {
Keys.onPressed: event => {
if (event.key === Qt.Key_Tab) {
suggestions.acceptSelectedWord();
event.accepted = true;
@@ -615,35 +639,41 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
} else if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) {
if (event.modifiers & Qt.ShiftModifier) {
// Insert newline
messageInputField.insert(messageInputField.cursorPosition, "\n")
event.accepted = true
} else { // Accept text
const inputText = messageInputField.text
messageInputField.clear()
root.handleInput(inputText)
event.accepted = true
messageInputField.insert(messageInputField.cursorPosition, "\n");
event.accepted = true;
} else {
// Accept text
const inputText = messageInputField.text;
messageInputField.clear();
root.handleInput(inputText);
event.accepted = true;
}
} else if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_V) { // Intercept Ctrl+V to handle image/file pasting
if (event.modifiers & Qt.ShiftModifier) { // Let Shift+Ctrl+V = plain paste
messageInputField.text += Quickshell.clipboardText
} else if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_V) {
// Intercept Ctrl+V to handle image/file pasting
if (event.modifiers & Qt.ShiftModifier) {
// Let Shift+Ctrl+V = plain paste
messageInputField.text += Quickshell.clipboardText;
event.accepted = true;
return;
}
// Try image paste first
const currentClipboardEntry = Cliphist.entries[0]
const cleanCliphistEntry = StringUtils.cleanCliphistEntry(currentClipboardEntry)
if (/^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(currentClipboardEntry)) { // First entry = currently copied entry = image?
decodeImageAndAttachProc.handleEntry(currentClipboardEntry)
const currentClipboardEntry = Cliphist.entries[0];
const cleanCliphistEntry = StringUtils.cleanCliphistEntry(currentClipboardEntry);
if (/^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(currentClipboardEntry)) {
// First entry = currently copied entry = image?
decodeImageAndAttachProc.handleEntry(currentClipboardEntry);
event.accepted = true;
return;
} else if (cleanCliphistEntry.startsWith("file://")) { // First entry = currently copied entry = image?
const fileName = decodeURIComponent(cleanCliphistEntry)
} else if (cleanCliphistEntry.startsWith("file://")) {
// First entry = currently copied entry = image?
const fileName = decodeURIComponent(cleanCliphistEntry);
Ai.attachFile(fileName);
event.accepted = true;
return;
}
event.accepted = false; // No image, let text pasting proceed
} else if (event.key === Qt.Key_Escape) { // Esc to detach file
} else if (event.key === Qt.Key_Escape) {
// Esc to detach file
if (Ai.pendingFilePath.length > 0) {
Ai.attachFile("");
event.accepted = true;
@@ -668,19 +698,18 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
anchors.fill: parent
cursorShape: sendButton.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
const inputText = messageInputField.text
root.handleInput(inputText)
messageInputField.clear()
const inputText = messageInputField.text;
root.handleInput(inputText);
messageInputField.clear();
}
}
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
// fill: sendButton.enabled ? 1 : 0
iconSize: 22
color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled
text: "send"
text: "arrow_upward"
}
}
}
@@ -699,59 +728,58 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
{
name: "",
sendDirectly: false,
dontAddSpace: true,
},
dontAddSpace: true
},
{
name: "clear",
sendDirectly: true,
},
sendDirectly: true
},
]
ApiInputBoxIndicator { // Model indicator
ApiInputBoxIndicator {
// Model indicator
icon: "api"
text: Ai.getModel().name
tooltipText: Translation.tr("Current model: %1\nSet it with %2model MODEL")
.arg(Ai.getModel().name)
.arg(root.commandPrefix)
tooltipText: Translation.tr("Current model: %1\nSet it with %2model MODEL").arg(Ai.getModel().name).arg(root.commandPrefix)
}
ApiInputBoxIndicator { // Tool indicator
ApiInputBoxIndicator {
// Tool indicator
icon: "service_toolbox"
text: Ai.currentTool.charAt(0).toUpperCase() + Ai.currentTool.slice(1)
tooltipText: Translation.tr("Current tool: %1\nSet it with %2tool TOOL")
.arg(Ai.currentTool)
.arg(root.commandPrefix)
tooltipText: Translation.tr("Current tool: %1\nSet it with %2tool TOOL").arg(Ai.currentTool).arg(root.commandPrefix)
}
Item { Layout.fillWidth: true }
Item {
Layout.fillWidth: true
}
ButtonGroup { // Command buttons
ButtonGroup {
// Command buttons
padding: 0
Repeater { // Command buttons
Repeater {
// Command buttons
model: commandButtonsRow.commandsShown
delegate: ApiCommandButton {
property string commandRepresentation: `${root.commandPrefix}${modelData.name}`
buttonText: commandRepresentation
downAction: () => {
if (modelData.sendDirectly) {
root.handleInput(commandRepresentation)
root.handleInput(commandRepresentation);
} else {
messageInputField.text = commandRepresentation + (modelData.dontAddSpace ? "" : " ")
messageInputField.cursorPosition = messageInputField.text.length
messageInputField.forceActiveFocus()
messageInputField.text = commandRepresentation + (modelData.dontAddSpace ? "" : " ");
messageInputField.cursorPosition = messageInputField.text.length;
messageInputField.forceActiveFocus();
}
if (modelData.name === "clear") {
messageInputField.text = ""
messageInputField.text = "";
}
}
}
}
}
}
}
}
}
@@ -12,6 +12,8 @@ import Quickshell
Item {
id: root
property real padding: 4
property var inputField: tagInputField
readonly property var responses: Booru.responses
property string previewDownloadPath: Directories.booruPreviews
@@ -141,7 +143,11 @@ Item {
ColumnLayout {
id: columnLayout
anchors.fill: parent
anchors {
fill: parent
margins: root.padding
}
spacing: root.padding
Item {
Layout.fillWidth: true
@@ -317,14 +323,12 @@ Item {
id: tagInputContainer
property real columnSpacing: 5
Layout.fillWidth: true
radius: Appearance.rounding.small
color: Appearance.colors.colLayer1
radius: Appearance.rounding.normal - root.padding
color: Appearance.colors.colLayer2
implicitWidth: tagInputField.implicitWidth
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin
+ commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45)
clip: true
border.color: Appearance.colors.colOutlineVariant
border.width: 1
Behavior on implicitHeight {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
@@ -456,10 +460,9 @@ Item {
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
// fill: sendButton.enabled ? 1 : 0
iconSize: 22
color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled
text: "send"
text: "arrow_upward"
}
}
}
@@ -127,6 +127,7 @@ Scope { // Scope
sourceComponent: FloatingWindow {
id: detachedSidebarRoot
property var contentParent: detachedSidebarBackground
color: "transparent"
visible: GlobalStates.sidebarLeftOpen
onVisibleChanged: {
@@ -21,7 +21,6 @@ Item {
...(root.translatorEnabled ? [{"icon": "translate", "name": Translation.tr("Translator")}] : []),
...((root.animeEnabled && !root.animeCloset) ? [{"icon": "bookmark_heart", "name": Translation.tr("Anime")}] : [])
]
property int selectedTab: 0
property int tabCount: swipeView.count
function focusActiveItem() {
@@ -31,67 +30,64 @@ Item {
Keys.onPressed: (event) => {
if (event.modifiers === Qt.ControlModifier) {
if (event.key === Qt.Key_PageDown) {
root.selectedTab = Math.min(root.selectedTab + 1, root.tabCount - 1)
swipeView.incrementCurrentIndex()
event.accepted = true;
}
}
else if (event.key === Qt.Key_PageUp) {
root.selectedTab = Math.max(root.selectedTab - 1, 0)
event.accepted = true;
}
else if (event.key === Qt.Key_Tab) {
root.selectedTab = (root.selectedTab + 1) % root.tabCount;
event.accepted = true;
}
else if (event.key === Qt.Key_Backtab) {
root.selectedTab = (root.selectedTab - 1 + root.tabCount) % root.tabCount;
swipeView.decrementCurrentIndex()
event.accepted = true;
}
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: sidebarPadding
anchors {
fill: parent
margins: sidebarPadding
}
spacing: sidebarPadding
PrimaryTabBar { // Tab strip
id: tabBar
visible: root.tabButtonList.length > 1
tabButtonList: root.tabButtonList
Synchronizer on currentIndex {
property alias source: root.selectedTab
Toolbar {
Layout.alignment: Qt.AlignHCenter
enableShadow: false
ToolbarTabBar {
id: tabBar
Layout.alignment: Qt.AlignHCenter
tabButtonList: root.tabButtonList
currentIndex: swipeView.currentIndex
}
}
SwipeView { // Content pages
id: swipeView
Layout.topMargin: 5
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 10
currentIndex: root.selectedTab
onCurrentIndexChanged: {
tabBar.enableIndicatorAnimation = true
root.selectedTab = currentIndex
}
implicitWidth: swipeView.implicitWidth
implicitHeight: swipeView.implicitHeight
radius: Appearance.rounding.normal
color: Appearance.colors.colLayer1
clip: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: swipeView.width
height: swipeView.height
radius: Appearance.rounding.small
SwipeView { // Content pages
id: swipeView
anchors.fill: parent
spacing: 10
currentIndex: tabBar.currentIndex
clip: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: swipeView.width
height: swipeView.height
radius: Appearance.rounding.small
}
}
}
contentChildren: [
...((root.aiChatEnabled || (!root.translatorEnabled && !root.animeEnabled)) ? [aiChat.createObject()] : []),
...(root.translatorEnabled ? [translator.createObject()] : []),
...(root.animeEnabled ? [anime.createObject()] : [])
]
contentChildren: [
...((root.aiChatEnabled || (!root.translatorEnabled && !root.animeEnabled)) ? [aiChat.createObject()] : []),
...(root.translatorEnabled ? [translator.createObject()] : []),
...(root.animeEnabled ? [anime.createObject()] : [])
]
}
}
Component {
@@ -13,17 +13,24 @@ import Quickshell.Io
*/
Item {
id: root
// Sizes
property real padding: 4
// Widgets
property var inputField: inputCanvas.inputTextArea
// Widget variables
property bool translationFor: false // Indicates if the translation is for an autocorrected text
property string translatedText: ""
property list<string> languages: []
// Options
property string targetLanguage: Config.options.language.translator.targetLanguage
property string sourceLanguage: Config.options.language.translator.sourceLanguage
property string hostLanguage: targetLanguage
// States
property bool showLanguageSelector: false
property bool languageSelectorTarget: false // true for target language, false for source language
@@ -99,7 +106,11 @@ Item {
}
ColumnLayout {
anchors.fill: parent
anchors {
fill: parent
margins: root.padding
}
StyledFlickable {
Layout.fillWidth: true
Layout.fillHeight: true
@@ -17,10 +17,8 @@ Rectangle {
default property alias actionButtons: actions.data
Layout.fillWidth: true
implicitHeight: Math.max(150, inputColumn.implicitHeight)
color: isInput ? Appearance.colors.colLayer1 : Appearance.colors.colSurfaceContainer
color: Appearance.colors.colLayer2
radius: Appearance.rounding.normal
border.color: isInput ? Appearance.colors.colOutlineVariant : "transparent"
border.width: isInput ? 1 : 0
signal inputTextChanged(); // Signal emitted when text changes
@@ -102,7 +102,7 @@ WindowDialog {
right: parent.right
}
iconSize: Appearance.font.pixelSize.larger
buttonIcon: "destruction"
buttonIcon: "flash_off"
text: Translation.tr("Enable")
checked: Config.options.light.antiFlashbang.enable
onCheckedChanged: {
@@ -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","musicRecognition"]
readonly property list<string> availableToggleTypes: ["network", "bluetooth", "idleInhibitor", "easyEffects", "nightLight", "darkMode", "cloudflareWarp", "gameMode", "screenSnip", "colorPicker", "onScreenKeyboard", "mic", "audio", "notifications", "powerProfile","musicRecognition", "antiFlashbang"]
readonly property int columns: Config.options.sidebar.quickToggles.android.columns
readonly property list<var> toggles: Config.ready ? Config.options.sidebar.quickToggles.android.toggles : []
readonly property list<var> toggleRows: toggleRowsForList(toggles)
@@ -0,0 +1,29 @@
import qs.modules.common
import qs.modules.common.widgets
import qs.services
import QtQuick
import Quickshell
AndroidQuickToggleButton {
id: root
property bool auto: Config.options.light.night.automatic
name: Translation.tr("Anti-flashbang")
toggled: Config.options.light.antiFlashbang.enable
buttonIcon: "flash_off"
mainAction: () => {
Config.options.light.antiFlashbang.enable = !Config.options.light.antiFlashbang.enable;
}
altAction: () => {
root.openMenu()
}
StyledToolTip {
text: Translation.tr("Anti-flashbang")
}
}
@@ -12,7 +12,7 @@ AndroidQuickToggleButton {
statusText: toggled ? Translation.tr("Unmuted") : Translation.tr("Muted")
toggled: !Audio.sink?.audio?.muted
buttonIcon: Audio.sink?.audio?.muted ? "volume_off" : "volume_up"
onClicked: {
mainAction: () => {
Audio.sink.audio.muted = !Audio.sink.audio.muted
}
@@ -14,7 +14,7 @@ AndroidQuickToggleButton {
toggled: BluetoothStatus.enabled
buttonIcon: BluetoothStatus.connected ? "bluetooth_connected" : BluetoothStatus.enabled ? "bluetooth" : "bluetooth_disabled"
onClicked: {
mainAction: () => {
Bluetooth.defaultAdapter.enabled = !Bluetooth.defaultAdapter?.enabled
}
altAction: () => {
@@ -13,7 +13,7 @@ AndroidQuickToggleButton {
toggled: false
buttonIcon: "cloud_lock"
onClicked: {
mainAction: () => {
if (toggled) {
root.toggled = false
Quickshell.execDetached(["warp-cli", "disconnect"])
@@ -13,7 +13,7 @@ AndroidQuickToggleButton {
toggled: false
buttonIcon: "colorize"
onClicked: {
mainAction: () => {
GlobalStates.sidebarRightOpen = false;
delayedActionTimer.start()
}
@@ -13,7 +13,7 @@ AndroidQuickToggleButton {
toggled: Appearance.m3colors.darkmode
buttonIcon: "contrast"
onClicked: event => {
mainAction: () => {
if (Appearance.m3colors.darkmode) {
Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, "--mode", "light", "--noswitch"]);
} else {
@@ -16,7 +16,7 @@ AndroidQuickToggleButton {
EasyEffects.fetchActiveState()
}
onClicked: {
mainAction: () => {
EasyEffects.toggle()
}
@@ -13,7 +13,7 @@ AndroidQuickToggleButton {
toggled: toggled
buttonIcon: "gamepad"
onClicked: {
mainAction: () => {
root.toggled = !root.toggled
if (root.toggled) {
Quickshell.execDetached(["bash", "-c", `hyprctl --batch "keyword animations:enabled 0; keyword decoration:shadow:enabled 0; keyword decoration:blur:enabled 0; keyword general:gaps_in 0; keyword general:gaps_out 0; keyword general:border_size 1; keyword decoration:rounding 0; keyword general:allow_tearing 1"`])
@@ -11,7 +11,7 @@ AndroidQuickToggleButton {
toggled: Idle.inhibit
buttonIcon: "coffee"
onClicked: {
mainAction: () => {
Idle.toggleInhibit()
}
StyledToolTip {
@@ -12,7 +12,7 @@ AndroidQuickToggleButton {
statusText: toggled ? Translation.tr("Enabled") : Translation.tr("Muted")
toggled: !Audio.source?.audio?.muted
buttonIcon: Audio.source?.audio?.muted ? "mic_off" : "mic"
onClicked: {
mainAction: () => {
Audio.source.audio.muted = !Audio.source.audio.muted
}
@@ -21,7 +21,7 @@ AndroidQuickToggleButton {
text: Translation.tr("Recognize music | Right-click to toggle source")
}
onClicked: {
mainAction: () => {
SongRec.toggleRunning()
}
@@ -12,7 +12,7 @@ AndroidQuickToggleButton {
toggled: Network.wifiStatus !== "disabled"
buttonIcon: Network.materialSymbol
onClicked: Network.toggleWifi()
mainAction: () => Network.toggleWifi()
altAction: () => {
root.openMenu()
}
@@ -14,7 +14,8 @@ AndroidQuickToggleButton {
toggled: Hyprsunset.active
buttonIcon: auto ? "night_sight_auto" : "bedtime"
onClicked: {
mainAction: () => {
Hyprsunset.toggle()
}
@@ -13,7 +13,7 @@ AndroidQuickToggleButton {
toggled: !Notifications.silent
buttonIcon: toggled ? "notifications_active" : "notifications_paused"
onClicked: {
mainAction: () => {
Notifications.silent = !Notifications.silent;
}
@@ -11,7 +11,8 @@ AndroidQuickToggleButton {
name: Translation.tr("Virtual Keyboard")
toggled: GlobalStates.oskOpen
buttonIcon: toggled ? "keyboard_hide" : "keyboard"
onClicked: {
mainAction: () => {
GlobalStates.oskOpen = !GlobalStates.oskOpen
}
@@ -22,7 +22,7 @@ AndroidQuickToggleButton {
case PowerProfile.Performance: return "Performance"
}
onClicked: (event) => {
mainAction: () => {
if (PowerProfiles.hasPerformanceProfile) {
switch(PowerProfiles.profile) {
case PowerProfile.PowerSaver: PowerProfiles.profile = PowerProfile.Balanced
@@ -13,6 +13,7 @@ GroupButton {
required property bool expandedSize
required property string buttonIcon
required property string name
required property var mainAction
property string statusText: toggled ? Translation.tr("Active") : Translation.tr("Inactive")
required property real baseCellWidth
@@ -54,6 +55,11 @@ GroupButton {
property color colText: (toggled && !(altAction && expandedSize)) ? Appearance.colors.colOnPrimary : Appearance.colors.colOnLayer2
property color colIcon: expandedSize ? (root.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnLayer3) : colText
onClicked: {
if (root.expandedSize && root.altAction) root.altAction();
else root.mainAction();
}
contentItem: RowLayout {
id: contentItem
spacing: 4
@@ -64,35 +70,63 @@ GroupButton {
rightMargin: root.horizontalPadding
}
Rectangle {
// Icon
MouseArea {
id: iconMouseArea
hoverEnabled: true
acceptedButtons: (root.expandedSize && root.altAction) ? Qt.LeftButton : Qt.NoButton
Layout.alignment: Qt.AlignHCenter
Layout.fillHeight: true
Layout.topMargin: root.verticalPadding
Layout.bottomMargin: root.verticalPadding
implicitWidth: height
radius: root.radius - root.verticalPadding
color: {
const baseColor = root.toggled ? Appearance.colors.colPrimary : Appearance.colors.colLayer3
const transparentizeAmount = (root.altAction && root.expandedSize) ? 0 : 1
return ColorUtils.transparentize(baseColor, transparentizeAmount)
}
implicitHeight: iconBackground.implicitHeight
implicitWidth: iconBackground.implicitWidth
cursorShape: Qt.PointingHandCursor
Behavior on radius {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
onClicked: root.mainAction()
MaterialSymbol {
anchors.centerIn: parent
fill: root.toggled ? 1 : 0
iconSize: root.expandedSize ? 22 : 24
color: root.colIcon
text: root.buttonIcon
Rectangle {
id: iconBackground
anchors.fill: parent
implicitWidth: height
radius: root.radius - root.verticalPadding
color: {
const baseColor = root.toggled ? Appearance.colors.colPrimary : Appearance.colors.colLayer3
const transparentizeAmount = (root.altAction && root.expandedSize) ? 0 : 1
return ColorUtils.transparentize(baseColor, transparentizeAmount)
}
Behavior on radius {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
MaterialSymbol {
anchors.centerIn: parent
fill: root.toggled ? 1 : 0
iconSize: root.expandedSize ? 22 : 24
color: root.colIcon
text: root.buttonIcon
}
// State layer
Loader {
anchors.fill: parent
active: (root.expandedSize && root.altAction)
sourceComponent: Rectangle {
radius: iconBackground.radius
color: ColorUtils.transparentize(root.colIcon, iconMouseArea.containsPress ? 0.88 : iconMouseArea.containsMouse ? 0.95 : 1)
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
}
}
}
// Text column for expanded size
Loader {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
@@ -119,8 +153,9 @@ GroupButton {
}
font {
pixelSize: Appearance.font.pixelSize.smaller
weight: 100
}
color: Appearance.colors.colSubtext
color: root.colText
elide: Text.ElideRight
text: root.statusText
}
@@ -14,7 +14,7 @@ AndroidQuickToggleButton {
toggled: false
buttonIcon: "screenshot_region"
onClicked: {
mainAction: () => {
GlobalStates.sidebarRightOpen = false;
delayedActionTimer.start()
}
@@ -245,4 +245,20 @@ DelegateChooser {
cellSize: modelData.size
} }
DelegateChoice { roleValue: "antiFlashbang"; AndroidAntiFlashbangToggle {
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
onOpenMenu: {
root.openNightLightDialog()
}
} }
}
@@ -186,6 +186,8 @@ MouseArea {
colBackgroundToggled: Appearance.colors.colSecondaryContainer
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
colRippleToggled: Appearance.colors.colSecondaryContainerActive
buttonRadius: height / 2
implicitHeight: 38
contentItem: RowLayout {
MaterialSymbol {
@@ -30,12 +30,7 @@ def image_colorfulness(image):
# scheme-content respects the image's colors very well, but it might
# look too saturated, so we only use it for not very colorful images to be safe
def pick_scheme(colorfulness):
if colorfulness < 10:
# return "scheme-monochrome"
return "scheme-content"
elif colorfulness < 20:
return "scheme-content"
elif colorfulness < 50:
if colorfulness < 40:
return "scheme-neutral"
else:
return "scheme-tonal-spot"
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
locked_state=$(busctl --user get-property org.freedesktop.secrets \
/org/freedesktop/secrets/collection/login \
org.freedesktop.Secret.Collection Locked)
if [[ "${locked_state}" == "b false" ]]; then
echo 'Keyring is unlocked' >&2
exit 0
else
echo 'Keyring is locked' >&2
exit 1
fi
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
data=$(secret-tool lookup 'application' 'illogical-impulse')
if [[ -z "$data" ]]; then
if "${SCRIPT_DIR}/is_unlocked.sh"; then
echo 'not found'
exit 1
else
echo 'locked'
exit 2
fi
fi
echo "$data"
@@ -1,12 +1,10 @@
#!/usr/bin/env bash
# Based on https://unix.stackexchange.com/a/602935
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Skip if already unlocked
locked_state=$(busctl --user get-property org.freedesktop.secrets \
/org/freedesktop/secrets/collection/login \
org.freedesktop.Secret.Collection Locked)
if [[ "${locked_state}" == "b false" ]]; then
echo 'Keyring is already unlocked.' >&2
if "${SCRIPT_DIR}/is_unlocked.sh"; then
exit 1
fi
+4 -2
View File
@@ -532,8 +532,6 @@ Singleton {
modelId = modelId.toLowerCase()
if (modelList.indexOf(modelId) !== -1) {
const model = models[modelId]
// Fetch API keys if needed
if (model?.requires_key) KeyringStorage.fetchKeyringData();
// See if policy prevents online models
if (Config.options.policies.ai === 2 && !model.endpoint.includes("localhost")) {
root.addMessage(
@@ -641,6 +639,10 @@ Singleton {
function makeRequest() {
const model = models[currentModelId];
// Fetch API keys if needed
if (model?.requires_key && !KeyringStorage.loaded) KeyringStorage.fetchKeyringData();
requester.currentStrategy = root.currentApiStrategy;
requester.currentStrategy.reset(); // Reset strategy state
@@ -86,6 +86,10 @@ Singleton {
return str.toLowerCase().replace(/\s+/g, "-");
}
function getUndescoreToKebabAppName(str) {
return str.toLowerCase().replace(/_/g, "-");
}
function guessIcon(str) {
if (!str || str.length == 0) return "image-missing";
@@ -124,6 +128,8 @@ Singleton {
const kebabNormalizedGuess = getKebabNormalizedAppName(str);
if (iconExists(kebabNormalizedGuess)) return kebabNormalizedGuess;
const undescoreToKebabGuess = getUndescoreToKebabAppName(str);
if (iconExists(undescoreToKebabGuess)) return undescoreToKebabGuess;
// Search in desktop entries
const iconSearchResults = Fuzzy.go(str, preppedIcons, {
@@ -47,7 +47,7 @@ Singleton {
}
})
},
"tagSearchTemplate": "https://yande.re/tag.json?order=count&name={{query}}*",
"tagSearchTemplate": "https://yande.re/tag.json?order=count&limit=10&name={{query}}*",
"tagMapFunc": (response) => {
return response.map(item => {
return {
@@ -81,7 +81,7 @@ Singleton {
}
})
},
"tagSearchTemplate": "https://konachan.net/tag.json?order=count&name={{query}}*",
"tagSearchTemplate": "https://konachan.net/tag.json?order=count&limit=10&name={{query}}*",
"tagMapFunc": (response) => {
return response.map(item => {
return {
@@ -142,7 +142,7 @@ Singleton {
}
})
},
"tagSearchTemplate": "https://danbooru.donmai.us/tags.json?search[name_matches]={{query}}*",
"tagSearchTemplate": "https://danbooru.donmai.us/tags.json?limit=10&search[name_matches]={{query}}*",
"tagMapFunc": (response) => {
return response.map(item => {
return {
@@ -151,7 +151,6 @@ Singleton {
}
})
}
},
"gelbooru": {
"name": "Gelbooru",
@@ -178,7 +177,7 @@ Singleton {
}
})
},
"tagSearchTemplate": "https://gelbooru.com/index.php?page=dapi&s=tag&q=index&json=1&orderby=count&name_pattern={{query}}%",
"tagSearchTemplate": "https://gelbooru.com/index.php?page=dapi&s=tag&q=index&json=1&orderby=count&limit=10&name_pattern={{query}}%",
"tagMapFunc": (response) => {
return response.tag.map(item => {
return {
@@ -86,7 +86,7 @@ Singleton {
property int rawMaxBrightness: 100
property real brightness
property real brightnessMultiplier: 1.0
property real multipliedBrightness: Math.max(0, Math.min(1, brightness * brightnessMultiplier))
property real multipliedBrightness: Math.max(0, Math.min(1, brightness * (Config.options.light.antiFlashbang.enable ? brightnessMultiplier : 1)))
property bool ready: false
property bool animateChanges: !monitor.isDdc
@@ -1,6 +1,7 @@
pragma Singleton
pragma ComponentBehavior: Bound
import qs
import qs.modules.common
import qs.modules.common.functions
import Quickshell;
@@ -89,11 +90,13 @@ Singleton {
Process {
id: getData
command: [ // We need to use echo for a newline so splitparser does parse
"bash", "-c", `echo $(secret-tool lookup 'application' 'illogical-impulse')`,
"bash", "-c", `${Directories.scriptPath}/keyring/try_lookup.sh 2> /dev/null`,
]
stdout: SplitParser {
onRead: data => {
if(data.length === 0) return;
stdout: StdioCollector {
id: keyringDataOutputCollector
onStreamFinished: {
const data = keyringDataOutputCollector.text;
if (data.length === 0 || !data.startsWith("{")) return;
try {
root.keyringData = JSON.parse(data);
// console.log("[KeyringStorage] Keyring data fetched:", JSON.stringify(root.keyringData));
@@ -105,13 +108,15 @@ Singleton {
}
}
onExited: (exitCode, exitStatus) => {
// console.log("[KeyringStorage] Keyring data fetch process exited with code:", exitCode);
if (exitCode !== 0) {
console.error("[KeyringStorage] Failed to get keyring data, reinitializing.");
console.log("[KeyringStorage] Keyring data fetch process exited with code:", exitCode);
if (exitCode === 1) {
console.error("[KeyringStorage] Entry not found, initializing.");
root.keyringData = {};
saveKeyringData()
}
root.loaded = true;
if (exitCode !== 2) {
root.loaded = true;
}
}
}