1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-07 05:55:37 -05:00

Merge remote changes with CPU/RAM monitor widgets

This commit is contained in:
purian23
2025-07-11 14:29:42 -04:00
14 changed files with 1533 additions and 829 deletions

View File

@@ -11,6 +11,11 @@ Singleton {
property var audioSinks: []
property string currentAudioSink: ""
// Microphone properties
property int micLevel: 50
property var audioSources: []
property string currentAudioSource: ""
// Real Audio Control
Process {
id: volumeChecker
@@ -27,6 +32,22 @@ Singleton {
}
}
// Microphone level checker
Process {
id: micLevelChecker
command: ["bash", "-c", "pactl get-source-volume @DEFAULT_SOURCE@ | grep -o '[0-9]*%' | head -1 | tr -d '%'"]
running: true
stdout: SplitParser {
splitMarker: "\n"
onRead: (data) => {
if (data.trim()) {
root.micLevel = Math.min(100, parseInt(data.trim()) || 50)
}
}
}
}
Process {
id: audioSinkLister
command: ["pactl", "list", "sinks"]
@@ -35,7 +56,6 @@ Singleton {
stdout: StdioCollector {
onStreamFinished: {
if (text.trim()) {
console.log("Parsing pactl sink output...")
let sinks = []
let lines = text.trim().split('\n')
@@ -88,7 +108,6 @@ Singleton {
sink.displayName = displayName
}
console.log("Final audio sinks:", JSON.stringify(sinks, null, 2))
root.audioSinks = sinks
defaultSinkChecker.running = true
}
@@ -96,6 +115,61 @@ Singleton {
}
}
// Audio source (microphone) lister
Process {
id: audioSourceLister
command: ["pactl", "list", "sources"]
running: true
stdout: StdioCollector {
onStreamFinished: {
if (text.trim()) {
let sources = []
let lines = text.trim().split('\n')
let currentSource = null
for (let line of lines) {
line = line.trim()
// New source starts
if (line.startsWith('Source #')) {
if (currentSource && currentSource.name && currentSource.id) {
sources.push(currentSource)
}
currentSource = {
id: line.replace('Source #', '').replace(':', ''),
name: '',
displayName: '',
active: false
}
}
// Source name
else if (line.startsWith('Name: ') && currentSource) {
currentSource.name = line.replace('Name: ', '')
}
// Description (display name)
else if (line.startsWith('Description: ') && currentSource) {
let desc = line.replace('Description: ', '')
currentSource.displayName = desc
}
}
// Add the last source
if (currentSource && currentSource.name && currentSource.id) {
sources.push(currentSource)
}
// Filter out monitor sources (we want actual input devices)
sources = sources.filter(source => !source.name.includes('.monitor'))
root.audioSources = sources
defaultSourceChecker.running = true
}
}
}
}
Process {
id: defaultSinkChecker
command: ["pactl", "get-default-sink"]
@@ -106,7 +180,6 @@ Singleton {
onRead: (data) => {
if (data.trim()) {
root.currentAudioSink = data.trim()
console.log("Default audio sink:", root.currentAudioSink)
// Update active status in audioSinks
let updatedSinks = []
@@ -124,6 +197,34 @@ Singleton {
}
}
// Default source (microphone) checker
Process {
id: defaultSourceChecker
command: ["pactl", "get-default-source"]
running: false
stdout: SplitParser {
splitMarker: "\n"
onRead: (data) => {
if (data.trim()) {
root.currentAudioSource = data.trim()
// Update active status in audioSources
let updatedSources = []
for (let source of root.audioSources) {
updatedSources.push({
id: source.id,
name: source.name,
displayName: source.displayName,
active: source.name === root.currentAudioSource
})
}
root.audioSources = updatedSources
}
}
}
}
function setVolume(percentage) {
let volumeSetProcess = Qt.createQmlObject('
import Quickshell.Io
@@ -135,6 +236,17 @@ Singleton {
', root)
}
function setMicLevel(percentage) {
let micSetProcess = Qt.createQmlObject('
import Quickshell.Io
Process {
command: ["pactl", "set-source-volume", "@DEFAULT_SOURCE@", "' + percentage + '%"]
running: true
onExited: micLevelChecker.running = true
}
', root)
}
function setAudioSink(sinkName) {
console.log("Setting audio sink to:", sinkName)
@@ -160,4 +272,39 @@ Singleton {
}
}
}
function setAudioSource(sourceName) {
console.log("Setting audio source to:", sourceName)
sourceSetProcess.command = ["pactl", "set-default-source", sourceName]
sourceSetProcess.running = true
}
// Dedicated process for setting audio source
Process {
id: sourceSetProcess
running: false
onExited: (exitCode) => {
console.log("Audio source change exit code:", exitCode)
if (exitCode === 0) {
console.log("Audio source changed successfully")
// Refresh current source and list
defaultSourceChecker.running = true
audioSourceLister.running = true
} else {
console.error("Failed to change audio source")
}
}
}
// Timer to refresh audio devices regularly (catches new Bluetooth devices)
Timer {
interval: 4000 // 4s refresh to catch new BT devices
running: true; repeat: true
onTriggered: {
audioSinkLister.running = true
audioSourceLister.running = true
}
}
}

View File

@@ -17,14 +17,13 @@ Singleton {
// Real Bluetooth Management
Process {
id: bluetoothStatusChecker
command: ["bluetoothctl", "show"]
command: ["bluetoothctl", "show"] // Use default controller
running: true
stdout: StdioCollector {
onStreamFinished: {
root.bluetoothAvailable = text.trim() !== "" && !text.includes("No default controller")
root.bluetoothEnabled = text.includes("Powered: yes")
console.log("Bluetooth available:", root.bluetoothAvailable, "enabled:", root.bluetoothEnabled)
if (root.bluetoothEnabled && root.bluetoothAvailable) {
bluetoothDeviceScanner.running = true
@@ -37,7 +36,7 @@ Singleton {
Process {
id: bluetoothDeviceScanner
command: ["bash", "-c", "bluetoothctl devices | while read -r line; do if [[ $line =~ Device\\ ([0-9A-F:]+)\\ (.+) ]]; then mac=\"${BASH_REMATCH[1]}\"; name=\"${BASH_REMATCH[2]}\"; if [[ ! $name =~ ^/org/bluez ]]; then info=$(bluetoothctl info $mac); connected=$(echo \"$info\" | grep 'Connected:' | grep -q 'yes' && echo 'true' || echo 'false'); battery=$(echo \"$info\" | grep 'Battery Percentage' | grep -o '([0-9]*)' | tr -d '()'); echo \"$mac|$name|$connected|${battery:-}\"; fi; fi; done"]
command: ["bash", "-c", "bluetoothctl devices | while read -r line; do if [[ $line =~ Device\\ ([0-9A-F:]+)\\ (.+) ]]; then mac=\"${BASH_REMATCH[1]}\"; name=\"${BASH_REMATCH[2]}\"; if [[ ! $name =~ ^/org/bluez ]]; then info=$(bluetoothctl info $mac); connected=$(echo \"$info\" | grep -m1 'Connected:' | awk '{print $2}'); battery=$(echo \"$info\" | grep -m1 'Battery Percentage:' | grep -o '[0-9]\\+'); echo \"$mac|$name|$connected|${battery:-}\"; fi; fi; done"]
running: false
stdout: StdioCollector {
@@ -52,7 +51,7 @@ Singleton {
if (parts.length >= 3) {
let mac = parts[0].trim()
let name = parts[1].trim()
let connected = parts[2].trim() === 'true'
let connected = parts[2].trim() === 'yes'
let battery = parts[3] ? parseInt(parts[3]) : -1
// Skip if name is still a technical path
@@ -82,7 +81,6 @@ Singleton {
}
root.bluetoothDevices = devices
console.log("Found", devices.length, "Bluetooth devices")
}
}
}
@@ -95,23 +93,12 @@ Singleton {
}
function startDiscovery() {
console.log("Starting Bluetooth discovery...")
let discoveryProcess = Qt.createQmlObject('
import Quickshell.Io
Process {
command: ["bluetoothctl", "scan", "on"]
running: true
onExited: {
root.scanning = true
// Scan for 10 seconds then get discovered devices
discoveryScanTimer.start()
}
}
', root)
root.scanning = true
// Run comprehensive scan that gets all devices
discoveryScanner.running = true
}
function stopDiscovery() {
console.log("Stopping Bluetooth discovery...")
let stopDiscoveryProcess = Qt.createQmlObject('
import Quickshell.Io
Process {
@@ -180,7 +167,6 @@ Singleton {
}
function toggleBluetoothDevice(mac) {
console.log("Toggling Bluetooth device:", mac)
let device = root.bluetoothDevices.find(d => d.mac === mac)
if (device) {
let action = device.connected ? "disconnect" : "connect"
@@ -207,26 +193,68 @@ Singleton {
', root)
}
// Timer for discovery scanning
// Timer to refresh adapter & device state
Timer {
id: discoveryScanTimer
interval: 8000 // 8 seconds
repeat: false
interval: 3000 // 3s refresh for more responsive updates
running: true; repeat: true
onTriggered: {
availableDeviceScanner.running = true
bluetoothStatusChecker.running = true
if (root.bluetoothEnabled) {
bluetoothDeviceScanner.running = true
// Also refresh paired devices to get current connection status
pairedDeviceChecker.discoveredToMerge = []
pairedDeviceChecker.running = true
}
}
}
// Scan for available/discoverable devices
property var discoveredDevices: []
// Handle discovered devices
function _handleDiscovered(found) {
let discoveredDevices = []
for (let device of found) {
let type = "bluetooth"
let nameLower = device.name.toLowerCase()
if (nameLower.includes("headphone") || nameLower.includes("airpod") || nameLower.includes("headset") || nameLower.includes("arctis") || nameLower.includes("audio")) type = "headset"
else if (nameLower.includes("mouse")) type = "mouse"
else if (nameLower.includes("keyboard")) type = "keyboard"
else if (nameLower.includes("phone") || nameLower.includes("iphone") || nameLower.includes("samsung") || nameLower.includes("android")) type = "phone"
else if (nameLower.includes("watch")) type = "watch"
else if (nameLower.includes("speaker")) type = "speaker"
else if (nameLower.includes("tv") || nameLower.includes("display")) type = "tv"
discoveredDevices.push({
mac: device.mac,
name: device.name,
type: type,
paired: false,
connected: false,
rssi: -70,
signalStrength: "fair",
canPair: true
})
console.log(" -", device.name, "(", device.mac, ")")
}
// Get paired devices first, then merge with discovered
pairedDeviceChecker.discoveredToMerge = discoveredDevices
pairedDeviceChecker.running = true
}
// Get only currently connected/paired devices that matter
Process {
id: availableDeviceScanner
command: ["bash", "-c", "timeout 5 bluetoothctl devices | grep -v 'Device.*/' | while read -r line; do if [[ $line =~ Device\ ([0-9A-F:]+)\ (.+) ]]; then mac=\"${BASH_REMATCH[1]}\"; name=\"${BASH_REMATCH[2]}\"; if [[ ! $name =~ ^/org/bluez ]] && [[ ! $name =~ hci0 ]]; then info=$(timeout 3 bluetoothctl info $mac 2>/dev/null); paired=$(echo \"$info\" | grep 'Paired:' | grep -q 'yes' && echo 'true' || echo 'false'); connected=$(echo \"$info\" | grep 'Connected:' | grep -q 'yes' && echo 'true' || echo 'false'); rssi=$(echo \"$info\" | grep 'RSSI:' | awk '{print $2}' | head -n1); echo \"$mac|$name|$paired|$connected|${rssi:-}\"; fi; fi; done"]
command: ["bash", "-c", "bluetoothctl devices | while read -r line; do if [[ $line =~ Device\\ ([A-F0-9:]+)\\ (.+) ]]; then mac=\"${BASH_REMATCH[1]}\"; name=\"${BASH_REMATCH[2]}\"; info=$(bluetoothctl info \"$mac\" 2>/dev/null); paired=$(echo \"$info\" | grep -m1 'Paired:' | awk '{print $2}'); connected=$(echo \"$info\" | grep -m1 'Connected:' | awk '{print $2}'); if [[ \"$paired\" == \"yes\" ]] || [[ \"$connected\" == \"yes\" ]]; then echo \"$mac|$name|$paired|$connected\"; fi; fi; done"]
running: false
stdout: StdioCollector {
onStreamFinished: {
let devices = []
if (text.trim()) {
let devices = []
let lines = text.trim().split('\n')
for (let line of lines) {
@@ -235,16 +263,15 @@ Singleton {
if (parts.length >= 4) {
let mac = parts[0].trim()
let name = parts[1].trim()
let paired = parts[2].trim() === 'true'
let connected = parts[3].trim() === 'true'
let rssi = parts[4] ? parseInt(parts[4]) : 0
let paired = parts[2].trim() === 'yes'
let connected = parts[3].trim() === 'yes'
// Skip if name is still a technical path
if (name.startsWith('/org/bluez') || name.includes('hci0')) {
// Skip technical names
if (name.startsWith('/org/bluez') || name.includes('hci0') || name.length < 3) {
continue
}
// Determine device type from name
// Determine device type
let type = "bluetooth"
let nameLower = name.toLowerCase()
if (nameLower.includes("headphone") || nameLower.includes("airpod") || nameLower.includes("headset") || nameLower.includes("arctis") || nameLower.includes("audio")) type = "headset"
@@ -255,32 +282,128 @@ Singleton {
else if (nameLower.includes("speaker")) type = "speaker"
else if (nameLower.includes("tv") || nameLower.includes("display")) type = "tv"
// Signal strength assessment
let signalStrength = "unknown"
if (rssi !== 0) {
if (rssi >= -50) signalStrength = "excellent"
else if (rssi >= -60) signalStrength = "good"
else if (rssi >= -70) signalStrength = "fair"
else signalStrength = "weak"
}
devices.push({
mac: mac,
name: name,
type: type,
paired: paired,
connected: connected,
rssi: rssi,
signalStrength: signalStrength,
canPair: !paired
rssi: 0,
signalStrength: "unknown",
canPair: false // Already paired
})
}
}
}
root.availableDevices = devices
console.log("Found", devices.length, "available Bluetooth devices")
}
root.availableDevices = devices
}
}
}
// Discovery scanner using bluetoothctl --timeout
Process {
id: discoveryScanner
// Discover for 8 s in non-interactive mode, then auto-exit
command: ["bluetoothctl",
"--timeout", "8",
"--monitor", // keeps stdout unbuffered
"scan", "on"]
running: false
stdout: StdioCollector {
onStreamFinished: {
/*
* bluetoothctl prints lines like:
* [NEW] Device 12:34:56:78:9A:BC My-Headphones
*/
const rx = /^\[NEW\] Device ([0-9A-F:]+)\s+(.+)$/i;
const found = text.split('\n')
.filter(l => rx.test(l))
.map(l => {
const [,mac,name] = l.match(rx);
return { mac, name };
});
root._handleDiscovered(found);
}
}
onExited: {
root.scanning = false
}
}
// Get paired devices and merge with discovered ones
Process {
id: pairedDeviceChecker
command: ["bash", "-c", "bluetoothctl devices | while read -r line; do if [[ $line =~ Device\\ ([A-F0-9:]+)\\ (.+) ]]; then mac=\"${BASH_REMATCH[1]}\"; name=\"${BASH_REMATCH[2]}\"; if [[ ${#name} -gt 3 ]] && [[ ! $name =~ ^/org/bluez ]] && [[ ! $name =~ hci0 ]]; then info=$(bluetoothctl info \"$mac\" 2>/dev/null); paired=$(echo \"$info\" | grep -m1 'Paired:' | awk '{print $2}'); connected=$(echo \"$info\" | grep -m1 'Connected:' | awk '{print $2}'); echo \"$mac|$name|$paired|$connected\"; fi; fi; done"]
running: false
property var discoveredToMerge: []
stdout: StdioCollector {
onStreamFinished: {
// Start with discovered devices (unpaired, available to pair)
let allDevices = [...pairedDeviceChecker.discoveredToMerge]
let seenMacs = new Set(allDevices.map(d => d.mac))
// Add only actually paired devices from bluetoothctl
if (text.trim()) {
let lines = text.trim().split('\n')
for (let line of lines) {
if (line.trim()) {
let parts = line.split('|')
if (parts.length >= 4) {
let mac = parts[0].trim()
let name = parts[1].trim()
let paired = parts[2].trim() === 'yes'
let connected = parts[3].trim() === 'yes'
// Only include if actually paired
if (!paired) continue
// Check if already in discovered list
if (seenMacs.has(mac)) {
// Update existing device to show it's paired
let existing = allDevices.find(d => d.mac === mac)
if (existing) {
existing.paired = true
existing.connected = connected
existing.canPair = false
}
continue
}
// Add paired device not found during scan
let type = "bluetooth"
let nameLower = name.toLowerCase()
if (nameLower.includes("headphone") || nameLower.includes("airpod") || nameLower.includes("headset") || nameLower.includes("arctis") || nameLower.includes("audio")) type = "headset"
else if (nameLower.includes("mouse")) type = "mouse"
else if (nameLower.includes("keyboard")) type = "keyboard"
else if (nameLower.includes("phone") || nameLower.includes("iphone") || nameLower.includes("samsung") || nameLower.includes("android")) type = "phone"
else if (nameLower.includes("watch")) type = "watch"
else if (nameLower.includes("speaker")) type = "speaker"
else if (nameLower.includes("tv") || nameLower.includes("display")) type = "tv"
allDevices.push({
mac: mac,
name: name,
type: type,
paired: true,
connected: connected,
rssi: -100,
signalStrength: "unknown",
canPair: false
})
}
}
}
}
root.availableDevices = allDevices
root.scanning = false
}
}
}

View File

@@ -27,16 +27,15 @@ Singleton {
target: modelData
Component.onCompleted: {
console.log("MPRIS Player connected:", modelData.identity)
if (root.trackedPlayer == null || modelData.isPlaying) {
root.trackedPlayer = modelData
}
}
Component.onDestruction: {
if (root.trackedPlayer == null || !root.trackedPlayer.isPlaying) {
if (root.trackedPlayer == null || root.trackedPlayer.playbackState !== MprisPlaybackState.Playing) {
for (const player of Mpris.players.values) {
if (player.playbackState.isPlaying) {
if (player.playbackState === MprisPlaybackState.Playing) {
root.trackedPlayer = player
break
}
@@ -73,7 +72,6 @@ Singleton {
onActivePlayerChanged: this.updateTrack()
function updateTrack() {
console.log(`MPRIS Track Update: ${this.activePlayer?.trackTitle ?? ""} : ${this.activePlayer?.trackArtist}`)
this.activeTrack = {
uniqueId: this.activePlayer?.uniqueId ?? 0,
artUrl: this.activePlayer?.trackArtUrl ?? "",
@@ -86,7 +84,7 @@ Singleton {
this.__reverse = false
}
property bool isPlaying: this.activePlayer && this.activePlayer.isPlaying
property bool isPlaying: this.activePlayer && this.activePlayer.playbackState === MprisPlaybackState.Playing
property bool canTogglePlaying: this.activePlayer?.canTogglePlaying ?? false
function togglePlaying() {
if (this.canTogglePlaying) this.activePlayer.togglePlaying()
@@ -128,7 +126,6 @@ Singleton {
function setActivePlayer(player) {
const targetPlayer = player ?? Mpris.players[0]
console.log(`[Mpris] Active player ${targetPlayer} << ${activePlayer}`)
if (targetPlayer && this.activePlayer) {
this.__reverse = Mpris.players.indexOf(targetPlayer) < Mpris.players.indexOf(this.activePlayer)
@@ -139,22 +136,4 @@ Singleton {
this.trackedPlayer = targetPlayer
}
// Debug timer
Timer {
interval: 3000
running: true
repeat: true
onTriggered: {
if (activePlayer) {
console.log(` Track: ${activePlayer.trackTitle || 'Unknown'} by ${activePlayer.trackArtist || 'Unknown'}`)
console.log(` State: ${activePlayer.playbackState}`)
} else if (Mpris.players.length === 0) {
console.log(" No MPRIS players detected. Try:")
console.log(" - mpv --script-opts=mpris-title='{{media-title}}' file.mp3")
console.log(" - firefox/chromium (YouTube, Spotify Web)")
console.log(" - vlc file.mp3")
console.log(" Check available players: busctl --user list | grep mpris")
}
}
}
}