Add desktop configuration
This commit is contained in:
1 parent
afda67ea6c
commit
c5611d9129
73 files changed
+137647
No files matched your search
+2945
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+2958
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1 @@
|
||||
permit persist wfalkowski
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+4003
File diff suppressed because it is too large.
Load diff
+3907
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user