quickshell: init config

This commit is contained in:
Bloxx12 2025-06-26 20:46:50 +02:00
commit 5e9c4e8e4c
Signed by: faukah
SSH key fingerprint: SHA256:Uj2AXqvtdCA4hn5Hq0ZonhIAyUqI1q4w2sMG3Z1TH7E
20 changed files with 1650 additions and 0 deletions

View file

@ -0,0 +1,103 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import QtQuick.Layouts
import Quickshell.Wayland
import Quickshell.Services.Pipewire
Singleton {
id: audioPopup
property bool popupOpen: true
property var volume: sink.ready ? audioPopup.sink.audio.volume : 0
property var visible: volume
property PwNode sink: Pipewire.defaultAudioSink
// bind the node so we can read its properties
PwObjectTracker {
objects: [audioPopup.sink]
}
Timer {
id: timer
interval: 3000
running: false
repeat: false
onTriggered: audioPopup.visible = false
}
LazyLoader {
id: loader
activeAsync: audioPopup.popupOpen
PanelWindow {
id: popup
width: 400
height: 30
visible: true
// Give the window an empty click mask so all clicks pass through it.
mask: Region {}
// Use the wlroots specific layer property to ensure it displays over
// fullscreen windows.
WlrLayershell.layer: WlrLayer.Overlay
color: "transparent"
anchors {
bottom: true
}
margins {
bottom: 250
}
Rectangle {
id: rect
Layout.fillWidth: true
anchors.verticalCenter: parent.verticalCenter
color: "white"
height: parent.height
width: parent.width
radius: 5
opacity: 0
anchors {
left: parent.left
}
Behavior on width {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
Rectangle {
color: "black"
height: 20
radius: height / 2
anchors {
left: parent.left
}
topLeftRadius: 0
bottomLeftRadius: 0
anchors.verticalCenter: parent.verticalCenter
width: parent.width * audioPopup.sink.audio.volume
}
}
}
}
function init() {
}
}

View file

@ -0,0 +1,46 @@
import QtQuick
import Quickshell
Rectangle {
width: text.width
implicitHeight: text.height
// border.color: "black"
border.width: 2
radius: 0
color: "transparent"
Behavior on implicitHeight {
NumberAnimation {
duration: 100
easing.type: Easing.OutCubic
}
}
Item {
width: parent.width
height: text.height
anchors.centerIn: parent
SystemClock {
id: clock
precision: SystemClock.Seconds
}
Text {
id: text
anchors.centerIn: parent
property var date: Date()
text: Qt.formatDateTime(clock.date, "hh mm")
font.family: "ComicShannsMono Nerd Font Mono"
font.weight: Font.ExtraBold
font.pointSize: 12
color: "black"
}
}
}

View file

@ -0,0 +1,310 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import "./config"
Singleton {
id: launcher
property bool launcherOpen: false
IpcHandler {
target: "launcher"
function open(): void {
launcher.launcherOpen = true;
}
function close(): void {
launcher.launcherOpen = false;
}
function toggle(): void {
launcher.launcherOpen = !launcher.launcherOpen;
}
}
LazyLoader {
id: loader
activeAsync: launcher.launcherOpen
PanelWindow {
implicitWidth: 450
implicitHeight: 7 + searchContainer.implicitHeight + list.topMargin * 2 + list.delegateHeight * 10
color: "transparent"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
Rectangle {
height: 7 + searchContainer.implicitHeight + list.topMargin + list.bottomMargin + Math.min(list.contentHeight, list.delegateHeight * 10)
Behavior on height {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
width: 450
color: Config.catppuccin.base
radius: 5
border.color: Config.catppuccin.mantle
border.width: 2
ColumnLayout {
anchors.fill: parent
anchors.margins: 7
anchors.bottomMargin: 0
spacing: 0
Rectangle {
id: searchContainer
Layout.fillWidth: true
implicitHeight: searchbox.implicitHeight + 10
color: Config.catppuccin.base
radius: 3
border.color: Config.catppuccin.mantle
RowLayout {
id: searchbox
anchors.fill: parent
anchors.margins: 5
TextInput {
id: search
Layout.fillWidth: true
color: Config.catppuccin.text
font.pointSize: 13
focus: true
Keys.forwardTo: [list]
Keys.onEscapePressed: launcher.launcherOpen = false
Keys.onPressed: event => {
if (event.modifiers & Qt.ControlModifier) {
if (event.key == Qt.Key_J) {
list.currentIndex = list.currentIndex == list.count - 1 ? 0 : list.currentIndex + 1;
event.accepted = true;
} else if (event.key == Qt.Key_K) {
list.currentIndex = list.currentIndex == 0 ? list.count - 1 : list.currentIndex - 1;
event.accepted = true;
}
}
}
onAccepted: {
if (list.currentItem) {
list.currentItem.clicked(null);
}
}
onTextChanged: {
list.currentIndex = 0;
}
}
}
}
ListView {
id: list
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
cacheBuffer: 0 // works around QTBUG-131106
//reuseItems: true
model: ScriptModel {
values: DesktopEntries.applications.values.map(object => {
const stxt = search.text.toLowerCase();
const ntxt = object.name.toLowerCase();
let si = 0;
let ni = 0;
let matches = [];
let startMatch = -1;
for (let si = 0; si != stxt.length; ++si) {
const sc = stxt[si];
while (true) {
// Drop any entries with letters that don't exist in order
if (ni == ntxt.length)
return null;
const nc = ntxt[ni++];
if (nc == sc) {
if (startMatch == -1)
startMatch = ni;
break;
} else {
if (startMatch != -1) {
matches.push({
index: startMatch,
length: ni - startMatch
});
startMatch = -1;
}
}
}
}
if (startMatch != -1) {
matches.push({
index: startMatch,
length: ni - startMatch + 1
});
}
return {
object: object,
matches: matches
};
}).filter(entry => entry !== null).sort((a, b) => {
let ai = 0;
let bi = 0;
let s = 0;
while (ai != a.matches.length && bi != b.matches.length) {
const am = a.matches[ai];
const bm = b.matches[bi];
s = bm.length - am.length;
if (s != 0)
return s;
s = am.index - bm.index;
if (s != 0)
return s;
++ai;
++bi;
}
s = a.matches.length - b.matches.length;
if (s != 0)
return s;
s = a.object.name.length - b.object.name.length;
if (s != 0)
return s;
return a.object.name.localeCompare(b.object.name);
}).map(entry => entry.object)
onValuesChanged: list.currentIndex = 0
}
topMargin: 7
bottomMargin: list.count == 0 ? 0 : 7
add: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: 100
}
}
displaced: Transition {
NumberAnimation {
property: "y"
duration: 200
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "opacity"
to: 1
duration: 100
}
}
move: Transition {
NumberAnimation {
property: "y"
duration: 200
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "opacity"
to: 1
duration: 100
}
}
remove: Transition {
NumberAnimation {
property: "y"
duration: 200
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "opacity"
to: 0
duration: 100
}
}
highlight: Rectangle {
radius: 5
color: "transparent"
border.color: Config.catppuccin.lavender
border.width: 2
}
keyNavigationEnabled: true
keyNavigationWraps: true
highlightMoveVelocity: -1
highlightMoveDuration: 50
preferredHighlightBegin: list.topMargin
preferredHighlightEnd: list.height - list.bottomMargin
highlightRangeMode: ListView.ApplyRange
snapMode: ListView.SnapToItem
readonly property real delegateHeight: 44
delegate: MouseArea {
required property DesktopEntry modelData
implicitHeight: list.delegateHeight
implicitWidth: ListView.view.width
onClicked: {
modelData.execute();
launcher.launcherOpen = false;
}
RowLayout {
id: delegateLayout
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: 5
}
IconImage {
Layout.alignment: Qt.AlignVCenter
asynchronous: true
implicitSize: 30
source: Quickshell.iconPath(modelData.icon)
}
Text {
text: modelData.name
color: Config.catppuccin.text
font.family: "JetBrainsMono Nerd Font Mono"
font.pointSize: 13
Layout.alignment: Qt.AlignVCenter
}
}
}
}
}
}
}
}
function init() {
}
}

View file

@ -0,0 +1,127 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
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 timeand will take up
// memory that could be used for something else.
LazyLoader {
id: popupLoader
PanelWindow {
id: popup
anchors {
top: true
right: true
}
margins {
top: 25
left: 25
}
width: rect.width
height: rect.height
// color blending is a bit odd as detailed in the type reference.
color: "transparent"
Rectangle {
id: rect
color: root.failed ? "#40802020" : "#40009020"
implicitHeight: layout.implicitHeight + 50
implicitWidth: layout.implicitWidth + 30
// 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
anchors {
top: parent.top
topMargin: 20
horizontalCenter: parent.horizontalCenter
}
Text {
text: root.failed ? "Reloaing failed." : "Reloading completed!"
color: "white"
}
Text {
text: root.errorString
color: "white"
// 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 {
id: bar
color: "#20ffffff"
anchors.bottom: parent.bottom
anchors.left: parent.left
height: 20
PropertyAnimation {
id: anim
target: bar
property: "width"
from: rect.width
to: 0
duration: failed ? 10000 : 800
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
}
}
// 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()
}
}
}
}

View file

@ -0,0 +1,57 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Effects
import QtQuick.Controls
import Quickshell
import Quickshell.Services.SystemTray
Rectangle {
id: root
required property var bar
width: parent.width
height: column.height + 10
color: "#30c0ffff"
radius: 5
border.color: "black"
border.width: 2
RowLayout {
id: column
spacing: 10
anchors.centerIn: parent
Repeater {
model: SystemTray.items
Item {
id: item
required property SystemTrayItem modelData
height: 35
width: 35
Image {
source: item.modelData.icon
anchors.fill: parent
MouseArea {
anchors.fill: parent
onClicked: function (mouse) {
if (mouse.button === Qt.LeftButton) {
item.modelData.activate();
}
if (mouse.button === Qt.RightButton) {
if (item.modelData.hasMenu) {}
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,46 @@
pragma Singleton
import Quickshell
import QtQuick
Singleton {
id: root
readonly property QtObject bar: QtObject {
readonly property int width: 50
readonly property var colors: QtObject {
readonly property color bar: "#1e1e2e"
readonly property color barOutline: "#50ffffff"
readonly property color widget: "#25ceffff"
readonly property color widgetActive: "#80ceffff"
readonly property color widgetOutline: "#40ffffff"
readonly property color widgetOutlineSeparate: "#20ffffff"
readonly property color separator: "#60ffffff"
}
}
readonly property QtObject border: QtObject {
readonly property int thickness: 8
readonly property color color: "#1e1e2e"
readonly property int rounding: 25
}
readonly property QtObject catppuccin: QtObject {
readonly property color base: "#1e1e2e"
readonly property color mantle: "#181825"
readonly property color surface0: "#313244"
readonly property color surface1: "#45475a"
readonly property color surface2: "#585b70"
readonly property color text: "#cdd6f4"
readonly property color rosewater: "#f5e0dc"
readonly property color lavender: "#b4befe"
readonly property color red: "#f38ba8"
readonly property color peach: "#fab387"
readonly property color yellow: "#f9e2af"
readonly property color green: "#a6e3a1"
readonly property color teal: "#a6e3a1"
readonly property color blue: "#89b4fa"
readonly property color mauve: "#cba6f7"
readonly property color flamingo: "#f2cdcd"
}
}

View file

@ -0,0 +1,50 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import "../../config"
import "components"
Item {
id: root
required property ShellScreen screen
anchors {
top: parent.top
bottom: parent.bottom
left: parent.left
}
implicitWidth: Config.bar.width
Item {
id: child
anchors {
top: parent.top
bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
}
implicitWidth: Math.max(clock.implicitWidth, workspaces.implicitWidth)
ColumnLayout {
spacing: 2
anchors {
top: parent.top
left: parent.left
right: parent.right
margins: 0
}
Clock {
id: clock
}
Workspaces {
id: workspaces
screen: root.screen
}
}
}
}

View file

@ -0,0 +1,34 @@
import QtQuick
import Quickshell
import "../../../config"
Rectangle {
id: root
width: text.width + 5
height: text.height + 5
implicitWidth: width
border.color: Config.catppuccin.rosewater
border.width: 0
radius: 5
color: "transparent"
Text {
id: text
anchors.centerIn: parent
property var date: Date()
text: Qt.formatDateTime(clock.date, "hh mm")
font.family: "JetBrainsMono NF Mono"
font.pointSize: 15
color: Config.catppuccin.text
}
SystemClock {
id: clock
precision: SystemClock.Seconds
}
}

View file

@ -0,0 +1,109 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import QtQuick.Layouts
import "../../../services/niri"
import "../../../config"
Rectangle {
id: root
required property ShellScreen screen
property var workspaces: Niri.workspaces
property var wsCount: Niri.workspaces.length
property var activeWorkspace: Niri.activeWorkspace
property var activeWorkspaceIndex: Niri.activeWorkspaceIndex
property int wsItemHeight: 15
signal workspaceAdded(workspace: var)
function onWorkspaceAdded(workspace: var) {
root.workspaces.push(workspace);
}
// property bool _: log()
// function log() {
// console.log(workspaces.values);
// return true;
// }
// Works
height: 300
// Gives warning
// height: workspaces.length * root.wsItemHeight
implicitWidth: list.implicitWidth
color: "transparent"
border.color: Config.catppuccin.rosewater
border.width: 0
radius: 7
Layout.fillWidth: true
ListView {
id: list
model: root.workspaces
implicitHeight: contentHeight
implicitWidth: contentItem.childrenRect.width
anchors.horizontalCenter: parent.horizontalCenter
// anchors.fill: parent
delegate: Item {
id: wsItem
// Name of the workspace
property string name: "VOID"
// ID of the workspace
required property string id
required property string output
property bool isActive: (id - 1 == root.activeWorkspaceIndex)
property real animActive: isActive ? 1 : 0.65
Behavior on animActive {
NumberAnimation {
duration: 150
}
}
// property bool isCorrectScreen: log()
// function log() {
// console.log("Screen name: " + root.screen.name);
// console.log(wsItem.output);
// console.log(wsItem.id);
// let isCorrect = root.screen.name == wsItem.output;
// console.log("isCorrect: ", isCorrect);
// return root.screen.name == wsItem.output;
// }
implicitHeight: root.wsItemHeight
implicitWidth: 50
anchors {
right: parent.right
left: parent.left
}
Rectangle {
anchors.centerIn: parent
height: wsItem.height - 5
width: parent.width * wsItem.animActive
radius: height / 2
border.color: Config.catppuccin.mantle
border.width: 0
color: Config.catppuccin.blue
}
}
}
Component.onCompleted: {
Niri.workspaces.forEach(workspace => {
root.workspaceAdded(workspace);
});
}
}

View file

@ -0,0 +1,9 @@
import Quickshell
import QtQuick
import QtQuick.Shapes
import "../notifications" as Notifications
Rectangle {
required property Item bar
}

View file

@ -0,0 +1,54 @@
import Quickshell
import QtQuick
import QtQuick.Effects
import "../../config"
Item {
id: root
required property Item bar
anchors.fill: parent
Rectangle {
id: rect
anchors.fill: parent
color: Config.border.color
visible: false
Behavior on color {
ColorAnimation {
duration: 150
easing.type: Easing.BezierSpline
}
}
}
Item {
id: mask
anchors.fill: parent
layer.enabled: true
visible: false
Rectangle {
anchors.fill: parent
anchors.margins: Config.border.thickness
anchors.leftMargin: root.bar.implicitWidth
radius: Config.border.rounding
}
}
MultiEffect {
anchors.fill: parent
maskEnabled: true
maskInverted: true
maskSource: mask
source: rect
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
}

View file

@ -0,0 +1,105 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Notifications
import QtQuick
import QtQuick.Effects
import "../bar"
import "../volume"
import "../notifications"
import "../../services"
import "../../config"
Variants {
model: Quickshell.screens
Scope {
id: scope
required property ShellScreen modelData
Exclusions {
screen: scope.modelData
bar: bar
}
PanelWindow {
id: win
screen: scope.modelData
color: "transparent"
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
// Clickthrough mask.
// Clickable areas of the window are determined by the provided region.
mask: Region {
// Start at the bottom left; right of the bar and on top of the border
x: bar.implicitWidth
y: Config.border.thickness
// Width is the window width - the bar's width - the border thickness
width: win.width - bar.implicitWidth - Config.border.thickness
// Height is window width - the border thickness x2 top border and bottom border.
height: win.height - Config.border.thickness * 2
// Setting the intersection mode to Xor will invert the mask and make everything in the mask region not clickable and pass through clicks inside it through the window.
intersection: Intersection.Xor
Region {
item: volume
intersection: Intersection.Subtract
}
}
anchors {
top: true
bottom: true
left: true
right: true
}
Item {
id: background
anchors.fill: parent
visible: false
Border {
bar: bar
}
Backgrounds {
bar: bar
}
}
MultiEffect {
anchors.fill: source
source: background
shadowEnabled: true
blurMax: 15
}
Bar {
id: bar
screen: scope.modelData
}
VolumeSlider {
id: volume
isInRightPanel: hover.isInRightPanel
screen: scope.modelData
}
Hover {
id: hover
screen: scope.modelData
bar: bar
}
}
}
}

View file

@ -0,0 +1,38 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import "../../config"
Scope {
id: root
required property ShellScreen screen
required property Item bar
ExclusionZone {
anchors.left: true
exclusiveZone: root.bar.implicitWidth
}
ExclusionZone {
anchors.top: true
}
ExclusionZone {
anchors.right: true
}
ExclusionZone {
anchors.bottom: true
}
component ExclusionZone: PanelWindow {
screen: root.screen
color: "transparent"
exclusiveZone: Config.border.thickness
implicitHeight: Config.border.thickness
implicitWidth: Config.border.thickness
mask: Region {}
}
}

View file

@ -0,0 +1,319 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
Singleton {
id: launcher
property bool launcherOpen: false
IpcHandler {
target: "launcher"
function open(): void {
launcher.launcherOpen = true;
}
function close(): void {
launcher.launcherOpen = false;
}
function toggle(): void {
launcher.launcherOpen = !launcher.launcherOpen;
}
}
LazyLoader {
id: loader
activeAsync: launcher.launcherOpen
PanelWindow {
width: 450
height: 7 + searchContainer.implicitHeight + list.topMargin * 2 + list.delegateHeight * 10
color: "transparent"
anchors {
bottom: parent.bottom
}
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
WlrLayershell.namespace: "shell:launcher"
Rectangle {
height: 7 + searchContainer.implicitHeight + list.topMargin + list.bottomMargin + Math.min(list.contentHeight, list.delegateHeight * 10)
Behavior on height {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
width: 450
color: "#30c0afaf"
radius: 5
border.color: "black"
border.width: 2
ColumnLayout {
anchors.fill: parent
anchors.margins: 7
anchors.bottomMargin: 0
spacing: 0
Rectangle {
id: searchContainer
Layout.fillWidth: true
implicitHeight: searchbox.implicitHeight + 10
color: "#30c0ffff"
radius: 3
border.color: "#50ffffff"
RowLayout {
id: searchbox
anchors.fill: parent
anchors.margins: 5
IconImage {
implicitSize: parent.height
source: "root:icons/magnifying-glass.svg"
}
TextInput {
id: search
Layout.fillWidth: true
color: "black"
focus: true
Keys.forwardTo: [list]
Keys.onEscapePressed: launcher.launcherOpen = false
Keys.onPressed: event => {
if (event.modifiers & Qt.ControlModifier) {
if (event.key == Qt.Key_J) {
list.currentIndex = list.currentIndex == list.count - 1 ? 0 : list.currentIndex + 1;
event.accepted = true;
} else if (event.key == Qt.Key_K) {
list.currentIndex = list.currentIndex == 0 ? list.count - 1 : list.currentIndex - 1;
event.accepted = true;
}
}
}
onAccepted: {
if (list.currentItem) {
list.currentItem.clicked(null);
}
}
onTextChanged: {
list.currentIndex = 0;
}
}
}
}
ListView {
id: list
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
cacheBuffer: 0 // works around QTBUG-131106
//reuseItems: true
model: ScriptModel {
values: DesktopEntries.applications.values.map(object => {
const stxt = search.text.toLowerCase();
const ntxt = object.name.toLowerCase();
let si = 0;
let ni = 0;
let matches = [];
let startMatch = -1;
for (let si = 0; si != stxt.length; ++si) {
const sc = stxt[si];
while (true) {
// Drop any entries with letters that don't exist in order
if (ni == ntxt.length)
return null;
const nc = ntxt[ni++];
if (nc == sc) {
if (startMatch == -1)
startMatch = ni;
break;
} else {
if (startMatch != -1) {
matches.push({
index: startMatch,
length: ni - startMatch
});
startMatch = -1;
}
}
}
}
if (startMatch != -1) {
matches.push({
index: startMatch,
length: ni - startMatch + 1
});
}
return {
object: object,
matches: matches
};
}).filter(entry => entry !== null).sort((a, b) => {
let ai = 0;
let bi = 0;
let s = 0;
while (ai != a.matches.length && bi != b.matches.length) {
const am = a.matches[ai];
const bm = b.matches[bi];
s = bm.length - am.length;
if (s != 0)
return s;
s = am.index - bm.index;
if (s != 0)
return s;
++ai;
++bi;
}
s = a.matches.length - b.matches.length;
if (s != 0)
return s;
s = a.object.name.length - b.object.name.length;
if (s != 0)
return s;
return a.object.name.localeCompare(b.object.name);
}).map(entry => entry.object)
onValuesChanged: list.currentIndex = 0
}
topMargin: 7
bottomMargin: list.count == 0 ? 0 : 7
add: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: 100
}
}
displaced: Transition {
NumberAnimation {
property: "y"
duration: 200
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "opacity"
to: 1
duration: 100
}
}
move: Transition {
NumberAnimation {
property: "y"
duration: 200
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "opacity"
to: 1
duration: 100
}
}
remove: Transition {
NumberAnimation {
property: "y"
duration: 200
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "opacity"
to: 0
duration: 100
}
}
highlight: Rectangle {
radius: 5
color: "#20e0ffff"
border.color: "#30ffffff"
border.width: 1
}
keyNavigationEnabled: true
keyNavigationWraps: true
highlightMoveVelocity: -1
highlightMoveDuration: 50
preferredHighlightBegin: list.topMargin
preferredHighlightEnd: list.height - list.bottomMargin
highlightRangeMode: ListView.ApplyRange
snapMode: ListView.SnapToItem
readonly property real delegateHeight: 44
delegate: MouseArea {
required property DesktopEntry modelData
implicitHeight: list.delegateHeight
implicitWidth: ListView.view.width
onClicked: {
modelData.execute();
launcher.launcherOpen = false;
}
RowLayout {
id: delegateLayout
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: 5
}
IconImage {
Layout.alignment: Qt.AlignVCenter
asynchronous: true
implicitSize: 30
source: Quickshell.iconPath(modelData.icon)
}
Text {
text: modelData.name
color: "black"
font.family: "ComicShannsMono Nerd Font Mono"
Layout.alignment: Qt.AlignVCenter
}
}
}
}
}
}
}
}
function init() {
}
}

View file

@ -0,0 +1,18 @@
import Quickshell
import Quickshell.Widgets
import Quickshell.Services.Notifications
import QtQuick
import QtQuick.Layouts
import "../../config"
import "../../services"
Rectangle {
id: root
color: "transparent"
required property Notification.Notif modelData
Text {
text: root.modelData.summary
}
}

View file

@ -0,0 +1,41 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Notifications
import QtQuick
import QtQuick.Effects
Rectangle {
id: root
required property bool isInRightPanel
required property ShellScreen screen
property bool isVisible
color: "transparent"
property bool _: log()
function log() {
console.log(hover.hovered);
return true
}
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
}
implicitWidth: 60
implicitHeight: screen.height / 3
HoverHandler {
id: hover
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
}
Rectangle {
anchors.right: parent.right
color: "green"
implicitWidth: hover.hovered | root.isInRightPanel ? 60 : 10
implicitHeight: root.screen.height / 3
}
}

View file

@ -0,0 +1,49 @@
import Quickshell
import QtQuick
import "../config"
MouseArea {
id: root
anchors.fill: parent
hoverEnabled: true
required property ShellScreen screen
// required property Panels panels
required property Item bar
property bool showVolumeMenu: false
property bool isInRightPanel: false
// function withinPanelHeight(panel: Item, x: real, y: real): bool {
// const panelY = Config.border.thickness + panel.y;
// return y >= panelY - Config.border.rounding && y <= panelY + panel.height + Config.border.rounding;
// }
// function inLeftBorder(x: real, y: real): bool {
// return x <= Config.border.thickness;
// }
function inRightPanel(x: real, y: real): bool {
// Cursor is in middle veritcal third of screen
// Cursor is in the right border
return y >= root.screen.height / 3 && y <= (root.screen.height / 3) * 2 && x >= root.screen.width - Config.border.thickness;
}
// Update on mouse cursor movement
onPositionChanged: event => {
const x = event.x;
const y = event.y;
root.isInRightPanel = inRightPanel(x, y);
console.log("In right panel: " + root.isInRightPanel);
console.log("x:" + x + " y: " + y);
}
onContainsMouseChanged: {
if (!containsMouse) {
root.isInRightPanel = false;
}
}
}

View file

@ -0,0 +1,20 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.Notifications
/**
* Provides extra features not in Quickshell.Services.Notifications:
* - Persistent storage
* - Popup notifications, with timeout
* - Notification groups by app
*/
Singleton {
id: root
NotificationServer {
}
}

View file

@ -0,0 +1,94 @@
// Kind thanks to https://github.com/MapoMagpie/nixos-flakes/blob/main/home/ui/quickshell/config/Data/Niri.qml
// This file was taken from there and further modified.
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
// property var data
property var workspaces: []
property var activeWorkspace: "VOID"
property var activeWorkspaceIndex: 0
property var windows: []
// property var activedWindowId: 0
Process {
id: proc
command: ["niri", "msg", "-j", "event-stream"]
running: true
stdout: SplitParser {
onRead: data => {
var event = JSON.parse(data);
let workspaces = [];
if (event.WorkspacesChanged) {
root.workspaces = event.WorkspacesChanged.workspaces;
root.workspaces = root.workspaces.sort((a, b) => a.id - b.id);
root.activeWorkspaceIndex = root.workspaces.findIndex(w => w.is_focused);
if (root.activeWorkspaceIndex < 0) {
root.activeWorkspaceIndex = 0;
}
root.activeWorkspace = root.workspaces[root.activeWorkspaceIndex].name;
}
if (event.WindowsChanged) {
root.windows = [...event.WindowsChanged.windows].sort((a, b) => a.id - b.id);
}
if (event.WindowOpenedOrChanged) {
const window = event.WindowOpenedOrChanged.window;
const index = root.windows.findIndex(w => w.id === window.id);
// console.log("window opened or changed: ", index, ", win id: ", window.id);
if (index >= 0) {
// console.log("replace window, old: ", root.windows[index].id, ", new: ", window.id);
root.windows[index] = window;
} else {
// console.log("push window, new: ", window.id);
root.windows.push(window);
}
root.windows = [...root.windows.sort((a, b) => a.id - b.id)];
}
if (event.WindowClosed) {
const index = root.windows.findIndex(w => w.id === event.WindowClosed.id);
// console.log("window closed: ", index, ", win id: ", event.WindowClosed.id);
if (index >= 0) {
root.windows.splice(index, 1);
}
root.windows = [...root.windows.sort((a, b) => a.id - b.id)];
}
if (event.WorkspaceActivated) {
root.activeWorkspaceIndex = root.workspaces.findIndex(w => w.id === event.WorkspaceActivated.id);
if (root.activeWorkspaceIndex < 0) {
root.activeWorkspaceIndex = 0;
}
root.activeWorkspace = root.workspaces[root.activeWorkspaceIndex].name;
}
}
}
}
// component Workspace: QtObject {
// required property int id
// property int idx
// property string name: "VOID"
// required property string output
// property bool is_active
// property bool is_focused
// property int active_window_id
// }
}
// {
// "workspaces": [
// {
// "id": 5,
// "idx": 4,
// "name": "GAME",
// "output": "DP-3",
// "is_active": false,
// "is_focused": false,
// "active_window_id": null
// },
// ]
// }

View file

@ -0,0 +1,21 @@
//@ pragma Env QS_NO_RELOAD_POPUP=1
import Quickshell
import QtQuick
import "modules"
import "modules/drawers"
import "services"
// import "modules/background"
ShellRoot {
id: shellroot
Component.onCompleted: [Launcher.init()]
Drawers {}
// Background {}Popup
//
}