quickshell and hyprland additions

This commit is contained in:
2026-03-15 13:56:00 +02:00
parent c9c27d1554
commit 1ad06b82a6
509 changed files with 68371 additions and 19 deletions

View File

@@ -0,0 +1,122 @@
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import QtQuick.Effects
import QtQuick.Controls
import qs.config
import qs.modules.components
import qs.modules.functions
StyledRect {
id: root
property bool hovered: false
property bool selected: false
required property int parentWidth
width: parentWidth
height: 50
color: {
if (selected || hovered)
return Appearance.m3colors.m3surfaceContainerHigh
else
return Appearance.m3colors.m3surface
}
radius: Metrics.radius(15)
Behavior on color {
PropertyAnimation {
duration: Metrics.chronoDuration(200)
easing.type: Easing.InSine
}
}
ClippingWrapperRectangle {
id: entryIcon
anchors.left: parent.left
anchors.leftMargin: Metrics.margin(10)
anchors.top: parent.top
anchors.topMargin: (parent.height / 2) - (size / 2)
property int size: 25
height: size
width: size
radius: Metrics.radius(1000)
color: "transparent"
child: Image {
source: Quickshell.iconPath(modelData.icon, "application-x-executable")
layer.enabled: true
layer.effect: MultiEffect { // Tint if needed, ngl this looks fucking cool when you use monochrome
saturation: (Config.runtime.appearance.tintIcons || (Config.runtime.appearance.colors.matugenScheme === "scheme-monochrome" && Config.runtime.appearance.colors.autogenerated) || Config.runtime.appearance.colors.scheme.toLowerCase() === "monochrome") ? -1.0 : 1.0
}
}
}
ColumnLayout {
anchors.left: entryIcon.right
anchors.leftMargin: Metrics.margin(10)
anchors.top: parent.top
anchors.topMargin: (parent.height / 2) - (height / 2)
height: 40
spacing: Metrics.spacing(-5)
StyledText {
font.weight: 400
text: modelData.name
font.pixelSize: Metrics.fontSize(14)
color: {
if (root.hovered || root.selected)
return Appearance.m3colors.m3onSurface
else
return Appearance.colors.colOutline
}
Behavior on color {
PropertyAnimation {
duration: Metrics.chronoDuration(200)
easing.type: Easing.InSine
}
}
}
StyledText {
font.weight: 400
text: StringUtils.shortText(modelData.comment, 65) // Limit maximum chars to 65
font.pixelSize: Metrics.fontSize(12)
color: {
if (root.hovered || root.selected)
return Qt.alpha(Appearance.m3colors.m3onSurface, 0.7)
else
return Qt.alpha(Appearance.colors.colOutline, 0.7)
}
Behavior on color {
PropertyAnimation {
duration: Metrics.chronoDuration(200)
easing.type: Easing.InSine
}
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: root.hovered = true
onExited: root.hovered = false
onClicked: {
modelData.execute()
IPCLoader.toggleLauncher()
}
}
}

View File

@@ -0,0 +1,170 @@
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Wayland
import QtQuick
import QtQuick.Layouts
import QtQuick.Effects
import QtQuick.Controls
import qs.modules.components
import qs.modules.functions
import qs.config
import qs.services
PanelWindow {
id: launcherWindow
readonly property bool launcherOpen: Globals.visiblility.launcher
visible: launcherOpen
focusable: true
aboveWindows: true // btw I never knew this was a property (read docs)
color: "transparent"
anchors {
top: true
bottom: true
left: true
right: true
}
exclusionMode: ExclusionMode.Ignore // why this? idk but it works atleast
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
ScrollView {
id: maskId
implicitHeight: DisplayMetrics.scaledHeight(0.623)
implicitWidth: DisplayMetrics.scaledWidth(0.3)
anchors.top: parent.top
anchors.left: parent.left
anchors.leftMargin: (parent.width / 2) - (implicitWidth / 2)
anchors.topMargin: (parent.height / 2) - (implicitHeight / 2)
clip: true
focus: true
Rectangle {
id: launcher
property string currentSearch: ""
property int entryIndex: 0
property list<DesktopEntry> appList: Apps.list
Connections {
target: launcherWindow
function onLauncherOpenChanged() {
if (!launcherWindow.launcherOpen) {
launcher.currentSearch = ""
launcher.entryIndex = 0
launcher.appList = Apps.list
}
}
}
anchors.fill: parent
color: Appearance.m3colors.m3surface
radius: Metrics.radius(21)
StyledRect {
id: searchBox
anchors.top: parent.top
anchors.topMargin: Metrics.margin(10)
color: Appearance.m3colors.m3surfaceContainerLow
width: parent.width - 20
anchors.left: parent.left
anchors.leftMargin: (parent.width / 2) - (width / 2)
height: 45
radius: Metrics.radius(15)
z: 2
focus: true
Keys.onDownPressed: launcher.entryIndex += 1
Keys.onUpPressed: {
if (launcher.entryIndex != 0)
launcher.entryIndex -= 1
}
Keys.onEscapePressed: Globals.visiblility.launcher = false
Keys.onPressed: event => {
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
launcher.appList[launcher.entryIndex].execute()
Globals.visiblility.launcher = false
} else if (event.key === Qt.Key_Backspace) {
launcher.currentSearch = launcher.currentSearch.slice(0, -1)
} else if (" abcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?".includes(event.text.toLowerCase())) {
launcher.currentSearch += event.text
}
launcher.appList = Apps.fuzzyQuery(launcher.currentSearch)
launcher.entryIndex = 0
}
MaterialSymbol {
id: iconText
anchors.left: parent.left
anchors.leftMargin: Metrics.margin(10)
icon: "search"
font.pixelSize: Metrics.fontSize(14)
font.weight: 600
anchors.top: parent.top
anchors.topMargin: (parent.height / 2) - ((font.pixelSize + 5) / 2)
opacity: 0.8
}
StyledText {
id: placeHolderText
anchors.left: iconText.right
anchors.leftMargin: Metrics.margin(10)
color: (launcher.currentSearch != "") ? Appearance.m3colors.m3onSurface : Appearance.colors.colOutline
text: (launcher.currentSearch != "") ? launcher.currentSearch : "Start typing to search ..."
font.pixelSize: Metrics.fontSize(13)
anchors.top: parent.top
anchors.topMargin: (parent.height / 2) - ((font.pixelSize + 5) / 2)
animate: false
opacity: 0.8
}
}
ScrollView {
anchors.top: searchBox.bottom
anchors.topMargin: Metrics.margin(10)
anchors.left: parent.left
anchors.leftMargin: (parent.width / 2) - (width / 2)
width: parent.width - 20
height: parent.height - searchBox.height - 20
ListView {
id: appList
anchors.fill: parent
spacing: Metrics.spacing(10)
anchors.bottomMargin: Metrics.margin(4)
model: launcher.appList
currentIndex: launcher.entryIndex
delegate: AppItem {
required property int index
required property DesktopEntry modelData
selected: index === launcher.entryIndex
parentWidth: appList.width
}
}
}
}
}
IpcHandler {
function toggle() {
Globals.visiblility.launcher = !Globals.visiblility.launcher;
}
target: "launcher"
}
}

View File

@@ -0,0 +1,313 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.config
import qs.modules.functions
import qs.modules.components
import qs.services
/*
This LauncherContent has been depricated.
And yet not used. (4/3/26)
*/
Item {
id: content
property int selectedIndex: -1
property string searchQuery: ""
property var calcVars: ({})
property alias listView: listView
property alias filteredModel: filteredModel
function launchCurrent() {
launchApp(listView.currentIndex)
}
function webSearchUrl(query) {
const engine = (Config.runtime.launcher.webSearchEngine || "").toLowerCase()
if (engine.startsWith("http"))
return engine.replace("%s", encodeURIComponent(query))
const engines = {
"google": "https://www.google.com/search?q=%s",
"duckduckgo": "https://duckduckgo.com/?q=%s",
"brave": "https://search.brave.com/search?q=%s",
"bing": "https://www.bing.com/search?q=%s",
"startpage": "https://www.startpage.com/search?q=%s"
}
const template = engines[engine] || engines["duckduckgo"]
return template.replace("%s", encodeURIComponent(query))
}
function moveSelection(delta) {
if (filteredModel.count === 0) return
selectedIndex = Math.max(0, Math.min(selectedIndex + delta, filteredModel.count - 1))
listView.currentIndex = selectedIndex
listView.positionViewAtIndex(selectedIndex, ListView.Contain)
}
function fuzzyMatch(text, pattern) {
text = text.toLowerCase()
pattern = pattern.toLowerCase()
let ti = 0, pi = 0
while (ti < text.length && pi < pattern.length) {
if (text[ti] === pattern[pi]) pi++
ti++
}
return pi === pattern.length
}
function evalExpression(expr) {
try {
const fn = new Function("vars", `
with (vars) { with (Math) { return (${expr}); } }
`)
const res = fn(calcVars)
if (res === undefined || Number.isNaN(res)) return null
return res
} catch (e) {
return null
}
}
function updateFilter() {
filteredModel.clear()
const query = searchQuery.toLowerCase().trim()
const calcVal = evalExpression(query)
if (calcVal !== null && query !== "") {
filteredModel.append({
name: String(calcVal),
displayName: String(calcVal),
comment: "Calculation",
icon: "",
exec: "",
isCalc: true,
isWeb: false
})
}
const sourceApps = AppRegistry.apps
if (query === "") {
for (let app of sourceApps) {
filteredModel.append({
name: app.name,
displayName: app.name,
comment: app.comment,
icon: AppRegistry.iconForDesktopIcon(app.icon),
exec: app.exec,
isCalc: false,
isWeb: false
})
}
selectedIndex = filteredModel.count > 0 ? 0 : -1
listView.currentIndex = selectedIndex
return
}
let exactMatches = []
let startsWithMatches = []
let containsMatches = []
let fuzzyMatches = []
for (let app of sourceApps) {
const name = app.name ? app.name.toLowerCase() : ""
const comment = app.comment ? app.comment.toLowerCase() : ""
if (name === query) exactMatches.push(app)
else if (name.startsWith(query)) startsWithMatches.push(app)
else if (name.includes(query) || comment.includes(query)) containsMatches.push(app)
else if (Config.runtime.launcher.fuzzySearchEnabled && fuzzyMatch(name, query)) fuzzyMatches.push(app)
}
const sortedResults = [
...exactMatches,
...startsWithMatches,
...containsMatches,
...fuzzyMatches
]
for (let app of sortedResults) {
filteredModel.append({
name: app.name,
displayName: app.name,
comment: app.comment,
icon: AppRegistry.iconForDesktopIcon(app.icon),
exec: app.exec,
isCalc: false,
isWeb: false
})
}
if (filteredModel.count === 0 && query !== "") {
filteredModel.append({
name: query,
displayName: "Search the web for \"" + query + "\"",
comment: "Web search",
icon: "public",
exec: webSearchUrl(query),
isCalc: false,
isWeb: true
})
}
selectedIndex = filteredModel.count > 0 ? 0 : -1
listView.currentIndex = selectedIndex
listView.positionViewAtBeginning()
}
function launchApp(idx) {
if (idx < 0 || idx >= filteredModel.count) return
const app = filteredModel.get(idx)
if (app.isCalc) return
if (app.isWeb)
Quickshell.execDetached(["xdg-open", app.exec])
else
Quickshell.execDetached(["bash", "-c", app.exec + " &"])
closeLauncher()
}
function closeLauncher() {
Globals.visiblility.launcher = false
}
function resetSearch() {
searchQuery = ""
updateFilter()
selectedIndex = -1
listView.currentIndex = -1
}
Connections {
target: AppRegistry
function onReady() {
updateFilter()
}
}
anchors.fill: parent
opacity: Globals.visiblility.launcher ? 1 : 0
anchors.margins: Metrics.margin(10)
ListModel { id: filteredModel }
ColumnLayout {
anchors.fill: parent
anchors.margins: Metrics.margin(16)
spacing: Metrics.spacing(12)
ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
ListView {
id: listView
model: filteredModel
spacing: Metrics.spacing(8)
clip: true
boundsBehavior: Flickable.StopAtBounds
highlightRangeMode: ListView.StrictlyEnforceRange
preferredHighlightBegin: 0
preferredHighlightEnd: height
highlightMoveDuration: 120
currentIndex: selectedIndex
delegate: Rectangle {
property bool isSelected: listView.currentIndex === index
width: listView.width
height: 60
radius: Appearance.rounding.normal
color: isSelected ? Appearance.m3colors.m3surfaceContainerHighest : "transparent"
Row {
anchors.fill: parent
anchors.margins: Metrics.margin(10)
spacing: Metrics.spacing(12)
Item {
width: 32
height: 32
Image {
anchors.fill: parent
visible: !model.isCalc && !model.isWeb
smooth: true
mipmap: true
antialiasing: true
fillMode: Image.PreserveAspectFit
sourceSize.width: 128
sourceSize.height: 128
source: model.icon
}
MaterialSymbol {
anchors.centerIn: parent
visible: model.isCalc
icon: "calculate"
iconSize: Metrics.iconSize(28)
color: Appearance.m3colors.m3onSurfaceVariant
}
MaterialSymbol {
anchors.centerIn: parent
visible: model.isWeb
icon: "public"
iconSize: Metrics.iconSize(28)
color: Appearance.m3colors.m3onSurfaceVariant
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: listView.width - 120
spacing: Metrics.spacing(4)
Text {
text: model.displayName
font.pixelSize: Metrics.fontSize(14)
font.bold: true
elide: Text.ElideRight
color: Appearance.m3colors.m3onSurface
}
Text {
text: model.comment
font.pixelSize: Metrics.fontSize(11)
elide: Text.ElideRight
color: Appearance.m3colors.m3onSurfaceVariant
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: launchApp(index)
onEntered: listView.currentIndex = index
}
}
}
}
}
Behavior on opacity {
enabled: Config.runtime.appearance.animations.enabled
NumberAnimation {
duration: Metrics.chronoDuration(400)
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animation.curves.standard
}
}
}