mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
Modularlize the shell
This commit is contained in:
127
Services/AudioService.qml
Normal file
127
Services/AudioService.qml
Normal file
@@ -0,0 +1,127 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property int volumeLevel: 50
|
||||
property var audioSinks: []
|
||||
property string currentAudioSink: ""
|
||||
|
||||
// Real Audio Control
|
||||
Process {
|
||||
id: volumeChecker
|
||||
command: ["bash", "-c", "pactl get-sink-volume @DEFAULT_SINK@ | grep -o '[0-9]*%' | head -1 | tr -d '%'"]
|
||||
running: true
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
root.volumeLevel = Math.min(100, parseInt(data.trim()) || 50)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: audioSinkLister
|
||||
command: ["bash", "-c", "pactl list sinks | grep -E '^Sink #|device.description|Name:' | paste - - - | sed 's/Sink #//g' | sed 's/Name: //g' | sed 's/device.description = //g' | sed 's/\"//g'"]
|
||||
running: true
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.trim()) {
|
||||
let sinks = []
|
||||
let lines = text.trim().split('\n')
|
||||
|
||||
for (let line of lines) {
|
||||
let parts = line.split('\t')
|
||||
if (parts.length >= 3) {
|
||||
let id = parts[0].trim()
|
||||
let name = parts[1].trim()
|
||||
let description = parts[2].trim()
|
||||
|
||||
// Use description as display name if available, fallback to name processing
|
||||
let displayName = description
|
||||
if (!description || description === name) {
|
||||
if (name.includes("analog-stereo")) displayName = "Built-in Speakers"
|
||||
else if (name.includes("bluez")) displayName = "Bluetooth Audio"
|
||||
else if (name.includes("usb")) displayName = "USB Audio"
|
||||
else if (name.includes("hdmi")) displayName = "HDMI Audio"
|
||||
else if (name.includes("easyeffects")) displayName = "EasyEffects"
|
||||
else displayName = name
|
||||
}
|
||||
|
||||
sinks.push({
|
||||
id: id,
|
||||
name: name,
|
||||
displayName: displayName,
|
||||
active: false // Will be determined by default sink
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
root.audioSinks = sinks
|
||||
defaultSinkChecker.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: defaultSinkChecker
|
||||
command: ["pactl", "get-default-sink"]
|
||||
running: false
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
root.currentAudioSink = data.trim()
|
||||
console.log("Default audio sink:", root.currentAudioSink)
|
||||
|
||||
// Update active status in audioSinks
|
||||
let updatedSinks = []
|
||||
for (let sink of root.audioSinks) {
|
||||
updatedSinks.push({
|
||||
id: sink.id,
|
||||
name: sink.name,
|
||||
displayName: sink.displayName,
|
||||
active: sink.name === root.currentAudioSink
|
||||
})
|
||||
}
|
||||
root.audioSinks = updatedSinks
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setVolume(percentage) {
|
||||
let volumeSetProcess = Qt.createQmlObject('
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
command: ["pactl", "set-sink-volume", "@DEFAULT_SINK@", "' + percentage + '%"]
|
||||
running: true
|
||||
onExited: volumeChecker.running = true
|
||||
}
|
||||
', root)
|
||||
}
|
||||
|
||||
function setAudioSink(sinkName) {
|
||||
let sinkSetProcess = Qt.createQmlObject('
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
command: ["pactl", "set-default-sink", "' + sinkName + '"]
|
||||
running: true
|
||||
onExited: {
|
||||
defaultSinkChecker.running = true
|
||||
audioSinkLister.running = true
|
||||
}
|
||||
}
|
||||
', root)
|
||||
}
|
||||
}
|
||||
121
Services/BluetoothService.qml
Normal file
121
Services/BluetoothService.qml
Normal file
@@ -0,0 +1,121 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property bool bluetoothEnabled: false
|
||||
property bool bluetoothAvailable: false
|
||||
property var bluetoothDevices: []
|
||||
|
||||
// Real Bluetooth Management
|
||||
Process {
|
||||
id: bluetoothStatusChecker
|
||||
command: ["bluetoothctl", "show"]
|
||||
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
|
||||
} else {
|
||||
root.bluetoothDevices = []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.trim()) {
|
||||
let devices = []
|
||||
let lines = text.trim().split('\n')
|
||||
|
||||
for (let line of lines) {
|
||||
if (line.trim()) {
|
||||
let parts = line.split('|')
|
||||
if (parts.length >= 3) {
|
||||
let mac = parts[0].trim()
|
||||
let name = parts[1].trim()
|
||||
let connected = parts[2].trim() === 'true'
|
||||
let battery = parts[3] ? parseInt(parts[3]) : -1
|
||||
|
||||
// Skip if name is still a technical path
|
||||
if (name.startsWith('/org/bluez') || name.includes('hci0')) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Determine device type from name
|
||||
let type = "bluetooth"
|
||||
let nameLower = name.toLowerCase()
|
||||
if (nameLower.includes("headphone") || nameLower.includes("airpod") || nameLower.includes("headset") || nameLower.includes("arctis")) 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")) type = "phone"
|
||||
else if (nameLower.includes("watch")) type = "watch"
|
||||
else if (nameLower.includes("speaker")) type = "speaker"
|
||||
|
||||
devices.push({
|
||||
mac: mac,
|
||||
name: name,
|
||||
type: type,
|
||||
connected: connected,
|
||||
battery: battery
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
root.bluetoothDevices = devices
|
||||
console.log("Found", devices.length, "Bluetooth devices")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scanDevices() {
|
||||
if (root.bluetoothEnabled && root.bluetoothAvailable) {
|
||||
bluetoothDeviceScanner.running = true
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
let toggleProcess = Qt.createQmlObject('
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
command: ["bluetoothctl", "' + action + '", "' + mac + '"]
|
||||
running: true
|
||||
onExited: bluetoothDeviceScanner.running = true
|
||||
}
|
||||
', root)
|
||||
}
|
||||
}
|
||||
|
||||
function toggleBluetooth() {
|
||||
let action = root.bluetoothEnabled ? "off" : "on"
|
||||
let toggleProcess = Qt.createQmlObject('
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
command: ["bluetoothctl", "power", "' + action + '"]
|
||||
running: true
|
||||
onExited: bluetoothStatusChecker.running = true
|
||||
}
|
||||
', root)
|
||||
}
|
||||
}
|
||||
107
Services/BrightnessService.qml
Normal file
107
Services/BrightnessService.qml
Normal file
@@ -0,0 +1,107 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property int brightnessLevel: 75
|
||||
property bool brightnessAvailable: false
|
||||
|
||||
// Check if brightness control is available
|
||||
Process {
|
||||
id: brightnessAvailabilityChecker
|
||||
command: ["bash", "-c", "if command -v brightnessctl > /dev/null; then echo 'brightnessctl'; elif command -v xbacklight > /dev/null; then echo 'xbacklight'; else echo 'none'; fi"]
|
||||
running: true
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
let method = data.trim()
|
||||
if (method === "brightnessctl" || method === "xbacklight") {
|
||||
root.brightnessAvailable = true
|
||||
brightnessChecker.running = true
|
||||
} else {
|
||||
root.brightnessAvailable = false
|
||||
console.log("Brightness control not available - no brightnessctl or xbacklight found")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Brightness Control
|
||||
Process {
|
||||
id: brightnessChecker
|
||||
command: ["bash", "-c", "if command -v brightnessctl > /dev/null; then brightnessctl get; elif command -v xbacklight > /dev/null; then xbacklight -get | cut -d. -f1; else echo 75; fi"]
|
||||
running: false
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
let brightness = parseInt(data.trim()) || 75
|
||||
// brightnessctl returns absolute value, need to convert to percentage
|
||||
if (brightness > 100) {
|
||||
brightnessMaxChecker.running = true
|
||||
} else {
|
||||
root.brightnessLevel = brightness
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: brightnessMaxChecker
|
||||
command: ["brightnessctl", "max"]
|
||||
running: false
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
let maxBrightness = parseInt(data.trim()) || 100
|
||||
brightnessCurrentChecker.property("maxBrightness", maxBrightness)
|
||||
brightnessCurrentChecker.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: brightnessCurrentChecker
|
||||
property int maxBrightness: 100
|
||||
command: ["brightnessctl", "get"]
|
||||
running: false
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
let currentBrightness = parseInt(data.trim()) || 75
|
||||
root.brightnessLevel = Math.round((currentBrightness / maxBrightness) * 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setBrightness(percentage) {
|
||||
if (!root.brightnessAvailable) {
|
||||
console.warn("Brightness control not available")
|
||||
return
|
||||
}
|
||||
|
||||
let brightnessSetProcess = Qt.createQmlObject('
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
command: ["bash", "-c", "if command -v brightnessctl > /dev/null; then brightnessctl set ' + percentage + '%; elif command -v xbacklight > /dev/null; then xbacklight -set ' + percentage + '; fi"]
|
||||
running: true
|
||||
onExited: brightnessChecker.running = true
|
||||
}
|
||||
', root)
|
||||
}
|
||||
}
|
||||
26
Services/ColorPickerService.qml
Normal file
26
Services/ColorPickerService.qml
Normal file
@@ -0,0 +1,26 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Color Picker Process
|
||||
Process {
|
||||
id: colorPickerProcess
|
||||
command: ["hyprpicker", "-a"]
|
||||
running: false
|
||||
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("Color picker failed. Make sure hyprpicker is installed: yay -S hyprpicker")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pickColor() {
|
||||
colorPickerProcess.running = true
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQml.Models
|
||||
import QtQuick
|
||||
import QtQml.Models
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Mpris
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
/**
|
||||
* A service that provides easy access to the active Mpris player.
|
||||
|
||||
159
Services/NetworkService.qml
Normal file
159
Services/NetworkService.qml
Normal file
@@ -0,0 +1,159 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property string networkStatus: "disconnected" // "ethernet", "wifi", "disconnected"
|
||||
property string ethernetIP: ""
|
||||
property string wifiIP: ""
|
||||
property bool wifiAvailable: false
|
||||
property bool wifiEnabled: true
|
||||
|
||||
// Real Network Management
|
||||
Process {
|
||||
id: networkStatusChecker
|
||||
command: ["bash", "-c", "nmcli -t -f DEVICE,TYPE,STATE device | grep -E '(ethernet|wifi)' && echo '---' && ip link show | grep -E '^[0-9]+:.*ethernet.*state UP'"]
|
||||
running: true
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.trim()) {
|
||||
console.log("Network status full output:", text.trim())
|
||||
|
||||
let hasEthernet = text.includes("ethernet:connected")
|
||||
let hasWifi = text.includes("wifi:connected")
|
||||
let ethernetCableUp = text.includes("state UP")
|
||||
|
||||
// Check if ethernet cable is physically connected but not managed
|
||||
if (hasEthernet || ethernetCableUp) {
|
||||
root.networkStatus = "ethernet"
|
||||
ethernetIPChecker.running = true
|
||||
console.log("Setting network status to ethernet (cable connected)")
|
||||
} else if (hasWifi) {
|
||||
root.networkStatus = "wifi"
|
||||
wifiIPChecker.running = true
|
||||
console.log("Setting network status to wifi")
|
||||
} else {
|
||||
root.networkStatus = "disconnected"
|
||||
root.ethernetIP = ""
|
||||
root.wifiIP = ""
|
||||
console.log("Setting network status to disconnected")
|
||||
}
|
||||
|
||||
// Always check WiFi radio status
|
||||
wifiRadioChecker.running = true
|
||||
} else {
|
||||
root.networkStatus = "disconnected"
|
||||
root.ethernetIP = ""
|
||||
root.wifiIP = ""
|
||||
console.log("No network output, setting to disconnected")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: wifiRadioChecker
|
||||
command: ["nmcli", "radio", "wifi"]
|
||||
running: false
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
let response = data.trim()
|
||||
root.wifiAvailable = response === "enabled" || response === "disabled"
|
||||
root.wifiEnabled = response === "enabled"
|
||||
console.log("WiFi available:", root.wifiAvailable, "enabled:", root.wifiEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: ethernetIPChecker
|
||||
command: ["bash", "-c", "ip route get 1.1.1.1 | grep -oP 'src \\K\\S+' | head -1"]
|
||||
running: false
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
root.ethernetIP = data.trim()
|
||||
console.log("Ethernet IP:", root.ethernetIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: wifiIPChecker
|
||||
command: ["bash", "-c", "nmcli -t -f IP4.ADDRESS dev show $(nmcli -t -f DEVICE,TYPE device | grep wifi | cut -d: -f1 | head -1) | cut -d: -f2 | cut -d/ -f1"]
|
||||
running: false
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
root.wifiIP = data.trim()
|
||||
console.log("WiFi IP:", root.wifiIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleNetworkConnection(type) {
|
||||
if (type === "ethernet") {
|
||||
// Toggle ethernet connection
|
||||
if (root.networkStatus === "ethernet") {
|
||||
// Disconnect ethernet
|
||||
let disconnectProcess = Qt.createQmlObject('
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
command: ["bash", "-c", "nmcli device disconnect $(nmcli -t -f DEVICE,TYPE device | grep ethernet | cut -d: -f1 | head -1)"]
|
||||
running: true
|
||||
onExited: networkStatusChecker.running = true
|
||||
}
|
||||
', root)
|
||||
} else {
|
||||
// Connect ethernet with proper nmcli device connect
|
||||
let connectProcess = Qt.createQmlObject('
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
command: ["bash", "-c", "nmcli device connect $(nmcli -t -f DEVICE,TYPE device | grep ethernet | cut -d: -f1 | head -1)"]
|
||||
running: true
|
||||
onExited: networkStatusChecker.running = true
|
||||
}
|
||||
', root)
|
||||
}
|
||||
} else if (type === "wifi") {
|
||||
// Connect to WiFi if disconnected
|
||||
if (root.networkStatus !== "wifi" && root.wifiEnabled) {
|
||||
let connectProcess = Qt.createQmlObject('
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
command: ["bash", "-c", "nmcli device connect $(nmcli -t -f DEVICE,TYPE device | grep wifi | cut -d: -f1 | head -1)"]
|
||||
running: true
|
||||
onExited: networkStatusChecker.running = true
|
||||
}
|
||||
', root)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleWifiRadio() {
|
||||
let action = root.wifiEnabled ? "off" : "on"
|
||||
let toggleProcess = Qt.createQmlObject('
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
command: ["nmcli", "radio", "wifi", "' + action + '"]
|
||||
running: true
|
||||
onExited: {
|
||||
networkStatusChecker.running = true
|
||||
}
|
||||
}
|
||||
', root)
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
QtObject {
|
||||
id: osService
|
||||
|
||||
property string osLogo: ""
|
||||
property string osName: ""
|
||||
|
||||
Process {
|
||||
id: osDetector
|
||||
command: ["lsb_release", "-i", "-s"]
|
||||
running: true
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
let osId = data.trim().toLowerCase()
|
||||
console.log("Detected OS:", osId)
|
||||
setOSInfo(osId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
osDetectorFallback.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: osDetectorFallback
|
||||
command: ["sh", "-c", "cat /etc/os-release | grep '^ID=' | cut -d'=' -f2 | tr -d '\"'"]
|
||||
running: false
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
let osId = data.trim().toLowerCase()
|
||||
console.log("Detected OS (fallback):", osId)
|
||||
setOSInfo(osId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
osService.osLogo = ""
|
||||
osService.osName = "Linux"
|
||||
console.log("OS detection failed, using generic icon")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setOSInfo(osId) {
|
||||
if (osId.includes("arch")) {
|
||||
osService.osLogo = "\uf303"
|
||||
osService.osName = "Arch Linux"
|
||||
} else if (osId.includes("ubuntu")) {
|
||||
osService.osLogo = "\uf31b"
|
||||
osService.osName = "Ubuntu"
|
||||
} else if (osId.includes("fedora")) {
|
||||
osService.osLogo = "\uf30a"
|
||||
osService.osName = "Fedora"
|
||||
} else if (osId.includes("debian")) {
|
||||
osService.osLogo = "\uf306"
|
||||
osService.osName = "Debian"
|
||||
} else if (osId.includes("opensuse")) {
|
||||
osService.osLogo = "\uef6d"
|
||||
osService.osName = "openSUSE"
|
||||
} else if (osId.includes("manjaro")) {
|
||||
osService.osLogo = "\uf312"
|
||||
osService.osName = "Manjaro"
|
||||
} else {
|
||||
osService.osLogo = "\uf033"
|
||||
osService.osName = "Linux"
|
||||
}
|
||||
}
|
||||
}
|
||||
110
Services/OSDetectorService.qml
Normal file
110
Services/OSDetectorService.qml
Normal file
@@ -0,0 +1,110 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property string osLogo: ""
|
||||
property string osName: ""
|
||||
|
||||
// OS Detection
|
||||
Process {
|
||||
id: osDetector
|
||||
command: ["lsb_release", "-i", "-s"]
|
||||
running: true
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
let osId = data.trim().toLowerCase()
|
||||
console.log("Detected OS:", osId)
|
||||
|
||||
// Set OS-specific Nerd Font icons and names
|
||||
if (osId.includes("arch")) {
|
||||
root.osLogo = "\uf303" // Arch Linux Nerd Font icon
|
||||
root.osName = "Arch Linux"
|
||||
console.log("Set Arch logo:", root.osLogo)
|
||||
} else if (osId.includes("ubuntu")) {
|
||||
root.osLogo = "\uf31b" // Ubuntu Nerd Font icon
|
||||
root.osName = "Ubuntu"
|
||||
} else if (osId.includes("fedora")) {
|
||||
root.osLogo = "\uf30a" // Fedora Nerd Font icon
|
||||
root.osName = "Fedora"
|
||||
} else if (osId.includes("debian")) {
|
||||
root.osLogo = "\uf306" // Debian Nerd Font icon
|
||||
root.osName = "Debian"
|
||||
} else if (osId.includes("opensuse")) {
|
||||
root.osLogo = "\uef6d" // openSUSE Nerd Font icon
|
||||
root.osName = "openSUSE"
|
||||
} else if (osId.includes("manjaro")) {
|
||||
root.osLogo = "\uf312" // Manjaro Nerd Font icon
|
||||
root.osName = "Manjaro"
|
||||
} else {
|
||||
root.osLogo = "\uf033" // Generic Linux Nerd Font icon
|
||||
root.osName = "Linux"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
// Fallback: try checking /etc/os-release
|
||||
osDetectorFallback.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback OS detection
|
||||
Process {
|
||||
id: osDetectorFallback
|
||||
command: ["sh", "-c", "grep '^ID=' /etc/os-release | cut -d'=' -f2 | tr -d '\"'"]
|
||||
running: false
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
let osId = data.trim().toLowerCase()
|
||||
console.log("Detected OS (fallback):", osId)
|
||||
|
||||
if (osId.includes("arch")) {
|
||||
root.osLogo = "\uf303"
|
||||
root.osName = "Arch Linux"
|
||||
} else if (osId.includes("ubuntu")) {
|
||||
root.osLogo = "\uf31b"
|
||||
root.osName = "Ubuntu"
|
||||
} else if (osId.includes("fedora")) {
|
||||
root.osLogo = "\uf30a"
|
||||
root.osName = "Fedora"
|
||||
} else if (osId.includes("debian")) {
|
||||
root.osLogo = "\uf306"
|
||||
root.osName = "Debian"
|
||||
} else if (osId.includes("opensuse")) {
|
||||
root.osLogo = "\uef6d"
|
||||
root.osName = "openSUSE"
|
||||
} else if (osId.includes("manjaro")) {
|
||||
root.osLogo = "\uf312"
|
||||
root.osName = "Manjaro"
|
||||
} else {
|
||||
root.osLogo = "\uf033"
|
||||
root.osName = "Linux"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
// Ultimate fallback - use generic apps icon (empty logo means fallback to "apps")
|
||||
root.osLogo = ""
|
||||
root.osName = "Linux"
|
||||
console.log("OS detection failed, using generic icon")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
QtObject {
|
||||
id: weatherService
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property var weather: ({
|
||||
available: false,
|
||||
@@ -32,7 +32,7 @@ QtObject {
|
||||
try {
|
||||
let parsedData = JSON.parse(text.trim())
|
||||
if (parsedData.current && parsedData.location) {
|
||||
weatherService.weather = {
|
||||
root.weather = {
|
||||
available: true,
|
||||
temp: parseInt(parsedData.current.temp_C || 0),
|
||||
tempF: parseInt(parsedData.current.temp_F || 0),
|
||||
@@ -45,15 +45,15 @@ QtObject {
|
||||
uv: parseInt(parsedData.current.uvIndex || 0),
|
||||
pressure: parseInt(parsedData.current.pressure || 0)
|
||||
}
|
||||
console.log("Weather updated:", weatherService.weather.city, weatherService.weather.temp + "°C")
|
||||
console.log("Weather updated:", root.weather.city, root.weather.temp + "°C")
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Failed to parse weather data:", e.message)
|
||||
weatherService.weather.available = false
|
||||
root.weather.available = false
|
||||
}
|
||||
} else {
|
||||
console.warn("No valid weather data received")
|
||||
weatherService.weather.available = false
|
||||
root.weather.available = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ QtObject {
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("Weather fetch failed with exit code:", exitCode)
|
||||
weatherService.weather.available = false
|
||||
root.weather.available = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
172
Services/WifiService.qml
Normal file
172
Services/WifiService.qml
Normal file
@@ -0,0 +1,172 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property string currentWifiSSID: ""
|
||||
property string wifiSignalStrength: "excellent" // "excellent", "good", "fair", "poor"
|
||||
property var wifiNetworks: []
|
||||
property var savedWifiNetworks: []
|
||||
|
||||
Process {
|
||||
id: currentWifiInfo
|
||||
command: ["bash", "-c", "nmcli -t -f ssid,signal connection show --active | grep -v '^--' | grep -v '^$'"]
|
||||
running: false
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
let parts = data.split(":")
|
||||
if (parts.length >= 2 && parts[0].trim() !== "") {
|
||||
root.currentWifiSSID = parts[0].trim()
|
||||
let signal = parseInt(parts[1]) || 100
|
||||
|
||||
if (signal >= 75) root.wifiSignalStrength = "excellent"
|
||||
else if (signal >= 50) root.wifiSignalStrength = "good"
|
||||
else if (signal >= 25) root.wifiSignalStrength = "fair"
|
||||
else root.wifiSignalStrength = "poor"
|
||||
|
||||
console.log("Active WiFi:", root.currentWifiSSID, "Signal:", signal + "%")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: wifiScanner
|
||||
command: ["nmcli", "-t", "-f", "SSID,SIGNAL,SECURITY", "dev", "wifi"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.trim()) {
|
||||
let networks = []
|
||||
let lines = text.trim().split('\n')
|
||||
|
||||
for (let line of lines) {
|
||||
let parts = line.split(':')
|
||||
if (parts.length >= 3 && parts[0].trim() !== "") {
|
||||
let ssid = parts[0].trim()
|
||||
let signal = parseInt(parts[1]) || 0
|
||||
let security = parts[2].trim()
|
||||
|
||||
// Skip duplicates
|
||||
if (!networks.find(n => n.ssid === ssid)) {
|
||||
networks.push({
|
||||
ssid: ssid,
|
||||
signal: signal,
|
||||
secured: security !== "",
|
||||
connected: ssid === root.currentWifiSSID,
|
||||
signalStrength: signal >= 75 ? "excellent" :
|
||||
signal >= 50 ? "good" :
|
||||
signal >= 25 ? "fair" : "poor"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by signal strength
|
||||
networks.sort((a, b) => b.signal - a.signal)
|
||||
root.wifiNetworks = networks
|
||||
console.log("Found", networks.length, "WiFi networks")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: savedWifiScanner
|
||||
command: ["nmcli", "-t", "-f", "NAME", "connection", "show"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.trim()) {
|
||||
let saved = []
|
||||
let lines = text.trim().split('\n')
|
||||
|
||||
for (let line of lines) {
|
||||
if (line.trim() && !line.includes("ethernet") && !line.includes("lo")) {
|
||||
saved.push({
|
||||
ssid: line.trim(),
|
||||
saved: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
root.savedWifiNetworks = saved
|
||||
console.log("Found", saved.length, "saved WiFi networks")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scanWifi() {
|
||||
wifiScanner.running = true
|
||||
savedWifiScanner.running = true
|
||||
currentWifiInfo.running = true
|
||||
}
|
||||
|
||||
function connectToWifi(ssid) {
|
||||
console.log("Connecting to WiFi:", ssid)
|
||||
|
||||
let connectProcess = Qt.createQmlObject('
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
command: ["nmcli", "dev", "wifi", "connect", "' + ssid + '"]
|
||||
running: true
|
||||
onExited: (exitCode) => {
|
||||
console.log("WiFi connection result:", exitCode)
|
||||
if (exitCode === 0) {
|
||||
console.log("Connected to WiFi successfully")
|
||||
} else {
|
||||
console.log("WiFi connection failed")
|
||||
}
|
||||
scanWifi()
|
||||
}
|
||||
}
|
||||
', root)
|
||||
}
|
||||
|
||||
function connectToWifiWithPassword(ssid, password) {
|
||||
console.log("Connecting to WiFi with password:", ssid)
|
||||
|
||||
let connectProcess = Qt.createQmlObject('
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
command: ["nmcli", "dev", "wifi", "connect", "' + ssid + '", "password", "' + password + '"]
|
||||
running: true
|
||||
onExited: (exitCode) => {
|
||||
console.log("WiFi connection with password result:", exitCode)
|
||||
if (exitCode === 0) {
|
||||
console.log("Connected to WiFi with password successfully")
|
||||
} else {
|
||||
console.log("WiFi connection with password failed")
|
||||
}
|
||||
scanWifi()
|
||||
}
|
||||
}
|
||||
', root)
|
||||
}
|
||||
|
||||
function forgetWifiNetwork(ssid) {
|
||||
console.log("Forgetting WiFi network:", ssid)
|
||||
let forgetProcess = Qt.createQmlObject('
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
command: ["nmcli", "connection", "delete", "' + ssid + '"]
|
||||
running: true
|
||||
onExited: (exitCode) => {
|
||||
console.log("WiFi forget result:", exitCode)
|
||||
scanWifi()
|
||||
}
|
||||
}
|
||||
', root)
|
||||
}
|
||||
}
|
||||
@@ -1 +1,9 @@
|
||||
singleton MprisController 1.0 MprisController.qml
|
||||
singleton MprisController 1.0 MprisController.qml
|
||||
singleton OSDetectorService 1.0 OSDetectorService.qml
|
||||
singleton ColorPickerService 1.0 ColorPickerService.qml
|
||||
singleton WeatherService 1.0 WeatherService.qml
|
||||
singleton NetworkService 1.0 NetworkService.qml
|
||||
singleton WifiService 1.0 WifiService.qml
|
||||
singleton AudioService 1.0 AudioService.qml
|
||||
singleton BluetoothService 1.0 BluetoothService.qml
|
||||
singleton BrightnessService 1.0 BrightnessService.qml
|
||||
Reference in New Issue
Block a user