diff --git a/home/bin/desktop/executable_quickshell-start.sh b/home/bin/desktop/executable_quickshell-start.sh new file mode 100644 index 0000000..9c7fe41 --- /dev/null +++ b/home/bin/desktop/executable_quickshell-start.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# Start the Quickshell bar on the current Wayland session. + +qs -c bar kill 2>/dev/null || true +qs -c bar --daemonize diff --git a/home/bin/installers/executable_sway-install.sh b/home/bin/installers/executable_sway-install.sh index 3334255..1446e6d 100644 --- a/home/bin/installers/executable_sway-install.sh +++ b/home/bin/installers/executable_sway-install.sh @@ -3,4 +3,4 @@ # Import library source $(dirname ${BASH_SOURCE[0]})/install-lib.sh -sudo $(packageManager) install sway waybar rofi mako alacritty +sudo $(packageManager) install sway waybar quickshell rofi mako alacritty brightnessctl diff --git a/home/bin/system/executable_system-start.sh b/home/bin/system/executable_system-start.sh index 0ee92ab..7e71026 100644 --- a/home/bin/system/executable_system-start.sh +++ b/home/bin/system/executable_system-start.sh @@ -5,9 +5,12 @@ pass git pull # Desktop ~/bin/connect-nas.sh -systemctl --user start polybar +if [ "${XDG_SESSION_TYPE}" = "wayland" ]; then + ~/bin/desktop/quickshell-start.sh +else + systemctl --user start polybar +fi systemctl --user restart streamdeck -waybar & /usr/libexec/polkit-gnome-authentication-agent-1 & # Keyring diff --git a/home/dot_config/quickshell/bar/Backlight.qml b/home/dot_config/quickshell/bar/Backlight.qml new file mode 100644 index 0000000..761769d --- /dev/null +++ b/home/dot_config/quickshell/bar/Backlight.qml @@ -0,0 +1,42 @@ +import QtQuick +import Quickshell +import Quickshell.Io + +Block { + id: root + + property int percentage: -1 + + visible: percentage >= 0 + blockColor: "#2980b9" + interactive: true + text: "\uf185 " + percentage + "%" + onWheel: wheel => { + const change = wheel.angleDelta.y > 0 ? "+5%" : "5%-"; + Quickshell.execDetached(["brightnessctl", "set", change]); + refreshTimer.restart(); + } + + Process { + id: brightnessQuery + command: ["brightnessctl", "-m"] + running: true + + stdout: StdioCollector { + onStreamFinished: { + const fields = text.trim().split(","); + root.percentage = fields.length >= 4 + ? parseInt(fields[3].replace("%", "")) + : -1; + } + } + } + + Timer { + id: refreshTimer + interval: 500 + repeat: true + running: true + onTriggered: brightnessQuery.running = true + } +} diff --git a/home/dot_config/quickshell/bar/Bar.qml b/home/dot_config/quickshell/bar/Bar.qml new file mode 100644 index 0000000..756b6dd --- /dev/null +++ b/home/dot_config/quickshell/bar/Bar.qml @@ -0,0 +1,239 @@ +import QtQuick +import Quickshell +import Quickshell.Hyprland +import Quickshell.Services.Pipewire +import Quickshell.Services.SystemTray +import Quickshell.Services.UPower +import Quickshell.Widgets + +PanelWindow { + id: bar + + property var sink: Pipewire.defaultAudioSink + property var source: Pipewire.defaultAudioSource + property var battery: UPower.displayDevice + + anchors { + top: true + left: true + right: true + } + implicitHeight: 38 + color: "#80000000" + + PwObjectTracker { + objects: [bar.sink, bar.source] + } + + SystemClock { + id: clock + precision: SystemClock.Seconds + } + + Row { + id: leftModules + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + } + spacing: 8 + + Row { + height: parent.height + spacing: 0 + + Repeater { + model: Hyprland.workspaces + + Rectangle { + id: workspaceButton + + required property var modelData + property bool active: modelData.active + + visible: modelData.id > 0 + width: 34 + height: parent.height + color: modelData.urgent ? "#e91e63" + : active ? "#0288d1" : "transparent" + + Rectangle { + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: 3 + color: workspaceButton.active ? "#b3e5fc" : "transparent" + } + + Text { + anchors.centerIn: parent + text: modelData.id === 2 ? "\uf120" + : modelData.id === 13 ? "\uf001" : modelData.name + color: "#ffffff" + font.family: "SF Pro Display, Font Awesome 6 Free" + font.pixelSize: 18 + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: Quickshell.execDetached([ + "hyprctl", + "dispatch", + "hl.dsp.focus({ workspace = " + workspaceButton.modelData.id + " })" + ]) + } + } + } + } + } + + Text { + anchors.centerIn: parent + width: Math.max(0, Math.min(implicitWidth, + parent.width - leftModules.width - rightModules.width - 32)) + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + text: Hyprland.activeToplevel ? Hyprland.activeToplevel.title : "" + color: "#ffffff" + font.family: "SF Pro Display, Helvetica, Arial, sans-serif" + font.pixelSize: 18 + } + + Row { + id: rightModules + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + rightMargin: 4 + } + spacing: 8 + + Block { + id: audioBlock + blockColor: bar.sink && bar.sink.audio && bar.sink.audio.muted + ? "#ffffff" : "#0288d1" + textColor: bar.sink && bar.sink.audio && bar.sink.audio.muted + ? "#000000" : "#ffffff" + interactive: true + text: { + if (!bar.sink || !bar.sink.audio) + return "\uf6a9"; + const output = bar.sink.audio.muted + ? "\uf6a9" : "\uf028 " + Math.round(bar.sink.audio.volume * 100) + "%"; + const input = bar.source && bar.source.audio + ? (bar.source.audio.muted ? "\uf131" + : "\uf130 " + Math.round(bar.source.audio.volume * 100) + "%") + : ""; + return output + (input ? " " + input : ""); + } + onClicked: mouse => { + if (mouse.button === Qt.MiddleButton && bar.sink && bar.sink.audio) + bar.sink.audio.muted = !bar.sink.audio.muted; + else + Quickshell.execDetached(["qjackctl"]); + } + onWheel: wheel => { + if (!bar.sink || !bar.sink.audio) + return; + const change = wheel.angleDelta.y > 0 ? 0.05 : -0.05; + bar.sink.audio.volume = Math.max(0, Math.min(1.5, + bar.sink.audio.volume + change)); + } + } + + Network {} + Backlight {} + + Block { + property int percentage: Math.round(bar.battery.percentage * 100) + + visible: bar.battery.ready && bar.battery.isLaptopBattery + blockColor: percentage <= 15 && UPower.onBattery + ? "#e91e63" : UPower.onBattery ? "#ffffff" : "#2980b9" + textColor: UPower.onBattery && percentage > 15 + ? "#000000" : "#ffffff" + text: { + if (!UPower.onBattery) + return "\uf1e6 " + percentage + "%"; + if (percentage <= 10) return "\uf244 " + percentage + "%"; + if (percentage <= 35) return "\uf243 " + percentage + "%"; + if (percentage <= 60) return "\uf242 " + percentage + "%"; + if (percentage <= 85) return "\uf241 " + percentage + "%"; + return "\uf240 " + percentage + "%"; + } + } + + Rectangle { + visible: trayRepeater.count > 0 + implicitWidth: trayItems.implicitWidth + 20 + implicitHeight: 32 + color: "#0288d1" + + Row { + id: trayItems + anchors.centerIn: parent + spacing: 10 + + Repeater { + id: trayRepeater + model: SystemTray.items + + Item { + id: trayItem + + required property var modelData + width: 26 + height: 30 + + IconImage { + anchors { + fill: parent + margins: 3 + } + source: trayItem.modelData.icon + opacity: trayMouse.containsMouse ? 1 : 0.82 + + Behavior on opacity { + NumberAnimation { duration: 120 } + } + } + + MouseArea { + id: trayMouse + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton + onClicked: mouse => { + if (mouse.button === Qt.MiddleButton) + trayItem.modelData.secondaryActivate(); + else if (mouse.button === Qt.RightButton || trayItem.modelData.onlyMenu) { + const point = mapToItem(bar.contentItem, 0, height); + trayItem.modelData.display(bar, point.x, point.y); + } else { + trayItem.modelData.activate(); + } + } + onWheel: wheel => trayItem.modelData.scroll( + wheel.angleDelta.y, false) + } + } + } + } + } + + Block { + property bool detailed: false + + interactive: true + text: Qt.formatDateTime(clock.date, + detailed ? "yyyy-MM-dd hh:mm:ss" : "hh:mm") + onClicked: detailed = !detailed + } + } +} diff --git a/home/dot_config/quickshell/bar/Block.qml b/home/dot_config/quickshell/bar/Block.qml new file mode 100644 index 0000000..222e00d --- /dev/null +++ b/home/dot_config/quickshell/bar/Block.qml @@ -0,0 +1,34 @@ +import QtQuick + +Rectangle { + id: root + + property color blockColor: "#0288d1" + property color textColor: "#ffffff" + property bool interactive: false + property string text: "" + signal clicked(var mouse) + signal wheel(var wheel) + + implicitWidth: label.implicitWidth + 20 + implicitHeight: 32 + color: blockColor + + Text { + id: label + anchors.centerIn: parent + text: root.text + color: root.textColor + font.family: "SF Pro Display, Font Awesome 6 Free" + font.pixelSize: 18 + } + + MouseArea { + anchors.fill: parent + enabled: root.interactive + acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton + cursorShape: Qt.PointingHandCursor + onClicked: mouse => root.clicked(mouse) + onWheel: wheel => root.wheel(wheel) + } +} diff --git a/home/dot_config/quickshell/bar/Network.qml b/home/dot_config/quickshell/bar/Network.qml new file mode 100644 index 0000000..f638445 --- /dev/null +++ b/home/dot_config/quickshell/bar/Network.qml @@ -0,0 +1,51 @@ +import QtQuick +import Quickshell +import Quickshell.Io + +Block { + id: root + + property string connection: "Disconnected" + property bool connected: connection !== "Disconnected" + + blockColor: connected ? "#2980b9" : "#e91e63" + interactive: true + text: connected ? "\uf1eb " + connection : "Disconnected !" + onClicked: Quickshell.execDetached(["nm-connection-editor"]) + + Process { + id: networkQuery + command: ["nmcli", "-t", "-f", "TYPE,NAME", "connection", "show", "--active"] + running: true + + stdout: StdioCollector { + onStreamFinished: { + const lines = text.trim().split("\n"); + for (let line of lines) { + const separator = line.indexOf(":"); + const type = line.slice(0, separator); + if (type === "802-11-wireless" || type === "wifi") { + root.connection = line.slice(separator + 1); + return; + } + } + for (let line of lines) { + const separator = line.indexOf(":"); + const type = line.slice(0, separator); + if (type === "802-3-ethernet" || type === "ethernet") { + root.connection = line.slice(separator + 1); + return; + } + } + root.connection = "Disconnected"; + } + } + } + + Timer { + interval: 5000 + repeat: true + running: true + onTriggered: networkQuery.running = true + } +} diff --git a/home/dot_config/quickshell/bar/shell.qml b/home/dot_config/quickshell/bar/shell.qml new file mode 100644 index 0000000..3a4b49f --- /dev/null +++ b/home/dot_config/quickshell/bar/shell.qml @@ -0,0 +1,12 @@ +import Quickshell + +ShellRoot { + Variants { + model: Quickshell.screens + + Bar { + required property var modelData + screen: modelData + } + } +}