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
@@ -78,7 +78,7 @@ Widget system: Quickshell | Support: Yes
| AI, settings app | Some widgets |
|:---|:---------------|
| <img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/7b98a354-4489-4a46-aa6a-d08616e77399" /> | <img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/6eba0d57-2606-4cea-8993-e6f169e82e70" /> |
| <img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/ea0154a1-e984-4bb6-a424-23247cefe3c6" /> | <img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/6eba0d57-2606-4cea-8993-e6f169e82e70" /> |
| Window management | Weeb power |
| <img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/e77a7c96-1905-4126-a2a0-434f818825a2" /> | <img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/c8544e99-8881-477f-b83a-d6e35c0184a1" /> |
+1
View File
@@ -5,3 +5,4 @@ __pycache__/
*.py[cod]
dots/.config/quickshell/ii/.qmlls.ini
.update-lock
/os-release
+4
View File
@@ -58,6 +58,9 @@ ii_check_venv() {
ii_check_quickshell_version() {
pacman -Q | grep -E 'quickshell|qt6-base'
}
ii_check_PKGBUILD_version() {
pacman -Q | grep '^illogical-impulse-'
}
e "Checking git repo info"
x git remote get-url origin
@@ -88,6 +91,7 @@ x ls -l ~/.local/state/quickshell/.venv
e "Checking versions"
x Hyprland --version
x ii_check_quickshell_version
x ii_check_PKGBUILD_version
e "Finished. Output saved as \"$output_file\"."
if ! command -v curl 2>&1 >>/dev/null ;then echo "\"curl\" not found, pastebin upload unavailable.";exit;fi
+1
View File
@@ -0,0 +1 @@
This folder contains tweakd configs when --via-nix is specified.
+26
View File
@@ -0,0 +1,26 @@
$lock_cmd = swaylock
# $lock_cmd = pidof hyprlock || hyprlock
$suspend_cmd = systemctl suspend || loginctl suspend
general {
lock_cmd = $lock_cmd
before_sleep_cmd = loginctl lock-session
after_sleep_cmd = hyprctl dispatch global quickshell:lockFocus
inhibit_sleep = 3
}
listener {
timeout = 300 # 5mins
on-timeout = loginctl lock-session
}
listener {
timeout = 600 # 10mins
on-timeout = hyprctl dispatch dpms off
on-resume = hyprctl dispatch dpms on
}
listener {
timeout = 900 # 15mins
on-timeout = $suspend_cmd
}
+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;
}
}
}
@@ -5,11 +5,11 @@ pkgdesc='Illogical Impulse Audio Dependencies'
arch=(any)
license=(None)
depends=(
cava
pavucontrol-qt
wireplumber
pipewire-pulse
libdbusmenu-gtk3
playerctl
cava
pavucontrol-qt
wireplumber
pipewire-pulse
libdbusmenu-gtk3
playerctl
)
@@ -5,8 +5,8 @@ pkgdesc='Illogical Impulse Backlight Dependencies'
arch=(any)
license=(None)
depends=(
geoclue
brightnessctl
ddcutil
geoclue
brightnessctl
ddcutil
)
@@ -5,7 +5,6 @@ pkgdesc='Illogical Impulse Basic Dependencies'
arch=(any)
license=(None)
depends=(
axel
bc
coreutils
cliphist
@@ -14,7 +13,6 @@ depends=(
wget
ripgrep
jq
meson
xdg-user-dirs
# Used in install script
rsync
@@ -6,16 +6,10 @@ arch=(any)
license=(None)
depends=(
hypridle
hyprcursor
hyprland
hyprland-qtutils
hyprland-qt-support
hyprlang
hyprlock
hyprpicker
hyprsunset
hyprutils
hyprwayland-scanner
xdg-desktop-portal-hyprland
wl-clipboard
)
@@ -5,11 +5,11 @@ pkgdesc='Illogical Impulse KDE Dependencies'
arch=(any)
license=(None)
depends=(
bluedevil
gnome-keyring
networkmanager
plasma-nm
polkit-kde-agent
dolphin
systemsettings
bluedevil
gnome-keyring
networkmanager
plasma-nm
polkit-kde-agent
dolphin
systemsettings
)
@@ -8,10 +8,10 @@ arch=("x86_64")
url="https://github.com/NanoMichael/${_pkgname}"
license=('MIT')
depends=(
tinyxml2
gtkmm3
gtksourceviewmm
cairomm
tinyxml2
gtkmm3
gtksourceviewmm
cairomm
)
makedepends=("git" "cmake")
source=("git+${url}.git")
@@ -23,9 +23,9 @@ pkgver() {
}
prepare() {
cd $_pkgname
sed -i 's/gtksourceviewmm-3.0/gtksourceviewmm-4.0/' CMakeLists.txt
sed -i 's/tinyxml2.so.10/tinyxml2.so.11/' CMakeLists.txt
cd $_pkgname
sed -i 's/gtksourceviewmm-3.0/gtksourceviewmm-4.0/' CMakeLists.txt
sed -i 's/tinyxml2.so.10/tinyxml2.so.11/' CMakeLists.txt
}
build() {
@@ -5,9 +5,9 @@ pkgdesc='Illogical Impulse XDG Desktop Portals'
arch=(any)
license=(None)
depends=(
xdg-desktop-portal
xdg-desktop-portal-kde
xdg-desktop-portal-gtk
xdg-desktop-portal-hyprland
xdg-desktop-portal
xdg-desktop-portal-kde
xdg-desktop-portal-gtk
xdg-desktop-portal-hyprland
)
@@ -12,5 +12,4 @@ depends=(
libsoup3
libportal-gtk4
gobject-introspection
sassc
)
@@ -44,7 +44,7 @@ _pkgsrc="$_pkgname"
source=("$_pkgsrc::git+$url.git#commit=$_commit"
quickshell-check.hook)
sha256sums=('SKIP'
'8543e21aeaaa5441b73a679160e7601a957f16c433e8d6bd9257e80bd0e94083')
'8543e21aeaaa5441b73a679160e7601a957f16c433e8d6bd9257e80bd0e94083')
pkgver() {
@@ -5,10 +5,10 @@ pkgdesc='Illogical Impulse Screenshot and Recording Dependencies'
arch=(any)
license=(None)
depends=(
hyprshot
slurp
swappy
tesseract
tesseract-data-eng
wf-recorder
hyprshot
slurp
swappy
tesseract
tesseract-data-eng
wf-recorder
)
@@ -5,23 +5,23 @@ pkgdesc='Illogical Impulse GTK/Qt Dependencies'
arch=(any)
license=(None)
depends=(
kdialog
qt6-5compat
qt6-avif-image-plugin
qt6-base
qt6-declarative
qt6-imageformats
qt6-multimedia
qt6-positioning
qt6-quicktimeline
qt6-sensors
qt6-svg
qt6-tools
qt6-translations
qt6-virtualkeyboard
qt6-wayland
syntax-highlighting
upower
wtype
ydotool
kdialog
syntax-highlighting
upower
wtype
ydotool
qt6-5compat
qt6-avif-image-plugin
qt6-base
qt6-declarative
qt6-imageformats
qt6-multimedia
qt6-positioning
qt6-quicktimeline
qt6-sensors
qt6-svg
qt6-tools
qt6-translations
qt6-virtualkeyboard
qt6-wayland
)
@@ -6,13 +6,11 @@ arch=(any)
license=(None)
depends=(
fuzzel
glib2 # for `gsettings` it seems?
glib2
imagemagick
hypridle
hyprutils
hyprlock
hyprpicker
nm-connection-editor
songrec
translate-shell
wlogout
@@ -2,7 +2,6 @@
### Must be one package per line as it needs to be compared against the explicitly installed list from pacman
illogical-impulse-ags
archlinux-xdg-menu
axel
bc
coreutils
cliphist
@@ -14,7 +13,6 @@ wget
ripgrep
gojq
npm
meson
typescript
gjs
xdg-user-dirs
+6 -1
View File
@@ -9,7 +9,12 @@
Note that this script must be idempotent.
TODO:
- [ ] Write a proper `flake.nix` and `home.nix` and other files under `dist-nix/home-manager/` to install all dependencies that `dist-arch/` does. (**excluding** the screenlock)
- [ ] Fix all TODOs inside `dist-nix`.
- [ ] Warn user if inode-limited filesystem (typically ext4) is used.
- [ ] Deal with error when running `systemctl --user enable ydotool --now`:
```plain
Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined (consider using --machine=<user>@.host --user to connect to bus of other user)
```
## Attentions
### PAM
+14 -418
View File
@@ -1,57 +1,8 @@
{
"nodes": {
"aquamarine": {
"inputs": {
"hyprutils": [
"hyprland",
"hyprutils"
],
"hyprwayland-scanner": [
"hyprland",
"hyprwayland-scanner"
],
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1760101617,
"narHash": "sha256-8jf/3ZCi+B7zYpIyV04+3wm72BD7Z801IlOzsOACR7I=",
"owner": "hyprwm",
"repo": "aquamarine",
"rev": "1826a9923881320306231b1c2090379ebf9fa4f8",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "aquamarine",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems_2"
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
@@ -67,28 +18,6 @@
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"hyprland",
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
@@ -110,269 +39,10 @@
"type": "github"
}
},
"hyprcursor": {
"inputs": {
"hyprlang": [
"hyprland",
"hyprlang"
],
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1753964049,
"narHash": "sha256-lIqabfBY7z/OANxHoPeIrDJrFyYy9jAM4GQLzZ2feCM=",
"owner": "hyprwm",
"repo": "hyprcursor",
"rev": "44e91d467bdad8dcf8bbd2ac7cf49972540980a5",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprcursor",
"type": "github"
}
},
"hyprgraphics": {
"inputs": {
"hyprutils": [
"hyprland",
"hyprutils"
],
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1760445448,
"narHash": "sha256-fXGjL6dw31FPFRrmIemzGiNSlfvEJTJNsmadZi+qNhI=",
"owner": "hyprwm",
"repo": "hyprgraphics",
"rev": "50fb9f069219f338a11cf0bcccb9e58357d67757",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprgraphics",
"type": "github"
}
},
"hyprland": {
"inputs": {
"aquamarine": "aquamarine",
"hyprcursor": "hyprcursor",
"hyprgraphics": "hyprgraphics",
"hyprland-protocols": "hyprland-protocols",
"hyprland-qtutils": "hyprland-qtutils",
"hyprlang": "hyprlang",
"hyprutils": "hyprutils",
"hyprwayland-scanner": "hyprwayland-scanner",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks",
"systems": "systems",
"xdph": "xdph"
},
"locked": {
"lastModified": 1761780088,
"narHash": "sha256-ylKrWQeIAGyysfHbgZpcWUs9UsbiOBIVXTPqaiV3lf0=",
"owner": "hyprwm",
"repo": "Hyprland",
"rev": "6ade4d58cab67e18aa758ef664e36421cab4d8b2",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "Hyprland",
"type": "github"
}
},
"hyprland-protocols": {
"inputs": {
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1759610243,
"narHash": "sha256-+KEVnKBe8wz+a6dTLq8YDcF3UrhQElwsYJaVaHXJtoI=",
"owner": "hyprwm",
"repo": "hyprland-protocols",
"rev": "bd153e76f751f150a09328dbdeb5e4fab9d23622",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprland-protocols",
"type": "github"
}
},
"hyprland-qt-support": {
"inputs": {
"hyprlang": [
"hyprland",
"hyprland-qtutils",
"hyprlang"
],
"nixpkgs": [
"hyprland",
"hyprland-qtutils",
"nixpkgs"
],
"systems": [
"hyprland",
"hyprland-qtutils",
"systems"
]
},
"locked": {
"lastModified": 1749154592,
"narHash": "sha256-DO7z5CeT/ddSGDEnK9mAXm1qlGL47L3VAHLlLXoCjhE=",
"owner": "hyprwm",
"repo": "hyprland-qt-support",
"rev": "4c8053c3c888138a30c3a6c45c2e45f5484f2074",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprland-qt-support",
"type": "github"
}
},
"hyprland-qtutils": {
"inputs": {
"hyprland-qt-support": "hyprland-qt-support",
"hyprlang": [
"hyprland",
"hyprlang"
],
"hyprutils": [
"hyprland",
"hyprland-qtutils",
"hyprlang",
"hyprutils"
],
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1759080228,
"narHash": "sha256-RgDoAja0T1hnF0pTc56xPfLfFOO8Utol2iITwYbUhTk=",
"owner": "hyprwm",
"repo": "hyprland-qtutils",
"rev": "629b15c19fa4082e4ce6be09fdb89e8c3312aed7",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprland-qtutils",
"type": "github"
}
},
"hyprlang": {
"inputs": {
"hyprutils": [
"hyprland",
"hyprutils"
],
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1758927902,
"narHash": "sha256-LZgMds7M94+vuMql2bERQ6LiFFdhgsEFezE4Vn+Ys3A=",
"owner": "hyprwm",
"repo": "hyprlang",
"rev": "4dafa28d4f79877d67a7d1a654cddccf8ebf15da",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprlang",
"type": "github"
}
},
"hyprutils": {
"inputs": {
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1759619523,
"narHash": "sha256-r1ed7AR2ZEb2U8gy321/Xcp1ho2tzn+gG1te/Wxsj1A=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "3df7bde01efb3a3e8e678d1155f2aa3f19e177ef",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprutils",
"type": "github"
}
},
"hyprwayland-scanner": {
"inputs": {
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1755184602,
"narHash": "sha256-RCBQN8xuADB0LEgaKbfRqwm6CdyopE1xIEhNc67FAbw=",
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"rev": "b3b0f1f40ae09d4447c20608e5a4faf8bf3c492d",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"type": "github"
}
},
"nixgl": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs_2"
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1752054764,
@@ -389,22 +59,6 @@
}
},
"nixpkgs": {
"locked": {
"lastModified": 1761114652,
"narHash": "sha256-f/QCJM/YhrV/lavyCVz8iU3rlZun6d+dAiC3H+CDle4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "01f116e4df6a15f4ccdffb1bcd41096869fb385c",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1746378225,
"narHash": "sha256-OeRSuL8PUjIfL3Q0fTbNJD/fmv1R+K2JAOqWJd3Oceg=",
@@ -419,7 +73,7 @@
"type": "github"
}
},
"nixpkgs_3": {
"nixpkgs_2": {
"locked": {
"lastModified": 1761597516,
"narHash": "sha256-wxX7u6D2rpkJLWkZ2E932SIvDJW8+ON/0Yy8+a5vsDU=",
@@ -434,53 +88,36 @@
"type": "indirect"
}
},
"pre-commit-hooks": {
"quickshell": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"hyprland",
"nixpkgs"
]
},
"locked": {
"lastModified": 1760663237,
"narHash": "sha256-BflA6U4AM1bzuRMR8QqzPXqh8sWVCNDzOdsxXEguJIc=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "ca5b894d3e3e151ffc1db040b6ce4dcc75d31c37",
"lastModified": 1761821581,
"narHash": "sha256-nLuc6jA7z+H/6bHPEBSOYPbz7RtvNCZiTKmYItJuBmM=",
"owner": "quickshell-mirror",
"repo": "quickshell",
"rev": "db1777c20b936a86528c1095cbcb1ebd92801402",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"owner": "quickshell-mirror",
"repo": "quickshell",
"rev": "db1777c20b936a86528c1095cbcb1ebd92801402",
"type": "github"
}
},
"root": {
"inputs": {
"home-manager": "home-manager",
"hyprland": "hyprland",
"nixgl": "nixgl",
"nixpkgs": "nixpkgs_3"
"nixpkgs": "nixpkgs_2",
"quickshell": "quickshell"
}
},
"systems": {
"locked": {
"lastModified": 1689347949,
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
"owner": "nix-systems",
"repo": "default-linux",
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default-linux",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
@@ -494,47 +131,6 @@
"repo": "default",
"type": "github"
}
},
"xdph": {
"inputs": {
"hyprland-protocols": [
"hyprland",
"hyprland-protocols"
],
"hyprlang": [
"hyprland",
"hyprlang"
],
"hyprutils": [
"hyprland",
"hyprutils"
],
"hyprwayland-scanner": [
"hyprland",
"hyprwayland-scanner"
],
"nixpkgs": [
"hyprland",
"nixpkgs"
],
"systems": [
"hyprland",
"systems"
]
},
"locked": {
"lastModified": 1760713634,
"narHash": "sha256-5HXelmz2x/uO26lvW7MudnadbAfoBnve4tRBiDVLtOM=",
"owner": "hyprwm",
"repo": "xdg-desktop-portal-hyprland",
"rev": "753bbbdf6a052994da94062e5b753288cef28dfb",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "xdg-desktop-portal-hyprland",
"type": "github"
}
}
},
"root": "root",
+9 -5
View File
@@ -9,13 +9,17 @@
url = "github:nix-community/home-manager/release-25.05";
inputs.nixpkgs.follows = "nixpkgs";
};
hyprland = {
url = "github:hyprwm/Hyprland";
};
#hyprland = {
# url = "github:hyprwm/Hyprland";
#};
nixgl.url = "github:nix-community/nixGL";
quickshell = {
url = "github:quickshell-mirror/quickshell/db1777c20b936a86528c1095cbcb1ebd92801402";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { nixpkgs, home-manager, nixgl, ... }:
outputs = { nixpkgs, home-manager, nixgl, quickshell, ... }:
let
home_attrs = rec {
username = import ./username.nix;
@@ -31,7 +35,7 @@
homeConfigurations = {
illogical_impulse = home-manager.lib.homeManagerConfiguration {
inherit pkgs;
extraSpecialArgs = { inherit nixgl home_attrs; };
extraSpecialArgs = { inherit home_attrs nixgl quickshell; };
modules = [
./home.nix
];
+162 -24
View File
@@ -1,4 +1,4 @@
{ config, lib, pkgs, nixgl, home_attrs, ... }:
{ config, lib, pkgs, nixgl, quickshell, home_attrs, ... }:
{
programs.home-manager.enable = true;
nixGL.packages = nixgl.packages;
@@ -10,6 +10,7 @@
xdg-desktop-portal-gnome
xdg-desktop-portal-gtk
xdg-desktop-portal-wlr
kdePackages.xdg-desktop-portal-kde
];
config.hyprland = {
default = [ "hyprland" "gtk" ];
@@ -21,7 +22,6 @@
## Allow fontconfig to discover fonts in home.packages
fonts.fontconfig.enable = true;
# home.sessionVariables.NIXOS_OZONE_WL = "1";
wayland.windowManager.hyprland = {
## Make sure home-manager not generate ~/.config/hypr/hyprland.conf
systemd.enable = false; plugins = []; settings = {}; extraConfig = "";
@@ -30,44 +30,182 @@
package = config.lib.nixGL.wrap pkgs.hyprland;
};
home = {
packages = with pkgs; [
##### Sure #####
## Basic cli tool
## inetutils: provides hostname, ifconfig, ping, etc.
## libnotify: provides notify-send
jq rsync inetutils libnotify
## Media related
brightnessctl pavucontrol
## Clipboard/Emoji
wl-clipboard cliphist
## Terminal and shell
foot cowsay lolcat
inetutils libnotify
##### Fonts/Icons/Cursors/Decoration #####
fontconfig
##### Other MISC #####
dbus xorg.xlsclients # some basic things
foot # Used in Quickshell and Hyprland config; its config is also included
kdePackages.kconfig # provide kwriteconfig6, used in install script
##### Other basic things #####
dbus xorg.xlsclients networkmanager
##### Not work, to be solved #####
# swaylock pamtester
# hyprlock pamtester
# TODO: migrate all packages from dist-arch. Note that for each package, must know why it's needed and how it's used specifically, cuz things may be need tweak to properly use the package installed by Nix, especially those have hardcoded path /usr/* .
# NOTE: below are migrated from dist-arch. For each package, must know why it's needed and how it's used specifically, cuz things may be need tweak to properly use the package installed by Nix, for example those have hardcoded path /usr/* .
### illogical-impulse-audio
libcava #cava
lxqt.pavucontrol-qt #pavucontrol-qt
libcava #cava (Used in Quickshell config)
lxqt.pavucontrol-qt #pavucontrol-qt (Used in Hyprland and Quickshell config)
wireplumber #wireplumber (not explicitly used)
pipewire #pipewire-pulse
libdbusmenu-gtk3 #libdbusmenu-gtk3 (not explicitly used)
playerctl #playerctl
pipewire #pipewire-pulse (not explicitly used)
libdbusmenu-gtk3 #libdbusmenu-gtk3 (not explicitly used)
playerctl #playerctl (Used in Hyprland and Quickshell config)
### illogical-impulse-backlight
# TODO: geoclue is used in https://github.com/end-4/dots-hyprland/blob/0551c010b586dbf5578c32de2735698cca0801a7/dots/.config/hypr/hyprland/scripts/start_geoclue_agent.sh with hardcoded absolute path to search the agent. Below will not work without futher tweaks in that start_geoclue_agent.sh
geoclue2 # geoclue
brightnessctl # brightnessctl
ddcutil # ddcutil
(geoclue2.override { withDemoAgent = true; }) #geoclue (which demo agent used in Quickshell config)
brightnessctl #brightnessctl (Used in Hyprland and Quickshell config)
ddcutil #ddcutil (Used in Quickshell config)
### illogical-impulse-basic
bc #bc (Used in quickshell/ii/scripts/colors/switchwall.sh for example)
uutils-coreutils-noprefix #coreutils (Too many executables involved, not sure where been used)
cliphist #cliphist (Used in Hyprland and Quickshell config)
cmake #cmake (Used in building quickshell and MicroTeX)
curlFull #curl (Used in Quickshell config)
wget #wget (Used in Quickshell config)
ripgrep #ripgrep (Not sure where been used)
jq #jq (Widely used)
xdg-user-dirs #xdg-user-dirs (Used in Hyprland and Quickshell config)
rsync #rsync (Used in install script)
yq-go #go-yq (Used in install script)
### illogical-impulse-bibata-modern-classic-bin
bibata-cursors #https://github.com/ful1e5/Bibata_Cursor (Used in Hyprland config, not necessary)
### illogical-impulse-fonts-themes
adw-gtk3 #adw-gtk-theme-git (https://github.com/lassekongo83/adw-gtk3) (Used in Quickshell config)
kdePackages.breeze kdePackages.breeze-icons #breeze (Used in kdeglobals config)
#breeze-plus (https://github.com/mjkim0727/breeze-plus) (TODO: Not available as nixpkg) (Used in kde-material-you-colors config)
darkly darkly-qt5 #darkly-bin (darkly is supposed to be set as the theme for Qt apps, just have not figured out how to properly set it yet.)
eza #eza (Used in Fish config: `alias ls 'eza --icons'`)
#fish (Install via system PM instead)
fontconfig #fontconfig (Basic thing)
kitty #kitty (Used in fuzzel, Hyprland, kdeglobals and Quickshell config; kitty config is also included as dots)
matugen #matugen-bin (Used in Quickshell)
#otf-space-grotesk (https://events.ccc.de/congress/2024/infos/styleguide.html) (TODO: Not available as Nixpkg) (Used in Quickshell and matugen config)
starship #starship (Used in Fish config)
#ttf-gabarito-git (Font name: Gabarito) (Used in fuzzel and Quickshell config) (TODO: Not available as Nixpkg)
nerd-fonts.jetbrains-mono #ttf-jetbrains-mono-nerd (Font name: JetBrains Mono NF, JetBrainsMono Nerd Font) (Used in foot, kdeglobals, kitty, qt5ct, qt6ct and Quickshell config)
material-symbols #ttf-material-symbols-variable-git (Font name: Material Symbols Rounded, Material Symbols Outlined) (Used in Hyprland, matugen, Quickshell and wlogout config)
#ttf-readex-pro (Font name: Readex Pro) (Used in Quickshell config) (TODO: seems not available as nixpkg)
roboto-flex #ttf-roboto-flex (Font name: Roboto Flex) (Used in Hyprland, matugen and Quickshell config)
rubik #ttf-rubik-vf (Font name: Rubik, Rubik Light) (Used in Hyprland, kdeglobals, matugen, qt5ct, qt6ct and Quickshell config)
twemoji-color-font #ttf-twemoji (Not explicitly used, but it may help as fallback for displaying emoji charaters)
### illogical-impulse-hyprland
hypridle #hypridle (Used for loginctl to lock session)
#hyprland (Need NixGL, included elsewhere)
#hyprlock (Should not be installed via Nix)
hyprpicker #hyprpicker (Used in Hyprland and Quickshell config)
hyprsunset #hyprsunset (Used in Quickshell config)
#xdg-desktop-portal-hyprland (DUPLICATE)
wl-clipboard #wl-clipboard (Surely needed)
### illogical-impulse-kde
kdePackages.bluedevil #bluedevil (Seems not being used anywhere, maybe a part of KDE settings panel)
#gnome-keyring #gnome-keyring (TODO: Install via system PM instead) (Provide executable gnome-keyring-daemon, used in Hyprland and Quickshell config)
networkmanager #networkmanager
kdePackages.plasma-nm #plasma-nm (Seems not being used anywhere, maybe a part of KDE settings panel)
#polkit-kde-agent (TODO: Install via system PM instead)
kdePackages.dolphin #dolphin (Used in Hyprland and Quickshell config)
kdePackages.systemsettings #systemsettings (Used in Hyprland keybinds.conf)
### illogical-impulse-microtex-git
# This package will be installed as /opt/MicroTeX
#MicroTeX#https://github.com/NanoMichael/MicroTeX
# TODO: Not available as nixpkg
### illogical-impulse-oneui4-icons-git
#OneUI4-Icons#https://github.com/end-4/OneUI4-Icons
# TODO: Custom repo, need to make a package
### illogical-impulse-portal
#xdg-desktop-portal (Included elsewhere)
#xdg-desktop-portal-kde (Included elsewhere)
#xdg-desktop-portal-gtk (Included elsewhere)
#xdg-desktop-portal-hyprland (Included elsewhere)
### illogical-impulse-python
#clang (Some python package may need this to be built, e.g. #1235; However when cmake is installed by Nix, then pkg-config, cairo etc will be used but they can only be accessible in Nix development environment for example nix-shell, nix develop, etc. See `sdata/uv/shell.nix`. )
uv #uv (Used for python venv)
gtk4 #gtk4 (Not explicitly used)
libadwaita #libadwaita (Not explicitly used)
libsoup_3 #libsoup3 (Not explicitly used)
libportal-gtk4 #libportal-gtk4 (Not explicitly used)
gobject-introspection #gobject-introspection (Not explicitly used)
### illogical-impulse-quickshell-git
#quickshell.packages.x86_64-linux.default (NixGL applicable, included elsewhere)
### illogical-impulse-screencapture
hyprshot #hyprshot (Used in Hyprland keybinds.conf as fallback)
slurp #slurp (Used in Hyprland and Quickshell config)
swappy #swappy (Used in Quickshell config)
tesseract #tesseract (Used in Quickshell and Hyprland config)
#tesseract-data-eng (Used as data for tesseract) (TODO: Seems not available as nixpkg)
wf-recorder #wf-recorder (Used in Quickshell config)
### illogical-impulse-toolkit
kdePackages.kdialog #kdialog (Used in Quickshell config)
# https://nixos.wiki/wiki/Qt
# https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/libraries/qt-6/srcs.nix
qt6.qt5compat #qt6-5compat
#qt6.qtimageformats (TODO: really?) #qt6-avif-image-plugin
qt6.qtbase #qt6-base
qt6.qtdeclarative #qt6-declarative
qt6.qtimageformats #qt6-imageformats
qt6.qtmultimedia #qt6-multimedia
qt6.qtpositioning #qt6-positioning
qt6.qtquicktimeline #qt6-quicktimeline
qt6.qtsensors #qt6-sensors
qt6.qtsvg #qt6-svg
qt6.qttools #qt6-tools
qt6.qttranslations #qt6-translations
qt6.qtvirtualkeyboard #qt6-virtualkeyboard
qt6.qtwayland #qt6-wayland
kdePackages.syntax-highlighting #syntax-highlighting (Used in Quickshell config)
upower #upower (Used in Quickshell config)
wtype #wtype (Used in Hyprland scripts/fuzzel-emoji.sh)
ydotool #ydotool (Used in Quickshell config)
### illogical-impulse-widgets
fuzzel #fuzzel (Used in Hyprland and Quickshell config; its config is also included)
glib #glib2 (Provide executable gsettings) (Used in install script, also in matugen and quickshell config)
imagemagick #imagemagick (Provide executable: magick) (Used in Quickshell config)
#hypridle (DUPLICATE)
#hyprutils (DUPLICATE)
#hyprlock (DUPLICATE)
#hyprpicker (DUPLICATE)
songrec #songrec (Used in Quickshell config)
translate-shell #translate-shell (Used in Quickshell config)
wlogout #wlogout (Used in Hyprland config)
]
++ [
#(config.lib.nixGL.wrap pkgs.hyprland)
(config.lib.nixGL.wrap quickshell.packages.x86_64-linux.default)
];
}//home_attrs;
}
+22 -6
View File
@@ -1,6 +1,14 @@
# This script is meant to be sourced.
# It's not for directly running.
function vianix-warning(){
printf "${STY_YELLOW}Currently \"--via-nix\" will run:\n"
printf " home-manager switch --flake .#illogical_impulse\n"
printf "If you are already using home-manager, it may override your current config,\n"
printf "despite that this should be reversible.\n"
pause
}
function install_home-manager(){
# https://nix-community.github.io/home-manager/index.xhtml#sec-install-standalone
local cmd=home-manager
@@ -16,6 +24,11 @@ function install_home-manager(){
command -v $cmd && return
echo "Failed in installing $cmd."
echo "Please install it by yourself and then retry."
echo ""
echo "Hint: It's also possible that the installation is actually successful,"
echo "but your \"\$PATH\" is not properly set."
echo "This can happen when you have used \"su user\" to switch user."
echo "If this is the problem, use \"su - user\" instead."
return 1
}
function install_nix(){
@@ -48,8 +61,8 @@ function install_curl(){
echo "Please install it by yourself and then retry."
return 1
}
function install_zsh(){
local cmd=zsh
function install_fish(){
local cmd=fish
if [[ "$OS_DISTRO_ID" == "arch" || "$OS_DISTRO_ID_LIKE" == "arch" || "$OS_DISTRO_ID" == "cachyos" ]]; then
x sudo pacman -Syu
@@ -99,15 +112,18 @@ function hm_deps(){
##################################################
##################################################
vianix-warning
if ! command -v curl >/dev/null 2>&1;then
echo -e "${STY_YELLOW}[$0]: \"curl\" not found.${STY_RST}"
showfun install_curl
v install_curl
fi
if ! command -v zsh >/dev/null 2>&1;then
echo -e "${STY_YELLOW}[$0]: \"zsh\" not found.${STY_RST}"
showfun install_zsh
v install_zsh
if ! command -v fish >/dev/null 2>&1;then
echo -e "${STY_YELLOW}[$0]: \"fish\" not found.${STY_RST}"
showfun install_fish
v install_fish
fi
if ! command -v swaylock >/dev/null 2>&1;then
echo -e "${STY_YELLOW}[$0]: \"swaylock\" not found.${STY_RST}"
+7 -18
View File
@@ -5,21 +5,6 @@
# This file is provided for any distros, mainly non-Arch(based) distros.
install-agsv1(){
x mkdir -p $REPO_ROOT/cache/agsv1
x cd $REPO_ROOT/cache/agsv1
try git init -b main
try git remote add origin https://github.com/Aylur/ags.git
x git pull origin main && git submodule update --init --recursive
x git fetch --tags
x git checkout v1.9.0
x npm install
x meson setup build # --reconfigure
x meson install -C build
x sudo mv /usr/local/bin/ags{,v1}
x cd $REPO_ROOT
}
install-Rubik(){
x mkdir -p $REPO_ROOT/cache/Rubik
x cd $REPO_ROOT/cache/Rubik
@@ -68,8 +53,8 @@ install-bibata(){
x cd $REPO_ROOT/cache/bibata-cursor
name="Bibata-Modern-Classic"
file="$name.tar.xz"
# Use axel because `curl -O` always downloads a file with 0 byte size, idk why
x axel https://github.com/ful1e5/Bibata_Cursor/releases/latest/download/$file
try rm $file
x curl -JLO https://github.com/ful1e5/Bibata_Cursor/releases/latest/download/$file
tar -xf $file
x sudo mkdir -p /usr/local/share/icons
x sudo cp -r $name /usr/local/share/icons
@@ -103,6 +88,10 @@ install-python-packages(){
# we need python 3.12 https://github.com/python-pillow/Pillow/issues/8089
x uv venv --prompt .venv $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV) -p 3.12
x source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate
x uv pip install -r sdata/uv/requirements.txt
if [[ "$INSTALL_VIA_NIX" = true ]]; then
x nix-shell ${REPO_ROOT}/sdata/uv/shell.nix --run "uv pip install -r ${REPO_ROOT}/sdata/uv/requirements.txt"
else
x uv pip install -r ${REPO_ROOT}/sdata/uv/requirements.txt
fi
x deactivate
}
+9 -3
View File
@@ -65,9 +65,15 @@ esac
# Helpful link(s):
# http://stackoverflow.com/questions/29581754
# https://github.com/which-distro/os-release
export OS_RELEASE_FILE=${OS_RELEASE_FILE:-/etc/os-release}
test -f ${OS_RELEASE_FILE} || \
( echo "${OS_RELEASE_FILE} does not exist. Aborting..." ; exit 1 ; )
OS_RELEASE_FILE_CUSTOM="${REPO_ROOT}/os-release"
if test -f "${OS_RELEASE_FILE_CUSTOM}"; then
printf "${STY_YELLOW}Warning: using custom os-release file \"${OS_RELEASE_FILE_CUSTOM}\".${STY_RST}\n"
OS_RELEASE_FILE="${OS_RELEASE_FILE_CUSTOM}"
elif test -f /etc/os-release; then
OS_RELEASE_FILE=/etc/os-release
else
printf "${STY_RED}/etc/os-release does not exist, aborting...${STY_RST}\n" ; exit 1
fi
export OS_DISTRO_ID=$(awk -F'=' '/^ID=/ { gsub("\"","",$2); print tolower($2) }' ${OS_RELEASE_FILE} 2> /dev/null)
export OS_DISTRO_ID_LIKE=$(awk -F'=' '/^ID_LIKE=/ { gsub("\"","",$2); print tolower($2) }' ${OS_RELEASE_FILE} 2> /dev/null)
+1
View File
@@ -9,6 +9,7 @@
# TODO: add --exp-files-regen Force copy the default config to ${EXP_FILE_PATH} (auto do this when not existed)
# TODO: Implement versioning, i.e. when user-defined yaml config file has version number mismatch with the default one, produce error. If only minor version number is not the same, the error can be ommitted via --exp-file-no-strict .
# TODO: add --exp-files-no-strict Ignore error when minor version number is not the same
# TODO: When --via-nix is specified, use dots-extra/vianix/hypridle.conf instead
#
# Stage 2 todos:
# TODO: Implement bool key symlink (both read-write and read-only), when the value of `symlink` is true, then instead using `rsync` or `cp`, use `ln`.
+7 -2
View File
@@ -79,13 +79,18 @@ case $SKIP_HYPRLAND in
v cp dots/.config/hypr/hyprland.conf $t
fi
t="$XDG_CONFIG_HOME/hypr/hypridle.conf"
if [[ "$INSTALL_VIA_NIX" = true ]]; then
s=dots-extra/vianix/hypridle.conf
else
s=dots/.config/hypr/hypridle.conf
fi
if [ -f $t ];then
echo -e "${STY_BLUE}[$0]: \"$t\" already exists.${STY_RST}"
v cp -f dots/.config/hypr/hypridle.conf $t.new
v cp -f $s $t.new
existed_hypridle_conf=y
else
echo -e "${STY_YELLOW}[$0]: \"$t\" does not exist yet.${STY_RST}"
v cp dots/.config/hypr/hypridle.conf $t
v cp $s $t
existed_hypridle_conf=n
fi
t="$XDG_CONFIG_HOME/hypr/hyprlock.conf"
+12
View File
@@ -0,0 +1,12 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = with pkgs; [
pkg-config
meson
ninja
cairo
dbus
dbus-glib
glib
];
}