Add desktop configuration

This commit is contained in:
wfalkowski committed 2026-10-15 21:17:12 +00:00
1 parent afda67ea6c
commit c5611d9129
73 files changed
+137647

No files matched your search

@@ -0,0 +1,548 @@
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import Quickshell.Wayland
import Quickshell.Widgets
import Quickshell.Services.SystemTray
import Quickshell.Services.Pipewire
import Quickshell.Services.UPower
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
ShellRoot {
id: root
// ───────────── SETTINGS & THEMING ─────────────
readonly property string mono: "JetBrainsMono Nerd Font"
readonly property int barWidth: 42
readonly property int wsCount: 5
readonly property color cBar: "#0a0a0a"
readonly property color cPanel: "#0d0d0d"
readonly property color cBorder: "#333333"
readonly property color cText: "#c8c8c8"
readonly property color cDim: "#555555"
readonly property color cBright: "#ffffff"
readonly property color cAccent: "#ffffff"
readonly property color cWarn: "#e05555"
// ───────────── STATE ─────────────
property bool launcherOpen: false
property bool netUp: false
property bool btOn: false
property string activeApplet: ""
PwObjectTracker { objects: [Pipewire] }
IpcHandler {
target: "launcher"
function toggle(): void { root.launcherOpen = !root.launcherOpen }
function open(): void { root.launcherOpen = true }
function close(): void { root.launcherOpen = false }
}
SystemClock { id: clock; precision: SystemClock.Minutes }
Process {
id: statusProc
command: ["sh", "-c",
"nmcli -t -f STATE general 2>/dev/null | grep -q '^connected' && echo net=1 || echo net=0; " +
"nmcli -t -f WIRELESS radio 2>/dev/null | grep -q 'enabled' && echo bt=1 || echo bt=0"]
stdout: StdioCollector {
onStreamFinished: {
root.netUp = text.includes("net=1")
root.btOn = text.includes("bt=1")
}
}
}
Timer { interval: 5000; running: true; repeat: true; triggeredOnStart: true; onTriggered: statusProc.running = true }
// ───────────── VERTICAL BAR ─────────────
Variants {
model: Quickshell.screens
PanelWindow {
id: bar
required property var modelData
screen: modelData
anchors { top: true; bottom: true; left: true }
implicitWidth: root.barWidth
color: root.cBar
Rectangle {
anchors { top: parent.top; bottom: parent.bottom; right: parent.right }
width: 1
color: root.cBorder
}
// Launcher Button & Workspaces (Top)
ColumnLayout {
anchors { top: parent.top; topMargin: 12; horizontalCenter: parent.horizontalCenter }
spacing: 6
// App Launcher Icon
Item {
Layout.alignment: Qt.AlignHCenter
implicitWidth: 32; implicitHeight: 32
Text {
anchors.centerIn: parent
text: ""
font.family: root.mono; font.pixelSize: 14
color: root.launcherOpen ? root.cBright : root.cDim
}
MouseArea {
anchors.fill: parent
onClicked: root.launcherOpen = !root.launcherOpen
}
}
Rectangle { Layout.alignment: Qt.AlignHCenter; width: 16; height: 1; color: root.cBorder; Layout.bottomMargin: 4 }
Repeater {
model: root.wsCount
delegate: Rectangle {
required property int index
readonly property int wsId: index + 1
readonly property bool active: Hyprland.focusedWorkspace?.id === wsId
readonly property bool occupied: Hyprland.workspaces.values.some(w => w.id === wsId)
Layout.alignment: Qt.AlignHCenter
implicitWidth: 32; implicitHeight: 32
color: "transparent"
Text {
anchors.centerIn: parent
text: parent.wsId
font.family: root.mono
font.pixelSize: 12
font.bold: parent.active
color: parent.active ? root.cBright : (parent.occupied ? root.cText : root.cDim)
}
MouseArea {
anchors.fill: parent
onClicked: Quickshell.execDetached(["hyprctl", "dispatch", "workspace " + parent.wsId])
}
}
}
}
// Clock & Applet Triggers (Bottom) - NOW WITH BLUETOOTH & NETWORK INDICATORS
ColumnLayout {
anchors { bottom: parent.bottom; bottomMargin: 12; horizontalCenter: parent.horizontalCenter }
spacing: 12
Text { Layout.alignment: Qt.AlignHCenter; text: Qt.formatDateTime(clock.date, "HH"); font.family: root.mono; font.pixelSize: 12; font.bold: true; color: root.cBright }
Text { Layout.alignment: Qt.AlignHCenter; text: Qt.formatDateTime(clock.date, "mm"); font.family: root.mono; font.pixelSize: 12; color: root.cDim; Layout.topMargin: -8 }
Rectangle { Layout.alignment: Qt.AlignHCenter; width: 16; height: 1; color: root.cBorder; Layout.topMargin: 4; Layout.bottomMargin: 4 }
// NETWORK INDICATOR (ADDED)
Text {
Layout.alignment: Qt.AlignHCenter
text: root.netUp ? "" : "" // Wi-Fi when connected, Ethernet when not (or vice versa based on preference)
font.family: root.mono; font.pixelSize: 14
color: root.netUp ? root.cAccent : root.cDim
MouseArea {
anchors.fill: parent;
anchors.margins: -8;
onClicked: root.activeApplet = root.activeApplet === "wifi" ? "" : "wifi"
}
}
Rectangle { Layout.alignment: Qt.AlignHCenter; width: 16; height: 1; color: root.cBorder; Layout.topMargin: 4; Layout.bottomMargin: 4 }
// BLUETOOTH INDICATOR (ADDED)
Text {
Layout.alignment: Qt.AlignHCenter
text: root.btOn ? "" : "" // Bluetooth on/off
font.family: root.mono; font.pixelSize: 14
color: root.btOn ? root.cAccent : root.cDim
MouseArea {
anchors.fill: parent;
anchors.margins: -8;
onClicked: root.activeApplet = root.activeApplet === "bluetooth" ? "" : "bluetooth"
}
}
Rectangle { Layout.alignment: Qt.AlignHCenter; width: 16; height: 1; color: root.cBorder; Layout.topMargin: 4; Layout.bottomMargin: 4 }
Text {
Layout.alignment: Qt.AlignHCenter
text: Pipewire.defaultAudioSink?.audio?.muted ? "" : ""
font.family: root.mono; font.pixelSize: 14
color: root.activeApplet === "sound" ? root.cAccent : root.cText
MouseArea { anchors.fill: parent; anchors.margins: -8; onClicked: root.activeApplet = root.activeApplet === "sound" ? "" : "sound" }
}
}
}
}
// ───────────── APP LAUNCHER OVERLAY ─────────────
PanelWindow {
id: launcherWindow
visible: root.launcherOpen
screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? Quickshell.screens[0]
// Centered overlay matching the design in screenshots
implicitWidth: 320
implicitHeight: 520
color: "transparent"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "launcher"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
Rectangle {
anchors.fill: parent
color: root.cPanel
border.color: root.cBorder
border.width: 1
FocusScope {
id: launcherScope
anchors.fill: parent
focus: launcherWindow.visible
Keys.onEscapePressed: root.launcherOpen = false
ColumnLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 10
// Search Input Box ("Apps...")
Rectangle {
Layout.fillWidth: true
implicitHeight: 34
color: "#141414"
border.color: searchInput.activeFocus ? root.cBright : root.cBorder
border.width: 1
RowLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 8
Text {
text: ""
font.family: root.mono
font.pixelSize: 12
color: root.cDim
}
TextInput {
id: searchInput
Layout.fillWidth: true
font.family: root.mono
font.pixelSize: 12
color: root.cBright
focus: true
selectByMouse: true
Text {
text: "Apps..."
font.family: root.mono
font.pixelSize: 12
color: root.cDim
visible: searchInput.text === ""
}
Keys.onDownPressed: appListView.incrementCurrentIndex()
Keys.onUpPressed: appListView.decrementCurrentIndex()
Keys.onReturnPressed: {
if (appListView.currentItem) {
appListView.currentItem.launch()
}
}
}
}
}
// Application List
ListView {
id: appListView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: 2
currentIndex: 0
model: appListProc.apps
delegate: Rectangle {
id: appItem
required property var modelData
required property int index
width: appListView.width
height: 36
color: ListView.isCurrentItem ? "#1a1a1a" : (itemArea.containsMouse ? "#141414" : "transparent")
border.color: ListView.isCurrentItem ? root.cBorder : "transparent"
function launch() {
Quickshell.execDetached(["sh", "-c", modelData.exec])
root.launcherOpen = false
searchInput.text = ""
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 10
Text {
text: ""
font.family: root.mono
font.pixelSize: 13
color: ListView.isCurrentItem ? root.cBright : root.cDim
}
Text {
text: modelData.name
font.family: root.mono
font.pixelSize: 12
color: ListView.isCurrentItem ? root.cBright : root.cText
Layout.fillWidth: true
elide: Text.ElideRight
}
}
MouseArea {
id: itemArea
anchors.fill: parent
hoverEnabled: true
onClicked: appItem.launch()
}
}
}
}
}
// Process to fetch installed desktop applications
Process {
id: appListProc
property var rawApps: []
property var apps: {
const query = searchInput.text.toLowerCase().trim()
if (!query) return rawApps
return rawApps.filter(a => a.name.toLowerCase().includes(query))
}
command: ["python3", "-c", `
import os, glob
apps = []
dirs = ['/usr/share/applications', os.path.expanduser('~/.local/share/applications')]
for d in dirs:
for path in glob.glob(os.path.join(d, '*.desktop')):
try:
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
name, exec_cmd, nodisplay = "", "", False
for line in f:
if line.startswith('Name=') and not name:
name = line.split('=', 1)[1].strip()
elif line.startswith('Exec=') and not exec_cmd:
exec_cmd = line.split('=', 1)[1].strip().split('%')[0].strip()
elif line.startswith('NoDisplay=true'):
nodisplay = True
if name and exec_cmd and not nodisplay:
apps.append(f"{name}|||{exec_cmd}")
except:
pass
apps = sorted(list(set(apps)), key=lambda x: x.lower())
for a in apps:
print(a)
`]
stdout: StdioCollector {
onStreamFinished: {
const lines = text.trim().split("\n")
let parsed = []
for (let line of lines) {
const parts = line.split("|||")
if (parts.length === 2) {
parsed.push({ name: parts[0], exec: parts[1] })
}
}
appListProc.rawApps = parsed
}
}
}
Component.onCompleted: appListProc.running = true
onVisibleChanged: {
if (visible) {
searchInput.text = ""
searchInput.forceActiveFocus()
}
}
}
// ───────────── APPLETS POPUP WINDOW ─────────────
PanelWindow {
id: appletWindow
visible: root.activeApplet !== ""
screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? Quickshell.screens[0]
anchors { left: true; bottom: true }
margins.left: root.barWidth + 12
margins.bottom: 12
implicitWidth: 340
implicitHeight: root.activeApplet === "sound" ? 420 : 380
color: "transparent"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "applet"
Rectangle {
anchors.fill: parent
color: root.cPanel
border.color: root.cBorder
border.width: 1
FocusScope {
anchors.fill: parent
focus: appletWindow.visible
Keys.onEscapePressed: root.activeApplet = ""
}
// ─ NETWORK APPLET ─
ColumnLayout {
anchors.fill: parent; anchors.margins: 16
visible: root.activeApplet === "wifi"
spacing: 16
property var wifiList: []
Process {
id: wifiScanProc
command: ["nmcli", "-t", "-f", "SSID,SIGNAL,SECURITY,IN-USE", "dev", "wifi"]
stdout: StdioCollector {
onStreamFinished: {
const lines = text.trim().split("\n")
let results = []
for (let l of lines) {
if (!l) continue
const parts = l.split(":")
if (parts[0] && !results.some(r => r.ssid === parts[0])) {
results.push({ ssid: parts[0], signal: parseInt(parts[1]), active: parts[3] === "*" })
}
}
results.sort((a, b) => b.active - a.active || b.signal - a.signal)
parent.wifiList = results
}
}
}
onVisibleChanged: if(visible) wifiScanProc.running = true
RowLayout {
Layout.fillWidth: true
ColumnLayout {
spacing: 2
Text { text: "Network"; font.family: root.mono; font.pixelSize: 14; font.bold: true; color: root.cBright }
Text { text: "WI-FI & ETHERNET"; font.family: root.mono; font.pixelSize: 10; color: root.cDim }
}
Item { Layout.fillWidth: true }
Rectangle {
width: 32; height: 18; color: root.netUp ? root.cBright : root.cBorder
Rectangle { x: root.netUp ? 15 : 1; y: 1; width: 16; height: 16; color: root.cPanel; Behavior on x { NumberAnimation { duration: 150 } } }
}
}
Rectangle { Layout.fillWidth: true; height: 1; color: root.cBorder }
Text { text: "ZNANE SIECI"; font.family: root.mono; font.pixelSize: 10; color: root.cDim }
ListView {
Layout.fillWidth: true; Layout.fillHeight: true; clip: true; spacing: 8
model: parent.wifiList
delegate: Rectangle {
width: ListView.view.width; height: 32; color: "transparent"
border.color: modelData.active ? root.cBorder : "transparent"
RowLayout {
anchors.fill: parent; anchors.leftMargin: 12; anchors.rightMargin: 12; spacing: 12
Text { text: ""; font.family: root.mono; font.pixelSize: 12; color: modelData.active ? root.cAccent : root.cDim }
Text { text: modelData.ssid; font.family: root.mono; font.pixelSize: 12; color: modelData.active ? root.cBright : root.cText; Layout.fillWidth: true; elide: Text.ElideRight }
}
MouseArea { anchors.fill: parent; onClicked: { Quickshell.execDetached(["foot", "-e", "nmtui-connect", modelData.ssid]); root.activeApplet = "" } }
}
}
}
// ─ AUDIO APPLET ─
ColumnLayout {
anchors.fill: parent; anchors.margins: 16
visible: root.activeApplet === "sound"
spacing: 12
RowLayout {
Layout.fillWidth: true
ColumnLayout {
spacing: 2
Text { text: "Audio"; font.family: root.mono; font.pixelSize: 14; font.bold: true; color: root.cBright }
Text { text: "POKRYJ HAŁAS"; font.family: root.mono; font.pixelSize: 10; color: root.cDim }
}
Item { Layout.fillWidth: true }
Rectangle {
width: 32; height: 18; color: !Pipewire.defaultAudioSink?.audio?.muted ? root.cBright : root.cBorder
Rectangle { x: !Pipewire.defaultAudioSink?.audio?.muted ? 15 : 1; y: 1; width: 16; height: 16; color: root.cPanel; Behavior on x { NumberAnimation { duration: 150 } } }
MouseArea { anchors.fill: parent; onClicked: if(Pipewire.defaultAudioSink?.audio) Pipewire.defaultAudioSink.audio.muted = !Pipewire.defaultAudioSink.audio.muted }
}
}
Rectangle { Layout.fillWidth: true; height: 1; color: root.cBorder; Layout.bottomMargin: 8 }
// Input Section
Text { text: "INPUT"; font.family: root.mono; font.pixelSize: 10; color: root.cDim }
Repeater {
model: Pipewire.nodes.values.filter(n => n.mediaClass === "Audio/Source" && !n.description.includes("Monitor"))
delegate: ColumnLayout {
Layout.fillWidth: true; spacing: 4
RowLayout {
Text { text: ""; font.family: root.mono; font.pixelSize: 12; color: root.cDim }
Text { text: modelData.description; font.family: root.mono; font.pixelSize: 11; color: root.cText; Layout.fillWidth: true; elide: Text.ElideRight }
}
Slider {
Layout.fillWidth: true; from: 0; to: 1; value: modelData.audio?.volume ?? 0
onValueChanged: if (modelData.audio && Math.abs(modelData.audio.volume - value) > 0.01) modelData.audio.volume = value
background: Rectangle { width: parent.width; height: 2; color: root.cBorder; y: parent.height/2; Rectangle { width: parent.visualPosition * parent.width; height: 2; color: root.cBright } }
handle: Rectangle { x: parent.visualPosition * (parent.width - width); y: parent.height/2 - height/2; width: 10; height: 10; color: root.cBright }
}
}
}
Item { Layout.preferredHeight: 8 }
// Output Section
Text { text: "OUTPUT"; font.family: root.mono; font.pixelSize: 10; color: root.cDim }
Repeater {
model: Pipewire.nodes.values.filter(n => n.mediaClass === "Audio/Sink")
delegate: ColumnLayout {
Layout.fillWidth: true; spacing: 4
RowLayout {
Text { text: ""; font.family: root.mono; font.pixelSize: 12; color: root.cDim }
Text { text: modelData.description; font.family: root.mono; font.pixelSize: 11; color: root.cText; Layout.fillWidth: true; elide: Text.ElideRight }
}
Slider {
Layout.fillWidth: true; from: 0; to: 1; value: modelData.audio?.volume ?? 0
onValueChanged: if (modelData.audio && Math.abs(modelData.audio.volume - value) > 0.01) modelData.audio.volume = value
background: Rectangle { width: parent.width; height: 2; color: root.cBorder; y: parent.height/2; Rectangle { width: parent.visualPosition * parent.width; height: 2; color: root.cBright } }
handle: Rectangle { x: parent.visualPosition * (parent.width - width); y: parent.height/2 - height/2; width: 10; height: 10; color: root.cBright }
}
}
}
Item { Layout.fillHeight: true }
}
}
}
}
}