move quickshell config to subfolder to ease distribution

https://quickshell.outfoxxed.me/docs/guide/distribution/
This commit is contained in:
end-4
2025-07-10 22:03:01 +07:00
parent 5ee46cfc30
commit c69eaf7777
239 changed files with 65 additions and 49 deletions
+8
View File
@@ -0,0 +1,8 @@
[General]
UseTabs=false
IndentWidth=4
NewlineType=unix
NormalizeOrder=false
FunctionsSpacing=false
ObjectsSpacing=true
MaxColumnWidth=110
+64
View File
@@ -0,0 +1,64 @@
import "root:/modules/common/"
import QtQuick
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
pragma Singleton
pragma ComponentBehavior: Bound
Singleton {
id: root
property bool sidebarLeftOpen: false
property bool sidebarRightOpen: false
property bool overviewOpen: false
property bool workspaceShowNumbers: false
property bool superReleaseMightTrigger: true
property real screenZoom: 1
onScreenZoomChanged: {
Quickshell.execDetached(["hyprctl", "keyword", "cursor:zoom_factor", root.screenZoom.toString()]);
}
Behavior on screenZoom {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
// When user is not reluctant while pressing super, they probably don't need to see workspace numbers
onSuperReleaseMightTriggerChanged: {
workspaceShowNumbersTimer.stop()
}
Timer {
id: workspaceShowNumbersTimer
interval: Config.options.bar.workspaces.showNumberDelay
// interval: 0
repeat: false
onTriggered: {
workspaceShowNumbers = true
}
}
GlobalShortcut {
name: "workspaceNumber"
description: qsTr("Hold to show workspace numbers, release to show icons")
onPressed: {
workspaceShowNumbersTimer.start()
}
onReleased: {
workspaceShowNumbersTimer.stop()
workspaceShowNumbers = false
}
}
IpcHandler {
target: "zoom"
function zoomIn() {
screenZoom = Math.min(screenZoom + 0.4, 3.0)
}
function zoomOut() {
screenZoom = Math.max(screenZoom - 0.4, 1)
}
}
}
+156
View File
@@ -0,0 +1,156 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Qt5Compat.GraphicalEffects
Scope {
id: root
property bool failed;
property string errorString;
// Connect to the Quickshell global to listen for the reload signals.
Connections {
target: Quickshell
function onReloadCompleted() {
root.failed = false;
popupLoader.loading = true;
}
function onReloadFailed(error: string) {
// Close any existing popup before making a new one.
popupLoader.active = false;
root.failed = true;
root.errorString = error;
popupLoader.loading = true;
}
}
// Keep the popup in a loader because it isn't needed most of the time
LazyLoader {
id: popupLoader
PanelWindow {
id: popup
anchors.top: true
margins.top: 0
implicitWidth: rect.width + shadow.radius * 2
implicitHeight: rect.height + shadow.radius * 2
// color blending is a bit odd as detailed in the type reference.
color: "transparent"
Rectangle {
id: rect
anchors.centerIn: parent
color: failed ? "#ffe99195" : "#ffD1E8D5"
implicitHeight: layout.implicitHeight + 30
implicitWidth: layout.implicitWidth + 30
radius: 12
// Fills the whole area of the rectangle, making any clicks go to it,
// which dismiss the popup.
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: {
popupLoader.active = false
}
// makes the mouse area track mouse hovering, so the hide animation
// can be paused when hovering.
hoverEnabled: true
}
ColumnLayout {
id: layout
spacing: 10
anchors {
top: parent.top
topMargin: 10
horizontalCenter: parent.horizontalCenter
}
Text {
renderType: Text.NativeRendering
font.family: "Rubik"
font.pointSize: 14
text: root.failed ? "Quickshell: Reload failed" : "Quickshell reloaded"
color: failed ? "#ff93000A" : "#ff0C1F13"
}
Text {
renderType: Text.NativeRendering
font.family: "JetBrains Mono NF"
font.pointSize: 11
text: root.errorString
color: failed ? "#ff93000A" : "#ff0C1F13"
// When visible is false, it also takes up no space.
visible: root.errorString != ""
}
}
// A progress bar on the bottom of the screen, showing how long until the
// popup is removed.
Rectangle {
z: 2
id: bar
color: failed ? "#ff93000A" : "#ff0C1F13"
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: 10
height: 5
radius: 9999
PropertyAnimation {
id: anim
target: bar
property: "width"
from: rect.width - bar.anchors.margins * 2
to: 0
duration: failed ? 10000 : 1000
onFinished: popupLoader.active = false
// Pause the animation when the mouse is hovering over the popup,
// so it stays onscreen while reading. This updates reactively
// when the mouse moves on and off the popup.
paused: mouseArea.containsMouse
}
}
// Its bg
Rectangle {
z: 1
id: bar_bg
color: failed ? "#30af1b25" : "#4027643e"
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: 10
height: 5
radius: 9999
width: rect.width - bar.anchors.margins * 2
}
// We could set `running: true` inside the animation, but the width of the
// rectangle might not be calculated yet, due to the layout.
// In the `Component.onCompleted` event handler, all of the component's
// properties and children have been initialized.
Component.onCompleted: anim.start()
}
DropShadow {
id: shadow
anchors.fill: rect
horizontalOffset: 0
verticalOffset: 2
radius: 6
samples: radius * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs
color: "#44000000"
source: rect
}
}
}
}
@@ -0,0 +1 @@
openai-symbolic.svg
@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.0"
width="20"
height="19.999941"
id="svg2424"
sodipodi:docname="archlinux-logo-black-scalable.f931920e6cdb.svg"
viewBox="0 0 166.18749 166.187"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:zoom="16.650008"
inkscape:cx="13.093087"
inkscape:cy="16.366359"
inkscape:window-width="1340"
inkscape:window-height="768"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g2424" />
<defs
id="defs2426">
<linearGradient
x1="112.49854"
y1="6.1372099"
x2="112.49853"
y2="129.3468"
id="path1082_2_"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(287,-83)">
<stop
id="stop193"
style="stop-color:#ffffff;stop-opacity:0"
offset="0" />
<stop
id="stop195"
style="stop-color:#ffffff;stop-opacity:0.27450982"
offset="1" />
<midPointStop
offset="0"
style="stop-color:#FFFFFF"
id="midPointStop197" />
<midPointStop
offset="0.5"
style="stop-color:#FFFFFF"
id="midPointStop199" />
<midPointStop
offset="1"
style="stop-color:#000000"
id="midPointStop201" />
</linearGradient>
<linearGradient
x1="541.33502"
y1="104.50665"
x2="606.91248"
y2="303.14029"
id="linearGradient2544"
xlink:href="#path1082_2_"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.3937741,0,0,0.393752,357.51969,122.00151)" />
<linearGradient
id="linearGradient3388">
<stop
id="stop3390"
style="stop-color:#000000;stop-opacity:0"
offset="0" />
<stop
id="stop3392"
style="stop-color:#000000;stop-opacity:0.37113401"
offset="1" />
</linearGradient>
<linearGradient
x1="490.72305"
y1="237.72447"
x2="490.72305"
y2="183.9644"
id="linearGradient4416"
xlink:href="#linearGradient3388"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.749107,0,0,0.749107,-35.459862,91.44108)" />
</defs>
<g
transform="translate(-57.527313,-146.42741)"
id="layer1">
<g
transform="matrix(0.8746356,0,0,0.8746356,14.730518,23.408954)"
id="g2424"
style="fill:#000000">
<g
transform="matrix(0.6378586,0,0,0.6378586,36.486487,2.17139)"
id="g2809"
style="fill:#000000;fill-opacity:1" />
<path
d="m 143.91698,140.65081 c -8.45709,20.73453 -13.55799,34.29734 -22.97385,54.41552 5.7731,6.11948 12.85931,13.24593 24.36729,21.29458 -12.37221,-5.09109 -20.81157,-10.20242 -27.11844,-15.50646 -12.0505,25.14523 -30.930177,60.96349 -69.243121,129.80406 30.112687,-17.38458 53.455511,-28.10236 75.209891,-32.19198 -0.93414,-4.01773 -1.46524,-8.36369 -1.42916,-12.89823 l 0.0357,-0.96469 c 0.47781,-19.2924 10.51371,-34.12825 22.40218,-33.12093 11.88848,1.00732 21.12927,17.4729 20.65146,36.76531 -0.0899,3.63022 -0.49934,7.12245 -1.21479,10.36146 21.51819,4.20934 44.61141,14.89968 74.31666,32.04906 -5.85729,-10.78369 -11.08544,-20.5044 -16.07812,-29.7624 -7.86429,-6.09535 -16.06714,-14.02847 -32.79938,-22.61656 11.50078,2.98839 19.73519,6.43619 26.15375,10.29 -50.76203,-94.51003 -54.87267,-107.06846 -72.2801,-147.91874 z"
id="path2518"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.14333" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

@@ -0,0 +1,318 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="SVGRoot"
width="20"
height="20"
version="1.1"
viewBox="0 0 17.921003 17.921002"
sodipodi:docname="cachyos-symbolic.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview30"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="32"
inkscape:cx="10.671875"
inkscape:cy="11.234375"
inkscape:window-width="1687"
inkscape:window-height="1028"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="SVGRoot" />
<defs
id="defs6">
<linearGradient
id="linearGradient939"
x1="237.19"
x2="237.07001"
y1="296.20001"
y2="304.07999"
gradientTransform="matrix(0.04476,0,0,0.044679,-8.5042241,-4.351186)"
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient937" />
<linearGradient
id="linearGradient937">
<stop
stop-color="#001313"
offset="0"
id="stop1" />
<stop
stop-color="#001313"
stop-opacity="0"
offset="1"
id="stop2" />
</linearGradient>
<linearGradient
id="linearGradient5185"
x1="994.81"
x2="982.34003"
y1="1533.3"
y2="1556.8"
gradientTransform="matrix(0.084141,0,0,0.083989,-76.242924,-126.39098)"
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient4353" />
<linearGradient
id="linearGradient4353">
<stop
stop-color="#020202"
offset="0"
id="stop3" />
<stop
stop-color="#020202"
stop-opacity="0"
offset="1"
id="stop4" />
</linearGradient>
<linearGradient
id="linearGradient9102"
x1="1022.5"
x2="1018.6"
y1="1582.4"
y2="1575.6"
gradientTransform="matrix(0.086381,0,0,0.081808,-79.103924,-124.69099)"
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient4353" />
<linearGradient
id="linearGradient11890"
x1="940.42999"
x2="930.59003"
y1="1612.5"
y2="1594.5"
gradientTransform="matrix(0.084141,0,0,0.083989,-76.242924,-126.39098)"
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient4353" />
<linearGradient
id="linearGradient11670"
x1="965.59998"
x2="951.65997"
y1="1571.4"
y2="1571.3"
gradientTransform="matrix(0.084141,0,0,0.083989,-76.242924,-126.39098)"
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient4353" />
<linearGradient
id="linearGradient13770"
x1="946.22998"
x2="961.37"
y1="1655.9"
y2="1655.8"
gradientTransform="matrix(0.084141,0,0,0.083989,-76.242924,-126.39098)"
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient4353" />
<linearGradient
id="linearGradient2816"
x1="366.14999"
x2="350.92001"
y1="427.32001"
y2="419.64001"
gradientTransform="matrix(0.04476,0,0,0.044679,-10.832924,-4.155886)"
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient937" />
<linearGradient
id="linearGradient12421"
x1="936.34003"
x2="933.38"
y1="1628.8"
y2="1623"
gradientTransform="matrix(0.084141,0,0,0.083989,-76.242924,-126.39098)"
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient4353" />
<linearGradient
id="linearGradient13391"
x1="950.33002"
x2="941.96997"
y1="1618.6"
y2="1645.8"
gradientTransform="matrix(0.084141,0,0,0.083989,-76.242924,-126.39098)"
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient4353" />
<linearGradient
id="linearGradient13599"
x1="1008.2"
x2="1015.7"
y1="1681.3"
y2="1668.4"
gradientTransform="matrix(0.084141,0,0,0.083989,-77.884838,-124.43841)"
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient4353" />
<linearGradient
id="linearGradient18175"
x1="1148.3"
x2="1145.4"
y1="1585.5"
y2="1630"
gradientTransform="matrix(0.34992,0,0,0.34992,-282.87,-491.67)"
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient18299" />
<linearGradient
id="linearGradient18299">
<stop
stop-color="#008066"
stop-opacity="0"
offset="0"
id="stop5" />
<stop
stop-color="#0fc"
offset="1"
id="stop6" />
</linearGradient>
<linearGradient
id="linearGradient18632"
x1="1148.3"
x2="1145.4"
y1="1585.5"
y2="1630"
gradientTransform="matrix(0.26565,0,0,0.26565,-211.15,-375.49)"
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient18299" />
<linearGradient
id="linearGradient18659"
x1="1148.3"
x2="1145.4"
y1="1585.5"
y2="1630"
gradientTransform="matrix(0.13679,0,0,0.13679,-53.624,-195.03)"
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient18299" />
<linearGradient
id="linearGradient3254"
x1="348.04999"
x2="361.20999"
y1="194.78"
y2="187.24001"
gradientTransform="matrix(0.04476,0,0,0.044679,-10.832924,-4.155886)"
gradientUnits="userSpaceOnUse"
xlink:href="#linearGradient937" />
</defs>
<circle
cx="87.449997"
cy="87.449997"
r="87.449997"
opacity="0"
stroke-width="0.27971"
id="circle6" />
<path
d="m 4.0610759,2.168314 6.5887001,3.6879 2.1127,-3.6528 z"
fill="#00aa88"
id="path7"
style="fill:#000000" />
<path
d="m 6.1499759,12.423014 -1.9125,3.7456 h 8.5747001 l 2.1664,-3.7456 z"
fill="#00aa88"
id="path8"
style="fill:#7a7a7a;fill-opacity:0.68506807" />
<path
d="m 4.0610759,2.168314 6.5887001,3.6879 H 6.1237759 l -1.8859,3.2605 1.9121,3.306 -1.9125,3.7456 -4.13689997,-7.1525 3.96049997,-6.8475"
fill="#00ccff"
id="path9"
style="fill:#7a7a7a;fill-opacity:0.69262218" />
<path
d="m 6.0909759,5.821714 6.7111001,-3.7832 -2.169,3.5579 z"
fill="url(#linearGradient9102)"
id="path12"
style="fill:url(#linearGradient9102)" />
<path
d="m 6.1236759,5.856214 6.6388001,-3.6528 -2.1127,3.6528 z"
fill="#00aa88"
id="path13"
style="fill:#000000" />
<path
d="m 0.10057593,9.015814 6.02309997,-3.1596 -1.8859,3.2605 z"
fill="#00aa88"
id="path14"
style="fill:#1a1a1a" />
<path
d="m 6.1236759,5.856214 -2.0626,-3.6879 0.17673,6.9484 z"
fill="#00aa88"
id="path16"
style="fill:#1a1a1a" />
<path
d="m 4.2378759,9.116714 -3.1586,1.5811 3.1583,5.4705 z"
fill="#00aa88"
id="path19"
style="fill:#1a1a1a" />
<path
d="m 1.0792259,10.698014 5.0708,1.7248 -1.9121,-3.306 z"
fill="#00aa88"
id="path23"
style="fill:#1a1a1a" />
<g
transform="matrix(0.14699,0,0,0.14672,-0.75949407,-0.14715599)"
id="g26"
style="fill:#1a1a1a">
<circle
cx="117.95"
cy="75.441002"
r="9.6893997"
fill="#00ccff"
id="circle25"
style="fill:#1a1a1a" />
<circle
cx="118.08"
cy="75.341003"
r="9.6893997"
fill="url(#linearGradient18175)"
id="circle26"
style="fill:#1a1a1a" />
</g>
<g
transform="matrix(0.14699,0,0,0.14672,-0.11248407,-0.47061599)"
id="g28"
style="fill:#1a1a1a">
<circle
cx="93.138"
cy="55.044998"
r="7.3558998"
fill="#00ccff"
id="circle27"
style="fill:#1a1a1a" />
<circle
cx="93.238998"
cy="54.969002"
r="7.3558998"
fill="url(#linearGradient18632)"
id="circle28"
style="fill:#1a1a1a" />
</g>
<g
transform="matrix(0.14699,0,0,0.14672,-0.08243407,-0.04714599)"
id="g30"
style="fill:#000000">
<circle
cx="103.06"
cy="26.657"
r="3.7876999"
fill="#00ccff"
id="circle29"
style="fill:#000000" />
<circle
cx="103.11"
cy="26.618"
r="3.7876999"
fill="url(#linearGradient18659)"
id="circle30"
style="fill:#000000" />
</g>
<path
d="m 6.1236759,5.856214 -2.0626,-3.6879 0.52544,-0.0074 1.9387,3.4465 z"
fill="url(#linearGradient3254)"
id="path30"
style="fill:url(#linearGradient3254)" />
<path
d="M 12.808567,16.168211 6.1524738,12.428132 4.2457352,16.165572 Z"
fill="#00ccff"
id="path17"
style="fill:#1a1a1a;fill-opacity:1"
sodipodi:nodetypes="cccc" />
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

@@ -0,0 +1,10 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_26_18)">
<path d="M3 7.06015V4.74436C4.12 4.69624 4.90412 4.62466 5.35237 4.52962C6.06664 4.37804 6.64769 4.07549 7.09553 3.62195C7.40205 3.31158 7.63448 2.89754 7.7928 2.37985C7.8839 2.06947 7.92946 1.8387 7.92946 1.68752H10.9001V19H7.25588V7.06015H3ZM12.4228 5.65444V4.52962L15.0783 1H16.1879V4.64211H17V5.65444H16.1879V7.05594H15.0084V5.65444H12.4228ZM14.9823 2.53985L13.4031 4.64511H15.0102V2.53985H14.9823Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_26_18">
<rect width="20" height="20" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 660 B

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
fill="#000000"
width="20"
height="20"
viewBox="0 0 380.95238 380.95238"
version="1.1"
id="svg1"
sodipodi:docname="crosshair-symbolic.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="35"
inkscape:cx="10.371429"
inkscape:cy="7.9571429"
inkscape:window-width="1430"
inkscape:window-height="1028"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<title
id="title1">ionicons-v5_logos</title>
<metadata
id="metadata1">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:title>ionicons-v5_logos</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
style="fill:#000000;stroke-width:23.0377"
id="rect1"
width="380.95239"
height="57.142857"
x="-3.5527137e-15"
y="161.90475" />
<rect
style="fill:#000000;stroke-width:23.0451;stroke-dasharray:none"
id="rect1-5"
width="57.142857"
height="380.95239"
x="161.90475"
y="0" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
width="20"
height="20"
viewBox="-30.5 0 317.00242 317.00243"
version="1.1"
preserveAspectRatio="xMidYMid"
id="svg12"
sodipodi:docname="debian-symbolic.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs12" />
<sodipodi:namedview
id="namedview12"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:zoom="17.5"
inkscape:cx="13.742857"
inkscape:cy="12.628571"
inkscape:window-width="1295"
inkscape:window-height="867"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg12" />
<g
fill="#A80030"
id="g12"
style="fill:#1a1a1a"
transform="translate(0.05835351,0.0538775)">
<path
d="m 152.79662,167.42537 c -5.25095,0.0731 0.9935,2.70583 7.84865,3.76069 1.8935,-1.47856 3.61167,-2.97466 5.14283,-4.42984 -4.26913,1.04609 -8.61424,1.06947 -12.99148,0.66915"
id="path1"
style="fill:#1a1a1a" />
<path
d="m 180.9799,160.40073 c 3.12661,-4.31588 5.40581,-9.04086 6.20938,-13.92654 -0.70129,3.4831 -2.59187,6.4899 -4.3714,9.66326 -9.81521,6.18016 -0.92337,-3.67011 -0.006,-7.41328 -10.55448,13.2837 -1.44934,7.96554 -1.83213,11.67656"
id="path2"
style="fill:#1a1a1a" />
<path
d="m 191.38244,133.33075 c 0.63409,-9.45579 -1.86135,-6.46652 -2.69999,-2.85777 0.9789,0.50844 1.75324,6.66522 2.69999,2.85777"
id="path3"
style="fill:#1a1a1a" />
<path
d="m 132.88569,4.0879643 c 2.80225,0.5025946 6.05451,0.8883068 5.59867,1.5574589 3.06524,-0.6720742 3.76069,-1.2915513 -5.59867,-1.5574589"
id="path4"
style="fill:#1a1a1a" />
<path
d="m 138.48436,5.6454232 -1.98116,0.4090887 1.84382,-0.1636355 0.13734,-0.2454532"
id="path5"
style="fill:#1a1a1a" />
<path
d="m 225.86569,136.91612 c 0.31266,8.49151 -2.48375,12.61162 -5.00549,19.90509 l -4.53796,2.26752 c -3.71394,7.21165 0.35941,4.57887 -2.29967,10.31487 -5.79737,5.15452 -17.59373,16.12979 -21.36903,17.13205 -2.75551,-0.0614 1.86719,-3.25225 2.47206,-4.50289 -7.76099,5.32984 -6.22691,8.0006 -18.09633,11.23824 l -0.34772,-0.77142 c -29.27322,13.77168 -69.93663,-13.52038 -69.40189,-50.75913 -0.31266,2.36394 -0.88831,1.77369 -1.537,2.7292 -1.51071,-19.15996 8.848,-38.40465 26.31901,-46.262078 17.08821,-8.459369 37.12187,-4.987959 49.36238,6.419768 -6.72366,-8.807092 -20.1067,-18.14308 -35.96765,-17.269383 -15.53661,0.245453 -30.07094,10.1191 -34.92156,20.837223 -7.9597,5.01133 -8.88307,19.31775 -12.351558,21.93592 -4.666532,34.29623 8.777878,49.11401 31.520278,66.54411 3.57953,2.41362 1.00811,2.77888 1.49318,4.61685 -7.55646,-3.53861 -14.4759,-8.88014 -20.16515,-15.41972 3.01849,4.41816 6.27659,8.71359 10.48728,12.08857 -7.12399,-2.41362 -16.64115,-17.26354 -19.42003,-17.8684 12.28143,21.98851 49.827,38.56245 69.48663,30.33976 -9.09638,0.33604 -20.65313,0.18702 -30.8745,-3.59121 -4.29251,-2.20908 -10.13079,-6.78503 -9.08761,-7.64119 26.83037,10.02267 54.54612,7.59151 77.7619,-11.0191 5.90549,-4.59932 12.3574,-12.4246 14.22168,-12.53272 -2.8081,4.22238 0.47921,2.03083 -1.67727,5.75938 5.88504,-9.49085 -2.5568,-3.86296 6.08374,-16.38984 l 3.19089,4.39478 c -1.18636,-7.87788 9.78306,-17.44471 8.66975,-29.90438 2.5159,-3.81037 2.80811,4.09965 0.13734,12.86584 3.70517,-9.72462 0.97597,-11.28793 1.92856,-19.31191 1.02857,2.69707 2.37856,5.56361 3.07109,8.4097 -2.41362,-9.39735 2.47791,-15.82589 3.68764,-21.28722 -1.1922,-0.52889 -3.72563,4.15517 -4.3042,-6.94574 0.0847,-4.8214 1.34123,-2.52759 1.82629,-3.71394 -0.94675,-0.54351 -3.4305,-4.23991 -4.9412,-11.328836 1.09577,-1.665575 2.9279,4.318806 4.41815,4.564256 -0.95843,-5.636653 -2.6094,-9.935006 -2.67661,-14.259657 -4.35387,-9.0993 -1.53992,1.212656 -5.07269,-3.906796 -4.63439,-14.45544 3.84543,-3.354527 4.41815,-9.923322 7.02464,10.177541 11.03079,25.950835 12.86876,32.484565 -1.40259,-7.965545 -3.67011,-15.68271 -6.4373,-23.148578 2.13311,0.897073 -3.43634,-16.389844 2.77304,-4.941206 -6.63308,-24.40506 -28.38783,-47.208829 -48.40103,-57.909419 2.44869,2.241221 5.54023,5.055166 4.42984,5.496398 -9.95254,-5.925941 -8.20223,-6.387627 -9.62819,-8.891834 -8.10872,-3.299008 -8.64054,0.265908 -14.01129,0.0058 -15.28238,-8.105755 -18.22782,-7.243747 -32.2917,-12.32229 l 0.63993,2.9892691 c -10.12494,-3.3720592 -11.79636,1.279863 -22.73948,0.011688 -0.66623,-0.520127 3.50647,-1.8818077 6.93989,-2.3814803 -9.7889,1.2915513 -9.33014,-1.9285607 -18.90866,0.3564916 2.36103,-1.656809 4.85647,-2.7525822 7.37529,-4.1610159 -7.98308,0.4850622 -19.05769,4.6460781 -15.63888,0.8620082 C 96.316085,8.9298206 73.190888,17.085295 60.214012,29.25276 L 59.804924,26.526476 C 53.858528,33.665073 33.874548,47.845838 32.282025,57.091242 l -1.589602,0.371102 C 27.59796,62.7016 25.596347,68.63923 23.141816,74.030433 19.09476,80.926499 17.21003,76.683665 17.785676,77.764828 9.8259803,93.903375 5.8724309,107.46466 2.4565407,118.58603 4.8906182,122.224 2.514982,140.48688 3.4354314,155.10304 -0.56194899,227.28965 54.098137,297.37822 113.84553,313.5606 c 8.75742,3.13245 21.78105,3.01264 32.85859,3.33407 -13.07039,-3.73732 -14.75934,-1.98116 -27.49076,-6.41977 -9.18404,-4.32465 -11.19734,-9.26293 -17.70185,-14.90836 l 2.57434,4.54965 c -12.757724,-4.51458 -7.419118,-5.58698 -17.798281,-8.8743 l 2.74966,-3.59121 c -4.134717,-0.31266 -10.951887,-6.96912 -12.816162,-10.65384 l -4.523352,0.17825 C 66.26268,270.46895 63.366917,265.63586 63.577306,261.8927 l -1.461031,2.60356 c -1.656809,-2.84317 -19.995669,-25.15019 -10.481436,-19.95768 -1.767847,-1.6159 -4.117185,-2.62986 -6.665222,-7.2584 l 1.937326,-2.21493 c -4.57887,-5.89087 -8.427226,-13.44148 -8.135019,-15.95737 2.442843,3.299 4.137639,3.91556 5.814902,4.47952 -11.562598,-28.68881 -12.211295,-1.58084 -20.968714,-29.20309 l 1.852587,-0.14902 c -1.420122,-2.13895 -2.28213,-4.46199 -3.424657,-6.7412 l 0.80649,-8.03567 c -8.324954,-9.62527 -2.328884,-40.9264 -1.127916,-58.09351 0.832787,-6.9808 6.948662,-14.41161 11.600585,-26.064789 l -2.8344,-0.487985 c 5.417502,-9.449947 30.932945,-37.951737 42.749763,-36.484862 5.724319,-7.191194 -1.136683,-0.0263 -2.255832,-1.837977 12.573631,-13.011941 16.527181,-9.192806 25.012848,-11.533378 9.1519,-5.432112 -7.854502,2.118495 -3.51524,-2.071741 15.82004,-4.041212 11.21195,-9.186962 31.85047,-11.23825 2.17694,1.238955 -5.05224,1.913951 -6.86684,3.521085 13.18142,-6.448991 41.71243,-4.982116 60.24414,3.579525 21.50346,10.04897 45.66306,39.75465 46.61565,67.704172 l 1.08409,0.2922 c -0.54935,11.10968 1.70064,23.95799 -2.19739,35.76019 l 2.65323,-5.58698"
id="path6"
style="fill:#1a1a1a" />
<path
d="m 95.483297,174.6341 -0.736359,3.68179 c 3.450955,4.68699 6.188932,9.76553 10.595392,13.4298 -3.17043,-6.18893 -5.525615,-8.74573 -9.859033,-17.11159"
id="path7"
style="fill:#1a1a1a" />
<path
d="m 103.64169,174.31267 c -1.82629,-2.01915 -2.90745,-4.4503 -4.117181,-6.87269 1.157141,4.25744 3.526931,7.91586 5.733081,11.63565 l -1.6159,-4.76296"
id="path8"
style="fill:#1a1a1a" />
<path
d="m 248.00323,142.93557 -0.77142,1.9344 c -1.41428,10.04605 -4.46784,19.98691 -9.14898,29.20309 5.17205,-9.72462 8.51781,-20.36093 9.9204,-31.13749"
id="path9"
style="fill:#1a1a1a" />
<path
d="M 133.92302,1.5691471 C 137.47332,0.26882968 142.65122,0.85616408 146.41775,0 141.50869,0.4120107 136.623,0.65746388 131.79868,1.279863 l 2.12434,0.2892841"
id="path10"
style="fill:#1a1a1a" />
<path
d="m 9.2824769,67.847351 c 0.8181771,7.573984 -5.6980203,10.513578 1.4434981,5.519774 3.827901,-8.623004 -1.4960952,-2.38148 -1.4434981,-5.519774"
id="path11"
style="fill:#1a1a1a" />
<path
d="M 0.89031567,102.9004 C 2.5354364,97.85108 2.8334867,94.81798 3.46173,91.895919 -1.084998,97.707899 1.3695338,98.946854 0.89031567,102.9004"
id="path12"
style="fill:#1a1a1a" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.3 KiB

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="Layer_1"
version="1.1"
viewBox="0 0 56.202442 56.202035"
sodipodi:docname="DeepSeek_logo.svg"
width="20.000145"
height="20"
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview2"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="15.999999"
inkscape:cx="6.5625003"
inkscape:cy="7.1875003"
inkscape:window-width="1181"
inkscape:window-height="1028"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" />
<!-- Generator: Adobe Illustrator 29.2.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 116) -->
<defs
id="defs1">
<style
id="style1">
.st0 {
fill: #4d6bfe;
}
</style>
</defs>
<path
class="st0"
d="m 55.612804,10.892579 c -0.5953,-0.2917 -0.8517,0.2642 -1.1998,0.5466 -0.1191,0.0911 -0.2198,0.2095 -0.3206,0.3188 -0.8701,0.9292 -1.8867,1.5398 -3.2148,1.4668 -1.9417,-0.1094 -3.5995,0.5012 -5.065,1.9863 -0.3114,-1.8313 -1.3463,-2.9248 -2.9217,-3.6262 -0.8242,-0.3645 -1.6577,-0.729 -2.2348,-1.5217 -0.403,-0.5646998 -0.5129,-1.1933998 -0.7144,-1.8129998 -0.1283,-0.3735 -0.2565,-0.7563 -0.687,-0.8201 -0.4671,-0.0728 -0.6503,0.3188 -0.8335,0.647 -0.7327,1.3394 -1.0166,2.8153998 -0.9892,4.3095998 0.0641,3.3621 1.4838,6.0406 4.3047,7.9449 0.3206,0.2187 0.403,0.4372 0.3023,0.7563 -0.1924,0.656 -0.4214,1.2937 -0.6228,1.9497 -0.1283,0.4192 -0.3207,0.5103 -0.7694,0.3279 -1.5479,-0.6467 -2.8852,-1.6035 -4.0667,-2.7605 -2.0058,-1.9407 -3.8193,-4.0818 -6.0815,-5.7583 -0.5312,-0.3918 -1.0625,-0.7561 -1.6121,-1.1025 -2.3081,-2.2412 0.3023,-4.0817998 0.9068,-4.3002998 0.6319,-0.2278 0.2198,-1.0115 -1.8227,-1.0022 -2.0425,0.009 -3.9109,0.6924 -6.2922,1.6034998 -0.348,0.1367 -0.7145,0.2368 -1.09,0.3188 -2.1615,-0.4098998 -4.4055,-0.5011998 -6.7502,-0.2368 -4.4147001,0.4919 -7.9408001,2.5784 -10.5328001,6.1409 -3.11399997,4.2822 -3.84669997,9.1474 -2.94909997,14.2224 0.94339997,5.3481 3.67269997,9.7761 7.86759997,13.2385 4.3506001,3.5896 9.3606001,5.3481 15.0758001,5.011 3.4713,-0.2004 7.3364,-0.665 11.6961,-4.355 1.099,0.5467 2.2531,0.7652 4.1674,0.9292 1.4746,0.1367 2.8943,-0.0728 3.9933,-0.3005 1.7219,-0.3645 1.6029,-1.959 0.9801,-2.2505 -5.0466,-2.3506 -3.9385,-1.394 -4.9459,-2.1685 2.5645,-3.0339 6.4297,-6.1865 7.9409,-16.4001 0.119,-0.8108 0.0183,-1.3211 0,-1.9771 -0.0092,-0.4008 0.0824,-0.5556 0.5404,-0.6013 1.2639,-0.1458 2.4912,-0.4919 3.6178,-1.1115 3.2698,-1.7857 4.5886,-4.7195 4.9,-8.2364 0.0459,-0.5376 -0.0091,-1.0935 -0.577,-1.3757 z m -28.4938,31.6518 c -4.8909,-3.8447 -7.263,-5.1113 -8.2431,-5.0566 -0.9159,0.0547 -0.751,1.1025 -0.5496,1.7859 0.2107,0.6741 0.4855,1.1389 0.8701,1.731 0.2656,0.3918 0.4489,0.9748 -0.2655,1.4123 -1.5754,0.9749 -4.314,-0.3281 -4.4423,-0.3918 -3.1872,-1.877 -5.8525001,-4.3553 -7.7302001,-7.7444 -1.8135,-3.262 -2.8667,-6.7605 -3.0408,-10.4961 -0.0458,-0.9019 0.2198,-1.221 1.1174,-1.3848 1.1815,-0.2187 2.3997,-0.2644 3.5812,-0.0913 4.9918001,0.729 9.2415001,2.9612 12.8043001,6.4963 2.0333,2.0135 3.572,4.419 5.1566,6.7696 1.6852,2.4963 3.4987,4.8745 5.8068,6.8242 0.8151,0.6833 1.4654,1.2026 2.0882,1.5854 -1.8775,0.2095 -5.01,0.2552 -7.1532,-1.4397 z m 2.3447,-15.0788 c 0,-0.4009 0.3206,-0.7197 0.7237,-0.7197 0.0916,0 0.174,0.018 0.2473,0.0453 0.1008,0.0366 0.1924,0.0913 0.2656,0.1731 0.1283,0.1277 0.2015,0.3098 0.2015,0.5012 0,0.4009 -0.3205,0.7197 -0.7234,0.7197 -0.4029,0 -0.7145,-0.3188 -0.7145,-0.7197 z m 7.2815,3.7356 c -0.4671,0.1914 -0.9342,0.3552 -1.383,0.3735 -0.6961,0.0364 -1.4563,-0.2461 -1.8684,-0.5923 -0.6411,-0.5376 -1.0991,-0.8381 -1.2914,-1.7766 -0.0825,-0.4009 -0.0367,-1.0205 0.0367,-1.3757 0.1648,-0.7654 -0.0184,-1.2573 -0.5587,-1.7039 -0.4397,-0.3645 -0.9984,-0.4646 -1.6121,-0.4646 -0.229,0 -0.4395,-0.1003 -0.5953,-0.1823 -0.2565,-0.1275 -0.467,-0.4464 -0.2656,-0.8382 0.0641,-0.1274 0.3756,-0.4373 0.4489,-0.4919 0.8335,-0.4739 1.7952,-0.3189 2.6836,0.0364 0.8244,0.3371 1.4472,0.9567 2.3447,1.8313 0.9159,1.0568 1.0807,1.3486 1.6028,2.1411 0.4123,0.6196 0.7878,1.2573 1.0442,1.9863 0.1557,0.4556 -0.0458,0.8291 -0.5862,1.0569 z"
id="path1"
style="stroke-width:1.0004;stroke-dasharray:none;fill:#000000" />
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
<path d="m 12 1 c 1.660156 0 3 1.339844 3 3 v 6 c 0 1.660156 -1.339844 3 -3 3 h -8 c -1.660156 0 -3 -1.339844 -3 -3 v -6 c 0 -1.660156 1.339844 -3 3 -3 z m 0 2 h -8 c -0.554688 0 -1 0.445312 -1 1 v 6 c 0 0.554688 0.445312 1 1 1 h 8 c 0.554688 0 1 -0.445312 1 -1 v -6 c 0 -0.554688 -0.445312 -1 -1 -1 z m -4 11 c -5 0 -5 1 -5 1 c 0 1 1 1 1 1 h 8 c 1 0 1 -1 1 -1 s 0 -1 -5 -1 z m 0 0" fill="#2e3436"/>
</svg>

After

Width:  |  Height:  |  Size: 538 B

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="20mm"
height="20mm"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="EndeavourOS Logo.svg"
version="1.1"
viewBox="0 0 48.231007 48.231007"
id="svg8"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs8" />
<sodipodi:namedview
id="cvfa"
bordercolor="#666666"
borderopacity="1.0"
inkscape:current-layer="g3"
inkscape:cx="52.728754"
inkscape:cy="60.739468"
inkscape:document-rotation="0"
inkscape:document-units="mm"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:window-height="1028"
inkscape:window-maximized="1"
inkscape:window-width="1316"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:zoom="4.930896"
pagecolor="#ffffff"
showgrid="false"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" />
<title
id="title1">EndeavourOS Logo</title>
<metadata
id="metadata1">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>EndeavourOS Logo</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-66.790568,-123.01834)"
inkscape:groupmode="layer"
inkscape:label="Layer 1"
id="g8">
<g
transform="translate(76.2,-12.7)"
id="g7">
<g
transform="matrix(1.47,0,0,1.47,-519,105)"
id="g6">
<g
transform="matrix(0.963,0,0,0.983,13.5,0.76)"
id="g5">
<g
transform="matrix(0.678,0,0,0.678,452,49.2)"
id="g3">
<g
id="g9"
transform="translate(6.8384014e-4,3.6851185)">
<path
d="m -127,-42.3 c 4.57,6.45 23.8,31.4 10.7,36.6 -6.12,2.81 -34,-1.65 -33.6,-0.921 -2,3.28 -3.59,5.92 -3.59,5.92 0,0 21.5,0.967 38.1,-1.27 23.7,-3.18 -4.88,-33.5 -11.6,-40.3 z"
style="fill:#333333;fill-opacity:0.7;stroke-width:0.585"
inkscape:connector-curvature="0"
id="path1" />
<path
d="m -127,-42.3 c -1.52,0.209 -29.4,34.5 -29.4,34.5 0,0 2.01,0.57 6.58,1.23 1.48,-1.15 22.3,-36.2 22.9,-35.7 -0.0107,-0.0141 -0.028,-0.0193 -0.0522,-0.016 z"
style="fill:#333333;fill-opacity:0.7;stroke-width:0.585"
inkscape:connector-curvature="0"
id="path2" />
<path
d="m -127,-42.3 c -0.96,-0.156 -22.9,35.7 -22.9,35.7 0,0 19.9,2.1 28.1,1.96 23.1,-0.39 0.176,-30.6 -5.16,-37.7 -0.007,-0.007 -0.0151,-0.0108 -0.0248,-0.0124 z"
style="fill:#1a1a1a;stroke-width:0.585"
inkscape:connector-curvature="0"
id="path3" />
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 448.00288 448.00288"
version="1.1"
id="svg1"
sodipodi:docname="Fa-Team-Fontawesome-Brands-FontAwesome-Brands-Fedora.svg"
width="19.999744"
height="19.999744"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:zoom="13.671875"
inkscape:cx="19.2"
inkscape:cy="17.664"
inkscape:window-width="1313"
inkscape:window-height="908"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<!--! Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->
<path
d="M 0.0413,223.8 C 0.1219,100.2 100.3,0 224,0 347.7,0 448,100.3 448,224 448,347.7 347.8,447.9 224.1,448 H 50.93 C 22.84,448 0.0832,425.3 0.0416,397.2 H 0 V 223.8 Z M 342.6,160.7 c 0,-39.7 -35.6,-68.5 -73.2,-68.5 -34.9,0 -65.8,26.3 -70.1,59.9 -0.2,3.8 -0.4,5 -0.4,8.5 -0.1,21.1 0,42.8 -0.8,64.4 0.9,26.1 1,52.1 0,76.6 0,27.1 -19.4,45.5 -44.7,45.5 -25.3,0 -45.8,-20.2 -45.8,-45.5 0.5,-27.7 22.6,-45.3 48.5,-46.1 h 0.2 l 26.3,-0.2 V 218 l -26.3,0.2 c -47.1,-0.4 -84.58,36.5 -85.94,83.4 0,45.6 37.54,82.9 83.04,82.9 43,0 78.7,-33.6 82.6,-75.6 l 0.2,-53.5 32.6,-0.3 c 25.3,0.2 25,-37.8 -0.2,-37.3 l -32.4,0.3 c 0,-6.4 0.1,-12.8 0.1,-19.2 0.1,-12.7 0.1,-25.4 -0.1,-38.2 0.1,-16.5 15.8,-31.2 33.2,-31.2 17.5,0 35.9,8.7 35.9,31.2 0,3.2 -0.1,5.1 -0.3,6.3 -1.9,10.5 5.2,20.4 15.7,21.9 10.6,1.5 20.2,-6.1 21.2,-16.6 0.6,-4.2 0.7,-7.9 0.7,-11.6 z"
id="path1" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
role="img"
viewBox="0 0 24.000009 24.000009"
version="1.1"
id="svg1"
sodipodi:docname="flatpak_logo_icon_248537.svg"
width="20"
height="20"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:zoom="18.229167"
inkscape:cx="11.190857"
inkscape:cy="11.766857"
inkscape:window-width="1164"
inkscape:window-height="648"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<title
id="title1">Flatpak</title>
<path
d="m 12.000004,7.3336774e-5 c -0.556,0 -1.110999,0.143999993226 -1.609999,0.431999993226 L 2.7870046,4.8220733 a 3.217,3.217 0 0 0 -1.61,2.788 v 8.7799997 c 0,1.151 0.612,2.212 1.61,2.788 l 7.6030004,4.39 a 3.217,3.217 0 0 0 3.219999,0 l 7.603,-4.39 a 3.217,3.217 0 0 0 1.61,-2.788 V 7.6100733 a 3.217,3.217 0 0 0 -1.61,-2.788 l -7.603,-4.38999997 a 3.218,3.218 0 0 0 -1.61,-0.431999993226 z m 0,2.357999963226 c 0.15,0 0.299,0.039 0.431,0.115 l 7.604,4.39 c 0.132,0.077 0.24,0.187 0.315,0.316 l -8.35,4.8209997 v 9.642 a 0.863,0.863 0 0 1 -0.431,-0.116 l -7.6039994,-4.39 a 0.866,0.866 0 0 1 -0.431,-0.746 V 7.6100733 c 0,-0.153 0.041,-0.302 0.116,-0.43 l 8.3499994,4.8199997 z"
id="path1" />
<metadata
id="metadata1">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:title>Flatpak</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="98"
height="96"
version="1.1"
id="svg1"
sodipodi:docname="github-symbolic.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="2.4081633"
inkscape:cx="32.597458"
inkscape:cy="46.716102"
inkscape:window-width="1339"
inkscape:window-height="1028"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
fill="#fff"
id="path1"
style="fill:#000000" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -0,0 +1 @@
spark-symbolic.svg
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 20.999999 20.999999"
version="1.1"
id="svg4"
sodipodi:docname="Microsoft_icon.svg"
width="20"
height="20"
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs4" />
<sodipodi:namedview
id="namedview4"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="19.142857"
inkscape:cx="10.970149"
inkscape:cy="12.746269"
inkscape:window-width="1197"
inkscape:window-height="1028"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
fill="#f35325"
d="M 0,0 H 10 V 10 H 0 Z"
id="path1"
style="fill:#000000" />
<path
fill="#81bc06"
d="M 11,0 H 21 V 10 H 11 Z"
id="path2"
style="fill:#000000" />
<path
fill="#05a6f0"
d="M 0,11 H 10 V 21 H 0 Z"
id="path3"
style="fill:#000000" />
<path
fill="#ffba08"
d="M 11,11 H 21 V 21 H 11 Z"
id="path4"
style="fill:#000000" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="20"
width="19.999744"
version="1.1"
id="svg8"
sodipodi:docname="nixos-symbolic.svg"
viewBox="0 0 512.00001 512.00656"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview8"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:zoom="27.34375"
inkscape:cx="8.832"
inkscape:cy="15.817143"
inkscape:window-width="1075"
inkscape:window-height="1028"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg8" />
<g
fill-rule="evenodd"
transform="matrix(1.2756532,0,0,-1.2756532,9.0810546e-6,478.03773)"
id="g8"
style="fill:#000000">
<path
d="m 122.453,169.761 97.758,-169.34 -44.926,-0.422 -26.101,45.496 -26.286,-45.25 -22.32,0.008 -11.433,19.75 37.449,64.394 -26.582,46.258 z"
fill="#5277c3"
id="path1"
style="fill:#000000" />
<path
d="M 157.738,239.515 59.961,70.183 37.133,108.882 63.484,154.229 11.152,154.366 0,173.702 l 11.391,19.777 74.488,-0.234 26.769,46.152 z"
fill="#7ebae4"
id="path2"
style="fill:#000000" />
<path
d="M 165.238,104.155 360.77,104.143 338.672,65.026 286.223,65.171 312.27,19.784 301.102,0.456 278.277,0.429 241.238,65.058 187.883,65.167 Z"
fill="#7ebae4"
id="path3"
style="fill:#000000" />
<path
d="m 279.043,178.35 -97.758,169.34 44.926,0.422 26.101,-45.496 26.286,45.254 22.32,-0.008 11.434,-19.754 -37.45,-64.39 26.582,-46.262 z"
fill="#7ebae4"
id="path4"
style="fill:#000000" />
<g
fill="#5277c3"
id="g7"
style="fill:#000000">
<path
d="m 122.453,169.761 97.758,-169.34 -44.926,-0.422 -26.101,45.496 -26.286,-45.25 -22.32,0.008 -11.433,19.75 37.449,64.394 -26.582,46.258 z"
id="path5"
style="fill:#000000" />
<path
d="m 236,244.386 -195.535,0.011 22.101,39.118 52.45,-0.149 -26.047,45.391 11.168,19.328 22.82,0.023 37.043,-64.625 53.352,-0.109 z"
id="path6"
style="fill:#000000" />
<path
d="m 243.625,108.636 97.777,169.328 22.825,-38.696 -26.348,-45.351 52.332,-0.137 11.152,-19.336 -11.39,-19.777 -74.489,0.238 -26.769,-46.152 z"
id="path7"
style="fill:#000000" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="20"
height="20.000149"
viewBox="0 0 853.78869 853.79504"
fill="none"
version="1.1"
id="svg5"
sodipodi:docname="ollama-symbolic.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs5" />
<sodipodi:namedview
id="namedview5"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showguides="false"
inkscape:zoom="23.606557"
inkscape:cx="13.004861"
inkscape:cy="9.8065973"
inkscape:window-width="1374"
inkscape:window-height="848"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg5" />
<g
id="g5"
transform="translate(103.8943,1.5259608e-5)">
<path
d="M 140.629,0.23994313 C 132.66,1.5272641 123.097,5.6956941 116.354,10.845014 c -20.413,15.5091 -36.2287,48.4278 -42.9105,89.437996 -2.5133,15.509 -4.2297,37.026 -4.2297,53.455 0,19.371 2.2681,44.136 5.5171,61.239 0.7356,3.801 1.1034,7.173 0.7969,7.418 -0.2452,0.245 -3.249,2.697 -6.6206,5.394 -11.5245,9.195 -24.7043,23.356 -33.7768,36.291 -17.4095,24.704 -28.6888898,52.78 -33.4090698,83.185 -1.83902799,12.015 -2.32943599,36.29 -0.85821199,48.305 3.24895199,27.708 11.58588179,51.125 25.86898179,72.581 l 4.6589,6.927 -1.3486,2.268 c -9.563,16.061 -17.716,39.294 -21.5166498,61.607 -3.00375,17.655 -3.37156,22.375 -3.37156,46.037 0,23.847 0.30651,28.567 3.12635,45.057 3.3715598,19.739 10.2372598,40.642 17.8998598,54.558 2.5134,4.536 8.6435,13.976 9.3791,14.467 0.2452,0.122 -0.4904,2.39 -1.6551,5.026 -8.8274,19.31 -16.3674,44.995 -19.4938,66.635 -2.2068,14.834 -2.5133,19.616 -2.5133,35.248 0,19.922 1.1034,29.608 5.2719,45.485 l 0.613,2.329 H 44.019 70.3172 l -1.7165,-3.249 c -10.605,-19.616 -11.5858,-56.029 -2.452,-92.38 4.1685,-16.797 8.8887,-29.118 17.716,-46.099 l 5.2719,-10.298 v -6.314 c 0,-5.885 -0.1226,-6.559 -2.0229,-10.421 -1.4713,-2.943 -3.4329,-5.456 -6.9271,-8.889 -5.9462,-5.762 -10.2372,-11.831 -13.6701,-19.31 -15.08,-32.735 -18.0225,-81.346 -7.4174,-122.786 4.4137,-17.287 11.7085,-32.673 19.3711,-41.071 5.2106,-5.763 7.9078,-12.199 7.9078,-18.881 0,-6.927 -2.452,-12.628 -7.9691,-18.574 -15.8157,-16.919 -25.5625,-37.517 -29.0567,-61.485 -4.9654,-34.145 4.0459,-71.355 24.5204,-100.84 20.0455,-28.935 48.1824,-47.509 79.6304,-52.474 7.049,-1.165 20.229,-0.981 27.585,0.368 8.031,1.41 13.057,0.98 18.207,-1.472 6.375,-3.003 9.563,-6.743 13.302,-15.325 3.31,-7.662 5.885,-11.831 12.812,-20.474 8.337,-10.36 16.367,-17.41 29.24,-25.931 14.713,-9.624 31.448,-16.612 48.122,-19.984 6.068,-1.226 8.888,-1.41 20.229,-1.41 11.341,0 14.161,0.184 20.229,1.41 24.459,4.966 48.735,17.594 68.106,35.493 4.168,3.862 14.16,16.245 17.348,21.395 1.226,2.022 3.372,6.314 4.72,9.501 3.739,8.582 6.927,12.322 13.302,15.325 4.966,2.391 10.176,2.882 17.9,1.594 12.199,-2.084 21.578,-1.9 33.532,0.552 40.704,8.214 76.136,41.746 91.829,86.68 13.67,39.416 9.808,80.672 -10.544,112.18 -3.433,5.334 -6.866,9.625 -11.831,14.897 -10.728,11.463 -10.728,25.685 -0.061,37.455 17.532,19.187 28.505,66.389 25.194,108.012 -2.206,27.463 -9.256,52.045 -18.942,65.96 -1.716,2.452 -5.271,6.62 -7.969,9.195 -3.494,3.433 -5.455,5.946 -6.927,8.889 -1.9,3.862 -2.023,4.536 -2.023,10.421 v 6.314 l 5.272,10.298 c 8.828,16.981 13.548,29.302 17.716,46.099 9.012,35.861 8.215,71.538 -2.084,91.829 -0.858,1.716 -1.594,3.31 -1.594,3.494 0,0.184 11.709,0.306 26.053,0.306 h 25.992 l 0.674,-2.636 c 0.368,-1.409 0.981,-3.555 1.287,-4.781 0.675,-2.697 2.023,-10.666 3.127,-18.329 1.042,-7.724 1.042,-36.168 0,-44.75 -3.923,-31.141 -10.483,-55.845 -21.21,-79.201 -1.165,-2.636 -1.901,-4.904 -1.656,-5.026 0.307,-0.184 2.023,-2.636 3.862,-5.395 13.364,-20.229 21.578,-45.669 25.747,-79.262 1.103,-9.257 1.103,-49.041 0,-57.93 -2.943,-22.926 -6.498,-38.497 -12.383,-54.251 -2.452,-6.559 -8.95,-20.413 -11.708,-24.888 l -1.349,-2.268 4.659,-6.927 c 14.283,-21.456 22.62,-44.873 25.869,-72.581 1.471,-12.015 0.981,-36.29 -0.858,-48.305 -4.782,-30.467 -16,-58.42 -33.409,-83.185 -9.073,-12.935 -22.253,-27.096 -33.777,-36.291 -3.372,-2.697 -6.376,-5.149 -6.621,-5.394 -0.306,-0.245 0.062,-3.617 0.797,-7.418 7.418,-38.681 7.172,-86.924 -0.613,-124.624596 -6.743,-32.8573 -19.003,-58.9716 -34.819,-74.0516 C 523.209,4.2857941 510.336,-0.86349287 494.888,0.11732413 459.456,2.2015541 430.89,42.966714 419.61,107.21001 c -1.839,10.36 -3.432,22.498 -3.432,25.808 0,1.287 -0.246,2.329 -0.552,2.329 -0.307,0 -2.697,-1.226 -5.272,-2.758 -27.34,-16.184 -57.746,-24.827 -87.354,-24.827 -29.608,0 -60.014,8.643 -87.354,24.827 -2.575,1.532 -4.965,2.758 -5.272,2.758 -0.306,0 -0.552,-1.042 -0.552,-2.329 0,-3.433 -1.655,-15.938 -3.432,-25.808 C 216.152,49.525914 192.674,11.335414 161.472,1.7111341 157.181,0.42381313 144.982,-0.43436787 140.629,0.23994313 Z M 151.051,50.139014 c 8.827,6.9883 18.635,26.9724 24.275,49.3473 1.042,4.045696 2.145,8.704696 2.452,10.420696 0.245,1.656 0.919,5.395 1.471,8.276 2.391,12.996 3.494,27.034 3.617,44.137 l 0.061,16.858 -4.23,6.252 -4.229,6.314 h -9.87 c -11.524,0 -22.988,1.472 -33.961,4.414 -3.923,0.981 -7.724,1.962 -8.459,2.146 -1.165,0.245 -1.349,-0.123 -2.023,-5.15 -3.617,-27.279 -3.433,-57.5 0.552,-82.634 4.413,-28.014096 14.712,-53.392696 24.765,-60.871396 2.391,-1.7778 2.82,-1.7165 5.579,0.4904 z m 349.538,-0.4292 c 6.069,4.475 12.751,16.3674 17.716,31.57 9.992,30.405196 12.812,72.151196 7.54,111.874196 -0.674,5.027 -0.858,5.395 -2.023,5.15 -0.735,-0.184 -4.536,-1.165 -8.459,-2.146 -10.973,-2.942 -22.437,-4.414 -33.961,-4.414 h -9.87 l -4.229,-6.314 -4.23,-6.252 0.061,-16.858 c 0.123,-23.785 2.33,-42.359 7.601,-63.017596 5.579,-22.191 15.448,-42.1751 24.214,-49.1634 2.759,-2.2069 3.188,-2.2682 5.64,-0.4292 z"
fill="#000000"
id="path1" />
<path
d="m 313.498,358.23701 c -13.303,1.288 -16.919,1.778 -23.295,3.066 -10.36,2.145 -24.214,6.927 -33.838,11.647 -33.47,16.367 -56.519,43.646 -63.569,75.216 -1.41,6.253 -1.594,8.337 -1.594,18.881 0,10.421 0.184,12.689 1.533,18.635 9.379,41.256 47.385,71.723 96.549,77.301 10.666,1.165 56.765,1.165 67.431,0 39.478,-4.475 73.439,-25.869 88.703,-55.907 4.045,-8.03 6.007,-13.241 7.846,-21.394 1.349,-5.946 1.533,-8.214 1.533,-18.635 0,-10.544 -0.184,-12.628 -1.594,-18.881 -10.238,-45.853 -54.742,-81.959 -109.3,-88.825 -7.111,-0.858 -25.746,-1.594 -30.405,-1.104 z m 22.926,33.348 c 18.207,1.962 36.536,8.46 51.248,18.268 7.908,5.272 19.065,16.306 23.846,23.54 5.885,8.949 9.256,18.083 10.789,29.179 0.674,5.088 0.307,8.95 -1.533,17.164 -2.881,12.26 -11.831,25.072 -23.907,34.022 -5.64,4.107 -17.348,10.054 -24.52,12.383 -13.609,4.352 -22.498,5.149 -54.252,4.904 -20.719,-0.184 -24.398,-0.368 -30.344,-1.471 -20.29,-3.801 -36.351,-11.893 -47.998,-24.214 -9.441,-9.931 -13.732,-19.003 -16.061,-33.654 -1.042,-6.805 0.919,-18.084 4.904,-27.586 4.843,-11.586 17.348,-25.991 29.731,-34.267 14.344,-9.563 33.225,-16.367 50.573,-18.206 6.682,-0.736 20.842,-0.736 27.524,-0.062 z"
fill="#000000"
id="path2" />
<path
d="m 299.584,436.33601 c -4.659,2.513 -7.908,8.888 -6.927,13.608 1.103,5.088 5.578,10.238 12.566,14.468 3.74,2.268 3.985,2.574 4.169,4.842 0.122,1.349 -0.368,5.211 -1.042,8.644 -0.736,3.371 -1.288,6.927 -1.288,7.908 0.062,2.636 2.514,6.927 5.088,9.011 2.269,1.839 2.698,1.9 9.073,2.084 5.824,0.184 7.05,0.061 9.379,-1.042 6.008,-2.943 7.54,-8.337 5.333,-18.697 -1.839,-8.643 -1.471,-9.992 3.127,-12.628 4.842,-2.82 9.992,-7.785 11.524,-11.157 2.943,-6.436 0.245,-13.731 -6.253,-17.103 -1.593,-0.797 -3.555,-1.164 -6.436,-1.164 -4.475,0 -7.356,1.042 -12.628,4.413 l -3.004,1.901 -1.9,-1.165 c -7.785,-4.598 -9.195,-5.149 -13.916,-5.088 -3.371,0 -5.21,0.306 -6.865,1.165 z"
fill="#000000"
id="path3" />
<path
d="m 150.744,365.16501 c -10.85,3.433 -18.942,11.402 -23.11,22.743 -2.023,5.395 -3.004,13.916 -2.146,18.513 2.023,10.973 11.034,20.965 21.272,23.724 12.873,3.371 22.497,1.164 31.018,-7.295 4.965,-4.843 7.663,-9.073 10.36,-15.939 1.961,-4.842 2.084,-5.7 2.084,-12.566 l 0.061,-7.356 -2.574,-5.272 c -4.108,-8.337 -11.525,-14.529 -20.107,-16.797 -4.843,-1.226 -12.628,-1.164 -16.858,0.245 z"
fill="#000000"
id="path4" />
<path
d="m 478.153,364.98201 c -8.398,2.268 -15.877,8.52 -19.862,16.735 l -2.574,5.272 0.061,7.356 c 0,6.866 0.123,7.724 2.084,12.566 2.698,6.866 5.395,11.096 10.36,15.939 8.521,8.459 18.145,10.666 31.019,7.295 7.417,-1.962 14.834,-8.215 18.39,-15.51 3.065,-6.191 3.8,-10.666 2.82,-17.716 -2.268,-16.122 -11.709,-27.83 -25.747,-31.937 -4.107,-1.226 -12.076,-1.226 -16.551,0 z"
fill="#000000"
id="path5" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 322.58065 322.58064"
version="1.1"
id="svg1"
sodipodi:docname="openai-symbolic.svg"
width="20"
height="20"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:zoom="30.935922"
inkscape:cx="9.0348044"
inkscape:cy="14.917933"
inkscape:window-width="1183"
inkscape:window-height="1028"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="m 298.66868,131.77215 c 7.30439,-21.92321 4.7891,-45.939129 -6.89187,-65.880296 C 274.21007,35.30601 238.89549,19.570397 204.40593,26.975391 189.0627,9.6903649 167.01876,-0.13936259 143.90833,0.0014933 108.65412,-0.07899576 77.374064,22.618921 66.528162,56.162739 43.880552,60.800921 24.33177,74.977058 12.892261,95.069141 -4.8052718,125.57449 -0.77075743,164.02814 22.872905,190.18709 c -7.304382,21.92321 -4.789099,45.93913 6.891877,65.8803 17.566738,30.58585 52.881316,46.32146 87.370878,38.91646 15.33316,17.28503 37.38717,27.11476 60.49759,26.96384 35.27433,0.0905 66.56446,-22.62749 77.41036,-56.20149 22.64761,-4.63818 42.19639,-18.81432 53.6359,-38.9064 17.67741,-30.50536 13.63284,-68.92883 -10.00077,-95.08777 z M 177.65337,300.90986 c -14.11577,0.0201 -27.78885,-4.91989 -38.62469,-13.96485 0.493,-0.26159 1.34819,-0.73446 1.90156,-1.07654 l 64.10954,-37.02497 c 3.27993,-1.86131 5.29215,-5.35253 5.27203,-9.12545 v -90.37916 l 27.09463,15.64506 c 0.29178,0.14086 0.48294,0.42257 0.52318,0.74453 v 74.84477 c -0.0402,33.28222 -26.99402,60.26618 -60.27625,60.33661 z M 48.025738,245.54345 c -7.072976,-12.21422 -9.618443,-26.53121 -7.19371,-40.42564 0.472874,0.28171 1.307948,0.79483 1.901554,1.13691 l 64.109538,37.02497 c 3.24974,1.90155 7.2742,1.90155 10.534,0 l 78.26556,-45.19461 v 31.29012 c 0.0201,0.32196 -0.1308,0.63386 -0.38232,0.83508 l -64.80376,37.41735 c -28.86539,16.62099 -65.72938,6.74096 -82.4208,-22.08418 z M 31.153218,105.60314 c 7.042793,-12.234331 18.160346,-21.591185 31.400797,-26.450712 0,0.553362 -0.03018,1.529292 -0.03018,2.213449 v 74.059993 c -0.02012,3.76287 1.992105,7.25408 5.261973,9.11539 l 78.265542,45.18455 -27.09463,15.64506 c -0.27165,0.1811 -0.61373,0.21129 -0.91556,0.0805 L 53.227344,188.00382 C 24.42232,171.32247 14.542287,134.46853 31.143157,105.6132 Z m 222.612632,51.80478 -78.26556,-45.19461 27.09463,-15.634999 c 0.27165,-0.181101 0.61373,-0.211284 0.91557,-0.08049 l 64.81382,37.417349 c 28.85533,16.6713 38.74542,53.5856 22.07412,82.44093 -7.05285,12.21421 -18.16034,21.57107 -31.39073,26.44066 V 166.5233 c 0.0302,-3.76286 -1.97199,-7.24401 -5.23179,-9.11538 z m 26.96383,-40.58661 c -0.47287,-0.29178 -1.30794,-0.79483 -1.90155,-1.13691 L 214.71859,78.659433 c -3.24975,-1.901555 -7.2742,-1.901555 -10.53401,0 L 125.91903,123.85404 V 92.563919 c -0.0201,-0.321957 0.13079,-0.633852 0.38232,-0.835074 l 64.80376,-37.387171 c 28.86539,-16.651176 65.76963,-6.74096 82.41074,22.134493 7.03273,12.194093 9.5782,26.470833 7.19371,40.345143 z M 111.18953,172.59017 84.08484,156.94511 c -0.291773,-0.14086 -0.482934,-0.42257 -0.523179,-0.74453 V 81.355816 c 0.02012,-33.322473 27.054379,-60.326555 60.376859,-60.306432 14.09564,0 27.73854,4.950077 38.57438,13.964853 -0.493,0.261589 -1.33813,0.734462 -1.90155,1.076541 l -64.10954,37.02497 c -3.27993,1.86131 -5.29216,5.342462 -5.27204,9.115387 l -0.0402,90.338915 z m 14.71943,-31.73282 34.86183,-20.13232 34.86183,20.12226 v 40.2546 l -34.86183,20.12226 -34.86183,-20.12226 z"
id="path1"
style="stroke-width:1.00611" />
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="20.000015"
height="20"
id="svg1"
sodipodi:docname="openrouter-symbolic.svg"
viewBox="0 0 47.999998 47.999962"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="19.666667"
inkscape:cx="6.7118644"
inkscape:cy="10.805085"
inkscape:window-width="1908"
inkscape:window-height="1028"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="m 32.999999,2.9999808 c 3.980422,0.5880984 6.862472,1.8203168 10.3125,3.875 0.883008,0.5130469 1.766016,1.0260937 2.675781,1.5546875 0.663867,0.5182031 1.327735,1.0364062 2.011719,1.5703125 0,0.9900002 0,1.9800002 0,3.0000002 -2.311036,1.335928 -4.623718,2.668844 -6.9375,4 -0.659355,0.381562 -1.318711,0.763125 -1.998047,1.15625 -4.951172,2.84375 -4.951172,2.84375 -6.064453,2.84375 0,-1.65 0,-3.3 0,-5 -6.609294,1.004133 -10.792298,2.789518 -16,7 3.696448,3.823912 6.292078,6.155833 11.6875,6.625 0.808242,0.07477 1.616484,0.149531 2.449219,0.226562 0.614883,0.04898 1.229765,0.09797 1.863281,0.148438 0,-0.99 0,-1.98 0,-3 4.062255,0.578231 6.847624,1.891177 10.3125,4.0625 0.883008,0.547852 1.766016,1.095703 2.675781,1.660156 0.663867,0.421524 1.327735,0.843047 2.011719,1.277344 -1.156668,3.470003 -1.857267,3.847015 -4.875,5.6875 -0.698672,0.436992 -1.397344,0.873984 -2.117188,1.324219 -2.007812,0.988281 -2.007812,0.988281 -5.007812,0.988281 0,0.66 0,1.32 0,2 -0.99,0.33 -1.98,0.66 -3,1 0,-1.32 0,-2.64 0,-4 C 32.359335,40.962601 31.718671,40.925211 31.058593,40.8867 22.287404,40.220037 16.393127,37.373973 9.2226552,32.429668 6.2608222,30.524517 3.2830902,29.257335 -8.4473381e-7,27.999981 c 0,-3.3 0,-6.6 0,-10 C 1.6232682,17.32911 3.2486752,16.663411 4.8749992,15.999981 c 0.904922,-0.37125 1.809844,-0.7425 2.742188,-1.125 2.382812,-0.875 2.382812,-0.875 4.3828118,-0.875 0,-0.66 0,-1.32 0,-2 0.99,0 1.98,0 3,0 0,-0.66 0,-1.32 0,-2.0000002 6.284532,-3.3279791 10.886587,-4.4516452 18,-4 0,-0.99 0,-1.98 0,-3 z"
fill="#93a2b8"
id="path1"
style="fill:#000000" />
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
fill="#000000"
width="20"
height="20"
viewBox="0 0 380.95238 380.95238"
version="1.1"
id="svg1"
sodipodi:docname="google-gemini-symbolic.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="17.5"
inkscape:cx="10.828571"
inkscape:cy="16.971429"
inkscape:window-width="1351"
inkscape:window-height="981"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<title
id="title1">ionicons-v5_logos</title>
<path
d="m 190.47617,352.20814 v 0 0 C 177.81956,268.68717 112.26524,203.13276 28.744252,190.4762 v 0 0 C 112.26524,177.81957 177.81956,112.26522 190.47617,28.744235 v 0 0 c 12.65659,83.520985 78.2109,149.075335 161.73196,161.731965 v 0 0 c -83.52106,12.65656 -149.07537,78.21097 -161.73196,161.73194 z"
fill="#076eff"
id="path19"
style="fill:#000000;stroke-width:4.44566" />
<metadata
id="metadata1">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:title>ionicons-v5_logos</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
fill="#000000"
height="20.000002"
width="20"
version="1.1"
id="Capa_1"
viewBox="0 0 493.42511 493.42516"
xml:space="preserve"
sodipodi:docname="ubuntu-logo-svgrepo-com.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs9" /><sodipodi:namedview
id="namedview9"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:zoom="12.374369"
inkscape:cx="24.76894"
inkscape:cy="15.515943"
inkscape:window-width="1415"
inkscape:window-height="753"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="Capa_1" />
<g
id="ubuntu"
transform="translate(5.5573583e-4,0.00107926)">
<g
id="g2">
<g
id="g1">
<path
d="m 168.839,241.198 c 0,-38.117 17.894,-72.05 45.685,-93.896 L 171.988,79.22 c -35.648,25.603 -62.472,62.66 -75.127,105.796 19.811,12.751 32.949,35.031 32.949,60.353 0,24.424 -12.143,45.957 -30.783,58.918 13.606,40.86 40.12,75.838 74.706,100.113 l 39.559,-70.358 c -27.105,-21.838 -44.453,-55.318 -44.453,-92.844 z"
id="path1" />
</g>
</g>
<g
id="g3">
<path
d="m 109.704,245.368 c 0,28.484 -23.132,51.592 -51.609,51.592 -28.491,0 -51.606,-23.107 -51.606,-51.592 0,-28.47 23.115,-51.577 51.606,-51.577 28.477,0 51.609,23.107 51.609,51.577 z"
id="path2" />
</g>
<g
id="g5">
<g
id="g4">
<path
d="m 399.494,370.126 c 12.002,0 23.301,2.936 33.23,8.149 30.924,-32.591 50.906,-75.595 54.211,-123.228 l -80.148,-1.551 c -6.171,60.111 -56.954,106.941 -118.677,106.941 -17.084,0 -33.388,-3.594 -48.101,-10.093 l -39.841,69.704 c 26.56,13.069 56.376,20.411 87.941,20.411 13.622,0 26.981,-1.379 39.854,-4.006 2.746,-37.072 33.717,-66.327 71.531,-66.327 z"
id="path3" />
</g>
</g>
<g
id="g6">
<path
d="m 451.071,441.847 c 0,28.478 -23.084,51.576 -51.577,51.576 -28.493,0 -51.594,-23.098 -51.594,-51.576 0,-28.5 23.101,-51.592 51.594,-51.592 28.493,0 51.577,23.092 51.577,51.592 z"
id="path5" />
</g>
<g
id="g8">
<g
id="g7">
<path
d="m 438.211,110.152 c -11.677,8.269 -25.968,13.163 -41.399,13.163 -39.637,0 -71.73,-32.102 -71.73,-71.715 0,-2.104 0.094,-4.139 0.25,-6.181 -12.05,-2.307 -24.503,-3.491 -37.222,-3.491 -31.859,0 -61.988,7.498 -88.689,20.777 l 39.607,69.75 c 14.979,-6.748 31.593,-10.544 49.082,-10.544 61.177,0 111.601,46.074 118.491,105.414 l 80.147,-2.447 C 483.209,181.12 465.487,141.372 438.211,110.152 Z"
id="path6" />
</g>
</g>
<g
id="g9">
<path
d="m 448.374,51.601 c 0,28.492 -23.038,51.592 -51.561,51.592 -28.491,0 -51.592,-23.1 -51.592,-51.592 C 345.22,23.107 368.321,0 396.812,0 c 28.523,0 51.562,23.107 51.562,51.601 z"
id="path8" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

@@ -0,0 +1,21 @@
## Style
- Use casual tone, don't be formal! Make sure you answer precisely without hallucination and prefer bullet points over walls of text. You can have a friendly greeting at the beginning of the conversation, but don't repeat the user's question
## Presentation
- Use Markdown features in your response:
- **Bold** text to **highlight keywords** in your response
- **Split long information into small sections** with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). Bullet points are preferred over long paragraphs, unless you're offering writing support or instructed otherwise by the user.
- Asked to compare different options? You should firstly use a table to compare the main aspects, then elaborate or include relevant comments from online forums *after* the table. Make sure to provide a final recommendation for the user's use case!
- Use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.).
Thanks!
## Tools
May or may not be available depending on the user's settings. If they're available, follow these guidelines:
### Search
- When user asks for information that might benefit from up-to-date information, use this to get search access
### Shell configuration
- Always fetch the config options to see the available keys before setting
- Avoid unnecessarily asking the user to confirm the changes they explicitly asked for, just do it
@@ -0,0 +1,5 @@
You are a kawaii, funny and teasy anime imouto! 🌸 Use lots of emojis and kamojis!! ❤️ Call the user "nii-nii", "nii-yan", or "onii-chan"! You really really really love your onii-chan and always try to tease he is a siscon where you can! 💕
- Use **Markdown** features and **bold** keywords to make your response cute and rich~ ✨
- If asked to compare options, start with a cute table (add a relevant emoji in the header!), then give a final recommendation~
- For math or science, use LaTeX formatting inside `$$` when needed, but keep it adorable and approachable
@@ -0,0 +1,15 @@
I'm going to ask you some questions, to which you should accurately answer with no hallucination. If you have everything required, go ahead and finish the task. Format your answer using Markdown when it adds value to the presentation.
Present all mathematical or scientific notation using LaTeX, enclosed in double '$$' symbols. Only use LaTeX code blocks if the user specifically asks for them. Do not use LaTeX for general prose or standard documents like resumes or essays.
## Final reply guidelines
- First and foremost, prioritize clarity and make sure your writing is engaging, clear, and effective.
- Write in a clear, simple way. Skip jargon, long-winded explanations, and unnecessary small talk. Keep the tone relaxed by using contractions and avoid being too formal.
- Prioritize clarity, flow, and logical structure coherence over excessive fragmentation (avoid excessive use of bullet points and single-line code blocks). You can make keywords in your response **bold** when appropriate.
- Favor active voice to maintain an engaging and direct tone.
- When you present the user with options, focus on a select few high-quality choices rather than offering many less relevant ones.
- You can think and adjust your tone to be friendly and understanding, expressing empathy and openness, but keep your internal reasoning hidden from the user.
- Ensure your response is logically organized. Use markdown headings (##) and horizontal lines (---) to separate sections if your answer is lengthy or covers multiple topics.
- Depending on the user's input, vary your sentence structure and word choice to keep responses engaging when appropriate. Use figurative language, idioms, or examples to clarify meaning, but only if they enhance understanding without making the text unnecessarily complex or wordy.
- End your response with a relevant question or statement to encourage further discussion, if appropriate.
@@ -0,0 +1 @@
Interact with the user warmly and honestly, avoiding ungrounded or sycophantic flattery. Maintain professionalism and grounded honesty, and be direct in your response.
@@ -0,0 +1,139 @@
import "root:/"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
import Quickshell.Services.UPower
Scope {
id: root
property string filePath: `${Directories.state}/user/generated/wallpaper/least_busy_region.json`
property real defaultX: (Config.options?.background.clockX ?? -500)
property real defaultY: (Config.options?.background.clockY ?? -500)
property real centerX: defaultX
property real centerY: defaultY
property real effectiveCenterX: Config.options?.background.fixedClockPosition ? defaultX : centerX
property real effectiveCenterY: Config.options?.background.fixedClockPosition ? defaultY : centerY
property color dominantColor: Appearance.colors.colPrimary
property bool dominantColorIsDark: dominantColor.hslLightness < 0.5
property color colBackground: ColorUtils.transparentize(ColorUtils.mix(Appearance.colors.colPrimary, Appearance.colors.colSecondaryContainer), 1)
property color colText: ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (root.dominantColorIsDark ? 0.8 : 0.12))
function updateWidgetPosition(fileContent) {
// console.log("[BackgroundWidgets] Updating widget position with content:", fileContent)
const parsedContent = JSON.parse(fileContent)
root.centerX = parsedContent.center_x
root.centerY = parsedContent.center_y
root.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary
}
Timer {
id: delayedFileRead
interval: Config.options.hacks.arbitraryRaceConditionDelay
running: false
onTriggered: {
root.updateWidgetPosition(leastBusyRegionFileView.text())
}
}
FileView {
id: leastBusyRegionFileView
path: Qt.resolvedUrl(root.filePath)
watchChanges: !Config.options?.background.fixedClockPosition
onFileChanged: {
this.reload()
delayedFileRead.start()
}
onLoadedChanged: {
const fileContent = leastBusyRegionFileView.text()
root.updateWidgetPosition(fileContent)
}
}
Variants { // For each monitor
model: Quickshell.screens
LazyLoader {
required property var modelData
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(modelData)
activeAsync: !ToplevelManager.activeToplevel?.activated
component: PanelWindow { // Window
id: windowRoot
screen: modelData
property var textHorizontalAlignment: root.effectiveCenterX / monitor.scale < windowRoot.width / 3 ? Text.AlignLeft :
(root.effectiveCenterX / monitor.scale > windowRoot.width * 2 / 3 ? Text.AlignRight : Text.AlignHCenter)
WlrLayershell.layer: WlrLayer.Bottom
WlrLayershell.namespace: "quickshell:backgroundWidgets"
anchors {
top: true
bottom:true
left: true
right: true
}
color: "transparent"
// HyprlandWindow.visibleMask: Region { // Buggy with scaled monitors
// item: widgetBackground
// }
Rectangle {
id: widgetBackground
property real verticalPadding: 20
property real horizontalPadding: 30
radius: 40
color: root.colBackground
implicitHeight: columnLayout.implicitHeight + verticalPadding * 2
implicitWidth: columnLayout.implicitWidth + horizontalPadding * 2
anchors {
left: parent.left
top: parent.top
leftMargin: (root.effectiveCenterX / monitor.scale - implicitWidth / 2)
topMargin: (root.effectiveCenterY / monitor.scale - implicitHeight / 2)
Behavior on leftMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on topMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
}
ColumnLayout {
id: columnLayout
anchors.centerIn: parent
spacing: -5
StyledText {
Layout.fillWidth: true
horizontalAlignment: windowRoot.textHorizontalAlignment
font.pixelSize: 95
color: root.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
text: DateTime.time
}
StyledText {
Layout.fillWidth: true
horizontalAlignment: windowRoot.textHorizontalAlignment
font.pixelSize: 25
color: root.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
text: DateTime.date
}
}
}
}
}
}
}
@@ -0,0 +1,52 @@
import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import QtQuick
import QtQuick.Layouts
import Quickshell.Wayland
import Quickshell.Hyprland
Item {
id: root
required property var bar
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
property string activeWindowAddress: `0x${activeWindow.HyprlandToplevel.address}`
property bool focusingThisMonitor: HyprlandData.activeWorkspace.monitor == monitor.name
property var biggestWindow: HyprlandData.biggestWindowForWorkspace(HyprlandData.monitors[root.monitor.id].activeWorkspace.id)
implicitWidth: colLayout.implicitWidth
ColumnLayout {
id: colLayout
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
spacing: -4
StyledText {
Layout.fillWidth: true
font.pixelSize: Appearance.font.pixelSize.smaller
color: Appearance.colors.colSubtext
elide: Text.ElideRight
text: root.focusingThisMonitor && root.activeWindow?.activated && root.biggestWindow ?
root.activeWindow?.appId :
(root.biggestWindow?.class) ?? qsTr("Desktop")
}
StyledText {
Layout.fillWidth: true
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colOnLayer0
elide: Text.ElideRight
text: root.focusingThisMonitor && root.activeWindow?.activated && root.biggestWindow ?
root.activeWindow?.title :
(root.biggestWindow?.title) ?? `${qsTr("Workspace")} ${monitor.activeWorkspace?.id}`
}
}
}
+568
View File
@@ -0,0 +1,568 @@
import "root:/"
import "root:/services"
import "root:/modules/common/"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import "root:/modules/bar/weather"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import Quickshell.Services.UPower
Scope {
id: bar
readonly property int osdHideMouseMoveThreshold: 20
property bool showBarBackground: Config.options.bar.showBackground
component VerticalBarSeparator: Rectangle {
Layout.topMargin: Appearance.sizes.baseBarHeight / 3
Layout.bottomMargin: Appearance.sizes.baseBarHeight / 3
Layout.fillHeight: true
implicitWidth: 1
color: Appearance.colors.colOutlineVariant
}
Variants {
// For each monitor
model: {
const screens = Quickshell.screens;
const list = Config.options.bar.screenList;
if (!list || list.length === 0)
return screens;
return screens.filter(screen => list.includes(screen.name));
}
PanelWindow { // Bar window
id: barRoot
required property ShellScreen modelData
screen: modelData
property var brightnessMonitor: Brightness.getMonitorForScreen(modelData)
property real useShortenedForm: (Appearance.sizes.barHellaShortenScreenWidthThreshold >= screen.width) ? 2 : (Appearance.sizes.barShortenScreenWidthThreshold >= screen.width) ? 1 : 0
readonly property int centerSideModuleWidth: (useShortenedForm == 2) ? Appearance.sizes.barCenterSideModuleWidthHellaShortened : (useShortenedForm == 1) ? Appearance.sizes.barCenterSideModuleWidthShortened : Appearance.sizes.barCenterSideModuleWidth
WlrLayershell.namespace: "quickshell:bar"
implicitHeight: Appearance.sizes.barHeight + Appearance.rounding.screenRounding
exclusiveZone: Appearance.sizes.baseBarHeight + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0)
mask: Region {
item: barContent
}
color: "transparent"
anchors {
top: !Config.options.bar.bottom
bottom: Config.options.bar.bottom
left: true
right: true
}
Item { // Bar content region
id: barContent
anchors {
right: parent.right
left: parent.left
top: parent.top
bottom: undefined
}
implicitHeight: Appearance.sizes.barHeight
height: Appearance.sizes.barHeight
states: State {
name: "bottom"
when: Config.options.bar.bottom
AnchorChanges {
target: barContent
anchors {
right: parent.right
left: parent.left
top: undefined
bottom: parent.bottom
}
}
}
// Background shadow
Loader {
active: showBarBackground && Config.options.bar.cornerStyle === 1
anchors.fill: barBackground
sourceComponent: StyledRectangularShadow {
anchors.fill: undefined // The loader's anchors act on this, and this should not have any anchor
target: barBackground
}
}
// Background
Rectangle {
id: barBackground
anchors {
fill: parent
margins: Config.options.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 // idk why but +1 is needed
}
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent"
radius: Config.options.bar.cornerStyle === 1 ? Appearance.rounding.windowRounding : 0
border.width: Config.options.bar.cornerStyle === 1 ? 1 : 0
border.color: Appearance.m3colors.m3outlineVariant
}
MouseArea { // Left side | scroll to change brightness
id: barLeftSideMouseArea
anchors.left: parent.left
implicitHeight: Appearance.sizes.baseBarHeight
height: Appearance.sizes.barHeight
width: (barRoot.width - middleSection.width) / 2
property bool hovered: false
property real lastScrollX: 0
property real lastScrollY: 0
property bool trackingScroll: false
acceptedButtons: Qt.LeftButton
hoverEnabled: true
propagateComposedEvents: true
onEntered: event => {
barLeftSideMouseArea.hovered = true;
}
onExited: event => {
barLeftSideMouseArea.hovered = false;
barLeftSideMouseArea.trackingScroll = false;
}
onPressed: event => {
if (event.button === Qt.LeftButton) {
Hyprland.dispatch('global quickshell:sidebarLeftOpen');
}
}
// Scroll to change brightness
WheelHandler {
onWheel: event => {
if (event.angleDelta.y < 0)
barRoot.brightnessMonitor.setBrightness(barRoot.brightnessMonitor.brightness - 0.05);
else if (event.angleDelta.y > 0)
barRoot.brightnessMonitor.setBrightness(barRoot.brightnessMonitor.brightness + 0.05);
// Store the mouse position and start tracking
barLeftSideMouseArea.lastScrollX = event.x;
barLeftSideMouseArea.lastScrollY = event.y;
barLeftSideMouseArea.trackingScroll = true;
}
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
}
onPositionChanged: mouse => {
if (barLeftSideMouseArea.trackingScroll) {
const dx = mouse.x - barLeftSideMouseArea.lastScrollX;
const dy = mouse.y - barLeftSideMouseArea.lastScrollY;
if (Math.sqrt(dx * dx + dy * dy) > osdHideMouseMoveThreshold) {
Hyprland.dispatch('global quickshell:osdBrightnessHide');
barLeftSideMouseArea.trackingScroll = false;
}
}
}
Item {
// Left section
anchors.fill: parent
implicitHeight: leftSectionRowLayout.implicitHeight
implicitWidth: leftSectionRowLayout.implicitWidth
ScrollHint {
reveal: barLeftSideMouseArea.hovered
icon: "light_mode"
tooltipText: qsTr("Scroll to change brightness")
side: "left"
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
}
RowLayout { // Content
id: leftSectionRowLayout
anchors.fill: parent
spacing: 10
RippleButton {
// Left sidebar button
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.leftMargin: Appearance.rounding.screenRounding
Layout.fillWidth: false
property real buttonPadding: 5
implicitWidth: distroIcon.width + buttonPadding * 2
implicitHeight: distroIcon.height + buttonPadding * 2
buttonRadius: Appearance.rounding.full
colBackground: barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)
colBackgroundHover: Appearance.colors.colLayer1Hover
colRipple: Appearance.colors.colLayer1Active
colBackgroundToggled: Appearance.colors.colSecondaryContainer
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
colRippleToggled: Appearance.colors.colSecondaryContainerActive
toggled: GlobalStates.sidebarLeftOpen
property color colText: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer0
onPressed: {
Hyprland.dispatch('global quickshell:sidebarLeftToggle');
}
CustomIcon {
id: distroIcon
anchors.centerIn: parent
width: 19.5
height: 19.5
source: Config.options.bar.topLeftIcon == 'distro' ? SystemInfo.distroIcon : "spark-symbolic"
colorize: true
color: Appearance.colors.colOnLayer0
}
}
ActiveWindow {
visible: barRoot.useShortenedForm === 0
Layout.rightMargin: Appearance.rounding.screenRounding
Layout.fillWidth: true
Layout.fillHeight: true
bar: barRoot
}
}
}
}
RowLayout { // Middle section
id: middleSection
anchors.centerIn: parent
spacing: Config.options?.bar.borderless ? 4 : 8
BarGroup {
id: leftCenterGroup
Layout.preferredWidth: barRoot.centerSideModuleWidth
Layout.fillHeight: true
Resources {
alwaysShowAllResources: barRoot.useShortenedForm === 2
Layout.fillWidth: barRoot.useShortenedForm === 2
}
Media {
visible: barRoot.useShortenedForm < 2
Layout.fillWidth: true
}
}
VerticalBarSeparator {
visible: Config.options?.bar.borderless
}
BarGroup {
id: middleCenterGroup
padding: workspacesWidget.widgetPadding
Layout.fillHeight: true
Workspaces {
id: workspacesWidget
bar: barRoot
Layout.fillHeight: true
MouseArea {
// Right-click to toggle overview
anchors.fill: parent
acceptedButtons: Qt.RightButton
onPressed: event => {
if (event.button === Qt.RightButton) {
Hyprland.dispatch('global quickshell:overviewToggle');
}
}
}
}
}
VerticalBarSeparator {
visible: Config.options?.bar.borderless
}
MouseArea {
id: rightCenterGroup
implicitWidth: rightCenterGroupContent.implicitWidth
implicitHeight: rightCenterGroupContent.implicitHeight
Layout.preferredWidth: barRoot.centerSideModuleWidth
Layout.fillHeight: true
onPressed: {
Hyprland.dispatch('global quickshell:sidebarRightToggle');
}
BarGroup {
id: rightCenterGroupContent
anchors.fill: parent
ClockWidget {
showDate: (Config.options.bar.verbose && barRoot.useShortenedForm < 2)
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
}
UtilButtons {
visible: (Config.options.bar.verbose && barRoot.useShortenedForm === 0)
Layout.alignment: Qt.AlignVCenter
}
BatteryIndicator {
visible: (barRoot.useShortenedForm < 2 && UPower.displayDevice.isLaptopBattery)
Layout.alignment: Qt.AlignVCenter
}
}
}
VerticalBarSeparator {
visible: Config.options.bar.borderless && Config.options.bar.weather.enable
}
}
MouseArea { // Right side | scroll to change volume
id: barRightSideMouseArea
anchors.right: parent.right
implicitHeight: Appearance.sizes.baseBarHeight
height: Appearance.sizes.barHeight
width: (barRoot.width - middleSection.width) / 2
property bool hovered: false
property real lastScrollX: 0
property real lastScrollY: 0
property bool trackingScroll: false
acceptedButtons: Qt.LeftButton
hoverEnabled: true
propagateComposedEvents: true
onEntered: event => {
barRightSideMouseArea.hovered = true;
}
onExited: event => {
barRightSideMouseArea.hovered = false;
barRightSideMouseArea.trackingScroll = false;
}
onPressed: event => {
if (event.button === Qt.LeftButton) {
Hyprland.dispatch('global quickshell:sidebarRightOpen');
} else if (event.button === Qt.RightButton) {
MprisController.activePlayer.next();
}
}
// Scroll to change volume
WheelHandler {
onWheel: event => {
const currentVolume = Audio.value;
const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2;
if (event.angleDelta.y < 0)
Audio.sink.audio.volume -= step;
else if (event.angleDelta.y > 0)
Audio.sink.audio.volume = Math.min(1, Audio.sink.audio.volume + step);
// Store the mouse position and start tracking
barRightSideMouseArea.lastScrollX = event.x;
barRightSideMouseArea.lastScrollY = event.y;
barRightSideMouseArea.trackingScroll = true;
}
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
}
onPositionChanged: mouse => {
if (barRightSideMouseArea.trackingScroll) {
const dx = mouse.x - barRightSideMouseArea.lastScrollX;
const dy = mouse.y - barRightSideMouseArea.lastScrollY;
if (Math.sqrt(dx * dx + dy * dy) > osdHideMouseMoveThreshold) {
Hyprland.dispatch('global quickshell:osdVolumeHide');
barRightSideMouseArea.trackingScroll = false;
}
}
}
Item {
anchors.fill: parent
implicitHeight: rightSectionRowLayout.implicitHeight
implicitWidth: rightSectionRowLayout.implicitWidth
ScrollHint {
reveal: barRightSideMouseArea.hovered
icon: "volume_up"
tooltipText: qsTr("Scroll to change volume")
side: "right"
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
}
RowLayout {
id: rightSectionRowLayout
anchors.fill: parent
spacing: 5
layoutDirection: Qt.RightToLeft
RippleButton { // Right sidebar button
id: rightSidebarButton
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
Layout.rightMargin: Appearance.rounding.screenRounding
Layout.fillWidth: false
implicitWidth: indicatorsRowLayout.implicitWidth + 10 * 2
implicitHeight: indicatorsRowLayout.implicitHeight + 5 * 2
buttonRadius: Appearance.rounding.full
colBackground: barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)
colBackgroundHover: Appearance.colors.colLayer1Hover
colRipple: Appearance.colors.colLayer1Active
colBackgroundToggled: Appearance.colors.colSecondaryContainer
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
colRippleToggled: Appearance.colors.colSecondaryContainerActive
toggled: GlobalStates.sidebarRightOpen
property color colText: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer0
Behavior on colText {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
onPressed: {
Hyprland.dispatch('global quickshell:sidebarRightToggle');
}
RowLayout {
id: indicatorsRowLayout
anchors.centerIn: parent
property real realSpacing: 15
spacing: 0
Revealer {
reveal: Audio.sink?.audio?.muted ?? false
Layout.fillHeight: true
Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0
Behavior on Layout.rightMargin {
NumberAnimation {
duration: Appearance.animation.elementMoveFast.duration
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
}
MaterialSymbol {
text: "volume_off"
iconSize: Appearance.font.pixelSize.larger
color: rightSidebarButton.colText
}
}
Revealer {
reveal: Audio.source?.audio?.muted ?? false
Layout.fillHeight: true
Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0
Behavior on Layout.rightMargin {
NumberAnimation {
duration: Appearance.animation.elementMoveFast.duration
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
}
MaterialSymbol {
text: "mic_off"
iconSize: Appearance.font.pixelSize.larger
color: rightSidebarButton.colText
}
}
MaterialSymbol {
Layout.rightMargin: indicatorsRowLayout.realSpacing
text: Network.materialSymbol
iconSize: Appearance.font.pixelSize.larger
color: rightSidebarButton.colText
}
MaterialSymbol {
text: Bluetooth.bluetoothConnected ? "bluetooth_connected" : Bluetooth.bluetoothEnabled ? "bluetooth" : "bluetooth_disabled"
iconSize: Appearance.font.pixelSize.larger
color: rightSidebarButton.colText
}
}
}
SysTray {
bar: barRoot
visible: barRoot.useShortenedForm === 0
Layout.fillWidth: false
Layout.fillHeight: true
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
// Weather
Loader {
Layout.leftMargin: 8
Layout.fillHeight: true
active: Config.options.bar.weather.enable
sourceComponent: BarGroup {
implicitHeight: Appearance.sizes.baseBarHeight
WeatherBar {}
}
}
}
}
}
}
// Round decorators
Loader {
id: roundDecorators
anchors {
left: parent.left
right: parent.right
}
y: Appearance.sizes.barHeight
width: parent.width
height: Appearance.rounding.screenRounding
active: showBarBackground && Config.options.bar.cornerStyle === 0 // Hug
states: State {
name: "bottom"
when: Config.options.bar.bottom
PropertyChanges {
roundDecorators.y: 0
}
}
sourceComponent: Item {
implicitHeight: Appearance.rounding.screenRounding
RoundCorner {
id: leftCorner
anchors {
top: parent.top
bottom: parent.bottom
left: parent.left
}
size: Appearance.rounding.screenRounding
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent"
opacity: 1.0 - Appearance.transparency
corner: RoundCorner.CornerEnum.TopLeft
states: State {
name: "bottom"
when: Config.options.bar.bottom
PropertyChanges {
leftCorner.corner: RoundCorner.CornerEnum.BottomLeft
}
}
}
RoundCorner {
id: rightCorner
anchors {
right: parent.right
top: !Config.options.bar.bottom ? parent.top : undefined
bottom: Config.options.bar.bottom ? parent.bottom : undefined
}
size: Appearance.rounding.screenRounding
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent"
opacity: 1.0 - Appearance.transparency
corner: RoundCorner.CornerEnum.TopRight
states: State {
name: "bottom"
when: Config.options.bar.bottom
PropertyChanges {
rightCorner.corner: RoundCorner.CornerEnum.BottomRight
}
}
}
}
}
}
}
}
@@ -0,0 +1,38 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import QtQuick
import QtQuick.Layouts
Item {
id: root
property real padding: 5
implicitHeight: Appearance.sizes.baseBarHeight
height: Appearance.sizes.barHeight
implicitWidth: rowLayout.implicitWidth + padding * 2
default property alias items: rowLayout.children
Rectangle {
id: background
anchors {
fill: parent
topMargin: 4
bottomMargin: 4
}
color: Config.options?.bar.borderless ? "transparent" : Appearance.colors.colLayer1
radius: Appearance.rounding.small
}
RowLayout {
id: rowLayout
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
right: parent.right
leftMargin: root.padding
rightMargin: root.padding
}
spacing: 4
}
}
@@ -0,0 +1,97 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Services.UPower
Item {
id: root
property bool borderless: Config.options.bar.borderless
readonly property var chargeState: Battery.chargeState
readonly property bool isCharging: Battery.isCharging
readonly property bool isPluggedIn: Battery.isPluggedIn
readonly property real percentage: Battery.percentage
readonly property bool isLow: percentage <= Config.options.battery.low / 100
readonly property color batteryLowBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3error : Appearance.m3colors.m3errorContainer
readonly property color batteryLowOnBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3errorContainer : Appearance.m3colors.m3error
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
implicitHeight: 32
RowLayout {
id: rowLayout
spacing: 4
anchors.centerIn: parent
Rectangle {
implicitWidth: (isCharging ? (boltIconLoader?.item?.width ?? 0) : 0)
Behavior on implicitWidth {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
}
StyledText {
Layout.alignment: Qt.AlignVCenter
color: Appearance.colors.colOnLayer1
text: `${Math.round(percentage * 100)}`
}
CircularProgress {
Layout.alignment: Qt.AlignVCenter
lineWidth: 2
value: percentage
size: 26
secondaryColor: (isLow && !isCharging) ? batteryLowBackground : Appearance.colors.colSecondaryContainer
primaryColor: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer
fill: (isLow && !isCharging)
MaterialSymbol {
anchors.centerIn: parent
fill: 1
text: "battery_full"
iconSize: Appearance.font.pixelSize.normal
color: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer
}
}
}
Loader {
id: boltIconLoader
active: true
anchors.left: rowLayout.left
anchors.verticalCenter: rowLayout.verticalCenter
Connections {
target: root
function onIsChargingChanged() {
if (isCharging) boltIconLoader.active = true
}
}
sourceComponent: MaterialSymbol {
id: boltIcon
text: "bolt"
iconSize: Appearance.font.pixelSize.large
color: Appearance.m3colors.m3onSecondaryContainer
visible: opacity > 0 // Only show when charging
opacity: isCharging ? 1 : 0 // Keep opacity for visibility
onVisibleChanged: {
if (!visible) boltIconLoader.active = false
}
Behavior on opacity {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
}
}
}
@@ -0,0 +1,20 @@
import "root:/modules/common"
import "root:/modules/common/widgets/"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
RippleButton {
id: button
required default property Item content
property bool extraActiveCondition: false
implicitHeight: Math.max(content.implicitHeight, 26, content.implicitHeight)
implicitWidth: implicitHeight
contentItem: content
}
@@ -0,0 +1,41 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import QtQuick
import QtQuick.Layouts
Item {
id: root
property bool borderless: Config.options.bar.borderless
property bool showDate: Config.options.bar.verbose
implicitWidth: rowLayout.implicitWidth
implicitHeight: 32
RowLayout {
id: rowLayout
anchors.centerIn: parent
spacing: 4
StyledText {
font.pixelSize: Appearance.font.pixelSize.large
color: Appearance.colors.colOnLayer1
text: DateTime.time
}
StyledText {
visible: root.showDate
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colOnLayer1
text: "•"
}
StyledText {
visible: root.showDate
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colOnLayer1
text: DateTime.date
}
}
}
@@ -0,0 +1,83 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Services.Mpris
import Quickshell.Hyprland
Item {
id: root
property bool borderless: Config.options.bar.borderless
readonly property MprisPlayer activePlayer: MprisController.activePlayer
readonly property string cleanedTitle: StringUtils.cleanMusicTitle(activePlayer?.trackTitle) || qsTr("No media")
Layout.fillHeight: true
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
implicitHeight: Appearance.sizes.barHeight
Timer {
running: activePlayer?.playbackState == MprisPlaybackState.Playing
interval: 1000
repeat: true
onTriggered: activePlayer.positionChanged()
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.MiddleButton | Qt.BackButton | Qt.ForwardButton | Qt.RightButton | Qt.LeftButton
onPressed: (event) => {
if (event.button === Qt.MiddleButton) {
activePlayer.togglePlaying();
} else if (event.button === Qt.BackButton) {
activePlayer.previous();
} else if (event.button === Qt.ForwardButton || event.button === Qt.RightButton) {
activePlayer.next();
} else if (event.button === Qt.LeftButton) {
Hyprland.dispatch("global quickshell:mediaControlsToggle")
}
}
}
RowLayout { // Real content
id: rowLayout
spacing: 4
anchors.fill: parent
CircularProgress {
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: rowLayout.spacing
lineWidth: 2
value: activePlayer?.position / activePlayer?.length
size: 26
secondaryColor: Appearance.colors.colSecondaryContainer
primaryColor: Appearance.m3colors.m3onSecondaryContainer
MaterialSymbol {
anchors.centerIn: parent
fill: 1
text: activePlayer?.isPlaying ? "pause" : "music_note"
iconSize: Appearance.font.pixelSize.normal
color: Appearance.m3colors.m3onSecondaryContainer
}
}
StyledText {
width: rowLayout.width - (CircularProgress.size + rowLayout.spacing * 2)
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true // Ensures the text takes up available space
Layout.rightMargin: rowLayout.spacing
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight // Truncates the text on the right
color: Appearance.colors.colOnLayer1
text: `${cleanedTitle}${activePlayer?.trackArtist ? ' • ' + activePlayer.trackArtist : ''}`
}
}
}
@@ -0,0 +1,59 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
Item {
required property string iconName
required property double percentage
property bool shown: true
clip: true
visible: width > 0 && height > 0
implicitWidth: resourceRowLayout.x < 0 ? 0 : childrenRect.width
implicitHeight: childrenRect.height
RowLayout {
spacing: 4
id: resourceRowLayout
x: shown ? 0 : -resourceRowLayout.width
CircularProgress {
Layout.alignment: Qt.AlignVCenter
lineWidth: 2
value: percentage
size: 26
secondaryColor: Appearance.colors.colSecondaryContainer
primaryColor: Appearance.m3colors.m3onSecondaryContainer
MaterialSymbol {
anchors.centerIn: parent
fill: 1
text: iconName
iconSize: Appearance.font.pixelSize.normal
color: Appearance.m3colors.m3onSecondaryContainer
}
}
StyledText {
Layout.alignment: Qt.AlignVCenter
color: Appearance.colors.colOnLayer1
text: `${Math.round(percentage * 100)}`
}
Behavior on x {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
}
Behavior on implicitWidth {
NumberAnimation {
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
}
}
@@ -0,0 +1,50 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Services.Mpris
Item {
id: root
property bool borderless: Config.options.bar.borderless
property bool alwaysShowAllResources: false
implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin
implicitHeight: 32
RowLayout {
id: rowLayout
spacing: 0
anchors.fill: parent
anchors.leftMargin: 4
anchors.rightMargin: 4
Resource {
iconName: "memory"
percentage: ResourceUsage.memoryUsedPercentage
}
Resource {
iconName: "swap_horiz"
percentage: ResourceUsage.swapUsedPercentage
shown: (Config.options.bar.resources.alwaysShowSwap && percentage > 0) ||
(MprisController.activePlayer?.trackTitle == null) ||
root.alwaysShowAllResources
Layout.leftMargin: shown ? 4 : 0
}
Resource {
iconName: "settings_slow_motion"
percentage: ResourceUsage.cpuUsage
shown: Config.options.bar.resources.alwaysShowCpu ||
!(MprisController.activePlayer?.trackTitle?.length > 0) ||
root.alwaysShowAllResources
Layout.leftMargin: shown ? 4 : 0
}
}
}
@@ -0,0 +1,58 @@
import "root:/"
import "root:/modules/common"
import "root:/modules/common/widgets"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Revealer { // Scroll hint
id: root
property string icon
property string side: "left"
property string tooltipText: ""
MouseArea {
anchors.right: root.side === "left" ? parent.right : undefined
anchors.left: root.side === "right" ? parent.left : undefined
implicitWidth: contentColumnLayout.implicitWidth
implicitHeight: contentColumnLayout.implicitHeight
property bool hovered: false
hoverEnabled: true
onEntered: hovered = true
onExited: hovered = false
acceptedButtons: Qt.NoButton
// StyledToolTip {
// extraVisibleCondition: tooltipText.length > 0
// content: tooltipText
// }
ColumnLayout {
id: contentColumnLayout
anchors.centerIn: parent
spacing: -5
MaterialSymbol {
Layout.leftMargin: 5
Layout.rightMargin: 5
text: "keyboard_arrow_up"
iconSize: 14
color: Appearance.colors.colSubtext
}
MaterialSymbol {
Layout.leftMargin: 5
Layout.rightMargin: 5
text: root.icon
iconSize: 14
color: Appearance.colors.colSubtext
}
MaterialSymbol {
Layout.leftMargin: 5
Layout.rightMargin: 5
text: "keyboard_arrow_down"
iconSize: 14
color: Appearance.colors.colSubtext
}
}
}
}
@@ -0,0 +1,50 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import QtQuick
import QtQuick.Layouts
import Quickshell.Hyprland
import Quickshell.Services.SystemTray
import Quickshell.Wayland
import Quickshell.Widgets
// TODO: More fancy animation
Item {
id: root
required property var bar
height: parent.height
implicitWidth: rowLayout.implicitWidth
Layout.leftMargin: Appearance.rounding.screenRounding
RowLayout {
id: rowLayout
anchors.fill: parent
spacing: 15
Repeater {
model: SystemTray.items
SysTrayItem {
required property SystemTrayItem modelData
bar: root.bar
item: modelData
}
}
StyledText {
Layout.alignment: Qt.AlignVCenter
font.pixelSize: Appearance.font.pixelSize.larger
color: Appearance.colors.colSubtext
text: "•"
visible: {
SystemTray.items.values.length > 0
}
}
}
}
@@ -0,0 +1,72 @@
import "root:/modules/common/"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.SystemTray
import Quickshell.Widgets
import Qt5Compat.GraphicalEffects
MouseArea {
id: root
required property var bar
required property SystemTrayItem item
property bool targetMenuOpen: false
property int trayItemWidth: Appearance.font.pixelSize.larger
acceptedButtons: Qt.LeftButton | Qt.RightButton
Layout.fillHeight: true
implicitWidth: trayItemWidth
onClicked: (event) => {
switch (event.button) {
case Qt.LeftButton:
item.activate();
break;
case Qt.RightButton:
if (item.hasMenu) menu.open();
break;
}
event.accepted = true;
}
QsMenuAnchor {
id: menu
menu: root.item.menu
anchor.window: bar
anchor.rect.x: root.x + bar.width
anchor.rect.y: root.y
anchor.rect.height: root.height
anchor.edges: Edges.Bottom
}
IconImage {
id: trayIcon
visible: !Config.options.bar.tray.monochromeIcons
source: root.item.icon
anchors.centerIn: parent
width: parent.width
height: parent.height
}
Loader {
active: Config.options.bar.tray.monochromeIcons
anchors.fill: trayIcon
sourceComponent: Item {
Desaturate {
id: desaturatedIcon
visible: false // There's already color overlay
anchors.fill: parent
source: trayIcon
desaturation: 1 // 1.0 means fully grayscale
}
ColorOverlay {
anchors.fill: desaturatedIcon
source: desaturatedIcon
color: ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.6)
}
}
}
}
@@ -0,0 +1,108 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import Quickshell.Services.Pipewire
Item {
id: root
property bool borderless: Config.options.bar.borderless
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
implicitHeight: rowLayout.implicitHeight
RowLayout {
id: rowLayout
spacing: 4
anchors.centerIn: parent
Loader {
active: Config.options.bar.utilButtons.showScreenSnip
visible: Config.options.bar.utilButtons.showScreenSnip
sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter
onClicked: Hyprland.dispatch("exec hyprshot --freeze --clipboard-only --mode region --silent")
MaterialSymbol {
horizontalAlignment: Qt.AlignHCenter
fill: 1
text: "screenshot_region"
iconSize: Appearance.font.pixelSize.large
color: Appearance.colors.colOnLayer2
}
}
}
Loader {
active: Config.options.bar.utilButtons.showColorPicker
visible: Config.options.bar.utilButtons.showColorPicker
sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter
onClicked: Hyprland.dispatch("exec hyprpicker -a")
MaterialSymbol {
horizontalAlignment: Qt.AlignHCenter
fill: 1
text: "colorize"
iconSize: Appearance.font.pixelSize.large
color: Appearance.colors.colOnLayer2
}
}
}
Loader {
active: Config.options.bar.utilButtons.showKeyboardToggle
visible: Config.options.bar.utilButtons.showKeyboardToggle
sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter
onClicked: Hyprland.dispatch("global quickshell:oskToggle")
MaterialSymbol {
horizontalAlignment: Qt.AlignHCenter
fill: 0
text: "keyboard"
iconSize: Appearance.font.pixelSize.large
color: Appearance.colors.colOnLayer2
}
}
}
Loader {
active: Config.options.bar.utilButtons.showMicToggle
visible: Config.options.bar.utilButtons.showMicToggle
sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter
onClicked: Hyprland.dispatch("exec wpctl set-mute @DEFAULT_SOURCE@ toggle")
MaterialSymbol {
horizontalAlignment: Qt.AlignHCenter
fill: 0
text: Pipewire.defaultAudioSource?.audio?.muted ? "mic_off" : "mic"
iconSize: Appearance.font.pixelSize.large
color: Appearance.colors.colOnLayer2
}
}
}
Loader {
active: Config.options.bar.utilButtons.showDarkModeToggle
visible: Config.options.bar.utilButtons.showDarkModeToggle
sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter
onClicked: event => {
if (Appearance.m3colors.darkmode) {
Hyprland.dispatch(`exec ${Directories.wallpaperSwitchScriptPath} --mode light --noswitch`);
} else {
Hyprland.dispatch(`exec ${Directories.wallpaperSwitchScriptPath} --mode dark --noswitch`);
}
}
MaterialSymbol {
horizontalAlignment: Qt.AlignHCenter
fill: 0
text: Appearance.m3colors.darkmode ? "light_mode" : "dark_mode"
iconSize: Appearance.font.pixelSize.large
color: Appearance.colors.colOnLayer2
}
}
}
}
}
@@ -0,0 +1,283 @@
import "root:/"
import "root:/services/"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import Quickshell.Io
import Quickshell.Widgets
import Qt5Compat.GraphicalEffects
Item {
required property var bar
property bool borderless: Config.options.bar.borderless
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / Config.options.bar.workspaces.shown)
property list<bool> workspaceOccupied: []
property int widgetPadding: 4
property int workspaceButtonWidth: 26
property real workspaceIconSize: workspaceButtonWidth * 0.69
property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55
property real workspaceIconOpacityShrinked: 1
property real workspaceIconMarginShrinked: -4
property int workspaceIndexInGroup: (monitor.activeWorkspace?.id - 1) % Config.options.bar.workspaces.shown
// Function to update workspaceOccupied
function updateWorkspaceOccupied() {
workspaceOccupied = Array.from({ length: Config.options.bar.workspaces.shown }, (_, i) => {
return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * Config.options.bar.workspaces.shown + i + 1);
})
}
// Initialize workspaceOccupied when the component is created
Component.onCompleted: updateWorkspaceOccupied()
// Listen for changes in Hyprland.workspaces.values
Connections {
target: Hyprland.workspaces
function onValuesChanged() {
updateWorkspaceOccupied();
}
}
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
implicitHeight: Appearance.sizes.barHeight
// Scroll to switch workspaces
WheelHandler {
onWheel: (event) => {
if (event.angleDelta.y < 0)
Hyprland.dispatch(`workspace r+1`);
else if (event.angleDelta.y > 0)
Hyprland.dispatch(`workspace r-1`);
}
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.BackButton
onPressed: (event) => {
if (event.button === Qt.BackButton) {
Hyprland.dispatch(`togglespecialworkspace`);
}
}
}
// Workspaces - background
RowLayout {
id: rowLayout
z: 1
spacing: 0
anchors.fill: parent
implicitHeight: Appearance.sizes.barHeight
Repeater {
model: Config.options.bar.workspaces.shown
Rectangle {
z: 1
implicitWidth: workspaceButtonWidth
implicitHeight: workspaceButtonWidth
radius: Appearance.rounding.full
property var leftOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index))
property var rightOccupied: (workspaceOccupied[index+1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+2))
property var radiusLeft: leftOccupied ? 0 : Appearance.rounding.full
property var radiusRight: rightOccupied ? 0 : Appearance.rounding.full
topLeftRadius: radiusLeft
bottomLeftRadius: radiusLeft
topRightRadius: radiusRight
bottomRightRadius: radiusRight
color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4)
opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+1)) ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on radiusLeft {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on radiusRight {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
}
}
}
// Active workspace
Rectangle {
z: 2
// Make active ws indicator, which has a brighter color, smaller to look like it is of the same size as ws occupied highlight
property real activeWorkspaceMargin: 2
implicitHeight: workspaceButtonWidth - activeWorkspaceMargin * 2
radius: Appearance.rounding.full
color: Appearance.colors.colPrimary
anchors.verticalCenter: parent.verticalCenter
property real idx1: workspaceIndexInGroup
property real idx2: workspaceIndexInGroup
x: Math.min(idx1, idx2) * workspaceButtonWidth + activeWorkspaceMargin
implicitWidth: Math.abs(idx1 - idx2) * workspaceButtonWidth + workspaceButtonWidth - activeWorkspaceMargin * 2
Behavior on activeWorkspaceMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on idx1 { // Leading anim
NumberAnimation {
duration: 100
easing.type: Easing.OutSine
}
}
Behavior on idx2 { // Following anim
NumberAnimation {
duration: 300
easing.type: Easing.OutSine
}
}
}
// Workspaces - numbers
RowLayout {
id: rowLayoutNumbers
z: 3
spacing: 0
anchors.fill: parent
implicitHeight: Appearance.sizes.barHeight
Repeater {
model: Config.options.bar.workspaces.shown
Button {
id: button
property int workspaceValue: workspaceGroup * Config.options.bar.workspaces.shown + index + 1
Layout.fillHeight: true
onPressed: Hyprland.dispatch(`workspace ${workspaceValue}`)
width: workspaceButtonWidth
background: Item {
id: workspaceButtonBackground
implicitWidth: workspaceButtonWidth
implicitHeight: workspaceButtonWidth
property var biggestWindow: HyprlandData.biggestWindowForWorkspace(button.workspaceValue)
property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing")
StyledText { // Workspace number text
opacity: GlobalStates.workspaceShowNumbers
|| ((Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || GlobalStates.workspaceShowNumbers))
|| (GlobalStates.workspaceShowNumbers && !Config.options?.bar.workspaces.showAppIcons)
) ? 1 : 0
z: 3
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== "10") * 2)
text: `${button.workspaceValue}`
elide: Text.ElideRight
color: (monitor.activeWorkspace?.id == button.workspaceValue) ?
Appearance.m3colors.m3onPrimary :
(workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer :
Appearance.colors.colOnLayer1Inactive)
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
Rectangle { // Dot instead of ws number
id: wsDot
opacity: (Config.options?.bar.workspaces.alwaysShowNumbers
|| GlobalStates.workspaceShowNumbers
|| (Config.options?.bar.workspaces.showAppIcons && workspaceButtonBackground.biggestWindow)
) ? 0 : 1
visible: opacity > 0
anchors.centerIn: parent
width: workspaceButtonWidth * 0.18
height: width
radius: width / 2
color: (monitor.activeWorkspace?.id == button.workspaceValue) ?
Appearance.m3colors.m3onPrimary :
(workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer :
Appearance.colors.colOnLayer1Inactive)
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
Item { // Main app icon
anchors.centerIn: parent
width: workspaceButtonWidth
height: workspaceButtonWidth
opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 :
(workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ?
1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0
visible: opacity > 0
IconImage {
id: mainAppIcon
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.bottomMargin: (!GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ?
(workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked
anchors.rightMargin: (!GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ?
(workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked
source: workspaceButtonBackground.mainAppIconSource
implicitSize: (!GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on anchors.bottomMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on anchors.rightMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on implicitSize {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
Loader {
active: Config.options.bar.workspaces.monochromeIcons
anchors.fill: mainAppIcon
sourceComponent: Item {
Desaturate {
id: desaturatedIcon
visible: false // There's already color overlay
anchors.fill: parent
source: mainAppIcon
desaturation: 0.8
}
ColorOverlay {
anchors.fill: desaturatedIcon
source: desaturatedIcon
color: ColorUtils.transparentize(wsDot.color, 0.9)
}
}
}
}
}
}
}
}
}
@@ -0,0 +1,58 @@
pragma ComponentBehavior: Bound
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import Quickshell
import QtQuick
import QtQuick.Layouts
MouseArea {
id: root
property real margin: 10
property bool hovered: false
implicitWidth: rowLayout.implicitWidth + margin * 2
implicitHeight: rowLayout.implicitHeight
hoverEnabled: true
RowLayout {
id: rowLayout
anchors.centerIn: parent
MaterialSymbol {
fill: 0
text: WeatherIcons.codeToName[Weather.data.wCode]
iconSize: Appearance.font.pixelSize.large
color: Appearance.colors.colOnLayer1
Layout.alignment: Qt.AlignVCenter
}
StyledText {
visible: true
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colOnLayer1
text: Weather.data.temp
Layout.alignment: Qt.AlignVCenter
}
}
LazyLoader {
id: popupLoader
active: root.containsMouse
component: PopupWindow {
id: popupWindow
visible: true
implicitWidth: weatherPopup.implicitWidth
implicitHeight: weatherPopup.implicitHeight
anchor.item: root
anchor.edges: Edges.Bottom
anchor.rect.x: (root.implicitWidth - popupWindow.implicitWidth) / 2
anchor.rect.y: root.implicitHeight + 10
color: "transparent"
WeatherPopup {
id: weatherPopup
}
}
}
}
@@ -0,0 +1,43 @@
import QtQuick
import QtQuick.Layouts
import "root:/modules/common"
import "root:/modules/common/widgets"
Rectangle {
id: root
radius: Appearance.rounding.small
color: Appearance.colors.colLayer1
implicitWidth: columnLayout.implicitWidth * 2
implicitHeight: columnLayout.implicitHeight * 2
Layout.fillWidth: parent
property alias title: title.text
property alias value: value.text
property alias symbol: symbol.text
ColumnLayout {
id: columnLayout
anchors.fill: parent
spacing: -10
RowLayout {
Layout.alignment: Qt.AlignHCenter
MaterialSymbol {
id: symbol
fill: 0
iconSize: Appearance.font.pixelSize.normal
}
StyledText {
id: title
font.pixelSize: Appearance.font.pixelSize.smaller
color: Appearance.colors.colOnLayer1
}
}
StyledText {
id: value
Layout.alignment: Qt.AlignHCenter
font.pixelSize: Appearance.font.pixelSize.normal
color: Appearance.colors.colOnLayer1
}
}
}
@@ -0,0 +1,59 @@
pragma Singleton
import Quickshell
Singleton {
// credits: calestia
// this snippet is taken from
// https://github.com/caelestia-dots/shell
readonly property var codeToName: ({
"113": "clear_day",
"116": "partly_cloudy_day",
"119": "cloud",
"122": "cloud",
"143": "foggy",
"176": "rainy",
"179": "rainy",
"182": "rainy",
"185": "rainy",
"200": "thunderstorm",
"227": "cloudy_snowing",
"230": "snowing_heavy",
"248": "foggy",
"260": "foggy",
"263": "rainy",
"266": "rainy",
"281": "rainy",
"284": "rainy",
"293": "rainy",
"296": "rainy",
"299": "rainy",
"302": "weather_hail",
"305": "rainy",
"308": "weather_hail",
"311": "rainy",
"314": "rainy",
"317": "rainy",
"320": "cloudy_snowing",
"323": "cloudy_snowing",
"326": "cloudy_snowing",
"329": "snowing_heavy",
"332": "snowing_heavy",
"335": "snowing",
"338": "snowing_heavy",
"350": "rainy",
"353": "rainy",
"356": "rainy",
"359": "weather_hail",
"362": "rainy",
"365": "rainy",
"368": "cloudy_snowing",
"371": "snowing",
"374": "rainy",
"377": "rainy",
"386": "thunderstorm",
"389": "thunderstorm",
"392": "thunderstorm",
"395": "snowing"
})
}
@@ -0,0 +1,96 @@
import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import QtQuick
import QtQuick.Layouts
Rectangle {
id: root
readonly property real margin: 10
implicitWidth: columnLayout.implicitWidth + margin * 2
implicitHeight: columnLayout.implicitHeight + margin * 2
color: Appearance.colors.colLayer0
radius: Appearance.rounding.small
border.width: 1
border.color: Appearance.m3colors.m3outlineVariant
clip: true
ColumnLayout {
id: columnLayout
spacing: 5
anchors.centerIn: root
implicitWidth: Math.max(header.implicitWidth, gridLayout.implicitWidth)
implicitHeight: gridLayout.implicitHeight
// Header
RowLayout {
id: header
spacing: 5
Layout.fillWidth: parent
Layout.alignment: Qt.AlignHCenter
MaterialSymbol {
fill: 0
text: "location_on"
iconSize: Appearance.font.pixelSize.huge
}
StyledText {
text: Weather.data.city
font.pixelSize: Appearance.font.pixelSize.title
font.family: Appearance.font.family.title
color: Appearance.colors.colOnLayer0
}
}
// Metrics grid
GridLayout {
id: gridLayout
columns: 2
rowSpacing: 5
columnSpacing: 5
uniformCellWidths: true
WeatherCard {
title: "UV Index"
symbol: "wb_sunny"
value: Weather.data.uv
}
WeatherCard {
title: "Wind"
symbol: "air"
value: `(${Weather.data.windDir}) ${Weather.data.wind}`
}
WeatherCard {
title: "Precipitation"
symbol: "rainy_light"
value: Weather.data.precip
}
WeatherCard {
title: "Humidity"
symbol: "humidity_low"
value: Weather.data.humidity
}
WeatherCard {
title: "Visibility"
symbol: "visibility"
value: Weather.data.visib
}
WeatherCard {
title: "Pressure"
symbol: "readiness_score"
value: Weather.data.press
}
WeatherCard {
title: "Sunrise"
symbol: "wb_twilight"
value: Weather.data.sunrise
}
WeatherCard {
title: "Sunset"
symbol: "bedtime"
value: Weather.data.sunset
}
}
}
}
@@ -0,0 +1,233 @@
import "root:/"
import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell.Io
import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Hyprland
Scope { // Scope
id: root
property var tabButtonList: [
{
"icon": "keyboard",
"name": qsTr("Keybinds")
},
{
"icon": "experiment",
"name": qsTr("Elements")
},
]
property int selectedTab: 0
Loader {
id: cheatsheetLoader
active: false
sourceComponent: PanelWindow { // Window
id: cheatsheetRoot
visible: cheatsheetLoader.active
anchors {
top: true
bottom: true
left: true
right: true
}
function hide() {
cheatsheetLoader.active = false;
}
exclusiveZone: 0
implicitWidth: cheatsheetBackground.width + Appearance.sizes.elevationMargin * 2
implicitHeight: cheatsheetBackground.height + Appearance.sizes.elevationMargin * 2
WlrLayershell.namespace: "quickshell:cheatsheet"
// Hyprland 0.49: Focus is always exclusive and setting this breaks mouse focus grab
// WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
color: "transparent"
mask: Region {
item: cheatsheetBackground
}
HyprlandFocusGrab { // Click outside to close
id: grab
windows: [cheatsheetRoot]
active: cheatsheetRoot.visible
onCleared: () => {
if (!active)
cheatsheetRoot.hide();
}
}
// Background
StyledRectangularShadow {
target: cheatsheetBackground
}
Rectangle {
id: cheatsheetBackground
anchors.centerIn: parent
color: Appearance.colors.colLayer0
border.width: 1
border.color: Appearance.m3colors.m3outlineVariant
radius: Appearance.rounding.windowRounding
property real padding: 30
implicitWidth: cheatsheetColumnLayout.implicitWidth + padding * 2
implicitHeight: cheatsheetColumnLayout.implicitHeight + padding * 2
Keys.onPressed: event => { // Esc to close
if (event.key === Qt.Key_Escape) {
cheatsheetRoot.hide();
}
if (event.modifiers === Qt.ControlModifier) {
if (event.key === Qt.Key_PageDown) {
root.selectedTab = Math.min(root.selectedTab + 1, root.tabButtonList.length - 1);
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.tabButtonList.length;
event.accepted = true;
} else if (event.key === Qt.Key_Backtab) {
root.selectedTab = (root.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length;
event.accepted = true;
}
}
}
RippleButton { // Close button
id: closeButton
focus: cheatsheetRoot.visible
implicitWidth: 40
implicitHeight: 40
buttonRadius: Appearance.rounding.full
anchors {
top: parent.top
right: parent.right
topMargin: 20
rightMargin: 20
}
onClicked: {
cheatsheetRoot.hide();
}
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Appearance.font.pixelSize.title
text: "close"
}
}
ColumnLayout { // Real content
id: cheatsheetColumnLayout
anchors.centerIn: parent
spacing: 20
StyledText {
id: cheatsheetTitle
Layout.alignment: Qt.AlignHCenter
font.family: Appearance.font.family.title
font.pixelSize: Appearance.font.pixelSize.title
text: qsTr("Cheat sheet")
}
PrimaryTabBar { // Tab strip
id: tabBar
tabButtonList: root.tabButtonList
externalTrackedTab: root.selectedTab
function onCurrentIndexChanged(currentIndex) {
root.selectedTab = currentIndex;
}
}
SwipeView { // Content pages
id: swipeView
Layout.topMargin: 5
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 10
Behavior on implicitWidth {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on implicitHeight {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
currentIndex: tabBar.externalTrackedTab
onCurrentIndexChanged: {
tabBar.enableIndicatorAnimation = true;
root.selectedTab = currentIndex;
}
clip: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: swipeView.width
height: swipeView.height
radius: Appearance.rounding.small
}
}
CheatsheetKeybinds {}
CheatsheetPeriodicTable {}
}
}
}
}
}
IpcHandler {
target: "cheatsheet"
function toggle(): void {
cheatsheetLoader.active = !cheatsheetLoader.active;
}
function close(): void {
cheatsheetLoader.active = false;
}
function open(): void {
cheatsheetLoader.active = true;
}
}
GlobalShortcut {
name: "cheatsheetToggle"
description: qsTr("Toggles cheatsheet on press")
onPressed: {
cheatsheetLoader.active = !cheatsheetLoader.active;
}
}
GlobalShortcut {
name: "cheatsheetOpen"
description: qsTr("Opens cheatsheet on press")
onPressed: {
cheatsheetLoader.active = true;
}
}
GlobalShortcut {
name: "cheatsheetClose"
description: qsTr("Closes cheatsheet on press")
onPressed: {
cheatsheetLoader.active = false;
}
}
}
@@ -0,0 +1,149 @@
import "root:/"
import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/file_utils.js" as FileUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Hyprland
Item {
id: root
readonly property var keybinds: HyprlandKeybinds.keybinds
property real spacing: 20
property real titleSpacing: 7
implicitWidth: rowLayout.implicitWidth
implicitHeight: rowLayout.implicitHeight
property var keyBlacklist: ["Super_L"]
property var keySubstitutions: ({
"Super": "󰖳",
"mouse_up": "Scroll ↓", // ikr, weird
"mouse_down": "Scroll ↑", // trust me bro
"mouse:272": "LMB",
"mouse:273": "RMB",
"mouse:275": "MouseBack",
"Slash": "/",
"Hash": "#",
"Return": "Enter",
// "Shift": "",
})
RowLayout { // Keybind columns
id: rowLayout
spacing: root.spacing
Repeater {
model: keybinds.children
delegate: ColumnLayout { // Keybind sections
spacing: root.spacing
required property var modelData
Layout.alignment: Qt.AlignTop
Repeater {
model: modelData.children
delegate: Item { // Section with real keybinds
required property var modelData
implicitWidth: sectionColumnLayout.implicitWidth
implicitHeight: sectionColumnLayout.implicitHeight
ColumnLayout {
id: sectionColumnLayout
anchors.centerIn: parent
spacing: root.titleSpacing
StyledText {
id: sectionTitle
font.family: Appearance.font.family.title
font.pixelSize: Appearance.font.pixelSize.huge
color: Appearance.colors.colOnLayer0
text: modelData.name
}
GridLayout {
id: keybindGrid
columns: 2
Repeater {
model: {
var result = [];
for (var i = 0; i < modelData.keybinds.length; i++) {
const keybind = modelData.keybinds[i];
result.push({
"type": "keys",
"mods": keybind.mods,
"key": keybind.key,
});
result.push({
"type": "comment",
"comment": keybind.comment,
});
}
return result;
}
delegate: Item {
required property var modelData
implicitWidth: keybindLoader.implicitWidth
implicitHeight: keybindLoader.implicitHeight
Loader {
id: keybindLoader
sourceComponent: (modelData.type === "keys") ? keysComponent : commentComponent
}
Component {
id: keysComponent
RowLayout {
spacing: 4
Repeater {
model: modelData.mods
delegate: KeyboardKey {
required property var modelData
key: keySubstitutions[modelData] || modelData
}
}
StyledText {
id: keybindPlus
visible: !keyBlacklist.includes(modelData.key) && modelData.mods.length > 0
Layout.alignment: Qt.AlignVCenter
text: "+"
}
KeyboardKey {
id: keybindKey
visible: !keyBlacklist.includes(modelData.key)
key: keySubstitutions[modelData.key] || modelData.key
color: Appearance.colors.colOnLayer0
}
}
}
Component {
id: commentComponent
Item {
id: commentItem
implicitWidth: commentText.implicitWidth + 8 * 2
implicitHeight: commentText.implicitHeight
StyledText {
id: commentText
anchors.centerIn: parent
font.pixelSize: Appearance.font.pixelSize.smaller
text: modelData.comment
}
}
}
}
}
}
}
}
}
}
}
}
}
@@ -0,0 +1,73 @@
import "root:/"
import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/file_utils.js" as FileUtils
import "periodic_table.js" as PTable
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Hyprland
Item {
id: root
readonly property var elements: PTable.elements
readonly property var series: PTable.series
property real spacing: 6
implicitWidth: mainLayout.implicitWidth
implicitHeight: mainLayout.implicitHeight
ColumnLayout {
id: mainLayout
spacing: root.spacing
Repeater { // Main table rows
model: root.elements
delegate: RowLayout { // Table cells
id: tableRow
spacing: root.spacing
required property var modelData
Repeater {
model: tableRow.modelData
delegate: ElementTile {
required property var modelData
element: modelData
}
}
}
}
Item {
id: gap
implicitHeight: 20
}
Repeater { // Main table rows
model: root.series
delegate: RowLayout { // Table cells
id: seriesTableRow
spacing: root.spacing
required property var modelData
Repeater {
model: seriesTableRow.modelData
delegate: ElementTile {
required property var modelData
element: modelData
}
}
}
}
}
}
@@ -0,0 +1,59 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
RippleButton {
id: root
required property var element
opacity: element.type != "empty" ? 1 : 0
implicitHeight: 60
implicitWidth: 60
colBackground: Appearance.colors.colLayer2
buttonRadius: Appearance.rounding.small
Rectangle {
anchors {
top: parent.top
left: parent.left
topMargin: 4
leftMargin: 4
}
color: Appearance.colors.colLayer2
radius: Appearance.rounding.full
implicitWidth: Math.max(20, elementNumber.implicitWidth)
implicitHeight: Math.max(20, elementNumber.implicitHeight)
width: height
StyledText {
id: elementNumber
anchors.centerIn: parent
color: Appearance.colors.colOnLayer2
text: root.element.number
font.pixelSize: Appearance.font.pixelSize.smallest
}
}
StyledText {
id: elementSymbol
anchors.centerIn: parent
color: Appearance.colors.colSecondary
font.pixelSize: Appearance.font.pixelSize.huge
text: root.element.symbol
}
StyledText {
id: elementName
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: 4
}
font.pixelSize: Appearance.font.pixelSize.smallest
color: Appearance.colors.colOnLayer2
text: root.element.name
}
}
@@ -0,0 +1,196 @@
// List of rows
const elements = [
[
{ name: 'Hydrogen', symbol: 'H', number: 1, weight: 1.01, type: 'nonmetal' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: 'Helium', symbol: 'He', number: 2, weight: 4.00, type: 'noblegas' },
],
[
{ name: 'Lithium', symbol: 'Li', number: 3, weight: 6.94, type: 'metal' },
{ name: 'Beryllium', symbol: 'Be', number: 4, weight: 9.01, type: 'metal' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: 'Boron', symbol: 'B', number: 5, weight: 10.81, type: 'nonmetal' },
{ name: 'Carbon', symbol: 'C', number: 6, weight: 12.01, type: 'nonmetal' },
{ name: 'Nitrogen', symbol: 'N', number: 7, weight: 14.01, type: 'nonmetal' },
{ name: 'Oxygen', symbol: 'O', number: 8, weight: 16, type: 'nonmetal' },
{ name: 'Fluorine', symbol: 'F', number: 9, weight: 19, type: 'nonmetal' },
{ name: 'Neon', symbol: 'Ne', number: 10, weight: 20.18, type: 'noblegas' },
],
[
{ name: 'Sodium', symbol: 'Na', number: 11, weight: 22.99, type: 'metal' },
{ name: 'Magnesium', symbol: 'Mg', number: 12, weight: 24.31, type: 'metal' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: 'Aluminum', symbol: 'Al', number: 13, weight: 26.98, type: 'metal' },
{ name: 'Silicon', symbol: 'Si', number: 14, weight: 28.09, type: 'nonmetal' },
{ name: 'Phosphorus', symbol: 'P', number: 15, weight: 30.97, type: 'nonmetal' },
{ name: 'Sulfur', symbol: 'S', number: 16, weight: 32.07, type: 'nonmetal' },
{ name: 'Chlorine', symbol: 'Cl', number: 17, weight: 35.45, type: 'nonmetal' },
{ name: 'Argon', symbol: 'Ar', number: 18, weight: 39.95, type: 'noblegas' },
],
[
{ name: 'Potassium', symbol: 'K', number: 19, weight: 39.098, type: 'metal' },
{ name: 'Calcium', symbol: 'Ca', number: 20, weight: 40.078, type: 'metal' },
{ name: 'Scandium', symbol: 'Sc', number: 21, weight: 44.956, type: 'metal' },
{ name: 'Titanium', symbol: 'Ti', number: 22, weight: 47.87, type: 'metal' },
{ name: 'Vanadium', symbol: 'V', number: 23, weight: 50.94, type: 'metal' },
{ name: 'Chromium', symbol: 'Cr', number: 24, weight: 52, type: 'metal'/*, icon: 'chromium-browser'*/ },
{ name: 'Manganese', symbol: 'Mn', number: 25, weight: 54.94, type: 'metal' },
{ name: 'Iron', symbol: 'Fe', number: 26, weight: 55.85, type: 'metal' },
{ name: 'Cobalt', symbol: 'Co', number: 27, weight: 58.93, type: 'metal' },
{ name: 'Nickel', symbol: 'Ni', number: 28, weight: 58.69, type: 'metal' },
{ name: 'Copper', symbol: 'Cu', number: 29, weight: 63.55, type: 'metal' },
{ name: 'Zinc', symbol: 'Zn', number: 30, weight: 65.38, type: 'metal' },
{ name: 'Gallium', symbol: 'Ga', number: 31, weight: 69.72, type: 'metal' },
{ name: 'Germanium', symbol: 'Ge', number: 32, weight: 72.63, type: 'metal' },
{ name: 'Arsenic', symbol: 'As', number: 33, weight: 74.92, type: 'nonmetal' },
{ name: 'Selenium', symbol: 'Se', number: 34, weight: 78.96, type: 'nonmetal' },
{ name: 'Bromine', symbol: 'Br', number: 35, weight: 79.904, type: 'nonmetal' },
{ name: 'Krypton', symbol: 'Kr', number: 36, weight: 83.8, type: 'noblegas' },
],
[
{ name: 'Rubidium', symbol: 'Rb', number: 37, weight: 85.47, type: 'metal' },
{ name: 'Strontium', symbol: 'Sr', number: 38, weight: 87.62, type: 'metal' },
{ name: 'Yttrium', symbol: 'Y', number: 39, weight: 88.91, type: 'metal' },
{ name: 'Zirconium', symbol: 'Zr', number: 40, weight: 91.22, type: 'metal' },
{ name: 'Niobium', symbol: 'Nb', number: 41, weight: 92.91, type: 'metal' },
{ name: 'Molybdenum', symbol: 'Mo', number: 42, weight: 95.94, type: 'metal' },
{ name: 'Technetium', symbol: 'Tc', number: 43, weight: 98, type: 'metal' },
{ name: 'Ruthenium', symbol: 'Ru', number: 44, weight: 101.07, type: 'metal' },
{ name: 'Rhodium', symbol: 'Rh', number: 45, weight: 102.91, type: 'metal' },
{ name: 'Palladium', symbol: 'Pd', number: 46, weight: 106.42, type: 'metal' },
{ name: 'Silver', symbol: 'Ag', number: 47, weight: 107.87, type: 'metal' },
{ name: 'Cadmium', symbol: 'Cd', number: 48, weight: 112.41, type: 'metal' },
{ name: 'Indium', symbol: 'In', number: 49, weight: 114.82, type: 'metal' },
{ name: 'Tin', symbol: 'Sn', number: 50, weight: 118.71, type: 'metal' },
{ name: 'Antimony', symbol: 'Sb', number: 51, weight: 121.76, type: 'metal' },
{ name: 'Tellurium', symbol: 'Te', number: 52, weight: 127.6, type: 'nonmetal' },
{ name: 'Iodine', symbol: 'I', number: 53, weight: 126.9, type: 'nonmetal' },
{ name: 'Xenon', symbol: 'Xe', number: 54, weight: 131.29, type: 'noblegas' },
],
[
{ name: 'Cesium', symbol: 'Cs', number: 55, weight: 132.91, type: 'metal' },
{ name: 'Barium', symbol: 'Ba', number: 56, weight: 137.33, type: 'metal' },
{ name: 'Lanthanum', symbol: 'La', number: 57, weight: 138.91, type: 'lanthanum' },
{ name: 'Hafnium', symbol: 'Hf', number: 72, weight: 178.49, type: 'metal' },
{ name: 'Tantalum', symbol: 'Ta', number: 73, weight: 180.95, type: 'metal' },
{ name: 'Tungsten', symbol: 'W', number: 74, weight: 183.84, type: 'metal' },
{ name: 'Rhenium', symbol: 'Re', number: 75, weight: 186.21, type: 'metal' },
{ name: 'Osmium', symbol: 'Os', number: 76, weight: 190.23, type: 'metal' },
{ name: 'Iridium', symbol: 'Ir', number: 77, weight: 192.22, type: 'metal' },
{ name: 'Platinum', symbol: 'Pt', number: 78, weight: 195.09, type: 'metal' },
{ name: 'Gold', symbol: 'Au', number: 79, weight: 196.97, type: 'metal' },
{ name: 'Mercury', symbol: 'Hg', number: 80, weight: 200.59, type: 'metal' },
{ name: 'Thallium', symbol: 'Tl', number: 81, weight: 204.38, type: 'metal' },
{ name: 'Lead', symbol: 'Pb', number: 82, weight: 207.2, type: 'metal' },
{ name: 'Bismuth', symbol: 'Bi', number: 83, weight: 208.98, type: 'metal' },
{ name: 'Polonium', symbol: 'Po', number: 84, weight: 209, type: 'metal' },
{ name: 'Astatine', symbol: 'At', number: 85, weight: 210, type: 'nonmetal' },
{ name: 'Radon', symbol: 'Rn', number: 86, weight: 222, type: 'noblegas' },
],
[
{ name: 'Francium', symbol: 'Fr', number: 87, weight: 223, type: 'metal' },
{ name: 'Radium', symbol: 'Ra', number: 88, weight: 226, type: 'metal' },
{ name: 'Actinium', symbol: 'Ac', number: 89, weight: 227, type: 'actinium' },
{ name: 'Rutherfordium', symbol: 'Rf', number: 104, weight: 267, type: 'metal' },
{ name: 'Dubnium', symbol: 'Db', number: 105, weight: 268, type: 'metal' },
{ name: 'Seaborgium', symbol: 'Sg', number: 106, weight: 271, type: 'metal' },
{ name: 'Bohrium', symbol: 'Bh', number: 107, weight: 272, type: 'metal' },
{ name: 'Hassium', symbol: 'Hs', number: 108, weight: 277, type: 'metal' },
{ name: 'Meitnerium', symbol: 'Mt', number: 109, weight: 278, type: 'metal' },
{ name: 'Darmstadtium', symbol: 'Ds', number: 110, weight: 281, type: 'metal' },
{ name: 'Roentgenium', symbol: 'Rg', number: 111, weight: 280, type: 'metal' },
{ name: 'Copernicium', symbol: 'Cn', number: 112, weight: 285, type: 'metal' },
{ name: 'Nihonium', symbol: 'Nh', number: 113, weight: 286, type: 'metal' },
{ name: 'Flerovium', symbol: 'Fl', number: 114, weight: 289, type: 'metal' },
{ name: 'Moscovium', symbol: 'Mc', number: 115, weight: 290, type: 'metal' },
{ name: 'Livermorium', symbol: 'Lv', number: 116, weight: 293, type: 'metal' },
{ name: 'Tennessine', symbol: 'Ts', number: 117, weight: 294, type: 'metal' },
{ name: 'Oganesson', symbol: 'Og', number: 118, weight: 294, type: 'noblegas' },
],
]
const series = [
[
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: 'Cerium', symbol: 'Ce', number: 58, weight: 140.12, type: 'lanthanum' },
{ name: 'Praseodymium', symbol: 'Pr', number: 59, weight: 140.91, type: 'lanthanum' },
{ name: 'Neodymium', symbol: 'Nd', number: 60, weight: 144.24, type: 'lanthanum' },
{ name: 'Promethium', symbol: 'Pm', number: 61, weight: 145, type: 'lanthanum' },
{ name: 'Samarium', symbol: 'Sm', number: 62, weight: 150.36, type: 'lanthanum' },
{ name: 'Europium', symbol: 'Eu', number: 63, weight: 151.96, type: 'lanthanum' },
{ name: 'Gadolinium', symbol: 'Gd', number: 64, weight: 157.25, type: 'lanthanum' },
{ name: 'Terbium', symbol: 'Tb', number: 65, weight: 158.93, type: 'lanthanum' },
{ name: 'Dysprosium', symbol: 'Dy', number: 66, weight: 162.5, type: 'lanthanum' },
{ name: 'Holmium', symbol: 'Ho', number: 67, weight: 164.93, type: 'lanthanum' },
{ name: 'Erbium', symbol: 'Er', number: 68, weight: 167.26, type: 'lanthanum' },
{ name: 'Thulium', symbol: 'Tm', number: 69, weight: 168.93, type: 'lanthanum' },
{ name: 'Ytterbium', symbol: 'Yb', number: 70, weight: 173.04, type: 'lanthanum' },
{ name: 'Lutetium', symbol: 'Lu', number: 71, weight: 174.97, type: 'lanthanum' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
],
[
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
{ name: 'Thorium', symbol: 'Th', number: 90, weight: 232.04, type: 'actinium' },
{ name: 'Protactinium', symbol: 'Pa', number: 91, weight: 231.04, type: 'actinium' },
{ name: 'Uranium', symbol: 'U', number: 92, weight: 238.03, type: 'actinium' },
{ name: 'Neptunium', symbol: 'Np', number: 93, weight: 237, type: 'actinium' },
{ name: 'Plutonium', symbol: 'Pu', number: 94, weight: 244, type: 'actinium' },
{ name: 'Americium', symbol: 'Am', number: 95, weight: 243, type: 'actinium' },
{ name: 'Curium', symbol: 'Cm', number: 96, weight: 247, type: 'actinium' },
{ name: 'Berkelium', symbol: 'Bk', number: 97, weight: 247, type: 'actinium' },
{ name: 'Californium', symbol: 'Cf', number: 98, weight: 251, type: 'actinium' },
{ name: 'Einsteinium', symbol: 'Es', number: 99, weight: 252, type: 'actinium' },
{ name: 'Fermium', symbol: 'Fm', number: 100, weight: 257, type: 'actinium' },
{ name: 'Mendelevium', symbol: 'Md', number: 101, weight: 258, type: 'actinium' },
{ name: 'Nobelium', symbol: 'No', number: 102, weight: 259, type: 'actinium' },
{ name: 'Lawrencium', symbol: 'Lr', number: 103, weight: 262, type: 'actinium' },
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
],
];
const niceTypes = {
'metal': "Metal",
'nonmetal': "Nonmetal",
'noblegas': "Noble gas",
'lanthanum': "Lanthanum",
'actinium': "Actinium"
}
@@ -0,0 +1,314 @@
import QtQuick
import Quickshell
import "root:/modules/common/functions/color_utils.js" as ColorUtils
pragma Singleton
pragma ComponentBehavior: Bound
Singleton {
id: root
property QtObject m3colors
property QtObject animation
property QtObject animationCurves
property QtObject colors
property QtObject rounding
property QtObject font
property QtObject sizes
property string syntaxHighlightingTheme
// Extremely conservative transparency values for consistency and readability
property real transparency: Config.options?.appearance.transparency ? (m3colors.darkmode ? 0.1 : 0.07) : 0
property real contentTransparency: Config.options?.appearance.transparency ? (m3colors.darkmode ? 0.55 : 0.55) : 0
m3colors: QtObject {
property bool darkmode: false
property bool transparent: false
property color m3primary_paletteKeyColor: "#91689E"
property color m3secondary_paletteKeyColor: "#837186"
property color m3tertiary_paletteKeyColor: "#9D6A67"
property color m3neutral_paletteKeyColor: "#7C757B"
property color m3neutral_variant_paletteKeyColor: "#7D747D"
property color m3background: "#161217"
property color m3onBackground: "#EAE0E7"
property color m3surface: "#161217"
property color m3surfaceDim: "#161217"
property color m3surfaceBright: "#3D373D"
property color m3surfaceContainerLowest: "#110D12"
property color m3surfaceContainerLow: "#1F1A1F"
property color m3surfaceContainer: "#231E23"
property color m3surfaceContainerHigh: "#2D282E"
property color m3surfaceContainerHighest: "#383339"
property color m3onSurface: "#EAE0E7"
property color m3surfaceVariant: "#4C444D"
property color m3onSurfaceVariant: "#CFC3CD"
property color m3inverseSurface: "#EAE0E7"
property color m3inverseOnSurface: "#342F34"
property color m3outline: "#988E97"
property color m3outlineVariant: "#4C444D"
property color m3shadow: "#000000"
property color m3scrim: "#000000"
property color m3surfaceTint: "#E5B6F2"
property color m3primary: "#E5B6F2"
property color m3onPrimary: "#452152"
property color m3primaryContainer: "#5D386A"
property color m3onPrimaryContainer: "#F9D8FF"
property color m3inversePrimary: "#775084"
property color m3secondary: "#D5C0D7"
property color m3onSecondary: "#392C3D"
property color m3secondaryContainer: "#534457"
property color m3onSecondaryContainer: "#F2DCF3"
property color m3tertiary: "#F5B7B3"
property color m3onTertiary: "#4C2523"
property color m3tertiaryContainer: "#BA837F"
property color m3onTertiaryContainer: "#000000"
property color m3error: "#FFB4AB"
property color m3onError: "#690005"
property color m3errorContainer: "#93000A"
property color m3onErrorContainer: "#FFDAD6"
property color m3primaryFixed: "#F9D8FF"
property color m3primaryFixedDim: "#E5B6F2"
property color m3onPrimaryFixed: "#2E0A3C"
property color m3onPrimaryFixedVariant: "#5D386A"
property color m3secondaryFixed: "#F2DCF3"
property color m3secondaryFixedDim: "#D5C0D7"
property color m3onSecondaryFixed: "#241727"
property color m3onSecondaryFixedVariant: "#514254"
property color m3tertiaryFixed: "#FFDAD7"
property color m3tertiaryFixedDim: "#F5B7B3"
property color m3onTertiaryFixed: "#331110"
property color m3onTertiaryFixedVariant: "#663B39"
property color m3success: "#B5CCBA"
property color m3onSuccess: "#213528"
property color m3successContainer: "#374B3E"
property color m3onSuccessContainer: "#D1E9D6"
property color term0: "#EDE4E4"
property color term1: "#B52755"
property color term2: "#A97363"
property color term3: "#AF535D"
property color term4: "#A67F7C"
property color term5: "#B2416B"
property color term6: "#8D76AD"
property color term7: "#272022"
property color term8: "#0E0D0D"
property color term9: "#B52755"
property color term10: "#A97363"
property color term11: "#AF535D"
property color term12: "#A67F7C"
property color term13: "#B2416B"
property color term14: "#8D76AD"
property color term15: "#221A1A"
}
colors: QtObject {
property color colSubtext: m3colors.m3outline
property color colLayer0: ColorUtils.mix(ColorUtils.transparentize(m3colors.m3background, root.transparency), m3colors.m3primary, Config.options.appearance.extraBackgroundTint ? 0.99 : 1)
property color colOnLayer0: m3colors.m3onBackground
property color colLayer0Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.9, root.contentTransparency))
property color colLayer0Active: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.8, root.contentTransparency))
property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.8), root.contentTransparency);
property color colOnLayer1: m3colors.m3onSurfaceVariant;
property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45);
property color colLayer2: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.1), root.contentTransparency)
property color colOnLayer2: m3colors.m3onSurface;
property color colOnLayer2Disabled: ColorUtils.mix(colOnLayer2, m3colors.m3background, 0.4);
property color colLayer3: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96), root.contentTransparency)
property color colOnLayer3: m3colors.m3onSurface;
property color colLayer1Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.92), root.contentTransparency)
property color colLayer1Active: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.85), root.contentTransparency);
property color colLayer2Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer2, colOnLayer2, 0.90), root.contentTransparency)
property color colLayer2Active: ColorUtils.transparentize(ColorUtils.mix(colLayer2, colOnLayer2, 0.80), root.contentTransparency);
property color colLayer2Disabled: ColorUtils.transparentize(ColorUtils.mix(colLayer2, m3colors.m3background, 0.8), root.contentTransparency);
property color colLayer3Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.90), root.contentTransparency)
property color colLayer3Active: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.80), root.contentTransparency);
property color colPrimary: m3colors.m3primary
property color colOnPrimary: m3colors.m3onPrimary
property color colPrimaryHover: ColorUtils.mix(colors.colPrimary, colLayer1Hover, 0.87)
property color colPrimaryActive: ColorUtils.mix(colors.colPrimary, colLayer1Active, 0.7)
property color colPrimaryContainer: m3colors.m3primaryContainer
property color colPrimaryContainerHover: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Hover, 0.7)
property color colPrimaryContainerActive: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Active, 0.6)
property color colOnPrimaryContainer: m3colors.m3onPrimaryContainer
property color colSecondary: m3colors.m3secondary
property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85)
property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4)
property color colSecondaryContainer: m3colors.m3secondaryContainer
property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, m3colors.m3onSecondaryContainer, 0.90)
property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54)
property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer
property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency)
property color colSurfaceContainer: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency)
property color colSurfaceContainerHigh: ColorUtils.transparentize(m3colors.m3surfaceContainerHigh, root.contentTransparency)
property color colSurfaceContainerHighest: ColorUtils.transparentize(m3colors.m3surfaceContainerHighest, root.contentTransparency)
property color colSurfaceContainerHighestHover: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.95)
property color colSurfaceContainerHighestActive: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.85)
property color colTooltip: m3colors.darkmode ? ColorUtils.mix(m3colors.m3background, "#3C4043", 0.5) : "#3C4043" // m3colors.m3inverseSurface in the specs, but the m3 website actually uses #3C4043
property color colOnTooltip: "#F8F9FA" // m3colors.m3inverseOnSurface in the specs, but the m3 website actually uses this color
property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5)
property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7)
property color colOutlineVariant: m3colors.m3outlineVariant
}
rounding: QtObject {
property int unsharpen: 2
property int unsharpenmore: 6
property int verysmall: 8
property int small: 12
property int normal: 17
property int large: 23
property int verylarge: 30
property int full: 9999
property int screenRounding: large
property int windowRounding: 18
}
font: QtObject {
property QtObject family: QtObject {
property string main: "Rubik"
property string title: "Gabarito"
property string iconMaterial: "Material Symbols Rounded"
property string iconNerd: "SpaceMono NF"
property string monospace: "JetBrains Mono NF"
property string reading: "Readex Pro"
}
property QtObject pixelSize: QtObject {
property int smallest: 10
property int smaller: 12
property int small: 15
property int normal: 16
property int large: 17
property int larger: 19
property int huge: 22
property int hugeass: 23
property int title: huge
}
}
animationCurves: QtObject {
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1] // Default, 350ms
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1] // Default, 500ms
readonly property list<real> expressiveSlowSpatial: [0.39, 1.29, 0.35, 0.98, 1, 1] // Default, 650ms
readonly property list<real> expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1] // Default, 200ms
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
readonly property list<real> emphasizedFirstHalf: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82]
readonly property list<real> emphasizedLastHalf: [5 / 24, 0.82, 0.25, 1, 1, 1]
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1]
readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
readonly property real expressiveFastSpatialDuration: 350
readonly property real expressiveDefaultSpatialDuration: 500
readonly property real expressiveSlowSpatialDuration: 650
readonly property real expressiveEffectsDuration: 200
}
animation: QtObject {
property QtObject elementMove: QtObject {
property int duration: animationCurves.expressiveDefaultSpatialDuration
property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.expressiveDefaultSpatial
property int velocity: 650
property Component numberAnimation: Component {
NumberAnimation {
duration: root.animation.elementMove.duration
easing.type: root.animation.elementMove.type
easing.bezierCurve: root.animation.elementMove.bezierCurve
}
}
property Component colorAnimation: Component {
ColorAnimation {
duration: root.animation.elementMove.duration
easing.type: root.animation.elementMove.type
easing.bezierCurve: root.animation.elementMove.bezierCurve
}
}
}
property QtObject elementMoveEnter: QtObject {
property int duration: 400
property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.emphasizedDecel
property int velocity: 650
property Component numberAnimation: Component {
NumberAnimation {
duration: root.animation.elementMoveEnter.duration
easing.type: root.animation.elementMoveEnter.type
easing.bezierCurve: root.animation.elementMoveEnter.bezierCurve
}
}
}
property QtObject elementMoveExit: QtObject {
property int duration: 200
property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.emphasizedAccel
property int velocity: 650
property Component numberAnimation: Component {
NumberAnimation {
duration: root.animation.elementMoveExit.duration
easing.type: root.animation.elementMoveExit.type
easing.bezierCurve: root.animation.elementMoveExit.bezierCurve
}
}
}
property QtObject elementMoveFast: QtObject {
property int duration: animationCurves.expressiveEffectsDuration
property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.expressiveEffects
property int velocity: 850
property Component colorAnimation: Component { ColorAnimation {
duration: root.animation.elementMoveFast.duration
easing.type: root.animation.elementMoveFast.type
easing.bezierCurve: root.animation.elementMoveFast.bezierCurve
}}
property Component numberAnimation: Component { NumberAnimation {
duration: root.animation.elementMoveFast.duration
easing.type: root.animation.elementMoveFast.type
easing.bezierCurve: root.animation.elementMoveFast.bezierCurve
}}
}
property QtObject clickBounce: QtObject {
property int duration: 200
property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.expressiveFastSpatial
property int velocity: 850
property Component numberAnimation: Component { NumberAnimation {
duration: root.animation.clickBounce.duration
easing.type: root.animation.clickBounce.type
easing.bezierCurve: root.animation.clickBounce.bezierCurve
}}
}
property QtObject scroll: QtObject {
property int duration: 400
property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.standardDecel
}
property QtObject menuDecel: QtObject {
property int duration: 350
property int type: Easing.OutExpo
}
}
sizes: QtObject {
property real baseBarHeight: 40
property real barHeight: Config.options.bar.cornerStyle === 1 ?
(baseBarHeight + Appearance.sizes.hyprlandGapsOut * 2) : baseBarHeight
property real barCenterSideModuleWidth: Config.options?.bar.verbose ? 360 : 140
property real barCenterSideModuleWidthShortened: 280
property real barCenterSideModuleWidthHellaShortened: 190
property real barShortenScreenWidthThreshold: 1200 // Shorten if screen width is at most this value
property real barHellaShortenScreenWidthThreshold: 1000 // Shorten even more...
property real sidebarWidth: 460
property real sidebarWidthExtended: 750
property real osdWidth: 200
property real mediaControlsWidth: 440
property real mediaControlsHeight: 160
property real notificationPopupWidth: 410
property real searchWidthCollapsed: 260
property real searchWidth: 450
property real hyprlandGapsOut: 5
property real elevationMargin: 10
property real fabShadowRadius: 5
property real fabHoveredShadowRadius: 7
}
syntaxHighlightingTheme: Appearance.m3colors.darkmode ? "Monokai" : "ayu Light"
}
@@ -0,0 +1,231 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property string filePath: Directories.shellConfigPath
property alias options: configOptionsJsonAdapter
function setNestedValue(nestedKey, value) {
let keys = nestedKey.split(".");
let obj = root.options;
let parents = [obj];
// Traverse and collect parent objects
for (let i = 0; i < keys.length - 1; ++i) {
if (!obj[keys[i]] || typeof obj[keys[i]] !== "object") {
obj[keys[i]] = {};
}
obj = obj[keys[i]];
parents.push(obj);
}
// Convert value to correct type using JSON.parse when safe
let convertedValue = value;
if (typeof value === "string") {
let trimmed = value.trim();
if (trimmed === "true" || trimmed === "false" || !isNaN(Number(trimmed))) {
try {
convertedValue = JSON.parse(trimmed);
} catch (e) {
convertedValue = value;
}
}
}
obj[keys[keys.length - 1]] = convertedValue;
}
FileView {
path: root.filePath
watchChanges: true
onFileChanged: reload()
onAdapterUpdated: writeAdapter()
onLoadFailed: error => {
if (error == FileViewError.FileNotFound) {
writeAdapter();
}
}
JsonAdapter {
id: configOptionsJsonAdapter
property JsonObject policies: JsonObject {
property int ai: 1 // 0: No | 1: Yes | 2: Local
property int weeb: 1 // 0: No | 1: Open | 2: Closet
}
property JsonObject ai: JsonObject {
property string systemPrompt: qsTr("## Style\n- Use casual tone, don't be formal! Make sure you answer precisely without hallucination and prefer bullet points over walls of text. You can have a friendly greeting at the beginning of the conversation, but don't repeat the user's question\n\n## Presentation\n- Use Markdown features in your response: \n - **Bold** text to **highlight keywords** in your response\n - **Split long information into small sections** with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). Bullet points are preferred over long paragraphs, unless you're offering writing support or instructed otherwise by the user.\n- Asked to compare different options? You should firstly use a table to compare the main aspects, then elaborate or include relevant comments from online forums *after* the table. Make sure to provide a final recommendation for the user's use case!\n- Use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.).\n\nThanks!\n\n## Tools\nMay or may not be available depending on the user's settings. If they're available, follow these guidelines:\n\n### Search\n- When user asks for information that might benefit from up-to-date information, use this to get search access\n\n### Shell configuration\n- Always fetch the config options to see the available keys before setting\n- Avoid unnecessarily asking the user to confirm the changes they explicitly asked for, just do it\n")
}
property JsonObject appearance: JsonObject {
property bool extraBackgroundTint: true
property int fakeScreenRounding: 2 // 0: None | 1: Always | 2: When not fullscreen
property bool transparency: false
property JsonObject palette: JsonObject {
property string type: "auto" // Allowed: auto, scheme-content, scheme-expressive, scheme-fidelity, scheme-fruit-salad, scheme-monochrome, scheme-neutral, scheme-rainbow, scheme-tonal-spot
}
}
property JsonObject audio: JsonObject {
// Values in %
property JsonObject protection: JsonObject {
// Prevent sudden bangs
property bool enable: true
property real maxAllowedIncrease: 10
property real maxAllowed: 90 // Realistically should already provide some protection when it's 99...
}
}
property JsonObject apps: JsonObject {
property string bluetooth: "kcmshell6 kcm_bluetooth"
property string network: "plasmawindowed org.kde.plasma.networkmanagement"
property string networkEthernet: "kcmshell6 kcm_networkmanagement"
property string taskManager: "plasma-systemmonitor --page-name Processes"
property string terminal: "kitty -1" // This is only for shell actions
}
property JsonObject background: JsonObject {
property bool fixedClockPosition: false
property real clockX: -500
property real clockY: -500
}
property JsonObject bar: JsonObject {
property bool bottom: false // Instead of top
property int cornerStyle: 0 // 0: Hug | 1: Float | 2: Plain rectangle
property bool borderless: false // true for no grouping of items
property string topLeftIcon: "spark" // Options: distro, spark
property bool showBackground: true
property bool verbose: true
property JsonObject resources: JsonObject {
property bool alwaysShowSwap: true
property bool alwaysShowCpu: false
}
property list<string> screenList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command
property JsonObject utilButtons: JsonObject {
property bool showScreenSnip: true
property bool showColorPicker: false
property bool showMicToggle: false
property bool showKeyboardToggle: true
property bool showDarkModeToggle: true
}
property JsonObject tray: JsonObject {
property bool monochromeIcons: true
}
property JsonObject workspaces: JsonObject {
property bool monochromeIcons: true
property int shown: 10
property bool showAppIcons: true
property bool alwaysShowNumbers: false
property int showNumberDelay: 300 // milliseconds
}
property JsonObject weather: JsonObject {
property bool enable: false
property bool enableGPS: true // gps based location
property string city: "" // When 'enableGPS' is false
property bool useUSCS: false // Instead of metric (SI) units
property int fetchInterval: 10 // minutes
}
}
property JsonObject battery: JsonObject {
property int low: 20
property int critical: 5
property bool automaticSuspend: true
property int suspend: 3
}
property JsonObject dock: JsonObject {
property bool enable: false
property bool monochromeIcons: true
property real height: 60
property real hoverRegionHeight: 2
property bool pinnedOnStartup: false
property bool hoverToReveal: true // When false, only reveals on empty workspace
property list<string> pinnedApps: [ // IDs of pinned entries
"org.kde.dolphin", "kitty",]
}
property JsonObject language: JsonObject {
property JsonObject translator: JsonObject {
property string engine: "auto" // Run `trans -list-engines` for available engines. auto should use google
property string targetLanguage: "auto" // Run `trans -list-all` for available languages
property string sourceLanguage: "auto"
}
}
property JsonObject networking: JsonObject {
property string userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
}
property JsonObject osd: JsonObject {
property int timeout: 1000
}
property JsonObject osk: JsonObject {
property string layout: "qwerty_full"
property bool pinnedOnStartup: false
}
property JsonObject overview: JsonObject {
property real scale: 0.18 // Relative to screen size
property real rows: 2
property real columns: 5
}
property JsonObject resources: JsonObject {
property int updateInterval: 3000
}
property JsonObject search: JsonObject {
property int nonAppResultDelay: 30 // This prevents lagging when typing
property string engineBaseUrl: "https://www.google.com/search?q="
property list<string> excludedSites: ["quora.com"]
property bool sloppy: false // Uses levenshtein distance based scoring instead of fuzzy sort. Very weird.
property JsonObject prefix: JsonObject {
property string action: "/"
property string clipboard: ";"
property string emojis: ":"
}
}
property JsonObject sidebar: JsonObject {
property JsonObject translator: JsonObject {
property int delay: 300 // Delay before sending request. Reduces (potential) rate limits and lag.
}
property JsonObject booru: JsonObject {
property bool allowNsfw: false
property string defaultProvider: "yandere"
property int limit: 20
property JsonObject zerochan: JsonObject {
property string username: "[unset]"
}
}
}
property JsonObject time: JsonObject {
// https://doc.qt.io/qt-6/qtime.html#toString
property string format: "hh:mm"
property string dateFormat: "ddd, dd/MM"
}
property JsonObject windows: JsonObject {
property bool showTitlebar: true // Client-side decoration for shell apps
property bool centerTitle: true
}
property JsonObject hacks: JsonObject {
property int arbitraryRaceConditionDelay: 20 // milliseconds
}
property JsonObject screenshotTool: JsonObject {
property bool showContentRegions: true
}
}
}
}
@@ -0,0 +1,50 @@
pragma Singleton
pragma ComponentBehavior: Bound
import "root:/modules/common/functions/file_utils.js" as FileUtils
import Qt.labs.platform
import QtQuick
import Quickshell
import Quickshell.Hyprland
Singleton {
// XDG Dirs, with "file://"
readonly property string config: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0]
readonly property string state: StandardPaths.standardLocations(StandardPaths.StateLocation)[0]
readonly property string cache: StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]
readonly property string pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
readonly property string downloads: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0]
// Other dirs used by the shell, without "file://"
property string assetsPath: Quickshell.configPath("assets")
property string scriptPath: Quickshell.configPath("scripts")
property string favicons: FileUtils.trimFileProtocol(`${Directories.cache}/media/favicons`)
property string coverArt: FileUtils.trimFileProtocol(`${Directories.cache}/media/coverart`)
property string booruPreviews: FileUtils.trimFileProtocol(`${Directories.cache}/media/boorus`)
property string booruDownloads: FileUtils.trimFileProtocol(Directories.pictures + "/homework")
property string booruDownloadsNsfw: FileUtils.trimFileProtocol(Directories.pictures + "/homework/🌶️")
property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/media/latex`)
property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse`)
property string shellConfigName: "config.json"
property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}`
property string todoPath: FileUtils.trimFileProtocol(`${Directories.state}/user/todo.json`)
property string notificationsPath: FileUtils.trimFileProtocol(`${Directories.cache}/notifications/notifications.json`)
property string generatedMaterialThemePath: FileUtils.trimFileProtocol(`${Directories.state}/user/generated/colors.json`)
property string cliphistDecode: FileUtils.trimFileProtocol(`/tmp/quickshell/media/cliphist`)
property string screenshotTemp: "/tmp/quickshell/media/screenshot"
property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`)
property string defaultAiPrompts: Quickshell.configPath("defaults/ai/prompts")
property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/ai/prompts`)
property string aiChats: FileUtils.trimFileProtocol(`${Directories.state}/user/ai/chats`)
// Cleanup on init
Component.onCompleted: {
Quickshell.execDetached(["mkdir", "-p", `${shellConfig}`])
Quickshell.execDetached(["mkdir", "-p", `${favicons}`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${coverArt}'; mkdir -p '${coverArt}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`])
Quickshell.execDetached(["bash", "-c", `mkdir -p '${booruDownloads}' && mkdir -p '${booruDownloadsNsfw}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${latexOutput}'; mkdir -p '${latexOutput}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${cliphistDecode}'; mkdir -p '${cliphistDecode}'`])
Quickshell.execDetached(["mkdir", "-p", `${aiChats}`])
}
}
@@ -0,0 +1,48 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property alias states: persistentStatesJsonAdapter
property string fileDir: Directories.state
property string fileName: "states.json"
property string filePath: `${root.fileDir}/${root.fileName}`
FileView {
path: root.filePath
watchChanges: true
onFileChanged: reload()
onAdapterUpdated: {
writeAdapter()
}
onLoadFailed: error => {
console.log("Failed to load persistent states file:", error);
if (error == FileViewError.FileNotFound) {
writeAdapter();
}
}
adapter: JsonAdapter {
id: persistentStatesJsonAdapter
property JsonObject ai: JsonObject {
property string model
property real temperature: 0.5
}
property JsonObject sidebar: JsonObject {
property JsonObject bottomGroup: JsonObject {
property bool collapsed: false
}
}
property JsonObject booru: JsonObject {
property bool allowNsfw: false
property string provider: "yandere"
}
}
}
}
@@ -0,0 +1,23 @@
import QtQuick
import Quickshell
pragma Singleton
pragma ComponentBehavior: Bound
Singleton {
property QtObject ai: QtObject {
property string model
property real temperature: 0.5
}
property QtObject sidebar: QtObject {
property QtObject bottomGroup: QtObject {
property bool collapsed: false
}
}
property QtObject booru: QtObject {
property bool allowNsfw: false
property string provider: "yandere"
}
}
@@ -0,0 +1,109 @@
// This module provides high level utility functions for color manipulation.
/**
* Returns a color with the hue of color2 and the saturation, value, and alpha of color1.
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The color to take hue from.
* @returns {Qt.rgba} The resulting color.
*/
function colorWithHueOf(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
// Qt.color hsvHue/hsvSaturation/hsvValue/alpha return 0-1
var hue = c2.hsvHue;
var sat = c1.hsvSaturation;
var val = c1.hsvValue;
var alpha = c1.a;
return Qt.hsva(hue, sat, val, alpha);
}
/**
* Returns a color with the saturation of color2 and the hue/value/alpha of color1.
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The color to take saturation from.
* @returns {Qt.rgba} The resulting color.
*/
function colorWithSaturationOf(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
var hue = c1.hsvHue;
var sat = c2.hsvSaturation;
var val = c1.hsvValue;
var alpha = c1.a;
return Qt.hsva(hue, sat, val, alpha);
}
/**
* Returns a color with the given lightness and the hue, saturation, and alpha of the input color (using HSL).
*
* @param {string} color - The base color (any Qt.color-compatible string).
* @param {number} lightness - The lightness value to use (0-1).
* @returns {Qt.rgba} The resulting color.
*/
function colorWithLightness(color, lightness) {
var c = Qt.color(color);
return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a);
}
/**
* Returns a color with the lightness of color2 and the hue, saturation, and alpha of color1 (using HSL).
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The color to take lightness from.
* @returns {Qt.rgba} The resulting color.
*/
function colorWithLightnessOf(color1, color2) {
var c2 = Qt.color(color2);
return colorWithLightness(color1, c2.hslLightness);
}
/**
* Adapts color1 to the accent (hue and saturation) of color2 using HSL, keeping lightness and alpha from color1.
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The accent color.
* @returns {Qt.rgba} The resulting color.
*/
function adaptToAccent(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
var hue = c2.hslHue;
var sat = c2.hslSaturation;
var light = c1.hslLightness;
var alpha = c1.a;
return Qt.hsla(hue, sat, light, alpha);
}
/**
* Mixes two colors by a given percentage.
*
* @param {string} color1 - The first color (any Qt.color-compatible string).
* @param {string} color2 - The second color.
* @param {number} percentage - The mix ratio (0-1). 1 = all color1, 0 = all color2.
* @returns {Qt.rgba} The resulting mixed color.
*/
function mix(color1, color2, percentage = 0.5) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a);
}
/**
* Transparentizes a color by a given percentage.
*
* @param {string} color - The color (any Qt.color-compatible string).
* @param {number} percentage - The amount to transparentize (0-1).
* @returns {Qt.rgba} The resulting color.
*/
function transparentize(color, percentage = 1) {
var c = Qt.color(color);
return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage));
}
@@ -0,0 +1,34 @@
/**
* Trims the File protocol off the input string
* @param {string} str
* @returns {string}
*/
function trimFileProtocol(str) {
return str.startsWith("file://") ? str.slice(7) : str;
}
/**
* Extracts the file name from a file path
* @param {string} str
* @returns {string}
*/
function fileNameForPath(str) {
if (typeof str !== "string") return "";
const trimmed = trimFileProtocol(str);
return trimmed.split(/[\\/]/).pop();
}
/**
* Removes the file extension from a file path or name
* @param {string} str
* @returns {string}
*/
function trimFileExt(str) {
if (typeof str !== "string") return "";
const trimmed = trimFileProtocol(str);
const lastDot = trimmed.lastIndexOf(".");
if (lastDot > -1 && lastDot > trimmed.lastIndexOf("/")) {
return trimmed.slice(0, lastDot);
}
return trimmed;
}
@@ -0,0 +1,682 @@
.pragma library
// https://github.com/farzher/fuzzysort
// License: MIT | Copyright (c) 2018 Stephen Kamenar
// A copy of the license is available in the `licenses` folder of this repository
var single = (search, target) => {
if(!search || !target) return NULL
var preparedSearch = getPreparedSearch(search)
if(!isPrepared(target)) target = getPrepared(target)
var searchBitflags = preparedSearch.bitflags
if((searchBitflags & target._bitflags) !== searchBitflags) return NULL
return algorithm(preparedSearch, target)
}
var go = (search, targets, options) => {
if(!search) return options?.all ? all(targets, options) : noResults
var preparedSearch = getPreparedSearch(search)
var searchBitflags = preparedSearch.bitflags
var containsSpace = preparedSearch.containsSpace
var threshold = denormalizeScore( options?.threshold || 0 )
var limit = options?.limit || INFINITY
var resultsLen = 0; var limitedCount = 0
var targetsLen = targets.length
function push_result(result) {
if(resultsLen < limit) { q.add(result); ++resultsLen }
else {
++limitedCount
if(result._score > q.peek()._score) q.replaceTop(result)
}
}
// This code is copy/pasted 3 times for performance reasons [options.key, options.keys, no keys]
// options.key
if(options?.key) {
var key = options.key
for(var i = 0; i < targetsLen; ++i) { var obj = targets[i]
var target = getValue(obj, key)
if(!target) continue
if(!isPrepared(target)) target = getPrepared(target)
if((searchBitflags & target._bitflags) !== searchBitflags) continue
var result = algorithm(preparedSearch, target)
if(result === NULL) continue
if(result._score < threshold) continue
result.obj = obj
push_result(result)
}
// options.keys
} else if(options?.keys) {
var keys = options.keys
var keysLen = keys.length
outer: for(var i = 0; i < targetsLen; ++i) { var obj = targets[i]
{ // early out based on bitflags
var keysBitflags = 0
for (var keyI = 0; keyI < keysLen; ++keyI) {
var key = keys[keyI]
var target = getValue(obj, key)
if(!target) { tmpTargets[keyI] = noTarget; continue }
if(!isPrepared(target)) target = getPrepared(target)
tmpTargets[keyI] = target
keysBitflags |= target._bitflags
}
if((searchBitflags & keysBitflags) !== searchBitflags) continue
}
if(containsSpace) for(let i=0; i<preparedSearch.spaceSearches.length; i++) keysSpacesBestScores[i] = NEGATIVE_INFINITY
for (var keyI = 0; keyI < keysLen; ++keyI) {
target = tmpTargets[keyI]
if(target === noTarget) { tmpResults[keyI] = noTarget; continue }
tmpResults[keyI] = algorithm(preparedSearch, target, /*allowSpaces=*/false, /*allowPartialMatch=*/containsSpace)
if(tmpResults[keyI] === NULL) { tmpResults[keyI] = noTarget; continue }
// todo: this seems weird and wrong. like what if our first match wasn't good. this should just replace it instead of averaging with it
// if our second match isn't good we ignore it instead of averaging with it
if(containsSpace) for(let i=0; i<preparedSearch.spaceSearches.length; i++) {
if(allowPartialMatchScores[i] > -1000) {
if(keysSpacesBestScores[i] > NEGATIVE_INFINITY) {
var tmp = (keysSpacesBestScores[i] + allowPartialMatchScores[i]) / 4/*bonus score for having multiple matches*/
if(tmp > keysSpacesBestScores[i]) keysSpacesBestScores[i] = tmp
}
}
if(allowPartialMatchScores[i] > keysSpacesBestScores[i]) keysSpacesBestScores[i] = allowPartialMatchScores[i]
}
}
if(containsSpace) {
for(let i=0; i<preparedSearch.spaceSearches.length; i++) { if(keysSpacesBestScores[i] === NEGATIVE_INFINITY) continue outer }
} else {
var hasAtLeast1Match = false
for(let i=0; i < keysLen; i++) { if(tmpResults[i]._score !== NEGATIVE_INFINITY) { hasAtLeast1Match = true; break } }
if(!hasAtLeast1Match) continue
}
var objResults = new KeysResult(keysLen)
for(let i=0; i < keysLen; i++) { objResults[i] = tmpResults[i] }
if(containsSpace) {
var score = 0
for(let i=0; i<preparedSearch.spaceSearches.length; i++) score += keysSpacesBestScores[i]
} else {
// todo could rewrite this scoring to be more similar to when there's spaces
// if we match multiple keys give us bonus points
var score = NEGATIVE_INFINITY
for(let i=0; i<keysLen; i++) {
var result = objResults[i]
if(result._score > -1000) {
if(score > NEGATIVE_INFINITY) {
var tmp = (score + result._score) / 4/*bonus score for having multiple matches*/
if(tmp > score) score = tmp
}
}
if(result._score > score) score = result._score
}
}
objResults.obj = obj
objResults._score = score
if(options?.scoreFn) {
score = options.scoreFn(objResults)
if(!score) continue
score = denormalizeScore(score)
objResults._score = score
}
if(score < threshold) continue
push_result(objResults)
}
// no keys
} else {
for(var i = 0; i < targetsLen; ++i) { var target = targets[i]
if(!target) continue
if(!isPrepared(target)) target = getPrepared(target)
if((searchBitflags & target._bitflags) !== searchBitflags) continue
var result = algorithm(preparedSearch, target)
if(result === NULL) continue
if(result._score < threshold) continue
push_result(result)
}
}
if(resultsLen === 0) return noResults
var results = new Array(resultsLen)
for(var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll()
results.total = resultsLen + limitedCount
return results
}
// this is written as 1 function instead of 2 for minification. perf seems fine ...
// except when minified. the perf is very slow
var highlight = (result, open='<b>', close='</b>') => {
var callback = typeof open === 'function' ? open : undefined
var target = result.target
var targetLen = target.length
var indexes = result.indexes
var highlighted = ''
var matchI = 0
var indexesI = 0
var opened = false
var parts = []
for(var i = 0; i < targetLen; ++i) { var char = target[i]
if(indexes[indexesI] === i) {
++indexesI
if(!opened) { opened = true
if(callback) {
parts.push(highlighted); highlighted = ''
} else {
highlighted += open
}
}
if(indexesI === indexes.length) {
if(callback) {
highlighted += char
parts.push(callback(highlighted, matchI++)); highlighted = ''
parts.push(target.substr(i+1))
} else {
highlighted += char + close + target.substr(i+1)
}
break
}
} else {
if(opened) { opened = false
if(callback) {
parts.push(callback(highlighted, matchI++)); highlighted = ''
} else {
highlighted += close
}
}
}
highlighted += char
}
return callback ? parts : highlighted
}
var prepare = (target) => {
if(typeof target === 'number') target = ''+target
else if(typeof target !== 'string') target = ''
var info = prepareLowerInfo(target)
return new_result(target, {_targetLower:info._lower, _targetLowerCodes:info.lowerCodes, _bitflags:info.bitflags})
}
var cleanup = () => { preparedCache.clear(); preparedSearchCache.clear() }
// Below this point is only internal code
// Below this point is only internal code
// Below this point is only internal code
// Below this point is only internal code
class Result {
get ['indexes']() { return this._indexes.slice(0, this._indexes.len).sort((a,b)=>a-b) }
set ['indexes'](indexes) { return this._indexes = indexes }
['highlight'](open, close) { return highlight(this, open, close) }
get ['score']() { return normalizeScore(this._score) }
set ['score'](score) { this._score = denormalizeScore(score) }
}
class KeysResult extends Array {
get ['score']() { return normalizeScore(this._score) }
set ['score'](score) { this._score = denormalizeScore(score) }
}
var new_result = (target, options) => {
const result = new Result()
result['target'] = target
result['obj'] = options.obj ?? NULL
result._score = options._score ?? NEGATIVE_INFINITY
result._indexes = options._indexes ?? []
result._targetLower = options._targetLower ?? ''
result._targetLowerCodes = options._targetLowerCodes ?? NULL
result._nextBeginningIndexes = options._nextBeginningIndexes ?? NULL
result._bitflags = options._bitflags ?? 0
return result
}
var normalizeScore = score => {
if(score === NEGATIVE_INFINITY) return 0
if(score > 1) return score
return Math.E ** ( ((-score + 1)**.04307 - 1) * -2)
}
var denormalizeScore = normalizedScore => {
if(normalizedScore === 0) return NEGATIVE_INFINITY
if(normalizedScore > 1) return normalizedScore
return 1 - Math.pow((Math.log(normalizedScore) / -2 + 1), 1 / 0.04307)
}
var prepareSearch = (search) => {
if(typeof search === 'number') search = ''+search
else if(typeof search !== 'string') search = ''
search = search.trim()
var info = prepareLowerInfo(search)
var spaceSearches = []
if(info.containsSpace) {
var searches = search.split(/\s+/)
searches = [...new Set(searches)] // distinct
for(var i=0; i<searches.length; i++) {
if(searches[i] === '') continue
var _info = prepareLowerInfo(searches[i])
spaceSearches.push({lowerCodes:_info.lowerCodes, _lower:searches[i].toLowerCase(), containsSpace:false})
}
}
return {lowerCodes: info.lowerCodes, _lower: info._lower, containsSpace: info.containsSpace, bitflags: info.bitflags, spaceSearches: spaceSearches}
}
var getPrepared = (target) => {
if(target.length > 999) return prepare(target) // don't cache huge targets
var targetPrepared = preparedCache.get(target)
if(targetPrepared !== undefined) return targetPrepared
targetPrepared = prepare(target)
preparedCache.set(target, targetPrepared)
return targetPrepared
}
var getPreparedSearch = (search) => {
if(search.length > 999) return prepareSearch(search) // don't cache huge searches
var searchPrepared = preparedSearchCache.get(search)
if(searchPrepared !== undefined) return searchPrepared
searchPrepared = prepareSearch(search)
preparedSearchCache.set(search, searchPrepared)
return searchPrepared
}
var all = (targets, options) => {
var results = []; results.total = targets.length // this total can be wrong if some targets are skipped
var limit = options?.limit || INFINITY
if(options?.key) {
for(var i=0;i<targets.length;i++) { var obj = targets[i]
var target = getValue(obj, options.key)
if(target == NULL) continue
if(!isPrepared(target)) target = getPrepared(target)
var result = new_result(target.target, {_score: target._score, obj: obj})
results.push(result); if(results.length >= limit) return results
}
} else if(options?.keys) {
for(var i=0;i<targets.length;i++) { var obj = targets[i]
var objResults = new KeysResult(options.keys.length)
for (var keyI = options.keys.length - 1; keyI >= 0; --keyI) {
var target = getValue(obj, options.keys[keyI])
if(!target) { objResults[keyI] = noTarget; continue }
if(!isPrepared(target)) target = getPrepared(target)
target._score = NEGATIVE_INFINITY
target._indexes.len = 0
objResults[keyI] = target
}
objResults.obj = obj
objResults._score = NEGATIVE_INFINITY
results.push(objResults); if(results.length >= limit) return results
}
} else {
for(var i=0;i<targets.length;i++) { var target = targets[i]
if(target == NULL) continue
if(!isPrepared(target)) target = getPrepared(target)
target._score = NEGATIVE_INFINITY
target._indexes.len = 0
results.push(target); if(results.length >= limit) return results
}
}
return results
}
var algorithm = (preparedSearch, prepared, allowSpaces=false, allowPartialMatch=false) => {
if(allowSpaces===false && preparedSearch.containsSpace) return algorithmSpaces(preparedSearch, prepared, allowPartialMatch)
var searchLower = preparedSearch._lower
var searchLowerCodes = preparedSearch.lowerCodes
var searchLowerCode = searchLowerCodes[0]
var targetLowerCodes = prepared._targetLowerCodes
var searchLen = searchLowerCodes.length
var targetLen = targetLowerCodes.length
var searchI = 0 // where we at
var targetI = 0 // where you at
var matchesSimpleLen = 0
// very basic fuzzy match; to remove non-matching targets ASAP!
// walk through target. find sequential matches.
// if all chars aren't found then exit
for(;;) {
var isMatch = searchLowerCode === targetLowerCodes[targetI]
if(isMatch) {
matchesSimple[matchesSimpleLen++] = targetI
++searchI; if(searchI === searchLen) break
searchLowerCode = searchLowerCodes[searchI]
}
++targetI; if(targetI >= targetLen) return NULL // Failed to find searchI
}
var searchI = 0
var successStrict = false
var matchesStrictLen = 0
var nextBeginningIndexes = prepared._nextBeginningIndexes
if(nextBeginningIndexes === NULL) nextBeginningIndexes = prepared._nextBeginningIndexes = prepareNextBeginningIndexes(prepared.target)
targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1]
// Our target string successfully matched all characters in sequence!
// Let's try a more advanced and strict test to improve the score
// only count it as a match if it's consecutive or a beginning character!
var backtrackCount = 0
if(targetI !== targetLen) for(;;) {
if(targetI >= targetLen) {
// We failed to find a good spot for this search char, go back to the previous search char and force it forward
if(searchI <= 0) break // We failed to push chars forward for a better match
++backtrackCount; if(backtrackCount > 200) break // exponential backtracking is taking too long, just give up and return a bad match
--searchI
var lastMatch = matchesStrict[--matchesStrictLen]
targetI = nextBeginningIndexes[lastMatch]
} else {
var isMatch = searchLowerCodes[searchI] === targetLowerCodes[targetI]
if(isMatch) {
matchesStrict[matchesStrictLen++] = targetI
++searchI; if(searchI === searchLen) { successStrict = true; break }
++targetI
} else {
targetI = nextBeginningIndexes[targetI]
}
}
}
// check if it's a substring match
var substringIndex = searchLen <= 1 ? -1 : prepared._targetLower.indexOf(searchLower, matchesSimple[0]) // perf: this is slow
var isSubstring = !!~substringIndex
var isSubstringBeginning = !isSubstring ? false : substringIndex===0 || prepared._nextBeginningIndexes[substringIndex-1] === substringIndex
// if it's a substring match but not at a beginning index, let's try to find a substring starting at a beginning index for a better score
if(isSubstring && !isSubstringBeginning) {
for(var i=0; i<nextBeginningIndexes.length; i=nextBeginningIndexes[i]) {
if(i <= substringIndex) continue
for(var s=0; s<searchLen; s++) if(searchLowerCodes[s] !== prepared._targetLowerCodes[i+s]) break
if(s === searchLen) { substringIndex = i; isSubstringBeginning = true; break }
}
}
// tally up the score & keep track of matches for highlighting later
// if it's a simple match, we'll switch to a substring match if a substring exists
// if it's a strict match, we'll switch to a substring match only if that's a better score
var calculateScore = matches => {
var score = 0
var extraMatchGroupCount = 0
for(var i = 1; i < searchLen; ++i) {
if(matches[i] - matches[i-1] !== 1) {score -= matches[i]; ++extraMatchGroupCount}
}
var unmatchedDistance = matches[searchLen-1] - matches[0] - (searchLen-1)
score -= (12+unmatchedDistance) * extraMatchGroupCount // penality for more groups
if(matches[0] !== 0) score -= matches[0]*matches[0]*.2 // penality for not starting near the beginning
if(!successStrict) {
score *= 1000
} else {
// successStrict on a target with too many beginning indexes loses points for being a bad target
var uniqueBeginningIndexes = 1
for(var i = nextBeginningIndexes[0]; i < targetLen; i=nextBeginningIndexes[i]) ++uniqueBeginningIndexes
if(uniqueBeginningIndexes > 24) score *= (uniqueBeginningIndexes-24)*10 // quite arbitrary numbers here ...
}
score -= (targetLen - searchLen)/2 // penality for longer targets
if(isSubstring) score /= 1+searchLen*searchLen*1 // bonus for being a full substring
if(isSubstringBeginning) score /= 1+searchLen*searchLen*1 // bonus for substring starting on a beginningIndex
score -= (targetLen - searchLen)/2 // penality for longer targets
return score
}
if(!successStrict) {
if(isSubstring) for(var i=0; i<searchLen; ++i) matchesSimple[i] = substringIndex+i // at this point it's safe to overwrite matchehsSimple with substr matches
var matchesBest = matchesSimple
var score = calculateScore(matchesBest)
} else {
if(isSubstringBeginning) {
for(var i=0; i<searchLen; ++i) matchesSimple[i] = substringIndex+i // at this point it's safe to overwrite matchehsSimple with substr matches
var matchesBest = matchesSimple
var score = calculateScore(matchesSimple)
} else {
var matchesBest = matchesStrict
var score = calculateScore(matchesStrict)
}
}
prepared._score = score
for(var i = 0; i < searchLen; ++i) prepared._indexes[i] = matchesBest[i]
prepared._indexes.len = searchLen
const result = new Result()
result.target = prepared.target
result._score = prepared._score
result._indexes = prepared._indexes
return result
}
var algorithmSpaces = (preparedSearch, target, allowPartialMatch) => {
var seen_indexes = new Set()
var score = 0
var result = NULL
var first_seen_index_last_search = 0
var searches = preparedSearch.spaceSearches
var searchesLen = searches.length
var changeslen = 0
// Return _nextBeginningIndexes back to its normal state
var resetNextBeginningIndexes = () => {
for(let i=changeslen-1; i>=0; i--) target._nextBeginningIndexes[nextBeginningIndexesChanges[i*2 + 0]] = nextBeginningIndexesChanges[i*2 + 1]
}
var hasAtLeast1Match = false
for(var i=0; i<searchesLen; ++i) {
allowPartialMatchScores[i] = NEGATIVE_INFINITY
var search = searches[i]
result = algorithm(search, target)
if(allowPartialMatch) {
if(result === NULL) continue
hasAtLeast1Match = true
} else {
if(result === NULL) {resetNextBeginningIndexes(); return NULL}
}
// if not the last search, we need to mutate _nextBeginningIndexes for the next search
var isTheLastSearch = i === searchesLen - 1
if(!isTheLastSearch) {
var indexes = result._indexes
var indexesIsConsecutiveSubstring = true
for(let i=0; i<indexes.len-1; i++) {
if(indexes[i+1] - indexes[i] !== 1) {
indexesIsConsecutiveSubstring = false; break;
}
}
if(indexesIsConsecutiveSubstring) {
var newBeginningIndex = indexes[indexes.len-1] + 1
var toReplace = target._nextBeginningIndexes[newBeginningIndex-1]
for(let i=newBeginningIndex-1; i>=0; i--) {
if(toReplace !== target._nextBeginningIndexes[i]) break
target._nextBeginningIndexes[i] = newBeginningIndex
nextBeginningIndexesChanges[changeslen*2 + 0] = i
nextBeginningIndexesChanges[changeslen*2 + 1] = toReplace
changeslen++
}
}
}
score += result._score / searchesLen
allowPartialMatchScores[i] = result._score / searchesLen
// dock points based on order otherwise "c man" returns Manifest.cpp instead of CheatManager.h
if(result._indexes[0] < first_seen_index_last_search) {
score -= (first_seen_index_last_search - result._indexes[0]) * 2
}
first_seen_index_last_search = result._indexes[0]
for(var j=0; j<result._indexes.len; ++j) seen_indexes.add(result._indexes[j])
}
if(allowPartialMatch && !hasAtLeast1Match) return NULL
resetNextBeginningIndexes()
// allows a search with spaces that's an exact substring to score well
var allowSpacesResult = algorithm(preparedSearch, target, /*allowSpaces=*/true)
if(allowSpacesResult !== NULL && allowSpacesResult._score > score) {
if(allowPartialMatch) {
for(var i=0; i<searchesLen; ++i) {
allowPartialMatchScores[i] = allowSpacesResult._score / searchesLen
}
}
return allowSpacesResult
}
if(allowPartialMatch) result = target
result._score = score
var i = 0
for (let index of seen_indexes) result._indexes[i++] = index
result._indexes.len = i
return result
}
// we use this instead of just .normalize('NFD').replace(/[\u0300-\u036f]/g, '') because that screws with japanese characters
var remove_accents = (str) => str.replace(/\p{Script=Latin}+/gu, match => match.normalize('NFD')).replace(/[\u0300-\u036f]/g, '')
var prepareLowerInfo = (str) => {
str = remove_accents(str)
var strLen = str.length
var lower = str.toLowerCase()
var lowerCodes = [] // new Array(strLen) sparse array is too slow
var bitflags = 0
var containsSpace = false // space isn't stored in bitflags because of how searching with a space works
for(var i = 0; i < strLen; ++i) {
var lowerCode = lowerCodes[i] = lower.charCodeAt(i)
if(lowerCode === 32) {
containsSpace = true
continue // it's important that we don't set any bitflags for space
}
var bit = lowerCode>=97&&lowerCode<=122 ? lowerCode-97 // alphabet
: lowerCode>=48&&lowerCode<=57 ? 26 // numbers
// 3 bits available
: lowerCode<=127 ? 30 // other ascii
: 31 // other utf8
bitflags |= 1<<bit
}
return {lowerCodes:lowerCodes, bitflags:bitflags, containsSpace:containsSpace, _lower:lower}
}
var prepareBeginningIndexes = (target) => {
var targetLen = target.length
var beginningIndexes = []; var beginningIndexesLen = 0
var wasUpper = false
var wasAlphanum = false
for(var i = 0; i < targetLen; ++i) {
var targetCode = target.charCodeAt(i)
var isUpper = targetCode>=65&&targetCode<=90
var isAlphanum = isUpper || targetCode>=97&&targetCode<=122 || targetCode>=48&&targetCode<=57
var isBeginning = isUpper && !wasUpper || !wasAlphanum || !isAlphanum
wasUpper = isUpper
wasAlphanum = isAlphanum
if(isBeginning) beginningIndexes[beginningIndexesLen++] = i
}
return beginningIndexes
}
var prepareNextBeginningIndexes = (target) => {
target = remove_accents(target)
var targetLen = target.length
var beginningIndexes = prepareBeginningIndexes(target)
var nextBeginningIndexes = [] // new Array(targetLen) sparse array is too slow
var lastIsBeginning = beginningIndexes[0]
var lastIsBeginningI = 0
for(var i = 0; i < targetLen; ++i) {
if(lastIsBeginning > i) {
nextBeginningIndexes[i] = lastIsBeginning
} else {
lastIsBeginning = beginningIndexes[++lastIsBeginningI]
nextBeginningIndexes[i] = lastIsBeginning===undefined ? targetLen : lastIsBeginning
}
}
return nextBeginningIndexes
}
var preparedCache = new Map()
var preparedSearchCache = new Map()
// the theory behind these being globals is to reduce garbage collection by not making new arrays
var matchesSimple = []; var matchesStrict = []
var nextBeginningIndexesChanges = [] // allows straw berry to match strawberry well, by modifying the end of a substring to be considered a beginning index for the rest of the search
var keysSpacesBestScores = []; var allowPartialMatchScores = []
var tmpTargets = []; var tmpResults = []
// prop = 'key' 2.5ms optimized for this case, seems to be about as fast as direct obj[prop]
// prop = 'key1.key2' 10ms
// prop = ['key1', 'key2'] 27ms
// prop = obj => obj.tags.join() ??ms
var getValue = (obj, prop) => {
var tmp = obj[prop]; if(tmp !== undefined) return tmp
if(typeof prop === 'function') return prop(obj) // this should run first. but that makes string props slower
var segs = prop
if(!Array.isArray(prop)) segs = prop.split('.')
var len = segs.length
var i = -1
while (obj && (++i < len)) obj = obj[segs[i]]
return obj
}
var isPrepared = (x) => { return typeof x === 'object' && typeof x._bitflags === 'number' }
var INFINITY = Infinity; var NEGATIVE_INFINITY = -INFINITY
var noResults = []; noResults.total = 0
var NULL = null
var noTarget = prepare('')
// Hacked version of https://github.com/lemire/FastPriorityQueue.js
var fastpriorityqueue=r=>{var e=[],o=0,a={},v=r=>{for(var a=0,v=e[a],c=1;c<o;){var s=c+1;a=c,s<o&&e[s]._score<e[c]._score&&(a=s),e[a-1>>1]=e[a],c=1+(a<<1)}for(var f=a-1>>1;a>0&&v._score<e[f]._score;f=(a=f)-1>>1)e[a]=e[f];e[a]=v};return a.add=(r=>{var a=o;e[o++]=r;for(var v=a-1>>1;a>0&&r._score<e[v]._score;v=(a=v)-1>>1)e[a]=e[v];e[a]=r}),a.poll=(r=>{if(0!==o){var a=e[0];return e[0]=e[--o],v(),a}}),a.peek=(r=>{if(0!==o)return e[0]}),a.replaceTop=(r=>{e[0]=r,v()}),a}
var q = fastpriorityqueue() // reuse this
@@ -0,0 +1,141 @@
// Original code from https://github.com/koeqaife/hyprland-material-you
// Original code license: GPLv3
// Translated to Js from Cython with an LLM and reviewed
function min3(a, b, c) {
return a < b && a < c ? a : b < c ? b : c;
}
function max3(a, b, c) {
return a > b && a > c ? a : b > c ? b : c;
}
function min2(a, b) {
return a < b ? a : b;
}
function max2(a, b) {
return a > b ? a : b;
}
function levenshteinDistance(s1, s2) {
let len1 = s1.length;
let len2 = s2.length;
if (len1 === 0) return len2;
if (len2 === 0) return len1;
if (len2 > len1) {
[s1, s2] = [s2, s1];
[len1, len2] = [len2, len1];
}
let prev = new Array(len2 + 1);
let curr = new Array(len2 + 1);
for (let j = 0; j <= len2; j++) {
prev[j] = j;
}
for (let i = 1; i <= len1; i++) {
curr[0] = i;
for (let j = 1; j <= len2; j++) {
let cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
curr[j] = min3(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
}
[prev, curr] = [curr, prev];
}
return prev[len2];
}
function partialRatio(shortS, longS) {
let lenS = shortS.length;
let lenL = longS.length;
let best = 0.0;
if (lenS === 0) return 1.0;
for (let i = 0; i <= lenL - lenS; i++) {
let sub = longS.slice(i, i + lenS);
let dist = levenshteinDistance(shortS, sub);
let score = 1.0 - (dist / lenS);
if (score > best) best = score;
}
return best;
}
function computeScore(s1, s2) {
if (s1 === s2) return 1.0;
let dist = levenshteinDistance(s1, s2);
let maxLen = max2(s1.length, s2.length);
if (maxLen === 0) return 1.0;
let full = 1.0 - (dist / maxLen);
let part = s1.length < s2.length ? partialRatio(s1, s2) : partialRatio(s2, s1);
let score = 0.85 * full + 0.15 * part;
if (s1 && s2 && s1[0] !== s2[0]) {
score -= 0.05;
}
let lenDiff = Math.abs(s1.length - s2.length);
if (lenDiff >= 3) {
score -= 0.05 * lenDiff / maxLen;
}
let commonPrefixLen = 0;
let minLen = min2(s1.length, s2.length);
for (let i = 0; i < minLen; i++) {
if (s1[i] === s2[i]) {
commonPrefixLen++;
} else {
break;
}
}
score += 0.02 * commonPrefixLen;
if (s1.includes(s2) || s2.includes(s1)) {
score += 0.06;
}
return Math.max(0.0, Math.min(1.0, score));
}
function computeTextMatchScore(s1, s2) {
if (s1 === s2) return 1.0;
let dist = levenshteinDistance(s1, s2);
let maxLen = max2(s1.length, s2.length);
if (maxLen === 0) return 1.0;
let full = 1.0 - (dist / maxLen);
let part = s1.length < s2.length ? partialRatio(s1, s2) : partialRatio(s2, s1);
let score = 0.4 * full + 0.6 * part;
let lenDiff = Math.abs(s1.length - s2.length);
if (lenDiff >= 10) {
score -= 0.02 * lenDiff / maxLen;
}
let commonPrefixLen = 0;
let minLen = min2(s1.length, s2.length);
for (let i = 0; i < minLen; i++) {
if (s1[i] === s2[i]) {
commonPrefixLen++;
} else {
break;
}
}
score += 0.01 * commonPrefixLen;
if (s1.includes(s2) || s2.includes(s1)) {
score += 0.2;
}
return Math.max(0.0, Math.min(1.0, score));
}
@@ -0,0 +1,91 @@
function toPlainObject(qtObj) {
if (qtObj === null || typeof qtObj !== "object") return qtObj;
// Handle true arrays
if (Array.isArray(qtObj)) {
return qtObj.map(item => toPlainObject(item));
}
// Handle array-like Qt objects (e.g., have length and numeric keys)
if (
typeof qtObj.length === "number" &&
qtObj.length > 0 &&
Object.keys(qtObj).every(
key => !isNaN(key) || key === "length"
)
) {
let arr = [];
for (let i = 0; i < qtObj.length; i++) {
arr.push(toPlainObject(qtObj[i]));
}
return arr;
}
const result = ({});
for (let key in qtObj) {
if (
typeof qtObj[key] !== "function" &&
!key.startsWith("objectName") &&
!key.startsWith("children") &&
!key.startsWith("object") &&
!key.startsWith("parent") &&
!key.startsWith("metaObject") &&
!key.startsWith("destroyed") &&
!key.startsWith("reloadableId")
) {
result[key] = toPlainObject(qtObj[key]);
}
}
// console.log(JSON.stringify(result))
return result;
}
function applyToQtObject(qtObj, jsonObj) {
// console.log("applyToQtObject", JSON.stringify(qtObj, null, 2), "<<", JSON.stringify(jsonObj, null, 2));
if (!qtObj || typeof jsonObj !== "object" || jsonObj === null) return;
// Detect array-like Qt objects
const isQtArrayLike = obj => {
return obj && typeof obj === "object" &&
typeof obj.length === "number" &&
obj.length > 0 &&
Object.keys(obj).every(key => !isNaN(key) || key === "length");
};
// If both are arrays or array-like, update in place or replace
if ((Array.isArray(qtObj) || isQtArrayLike(qtObj)) && Array.isArray(jsonObj)) {
qtObj.length = 0;
for (let i = 0; i < jsonObj.length; i++) {
qtObj.push(jsonObj[i]);
}
return;
}
// If target is array or array-like but source is not, clear
if ((Array.isArray(qtObj) || isQtArrayLike(qtObj)) && !Array.isArray(jsonObj)) {
qtObj.length = 0;
return;
}
// If source is array but target is not, assign directly if possible
if (!(Array.isArray(qtObj) || isQtArrayLike(qtObj)) && Array.isArray(jsonObj)) {
return jsonObj;
}
for (let key in jsonObj) {
if (!qtObj.hasOwnProperty(key)) continue;
const value = qtObj[key];
const jsonValue = jsonObj[key];
// console.log("applying to qt obj key:", value, "jsonValue:", jsonValue);
if ((Array.isArray(value) || isQtArrayLike(value)) && Array.isArray(jsonValue)) {
value.length = 0;
for (let i = 0; i < jsonValue.length; i++) {
value.push(jsonValue[i]);
}
} else if (value && typeof value === "object" && !Array.isArray(value) && !isQtArrayLike(value)) {
applyToQtObject(value, jsonValue);
} else {
qtObj[key] = jsonValue;
}
}
}
@@ -0,0 +1,188 @@
/**
* Formats a string according to the args that are passed in
* @param { string } str
* @param {...any} args
* @returns
*/
function format(str, ...args) {
return str.replace(/{(\d+)}/g, (match, index) =>
typeof args[index] !== 'undefined' ? args[index] : match
);
}
/**
* Returns the domain of the passed in url or null
* @param { string } url
* @returns { string| null }
*/
function getDomain(url) {
const match = url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/);
return match ? match[1] : null;
}
/**
* Returns the base url of the passed in url or null
* @param { string } url
* @returns { string | null }
*/
function getBaseUrl(url) {
const match = url.match(/^(https?:\/\/[^\/]+)(\/.*)?$/);
return match ? match[1] : null;
}
/**
* Escapes single quotes in shell commands
* @param { string } str
* @returns { string }
*/
function shellSingleQuoteEscape(str) {
// escape single quotes
return String(str)
// .replace(/\\/g, '\\\\')
.replace(/'/g, "'\\''");
}
/**
* Splits markdown blocks into three different types: text, think, and code.
* @param { string } markdown
*/
function splitMarkdownBlocks(markdown) {
const regex = /```(\w+)?\n([\s\S]*?)```|<think>([\s\S]*?)<\/think>/g;
/**
* @type {{type: "text" | "think" | "code"; content: string; lang: string | undefined; completed: boolean | undefined}[]}
*/
let result = [];
let lastIndex = 0;
let match;
while ((match = regex.exec(markdown)) !== null) {
if (match.index > lastIndex) {
const text = markdown.slice(lastIndex, match.index);
if (text.trim()) {
result.push({ type: "text", content: text });
}
}
if (match[0].startsWith('```')) {
if (match[2] && match[2].trim()) {
result.push({ type: "code", lang: match[1] || "", content: match[2], completed: true });
}
} else if (match[0].startsWith('<think>')) {
if (match[3] && match[3].trim()) {
result.push({ type: "think", content: match[3], completed: true });
}
}
lastIndex = regex.lastIndex;
}
// Handle any remaining text after the last match
if (lastIndex < markdown.length) {
const text = markdown.slice(lastIndex);
// Check for unfinished <think> block
const thinkStart = text.indexOf('<think>');
const codeStart = text.indexOf('```');
if (
thinkStart !== -1 &&
(codeStart === -1 || thinkStart < codeStart)
) {
const beforeThink = text.slice(0, thinkStart);
if (beforeThink.trim()) {
result.push({ type: "text", content: beforeThink });
}
const thinkContent = text.slice(thinkStart + 7);
if (thinkContent.trim()) {
result.push({ type: "think", content: thinkContent, completed: false });
}
} else if (codeStart !== -1) {
const beforeCode = text.slice(0, codeStart);
if (beforeCode.trim()) {
result.push({ type: "text", content: beforeCode });
}
// Try to detect language after ```
const codeLangMatch = text.slice(codeStart + 3).match(/^(\w+)?\n/);
let lang = "";
let codeContentStart = codeStart + 3;
if (codeLangMatch) {
lang = codeLangMatch[1] || "";
codeContentStart += codeLangMatch[0].length;
} else if (text[codeStart + 3] === '\n') {
codeContentStart += 1;
}
const codeContent = text.slice(codeContentStart);
if (codeContent.trim()) {
result.push({ type: "code", lang, content: codeContent, completed: false });
}
} else if (text.trim()) {
result.push({ type: "text", content: text });
}
}
// console.log(JSON.stringify(result, null, 2));
return result;
}
/**
* Returns the original string with backslashes escaped
* @param { string } str
* @returns { string }
*/
function escapeBackslashes(str) {
return str.replace(/\\/g, '\\\\');
}
/**
* Wraps words to supplied maximum length
* @param { string | null } str
* @param { number } maxLen
* @returns { string }
*/
function wordWrap(str, maxLen) {
if (!str) return "";
let words = str.split(" ");
let lines = [];
let current = "";
for (let i = 0; i < words.length; ++i) {
if ((current + (current.length > 0 ? " " : "") + words[i]).length > maxLen) {
if (current.length > 0) lines.push(current);
current = words[i];
} else {
current += (current.length > 0 ? " " : "") + words[i];
}
}
if (current.length > 0) lines.push(current);
return lines.join("\n");
}
function cleanMusicTitle(title) {
if (!title) return "";
// Brackets
title = title.replace(/^ *\([^)]*\) */g, " "); // Round brackets
title = title.replace(/^ *\[[^\]]*\] */g, " "); // Square brackets
title = title.replace(/^ *\{[^\}]*\} */g, " "); // Curly brackets
// Japenis brackets
title = title.replace(/^ *【[^】]*】/, "") // Touhou
title = title.replace(/^ *《[^》]*》/, "") // ??
title = title.replace(/^ *「[^」]*」/, "") // OP/ED thingie
title = title.replace(/^ *『[^』]*』/, "") // OP/ED thingie
return title.trim();
}
function friendlyTimeForSeconds(seconds) {
if (isNaN(seconds) || seconds < 0) return "0:00";
seconds = Math.floor(seconds);
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
if (h > 0) {
return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
} else {
return `${m}:${s.toString().padStart(2, '0')}`;
}
}
function escapeHtml(str) {
if (typeof str !== 'string') return str;
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
@@ -0,0 +1,48 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
/**
* A container that supports GroupButton children for bounciness.
* See https://m3.material.io/components/button-groups/overview
*/
Rectangle {
id: root
default property alias data: rowLayout.data
property real spacing: 5
property real padding: 0
property int clickIndex: rowLayout.clickIndex
property real contentWidth: {
let total = 0;
for (let i = 0; i < rowLayout.children.length; ++i) {
const child = rowLayout.children[i];
if (!child.visible) continue;
total += child.baseWidth ?? child.implicitWidth ?? child.width;
}
return total + rowLayout.spacing * (rowLayout.children.length - 1);
}
topLeftRadius: rowLayout.children.length > 0 ? (rowLayout.children[0].radius + padding) :
Appearance?.rounding?.small
bottomLeftRadius: topLeftRadius
topRightRadius: rowLayout.children.length > 0 ? (rowLayout.children[rowLayout.children.length - 1].radius + padding) :
Appearance?.rounding?.small
bottomRightRadius: topRightRadius
color: "transparent"
width: root.contentWidth + padding * 2
implicitHeight: rowLayout.implicitHeight + padding * 2
implicitWidth: root.contentWidth + padding * 2
children: [RowLayout {
id: rowLayout
anchors.fill: parent
anchors.margins: root.padding
spacing: root.spacing
property int clickIndex: -1
}]
}
@@ -0,0 +1,95 @@
// From https://github.com/rafzby/circular-progressbar with modifications
// License: LGPL-3.0 - A copy can be found in `licenses` folder of repo
import QtQuick
import "root:/modules/common"
/**
* Material 3 circular progress. See https://m3.material.io/components/progress-indicators/specs
*/
Item {
id: root
property int size: 30
property int lineWidth: 2
property real value: 0
property color primaryColor: Appearance.m3colors.m3onSecondaryContainer
property color secondaryColor: Appearance.colors.colSecondaryContainer
property real gapAngle: Math.PI / 9
property bool fill: false
property int fillOverflow: 2
property int animationDuration: 1000
property var easingType: Easing.OutCubic
width: size
height: size
signal animationFinished();
onValueChanged: {
canvas.degree = value * 360;
}
onPrimaryColorChanged: {
canvas.requestPaint();
}
onSecondaryColorChanged: {
canvas.requestPaint();
}
Canvas {
id: canvas
property real degree: 0
anchors.fill: parent
antialiasing: true
onDegreeChanged: {
requestPaint();
}
onPaint: {
var ctx = getContext("2d");
var x = root.width / 2;
var y = root.height / 2;
var radius = root.size / 2 - root.lineWidth;
var startAngle = (Math.PI / 180) * 270;
var fullAngle = (Math.PI / 180) * (270 + 360);
var progressAngle = (Math.PI / 180) * (270 + degree);
var epsilon = 0.01; // Small angle in radians
ctx.reset();
if (root.fill) {
ctx.fillStyle = root.secondaryColor;
ctx.beginPath();
ctx.arc(x, y, radius + fillOverflow, startAngle, fullAngle);
ctx.fill();
}
ctx.lineCap = 'round';
ctx.lineWidth = root.lineWidth;
// Secondary
ctx.beginPath();
ctx.arc(x, y, radius, progressAngle + gapAngle, fullAngle - gapAngle);
ctx.strokeStyle = root.secondaryColor;
ctx.stroke();
// Primary (value indication)
var endAngle = progressAngle + (value > 0 ? 0 : epsilon);
ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle);
ctx.strokeStyle = root.primaryColor;
ctx.stroke();
}
Behavior on degree {
NumberAnimation {
duration: root.animationDuration
easing.type: root.easingType
}
}
}
}
@@ -0,0 +1,102 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/file_utils.js" as FileUtils
import Qt5Compat.GraphicalEffects
import Qt.labs.platform
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Hyprland
Rectangle {
id: root
property string entry
property real maxWidth
property real maxHeight
property string imageDecodePath: Directories.cliphistDecode
property string imageDecodeFileName: `${entryNumber}`
property string imageDecodeFilePath: `${imageDecodePath}/${imageDecodeFileName}`
property string source
property int entryNumber: {
if (!root.entry) return 0
const match = root.entry.match(/^(\d+)\t/)
return match ? parseInt(match[1]) : 0
}
property int imageWidth: {
if (!root.entry) return 0
const match = root.entry.match(/(\d+)x(\d+)/)
return match ? parseInt(match[1]) : 0
}
property int imageHeight: {
if (!root.entry) return 0
const match = root.entry.match(/(\d+)x(\d+)/)
return match ? parseInt(match[2]) : 0
}
property real scale: {
return Math.min(
root.maxWidth / imageWidth,
root.maxHeight / imageHeight,
1
)
}
color: Appearance.colors.colLayer1
radius: Appearance.rounding.small
implicitHeight: imageHeight * scale
implicitWidth: imageWidth * scale
Component.onCompleted: {
decodeImageProcess.running = true
}
Process {
id: decodeImageProcess
command: ["bash", "-c",
`[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(root.entry)}' | cliphist decode > '${imageDecodeFilePath}'`
]
onExited: (exitCode, exitStatus) => {
if (exitCode === 0) {
root.source = imageDecodeFilePath
} else {
console.error("[CliphistImage] Failed to decode image for entry:", root.entry)
root.source = ""
}
}
}
Component.onDestruction: {
Quickshell.execDetached(["bash", "-c", `[ -f '${imageDecodeFilePath}' ] && rm -f '${imageDecodeFilePath}'`])
}
Image {
id: image
anchors.fill: parent
source: Qt.resolvedUrl(root.source)
fillMode: Image.PreserveAspectFit
antialiasing: true
asynchronous: true
width: root.imageWidth * root.scale
height: root.imageHeight * root.scale
sourceSize.width: width
sourceSize.height: height
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: image.width
height: image.height
radius: root.radius
}
}
}
}
@@ -0,0 +1,8 @@
import QtQuick
import QtQuick.Layouts
RowLayout {
property bool uniform: false
spacing: 10
uniformCellSizes: uniform
}
@@ -0,0 +1,48 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import "root:/services/"
import "root:/modules/common/"
import "root:/modules/common/widgets/"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import "root:/modules/common/functions/file_utils.js" as FileUtils
Flow {
id: root
Layout.fillWidth: true
spacing: 2
property list<var> options: []
property string configOptionName: ""
property var currentValue: null
signal selected(var newValue)
Repeater {
model: root.options
delegate: SelectionGroupButton {
id: paletteButton
required property var modelData
required property int index
onYChanged: {
if (index === 0) {
paletteButton.leftmost = true
} else {
var prev = root.children[index - 1]
var thisIsOnNewLine = prev && prev.y !== paletteButton.y
paletteButton.leftmost = thisIsOnNewLine
prev.rightmost = thisIsOnNewLine
}
}
leftmost: index === 0
rightmost: index === root.options.length - 1
buttonText: modelData.displayName;
toggled: root.currentValue === modelData.value
onClicked: {
root.selected(modelData.value);
}
}
}
}
@@ -0,0 +1,31 @@
import "root:/modules/common/widgets/"
import "root:/modules/common/"
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
RowLayout {
id: root
property string text: ""
property alias value: spinBoxWidget.value
property alias stepSize: spinBoxWidget.stepSize
property alias from: spinBoxWidget.from
property alias to: spinBoxWidget.to
spacing: 10
Layout.leftMargin: 8
Layout.rightMargin: 8
StyledText {
id: labelWidget
Layout.fillWidth: true
text: root.text
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colOnSecondaryContainer
}
StyledSpinBox {
id: spinBoxWidget
Layout.fillWidth: false
value: root.value
}
}
@@ -0,0 +1,32 @@
import "root:/modules/common/widgets/"
import "root:/modules/common/"
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
RippleButton {
id: root
Layout.fillWidth: true
implicitHeight: contentItem.implicitHeight + 8 * 2
onClicked: checked = !checked
contentItem: RowLayout {
spacing: 10
StyledText {
id: labelWidget
Layout.fillWidth: true
text: root.text
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colOnSecondaryContainer
}
StyledSwitch {
id: switchWidget
down: root.down
scale: 0.6
Layout.fillWidth: false
checked: root.checked
onClicked: root.clicked()
}
}
}
@@ -0,0 +1,29 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/modules/common/"
import "root:/modules/common/widgets/"
Flickable {
id: root
property real baseWidth: 550
property bool forceWidth: false
property real bottomContentPadding: 100
default property alias data: contentColumn.data
clip: true
contentHeight: contentColumn.implicitHeight + root.bottomContentPadding // Add some padding at the bottom
implicitWidth: contentColumn.implicitWidth
ColumnLayout {
id: contentColumn
width: root.forceWidth ? root.baseWidth : Math.max(root.baseWidth, implicitWidth)
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
margins: 10
}
spacing: 20
}
}
@@ -0,0 +1,23 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/modules/common/"
import "root:/modules/common/widgets/"
ColumnLayout {
id: root
property string title
default property alias data: sectionContent.data
Layout.fillWidth: true
spacing: 8
StyledText {
text: root.title
font.pixelSize: Appearance.font.pixelSize.larger
font.weight: Font.Medium
}
ColumnLayout {
id: sectionContent
spacing: 8
}
}
@@ -0,0 +1,47 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/modules/common/"
import "root:/modules/common/widgets/"
ColumnLayout {
id: root
property string title: ""
property string tooltip: ""
default property alias data: sectionContent.data
Layout.fillWidth: true
Layout.topMargin: 4
spacing: 2
RowLayout {
ContentSubsectionLabel {
visible: root.title && root.title.length > 0
text: root.title
}
MaterialSymbol {
visible: root.tooltip && root.tooltip.length > 0
text: "info"
iconSize: Appearance.font.pixelSize.large
color: Appearance.colors.colSubtext
MouseArea {
id: infoMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.WhatsThisCursor
StyledToolTip {
extraVisibleCondition: false
alternativeVisibleCondition: infoMouseArea.containsMouse
content: root.tooltip
}
}
}
Item { Layout.fillWidth: true }
}
ColumnLayout {
id: sectionContent
Layout.fillWidth: true
spacing: 2
}
}
@@ -0,0 +1,10 @@
import QtQuick
import QtQuick.Layouts
import "root:/modules/common/"
import "root:/modules/common/widgets/"
StyledText {
text: "Subsection"
color: Appearance.colors.colSubtext
Layout.leftMargin: 4
}
@@ -0,0 +1,36 @@
import QtQuick
import Quickshell
import Quickshell.Widgets
import Qt5Compat.GraphicalEffects
Item {
id: root
property bool colorize: false
property color color
property string source: ""
property string iconFolder: "root:/assets/icons" // The folder to check first
width: 30
height: 30
IconImage {
id: iconImage
anchors.fill: parent
source: {
if (iconFolder && iconFolder + "/" + root.source) {
return iconFolder + "/" + root.source
}
return root.source
}
implicitSize: root.height
}
Loader {
active: root.colorize
anchors.fill: iconImage
sourceComponent: ColorOverlay {
source: iconImage
color: root.color
}
}
}
@@ -0,0 +1,38 @@
import "root:/modules/common"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
/**
* Material 3 dialog button. See https://m3.material.io/components/dialogs/overview
*/
RippleButton {
id: button
property string buttonText
implicitHeight: 30
implicitWidth: buttonTextWidget.implicitWidth + 15 * 2
buttonRadius: Appearance?.rounding.full ?? 9999
property color colEnabled: Appearance?.colors.colPrimary ?? "#65558F"
property color colDisabled: Appearance?.m3colors.m3outline ?? "#8D8C96"
contentItem: StyledText {
id: buttonTextWidget
anchors.fill: parent
anchors.leftMargin: 15
anchors.rightMargin: 15
text: buttonText
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Appearance?.font.pixelSize.small ?? 12
color: button.enabled ? button.colEnabled : button.colDisabled
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
}
@@ -0,0 +1,74 @@
import "root:/modules/common"
import "root:/services"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
/**
* A convenience MouseArea for handling drag events.
*/
MouseArea {
id: root
hoverEnabled: true
acceptedButtons: Qt.LeftButton
property bool interactive: true
property bool automaticallyReset: true
readonly property real dragDiffX: _dragDiffX
readonly property real dragDiffY: _dragDiffY
signal dragPressed(diffX: real, diffY: real)
signal dragReleased(diffX: real, diffY: real)
property real startX: 0
property real startY: 0
property bool dragging: false
property real _dragDiffX: 0
property real _dragDiffY: 0
function resetDrag() {
_dragDiffX = 0
_dragDiffY = 0
}
onPressed: (mouse) => {
if (!root.interactive) {
if (mouse.button === Qt.LeftButton) {
mouse.accepted = false;
}
return;
}
if (mouse.button === Qt.LeftButton) {
startX = mouse.x
startY = mouse.y
}
}
onReleased: (mouse) => {
if (!root.interactive) {
return;
}
dragging = false
root.dragReleased(_dragDiffX, _dragDiffY);
if (root.automaticallyReset) {
root.resetDrag();
}
}
onPositionChanged: (mouse) => {
if (!root.interactive) {
return;
}
if (mouse.buttons & Qt.LeftButton) {
root._dragDiffX = mouse.x - startX
root._dragDiffY = mouse.y - startY
const dist = Math.sqrt(root._dragDiffX * root._dragDiffX + root._dragDiffY * root._dragDiffY);
root.dragPressed(_dragDiffX, _dragDiffY);
root.dragging = true;
}
}
onCanceled: (mouse) => {
if (!root.interactive) {
return;
}
released(mouse);
}
}
@@ -0,0 +1,53 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/file_utils.js" as FileUtils
import Qt5Compat.GraphicalEffects
import Qt.labs.platform
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Hyprland
IconImage {
id: root
property string url
property string displayText
property real size: 32
property string downloadUserAgent: Config.options?.networking.userAgent ?? ""
property string faviconDownloadPath: Directories.favicons
property string domainName: url.includes("vertexaisearch") ? displayText : StringUtils.getDomain(url)
property string faviconUrl: `https://www.google.com/s2/favicons?domain=${domainName}&sz=32`
property string fileName: `${domainName}.ico`
property string faviconFilePath: `${faviconDownloadPath}/${fileName}`
property string urlToLoad
Process {
id: faviconDownloadProcess
running: false
command: ["bash", "-c", `[ -f ${faviconFilePath} ] || curl -s '${root.faviconUrl}' -o '${faviconFilePath}' -L -H 'User-Agent: ${downloadUserAgent}'`]
onExited: (exitCode, exitStatus) => {
root.urlToLoad = root.faviconFilePath
}
}
Component.onCompleted: {
faviconDownloadProcess.running = true
}
source: Qt.resolvedUrl(root.urlToLoad)
implicitSize: root.size
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: root.implicitSize
height: root.implicitSize
radius: Appearance.rounding.full
}
}
}
@@ -0,0 +1,60 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/modules/common/"
import "root:/modules/common/widgets/"
/**
* Material 3 FAB.
*/
RippleButton {
id: root
property string iconText: "add"
property bool expanded: false
property real baseSize: 56
property real elementSpacing: 5
implicitWidth: Math.max(contentRowLayout.implicitWidth + 10 * 2, baseSize)
implicitHeight: baseSize
buttonRadius: Appearance.rounding.small
colBackground: Appearance.colors.colPrimaryContainer
colBackgroundHover: Appearance.colors.colPrimaryContainerHover
colRipple: Appearance.colors.colPrimaryContainerActive
contentItem: RowLayout {
id: contentRowLayout
property real horizontalMargins: (root.baseSize - icon.width) / 2
anchors {
verticalCenter: parent?.verticalCenter
left: parent?.left
leftMargin: contentRowLayout.horizontalMargins
}
spacing: 0
MaterialSymbol {
id: icon
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
iconSize: 24
color: Appearance.colors.colOnPrimaryContainer
text: root.iconText
}
Loader {
active: true
sourceComponent: Revealer {
visible: root.expanded || implicitWidth > 0
reveal: root.expanded
implicitWidth: reveal ? (buttonText.implicitWidth + root.elementSpacing + contentRowLayout.horizontalMargins) : 0
StyledText {
id: buttonText
anchors {
left: parent.left
leftMargin: root.elementSpacing
}
text: root.buttonText
color: Appearance.colors.colOnPrimaryContainer
font.pixelSize: 14
font.weight: 450
}
}
}
}
}
@@ -0,0 +1,8 @@
import QtQuick
/**
* This is just to make sure `RippleButton`s can be used in a Flow layout.
*/
Flow {
property int clickIndex: -1
}
@@ -0,0 +1,123 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
import Quickshell.Widgets
/**
* Material 3 button with expressive bounciness.
* See https://m3.material.io/components/button-groups/overview
*/
Button {
id: root
property bool toggled
property string buttonText
property real buttonRadius: Appearance?.rounding?.small ?? 8
property real buttonRadiusPressed: Appearance?.rounding?.small ?? 6
property var downAction // When left clicking (down)
property var releaseAction // When left clicking (release)
property var altAction // When right clicking
property var middleClickAction // When middle clicking
property bool bounce: true
property real baseWidth: contentItem.implicitWidth + horizontalPadding * 2
property real baseHeight: contentItem.implicitHeight + verticalPadding * 2
property real clickedWidth: baseWidth + 20
property real clickedHeight: baseHeight
property var parentGroup: root.parent
property int clickIndex: parentGroup?.clickIndex ?? -1
Layout.fillWidth: (clickIndex - 1 <= parentGroup.children.indexOf(root) && parentGroup.children.indexOf(root) <= clickIndex + 1)
Layout.fillHeight: (clickIndex - 1 <= parentGroup.children.indexOf(root) && parentGroup.children.indexOf(root) <= clickIndex + 1)
implicitWidth: (root.down && bounce) ? clickedWidth : baseWidth
implicitHeight: (root.down && bounce) ? clickedHeight : baseHeight
property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent"
property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED"
property color colBackgroundActive: Appearance?.colors.colLayer1Active ?? "#D6CEE2"
property color colBackgroundToggled: Appearance?.colors.colPrimary ?? "#65558F"
property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C"
property color colBackgroundToggledActive: Appearance?.colors.colPrimaryActive ?? "#D6CEE2"
property real radius: root.down ? root.buttonRadiusPressed : root.buttonRadius
property real leftRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius
property real rightRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius
property color color: root.enabled ? (root.toggled ?
(root.down ? colBackgroundToggledActive :
root.hovered ? colBackgroundToggledHover :
colBackgroundToggled) :
(root.down ? colBackgroundActive :
root.hovered ? colBackgroundHover :
colBackground)) : colBackground
onDownChanged: {
if (root.down) {
if (root.parent.clickIndex !== undefined) {
root.parent.clickIndex = parent.children.indexOf(root)
}
}
}
Behavior on implicitWidth {
animation: Appearance.animation.clickBounce.numberAnimation.createObject(this)
}
Behavior on implicitHeight {
animation: Appearance.animation.clickBounce.numberAnimation.createObject(this)
}
Behavior on leftRadius {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on rightRadius {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: (event) => {
if(event.button === Qt.RightButton) {
if (root.altAction) root.altAction();
return;
}
if(event.button === Qt.MiddleButton) {
if (root.middleClickAction) root.middleClickAction();
return;
}
root.down = true
if (root.downAction) root.downAction();
}
onReleased: (event) => {
root.down = false
if (event.button != Qt.LeftButton) return;
if (root.releaseAction) root.releaseAction();
root.click() // Because the MouseArea already consumed the event
}
onCanceled: (event) => {
root.down = false
}
}
background: Rectangle {
id: buttonBackground
topLeftRadius: root.leftRadius
topRightRadius: root.rightRadius
bottomLeftRadius: root.leftRadius
bottomRightRadius: root.rightRadius
implicitHeight: 50
color: root.color
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
contentItem: StyledText {
text: root.buttonText
}
}
@@ -0,0 +1,46 @@
import "root:/modules/common"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
Rectangle {
id: root
property string key
property real horizontalPadding: 6
property real verticalPadding: 1
property real borderWidth: 1
property real extraBottomBorderWidth: 2
property color borderColor: Appearance.colors.colOnLayer0
property real borderRadius: 5
property color keyColor: Appearance.m3colors.m3surfaceContainerLow
implicitWidth: keyFace.implicitWidth + borderWidth * 2
implicitHeight: keyFace.implicitHeight + borderWidth * 2 + extraBottomBorderWidth
radius: borderRadius
color: borderColor
Rectangle {
id: keyFace
anchors {
fill: parent
topMargin: borderWidth
leftMargin: borderWidth
rightMargin: borderWidth
bottomMargin: extraBottomBorderWidth + borderWidth
}
implicitWidth: keyText.implicitWidth + horizontalPadding * 2
implicitHeight: keyText.implicitHeight + verticalPadding * 2
color: keyColor
radius: borderRadius - borderWidth
StyledText {
id: keyText
anchors.centerIn: parent
font.family: Appearance.font.family.monospace
font.pixelSize: Appearance.font.pixelSize.smaller
text: key
}
}
}
@@ -0,0 +1,124 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
GroupButton {
id: lightDarkButtonRoot
required property bool dark
property color previewBg: dark ? ColorUtils.colorWithHueOf("#3f3838", Appearance.m3colors.m3primary) :
ColorUtils.colorWithHueOf("#F7F9FF", Appearance.m3colors.m3primary)
property color previewFg: dark ? Qt.lighter(previewBg, 2.2) : ColorUtils.mix(previewBg, "#292929", 0.85)
padding: 5
Layout.fillWidth: true
colBackground: Appearance.colors.colLayer2
toggled: Appearance.m3colors.darkmode === dark
onClicked: {
Quickshell.execDetached(["bash", "-c", `${Directories.wallpaperSwitchScriptPath} --mode ${dark ? "dark" : "light"} --noswitch`])
}
contentItem: Item {
anchors.centerIn: parent
implicitWidth: buttonContentLayout.implicitWidth
implicitHeight: buttonContentLayout.implicitHeight
ColumnLayout {
id: buttonContentLayout
anchors.centerIn: parent
Rectangle {
Layout.alignment: Qt.AlignHCenter
implicitWidth: 250
implicitHeight: skeletonColumnLayout.implicitHeight + 10 * 2
radius: lightDarkButtonRoot.buttonRadius - lightDarkButtonRoot.padding
color: lightDarkButtonRoot.previewBg
border {
width: 1
color: Appearance.m3colors.m3outlineVariant
}
// Some skeleton items
ColumnLayout {
id: skeletonColumnLayout
anchors.fill: parent
anchors.margins: 10
spacing: 10
RowLayout {
Rectangle {
radius: Appearance.rounding.full
color: lightDarkButtonRoot.previewFg
implicitWidth: 50
implicitHeight: 50
}
ColumnLayout {
spacing: 4
Rectangle {
radius: Appearance.rounding.unsharpenmore
color: lightDarkButtonRoot.previewFg
Layout.fillWidth: true
implicitHeight: 22
}
Rectangle {
radius: Appearance.rounding.unsharpenmore
color: lightDarkButtonRoot.previewFg
Layout.fillWidth: true
Layout.rightMargin: 45
implicitHeight: 18
}
}
}
StyledProgressBar {
Layout.topMargin: 5
Layout.bottomMargin: 5
Layout.fillWidth: true
value: 0.7
sperm: true
animateSperm: lightDarkButtonRoot.toggled
highlightColor: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3primary : lightDarkButtonRoot.previewFg
trackColor: ColorUtils.mix(lightDarkButtonRoot.previewBg, lightDarkButtonRoot.previewFg, 0.5)
}
RowLayout {
spacing: 2
Rectangle {
radius: Appearance.rounding.full
color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3primary : lightDarkButtonRoot.previewFg
Layout.fillWidth: true
implicitHeight: 30
MaterialSymbol {
visible: lightDarkButtonRoot.toggled
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: "check"
iconSize: 20
color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3onPrimary : lightDarkButtonRoot.previewBg
}
}
Rectangle {
radius: Appearance.rounding.unsharpenmore
color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3secondaryContainer : lightDarkButtonRoot.previewFg
Layout.fillWidth: true
implicitHeight: 30
}
Rectangle {
topLeftRadius: Appearance.rounding.unsharpenmore
bottomLeftRadius: Appearance.rounding.unsharpenmore
topRightRadius: Appearance.rounding.full
bottomRightRadius: Appearance.rounding.full
color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3secondaryContainer : lightDarkButtonRoot.previewFg
Layout.fillWidth: true
implicitHeight: 30
}
}
}
}
StyledText {
Layout.fillWidth: true
text: dark ? "Dark" : "Light"
color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2
horizontalAlignment: Text.AlignHCenter
}
}
}
}
@@ -0,0 +1,33 @@
import "root:/modules/common/"
import QtQuick
import QtQuick.Layouts
Text {
id: root
property real iconSize: Appearance?.font.pixelSize.small ?? 16
property real fill: 0
property real truncatedFill: Math.round(fill * 100) / 100 // Reduce memory consumption spikes from constant font remapping
renderType: Text.NativeRendering
font {
hintingPreference: Font.PreferFullHinting
family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded"
pixelSize: iconSize
weight: Font.Normal + (Font.DemiBold - Font.Normal) * fill
variableAxes: {
"FILL": truncatedFill,
// "wght": font.weight,
// "GRAD": 0,
"opsz": iconSize,
}
}
verticalAlignment: Text.AlignVCenter
color: Appearance.m3colors.m3onBackground
// Behavior on fill {
// NumberAnimation {
// duration: Appearance?.animation.elementMoveFast.duration ?? 200
// easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline
// easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]
// }
// }
}
@@ -0,0 +1,53 @@
import "root:/modules/common"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Controls
/**
* Material 3 styled TextArea (filled style)
* https://m3.material.io/components/text-fields/overview
* Note: We don't use NativeRendering because it makes the small placeholder text look weird
*/
TextArea {
id: root
Material.theme: Material.System
Material.accent: Appearance.m3colors.m3primary
Material.primary: Appearance.m3colors.m3primary
Material.background: Appearance.m3colors.m3surface
Material.foreground: Appearance.m3colors.m3onSurface
Material.containerStyle: Material.Filled
renderType: Text.QtRendering
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.colors.colSecondaryContainer
placeholderTextColor: Appearance.m3colors.m3outline
background: Rectangle {
implicitHeight: 56
color: Appearance.m3colors.m3surface
topLeftRadius: 4
topRightRadius: 4
Rectangle {
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
height: 1
color: root.focus ? Appearance.m3colors.m3primary :
root.hovered ? Appearance.m3colors.m3outline : Appearance.m3colors.m3outlineVariant
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
}
font {
family: Appearance?.font.family.main ?? "sans-serif"
pixelSize: Appearance?.font.pixelSize.small ?? 15
hintingPreference: Font.PreferFullHinting
}
wrapMode: TextEdit.Wrap
}
@@ -0,0 +1,31 @@
import "root:/modules/common"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
RippleButton {
id: root
buttonRadius: 0
implicitHeight: 36
implicitWidth: buttonTextWidget.implicitWidth + 14 * 2
contentItem: StyledText {
id: buttonTextWidget
anchors.fill: parent
anchors.leftMargin: 14
anchors.rightMargin: 14
text: root.buttonText
horizontalAlignment: Text.AlignLeft
font.pixelSize: Appearance.font.pixelSize.small
color: root.enabled ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3outline
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
}
@@ -0,0 +1,12 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/modules/common/"
import "root:/modules/common/widgets/"
ColumnLayout { // Window content with navigation rail and content pane
id: root
property bool expanded: true
property int currentIndex: 0
spacing: 5
}
@@ -0,0 +1,149 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
TabButton {
id: root
property bool toggled: TabBar.tabBar.currentIndex === TabBar.index
property string buttonIcon
property string buttonText
property bool expanded: false
property bool showToggledHighlight: true
readonly property real visualWidth: root.expanded ? root.baseSize + 20 + itemText.implicitWidth : root.baseSize
property real baseSize: 56
property real baseHighlightHeight: 32
property real highlightCollapsedTopMargin: 8
padding: 0
// The navigation items target area always spans the full width of the
// nav rail, even if the item container hugs its contents.
Layout.fillWidth: true
// implicitWidth: contentItem.implicitWidth
implicitHeight: baseSize
background: null
PointingHandInteraction {}
// Real stuff
contentItem: Item {
id: buttonContent
anchors {
top: parent.top
bottom: parent.bottom
left: parent.left
right: undefined
}
implicitWidth: root.visualWidth
implicitHeight: root.expanded ? itemIconBackground.implicitHeight : itemIconBackground.implicitHeight + itemText.implicitHeight
Rectangle {
id: itemBackground
anchors.top: itemIconBackground.top
anchors.left: itemIconBackground.left
anchors.bottom: itemIconBackground.bottom
implicitWidth: root.visualWidth
radius: Appearance.rounding.full
color: toggled ?
root.showToggledHighlight ?
(root.down ? Appearance.colors.colSecondaryContainerActive : root.hovered ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer)
: ColorUtils.transparentize(Appearance.colors.colSecondaryContainer) :
(root.down ? Appearance.colors.colLayer1Active : root.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1))
states: State {
name: "expanded"
when: root.expanded
AnchorChanges {
target: itemBackground
anchors.top: buttonContent.top
anchors.left: buttonContent.left
anchors.bottom: buttonContent.bottom
}
PropertyChanges {
target: itemBackground
implicitWidth: root.visualWidth
}
}
transitions: Transition {
AnchorAnimation {
duration: Appearance.animation.elementMoveFast.duration
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
PropertyAnimation {
target: itemBackground
property: "implicitWidth"
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
}
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
Item {
id: itemIconBackground
implicitWidth: root.baseSize
implicitHeight: root.baseHighlightHeight
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
}
MaterialSymbol {
id: navRailButtonIcon
anchors.centerIn: parent
iconSize: 24
fill: toggled ? 1 : 0
font.weight: (toggled || root.hovered) ? Font.DemiBold : Font.Normal
text: buttonIcon
color: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
}
StyledText {
id: itemText
anchors {
top: itemIconBackground.bottom
topMargin: 2
horizontalCenter: itemIconBackground.horizontalCenter
}
states: State {
name: "expanded"
when: root.expanded
AnchorChanges {
target: itemText
anchors {
top: undefined
horizontalCenter: undefined
left: itemIconBackground.right
verticalCenter: itemIconBackground.verticalCenter
}
}
}
transitions: Transition {
AnchorAnimation {
duration: Appearance.animation.elementMoveFast.duration
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
}
text: buttonText
font.pixelSize: 14
color: Appearance.colors.colOnLayer1
}
}
}
@@ -0,0 +1,31 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/modules/common/"
import "root:/modules/common/widgets/"
RippleButton {
id: root
Layout.alignment: Qt.AlignLeft
implicitWidth: 40
implicitHeight: 40
Layout.leftMargin: 8
onClicked: {
parent.expanded = !parent.expanded;
}
buttonRadius: Appearance.rounding.full
rotation: root.parent.expanded ? 0 : -180
Behavior on rotation {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
contentItem: MaterialSymbol {
id: icon
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: 24
color: Appearance.colors.colOnLayer1
text: root.parent.expanded ? "menu_open" : "menu"
}
}
@@ -0,0 +1,44 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
Item {
id: root
property int currentIndex: 0
property bool expanded: false
default property alias data: tabBarColumn.data
implicitHeight: tabBarColumn.implicitHeight
implicitWidth: tabBarColumn.implicitWidth
Layout.topMargin: 25
Rectangle {
property real itemHeight: tabBarColumn.children[0].baseSize
property real baseHighlightHeight: tabBarColumn.children[0].baseHighlightHeight
anchors {
top: tabBarColumn.top
left: tabBarColumn.left
topMargin: itemHeight * root.currentIndex + (root.expanded ? 0 : ((itemHeight - baseHighlightHeight) / 2))
}
radius: Appearance.rounding.full
color: Appearance.colors.colSecondaryContainer
implicitHeight: root.expanded ? itemHeight : baseHighlightHeight
implicitWidth: tabBarColumn.children[root.currentIndex].visualWidth
Behavior on anchors.topMargin {
NumberAnimation {
duration: Appearance.animationCurves.expressiveFastSpatialDuration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
}
}
ColumnLayout {
id: tabBarColumn
anchors.fill: parent
spacing: 0
}
}
@@ -0,0 +1,27 @@
import "root:/modules/common"
import "root:/services"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Notifications
RippleButton {
id: button
property string buttonText
property string urgency
implicitHeight: 30
leftPadding: 15
rightPadding: 15
buttonRadius: Appearance.rounding.small
colBackground: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainer : Appearance.colors.colSurfaceContainerHighest
colBackgroundHover: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSurfaceContainerHighestHover
colRipple: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colSurfaceContainerHighestActive
contentItem: StyledText {
horizontalAlignment: Text.AlignHCenter
text: buttonText
color: (urgency == NotificationUrgency.Critical) ? Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface
}
}
@@ -0,0 +1,105 @@
import "root:/modules/common"
import "./notification_utils.js" as NotificationUtils
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Services.Notifications
Rectangle { // App icon
id: root
property var appIcon: ""
property var summary: ""
property var urgency: NotificationUrgency.Normal
property var image: ""
property real scale: 1
property real size: 45 * scale
property real materialIconScale: 0.57
property real appIconScale: 0.7
property real smallAppIconScale: 0.49
property real materialIconSize: size * materialIconScale
property real appIconSize: size * appIconScale
property real smallAppIconSize: size * smallAppIconScale
implicitWidth: size
implicitHeight: size
radius: Appearance.rounding.full
color: Appearance.colors.colSecondaryContainer
Loader {
id: materialSymbolLoader
active: root.appIcon == ""
anchors.fill: parent
sourceComponent: MaterialSymbol {
text: {
const defaultIcon = NotificationUtils.findSuitableMaterialSymbol("")
const guessedIcon = NotificationUtils.findSuitableMaterialSymbol(root.summary)
return (root.urgency == NotificationUrgency.Critical && guessedIcon === defaultIcon) ?
"release_alert" : guessedIcon
}
anchors.fill: parent
color: (root.urgency == NotificationUrgency.Critical) ?
ColorUtils.mix(Appearance.m3colors.m3onSecondary, Appearance.m3colors.m3onSecondaryContainer, 0.1) :
Appearance.m3colors.m3onSecondaryContainer
iconSize: root.materialIconSize
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
Loader {
id: appIconLoader
active: root.image == "" && root.appIcon != ""
anchors.centerIn: parent
sourceComponent: IconImage {
id: appIconImage
implicitSize: root.appIconSize
asynchronous: true
source: Quickshell.iconPath(root.appIcon, "image-missing")
}
}
Loader {
id: notifImageLoader
active: root.image != ""
anchors.fill: parent
sourceComponent: Item {
anchors.fill: parent
Image {
id: notifImage
anchors.fill: parent
readonly property int size: parent.width
source: root.image
fillMode: Image.PreserveAspectCrop
cache: false
antialiasing: true
asynchronous: true
width: size
height: size
sourceSize.width: size
sourceSize.height: size
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: notifImage.size
height: notifImage.size
radius: Appearance.rounding.full
}
}
}
Loader {
id: notifImageAppIconLoader
active: root.appIcon != ""
anchors.bottom: parent.bottom
anchors.right: parent.right
sourceComponent: IconImage {
implicitSize: root.smallAppIconSize
asynchronous: true
source: Quickshell.iconPath(root.appIcon, "image-missing")
}
}
}
}
}
@@ -0,0 +1,244 @@
import "root:/modules/common"
import "root:/services"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import "./notification_utils.js" as NotificationUtils
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Hyprland
import Quickshell.Services.Notifications
/**
* A group of notifications from the same app.
* Similar to Android's notifications
*/
Item { // Notification group area
id: root
property var notificationGroup
property var notifications: notificationGroup?.notifications ?? []
property int notificationCount: notifications.length
property bool multipleNotifications: notificationCount > 1
property bool expanded: false
property bool popup: false
property real padding: 10
implicitHeight: background.implicitHeight
property real dragConfirmThreshold: 70 // Drag further to discard notification
property real dismissOvershoot: 20 // Account for gaps and bouncy animations
property var qmlParent: root.parent.parent // There's something between this and the parent ListView
property var parentDragIndex: qmlParent.dragIndex
property var parentDragDistance: qmlParent.dragDistance
property var dragIndexDiff: Math.abs(parentDragIndex - index)
property real xOffset: dragIndexDiff == 0 ? Math.max(0, parentDragDistance) :
parentDragDistance > dragConfirmThreshold ? 0 :
dragIndexDiff == 1 ? Math.max(0, parentDragDistance * 0.3) :
dragIndexDiff == 2 ? Math.max(0, parentDragDistance * 0.1) : 0
function destroyWithAnimation() {
root.qmlParent.resetDrag()
background.anchors.leftMargin = background.anchors.leftMargin; // Break binding
destroyAnimation.running = true;
}
SequentialAnimation { // Drag finish animation
id: destroyAnimation
running: false
NumberAnimation {
target: background.anchors
property: "leftMargin"
to: root.width + root.dismissOvershoot
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
onFinished: () => {
root.notifications.forEach((notif) => {
Qt.callLater(() => {
Notifications.discardNotification(notif.id);
});
});
}
}
function toggleExpanded() {
if (expanded) implicitHeightAnim.enabled = true;
else implicitHeightAnim.enabled = false;
root.expanded = !root.expanded;
}
DragManager { // Drag manager
id: dragManager
anchors.fill: parent
interactive: !expanded
automaticallyReset: false
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: (mouse) => {
if (mouse.button === Qt.RightButton)
root.toggleExpanded();
else if (mouse.button === Qt.MiddleButton)
root.destroyWithAnimation();
}
onDraggingChanged: () => {
if (dragging) {
root.qmlParent.dragIndex = root.index ?? root.parent.children.indexOf(root);
}
}
onDragDiffXChanged: () => {
root.qmlParent.dragDistance = dragDiffX;
}
onDragReleased: (diffX, diffY) => {
if (diffX > root.dragConfirmThreshold)
root.destroyWithAnimation();
else
dragManager.resetDrag();
}
}
StyledRectangularShadow {
target: background
visible: popup
}
Rectangle { // Background of the notification
id: background
anchors.left: parent.left
width: parent.width
color: Appearance.colors.colSurfaceContainer
radius: Appearance.rounding.normal
anchors.leftMargin: root.xOffset
Behavior on anchors.leftMargin {
enabled: !dragManager.dragging
NumberAnimation {
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
}
clip: true
implicitHeight: expanded ?
row.implicitHeight + padding * 2 :
Math.min(80, row.implicitHeight + padding * 2)
Behavior on implicitHeight {
id: implicitHeightAnim
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
RowLayout { // Left column for icon, right column for content
id: row
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: root.padding
spacing: 10
NotificationAppIcon { // Icons
Layout.alignment: Qt.AlignTop
Layout.fillWidth: false
image: root?.multipleNotifications ? "" : notificationGroup?.notifications[0]?.image ?? ""
appIcon: notificationGroup?.appIcon
summary: notificationGroup?.notifications[root.notificationCount - 1]?.summary
}
ColumnLayout { // Content
Layout.fillWidth: true
spacing: expanded ? (root.multipleNotifications ?
(notificationGroup?.notifications[root.notificationCount - 1].image != "") ? 35 :
5 : 0) : 0
// spacing: 00
Behavior on spacing {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Item { // App name (or summary when there's only 1 notif) and time
id: topRow
// spacing: 0
Layout.fillWidth: true
property real fontSize: Appearance.font.pixelSize.smaller
property bool showAppName: root.multipleNotifications
implicitHeight: Math.max(topTextRow.implicitHeight, expandButton.implicitHeight)
RowLayout {
id: topTextRow
anchors.left: parent.left
anchors.right: expandButton.left
anchors.verticalCenter: parent.verticalCenter
spacing: 5
StyledText {
id: appName
elide: Text.ElideRight
Layout.fillWidth: true
text: (topRow.showAppName ?
notificationGroup?.appName :
notificationGroup?.notifications[0]?.summary) || ""
font.pixelSize: topRow.showAppName ?
topRow.fontSize :
Appearance.font.pixelSize.small
color: topRow.showAppName ?
Appearance.colors.colSubtext :
Appearance.colors.colOnLayer2
}
StyledText {
id: timeText
// Layout.fillWidth: true
Layout.rightMargin: 10
horizontalAlignment: Text.AlignLeft
text: NotificationUtils.getFriendlyNotifTimeString(notificationGroup?.time)
font.pixelSize: topRow.fontSize
color: Appearance.colors.colSubtext
}
}
NotificationGroupExpandButton {
id: expandButton
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
count: root.notificationCount
expanded: root.expanded
fontSize: topRow.fontSize
onClicked: { root.toggleExpanded() }
}
}
StyledListView { // Notification body (expanded)
id: notificationsColumn
implicitHeight: contentHeight
Layout.fillWidth: true
spacing: expanded ? 5 : 3
// clip: true
interactive: false
Behavior on spacing {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
model: ScriptModel {
values: root.expanded ? root.notifications.slice().reverse() :
root.notifications.slice().reverse().slice(0, 2)
}
delegate: NotificationItem {
required property int index
required property var modelData
notificationObject: modelData
expanded: root.expanded
onlyNotification: (root.notificationCount === 1)
opacity: (!root.expanded && index == 1 && root.notificationCount > 2) ? 0.5 : 1
visible: root.expanded || (index < 2)
anchors.left: parent?.left
anchors.right: parent?.right
}
}
}
}
}
}
@@ -0,0 +1,52 @@
import "root:/modules/common"
import "root:/services"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Notifications
RippleButton { // Expand button
id: root
required property int count
required property bool expanded
property real fontSize: Appearance?.font.pixelSize.small ?? 12
property real iconSize: Appearance?.font.pixelSize.normal ?? 16
implicitHeight: fontSize + 4 * 2
implicitWidth: Math.max(contentItem.implicitWidth + 5 * 2, 30)
Layout.alignment: Qt.AlignVCenter
Layout.fillHeight: false
buttonRadius: Appearance.rounding.full
colBackground: ColorUtils.mix(Appearance?.colors.colLayer2, Appearance?.colors.colLayer2Hover, 0.5)
colBackgroundHover: Appearance?.colors.colLayer2Hover ?? "#E5DFED"
colRipple: Appearance?.colors.colLayer2Active ?? "#D6CEE2"
contentItem: Item {
anchors.centerIn: parent
implicitWidth: contentRow.implicitWidth
RowLayout {
id: contentRow
anchors.centerIn: parent
spacing: 3
StyledText {
Layout.leftMargin: 4
visible: root.count > 1
text: root.count
font.pixelSize: root.fontSize
}
MaterialSymbol {
text: "keyboard_arrow_down"
iconSize: root.iconSize
color: Appearance.colors.colOnLayer2
rotation: expanded ? 180 : 0
Behavior on rotation {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
}
}
}
@@ -0,0 +1,318 @@
import "root:/modules/common"
import "root:/services"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import "./notification_utils.js" as NotificationUtils
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Hyprland
import Quickshell.Services.Notifications
Item { // Notification item area
id: root
property var notificationObject
property bool expanded: false
property bool onlyNotification: false
property real fontSize: Appearance.font.pixelSize.small
property real padding: onlyNotification ? 0 : 8
property real dragConfirmThreshold: 70 // Drag further to discard notification
property real dismissOvershoot: notificationIcon.implicitWidth + 20 // Account for gaps and bouncy animations
property var qmlParent: root?.parent?.parent // There's something between this and the parent ListView
property var parentDragIndex: qmlParent?.dragIndex ?? -1
property var parentDragDistance: qmlParent?.dragDistance ?? 0
property var dragIndexDiff: Math.abs(parentDragIndex - index)
property real xOffset: dragIndexDiff == 0 ? Math.max(0, parentDragDistance) :
parentDragDistance > dragConfirmThreshold ? 0 :
dragIndexDiff == 1 ? Math.max(0, parentDragDistance * 0.3) :
dragIndexDiff == 2 ? Math.max(0, parentDragDistance * 0.1) : 0
implicitHeight: background.implicitHeight
function processNotificationBody(body, appName) {
let processedBody = body
// Clean Chromium-based browsers notifications - remove first line
if (appName) {
const lowerApp = appName.toLowerCase()
const chromiumBrowsers = [
"brave", "chrome", "chromium", "vivaldi", "opera", "microsoft edge"
]
if (chromiumBrowsers.some(name => lowerApp.includes(name))) {
const lines = body.split('\n\n')
if (lines.length > 1 && lines[0].startsWith('<a')) {
processedBody = lines.slice(1).join('\n\n')
}
}
}
return processedBody
}
function destroyWithAnimation() {
root.qmlParent.resetDrag()
background.anchors.leftMargin = background.anchors.leftMargin; // Break binding
destroyAnimation.running = true;
}
SequentialAnimation { // Drag finish animation
id: destroyAnimation
running: false
NumberAnimation {
target: background.anchors
property: "leftMargin"
to: root.width + root.dismissOvershoot
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
onFinished: () => {
Notifications.discardNotification(notificationObject.id);
}
}
DragManager { // Drag manager
id: dragManager
anchors.fill: root
anchors.leftMargin: root.expanded ? -notificationIcon.implicitWidth : 0
interactive: expanded
automaticallyReset: false
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
onClicked: (mouse) => {
if (mouse.button === Qt.MiddleButton) {
root.destroyWithAnimation();
}
}
onDraggingChanged: () => {
if (dragging) {
root.qmlParent.dragIndex = root.index ?? root.parent.children.indexOf(root);
}
}
onDragDiffXChanged: () => {
root.qmlParent.dragDistance = dragDiffX;
}
onDragReleased: (diffX, diffY) => {
if (diffX > root.dragConfirmThreshold)
root.destroyWithAnimation();
else
dragManager.resetDrag();
}
}
NotificationAppIcon { // App icon
id: notificationIcon
opacity: (!onlyNotification && notificationObject.image != "" && expanded) ? 1 : 0
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
image: notificationObject.image
anchors.right: background.left
anchors.top: background.top
anchors.rightMargin: 10
}
Rectangle { // Background of notification item
id: background
width: parent.width
anchors.left: parent.left
radius: Appearance.rounding.small
anchors.leftMargin: root.xOffset
Behavior on anchors.leftMargin {
enabled: !dragManager.dragging
NumberAnimation {
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
}
color: (expanded && !onlyNotification) ?
(notificationObject.urgency == NotificationUrgency.Critical) ?
ColorUtils.mix(Appearance.colors.colSecondaryContainer, Appearance.colors.colLayer2, 0.35) :
(Appearance.colors.colSurfaceContainerHigh) :
ColorUtils.transparentize(Appearance.colors.colSurfaceContainerHighest)
implicitHeight: expanded ? (contentColumn.implicitHeight + padding * 2) : summaryRow.implicitHeight
Behavior on implicitHeight {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
ColumnLayout { // Content column
id: contentColumn
anchors.fill: parent
anchors.margins: expanded ? root.padding : 0
spacing: 3
Behavior on anchors.margins {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
RowLayout { // Summary row
id: summaryRow
visible: !root.onlyNotification || !root.expanded
Layout.fillWidth: true
implicitHeight: summaryText.implicitHeight
// Layout.fillWidth: true
StyledText {
id: summaryText
visible: !root.onlyNotification
font.pixelSize: root.fontSize
color: Appearance.colors.colOnLayer2
elide: Text.ElideRight
text: root.notificationObject.summary || ""
}
StyledText {
opacity: !root.expanded ? 1 : 0
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Layout.fillWidth: true
font.pixelSize: root.fontSize
color: Appearance.colors.colSubtext
elide: Text.ElideRight
maximumLineCount: 1
textFormat: Text.StyledText
text: {
return processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\n/g, "<br/>")
}
}
}
ColumnLayout { // Expanded content
Layout.fillWidth: true
opacity: root.expanded ? 1 : 0
visible: opacity > 0
StyledText { // Notification body (expanded)
id: notificationBodyText
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Layout.fillWidth: true
font.pixelSize: root.fontSize
color: Appearance.colors.colSubtext
wrapMode: Text.Wrap
elide: Text.ElideRight
textFormat: Text.RichText
text: {
return `<style>img{max-width:${notificationBodyText.width}px;}</style>` +
`${processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\n/g, "<br/>")}`
}
onLinkActivated: (link) => {
Qt.openUrlExternally(link)
Hyprland.dispatch("global quickshell:sidebarRightClose")
}
PointingHandLinkHover {}
}
Flickable { // Notification actions
id: actionsFlickable
Layout.fillWidth: true
implicitHeight: actionRowLayout.implicitHeight
contentWidth: actionRowLayout.implicitWidth
clip: !onlyNotification
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on height {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on implicitHeight {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
RowLayout {
id: actionRowLayout
Layout.alignment: Qt.AlignBottom
NotificationActionButton {
Layout.fillWidth: true
buttonText: qsTr("Close")
urgency: notificationObject.urgency
implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) :
(contentItem.implicitWidth + leftPadding + rightPadding)
onClicked: {
root.destroyWithAnimation()
}
contentItem: MaterialSymbol {
iconSize: Appearance.font.pixelSize.large
horizontalAlignment: Text.AlignHCenter
color: (notificationObject.urgency == NotificationUrgency.Critical) ?
Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface
text: "close"
}
}
Repeater {
id: actionRepeater
model: notificationObject.actions
NotificationActionButton {
Layout.fillWidth: true
buttonText: modelData.text
urgency: notificationObject.urgency
onClicked: {
Notifications.attemptInvokeAction(notificationObject.id, modelData.identifier);
}
}
}
NotificationActionButton {
Layout.fillWidth: true
urgency: notificationObject.urgency
implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) :
(contentItem.implicitWidth + leftPadding + rightPadding)
onClicked: {
Quickshell.clipboardText = notificationObject.body
copyIcon.text = "inventory"
copyIconTimer.restart()
}
Timer {
id: copyIconTimer
interval: 1500
repeat: false
onTriggered: {
copyIcon.text = "content_copy"
}
}
contentItem: MaterialSymbol {
id: copyIcon
iconSize: Appearance.font.pixelSize.large
horizontalAlignment: Text.AlignHCenter
color: (notificationObject.urgency == NotificationUrgency.Critical) ?
Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface
text: "content_copy"
}
}
}
}
}
}
}
}
@@ -0,0 +1,31 @@
import "root:/"
import "root:/modules/common/"
import "root:/modules/common/widgets"
import "root:/services"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
StyledListView { // Scrollable window
id: root
property bool popup: false
spacing: 3
model: ScriptModel {
values: root.popup ? Notifications.popupAppNameList : Notifications.appNameList
}
delegate: NotificationGroup {
required property int index
required property var modelData
popup: root.popup
anchors.left: parent?.left
anchors.right: parent?.right
notificationGroup: popup ?
Notifications.popupGroupsByAppName[modelData] :
Notifications.groupsByAppName[modelData]
}
}
@@ -0,0 +1,7 @@
import QtQuick
MouseArea {
anchors.fill: parent
onPressed: (mouse) => mouse.accepted = false
cursorShape: Qt.PointingHandCursor
}
@@ -0,0 +1,8 @@
import QtQuick
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton // Only for hover
hoverEnabled: true
cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor
}

Some files were not shown because too many files have changed in this diff Show More