1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-29 16:02:51 -05:00

refactor: start trimming AI bullcrap patterns

This commit is contained in:
bbedward
2025-07-17 13:25:35 -04:00
parent cc9b575a1d
commit 77cc9c288b
21 changed files with 541 additions and 1069 deletions

View File

@@ -28,8 +28,6 @@ Singleton {
function onReadyChanged() { _rebuildModels() }
function onDefaultAudioSinkChanged() { _rebuildModels() }
function onDefaultAudioSourceChanged() { _rebuildModels() }
function onNodeAdded() { _rebuildModels() }
function onNodeRemoved() { _rebuildModels() }
}
Timer {

View File

@@ -7,7 +7,7 @@ import Quickshell.Services.UPower
Singleton {
id: root
readonly property var device: UPower.displayDevice
readonly property UPowerDevice device: UPower.displayDevice
readonly property bool batteryAvailable: device && device.ready && device.isLaptopBattery
readonly property int batteryLevel: batteryAvailable ? device.percentage * 100.0 : 0
readonly property bool isCharging: batteryAvailable && device.state === UPowerDeviceState.Charging

View File

@@ -7,477 +7,170 @@ import Quickshell.Bluetooth
Singleton {
id: root
property bool bluetoothEnabled: false
property bool bluetoothAvailable: false
readonly property list<BluetoothDevice> bluetoothDevices: []
readonly property list<BluetoothDevice> availableDevices: []
property bool scanning: false
property bool discoverable: false
readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter
readonly property bool available: adapter !== null
readonly property bool enabled: adapter?.enabled ?? false
readonly property bool discovering: adapter?.discovering ?? false
property var connectingDevices: ({})
readonly property var devices: {
var deviceList = []
if (!adapter) return deviceList
for (var i = 0; i < adapter.devices.count; i++) {
var dev = adapter.devices.get(i)
if (dev && dev.ready && _isValidDevice(dev)) {
deviceList.push({
address: dev.address,
name: dev.name || dev.deviceName,
paired: dev.paired,
connected: dev.connected,
iconName: _getDeviceIcon(dev),
type: _getDeviceType(dev),
batteryLevel: dev.batteryAvailable ? Math.round(dev.battery * 100) : -1,
batteryAvailable: dev.batteryAvailable,
native: dev
})
}
}
return deviceList
}
readonly property var pairedDevices: {
return devices.filter(dev => dev.paired)
}
readonly property var availableDevices: {
if (!discovering) return []
var availableList = []
if (Bluetooth.devices && Bluetooth.devices.values) {
for (var device of Bluetooth.devices.values) {
if (device && device.ready && !device.paired && _isValidDevice(device)) {
availableList.push({
address: device.address,
name: device.name || device.deviceName,
paired: false,
connected: false,
iconName: _getDeviceIcon(device),
type: _getDeviceType(device),
batteryLevel: -1,
batteryAvailable: false,
native: device
})
}
}
}
return availableList
}
readonly property var allDevicesWithBattery: {
return devices.filter(dev => dev.batteryAvailable && dev.batteryLevel >= 0)
}
Component.onCompleted: {
refreshBluetoothState()
updateDevices()
if (adapter && adapter.devices) {
adapter.devices.itemAdded.connect(devicesChanged)
adapter.devices.itemRemoved.connect(devicesChanged)
}
if (Bluetooth.devices) {
Bluetooth.devices.itemAdded.connect(devicesChanged)
Bluetooth.devices.itemRemoved.connect(devicesChanged)
}
}
Connections {
target: Bluetooth
function onDefaultAdapterChanged() {
console.log("BluetoothService: Default adapter changed")
refreshBluetoothState()
updateDevices()
}
}
Connections {
target: Bluetooth.defaultAdapter
function onEnabledChanged() {
refreshBluetoothState()
updateDevices()
}
function onDiscoveringChanged() {
refreshBluetoothState()
updateDevices()
}
}
Connections {
target: Bluetooth.defaultAdapter ? Bluetooth.defaultAdapter.devices : null
function onModelReset() {
updateDevices()
}
function onItemAdded() {
updateDevices()
}
function onItemRemoved() {
updateDevices()
}
}
Connections {
target: Bluetooth.devices
function onModelReset() {
updateDevices()
}
function onItemAdded() {
updateDevices()
}
function onItemRemoved() {
updateDevices()
}
}
function refreshBluetoothState() {
root.bluetoothAvailable = Bluetooth.defaultAdapter !== null
root.bluetoothEnabled = Bluetooth.defaultAdapter ? Bluetooth.defaultAdapter.enabled : false
root.scanning = Bluetooth.defaultAdapter ? Bluetooth.defaultAdapter.discovering : false
root.discoverable = Bluetooth.defaultAdapter ? Bluetooth.defaultAdapter.discoverable : false
}
function updateDevices() {
if (!Bluetooth.defaultAdapter) {
clearDeviceList(root.bluetoothDevices)
clearDeviceList(root.availableDevices)
root.bluetoothDevices = []
root.availableDevices = []
return
}
let newPairedDevices = []
let newAvailableDevices = []
let allNativeDevices = []
let adapterDevices = Bluetooth.defaultAdapter.devices
if (adapterDevices.values) {
allNativeDevices = allNativeDevices.concat(adapterDevices.values)
}
if (Bluetooth.devices.values) {
for (let device of Bluetooth.devices.values) {
if (!allNativeDevices.some(d => d.address === device.address)) {
allNativeDevices.push(device)
}
}
}
for (let device of allNativeDevices) {
if (!device) continue
let deviceType = getDeviceType(device.name || device.deviceName, device.icon)
let displayName = device.name || device.deviceName
if (!displayName || displayName.startsWith('/org/bluez') || displayName.includes('hci0') || displayName.length < 2) {
continue
}
if (device.paired) {
let existingDevice = findDeviceInList(root.bluetoothDevices, device.address)
if (existingDevice) {
updateDeviceData(existingDevice, device, deviceType, displayName)
newPairedDevices.push(existingDevice)
} else {
let newDevice = createBluetoothDevice(device, deviceType, displayName)
newPairedDevices.push(newDevice)
}
} else {
if (Bluetooth.defaultAdapter.discovering && isDeviceDiscoverable(device)) {
let existingDevice = findDeviceInList(root.availableDevices, device.address)
if (existingDevice) {
updateDeviceData(existingDevice, device, deviceType, displayName)
newAvailableDevices.push(existingDevice)
} else {
let newDevice = createBluetoothDevice(device, deviceType, displayName)
newAvailableDevices.push(newDevice)
}
}
}
}
cleanupOldDevices(root.bluetoothDevices, newPairedDevices)
cleanupOldDevices(root.availableDevices, newAvailableDevices)
console.log("BluetoothService: Found", newPairedDevices.length, "paired devices and", newAvailableDevices.length, "available devices")
root.bluetoothDevices = newPairedDevices
root.availableDevices = newAvailableDevices
}
function createBluetoothDevice(nativeDevice, deviceType, displayName) {
return deviceComponent.createObject(root, {
mac: nativeDevice.address,
name: displayName,
type: deviceType,
paired: nativeDevice.paired,
connected: nativeDevice.connected,
battery: nativeDevice.batteryAvailable ? Math.round(nativeDevice.battery * 100) : -1,
signalStrength: nativeDevice.connected ? "excellent" : "unknown",
canPair: !nativeDevice.paired,
nativeDevice: nativeDevice,
connecting: false,
connectionFailed: false
})
}
function updateDeviceData(deviceObj, nativeDevice, deviceType, displayName) {
deviceObj.name = displayName
deviceObj.type = deviceType
deviceObj.paired = nativeDevice.paired
// If device connected state changed, clear connecting/failed states and refresh audio
if (deviceObj.connected !== nativeDevice.connected) {
deviceObj.connecting = false
deviceObj.connectionFailed = false
// Refresh audio devices when bluetooth audio device connects/disconnects
if (deviceType === "headset" || deviceType === "speaker") {
Qt.callLater(() => {
if (typeof AudioService !== 'undefined') {
AudioService.updateDevices()
}
})
}
}
deviceObj.connected = nativeDevice.connected
deviceObj.battery = nativeDevice.batteryAvailable ? Math.round(nativeDevice.battery * 100) : -1
deviceObj.signalStrength = nativeDevice.connected ? "excellent" : "unknown"
deviceObj.canPair = !nativeDevice.paired
deviceObj.nativeDevice = nativeDevice
}
function findDeviceInList(deviceList, address) {
for (let device of deviceList) {
if (device.mac === address) {
return device
}
}
return null
}
function cleanupOldDevices(oldList, newList) {
for (let oldDevice of oldList) {
if (!newList.includes(oldDevice)) {
oldDevice.destroy()
if (adapter && adapter.devices) {
adapter.devices.itemAdded.connect(devicesChanged)
adapter.devices.itemRemoved.connect(devicesChanged)
}
}
}
function clearDeviceList(deviceList) {
for (let device of deviceList) {
device.destroy()
}
}
function isDeviceDiscoverable(device) {
let displayName = device.name || device.deviceName
function _isValidDevice(device) {
var displayName = device.name || device.deviceName
if (!displayName || displayName.length < 2) return false
if (displayName.startsWith('/org/bluez') || displayName.includes('hci0')) return false
let nameLower = displayName.toLowerCase()
if (nameLower.match(/^[0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}/)) {
return false
}
if (displayName.length < 3) return false
if (nameLower.includes('iphone') || nameLower.includes('ipad') ||
nameLower.includes('airpods') || nameLower.includes('samsung') ||
nameLower.includes('galaxy') || nameLower.includes('pixel') ||
nameLower.includes('headphone') || nameLower.includes('speaker') ||
nameLower.includes('mouse') || nameLower.includes('keyboard') ||
nameLower.includes('watch') || nameLower.includes('buds') ||
nameLower.includes('android')) {
return true
}
return displayName.length >= 4 && !displayName.match(/^[A-Z0-9_-]+$/)
return displayName.length >= 3
}
function getDeviceType(name, icon) {
if (!name && !icon) return "bluetooth"
let nameLower = (name || "").toLowerCase()
let iconLower = (icon || "").toLowerCase()
if (iconLower.includes("audio") || iconLower.includes("headset") || iconLower.includes("headphone") ||
nameLower.includes("headphone") || nameLower.includes("airpod") || nameLower.includes("headset") ||
nameLower.includes("arctis") || nameLower.includes("audio")) return "headset"
else if (iconLower.includes("input-mouse") || nameLower.includes("mouse")) return "mouse"
else if (iconLower.includes("input-keyboard") || nameLower.includes("keyboard")) return "keyboard"
else if (iconLower.includes("phone") || nameLower.includes("phone") || nameLower.includes("iphone") ||
nameLower.includes("samsung") || nameLower.includes("android")) return "phone"
else if (iconLower.includes("watch") || nameLower.includes("watch")) return "watch"
else if (iconLower.includes("audio-speakers") || nameLower.includes("speaker")) return "speaker"
else if (iconLower.includes("video-display") || nameLower.includes("tv") || nameLower.includes("display")) return "tv"
function _getDeviceIcon(device) {
var name = (device.name || device.deviceName || "").toLowerCase()
var icon = (device.icon || "").toLowerCase()
if (icon.includes("headset") || icon.includes("audio") || name.includes("headphone") ||
name.includes("airpod") || name.includes("headset") || name.includes("arctis")) return "headset"
if (icon.includes("mouse") || name.includes("mouse")) return "mouse"
if (icon.includes("keyboard") || name.includes("keyboard")) return "keyboard"
if (icon.includes("phone") || name.includes("phone") || name.includes("iphone") ||
name.includes("android") || name.includes("samsung")) return "smartphone"
if (icon.includes("watch") || name.includes("watch")) return "watch"
if (icon.includes("speaker") || name.includes("speaker")) return "speaker"
if (icon.includes("display") || name.includes("tv")) return "tv"
return "bluetooth"
}
function startDiscovery() {
if (Bluetooth.defaultAdapter && Bluetooth.defaultAdapter.enabled) {
Bluetooth.defaultAdapter.discovering = true
updateDevices()
}
function _getDeviceType(device) {
var name = (device.name || device.deviceName || "").toLowerCase()
var icon = (device.icon || "").toLowerCase()
if (icon.includes("headset") || icon.includes("audio") || name.includes("headphone") ||
name.includes("airpod") || name.includes("headset") || name.includes("arctis")) return "headset"
if (icon.includes("mouse") || name.includes("mouse")) return "mouse"
if (icon.includes("keyboard") || name.includes("keyboard")) return "keyboard"
if (icon.includes("phone") || name.includes("phone") || name.includes("iphone") ||
name.includes("android") || name.includes("samsung")) return "phone"
if (icon.includes("watch") || name.includes("watch")) return "watch"
if (icon.includes("speaker") || name.includes("speaker")) return "speaker"
if (icon.includes("display") || name.includes("tv")) return "tv"
return "bluetooth"
}
function stopDiscovery() {
if (Bluetooth.defaultAdapter) {
Bluetooth.defaultAdapter.discovering = false
updateDevices()
}
function toggleAdapter() {
if (adapter) adapter.enabled = !adapter.enabled
}
function pairDevice(mac) {
console.log("Pairing device:", mac)
let device = findDeviceByMac(mac)
function startScan() {
if (adapter) adapter.discovering = true
}
function stopScan() {
if (adapter) adapter.discovering = false
}
function connect(address) {
var device = _findDevice(address)
if (device) device.connect()
}
function disconnect(address) {
var device = _findDevice(address)
if (device) device.disconnect()
}
function pair(address) {
var device = _findDevice(address)
if (device) device.pair()
}
function forget(address) {
var device = _findDevice(address)
if (device) device.forget()
}
function toggle(address) {
var device = _findDevice(address)
if (device) {
device.pair()
if (device.connected) device.disconnect()
else device.connect()
}
}
function connectDevice(mac) {
console.log("Connecting to device:", mac)
let device = findDeviceByMac(mac)
if (device) {
device.connect()
}
}
function removeDevice(mac) {
console.log("Removing device:", mac)
let device = findDeviceByMac(mac)
if (device) {
device.forget()
}
}
function toggleBluetoothDevice(mac) {
let typedDevice = findDeviceInList(root.bluetoothDevices, mac)
if (!typedDevice) {
typedDevice = findDeviceInList(root.availableDevices, mac)
}
if (typedDevice && typedDevice.nativeDevice) {
if (typedDevice.connected) {
console.log("Disconnecting device:", mac)
typedDevice.connecting = false
typedDevice.connectionFailed = false
typedDevice.nativeDevice.connected = false
} else {
console.log("Connecting to device:", mac)
typedDevice.connecting = true
typedDevice.connectionFailed = false
// Set a timeout to handle connection failure
Qt.callLater(() => {
connectionTimeout.deviceMac = mac
connectionTimeout.start()
})
typedDevice.nativeDevice.connected = true
}
}
}
function toggleBluetooth() {
if (Bluetooth.defaultAdapter) {
Bluetooth.defaultAdapter.enabled = !Bluetooth.defaultAdapter.enabled
}
}
function findDeviceByMac(mac) {
let typedDevice = findDeviceInList(root.bluetoothDevices, mac)
if (typedDevice && typedDevice.nativeDevice) {
return typedDevice.nativeDevice
}
typedDevice = findDeviceInList(root.availableDevices, mac)
if (typedDevice && typedDevice.nativeDevice) {
return typedDevice.nativeDevice
}
if (Bluetooth.defaultAdapter) {
let adapterDevices = Bluetooth.defaultAdapter.devices
if (adapterDevices.values) {
for (let device of adapterDevices.values) {
if (device && device.address === mac) {
return device
}
}
}
}
if (Bluetooth.devices.values) {
for (let device of Bluetooth.devices.values) {
if (device && device.address === mac) {
return device
}
}
}
return null
}
Timer {
id: bluetoothMonitorTimer
interval: 2000
running: false
repeat: true
onTriggered: {
updateDevices()
}
}
function enableMonitoring(enabled) {
bluetoothMonitorTimer.running = enabled
if (enabled) {
refreshBluetoothState()
updateDevices()
}
}
Timer {
id: bluetoothStateRefreshTimer
interval: 5000
running: true
repeat: true
onTriggered: {
refreshBluetoothState()
}
}
Timer {
id: connectionTimeout
interval: 10000 // 10 second timeout
running: false
repeat: false
property string deviceMac: ""
onTriggered: {
if (deviceMac) {
let typedDevice = findDeviceInList(root.bluetoothDevices, deviceMac)
if (!typedDevice) {
typedDevice = findDeviceInList(root.availableDevices, deviceMac)
}
if (typedDevice && typedDevice.connecting && !typedDevice.connected) {
console.log("Connection timeout for device:", deviceMac)
typedDevice.connecting = false
typedDevice.connectionFailed = true
// Clear failure state after 3 seconds
Qt.callLater(() => {
clearFailureTimer.deviceMac = deviceMac
clearFailureTimer.start()
})
}
deviceMac = ""
}
}
}
Timer {
id: clearFailureTimer
interval: 3000
running: false
repeat: false
property string deviceMac: ""
onTriggered: {
if (deviceMac) {
let typedDevice = findDeviceInList(root.bluetoothDevices, deviceMac)
if (!typedDevice) {
typedDevice = findDeviceInList(root.availableDevices, deviceMac)
}
if (typedDevice) {
typedDevice.connectionFailed = false
}
deviceMac = ""
}
}
}
component BluetoothDevice: QtObject {
required property string mac
required property string name
required property string type
required property bool paired
required property bool connected
required property int battery
required property string signalStrength
required property bool canPair
required property var nativeDevice // Reference to native Quickshell device
property bool connecting: false
property bool connectionFailed: false
readonly property string displayName: name
readonly property bool batteryAvailable: battery >= 0
readonly property string connectionStatus: {
if (connecting) return "Connecting..."
if (connectionFailed) return "Connection Failed"
if (connected) return "Connected"
return "Disconnected"
}
}
Component {
id: deviceComponent
BluetoothDevice {}
function _findDevice(address) {
if (!adapter) return null
return adapter.devices.values.find(d => d.address === address) ||
(Bluetooth.devices ? Bluetooth.devices.values.find(d => d.address === address) : null)
}
}

View File

@@ -1,159 +1,74 @@
import QtQuick
import QtQml.Models
import Quickshell
import Quickshell.Io
import Quickshell.Services.Mpris
import Quickshell.Widgets
pragma Singleton
pragma ComponentBehavior: Bound
/**
* A service that provides easy access to the active Mpris player.
*/
Singleton {
id: root
property MprisPlayer trackedPlayer: null
property MprisPlayer activePlayer: trackedPlayer ?? Mpris.players.values[0] ?? null
signal trackChanged(reverse: bool)
property bool __reverse: false
readonly property list<MprisPlayer> availablePlayers: Mpris.players.values
property var activeTrack
property MprisPlayer activePlayer: null
property MprisPlayer _candidatePlayer: availablePlayers.find(p => p.isPlaying)
?? availablePlayers.find(p => p.canControl && p.canPlay)
?? null
Instantiator {
model: Mpris.players
Connections {
required property MprisPlayer modelData
target: modelData
Component.onCompleted: {
if (root.trackedPlayer == null || modelData.isPlaying) {
root.trackedPlayer = modelData
}
}
Component.onDestruction: {
if (root.trackedPlayer == null || root.trackedPlayer.playbackState !== MprisPlaybackState.Playing) {
for (const player of Mpris.players.values) {
if (player.playbackState === MprisPlaybackState.Playing) {
root.trackedPlayer = player
break
}
}
if (trackedPlayer == null && Mpris.players.values.length != 0) {
trackedPlayer = Mpris.players.values[0]
}
}
}
function onPlaybackStateChanged() {
if (root.trackedPlayer !== modelData) root.trackedPlayer = modelData
Timer {
id: playerSwitchTimer
interval: 300
onTriggered: {
if (_candidatePlayer !== activePlayer) {
activePlayer = _candidatePlayer
}
}
}
Connections {
target: activePlayer
function onPostTrackChanged() {
root.updateTrack()
}
function onTrackArtUrlChanged() {
if (root.activePlayer.uniqueId == root.activeTrack.uniqueId && root.activePlayer.trackArtUrl != root.activeTrack.artUrl) {
const r = root.__reverse
root.updateTrack()
root.__reverse = r
}
on_CandidatePlayerChanged: {
if (_candidatePlayer === null && activePlayer !== null) {
playerSwitchTimer.restart()
} else if (_candidatePlayer !== null) {
playerSwitchTimer.stop()
activePlayer = _candidatePlayer
}
}
onActivePlayerChanged: this.updateTrack()
IpcHandler {
target: "mpris"
function updateTrack() {
this.activeTrack = {
uniqueId: this.activePlayer?.uniqueId ?? 0,
artUrl: this.activePlayer?.trackArtUrl ?? "",
title: this.activePlayer?.trackTitle || "Unknown Title",
artist: this.activePlayer?.trackArtist || "Unknown Artist",
album: this.activePlayer?.trackAlbum || "Unknown Album",
function list(): string {
return root.availablePlayers.map(p => p.identity).join("");
}
this.trackChanged(__reverse)
this.__reverse = false
}
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()
}
property bool canGoPrevious: this.activePlayer?.canGoPrevious ?? false
function previous() {
if (this.canGoPrevious) {
this.__reverse = true
this.activePlayer.previous()
}
}
property bool canGoNext: this.activePlayer?.canGoNext ?? false
function next() {
if (this.canGoNext) {
this.__reverse = false
this.activePlayer.next()
}
}
property bool canChangeVolume: this.activePlayer && this.activePlayer.volumeSupported && this.activePlayer.canControl
property bool loopSupported: this.activePlayer && this.activePlayer.loopSupported && this.activePlayer.canControl
property var loopState: this.activePlayer?.loopState ?? MprisLoopState.None
function setLoopState(loopState) {
if (this.loopSupported) {
this.activePlayer.loopState = loopState
}
}
property bool shuffleSupported: this.activePlayer && this.activePlayer.shuffleSupported && this.activePlayer.canControl
property bool hasShuffle: this.activePlayer?.shuffle ?? false
function setShuffle(shuffle) {
if (this.shuffleSupported) {
this.activePlayer.shuffle = shuffle
}
}
function setActivePlayer(player) {
const targetPlayer = player ?? Mpris.players[0]
if (targetPlayer && this.activePlayer) {
this.__reverse = Mpris.players.indexOf(targetPlayer) < Mpris.players.indexOf(this.activePlayer)
} else {
this.__reverse = false
function play(): void {
if (root.activePlayer?.canPlay)
root.activePlayer.play();
}
this.trackedPlayer = targetPlayer
}
function pause(): void {
if (root.activePlayer?.canPause)
root.activePlayer.pause();
}
// Seeking support
property bool canSeek: this.activePlayer?.canSeek ?? false
property real position: this.activePlayer?.position ?? 0
property real length: this.activePlayer?.length ?? 0
function playPause(): void {
if (root.activePlayer?.canTogglePlaying)
root.activePlayer.togglePlaying();
}
function seek(offsetUs) {
if (this.canSeek && this.activePlayer) {
this.activePlayer.seek(offsetUs)
function previous(): void {
if (root.activePlayer?.canGoPrevious)
root.activePlayer.previous();
}
function next(): void {
if (root.activePlayer?.canGoNext)
root.activePlayer.next();
}
function stop(): void {
root.activePlayer?.stop();
}
}
function setPosition(trackId, positionUs) {
if (this.canSeek && this.activePlayer && typeof this.activePlayer.setPosition === "function") {
this.activePlayer.setPosition(trackId, positionUs)
} else if (this.canSeek && this.activePlayer) {
// Fallback to setting position property
this.activePlayer.position = positionUs
}
}
}

View File

@@ -12,25 +12,7 @@ PanelWindow {
id: centerCommandCenter
property var theme: Theme
property bool hasActiveMedia: root.hasActiveMedia
property var weather: root.weather
property bool showMediaPlayer: hasActiveMedia || hideMediaTimer.running
Timer {
id: hideMediaTimer
interval: 3000
running: false
repeat: false
}
onHasActiveMediaChanged: {
if (hasActiveMedia) {
hideMediaTimer.stop()
} else {
hideMediaTimer.start()
}
}
readonly property bool hasActiveMedia: MprisController.activePlayer !== null
visible: root.calendarVisible
@@ -70,7 +52,7 @@ PanelWindow {
// Main row with widgets and calendar
let widgetHeight = 160 // Media widget always present
widgetHeight += (weather ? 140 : 80) + theme.spacingM // Weather widget always present
widgetHeight += 140 + theme.spacingM // Weather widget always present
let calendarHeight = 300
let mainRowHeight = Math.max(widgetHeight, calendarHeight)
@@ -177,7 +159,7 @@ PanelWindow {
width: parent.width
height: {
let widgetHeight = 160 // Media widget always present
widgetHeight += (weather ? 140 : 80) + theme.spacingM // Weather widget always present
widgetHeight += 140 + theme.spacingM // Weather widget always present
let calendarHeight = 300
return Math.max(widgetHeight, calendarHeight)
}
@@ -204,9 +186,8 @@ PanelWindow {
WeatherWidget {
visible: true // Always visible - shows placeholder when no weather
width: parent.width
height: weather ? 140 : 80
height: 140
theme: centerCommandCenter.theme
weather: centerCommandCenter.weather
}
}

View File

@@ -12,6 +12,32 @@ Rectangle {
property MprisPlayer activePlayer: MprisController.activePlayer
property var theme: Theme
property string lastValidTitle: ""
property string lastValidArtist: ""
property string lastValidAlbum: ""
property string lastValidArtUrl: ""
Timer {
id: clearCacheTimer
interval: 2000
onTriggered: {
if (!activePlayer) {
lastValidTitle = ""
lastValidArtist = ""
lastValidAlbum = ""
lastValidArtUrl = ""
}
}
}
onActivePlayerChanged: {
if (!activePlayer) {
clearCacheTimer.restart()
} else {
clearCacheTimer.stop()
}
}
width: parent.width
height: parent.height
radius: theme.cornerRadiusLarge
@@ -49,15 +75,6 @@ Rectangle {
}
}
// Initialize when player changes
onActivePlayerChanged: {
if (activePlayer) {
currentPosition = activePlayer.position || 0
} else {
currentPosition = 0
}
}
// Backend events
Connections {
target: activePlayer
@@ -85,7 +102,7 @@ Rectangle {
Column {
anchors.centerIn: parent
spacing: theme.spacingS
visible: !activePlayer || !activePlayer.trackTitle || activePlayer.trackTitle === ""
visible: (!activePlayer && !lastValidTitle) || (activePlayer && activePlayer.trackTitle === "" && lastValidTitle === "")
Text {
text: "music_note"
@@ -107,7 +124,7 @@ Rectangle {
Column {
anchors.fill: parent
spacing: theme.spacingS
visible: activePlayer && activePlayer.trackTitle && activePlayer.trackTitle !== ""
visible: activePlayer && activePlayer.trackTitle !== "" || lastValidTitle !== ""
// Normal media info when playing
Row {
@@ -129,7 +146,12 @@ Rectangle {
Image {
id: albumArt
anchors.fill: parent
source: activePlayer?.trackArtUrl || ""
source: activePlayer?.trackArtUrl || lastValidArtUrl || ""
onSourceChanged: {
if (activePlayer?.trackArtUrl) {
lastValidArtUrl = activePlayer.trackArtUrl;
}
}
fillMode: Image.PreserveAspectCrop
smooth: true
}
@@ -157,7 +179,12 @@ Rectangle {
spacing: theme.spacingXS
Text {
text: activePlayer?.trackTitle || "Unknown Track"
text: activePlayer?.trackTitle || lastValidTitle || "Unknown Track"
onTextChanged: {
if (activePlayer?.trackTitle) {
lastValidTitle = activePlayer.trackTitle;
}
}
font.pixelSize: theme.fontSizeMedium
font.weight: Font.Bold
color: theme.surfaceText
@@ -166,7 +193,12 @@ Rectangle {
}
Text {
text: activePlayer?.trackArtist || "Unknown Artist"
text: activePlayer?.trackArtist || lastValidArtist || "Unknown Artist"
onTextChanged: {
if (activePlayer?.trackArtist) {
lastValidArtist = activePlayer.trackArtist;
}
}
font.pixelSize: theme.fontSizeSmall
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.8)
width: parent.width
@@ -174,7 +206,12 @@ Rectangle {
}
Text {
text: activePlayer?.trackAlbum || ""
text: activePlayer?.trackAlbum || lastValidAlbum || ""
onTextChanged: {
if (activePlayer?.trackAlbum) {
lastValidAlbum = activePlayer.trackAlbum;
}
}
font.pixelSize: theme.fontSizeSmall
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.6)
width: parent.width

View File

@@ -8,7 +8,6 @@ Rectangle {
id: weatherWidget
property var theme: Theme
property var weather
width: parent.width
height: parent.height
@@ -31,26 +30,18 @@ Rectangle {
Column {
anchors.centerIn: parent
spacing: theme.spacingS
visible: !weather || !weather.available || weather.temp === 0
visible: !WeatherService.weather.available || WeatherService.weather.temp === 0
Text {
text: weather && weather.loading ? "cloud_sync" : "cloud_off"
text: "cloud_off"
font.family: theme.iconFont
font.pixelSize: theme.iconSize + 8
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.5)
anchors.horizontalCenter: parent.horizontalCenter
RotationAnimation on rotation {
from: 0
to: 360
duration: 2000
running: weather && weather.loading
loops: Animation.Infinite
}
}
Text {
text: weather && weather.loading ? "Loading Weather..." : "No Weather Data"
text: "No Weather Data"
font.pixelSize: theme.fontSizeMedium
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter
@@ -62,7 +53,7 @@ Rectangle {
anchors.fill: parent
anchors.margins: theme.spacingL
spacing: theme.spacingS
visible: weather && weather.available && weather.temp !== 0
visible: WeatherService.weather.available && WeatherService.weather.temp !== 0
// Weather header info
Item {
@@ -70,12 +61,12 @@ Rectangle {
height: 60
Row {
anchors.fill: parent
anchors.centerIn: parent
spacing: theme.spacingL
// Weather icon
Text {
text: weather ? WeatherService.getWeatherIcon(weather.wCode) : ""
text: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
font.family: theme.iconFont
font.pixelSize: theme.iconSize + 8
color: theme.primary
@@ -87,7 +78,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
Text {
text: weather ? ((Prefs.useFahrenheit ? weather.tempF : weather.temp) + "°" + (Prefs.useFahrenheit ? "F" : "C")) : ""
text: (Prefs.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°" + (Prefs.useFahrenheit ? "F" : "C")
font.pixelSize: theme.fontSizeXLarge
color: theme.surfaceText
font.weight: Font.Light
@@ -96,13 +87,13 @@ Rectangle {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: if (weather) Prefs.setTemperatureUnit(!Prefs.useFahrenheit)
enabled: weather !== null
onClicked: if (WeatherService.weather.available) Prefs.setTemperatureUnit(!Prefs.useFahrenheit)
enabled: WeatherService.weather.available
}
}
Text {
text: weather ? weather.city : ""
text: WeatherService.weather.city || ""
font.pixelSize: theme.fontSizeMedium
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.7)
visible: text.length > 0
@@ -127,7 +118,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: weather ? weather.humidity + "%" : "--"
text: WeatherService.weather.humidity ? WeatherService.weather.humidity + "%" : "--"
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
@@ -144,7 +135,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: weather ? weather.wind : "--"
text: WeatherService.weather.wind || "--"
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
@@ -161,7 +152,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: weather ? weather.sunrise : "--"
text: WeatherService.weather.sunrise || "--"
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
@@ -178,7 +169,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: weather ? weather.sunset : "--"
text: WeatherService.weather.sunset || "--"
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
anchors.verticalCenter: parent.verticalCenter

View File

@@ -9,9 +9,6 @@ import qs.Services
Item {
id: bluetoothTab
property bool bluetoothEnabled: false
property var bluetoothDevices: []
ScrollView {
anchors.fill: parent
clip: true
@@ -28,8 +25,8 @@ Item {
height: 60
radius: Theme.cornerRadius
color: bluetoothToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
(bluetoothTab.bluetoothEnabled ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12))
border.color: bluetoothTab.bluetoothEnabled ? Theme.primary : "transparent"
(BluetoothService.enabled ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12))
border.color: BluetoothService.enabled ? Theme.primary : "transparent"
border.width: 2
Row {
@@ -42,7 +39,7 @@ Item {
text: "bluetooth"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSizeLarge
color: bluetoothTab.bluetoothEnabled ? Theme.primary : Theme.surfaceText
color: BluetoothService.enabled ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
@@ -53,12 +50,12 @@ Item {
Text {
text: "Bluetooth"
font.pixelSize: Theme.fontSizeLarge
color: bluetoothTab.bluetoothEnabled ? Theme.primary : Theme.surfaceText
color: BluetoothService.enabled ? Theme.primary : Theme.surfaceText
font.weight: Font.Medium
}
Text {
text: bluetoothTab.bluetoothEnabled ? "Enabled" : "Disabled"
text: BluetoothService.enabled ? "Enabled" : "Disabled"
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
}
@@ -72,7 +69,7 @@ Item {
cursorShape: Qt.PointingHandCursor
onClicked: {
BluetoothService.toggleBluetooth()
BluetoothService.toggleAdapter()
}
}
}
@@ -80,7 +77,7 @@ Item {
Column {
width: parent.width
spacing: Theme.spacingM
visible: bluetoothTab.bluetoothEnabled
visible: BluetoothService.enabled
Text {
text: "Paired Devices"
@@ -90,7 +87,7 @@ Item {
}
Repeater {
model: bluetoothTab.bluetoothDevices
model: BluetoothService.pairedDevices
Rectangle {
width: parent.width
@@ -108,33 +105,11 @@ Item {
spacing: Theme.spacingM
Text {
text: {
switch (modelData.type) {
case "headset": return "headset"
case "mouse": return "mouse"
case "keyboard": return "keyboard"
case "phone": return "smartphone"
default: return "bluetooth"
}
}
text: modelData.iconName
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize
color: {
if (modelData.connecting) return Theme.primary
if (modelData.connected) return Theme.primary
return Theme.surfaceText
}
color: modelData.connected ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
opacity: modelData.connecting ? 0.6 : 1.0
Behavior on opacity {
SequentialAnimation {
running: modelData.connecting
loops: Animation.Infinite
NumberAnimation { from: 1.0; to: 0.3; duration: 800 }
NumberAnimation { from: 0.3; to: 1.0; duration: 800 }
}
}
}
Column {
@@ -152,20 +127,26 @@ Item {
spacing: Theme.spacingXS
Text {
text: modelData.connectionStatus
text: modelData.connected ? "Connected" : "Disconnected"
font.pixelSize: Theme.fontSizeSmall
color: {
if (modelData.connecting) return Theme.primary
if (modelData.connectionFailed) return Theme.error
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
}
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
}
Text {
text: modelData.battery >= 0 ? "• " + modelData.battery + "%" : ""
text: {
if (modelData.batteryAvailable && modelData.batteryLevel >= 0) {
return "• " + modelData.batteryLevel + "%"
}
var btBattery = BatteryService.bluetoothDevices.find(dev =>
dev.name === modelData.name ||
dev.name.toLowerCase().includes(modelData.name.toLowerCase()) ||
modelData.name.toLowerCase().includes(dev.name.toLowerCase())
)
return btBattery ? "• " + btBattery.percentage + "%" : ""
}
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
visible: modelData.battery >= 0
visible: text.length > 0
}
}
}
@@ -196,16 +177,13 @@ Item {
MouseArea {
id: btMenuButtonArea
anchors.fill: parent
hoverEnabled: !modelData.connecting
enabled: !modelData.connecting
cursorShape: modelData.connecting ? Qt.ArrowCursor : Qt.PointingHandCursor
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (!modelData.connecting) {
bluetoothContextMenuWindow.deviceData = modelData
let localPos = btMenuButtonArea.mapToItem(bluetoothTab, btMenuButtonArea.width / 2, btMenuButtonArea.height)
bluetoothContextMenuWindow.show(localPos.x, localPos.y)
}
bluetoothContextMenuWindow.deviceData = modelData
let localPos = btMenuButtonArea.mapToItem(bluetoothTab, btMenuButtonArea.width / 2, btMenuButtonArea.height)
bluetoothContextMenuWindow.show(localPos.x, localPos.y)
}
}
@@ -218,14 +196,11 @@ Item {
id: btDeviceArea
anchors.fill: parent
anchors.rightMargin: 40 // Don't overlap with menu button
hoverEnabled: !modelData.connecting
enabled: !modelData.connecting
cursorShape: modelData.connecting ? Qt.ArrowCursor : Qt.PointingHandCursor
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (!modelData.connecting) {
BluetoothService.toggleBluetoothDevice(modelData.mac)
}
BluetoothService.toggle(modelData.address)
}
}
}
@@ -235,7 +210,7 @@ Item {
Column {
width: parent.width
spacing: Theme.spacingM
visible: bluetoothTab.bluetoothEnabled
visible: BluetoothService.enabled
Row {
width: parent.width
@@ -264,7 +239,7 @@ Item {
spacing: Theme.spacingXS
Text {
text: BluetoothService.scanning ? "stop" : "bluetooth_searching"
text: BluetoothService.discovering ? "stop" : "bluetooth_searching"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 4
color: Theme.primary
@@ -273,7 +248,7 @@ Item {
Text {
id: scanText
text: BluetoothService.scanning ? "Stop Scanning" : "Start Scanning"
text: BluetoothService.discovering ? "Stop Scanning" : "Start Scanning"
font.pixelSize: Theme.fontSizeMedium
color: Theme.primary
font.weight: Font.Medium
@@ -288,10 +263,10 @@ Item {
cursorShape: Qt.PointingHandCursor
onClicked: {
if (BluetoothService.scanning) {
BluetoothService.stopDiscovery()
if (BluetoothService.discovering) {
BluetoothService.stopScan()
} else {
BluetoothService.startDiscovery()
BluetoothService.startScan()
}
}
}
@@ -317,18 +292,7 @@ Item {
spacing: Theme.spacingM
Text {
text: {
switch (modelData.type) {
case "headset": return "headset"
case "mouse": return "mouse"
case "keyboard": return "keyboard"
case "phone": return "smartphone"
case "watch": return "watch"
case "speaker": return "speaker"
case "tv": return "tv"
default: return "bluetooth"
}
}
text: modelData.iconName
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize
color: modelData.paired ? Theme.secondary : Theme.surfaceText
@@ -350,21 +314,10 @@ Item {
spacing: Theme.spacingXS
Text {
text: {
if (modelData.paired && modelData.connected) return "Connected"
if (modelData.paired) return "Paired"
return "Signal: " + modelData.signalStrength
}
text: modelData.paired ? "Available" : "Not paired"
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
}
Text {
text: modelData.rssi !== 0 ? "• " + modelData.rssi + " dBm" : ""
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
visible: modelData.rssi !== 0
}
}
}
}
@@ -379,11 +332,11 @@ Item {
color: actionButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
border.color: Theme.primary
border.width: 1
visible: modelData.canPair || modelData.paired
visible: true
Text {
anchors.centerIn: parent
text: modelData.paired ? (modelData.connected ? "Disconnect" : "Connect") : "Pair"
text: modelData.paired ? "Connect" : "Pair"
font.pixelSize: Theme.fontSizeSmall
color: Theme.primary
font.weight: Font.Medium
@@ -397,13 +350,9 @@ Item {
onClicked: {
if (modelData.paired) {
if (modelData.connected) {
BluetoothService.toggleBluetoothDevice(modelData.mac)
} else {
BluetoothService.connectDevice(modelData.mac)
}
BluetoothService.connect(modelData.address)
} else {
BluetoothService.pairDevice(modelData.mac)
BluetoothService.pair(modelData.address)
}
}
}
@@ -418,9 +367,9 @@ Item {
onClicked: {
if (modelData.paired) {
BluetoothService.toggleBluetoothDevice(modelData.mac)
BluetoothService.connect(modelData.address)
} else {
BluetoothService.pairDevice(modelData.mac)
BluetoothService.pair(modelData.address)
}
}
}
@@ -430,7 +379,7 @@ Item {
Column {
width: parent.width
spacing: Theme.spacingM
visible: BluetoothService.scanning && BluetoothService.availableDevices.length === 0
visible: BluetoothService.discovering && BluetoothService.availableDevices.length === 0
Row {
anchors.horizontalCenter: parent.horizontalCenter
@@ -473,7 +422,7 @@ Item {
text: "No devices found. Put your device in pairing mode and click Start Scanning."
font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
visible: BluetoothService.availableDevices.length === 0 && !BluetoothService.scanning
visible: BluetoothService.availableDevices.length === 0 && !BluetoothService.discovering
wrapMode: Text.WordWrap
width: parent.width
horizontalAlignment: Text.AlignHCenter
@@ -568,7 +517,7 @@ Item {
onClicked: {
if (bluetoothContextMenuWindow.deviceData) {
BluetoothService.toggleBluetoothDevice(bluetoothContextMenuWindow.deviceData.mac)
BluetoothService.toggle(bluetoothContextMenuWindow.deviceData.address)
}
bluetoothContextMenuWindow.hide()
}
@@ -634,7 +583,7 @@ Item {
onClicked: {
if (bluetoothContextMenuWindow.deviceData) {
BluetoothService.removeDevice(bluetoothContextMenuWindow.deviceData.mac)
BluetoothService.forget(bluetoothContextMenuWindow.deviceData.address)
}
bluetoothContextMenuWindow.hide()
}

View File

@@ -33,9 +33,9 @@ PanelWindow {
property bool powerOptionsExpanded: false
Rectangle {
width: Math.min(600, parent.width - Theme.spacingL * 2)
width: Math.min(600, Screen.width - Theme.spacingL * 2)
height: controlCenterPopup.powerOptionsExpanded ? 570 : 500
x: Math.max(Theme.spacingL, parent.width - width - Theme.spacingL)
x: Math.max(Theme.spacingL, Screen.width - width - Theme.spacingL)
y: Theme.barHeight + Theme.spacingXS
color: Theme.popupBackground()
radius: Theme.cornerRadiusLarge
@@ -560,7 +560,7 @@ PanelWindow {
tabs.push({name: "Audio", icon: "volume_up", id: "audio", available: true})
// Show Bluetooth only if available
if (root.bluetoothAvailable) {
if (BluetoothService.available) {
tabs.push({name: "Bluetooth", icon: "bluetooth", id: "bluetooth", available: true})
}
@@ -573,7 +573,7 @@ PanelWindow {
Rectangle {
property int tabCount: {
let count = 3 // Network + Audio + Display (always visible)
if (root.bluetoothAvailable) count++
if (BluetoothService.available) count++
return count
}
width: (parent.width - Theme.spacingXS * (tabCount - 1)) / tabCount
@@ -646,26 +646,10 @@ PanelWindow {
anchors.margins: Theme.spacingM
visible: controlCenterPopup.currentTab === "network"
// Bind properties from root
networkStatus: root.networkStatus
wifiAvailable: root.wifiAvailable
wifiEnabled: root.wifiEnabled
wifiToggling: root.wifiToggling
ethernetIP: root.ethernetIP
ethernetInterface: root.ethernetInterface
ethernetConnected: root.ethernetConnected
currentWifiSSID: root.currentWifiSSID
wifiIP: root.wifiIP
wifiSignalStrength: root.wifiSignalStrength
wifiNetworks: root.wifiNetworks
wifiScanning: root.wifiScanning
wifiConnectionStatus: root.wifiConnectionStatus
wifiPasswordSSID: root.wifiPasswordSSID
wifiPasswordInput: root.wifiPasswordInput
wifiPasswordDialogVisible: root.wifiPasswordDialogVisible
changingNetworkPreference: root.changingNetworkPreference
// Bind the auto-refresh flag
onWifiAutoRefreshEnabledChanged: {
root.wifiAutoRefreshEnabled = wifiAutoRefreshEnabled
}
@@ -682,11 +666,7 @@ PanelWindow {
BluetoothTab {
anchors.fill: parent
anchors.margins: Theme.spacingM
visible: root.bluetoothAvailable && controlCenterPopup.currentTab === "bluetooth"
// Bind properties from root
bluetoothEnabled: root.bluetoothEnabled
bluetoothDevices: root.bluetoothDevices
visible: BluetoothService.available && controlCenterPopup.currentTab === "bluetooth"
}
// Display Tab

View File

@@ -11,32 +11,16 @@ Item {
property int networkSubTab: {
// Default to WiFi tab if WiFi is connected, otherwise Ethernet
if (networkStatus === "wifi") return 1
else if (networkStatus === "ethernet") return 0
if (NetworkService.networkStatus === "wifi") return 1
else if (NetworkService.networkStatus === "ethernet") return 0
else return 1 // Default to WiFi when nothing is connected
}
// Expose properties that the parent needs to bind to
property bool wifiAutoRefreshEnabled: false
// These should be bound from parent
property string networkStatus: ""
property bool wifiAvailable: false
property bool wifiEnabled: false
property bool wifiToggling: false
property string ethernetIP: ""
property string ethernetInterface: ""
property bool ethernetConnected: false
property string currentWifiSSID: ""
property string wifiIP: ""
property string wifiSignalStrength: ""
property var wifiNetworks: []
property bool wifiScanning: false
property string wifiConnectionStatus: ""
property string wifiPasswordSSID: ""
property string wifiPasswordInput: ""
property bool wifiPasswordDialogVisible: false
property bool changingNetworkPreference: false
Column {
anchors.fill: parent
@@ -101,7 +85,7 @@ Item {
spacing: Theme.spacingXS
Text {
text: networkTab.wifiEnabled ? "wifi" : "wifi_off"
text: NetworkService.wifiEnabled ? "wifi" : "wifi_off"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 4
color: networkTab.networkSubTab === 1 ? Theme.primary : Theme.surfaceText
@@ -160,8 +144,8 @@ Item {
height: 70
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5)
border.color: networkTab.networkStatus === "ethernet" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: networkTab.networkStatus === "ethernet" ? 2 : 1
border.color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: NetworkService.networkStatus === "ethernet" ? 2 : 1
visible: true
Row {
@@ -190,7 +174,7 @@ Item {
}
Text {
text: networkTab.ethernetConnected ? (networkTab.ethernetIP || "Connected") : "Disconnected"
text: NetworkService.ethernetConnected ? (NetworkService.ethernetIP || "Connected") : "Disconnected"
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
}
@@ -342,8 +326,8 @@ Item {
height: 50
radius: Theme.cornerRadius
color: wifiToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
visible: networkTab.wifiAvailable
opacity: networkTab.wifiToggling ? 0.6 : 1.0
visible: NetworkService.wifiAvailable
opacity: NetworkService.wifiToggling ? 0.6 : 1.0
Behavior on opacity {
NumberAnimation {
@@ -360,17 +344,17 @@ Item {
Text {
id: wifiToggleIcon
text: networkTab.wifiToggling ? "sync" : "power_settings_new"
text: NetworkService.wifiToggling ? "sync" : "power_settings_new"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize
color: networkTab.wifiEnabled ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
color: NetworkService.wifiEnabled ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
anchors.verticalCenter: parent.verticalCenter
rotation: networkTab.wifiToggling ? wifiToggleIcon.rotation : 0
rotation: NetworkService.wifiToggling ? wifiToggleIcon.rotation : 0
RotationAnimation {
target: wifiToggleIcon
property: "rotation"
running: networkTab.wifiToggling
running: NetworkService.wifiToggling
from: 0
to: 360
duration: 1000
@@ -386,7 +370,7 @@ Item {
}
Text {
text: networkTab.wifiToggling ? "Switching WiFi..." : (networkTab.wifiEnabled ? "Turn WiFi Off" : "Turn WiFi On")
text: NetworkService.wifiToggling ? "Switching WiFi..." : (NetworkService.wifiEnabled ? "Turn WiFi Off" : "Turn WiFi On")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
@@ -413,7 +397,7 @@ Item {
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5)
border.color: networkTab.networkStatus === "wifi" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: networkTab.networkStatus === "wifi" ? 2 : 1
visible: networkTab.wifiAvailable && networkTab.wifiEnabled
visible: NetworkService.wifiAvailable && NetworkService.wifiEnabled
Row {
anchors.left: parent.left
@@ -423,10 +407,10 @@ Item {
Text {
text: networkTab.networkStatus === "wifi" ?
(networkTab.wifiSignalStrength === "excellent" ? "wifi" :
networkTab.wifiSignalStrength === "good" ? "wifi_2_bar" :
networkTab.wifiSignalStrength === "fair" ? "wifi_1_bar" :
networkTab.wifiSignalStrength === "poor" ? "wifi_calling_3" : "wifi") : "wifi"
(WifiService.wifiSignalStrength === "excellent" ? "wifi" :
WifiService.wifiSignalStrength === "good" ? "wifi_2_bar" :
WifiService.wifiSignalStrength === "fair" ? "wifi_1_bar" :
WifiService.wifiSignalStrength === "poor" ? "wifi_calling_3" : "wifi") : "wifi"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSizeLarge
color: networkTab.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
@@ -438,14 +422,14 @@ Item {
anchors.verticalCenter: parent.verticalCenter
Text {
text: networkTab.networkStatus === "wifi" ? (networkTab.currentWifiSSID || "Connected") : "Not Connected"
text: NetworkService.networkStatus === "wifi" ? (WifiService.currentWifiSSID || "Connected") : "Not Connected"
font.pixelSize: Theme.fontSizeLarge
color: networkTab.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
font.weight: Font.Medium
}
Text {
text: networkTab.networkStatus === "wifi" ? (networkTab.wifiIP || "Connected") : "Select a network below"
text: NetworkService.networkStatus === "wifi" ? (NetworkService.wifiIP || "Connected") : "Select a network below"
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
}
@@ -496,10 +480,10 @@ Item {
}
Text {
text: networkTab.changingNetworkPreference ? "Switching..." :
(networkTab.networkStatus === "wifi" ? "" : "Prefer over Ethernet")
text: NetworkService.changingNetworkPreference ? "Switching..." :
(NetworkService.networkStatus === "wifi" ? "" : "Prefer over Ethernet")
font.pixelSize: Theme.fontSizeSmall
color: networkTab.networkStatus === "wifi" ? Theme.background : Theme.primary
color: NetworkService.networkStatus === "wifi" ? Theme.background : Theme.primary
anchors.verticalCenter: parent.verticalCenter
font.weight: Font.Medium
}
@@ -513,7 +497,7 @@ Item {
enabled: !networkTab.changingNetworkPreference
onClicked: {
console.log("Force WiFi preference clicked")
if (networkTab.networkStatus !== "wifi") {
if (NetworkService.networkStatus !== "wifi") {
NetworkService.setNetworkPreference("wifi")
} else {
NetworkService.setNetworkPreference("auto")
@@ -528,7 +512,7 @@ Item {
Column {
width: parent.width
spacing: Theme.spacingM
visible: networkTab.wifiEnabled
visible: NetworkService.wifiEnabled
Row {
width: parent.width
@@ -547,21 +531,21 @@ Item {
height: 32
radius: 16
color: refreshArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
networkTab.wifiScanning ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent"
WifiService.isScanning ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent"
Text {
id: refreshIcon
anchors.centerIn: parent
text: networkTab.wifiScanning ? "sync" : "refresh"
text: WifiService.isScanning ? "sync" : "refresh"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 4
color: Theme.surfaceText
rotation: networkTab.wifiScanning ? refreshIcon.rotation : 0
rotation: WifiService.isScanning ? refreshIcon.rotation : 0
RotationAnimation {
target: refreshIcon
property: "rotation"
running: networkTab.wifiScanning
running: WifiService.isScanning
from: 0
to: 360
duration: 1000
@@ -581,7 +565,7 @@ Item {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: !networkTab.wifiScanning
enabled: !WifiService.isScanning
onClicked: {
if (NetworkService.wifiEnabled) {
WifiService.scanWifi()
@@ -598,27 +582,27 @@ Item {
height: 40
radius: Theme.cornerRadius
color: {
if (networkTab.wifiConnectionStatus === "connecting") {
if (WifiService.connectionStatus === "connecting") {
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
} else if (networkTab.wifiConnectionStatus === "failed") {
} else if (WifiService.connectionStatus === "failed") {
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
} else if (networkTab.wifiConnectionStatus === "connected") {
} else if (WifiService.connectionStatus === "connected") {
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.12)
}
return "transparent"
}
border.color: {
if (networkTab.wifiConnectionStatus === "connecting") {
if (WifiService.connectionStatus === "connecting") {
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.3)
} else if (networkTab.wifiConnectionStatus === "failed") {
} else if (WifiService.connectionStatus === "failed") {
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3)
} else if (networkTab.wifiConnectionStatus === "connected") {
} else if (WifiService.connectionStatus === "connected") {
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.3)
}
return "transparent"
}
border.width: networkTab.wifiConnectionStatus !== "" ? 1 : 0
visible: networkTab.wifiConnectionStatus !== ""
border.width: WifiService.connectionStatus !== "" ? 1 : 0
visible: WifiService.connectionStatus !== ""
Row {
anchors.centerIn: parent
@@ -627,26 +611,26 @@ Item {
Text {
id: connectionIcon
text: {
if (networkTab.wifiConnectionStatus === "connecting") return "sync"
if (networkTab.wifiConnectionStatus === "failed") return "error"
if (networkTab.wifiConnectionStatus === "connected") return "check_circle"
if (WifiService.connectionStatus === "connecting") return "sync"
if (WifiService.connectionStatus === "failed") return "error"
if (WifiService.connectionStatus === "connected") return "check_circle"
return ""
}
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 6
color: {
if (networkTab.wifiConnectionStatus === "connecting") return Theme.warning
if (networkTab.wifiConnectionStatus === "failed") return Theme.error
if (networkTab.wifiConnectionStatus === "connected") return Theme.success
if (WifiService.connectionStatus === "connecting") return Theme.warning
if (WifiService.connectionStatus === "failed") return Theme.error
if (WifiService.connectionStatus === "connected") return Theme.success
return Theme.surfaceText
}
anchors.verticalCenter: parent.verticalCenter
rotation: networkTab.wifiConnectionStatus === "connecting" ? connectionIcon.rotation : 0
rotation: WifiService.connectionStatus === "connecting" ? connectionIcon.rotation : 0
RotationAnimation {
target: connectionIcon
property: "rotation"
running: networkTab.wifiConnectionStatus === "connecting"
running: WifiService.connectionStatus === "connecting"
from: 0
to: 360
duration: 1000
@@ -663,16 +647,16 @@ Item {
Text {
text: {
if (networkTab.wifiConnectionStatus === "connecting") return "Connecting to " + WifiService.connectingSSID
if (networkTab.wifiConnectionStatus === "failed") return "Failed to connect to " + WifiService.connectingSSID
if (networkTab.wifiConnectionStatus === "connected") return "Connected to " + WifiService.connectingSSID
if (WifiService.connectionStatus === "connecting") return "Connecting to " + WifiService.connectingSSID
if (WifiService.connectionStatus === "failed") return "Failed to connect to " + WifiService.connectingSSID
if (WifiService.connectionStatus === "connected") return "Connected to " + WifiService.connectingSSID
return ""
}
font.pixelSize: Theme.fontSizeMedium
color: {
if (networkTab.wifiConnectionStatus === "connecting") return Theme.warning
if (networkTab.wifiConnectionStatus === "failed") return Theme.error
if (networkTab.wifiConnectionStatus === "connected") return Theme.success
if (WifiService.connectionStatus === "connecting") return Theme.warning
if (WifiService.connectionStatus === "failed") return Theme.error
if (WifiService.connectionStatus === "connected") return Theme.success
return Theme.surfaceText
}
anchors.verticalCenter: parent.verticalCenter
@@ -689,7 +673,7 @@ Item {
// WiFi networks list (only show if WiFi is available and enabled)
Repeater {
model: networkTab.wifiAvailable && networkTab.wifiEnabled ? networkTab.wifiNetworks : []
model: NetworkService.wifiAvailable && NetworkService.wifiEnabled ? WifiService.wifiNetworks : []
Rectangle {
width: parent.width
@@ -828,7 +812,7 @@ Item {
Column {
width: parent.width
spacing: Theme.spacingM
visible: !networkTab.wifiEnabled
visible: !NetworkService.wifiEnabled
anchors.horizontalCenter: parent.horizontalCenter
Text {

View File

@@ -163,7 +163,7 @@ Item {
// Global mouse area for drag tracking
MouseArea {
id: sliderGlobalMouseArea
anchors.fill: slider.parent // Fill the entire settings popup
anchors.fill: sliderContainer
enabled: sliderMouseArea.isDragging
visible: false
preventStealing: true

View File

@@ -42,7 +42,7 @@ PanelWindow {
Rectangle {
width: 400
height: 500
x: parent.width - width - Theme.spacingL
x: Screen.width - width - Theme.spacingL
y: Theme.barHeight + Theme.spacingXS
color: Theme.popupBackground()
radius: Theme.cornerRadiusLarge

View File

@@ -46,9 +46,9 @@ PanelWindow {
Rectangle {
id: dropdownContent
width: Math.min(600, parent.width - Theme.spacingL * 2)
height: Math.min(600, parent.height - Theme.barHeight - Theme.spacingS * 2)
x: Math.max(Theme.spacingL, parent.width - width - Theme.spacingL)
width: Math.min(600, Screen.width - Theme.spacingL * 2)
height: Math.min(600, Screen.height - Theme.barHeight - Theme.spacingS * 2)
x: Math.max(Theme.spacingL, Screen.width - width - Theme.spacingL)
y: Theme.barHeight + Theme.spacingXS
radius: Theme.cornerRadiusLarge

View File

@@ -3,13 +3,14 @@ import Quickshell
import Quickshell.Io
import Quickshell.Services.Mpris
import qs.Common
import qs.Services
Item {
id: root
property list<real> audioLevels: [0, 0, 0, 0]
property bool hasActiveMedia: false
property var activePlayer: null
readonly property MprisPlayer activePlayer: MprisController.activePlayer
readonly property bool hasActiveMedia: activePlayer !== null
property bool cavaAvailable: false
width: 20

View File

@@ -5,12 +5,6 @@ import qs.Services
Rectangle {
id: root
property string networkStatus: "disconnected"
property string wifiSignalStrength: "good"
property int volumeLevel: 50
property bool volumeMuted: false
property bool bluetoothAvailable: false
property bool bluetoothEnabled: false
property bool isActive: false
signal clicked()
@@ -30,9 +24,9 @@ Rectangle {
// Network Status Icon
Text {
text: {
if (root.networkStatus === "ethernet") return "lan"
else if (root.networkStatus === "wifi") {
switch (root.wifiSignalStrength) {
if (NetworkService.networkStatus === "ethernet") return "lan"
else if (NetworkService.networkStatus === "wifi") {
switch (WifiService.wifiSignalStrength) {
case "excellent": return "wifi"
case "good": return "wifi_2_bar"
case "fair": return "wifi_1_bar"
@@ -45,7 +39,7 @@ Rectangle {
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 8
font.weight: Theme.iconFontWeight
color: root.networkStatus !== "disconnected" ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
color: NetworkService.networkStatus !== "disconnected" ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
anchors.verticalCenter: parent.verticalCenter
visible: true
}
@@ -56,9 +50,9 @@ Rectangle {
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 8
font.weight: Theme.iconFontWeight
color: root.bluetoothEnabled ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
color: BluetoothService.enabled ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
anchors.verticalCenter: parent.verticalCenter
visible: root.bluetoothAvailable && root.bluetoothEnabled
visible: BluetoothService.available && BluetoothService.enabled
}
// Audio Icon with scroll wheel support
@@ -70,8 +64,8 @@ Rectangle {
Text {
id: audioIcon
text: root.volumeMuted ? "volume_off" :
root.volumeLevel < 33 ? "volume_down" : "volume_up"
text: AudioService.sinkMuted ? "volume_off" :
AudioService.volumeLevel < 33 ? "volume_down" : "volume_up"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 8
font.weight: Theme.iconFontWeight
@@ -88,7 +82,7 @@ Rectangle {
onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y
let currentVolume = root.volumeLevel
let currentVolume = AudioService.volumeLevel
let newVolume
if (delta > 0) {

View File

@@ -10,12 +10,10 @@ Rectangle {
radius: Theme.cornerRadius
color: launcherArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
property string osLogo: ""
Text {
anchors.centerIn: parent
text: root.osLogo || "apps"
font.family: root.osLogo ? "NerdFont" : Theme.iconFont
text: OSDetectorService.osLogo || "apps"
font.family: OSDetectorService.osLogo ? "NerdFont" : Theme.iconFont
font.pixelSize: Theme.iconSize - 6
font.weight: Theme.iconFontWeight
color: Theme.surfaceText

View File

@@ -1,111 +1,92 @@
import QtQuick
import Quickshell.Services.Mpris
import qs.Common
import qs.Services
Rectangle {
id: root
property var activePlayer: null
property bool hasActiveMedia: false
// Add a stable visibility property that doesn't flicker during track changes
property bool stableVisible: false
readonly property MprisPlayer activePlayer: MprisController.activePlayer
readonly property bool playerAvailable: activePlayer !== null
readonly property int contentWidth: Math.min(280, mediaRow.implicitWidth + Theme.spacingS * 2)
signal clicked()
// Use a timer to stabilize visibility during track changes
Timer {
id: visibilityTimer
interval: 1000 // 1 second delay before hiding
onTriggered: root.stableVisible = root.hasActiveMedia
}
onHasActiveMediaChanged: {
if (hasActiveMedia) {
// Show immediately when media becomes available
stableVisible = true
visibilityTimer.stop()
} else {
// Delay hiding to avoid flicker during track changes
visibilityTimer.restart()
}
}
visible: stableVisible
width: stableVisible ? Math.min(280, mediaRow.implicitWidth + Theme.spacingS * 2) : 0
height: 30
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
states: [
State {
name: "shown"
when: playerAvailable
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
PropertyChanges {
target: root
opacity: 1
width: contentWidth
}
},
State {
name: "hidden"
when: !playerAvailable
PropertyChanges {
target: root
opacity: 0
width: 0
}
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
]
Row {
id: mediaRow
anchors.centerIn: parent
spacing: Theme.spacingXS
// Media info section (clickable to open full player)
Row {
id: mediaInfo
spacing: Theme.spacingXS
AudioVisualization {
width: 20
height: Theme.iconSize
anchors.verticalCenter: parent.verticalCenter
hasActiveMedia: root.hasActiveMedia
activePlayer: root.activePlayer
}
Text {
id: mediaText
anchors.verticalCenter: parent.verticalCenter
width: 140
text: {
// Handle the case when activePlayer is temporarily null during track changes
if (!activePlayer || !activePlayer.trackTitle) {
return "Loading..."
return "";
}
// Check if it's web media by looking at player identity
let identity = activePlayer.identity || ""
let isWebMedia = identity.toLowerCase().includes("firefox") ||
identity.toLowerCase().includes("chrome") ||
identity.toLowerCase().includes("chromium") ||
identity.toLowerCase().includes("edge") ||
identity.toLowerCase().includes("safari")
let title = ""
let subtitle = ""
let identity = activePlayer.identity || "";
let isWebMedia = identity.toLowerCase().includes("firefox") || identity.toLowerCase().includes("chrome") || identity.toLowerCase().includes("chromium") || identity.toLowerCase().includes("edge") || identity.toLowerCase().includes("safari");
let title = "";
let subtitle = "";
if (isWebMedia && activePlayer.trackTitle) {
title = activePlayer.trackTitle
subtitle = activePlayer.trackArtist || identity
title = activePlayer.trackTitle;
subtitle = activePlayer.trackArtist || identity;
} else {
title = activePlayer.trackTitle || "Unknown Track"
subtitle = activePlayer.trackArtist || ""
title = activePlayer.trackTitle || "Unknown Track";
subtitle = activePlayer.trackArtist || "";
}
if (title.length > 20)
title = title.substring(0, 20) + "...";
// Truncate title and subtitle to fit in available space - more generous limits
if (title.length > 20) title = title.substring(0, 20) + "..."
if (subtitle.length > 22) subtitle = subtitle.substring(0, 22) + "..."
if (subtitle.length > 22)
subtitle = subtitle.substring(0, 22) + "...";
return subtitle.length > 0 ? title + " • " + subtitle : title
return subtitle.length > 0 ? title + " • " + subtitle : title;
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
@@ -117,7 +98,9 @@ Rectangle {
cursorShape: Qt.PointingHandCursor
onClicked: root.clicked()
}
}
}
// Control buttons
@@ -132,8 +115,8 @@ Rectangle {
radius: 10
anchors.verticalCenter: parent.verticalCenter
color: prevArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
visible: stableVisible
opacity: (activePlayer && activePlayer.canGoPrevious) ? 1.0 : 0.3
visible: root.playerAvailable
opacity: (activePlayer && activePlayer.canGoPrevious) ? 1 : 0.3
Text {
anchors.centerIn: parent
@@ -145,13 +128,17 @@ Rectangle {
MouseArea {
id: prevArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (activePlayer) activePlayer.previous()
if (activePlayer)
activePlayer.previous();
}
}
}
// Play/Pause button
@@ -160,16 +147,16 @@ Rectangle {
height: 24
radius: 12
anchors.verticalCenter: parent.verticalCenter
color: activePlayer?.playbackState === 1 ? Theme.primary : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
visible: stableVisible
opacity: activePlayer ? 1.0 : 0.3
color: activePlayer && activePlayer.playbackState === 1 ? Theme.primary : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
visible: root.playerAvailable
opacity: activePlayer ? 1 : 0.3
Text {
anchors.centerIn: parent
text: activePlayer?.playbackState === 1 ? "pause" : "play_arrow"
text: activePlayer && activePlayer.playbackState === 1 ? "pause" : "play_arrow"
font.family: Theme.iconFont
font.pixelSize: 14
color: activePlayer?.playbackState === 1 ? Theme.background : Theme.primary
color: activePlayer && activePlayer.playbackState === 1 ? Theme.background : Theme.primary
}
MouseArea {
@@ -177,9 +164,12 @@ Rectangle {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (activePlayer) activePlayer.togglePlaying()
if (activePlayer)
activePlayer.togglePlaying();
}
}
}
// Next button
@@ -189,8 +179,8 @@ Rectangle {
radius: 10
anchors.verticalCenter: parent.verticalCenter
color: nextArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
visible: stableVisible
opacity: (activePlayer && activePlayer.canGoNext) ? 1.0 : 0.3
visible: playerAvailable
opacity: (activePlayer && activePlayer.canGoNext) ? 1 : 0.3
Text {
anchors.centerIn: parent
@@ -202,14 +192,69 @@ Rectangle {
MouseArea {
id: nextArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (activePlayer) activePlayer.next()
if (activePlayer)
activePlayer.next();
}
}
}
}
}
transitions: [
Transition {
from: "shown"
to: "hidden"
SequentialAnimation {
PauseAnimation {
duration: 500
}
NumberAnimation {
properties: "opacity,width"
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
},
Transition {
from: "hidden"
to: "shown"
NumberAnimation {
properties: "opacity,width"
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
]
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}

View File

@@ -31,19 +31,6 @@ PanelWindow {
}
// Properties exposed to shell
property bool hasActiveMedia: false
property var activePlayer: null
property bool weatherAvailable: false
property string weatherCode: ""
property int weatherTemp: 0
property int weatherTempF: 0
property string osLogo: ""
property string networkStatus: "disconnected"
property string wifiSignalStrength: "good"
property int volumeLevel: 50
property bool volumeMuted: false
property bool bluetoothAvailable: false
property bool bluetoothEnabled: false
// Shell reference to access root properties directly
property var shellRoot: null
@@ -155,7 +142,6 @@ PanelWindow {
LauncherButton {
anchors.verticalCenter: parent.verticalCenter
osLogo: topBar.osLogo
}
WorkspaceSwitcher {
@@ -184,9 +170,7 @@ PanelWindow {
anchors.verticalCenter: parent.verticalCenter
anchors.right: clockWidget.left
anchors.rightMargin: Theme.spacingS
activePlayer: topBar.activePlayer
hasActiveMedia: topBar.hasActiveMedia
visible: Prefs.showMusic && topBar.hasActiveMedia
visible: Prefs.showMusic && MprisController.activePlayer
onClicked: {
if (topBar.shellRoot) {
@@ -201,11 +185,7 @@ PanelWindow {
anchors.left: clockWidget.right
anchors.leftMargin: Theme.spacingS
weatherAvailable: topBar.weatherAvailable
weatherCode: topBar.weatherCode
weatherTemp: topBar.weatherTemp
weatherTempF: topBar.weatherTempF
visible: Prefs.showWeather && topBar.weatherAvailable && topBar.weatherTemp > 0 && topBar.weatherTempF > 0
visible: Prefs.showWeather && WeatherService.weather.available && WeatherService.weather.temp > 0 && WeatherService.weather.tempF > 0
onClicked: {
if (topBar.shellRoot) {
@@ -307,12 +287,6 @@ PanelWindow {
ControlCenterButton {
anchors.verticalCenter: parent.verticalCenter
networkStatus: topBar.networkStatus
wifiSignalStrength: topBar.wifiSignalStrength
volumeLevel: topBar.volumeLevel
volumeMuted: topBar.volumeMuted
bluetoothAvailable: topBar.bluetoothAvailable
bluetoothEnabled: topBar.bluetoothEnabled
isActive: topBar.shellRoot ? topBar.shellRoot.controlCenterVisible : false
onClicked: {

View File

@@ -5,10 +5,6 @@ import qs.Services
Rectangle {
id: root
property bool weatherAvailable: false
property string weatherCode: ""
property int weatherTemp: 0
property int weatherTempF: 0
signal clicked()
@@ -40,7 +36,7 @@ Rectangle {
spacing: Theme.spacingXS
Text {
text: WeatherService.getWeatherIcon(weatherCode)
text: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 4
color: Theme.primary
@@ -48,7 +44,7 @@ Rectangle {
}
Text {
text: (Prefs.useFahrenheit ? weatherTempF : weatherTemp) + "°" + (Prefs.useFahrenheit ? "F" : "C")
text: (Prefs.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°" + (Prefs.useFahrenheit ? "F" : "C")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium

View File

@@ -70,7 +70,7 @@ PanelWindow {
QsMenuOpener {
id: menuOpener
menu: root.currentTrayItem?.menu
menu: root.currentTrayItem ? root.currentTrayItem.menu : null
}
// Custom menu styling using ListView

View File

@@ -37,19 +37,11 @@ ShellRoot {
property real trayMenuY: 0
property var currentTrayMenu: null
property var currentTrayItem: null
property string osLogo: OSDetectorService.osLogo
property string osName: OSDetectorService.osName
property bool notificationHistoryVisible: false
property bool mediaPlayerVisible: false
property MprisPlayer activePlayer: MprisController.activePlayer
property bool hasActiveMedia: activePlayer && (activePlayer.trackTitle || activePlayer.trackArtist)
property bool hasActiveMedia: MprisController.active && (MprisController.active.trackTitle || MprisController.active.trackArtist)
property bool controlCenterVisible: false
// Monitor control center visibility to enable/disable bluetooth scanning
onControlCenterVisibleChanged: {
console.log("Control center", controlCenterVisible ? "opened" : "closed")
BluetoothService.enableMonitoring(controlCenterVisible)
}
property bool batteryPopupVisible: false
property bool powerMenuVisible: false
property bool powerConfirmVisible: false
@@ -58,52 +50,11 @@ ShellRoot {
property string powerConfirmMessage: ""
property bool settingsVisible: false
// Network properties from NetworkService
property string networkStatus: NetworkService.networkStatus
property string ethernetIP: NetworkService.ethernetIP
property string ethernetInterface: NetworkService.ethernetInterface
property bool ethernetConnected: NetworkService.ethernetConnected
property string wifiIP: NetworkService.wifiIP
property bool bluetoothEnabled: BluetoothService.bluetoothEnabled
property bool bluetoothAvailable: BluetoothService.bluetoothAvailable
property bool wifiEnabled: NetworkService.wifiEnabled
property bool wifiAvailable: NetworkService.wifiAvailable
property bool wifiToggling: NetworkService.wifiToggling
property bool changingNetworkPreference: NetworkService.changingPreference
// WiFi properties from WifiService
property string wifiSignalStrength: WifiService.wifiSignalStrength
property string currentWifiSSID: WifiService.currentWifiSSID
property var wifiNetworks: WifiService.wifiNetworks
property var savedWifiNetworks: WifiService.savedWifiNetworks
property bool wifiScanning: WifiService.isScanning
// Audio properties from AudioService
property int volumeLevel: AudioService.volumeLevel
property bool volumeMuted: AudioService.sinkMuted
property var audioSinks: AudioService.audioSinks
property string currentAudioSink: AudioService.currentAudioSink
// Microphone properties from AudioService
property int micLevel: AudioService.micLevel
property var audioSources: AudioService.audioSources
property string currentAudioSource: AudioService.currentAudioSource
// Bluetooth properties from BluetoothService
property var bluetoothDevices: BluetoothService.bluetoothDevices
// Brightness properties from BrightnessService
property int brightnessLevel: BrightnessService.brightnessLevel
// Calendar properties from CalendarService
property bool calendarAvailable: CalendarService.khalAvailable
property var calendarEvents: CalendarService.eventsByDate
// WiFi password dialog
property bool wifiPasswordDialogVisible: false
property string wifiPasswordSSID: ""
property string wifiPasswordInput: ""
property string wifiConnectionStatus: WifiService.connectionStatus
property bool wifiAutoRefreshEnabled: false
// Wallpaper error status
@@ -116,8 +67,6 @@ ShellRoot {
property bool isMediumScreen: screenWidth >= 1200 && screenWidth < 1600
property bool isLargeScreen: screenWidth >= 1600
// Weather data from WeatherService
property var weather: WeatherService.weather
// Weather configuration
@@ -171,19 +120,6 @@ ShellRoot {
modelData: item
// Connect shell properties
hasActiveMedia: root.hasActiveMedia
activePlayer: root.activePlayer
weatherAvailable: root.weather.available
weatherCode: root.weather.wCode
weatherTemp: root.weather.temp
weatherTempF: root.weather.tempF
osLogo: root.osLogo
networkStatus: root.networkStatus
wifiSignalStrength: root.wifiSignalStrength
volumeLevel: root.volumeLevel
volumeMuted: root.volumeMuted
bluetoothAvailable: root.bluetoothAvailable
bluetoothEnabled: root.bluetoothEnabled
shellRoot: root
notificationCount: NotificationService.notifications.length
processDropdown: processListDropdown