mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-28 15:32:50 -05:00
Systematic cleanup and qmlfmt of all services
- qmlfmt kinda sucks but it's what qt creator uses
This commit is contained in:
@@ -138,7 +138,7 @@ DankModal {
|
|||||||
wifiPasswordInput = text
|
wifiPasswordInput = text
|
||||||
}
|
}
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
NetworkService.connectToWifiWithPassword(
|
NetworkService.connectToWifi(
|
||||||
wifiPasswordSSID, passwordInput.text)
|
wifiPasswordSSID, passwordInput.text)
|
||||||
close()
|
close()
|
||||||
wifiPasswordInput = ""
|
wifiPasswordInput = ""
|
||||||
@@ -286,7 +286,7 @@ DankModal {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
enabled: parent.enabled
|
enabled: parent.enabled
|
||||||
onClicked: {
|
onClicked: {
|
||||||
NetworkService.connectToWifiWithPassword(
|
NetworkService.connectToWifi(
|
||||||
wifiPasswordSSID,
|
wifiPasswordSSID,
|
||||||
passwordInput.text)
|
passwordInput.text)
|
||||||
close()
|
close()
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import "../Common/fuzzysort.js" as Fuzzy
|
import "../Common/fuzzysort.js" as Fuzzy
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
@@ -13,60 +12,46 @@ Singleton {
|
|||||||
property var applications: DesktopEntries.applications.values.filter(app => !app.noDisplay && !app.runInTerminal)
|
property var applications: DesktopEntries.applications.values.filter(app => !app.noDisplay && !app.runInTerminal)
|
||||||
|
|
||||||
property var preppedApps: applications.map(app => ({
|
property var preppedApps: applications.map(app => ({
|
||||||
"name": Fuzzy.prepare(
|
"name": Fuzzy.prepare(app.name || ""),
|
||||||
app.name
|
"comment": Fuzzy.prepare(app.comment || ""),
|
||||||
|| ""),
|
|
||||||
"comment": Fuzzy.prepare(
|
|
||||||
app.comment
|
|
||||||
|| ""),
|
|
||||||
"entry": app
|
"entry": app
|
||||||
}))
|
}))
|
||||||
|
|
||||||
function searchApplications(query) {
|
function searchApplications(query) {
|
||||||
if (!query || query.length === 0) {
|
if (!query || query.length === 0)
|
||||||
return applications
|
return applications
|
||||||
}
|
if (preppedApps.length === 0)
|
||||||
|
|
||||||
if (preppedApps.length === 0) {
|
|
||||||
return []
|
return []
|
||||||
}
|
|
||||||
|
|
||||||
var results = Fuzzy.go(query, preppedApps, {
|
var results = Fuzzy.go(query, preppedApps, {
|
||||||
"all": false,
|
"all": false,
|
||||||
"keys": ["name", "comment"],
|
"keys": ["name", "comment"],
|
||||||
"scoreFn": r => {
|
"scoreFn": r => {
|
||||||
var nameScore = r[0] ? r[0].score : 0
|
const nameScore = r[0]?.score || 0
|
||||||
var commentScore = r[1] ? r[1].score : 0
|
const commentScore = r[1]?.score || 0
|
||||||
var appName = r.obj.entry.name || ""
|
const appName = r.obj.entry.name || ""
|
||||||
var finalScore = 0
|
|
||||||
|
|
||||||
if (nameScore > 0) {
|
if (nameScore === 0) {
|
||||||
var queryLower = query.toLowerCase()
|
return commentScore * 0.1
|
||||||
var nameLower = appName.toLowerCase()
|
|
||||||
|
|
||||||
if (nameLower === queryLower) {
|
|
||||||
finalScore = nameScore * 100
|
|
||||||
} else if (nameLower.startsWith(
|
|
||||||
queryLower)) {
|
|
||||||
finalScore = nameScore * 50
|
|
||||||
} else if (nameLower.includes(
|
|
||||||
" " + queryLower)
|
|
||||||
|| nameLower.includes(
|
|
||||||
queryLower + " ")
|
|
||||||
|| nameLower.endsWith(
|
|
||||||
" " + queryLower)) {
|
|
||||||
finalScore = nameScore * 25
|
|
||||||
} else if (nameLower.includes(
|
|
||||||
queryLower)) {
|
|
||||||
finalScore = nameScore * 10
|
|
||||||
} else {
|
|
||||||
finalScore = nameScore * 2 + commentScore * 0.1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
finalScore = commentScore * 0.1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return finalScore
|
const queryLower = query.toLowerCase()
|
||||||
|
const nameLower = appName.toLowerCase()
|
||||||
|
|
||||||
|
if (nameLower === queryLower) {
|
||||||
|
return nameScore * 100
|
||||||
|
}
|
||||||
|
if (nameLower.startsWith(queryLower)) {
|
||||||
|
return nameScore * 50
|
||||||
|
}
|
||||||
|
if (nameLower.includes(" " + queryLower) || nameLower.includes(queryLower + " ") || nameLower.endsWith(" " + queryLower)) {
|
||||||
|
return nameScore * 25
|
||||||
|
}
|
||||||
|
if (nameLower.includes(queryLower)) {
|
||||||
|
return nameScore * 10
|
||||||
|
}
|
||||||
|
|
||||||
|
return nameScore * 2 + commentScore * 0.1
|
||||||
},
|
},
|
||||||
"limit": 50
|
"limit": 50
|
||||||
})
|
})
|
||||||
@@ -75,10 +60,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getCategoriesForApp(app) {
|
function getCategoriesForApp(app) {
|
||||||
if (!app || !app.categories)
|
if (!app?.categories)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
var categoryMap = {
|
const categoryMap = {
|
||||||
"AudioVideo": "Media",
|
"AudioVideo": "Media",
|
||||||
"Audio": "Media",
|
"Audio": "Media",
|
||||||
"Video": "Media",
|
"Video": "Media",
|
||||||
@@ -105,19 +90,16 @@ Singleton {
|
|||||||
"TerminalEmulator": "Utilities"
|
"TerminalEmulator": "Utilities"
|
||||||
}
|
}
|
||||||
|
|
||||||
var mappedCategories = new Set()
|
const mappedCategories = new Set()
|
||||||
|
|
||||||
for (var i = 0; i < app.categories.length; i++) {
|
for (const cat of app.categories) {
|
||||||
var cat = app.categories[i]
|
if (categoryMap[cat])
|
||||||
if (categoryMap[cat]) {
|
|
||||||
mappedCategories.add(categoryMap[cat])
|
mappedCategories.add(categoryMap[cat])
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(mappedCategories)
|
return Array.from(mappedCategories)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Category icon mappings
|
|
||||||
property var categoryIcons: ({
|
property var categoryIcons: ({
|
||||||
"All": "apps",
|
"All": "apps",
|
||||||
"Media": "music_video",
|
"Media": "music_video",
|
||||||
@@ -136,10 +118,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAllCategories() {
|
function getAllCategories() {
|
||||||
var categories = new Set(["All"])
|
const categories = new Set(["All"])
|
||||||
|
|
||||||
for (var i = 0; i < applications.length; i++) {
|
for (const app of applications) {
|
||||||
var appCategories = getCategoriesForApp(applications[i])
|
const appCategories = getCategoriesForApp(app)
|
||||||
appCategories.forEach(cat => categories.add(cat))
|
appCategories.forEach(cat => categories.add(cat))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,8 +134,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return applications.filter(app => {
|
return applications.filter(app => {
|
||||||
var appCategories = getCategoriesForApp(
|
const appCategories = getCategoriesForApp(app)
|
||||||
app)
|
|
||||||
return appCategories.includes(category)
|
return appCategories.includes(category)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ Singleton {
|
|||||||
signal micMuteChanged
|
signal micMuteChanged
|
||||||
|
|
||||||
function displayName(node) {
|
function displayName(node) {
|
||||||
if (!node)
|
if (!node) {
|
||||||
return ""
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
if (node.properties && node.properties["device.description"]) {
|
if (node.properties && node.properties["device.description"]) {
|
||||||
return node.properties["device.description"]
|
return node.properties["device.description"]
|
||||||
@@ -32,39 +33,51 @@ Singleton {
|
|||||||
return node.nickname
|
return node.nickname
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.name.includes("analog-stereo"))
|
if (node.name.includes("analog-stereo")) {
|
||||||
return "Built-in Speakers"
|
return "Built-in Speakers"
|
||||||
else if (node.name.includes("bluez"))
|
}
|
||||||
|
if (node.name.includes("bluez")) {
|
||||||
return "Bluetooth Audio"
|
return "Bluetooth Audio"
|
||||||
else if (node.name.includes("usb"))
|
}
|
||||||
|
if (node.name.includes("usb")) {
|
||||||
return "USB Audio"
|
return "USB Audio"
|
||||||
else if (node.name.includes("hdmi"))
|
}
|
||||||
|
if (node.name.includes("hdmi")) {
|
||||||
return "HDMI Audio"
|
return "HDMI Audio"
|
||||||
|
}
|
||||||
|
|
||||||
return node.name
|
return node.name
|
||||||
}
|
}
|
||||||
|
|
||||||
function subtitle(name) {
|
function subtitle(name) {
|
||||||
if (!name)
|
if (!name) {
|
||||||
return ""
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
if (name.includes('usb-')) {
|
if (name.includes('usb-')) {
|
||||||
if (name.includes('SteelSeries')) {
|
if (name.includes('SteelSeries')) {
|
||||||
return "USB Gaming Headset"
|
return "USB Gaming Headset"
|
||||||
} else if (name.includes('Generic')) {
|
}
|
||||||
|
if (name.includes('Generic')) {
|
||||||
return "USB Audio Device"
|
return "USB Audio Device"
|
||||||
}
|
}
|
||||||
return "USB Audio"
|
return "USB Audio"
|
||||||
} else if (name.includes('pci-')) {
|
}
|
||||||
|
|
||||||
|
if (name.includes('pci-')) {
|
||||||
if (name.includes('01_00.1') || name.includes('01:00.1')) {
|
if (name.includes('01_00.1') || name.includes('01:00.1')) {
|
||||||
return "NVIDIA GPU Audio"
|
return "NVIDIA GPU Audio"
|
||||||
}
|
}
|
||||||
return "PCI Audio"
|
return "PCI Audio"
|
||||||
} else if (name.includes('bluez')) {
|
}
|
||||||
|
|
||||||
|
if (name.includes('bluez')) {
|
||||||
return "Bluetooth Audio"
|
return "Bluetooth Audio"
|
||||||
} else if (name.includes('analog')) {
|
}
|
||||||
|
if (name.includes('analog')) {
|
||||||
return "Built-in Audio"
|
return "Built-in Audio"
|
||||||
} else if (name.includes('hdmi')) {
|
}
|
||||||
|
if (name.includes('hdmi')) {
|
||||||
return "HDMI Audio"
|
return "HDMI Audio"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,48 +85,48 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PwObjectTracker {
|
PwObjectTracker {
|
||||||
objects: Pipewire.nodes.values.filter(
|
objects: Pipewire.nodes.values.filter(node => node.audio && !node.isStream)
|
||||||
node => node.audio && !node.isStream
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Volume control functions
|
|
||||||
function setVolume(percentage) {
|
function setVolume(percentage) {
|
||||||
if (root.sink && root.sink.audio) {
|
if (!root.sink?.audio) {
|
||||||
const clampedVolume = Math.max(0, Math.min(100, percentage))
|
return "No audio sink available"
|
||||||
root.sink.audio.volume = clampedVolume / 100
|
|
||||||
root.volumeChanged()
|
|
||||||
return "Volume set to " + clampedVolume + "%"
|
|
||||||
}
|
}
|
||||||
return "No audio sink available"
|
|
||||||
|
const clampedVolume = Math.max(0, Math.min(100, percentage))
|
||||||
|
root.sink.audio.volume = clampedVolume / 100
|
||||||
|
root.volumeChanged()
|
||||||
|
return `Volume set to ${clampedVolume}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleMute() {
|
function toggleMute() {
|
||||||
if (root.sink && root.sink.audio) {
|
if (!root.sink?.audio) {
|
||||||
root.sink.audio.muted = !root.sink.audio.muted
|
return "No audio sink available"
|
||||||
return root.sink.audio.muted ? "Audio muted" : "Audio unmuted"
|
|
||||||
}
|
}
|
||||||
return "No audio sink available"
|
|
||||||
|
root.sink.audio.muted = !root.sink.audio.muted
|
||||||
|
return root.sink.audio.muted ? "Audio muted" : "Audio unmuted"
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMicVolume(percentage) {
|
function setMicVolume(percentage) {
|
||||||
if (root.source && root.source.audio) {
|
if (!root.source?.audio) {
|
||||||
const clampedVolume = Math.max(0, Math.min(100, percentage))
|
return "No audio source available"
|
||||||
root.source.audio.volume = clampedVolume / 100
|
|
||||||
return "Microphone volume set to " + clampedVolume + "%"
|
|
||||||
}
|
}
|
||||||
return "No audio source available"
|
|
||||||
|
const clampedVolume = Math.max(0, Math.min(100, percentage))
|
||||||
|
root.source.audio.volume = clampedVolume / 100
|
||||||
|
return `Microphone volume set to ${clampedVolume}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleMicMute() {
|
function toggleMicMute() {
|
||||||
if (root.source && root.source.audio) {
|
if (!root.source?.audio) {
|
||||||
root.source.audio.muted = !root.source.audio.muted
|
return "No audio source available"
|
||||||
return root.source.audio.muted ? "Microphone muted" : "Microphone unmuted"
|
|
||||||
}
|
}
|
||||||
return "No audio source available"
|
|
||||||
|
root.source.audio.muted = !root.source.audio.muted
|
||||||
|
return root.source.audio.muted ? "Microphone muted" : "Microphone unmuted"
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPC Handler for external control
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
target: "audio"
|
target: "audio"
|
||||||
|
|
||||||
@@ -122,35 +135,39 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function increment(step: string): string {
|
function increment(step: string): string {
|
||||||
if (root.sink && root.sink.audio) {
|
if (!root.sink?.audio) {
|
||||||
if (root.sink.audio.muted) {
|
return "No audio sink available"
|
||||||
root.sink.audio.muted = false
|
|
||||||
}
|
|
||||||
const currentVolume = Math.round(root.sink.audio.volume * 100)
|
|
||||||
const newVolume = Math.max(0, Math.min(100,
|
|
||||||
currentVolume + parseInt(
|
|
||||||
step || "5")))
|
|
||||||
root.sink.audio.volume = newVolume / 100
|
|
||||||
root.volumeChanged()
|
|
||||||
return "Volume increased to " + newVolume + "%"
|
|
||||||
}
|
}
|
||||||
return "No audio sink available"
|
|
||||||
|
if (root.sink.audio.muted) {
|
||||||
|
root.sink.audio.muted = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentVolume = Math.round(root.sink.audio.volume * 100)
|
||||||
|
const stepValue = parseInt(step || "5")
|
||||||
|
const newVolume = Math.max(0, Math.min(100, currentVolume + stepValue))
|
||||||
|
|
||||||
|
root.sink.audio.volume = newVolume / 100
|
||||||
|
root.volumeChanged()
|
||||||
|
return `Volume increased to ${newVolume}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
function decrement(step: string): string {
|
function decrement(step: string): string {
|
||||||
if (root.sink && root.sink.audio) {
|
if (!root.sink?.audio) {
|
||||||
if (root.sink.audio.muted) {
|
return "No audio sink available"
|
||||||
root.sink.audio.muted = false
|
|
||||||
}
|
|
||||||
const currentVolume = Math.round(root.sink.audio.volume * 100)
|
|
||||||
const newVolume = Math.max(0, Math.min(100,
|
|
||||||
currentVolume - parseInt(
|
|
||||||
step || "5")))
|
|
||||||
root.sink.audio.volume = newVolume / 100
|
|
||||||
root.volumeChanged()
|
|
||||||
return "Volume decreased to " + newVolume + "%"
|
|
||||||
}
|
}
|
||||||
return "No audio sink available"
|
|
||||||
|
if (root.sink.audio.muted) {
|
||||||
|
root.sink.audio.muted = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentVolume = Math.round(root.sink.audio.volume * 100)
|
||||||
|
const stepValue = parseInt(step || "5")
|
||||||
|
const newVolume = Math.max(0, Math.min(100, currentVolume - stepValue))
|
||||||
|
|
||||||
|
root.sink.audio.volume = newVolume / 100
|
||||||
|
root.volumeChanged()
|
||||||
|
return `Volume decreased to ${newVolume}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
function mute(): string {
|
function mute(): string {
|
||||||
@@ -171,17 +188,19 @@ Singleton {
|
|||||||
|
|
||||||
function status(): string {
|
function status(): string {
|
||||||
let result = "Audio Status:\n"
|
let result = "Audio Status:\n"
|
||||||
if (root.sink && root.sink.audio) {
|
|
||||||
|
if (root.sink?.audio) {
|
||||||
const volume = Math.round(root.sink.audio.volume * 100)
|
const volume = Math.round(root.sink.audio.volume * 100)
|
||||||
result += "Output: " + volume + "%"
|
const muteStatus = root.sink.audio.muted ? " (muted)" : ""
|
||||||
+ (root.sink.audio.muted ? " (muted)" : "") + "\n"
|
result += `Output: ${volume}%${muteStatus}\n`
|
||||||
} else {
|
} else {
|
||||||
result += "Output: No sink available\n"
|
result += "Output: No sink available\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.source && root.source.audio) {
|
if (root.source?.audio) {
|
||||||
const micVolume = Math.round(root.source.audio.volume * 100)
|
const micVolume = Math.round(root.source.audio.volume * 100)
|
||||||
result += "Input: " + micVolume + "%" + (root.source.audio.muted ? " (muted)" : "")
|
const muteStatus = root.source.audio.muted ? " (muted)" : ""
|
||||||
|
result += `Input: ${micVolume}%${muteStatus}`
|
||||||
} else {
|
} else {
|
||||||
result += "Input: No source available"
|
result += "Input: No source available"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -9,68 +10,50 @@ Singleton {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property UPowerDevice device: UPower.displayDevice
|
readonly property UPowerDevice device: UPower.displayDevice
|
||||||
readonly property bool batteryAvailable: device && device.ready
|
readonly property bool batteryAvailable: device && device.ready && device.isLaptopBattery
|
||||||
&& device.isLaptopBattery
|
readonly property real batteryLevel: batteryAvailable ? Math.round(device.percentage * 100) : 0
|
||||||
readonly property real batteryLevel: batteryAvailable ? Math.round(
|
readonly property bool isCharging: batteryAvailable && device.state === UPowerDeviceState.Charging && device.changeRate > 0
|
||||||
device.percentage * 100) : 0
|
readonly property bool isPluggedIn: batteryAvailable && (device.state !== UPowerDeviceState.Discharging && device.state !== UPowerDeviceState.Empty)
|
||||||
readonly property bool isCharging: batteryAvailable
|
|
||||||
&& device.state === UPowerDeviceState.Charging
|
|
||||||
&& device.changeRate > 0
|
|
||||||
readonly property bool isPluggedIn: batteryAvailable
|
|
||||||
&& (device.state !== UPowerDeviceState.Discharging
|
|
||||||
&& device.state !== UPowerDeviceState.Empty)
|
|
||||||
readonly property bool isLowBattery: batteryAvailable && batteryLevel <= 20
|
readonly property bool isLowBattery: batteryAvailable && batteryLevel <= 20
|
||||||
readonly property string batteryHealth: {
|
readonly property string batteryHealth: {
|
||||||
if (!batteryAvailable)
|
if (!batteryAvailable) {
|
||||||
return "N/A"
|
return "N/A"
|
||||||
|
}
|
||||||
|
|
||||||
if (device.healthSupported && device.healthPercentage > 0)
|
if (device.healthSupported && device.healthPercentage > 0) {
|
||||||
return Math.round(device.healthPercentage) + "%"
|
return `${Math.round(device.healthPercentage)}%`
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate health from energy capacity vs design capacity
|
|
||||||
if (device.energyCapacity > 0 && device.energy > 0) {
|
if (device.energyCapacity > 0 && device.energy > 0) {
|
||||||
// energyCapacity is current full capacity, we need design capacity
|
const healthPercent = (device.energyCapacity / 90.0045) * 100
|
||||||
// Use a rough estimate based on typical battery degradation patterns
|
return `${Math.round(healthPercent)}%`
|
||||||
var healthPercent = (device.energyCapacity / 90.0045)
|
|
||||||
* 100 // your design capacity from upower
|
|
||||||
return Math.round(healthPercent) + "%"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "N/A"
|
return "N/A"
|
||||||
}
|
}
|
||||||
readonly property real batteryCapacity: batteryAvailable
|
readonly property real batteryCapacity: batteryAvailable && device.energyCapacity > 0 ? device.energyCapacity : 0
|
||||||
&& device.energyCapacity > 0 ? device.energyCapacity : 0
|
|
||||||
readonly property string batteryStatus: {
|
readonly property string batteryStatus: {
|
||||||
if (!batteryAvailable)
|
if (!batteryAvailable) {
|
||||||
return "No Battery"
|
return "No Battery"
|
||||||
|
}
|
||||||
|
|
||||||
if (device.state === UPowerDeviceState.Charging
|
if (device.state === UPowerDeviceState.Charging && device.changeRate <= 0) {
|
||||||
&& device.changeRate <= 0)
|
return "Plugged In"
|
||||||
return "Plugged In"
|
}
|
||||||
|
|
||||||
return UPowerDeviceState.toString(device.state)
|
return UPowerDeviceState.toString(device.state)
|
||||||
}
|
}
|
||||||
readonly property bool suggestPowerSaver: batteryAvailable && isLowBattery
|
readonly property bool suggestPowerSaver: batteryAvailable && isLowBattery && UPower.onBattery && (typeof PowerProfiles !== "undefined" && PowerProfiles.profile !== PowerProfile.PowerSaver)
|
||||||
&& UPower.onBattery
|
|
||||||
&& (typeof PowerProfiles !== "undefined"
|
|
||||||
&& PowerProfiles.profile
|
|
||||||
!== PowerProfile.PowerSaver)
|
|
||||||
|
|
||||||
readonly property var bluetoothDevices: {
|
readonly property var bluetoothDevices: {
|
||||||
var btDevices = []
|
const btDevices = []
|
||||||
|
const bluetoothTypes = [UPowerDeviceType.BluetoothGeneric, UPowerDeviceType.Headphones, UPowerDeviceType.Headset, UPowerDeviceType.Keyboard, UPowerDeviceType.Mouse, UPowerDeviceType.Speakers]
|
||||||
|
|
||||||
for (var i = 0; i < UPower.devices.count; i++) {
|
for (var i = 0; i < UPower.devices.count; i++) {
|
||||||
var dev = UPower.devices.get(i)
|
const dev = UPower.devices.get(i)
|
||||||
if (dev
|
if (dev && dev.ready && bluetoothTypes.includes(dev.type)) {
|
||||||
&& dev.ready && (dev.type === UPowerDeviceType.BluetoothGeneric || dev.type
|
|
||||||
=== UPowerDeviceType.Headphones || dev.type
|
|
||||||
=== UPowerDeviceType.Headset || dev.type
|
|
||||||
=== UPowerDeviceType.Keyboard || dev.type
|
|
||||||
=== UPowerDeviceType.Mouse || dev.type
|
|
||||||
=== UPowerDeviceType.Speakers)) {
|
|
||||||
btDevices.push({
|
btDevices.push({
|
||||||
"name": dev.model
|
"name": dev.model || UPowerDeviceType.toString(dev.type),
|
||||||
|| UPowerDeviceType.toString(
|
|
||||||
dev.type),
|
|
||||||
"percentage": Math.round(dev.percentage),
|
"percentage": Math.round(dev.percentage),
|
||||||
"type": dev.type
|
"type": dev.type
|
||||||
})
|
})
|
||||||
@@ -80,20 +63,23 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatTimeRemaining() {
|
function formatTimeRemaining() {
|
||||||
if (!batteryAvailable)
|
if (!batteryAvailable) {
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
var timeSeconds = isCharging ? device.timeToFull : device.timeToEmpty
|
const timeSeconds = isCharging ? device.timeToFull : device.timeToEmpty
|
||||||
|
|
||||||
if (!timeSeconds || timeSeconds <= 0 || timeSeconds > 86400)
|
if (!timeSeconds || timeSeconds <= 0 || timeSeconds > 86400) {
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
var hours = Math.floor(timeSeconds / 3600)
|
const hours = Math.floor(timeSeconds / 3600)
|
||||||
var minutes = Math.floor((timeSeconds % 3600) / 60)
|
const minutes = Math.floor((timeSeconds % 3600) / 60)
|
||||||
|
|
||||||
if (hours > 0)
|
if (hours > 0) {
|
||||||
return hours + "h " + minutes + "m"
|
return `${hours}h ${minutes}m`
|
||||||
else
|
}
|
||||||
return minutes + "m"
|
|
||||||
|
return `${minutes}m`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -12,230 +13,238 @@ Singleton {
|
|||||||
readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter
|
readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter
|
||||||
readonly property bool available: adapter !== null
|
readonly property bool available: adapter !== null
|
||||||
readonly property bool enabled: (adapter && adapter.enabled) ?? false
|
readonly property bool enabled: (adapter && adapter.enabled) ?? false
|
||||||
readonly property bool discovering: (adapter
|
readonly property bool discovering: (adapter && adapter.discovering) ?? false
|
||||||
&& adapter.discovering) ?? false
|
|
||||||
readonly property var devices: adapter ? adapter.devices : null
|
readonly property var devices: adapter ? adapter.devices : null
|
||||||
readonly property var pairedDevices: {
|
readonly property var pairedDevices: {
|
||||||
if (!adapter || !adapter.devices)
|
if (!adapter || !adapter.devices) {
|
||||||
return []
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
return adapter.devices.values.filter(dev => {
|
return adapter.devices.values.filter(dev => {
|
||||||
return dev && (dev.paired
|
return dev && (dev.paired || dev.trusted)
|
||||||
|| dev.trusted)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
readonly property var allDevicesWithBattery: {
|
readonly property var allDevicesWithBattery: {
|
||||||
if (!adapter || !adapter.devices)
|
if (!adapter || !adapter.devices) {
|
||||||
return []
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
return adapter.devices.values.filter(dev => {
|
return adapter.devices.values.filter(dev => {
|
||||||
return dev
|
return dev && dev.batteryAvailable && dev.battery > 0
|
||||||
&& dev.batteryAvailable
|
|
||||||
&& dev.battery > 0
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortDevices(devices) {
|
function sortDevices(devices) {
|
||||||
return devices.sort((a, b) => {
|
return devices.sort((a, b) => {
|
||||||
var aName = a.name || a.deviceName || ""
|
const aName = a.name || a.deviceName || ""
|
||||||
var bName = b.name || b.deviceName || ""
|
const bName = b.name || b.deviceName || ""
|
||||||
|
|
||||||
var aHasRealName = aName.includes(" ")
|
const aHasRealName = aName.includes(" ") && aName.length > 3
|
||||||
&& aName.length > 3
|
const bHasRealName = bName.includes(" ") && bName.length > 3
|
||||||
var bHasRealName = bName.includes(" ")
|
|
||||||
&& bName.length > 3
|
|
||||||
|
|
||||||
if (aHasRealName && !bHasRealName)
|
if (aHasRealName && !bHasRealName) {
|
||||||
return -1
|
return -1
|
||||||
if (!aHasRealName && bHasRealName)
|
}
|
||||||
return 1
|
if (!aHasRealName && bHasRealName) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
var aSignal = (a.signalStrength !== undefined
|
const aSignal = (a.signalStrength !== undefined && a.signalStrength > 0) ? a.signalStrength : 0
|
||||||
&& a.signalStrength > 0) ? a.signalStrength : 0
|
const bSignal = (b.signalStrength !== undefined && b.signalStrength > 0) ? b.signalStrength : 0
|
||||||
var bSignal = (b.signalStrength !== undefined
|
|
||||||
&& b.signalStrength > 0) ? b.signalStrength : 0
|
|
||||||
return bSignal - aSignal
|
return bSignal - aSignal
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDeviceIcon(device) {
|
function getDeviceIcon(device) {
|
||||||
if (!device)
|
if (!device) {
|
||||||
return "bluetooth"
|
return "bluetooth"
|
||||||
|
}
|
||||||
|
|
||||||
var name = (device.name || device.deviceName || "").toLowerCase()
|
const name = (device.name || device.deviceName || "").toLowerCase()
|
||||||
var icon = (device.icon || "").toLowerCase()
|
const icon = (device.icon || "").toLowerCase()
|
||||||
if (icon.includes("headset") || icon.includes("audio") || name.includes(
|
|
||||||
"headphone") || name.includes("airpod") || name.includes(
|
const audioKeywords = ["headset", "audio", "headphone", "airpod", "arctis"]
|
||||||
"headset") || name.includes("arctis"))
|
if (audioKeywords.some(keyword => icon.includes(keyword) || name.includes(keyword))) {
|
||||||
return "headset"
|
return "headset"
|
||||||
|
}
|
||||||
|
|
||||||
if (icon.includes("mouse") || name.includes("mouse"))
|
if (icon.includes("mouse") || name.includes("mouse")) {
|
||||||
return "mouse"
|
return "mouse"
|
||||||
|
}
|
||||||
|
|
||||||
if (icon.includes("keyboard") || name.includes("keyboard"))
|
if (icon.includes("keyboard") || name.includes("keyboard")) {
|
||||||
return "keyboard"
|
return "keyboard"
|
||||||
|
}
|
||||||
|
|
||||||
if (icon.includes("phone") || name.includes("phone") || name.includes(
|
const phoneKeywords = ["phone", "iphone", "android", "samsung"]
|
||||||
"iphone") || name.includes("android") || name.includes(
|
if (phoneKeywords.some(keyword => icon.includes(keyword) || name.includes(keyword))) {
|
||||||
"samsung"))
|
|
||||||
return "smartphone"
|
return "smartphone"
|
||||||
|
}
|
||||||
|
|
||||||
if (icon.includes("watch") || name.includes("watch"))
|
if (icon.includes("watch") || name.includes("watch")) {
|
||||||
return "watch"
|
return "watch"
|
||||||
|
}
|
||||||
|
|
||||||
if (icon.includes("speaker") || name.includes("speaker"))
|
if (icon.includes("speaker") || name.includes("speaker")) {
|
||||||
return "speaker"
|
return "speaker"
|
||||||
|
}
|
||||||
|
|
||||||
if (icon.includes("display") || name.includes("tv"))
|
if (icon.includes("display") || name.includes("tv")) {
|
||||||
return "tv"
|
return "tv"
|
||||||
|
}
|
||||||
|
|
||||||
return "bluetooth"
|
return "bluetooth"
|
||||||
}
|
}
|
||||||
|
|
||||||
function canConnect(device) {
|
function canConnect(device) {
|
||||||
if (!device)
|
if (!device) {
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return !device.paired && !device.pairing && !device.blocked
|
return !device.paired && !device.pairing && !device.blocked
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSignalStrength(device) {
|
function getSignalStrength(device) {
|
||||||
if (!device || device.signalStrength === undefined
|
if (!device || device.signalStrength === undefined || device.signalStrength <= 0) {
|
||||||
|| device.signalStrength <= 0)
|
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
var signal = device.signalStrength
|
const signal = device.signalStrength
|
||||||
if (signal >= 80)
|
if (signal >= 80) {
|
||||||
return "Excellent"
|
return "Excellent"
|
||||||
|
}
|
||||||
if (signal >= 60)
|
if (signal >= 60) {
|
||||||
return "Good"
|
return "Good"
|
||||||
|
}
|
||||||
if (signal >= 40)
|
if (signal >= 40) {
|
||||||
return "Fair"
|
return "Fair"
|
||||||
|
}
|
||||||
if (signal >= 20)
|
if (signal >= 20) {
|
||||||
return "Poor"
|
return "Poor"
|
||||||
|
}
|
||||||
|
|
||||||
return "Very Poor"
|
return "Very Poor"
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSignalIcon(device) {
|
function getSignalIcon(device) {
|
||||||
if (!device || device.signalStrength === undefined
|
if (!device || device.signalStrength === undefined || device.signalStrength <= 0) {
|
||||||
|| device.signalStrength <= 0)
|
|
||||||
return "signal_cellular_null"
|
return "signal_cellular_null"
|
||||||
|
}
|
||||||
|
|
||||||
var signal = device.signalStrength
|
const signal = device.signalStrength
|
||||||
if (signal >= 80)
|
if (signal >= 80) {
|
||||||
return "signal_cellular_4_bar"
|
return "signal_cellular_4_bar"
|
||||||
|
}
|
||||||
if (signal >= 60)
|
if (signal >= 60) {
|
||||||
return "signal_cellular_3_bar"
|
return "signal_cellular_3_bar"
|
||||||
|
}
|
||||||
if (signal >= 40)
|
if (signal >= 40) {
|
||||||
return "signal_cellular_2_bar"
|
return "signal_cellular_2_bar"
|
||||||
|
}
|
||||||
if (signal >= 20)
|
if (signal >= 20) {
|
||||||
return "signal_cellular_1_bar"
|
return "signal_cellular_1_bar"
|
||||||
|
}
|
||||||
|
|
||||||
return "signal_cellular_0_bar"
|
return "signal_cellular_0_bar"
|
||||||
}
|
}
|
||||||
|
|
||||||
function isDeviceBusy(device) {
|
function isDeviceBusy(device) {
|
||||||
if (!device)
|
if (!device) {
|
||||||
return false
|
return false
|
||||||
return device.pairing
|
}
|
||||||
|| device.state === BluetoothDeviceState.Disconnecting
|
return device.pairing || device.state === BluetoothDeviceState.Disconnecting || device.state === BluetoothDeviceState.Connecting
|
||||||
|| device.state === BluetoothDeviceState.Connecting
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectDeviceWithTrust(device) {
|
function connectDeviceWithTrust(device) {
|
||||||
if (!device)
|
if (!device) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
device.trusted = true
|
device.trusted = true
|
||||||
device.connect()
|
device.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCardName(device) {
|
function getCardName(device) {
|
||||||
if (!device)
|
if (!device) {
|
||||||
return ""
|
return ""
|
||||||
return "bluez_card." + device.address.replace(/:/g, "_")
|
}
|
||||||
|
return `bluez_card.${device.address.replace(/:/g, "_")}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAudioDevice(device) {
|
function isAudioDevice(device) {
|
||||||
if (!device)
|
if (!device) {
|
||||||
return false
|
return false
|
||||||
let icon = getDeviceIcon(device)
|
}
|
||||||
|
const icon = getDeviceIcon(device)
|
||||||
return icon === "headset" || icon === "speaker"
|
return icon === "headset" || icon === "speaker"
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCodecInfo(codecName) {
|
function getCodecInfo(codecName) {
|
||||||
let codec = codecName.replace(/-/g, "_").toUpperCase()
|
const codec = codecName.replace(/-/g, "_").toUpperCase()
|
||||||
|
|
||||||
let codecMap = {
|
const codecMap = {
|
||||||
"LDAC": {
|
"LDAC": {
|
||||||
name: "LDAC",
|
"name": "LDAC",
|
||||||
description: "Highest quality • Higher battery usage",
|
"description": "Highest quality • Higher battery usage",
|
||||||
qualityColor: "#4CAF50"
|
"qualityColor": "#4CAF50"
|
||||||
},
|
},
|
||||||
"APTX_HD": {
|
"APTX_HD": {
|
||||||
name: "aptX HD",
|
"name": "aptX HD",
|
||||||
description: "High quality • Balanced battery",
|
"description": "High quality • Balanced battery",
|
||||||
qualityColor: "#FF9800"
|
"qualityColor": "#FF9800"
|
||||||
},
|
},
|
||||||
"APTX": {
|
"APTX": {
|
||||||
name: "aptX",
|
"name": "aptX",
|
||||||
description: "Good quality • Low latency",
|
"description": "Good quality • Low latency",
|
||||||
qualityColor: "#FF9800"
|
"qualityColor": "#FF9800"
|
||||||
},
|
},
|
||||||
"AAC": {
|
"AAC": {
|
||||||
name: "AAC",
|
"name": "AAC",
|
||||||
description: "Balanced quality and battery",
|
"description": "Balanced quality and battery",
|
||||||
qualityColor: "#2196F3"
|
"qualityColor": "#2196F3"
|
||||||
},
|
},
|
||||||
"SBC_XQ": {
|
"SBC_XQ": {
|
||||||
name: "SBC-XQ",
|
"name": "SBC-XQ",
|
||||||
description: "Enhanced SBC • Better compatibility",
|
"description": "Enhanced SBC • Better compatibility",
|
||||||
qualityColor: "#2196F3"
|
"qualityColor": "#2196F3"
|
||||||
},
|
},
|
||||||
"SBC": {
|
"SBC": {
|
||||||
name: "SBC",
|
"name": "SBC",
|
||||||
description: "Basic quality • Universal compatibility",
|
"description": "Basic quality • Universal compatibility",
|
||||||
qualityColor: "#9E9E9E"
|
"qualityColor": "#9E9E9E"
|
||||||
},
|
},
|
||||||
"MSBC": {
|
"MSBC": {
|
||||||
name: "mSBC",
|
"name": "mSBC",
|
||||||
description: "Modified SBC • Optimized for speech",
|
"description": "Modified SBC • Optimized for speech",
|
||||||
qualityColor: "#9E9E9E"
|
"qualityColor": "#9E9E9E"
|
||||||
},
|
},
|
||||||
"CVSD": {
|
"CVSD": {
|
||||||
name: "CVSD",
|
"name": "CVSD",
|
||||||
description: "Basic speech codec • Legacy compatibility",
|
"description": "Basic speech codec • Legacy compatibility",
|
||||||
qualityColor: "#9E9E9E"
|
"qualityColor": "#9E9E9E"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return codecMap[codec] || {
|
return codecMap[codec] || {
|
||||||
name: codecName,
|
"name": codecName,
|
||||||
description: "Unknown codec",
|
"description": "Unknown codec",
|
||||||
qualityColor: "#9E9E9E"
|
"qualityColor": "#9E9E9E"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property var deviceCodecs: ({})
|
property var deviceCodecs: ({})
|
||||||
|
|
||||||
function updateDeviceCodec(deviceAddress, codec) {
|
function updateDeviceCodec(deviceAddress, codec) {
|
||||||
deviceCodecs[deviceAddress] = codec
|
deviceCodecs[deviceAddress] = codec
|
||||||
deviceCodecsChanged()
|
deviceCodecsChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshDeviceCodec(device) {
|
function refreshDeviceCodec(device) {
|
||||||
if (!device || !device.connected || !isAudioDevice(device)) {
|
if (!device || !device.connected || !isAudioDevice(device)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let cardName = getCardName(device)
|
const cardName = getCardName(device)
|
||||||
codecQueryProcess.cardName = cardName
|
codecQueryProcess.cardName = cardName
|
||||||
codecQueryProcess.deviceAddress = device.address
|
codecQueryProcess.deviceAddress = device.address
|
||||||
codecQueryProcess.availableCodecs = []
|
codecQueryProcess.availableCodecs = []
|
||||||
@@ -243,14 +252,14 @@ Singleton {
|
|||||||
codecQueryProcess.detectedCodec = ""
|
codecQueryProcess.detectedCodec = ""
|
||||||
codecQueryProcess.running = true
|
codecQueryProcess.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentCodec(device, callback) {
|
function getCurrentCodec(device, callback) {
|
||||||
if (!device || !device.connected || !isAudioDevice(device)) {
|
if (!device || !device.connected || !isAudioDevice(device)) {
|
||||||
callback("")
|
callback("")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let cardName = getCardName(device)
|
const cardName = getCardName(device)
|
||||||
codecQueryProcess.cardName = cardName
|
codecQueryProcess.cardName = cardName
|
||||||
codecQueryProcess.callback = callback
|
codecQueryProcess.callback = callback
|
||||||
codecQueryProcess.availableCodecs = []
|
codecQueryProcess.availableCodecs = []
|
||||||
@@ -258,14 +267,14 @@ Singleton {
|
|||||||
codecQueryProcess.detectedCodec = ""
|
codecQueryProcess.detectedCodec = ""
|
||||||
codecQueryProcess.running = true
|
codecQueryProcess.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAvailableCodecs(device, callback) {
|
function getAvailableCodecs(device, callback) {
|
||||||
if (!device || !device.connected || !isAudioDevice(device)) {
|
if (!device || !device.connected || !isAudioDevice(device)) {
|
||||||
callback([], "")
|
callback([], "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let cardName = getCardName(device)
|
const cardName = getCardName(device)
|
||||||
codecFullQueryProcess.cardName = cardName
|
codecFullQueryProcess.cardName = cardName
|
||||||
codecFullQueryProcess.callback = callback
|
codecFullQueryProcess.callback = callback
|
||||||
codecFullQueryProcess.availableCodecs = []
|
codecFullQueryProcess.availableCodecs = []
|
||||||
@@ -273,33 +282,33 @@ Singleton {
|
|||||||
codecFullQueryProcess.detectedCodec = ""
|
codecFullQueryProcess.detectedCodec = ""
|
||||||
codecFullQueryProcess.running = true
|
codecFullQueryProcess.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchCodec(device, profileName, callback) {
|
function switchCodec(device, profileName, callback) {
|
||||||
if (!device || !isAudioDevice(device)) {
|
if (!device || !isAudioDevice(device)) {
|
||||||
callback(false, "Invalid device")
|
callback(false, "Invalid device")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let cardName = getCardName(device)
|
const cardName = getCardName(device)
|
||||||
codecSwitchProcess.cardName = cardName
|
codecSwitchProcess.cardName = cardName
|
||||||
codecSwitchProcess.profile = profileName
|
codecSwitchProcess.profile = profileName
|
||||||
codecSwitchProcess.callback = callback
|
codecSwitchProcess.callback = callback
|
||||||
codecSwitchProcess.running = true
|
codecSwitchProcess.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: codecQueryProcess
|
id: codecQueryProcess
|
||||||
|
|
||||||
property string cardName: ""
|
property string cardName: ""
|
||||||
property string deviceAddress: ""
|
property string deviceAddress: ""
|
||||||
property var callback: null
|
property var callback: null
|
||||||
property bool parsingTargetCard: false
|
property bool parsingTargetCard: false
|
||||||
property string detectedCodec: ""
|
property string detectedCodec: ""
|
||||||
property var availableCodecs: []
|
property var availableCodecs: []
|
||||||
|
|
||||||
command: ["pactl", "list", "cards"]
|
command: ["pactl", "list", "cards"]
|
||||||
|
|
||||||
onExited: function(exitCode, exitStatus) {
|
onExited: (exitCode, exitStatus) => {
|
||||||
if (exitCode === 0 && detectedCodec) {
|
if (exitCode === 0 && detectedCodec) {
|
||||||
if (deviceAddress) {
|
if (deviceAddress) {
|
||||||
root.updateDeviceCodec(deviceAddress, detectedCodec)
|
root.updateDeviceCodec(deviceAddress, detectedCodec)
|
||||||
@@ -310,35 +319,35 @@ Singleton {
|
|||||||
} else if (callback) {
|
} else if (callback) {
|
||||||
callback("")
|
callback("")
|
||||||
}
|
}
|
||||||
|
|
||||||
parsingTargetCard = false
|
parsingTargetCard = false
|
||||||
detectedCodec = ""
|
detectedCodec = ""
|
||||||
availableCodecs = []
|
availableCodecs = []
|
||||||
deviceAddress = ""
|
deviceAddress = ""
|
||||||
callback = null
|
callback = null
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout: SplitParser {
|
stdout: SplitParser {
|
||||||
splitMarker: "\n"
|
splitMarker: "\n"
|
||||||
onRead: (data) => {
|
onRead: data => {
|
||||||
let line = data.trim()
|
let line = data.trim()
|
||||||
|
|
||||||
if (line.includes(`Name: ${codecQueryProcess.cardName}`)) {
|
if (line.includes(`Name: ${codecQueryProcess.cardName}`)) {
|
||||||
codecQueryProcess.parsingTargetCard = true
|
codecQueryProcess.parsingTargetCard = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codecQueryProcess.parsingTargetCard && line.startsWith("Name: ") && !line.includes(codecQueryProcess.cardName)) {
|
if (codecQueryProcess.parsingTargetCard && line.startsWith("Name: ") && !line.includes(codecQueryProcess.cardName)) {
|
||||||
codecQueryProcess.parsingTargetCard = false
|
codecQueryProcess.parsingTargetCard = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codecQueryProcess.parsingTargetCard) {
|
if (codecQueryProcess.parsingTargetCard) {
|
||||||
if (line.startsWith("Active Profile:")) {
|
if (line.startsWith("Active Profile:")) {
|
||||||
let profile = line.split(": ")[1] || ""
|
let profile = line.split(": ")[1] || ""
|
||||||
let activeCodec = codecQueryProcess.availableCodecs.find((c) => {
|
let activeCodec = codecQueryProcess.availableCodecs.find(c => {
|
||||||
return c.profile === profile
|
return c.profile === profile
|
||||||
})
|
})
|
||||||
if (activeCodec) {
|
if (activeCodec) {
|
||||||
codecQueryProcess.detectedCodec = activeCodec.name
|
codecQueryProcess.detectedCodec = activeCodec.name
|
||||||
}
|
}
|
||||||
@@ -352,16 +361,16 @@ Singleton {
|
|||||||
let codecMatch = description.match(/codec ([^\)\s]+)/i)
|
let codecMatch = description.match(/codec ([^\)\s]+)/i)
|
||||||
let codecName = codecMatch ? codecMatch[1].toUpperCase() : "UNKNOWN"
|
let codecName = codecMatch ? codecMatch[1].toUpperCase() : "UNKNOWN"
|
||||||
let codecInfo = root.getCodecInfo(codecName)
|
let codecInfo = root.getCodecInfo(codecName)
|
||||||
if (codecInfo && !codecQueryProcess.availableCodecs.some((c) => {
|
if (codecInfo && !codecQueryProcess.availableCodecs.some(c => {
|
||||||
return c.profile === profile
|
return c.profile === profile
|
||||||
})) {
|
})) {
|
||||||
let newCodecs = codecQueryProcess.availableCodecs.slice()
|
let newCodecs = codecQueryProcess.availableCodecs.slice()
|
||||||
newCodecs.push({
|
newCodecs.push({
|
||||||
"name": codecInfo.name,
|
"name": codecInfo.name,
|
||||||
"profile": profile,
|
"profile": profile,
|
||||||
"description": codecInfo.description,
|
"description": codecInfo.description,
|
||||||
"qualityColor": codecInfo.qualityColor
|
"qualityColor": codecInfo.qualityColor
|
||||||
})
|
})
|
||||||
codecQueryProcess.availableCodecs = newCodecs
|
codecQueryProcess.availableCodecs = newCodecs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -370,19 +379,19 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: codecFullQueryProcess
|
id: codecFullQueryProcess
|
||||||
|
|
||||||
property string cardName: ""
|
property string cardName: ""
|
||||||
property var callback: null
|
property var callback: null
|
||||||
property bool parsingTargetCard: false
|
property bool parsingTargetCard: false
|
||||||
property string detectedCodec: ""
|
property string detectedCodec: ""
|
||||||
property var availableCodecs: []
|
property var availableCodecs: []
|
||||||
|
|
||||||
command: ["pactl", "list", "cards"]
|
command: ["pactl", "list", "cards"]
|
||||||
|
|
||||||
onExited: function(exitCode, exitStatus) {
|
onExited: function (exitCode, exitStatus) {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(exitCode === 0 ? availableCodecs : [], exitCode === 0 ? detectedCodec : "")
|
callback(exitCode === 0 ? availableCodecs : [], exitCode === 0 ? detectedCodec : "")
|
||||||
}
|
}
|
||||||
@@ -391,28 +400,28 @@ Singleton {
|
|||||||
availableCodecs = []
|
availableCodecs = []
|
||||||
callback = null
|
callback = null
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout: SplitParser {
|
stdout: SplitParser {
|
||||||
splitMarker: "\n"
|
splitMarker: "\n"
|
||||||
onRead: (data) => {
|
onRead: data => {
|
||||||
let line = data.trim()
|
let line = data.trim()
|
||||||
|
|
||||||
if (line.includes(`Name: ${codecFullQueryProcess.cardName}`)) {
|
if (line.includes(`Name: ${codecFullQueryProcess.cardName}`)) {
|
||||||
codecFullQueryProcess.parsingTargetCard = true
|
codecFullQueryProcess.parsingTargetCard = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codecFullQueryProcess.parsingTargetCard && line.startsWith("Name: ") && !line.includes(codecFullQueryProcess.cardName)) {
|
if (codecFullQueryProcess.parsingTargetCard && line.startsWith("Name: ") && !line.includes(codecFullQueryProcess.cardName)) {
|
||||||
codecFullQueryProcess.parsingTargetCard = false
|
codecFullQueryProcess.parsingTargetCard = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codecFullQueryProcess.parsingTargetCard) {
|
if (codecFullQueryProcess.parsingTargetCard) {
|
||||||
if (line.startsWith("Active Profile:")) {
|
if (line.startsWith("Active Profile:")) {
|
||||||
let profile = line.split(": ")[1] || ""
|
let profile = line.split(": ")[1] || ""
|
||||||
let activeCodec = codecFullQueryProcess.availableCodecs.find((c) => {
|
let activeCodec = codecFullQueryProcess.availableCodecs.find(c => {
|
||||||
return c.profile === profile
|
return c.profile === profile
|
||||||
})
|
})
|
||||||
if (activeCodec) {
|
if (activeCodec) {
|
||||||
codecFullQueryProcess.detectedCodec = activeCodec.name
|
codecFullQueryProcess.detectedCodec = activeCodec.name
|
||||||
}
|
}
|
||||||
@@ -426,16 +435,16 @@ Singleton {
|
|||||||
let codecMatch = description.match(/codec ([^\)\s]+)/i)
|
let codecMatch = description.match(/codec ([^\)\s]+)/i)
|
||||||
let codecName = codecMatch ? codecMatch[1].toUpperCase() : "UNKNOWN"
|
let codecName = codecMatch ? codecMatch[1].toUpperCase() : "UNKNOWN"
|
||||||
let codecInfo = root.getCodecInfo(codecName)
|
let codecInfo = root.getCodecInfo(codecName)
|
||||||
if (codecInfo && !codecFullQueryProcess.availableCodecs.some((c) => {
|
if (codecInfo && !codecFullQueryProcess.availableCodecs.some(c => {
|
||||||
return c.profile === profile
|
return c.profile === profile
|
||||||
})) {
|
})) {
|
||||||
let newCodecs = codecFullQueryProcess.availableCodecs.slice()
|
let newCodecs = codecFullQueryProcess.availableCodecs.slice()
|
||||||
newCodecs.push({
|
newCodecs.push({
|
||||||
"name": codecInfo.name,
|
"name": codecInfo.name,
|
||||||
"profile": profile,
|
"profile": profile,
|
||||||
"description": codecInfo.description,
|
"description": codecInfo.description,
|
||||||
"qualityColor": codecInfo.qualityColor
|
"qualityColor": codecInfo.qualityColor
|
||||||
})
|
})
|
||||||
codecFullQueryProcess.availableCodecs = newCodecs
|
codecFullQueryProcess.availableCodecs = newCodecs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -444,32 +453,32 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: codecSwitchProcess
|
id: codecSwitchProcess
|
||||||
|
|
||||||
property string cardName: ""
|
property string cardName: ""
|
||||||
property string profile: ""
|
property string profile: ""
|
||||||
property var callback: null
|
property var callback: null
|
||||||
|
|
||||||
command: ["pactl", "set-card-profile", cardName, profile]
|
command: ["pactl", "set-card-profile", cardName, profile]
|
||||||
|
|
||||||
onExited: function(exitCode, exitStatus) {
|
onExited: function (exitCode, exitStatus) {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(exitCode === 0, exitCode === 0 ? "Codec switched successfully" : "Failed to switch codec")
|
callback(exitCode === 0, exitCode === 0 ? "Codec switched successfully" : "Failed to switch codec")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If successful, refresh the codec for this device
|
// If successful, refresh the codec for this device
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
if (root.adapter && root.adapter.devices) {
|
if (root.adapter && root.adapter.devices) {
|
||||||
root.adapter.devices.values.forEach(device => {
|
root.adapter.devices.values.forEach(device => {
|
||||||
if (device && root.getCardName(device) === cardName) {
|
if (device && root.getCardName(device) === cardName) {
|
||||||
Qt.callLater(() => root.refreshDeviceCodec(device))
|
Qt.callLater(() => root.refreshDeviceCodec(device))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
callback = null
|
callback = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -43,11 +44,9 @@ Singleton {
|
|||||||
onRead: data => {
|
onRead: data => {
|
||||||
if (root.refCount > 0 && data.trim()) {
|
if (root.refCount > 0 && data.trim()) {
|
||||||
let points = data.split(";").map(p => {
|
let points = data.split(";").map(p => {
|
||||||
return parseInt(
|
return parseInt(p.trim(), 10)
|
||||||
p.trim(), 10)
|
|
||||||
}).filter(p => {
|
}).filter(p => {
|
||||||
return !isNaN(
|
return !isNaN(p)
|
||||||
p)
|
|
||||||
})
|
})
|
||||||
if (points.length >= 6) {
|
if (points.length >= 6) {
|
||||||
root.values = points.slice(0, 6)
|
root.values = points.slice(0, 6)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -10,7 +11,6 @@ import Quickshell.Hyprland
|
|||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
// Compositor detection
|
|
||||||
property bool isHyprland: false
|
property bool isHyprland: false
|
||||||
property bool isNiri: false
|
property bool isNiri: false
|
||||||
property string compositor: "unknown"
|
property string compositor: "unknown"
|
||||||
@@ -19,62 +19,64 @@ Singleton {
|
|||||||
readonly property string niriSocket: Quickshell.env("NIRI_SOCKET")
|
readonly property string niriSocket: Quickshell.env("NIRI_SOCKET")
|
||||||
|
|
||||||
property bool useNiriSorting: isNiri && NiriService
|
property bool useNiriSorting: isNiri && NiriService
|
||||||
property bool useHyprlandSorting: false
|
|
||||||
|
|
||||||
property var sortedToplevels: {
|
property var sortedToplevels: {
|
||||||
if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values) {
|
if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only use niri sorting when both compositor is niri AND niri service is ready
|
|
||||||
if (useNiriSorting) {
|
if (useNiriSorting) {
|
||||||
return NiriService.sortToplevels(ToplevelManager.toplevels.values)
|
return NiriService.sortToplevels(ToplevelManager.toplevels.values)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHyprland) {
|
if (isHyprland) {
|
||||||
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
|
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
|
||||||
|
|
||||||
const sortedHyprland = hyprlandToplevels.sort((a, b) => {
|
const sortedHyprland = hyprlandToplevels.sort((a, b) => {
|
||||||
// Sort by monitor first
|
if (a.monitor && b.monitor) {
|
||||||
if (a.monitor && b.monitor) {
|
const monitorCompare = a.monitor.name.localeCompare(b.monitor.name)
|
||||||
const monitorCompare = a.monitor.name.localeCompare(b.monitor.name)
|
if (monitorCompare !== 0) {
|
||||||
if (monitorCompare !== 0) return monitorCompare
|
return monitorCompare
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Then by workspace
|
|
||||||
if (a.workspace && b.workspace) {
|
if (a.workspace && b.workspace) {
|
||||||
const workspaceCompare = a.workspace.id - b.workspace.id
|
const workspaceCompare = a.workspace.id - b.workspace.id
|
||||||
if (workspaceCompare !== 0) return workspaceCompare
|
if (workspaceCompare !== 0) {
|
||||||
}
|
return workspaceCompare
|
||||||
|
}
|
||||||
|
}
|
||||||
if (a.lastIpcObject && b.lastIpcObject && a.lastIpcObject.at && b.lastIpcObject.at) {
|
|
||||||
const aX = a.lastIpcObject.at[0]
|
if (a.lastIpcObject && b.lastIpcObject && a.lastIpcObject.at && b.lastIpcObject.at) {
|
||||||
const bX = b.lastIpcObject.at[0]
|
const aX = a.lastIpcObject.at[0]
|
||||||
const aY = a.lastIpcObject.at[1]
|
const bX = b.lastIpcObject.at[0]
|
||||||
const bY = b.lastIpcObject.at[1]
|
const aY = a.lastIpcObject.at[1]
|
||||||
|
const bY = b.lastIpcObject.at[1]
|
||||||
const xCompare = aX - bX
|
|
||||||
if (Math.abs(xCompare) > 10) return xCompare
|
const xCompare = aX - bX
|
||||||
return aY - bY
|
if (Math.abs(xCompare) > 10) {
|
||||||
}
|
return xCompare
|
||||||
|
}
|
||||||
if (a.lastIpcObject && !b.lastIpcObject) return -1
|
return aY - bY
|
||||||
if (!a.lastIpcObject && b.lastIpcObject) return 1
|
}
|
||||||
|
|
||||||
if (a.title && b.title) {
|
if (a.lastIpcObject && !b.lastIpcObject) {
|
||||||
return a.title.localeCompare(b.title)
|
return -1
|
||||||
}
|
}
|
||||||
|
if (!a.lastIpcObject && b.lastIpcObject) {
|
||||||
|
return 1
|
||||||
return 0
|
}
|
||||||
})
|
|
||||||
|
if (a.title && b.title) {
|
||||||
// Return the wayland Toplevel objects
|
return a.title.localeCompare(b.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
|
||||||
return sortedHyprland.map(hyprToplevel => hyprToplevel.wayland).filter(wayland => wayland !== null)
|
return sortedHyprland.map(hyprToplevel => hyprToplevel.wayland).filter(wayland => wayland !== null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// For other compositors or when services aren't ready yet, return unsorted toplevels
|
|
||||||
return ToplevelManager.toplevels.values
|
return ToplevelManager.toplevels.values
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +84,7 @@ Singleton {
|
|||||||
detectCompositor()
|
detectCompositor()
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterCurrentWorkspace(toplevels, screen){
|
function filterCurrentWorkspace(toplevels, screen) {
|
||||||
if (useNiriSorting) {
|
if (useNiriSorting) {
|
||||||
return NiriService.filterCurrentWorkspace(toplevels, screen)
|
return NiriService.filterCurrentWorkspace(toplevels, screen)
|
||||||
}
|
}
|
||||||
@@ -97,11 +99,10 @@ Singleton {
|
|||||||
return toplevels
|
return toplevels
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentWorkspaceId = null
|
let currentWorkspaceId = null
|
||||||
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
|
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
|
||||||
|
|
||||||
for (var i = 0; i < hyprlandToplevels.length; i++) {
|
for (const hyprToplevel of hyprlandToplevels) {
|
||||||
var hyprToplevel = hyprlandToplevels[i]
|
|
||||||
if (hyprToplevel.monitor && hyprToplevel.monitor.name === screenName && hyprToplevel.workspace) {
|
if (hyprToplevel.monitor && hyprToplevel.monitor.name === screenName && hyprToplevel.workspace) {
|
||||||
if (hyprToplevel.activated) {
|
if (hyprToplevel.activated) {
|
||||||
currentWorkspaceId = hyprToplevel.workspace.id
|
currentWorkspaceId = hyprToplevel.workspace.id
|
||||||
@@ -115,8 +116,7 @@ Singleton {
|
|||||||
|
|
||||||
if (currentWorkspaceId === null && Hyprland.workspaces) {
|
if (currentWorkspaceId === null && Hyprland.workspaces) {
|
||||||
const workspaces = Array.from(Hyprland.workspaces.values)
|
const workspaces = Array.from(Hyprland.workspaces.values)
|
||||||
for (var k = 0; k < workspaces.length; k++) {
|
for (const workspace of workspaces) {
|
||||||
var workspace = workspaces[k]
|
|
||||||
if (workspace.monitor && workspace.monitor === screenName) {
|
if (workspace.monitor && workspace.monitor === screenName) {
|
||||||
if (Hyprland.focusedWorkspace && workspace.id === Hyprland.focusedWorkspace.id) {
|
if (Hyprland.focusedWorkspace && workspace.id === Hyprland.focusedWorkspace.id) {
|
||||||
currentWorkspaceId = workspace.id
|
currentWorkspaceId = workspace.id
|
||||||
@@ -134,18 +134,16 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return toplevels.filter(toplevel => {
|
return toplevels.filter(toplevel => {
|
||||||
for (var j = 0; j < hyprlandToplevels.length; j++) {
|
for (const hyprToplevel of hyprlandToplevels) {
|
||||||
var hyprToplevel = hyprlandToplevels[j]
|
if (hyprToplevel.wayland === toplevel) {
|
||||||
if (hyprToplevel.wayland === toplevel) {
|
return hyprToplevel.workspace && hyprToplevel.workspace.id === currentWorkspaceId
|
||||||
return hyprToplevel.workspace && hyprToplevel.workspace.id === currentWorkspaceId
|
}
|
||||||
}
|
}
|
||||||
}
|
return false
|
||||||
return false
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function detectCompositor() {
|
function detectCompositor() {
|
||||||
// Check for Hyprland first
|
|
||||||
if (hyprlandSignature && hyprlandSignature.length > 0) {
|
if (hyprlandSignature && hyprlandSignature.length > 0) {
|
||||||
isHyprland = true
|
isHyprland = true
|
||||||
isNiri = false
|
isNiri = false
|
||||||
@@ -154,12 +152,9 @@ Singleton {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for Niri
|
|
||||||
if (niriSocket && niriSocket.length > 0) {
|
if (niriSocket && niriSocket.length > 0) {
|
||||||
// Verify the socket actually exists
|
|
||||||
niriSocketCheck.running = true
|
niriSocketCheck.running = true
|
||||||
} else {
|
} else {
|
||||||
// No compositor detected, default to Niri
|
|
||||||
isHyprland = false
|
isHyprland = false
|
||||||
isNiri = false
|
isNiri = false
|
||||||
compositor = "unknown"
|
compositor = "unknown"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -94,8 +95,7 @@ Singleton {
|
|||||||
// Increment reference count for this module
|
// Increment reference count for this module
|
||||||
const currentCount = moduleRefCounts[module] || 0
|
const currentCount = moduleRefCounts[module] || 0
|
||||||
moduleRefCounts[module] = currentCount + 1
|
moduleRefCounts[module] = currentCount + 1
|
||||||
console.log("Adding ref for module:", module, "count:",
|
console.log("Adding ref for module:", module, "count:", moduleRefCounts[module])
|
||||||
moduleRefCounts[module])
|
|
||||||
|
|
||||||
// Add to enabled modules if not already there
|
// Add to enabled modules if not already there
|
||||||
if (enabledModules.indexOf(module) === -1) {
|
if (enabledModules.indexOf(module) === -1) {
|
||||||
@@ -107,8 +107,7 @@ Singleton {
|
|||||||
|
|
||||||
if (modulesChanged || refCount === 1) {
|
if (modulesChanged || refCount === 1) {
|
||||||
enabledModules = enabledModules.slice() // Force property change
|
enabledModules = enabledModules.slice() // Force property change
|
||||||
moduleRefCounts = Object.assign(
|
moduleRefCounts = Object.assign({}, moduleRefCounts) // Force property change
|
||||||
{}, moduleRefCounts) // Force property change
|
|
||||||
updateAllStats()
|
updateAllStats()
|
||||||
} else if (gpuPciIds.length > 0 && refCount > 0) {
|
} else if (gpuPciIds.length > 0 && refCount > 0) {
|
||||||
// If we have GPU PCI IDs and active modules, make sure to update
|
// If we have GPU PCI IDs and active modules, make sure to update
|
||||||
@@ -128,8 +127,7 @@ Singleton {
|
|||||||
if (currentCount > 1) {
|
if (currentCount > 1) {
|
||||||
// Decrement reference count
|
// Decrement reference count
|
||||||
moduleRefCounts[module] = currentCount - 1
|
moduleRefCounts[module] = currentCount - 1
|
||||||
console.log("Removing ref for module:", module, "count:",
|
console.log("Removing ref for module:", module, "count:", moduleRefCounts[module])
|
||||||
moduleRefCounts[module])
|
|
||||||
} else if (currentCount === 1) {
|
} else if (currentCount === 1) {
|
||||||
// Remove completely when count reaches 0
|
// Remove completely when count reaches 0
|
||||||
delete moduleRefCounts[module]
|
delete moduleRefCounts[module]
|
||||||
@@ -137,8 +135,7 @@ Singleton {
|
|||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
enabledModules.splice(index, 1)
|
enabledModules.splice(index, 1)
|
||||||
modulesChanged = true
|
modulesChanged = true
|
||||||
console.log("Disabling module:", module,
|
console.log("Disabling module:", module, "(no more refs)")
|
||||||
"(no more refs)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,8 +143,7 @@ Singleton {
|
|||||||
|
|
||||||
if (modulesChanged) {
|
if (modulesChanged) {
|
||||||
enabledModules = enabledModules.slice() // Force property change
|
enabledModules = enabledModules.slice() // Force property change
|
||||||
moduleRefCounts = Object.assign(
|
moduleRefCounts = Object.assign({}, moduleRefCounts) // Force property change
|
||||||
{}, moduleRefCounts) // Force property change
|
|
||||||
|
|
||||||
// Clear cursor data when CPU or process modules are no longer active
|
// Clear cursor data when CPU or process modules are no longer active
|
||||||
if (!enabledModules.includes("cpu")) {
|
if (!enabledModules.includes("cpu")) {
|
||||||
@@ -174,8 +170,7 @@ Singleton {
|
|||||||
gpuPciIds = gpuPciIds.concat([pciId])
|
gpuPciIds = gpuPciIds.concat([pciId])
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Adding GPU PCI ID ref:", pciId, "count:",
|
console.log("Adding GPU PCI ID ref:", pciId, "count:", gpuPciIdRefCounts[pciId])
|
||||||
gpuPciIdRefCounts[pciId])
|
|
||||||
// Force property change notification
|
// Force property change notification
|
||||||
gpuPciIdRefCounts = Object.assign({}, gpuPciIdRefCounts)
|
gpuPciIdRefCounts = Object.assign({}, gpuPciIdRefCounts)
|
||||||
}
|
}
|
||||||
@@ -185,8 +180,7 @@ Singleton {
|
|||||||
if (currentCount > 1) {
|
if (currentCount > 1) {
|
||||||
// Decrement reference count
|
// Decrement reference count
|
||||||
gpuPciIdRefCounts[pciId] = currentCount - 1
|
gpuPciIdRefCounts[pciId] = currentCount - 1
|
||||||
console.log("Removing GPU PCI ID ref:", pciId, "count:",
|
console.log("Removing GPU PCI ID ref:", pciId, "count:", gpuPciIdRefCounts[pciId])
|
||||||
gpuPciIdRefCounts[pciId])
|
|
||||||
} else if (currentCount === 1) {
|
} else if (currentCount === 1) {
|
||||||
// Remove completely when count reaches 0
|
// Remove completely when count reaches 0
|
||||||
delete gpuPciIdRefCounts[pciId]
|
delete gpuPciIdRefCounts[pciId]
|
||||||
@@ -271,12 +265,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add cursor data if available for accurate CPU percentages
|
// Add cursor data if available for accurate CPU percentages
|
||||||
if ((enabledModules.includes("cpu") || enabledModules.includes("all"))
|
if ((enabledModules.includes("cpu") || enabledModules.includes("all")) && cpuCursor) {
|
||||||
&& cpuCursor) {
|
|
||||||
cmd.push("--cpu-cursor", cpuCursor)
|
cmd.push("--cpu-cursor", cpuCursor)
|
||||||
}
|
}
|
||||||
if ((enabledModules.includes("processes") || enabledModules.includes(
|
if ((enabledModules.includes("processes") || enabledModules.includes("all")) && procCursor) {
|
||||||
"all")) && procCursor) {
|
|
||||||
cmd.push("--proc-cursor", procCursor)
|
cmd.push("--proc-cursor", procCursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,8 +276,7 @@ Singleton {
|
|||||||
cmd.push("--gpu-pci-ids", gpuPciIds.join(","))
|
cmd.push("--gpu-pci-ids", gpuPciIds.join(","))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enabledModules.indexOf("processes") !== -1
|
if (enabledModules.indexOf("processes") !== -1 || enabledModules.indexOf("all") !== -1) {
|
||||||
|| enabledModules.indexOf("all") !== -1) {
|
|
||||||
cmd.push("--limit", "100") // Get more data for client sorting
|
cmd.push("--limit", "100") // Get more data for client sorting
|
||||||
cmd.push("--sort", "cpu") // Always get CPU sorted data
|
cmd.push("--sort", "cpu") // Always get CPU sorted data
|
||||||
if (noCpu) {
|
if (noCpu) {
|
||||||
@@ -301,7 +292,6 @@ Singleton {
|
|||||||
const cpu = data.cpu
|
const cpu = data.cpu
|
||||||
cpuSampleCount++
|
cpuSampleCount++
|
||||||
|
|
||||||
// Use dgop CPU numbers directly without modification
|
|
||||||
cpuUsage = cpu.usage || 0
|
cpuUsage = cpu.usage || 0
|
||||||
cpuFrequency = cpu.frequency || 0
|
cpuFrequency = cpu.frequency || 0
|
||||||
cpuTemperature = cpu.temperature || 0
|
cpuTemperature = cpu.temperature || 0
|
||||||
@@ -310,7 +300,6 @@ Singleton {
|
|||||||
perCoreCpuUsage = cpu.coreUsage || []
|
perCoreCpuUsage = cpu.coreUsage || []
|
||||||
addToHistory(cpuHistory, cpuUsage)
|
addToHistory(cpuHistory, cpuUsage)
|
||||||
|
|
||||||
// Store the opaque cursor string for next sampling
|
|
||||||
if (cpu.cursor) {
|
if (cpu.cursor) {
|
||||||
cpuCursor = cpu.cursor
|
cpuCursor = cpu.cursor
|
||||||
}
|
}
|
||||||
@@ -322,14 +311,12 @@ Singleton {
|
|||||||
const availableKB = mem.available || 0
|
const availableKB = mem.available || 0
|
||||||
const freeKB = mem.free || 0
|
const freeKB = mem.free || 0
|
||||||
|
|
||||||
// Update MB properties
|
|
||||||
totalMemoryMB = totalKB / 1024
|
totalMemoryMB = totalKB / 1024
|
||||||
availableMemoryMB = availableKB / 1024
|
availableMemoryMB = availableKB / 1024
|
||||||
freeMemoryMB = freeKB / 1024
|
freeMemoryMB = freeKB / 1024
|
||||||
usedMemoryMB = totalMemoryMB - availableMemoryMB
|
usedMemoryMB = totalMemoryMB - availableMemoryMB
|
||||||
memoryUsage = totalKB > 0 ? ((totalKB - availableKB) / totalKB) * 100 : 0
|
memoryUsage = totalKB > 0 ? ((totalKB - availableKB) / totalKB) * 100 : 0
|
||||||
|
|
||||||
// Update KB properties for compatibility
|
|
||||||
totalMemoryKB = totalKB
|
totalMemoryKB = totalKB
|
||||||
usedMemoryKB = totalKB - availableKB
|
usedMemoryKB = totalKB - availableKB
|
||||||
totalSwapKB = mem.swaptotal || 0
|
totalSwapKB = mem.swaptotal || 0
|
||||||
@@ -339,7 +326,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data.network && Array.isArray(data.network)) {
|
if (data.network && Array.isArray(data.network)) {
|
||||||
// Store raw network interface data
|
|
||||||
networkInterfaces = data.network
|
networkInterfaces = data.network
|
||||||
|
|
||||||
let totalRx = 0
|
let totalRx = 0
|
||||||
@@ -365,7 +351,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data.disk && Array.isArray(data.disk)) {
|
if (data.disk && Array.isArray(data.disk)) {
|
||||||
// Store raw disk device data
|
|
||||||
diskDevices = data.disk
|
diskDevices = data.disk
|
||||||
|
|
||||||
let totalRead = 0
|
let totalRead = 0
|
||||||
@@ -399,53 +384,40 @@ Singleton {
|
|||||||
processSampleCount++
|
processSampleCount++
|
||||||
|
|
||||||
for (const proc of data.processes) {
|
for (const proc of data.processes) {
|
||||||
// Only show CPU usage if we have had at least 2 samples (first sample is inaccurate)
|
|
||||||
const cpuUsage = processSampleCount >= 2 ? (proc.cpu || 0) : 0
|
const cpuUsage = processSampleCount >= 2 ? (proc.cpu || 0) : 0
|
||||||
|
|
||||||
newProcesses.push({
|
newProcesses.push({
|
||||||
"pid": proc.pid || 0,
|
"pid": proc.pid || 0,
|
||||||
"ppid": proc.ppid || 0,
|
"ppid": proc.ppid || 0,
|
||||||
"cpu": cpuUsage,
|
"cpu": cpuUsage,
|
||||||
"memoryPercent": proc.memoryPercent
|
"memoryPercent": proc.memoryPercent || proc.pssPercent || 0,
|
||||||
|| proc.pssPercent || 0,
|
"memoryKB": proc.memoryKB || proc.pssKB || 0,
|
||||||
"memoryKB": proc.memoryKB
|
|
||||||
|| proc.pssKB || 0,
|
|
||||||
"command": proc.command || "",
|
"command": proc.command || "",
|
||||||
"fullCommand": proc.fullCommand || "",
|
"fullCommand": proc.fullCommand || "",
|
||||||
"displayName": (proc.command
|
"displayName": (proc.command && proc.command.length > 15) ? proc.command.substring(0, 15) + "..." : (proc.command || "")
|
||||||
&& proc.command.length
|
|
||||||
> 15) ? proc.command.substring(
|
|
||||||
0,
|
|
||||||
15) + "..." : (proc.command || "")
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
allProcesses = newProcesses
|
allProcesses = newProcesses
|
||||||
applySorting()
|
applySorting()
|
||||||
|
|
||||||
// Store the single opaque cursor string for the entire process list
|
|
||||||
if (data.cursor) {
|
if (data.cursor) {
|
||||||
procCursor = data.cursor
|
procCursor = data.cursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle both gpu and gpu-temp module data
|
const gpuData = (data.gpu && data.gpu.gpus) || data.gpus
|
||||||
const gpuData = (data.gpu && data.gpu.gpus)
|
|
||||||
|| data.gpus // Handle both meta format and direct gpu command format
|
|
||||||
if (gpuData && Array.isArray(gpuData)) {
|
if (gpuData && Array.isArray(gpuData)) {
|
||||||
// Check if this is temperature update data (has PCI IDs being monitored)
|
// Check if this is temperature update data (has PCI IDs being monitored)
|
||||||
if (gpuPciIds.length > 0 && availableGpus
|
if (gpuPciIds.length > 0 && availableGpus && availableGpus.length > 0) {
|
||||||
&& availableGpus.length > 0) {
|
|
||||||
// This is temperature data - merge with existing GPU metadata
|
// This is temperature data - merge with existing GPU metadata
|
||||||
const updatedGpus = availableGpus.slice()
|
const updatedGpus = availableGpus.slice()
|
||||||
for (var i = 0; i < updatedGpus.length; i++) {
|
for (var i = 0; i < updatedGpus.length; i++) {
|
||||||
const existingGpu = updatedGpus[i]
|
const existingGpu = updatedGpus[i]
|
||||||
const tempGpu = gpuData.find(
|
const tempGpu = gpuData.find(g => g.pciId === existingGpu.pciId)
|
||||||
g => g.pciId === existingGpu.pciId)
|
|
||||||
// Only update temperature if this GPU's PCI ID is being monitored
|
// Only update temperature if this GPU's PCI ID is being monitored
|
||||||
if (tempGpu && gpuPciIds.includes(existingGpu.pciId)) {
|
if (tempGpu && gpuPciIds.includes(existingGpu.pciId)) {
|
||||||
updatedGpus[i] = Object.assign({}, existingGpu, {
|
updatedGpus[i] = Object.assign({}, existingGpu, {
|
||||||
"temperature": tempGpu.temperature
|
"temperature": tempGpu.temperature || 0
|
||||||
|| 0
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -454,8 +426,7 @@ Singleton {
|
|||||||
// This is initial GPU metadata - set the full list
|
// This is initial GPU metadata - set the full list
|
||||||
const gpuList = []
|
const gpuList = []
|
||||||
for (const gpu of gpuData) {
|
for (const gpu of gpuData) {
|
||||||
let displayName = gpu.displayName || gpu.name
|
let displayName = gpu.displayName || gpu.name || "Unknown GPU"
|
||||||
|| "Unknown GPU"
|
|
||||||
let fullName = gpu.fullName || gpu.name || "Unknown GPU"
|
let fullName = gpu.fullName || gpu.name || "Unknown GPU"
|
||||||
|
|
||||||
gpuList.push({
|
gpuList.push({
|
||||||
@@ -501,24 +472,24 @@ Singleton {
|
|||||||
|
|
||||||
function getProcessIcon(command) {
|
function getProcessIcon(command) {
|
||||||
const cmd = command.toLowerCase()
|
const cmd = command.toLowerCase()
|
||||||
if (cmd.includes("firefox") || cmd.includes("chrome") ||
|
if (cmd.includes("firefox") || cmd.includes("chrome") || cmd.includes("browser") || cmd.includes("chromium")) {
|
||||||
cmd.includes("browser") || cmd.includes("chromium"))
|
|
||||||
return "web"
|
return "web"
|
||||||
if (cmd.includes("code") || cmd.includes("editor")
|
}
|
||||||
|| cmd.includes("vim"))
|
if (cmd.includes("code") || cmd.includes("editor") || cmd.includes("vim")) {
|
||||||
return "code"
|
return "code"
|
||||||
if (cmd.includes("terminal") || cmd.includes("bash")
|
}
|
||||||
|| cmd.includes("zsh"))
|
if (cmd.includes("terminal") || cmd.includes("bash") || cmd.includes("zsh")) {
|
||||||
return "terminal"
|
return "terminal"
|
||||||
if (cmd.includes("music") || cmd.includes("audio") || cmd.includes(
|
}
|
||||||
"spotify"))
|
if (cmd.includes("music") || cmd.includes("audio") || cmd.includes("spotify")) {
|
||||||
return "music_note"
|
return "music_note"
|
||||||
if (cmd.includes("video") || cmd.includes("vlc") || cmd.includes("mpv"))
|
}
|
||||||
|
if (cmd.includes("video") || cmd.includes("vlc") || cmd.includes("mpv")) {
|
||||||
return "play_circle"
|
return "play_circle"
|
||||||
if (cmd.includes("systemd") || cmd.includes("elogind") ||
|
}
|
||||||
cmd.includes("kernel") || cmd.includes("kthread") ||
|
if (cmd.includes("systemd") || cmd.includes("elogind") || cmd.includes("kernel") || cmd.includes("kthread") || cmd.includes("kworker")) {
|
||||||
cmd.includes("kworker"))
|
|
||||||
return "settings"
|
return "settings"
|
||||||
|
}
|
||||||
return "memory"
|
return "memory"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,22 +499,25 @@ Singleton {
|
|||||||
|
|
||||||
function formatMemoryUsage(memoryKB) {
|
function formatMemoryUsage(memoryKB) {
|
||||||
const mem = memoryKB || 0
|
const mem = memoryKB || 0
|
||||||
if (mem < 1024)
|
if (mem < 1024) {
|
||||||
return mem.toFixed(0) + " KB"
|
return mem.toFixed(0) + " KB"
|
||||||
else if (mem < 1024 * 1024)
|
} else if (mem < 1024 * 1024) {
|
||||||
return (mem / 1024).toFixed(1) + " MB"
|
return (mem / 1024).toFixed(1) + " MB"
|
||||||
else
|
} else {
|
||||||
return (mem / (1024 * 1024)).toFixed(1) + " GB"
|
return (mem / (1024 * 1024)).toFixed(1) + " GB"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatSystemMemory(memoryKB) {
|
function formatSystemMemory(memoryKB) {
|
||||||
const mem = memoryKB || 0
|
const mem = memoryKB || 0
|
||||||
if (mem === 0)
|
if (mem === 0) {
|
||||||
return "--"
|
return "--"
|
||||||
if (mem < 1024 * 1024)
|
}
|
||||||
|
if (mem < 1024 * 1024) {
|
||||||
return (mem / 1024).toFixed(0) + " MB"
|
return (mem / 1024).toFixed(0) + " MB"
|
||||||
else
|
} else {
|
||||||
return (mem / (1024 * 1024)).toFixed(1) + " GB"
|
return (mem / (1024 * 1024)).toFixed(1) + " GB"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function killProcess(pid) {
|
function killProcess(pid) {
|
||||||
@@ -560,8 +534,9 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applySorting() {
|
function applySorting() {
|
||||||
if (!allProcesses || allProcesses.length === 0)
|
if (!allProcesses || allProcesses.length === 0) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const sorted = allProcesses.slice()
|
const sorted = allProcesses.slice()
|
||||||
sorted.sort((a, b) => {
|
sorted.sort((a, b) => {
|
||||||
@@ -595,8 +570,7 @@ Singleton {
|
|||||||
Timer {
|
Timer {
|
||||||
id: updateTimer
|
id: updateTimer
|
||||||
interval: root.updateInterval
|
interval: root.updateInterval
|
||||||
running: root.dgopAvailable && root.refCount > 0
|
running: root.dgopAvailable && root.refCount > 0 && root.enabledModules.length > 0
|
||||||
&& root.enabledModules.length > 0
|
|
||||||
repeat: true
|
repeat: true
|
||||||
triggeredOnStart: true
|
triggeredOnStart: true
|
||||||
onTriggered: root.updateAllStats()
|
onTriggered: root.updateAllStats()
|
||||||
@@ -611,12 +585,11 @@ Singleton {
|
|||||||
//console.log("DgopService command:", JSON.stringify(command))
|
//console.log("DgopService command:", JSON.stringify(command))
|
||||||
}
|
}
|
||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
console.warn("Dgop process failed with exit code:",
|
console.warn("Dgop process failed with exit code:", exitCode)
|
||||||
exitCode)
|
isUpdating = false
|
||||||
isUpdating = false
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
if (text.trim()) {
|
if (text.trim()) {
|
||||||
@@ -638,12 +611,10 @@ Singleton {
|
|||||||
command: ["dgop", "gpu", "--json"]
|
command: ["dgop", "gpu", "--json"]
|
||||||
running: false
|
running: false
|
||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
console.warn(
|
console.warn("GPU init process failed with exit code:", exitCode)
|
||||||
"GPU init process failed with exit code:",
|
}
|
||||||
exitCode)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
if (text.trim()) {
|
if (text.trim()) {
|
||||||
@@ -663,24 +634,23 @@ Singleton {
|
|||||||
command: ["which", "dgop"]
|
command: ["which", "dgop"]
|
||||||
running: false
|
running: false
|
||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
dgopAvailable = (exitCode === 0)
|
dgopAvailable = (exitCode === 0)
|
||||||
if (dgopAvailable) {
|
if (dgopAvailable) {
|
||||||
initializeGpuMetadata()
|
initializeGpuMetadata()
|
||||||
// Load persisted GPU PCI IDs from session state
|
// Load persisted GPU PCI IDs from session state
|
||||||
if (SessionData.enabledGpuPciIds
|
if (SessionData.enabledGpuPciIds && SessionData.enabledGpuPciIds.length > 0) {
|
||||||
&& SessionData.enabledGpuPciIds.length > 0) {
|
for (const pciId of SessionData.enabledGpuPciIds) {
|
||||||
for (const pciId of SessionData.enabledGpuPciIds) {
|
addGpuPciId(pciId)
|
||||||
addGpuPciId(pciId)
|
}
|
||||||
}
|
// Trigger update if we already have active modules
|
||||||
// Trigger update if we already have active modules
|
if (refCount > 0 && enabledModules.length > 0) {
|
||||||
if (refCount > 0 && enabledModules.length > 0) {
|
updateAllStats()
|
||||||
updateAllStats()
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
console.warn("dgop is not installed or not in PATH")
|
||||||
console.warn("dgop is not installed or not in PATH")
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
@@ -688,10 +658,10 @@ Singleton {
|
|||||||
command: ["cat", "/etc/os-release"]
|
command: ["cat", "/etc/os-release"]
|
||||||
running: false
|
running: false
|
||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
console.warn("Failed to read /etc/os-release")
|
console.warn("Failed to read /etc/os-release")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
if (text.trim()) {
|
if (text.trim()) {
|
||||||
@@ -703,11 +673,9 @@ Singleton {
|
|||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const trimmedLine = line.trim()
|
const trimmedLine = line.trim()
|
||||||
if (trimmedLine.startsWith('PRETTY_NAME=')) {
|
if (trimmedLine.startsWith('PRETTY_NAME=')) {
|
||||||
prettyName = trimmedLine.substring(12).replace(
|
prettyName = trimmedLine.substring(12).replace(/^["']|["']$/g, '')
|
||||||
/^["']|["']$/g, '')
|
|
||||||
} else if (trimmedLine.startsWith('NAME=')) {
|
} else if (trimmedLine.startsWith('NAME=')) {
|
||||||
name = trimmedLine.substring(5).replace(
|
name = trimmedLine.substring(5).replace(/^["']|["']$/g, '')
|
||||||
/^["']|["']$/g, '')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -21,9 +22,10 @@ Singleton {
|
|||||||
property bool skipDdcRead: false
|
property bool skipDdcRead: false
|
||||||
property int brightnessLevel: {
|
property int brightnessLevel: {
|
||||||
const deviceToUse = lastIpcDevice === "" ? getDefaultDevice() : (lastIpcDevice || currentDevice)
|
const deviceToUse = lastIpcDevice === "" ? getDefaultDevice() : (lastIpcDevice || currentDevice)
|
||||||
if (!deviceToUse) return 50
|
if (!deviceToUse) {
|
||||||
|
return 50
|
||||||
|
}
|
||||||
|
|
||||||
// Always use cached values for consistency
|
|
||||||
return getDeviceBrightness(deviceToUse)
|
return getDeviceBrightness(deviceToUse)
|
||||||
}
|
}
|
||||||
property int maxBrightness: 100
|
property int maxBrightness: 100
|
||||||
@@ -34,7 +36,6 @@ Singleton {
|
|||||||
|
|
||||||
property bool nightModeActive: nightModeEnabled
|
property bool nightModeActive: nightModeEnabled
|
||||||
|
|
||||||
// Night Mode Properties
|
|
||||||
property bool nightModeEnabled: false
|
property bool nightModeEnabled: false
|
||||||
property bool automationAvailable: false
|
property bool automationAvailable: false
|
||||||
property bool geoclueAvailable: false
|
property bool geoclueAvailable: false
|
||||||
@@ -47,32 +48,25 @@ Singleton {
|
|||||||
|
|
||||||
function setBrightnessInternal(percentage, device) {
|
function setBrightnessInternal(percentage, device) {
|
||||||
const clampedValue = Math.max(1, Math.min(100, percentage))
|
const clampedValue = Math.max(1, Math.min(100, percentage))
|
||||||
const actualDevice = device === "" ? getDefaultDevice(
|
const actualDevice = device === "" ? getDefaultDevice() : (device || currentDevice || getDefaultDevice())
|
||||||
) : (device || currentDevice
|
|
||||||
|| getDefaultDevice())
|
|
||||||
|
|
||||||
// Update the device brightness cache immediately for all devices
|
|
||||||
if (actualDevice) {
|
if (actualDevice) {
|
||||||
var newBrightness = Object.assign({}, deviceBrightness)
|
const newBrightness = Object.assign({}, deviceBrightness)
|
||||||
newBrightness[actualDevice] = clampedValue
|
newBrightness[actualDevice] = clampedValue
|
||||||
deviceBrightness = newBrightness
|
deviceBrightness = newBrightness
|
||||||
}
|
}
|
||||||
|
|
||||||
const deviceInfo = getCurrentDeviceInfoByName(actualDevice)
|
const deviceInfo = getCurrentDeviceInfoByName(actualDevice)
|
||||||
|
|
||||||
if (deviceInfo && deviceInfo.class === "ddc") {
|
if (deviceInfo && deviceInfo.class === "ddc") {
|
||||||
// Use ddcutil for DDC devices
|
ddcBrightnessSetProcess.command = ["ddcutil", "setvcp", "-d", String(deviceInfo.ddcDisplay), "10", String(clampedValue)]
|
||||||
ddcBrightnessSetProcess.command = ["ddcutil", "setvcp", "-d", String(
|
|
||||||
deviceInfo.ddcDisplay), "10", String(
|
|
||||||
clampedValue)]
|
|
||||||
ddcBrightnessSetProcess.running = true
|
ddcBrightnessSetProcess.running = true
|
||||||
} else {
|
} else {
|
||||||
// Use brightnessctl for regular devices
|
if (device) {
|
||||||
if (device)
|
brightnessSetProcess.command = ["brightnessctl", "-d", device, "set", `${clampedValue}%`]
|
||||||
brightnessSetProcess.command
|
} else {
|
||||||
= ["brightnessctl", "-d", device, "set", clampedValue + "%"]
|
brightnessSetProcess.command = ["brightnessctl", "set", `${clampedValue}%`]
|
||||||
else
|
}
|
||||||
brightnessSetProcess.command = ["brightnessctl", "set", clampedValue + "%"]
|
|
||||||
brightnessSetProcess.running = true
|
brightnessSetProcess.running = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,26 +77,23 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setCurrentDevice(deviceName, saveToSession = false) {
|
function setCurrentDevice(deviceName, saveToSession = false) {
|
||||||
if (currentDevice === deviceName)
|
if (currentDevice === deviceName) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
currentDevice = deviceName
|
currentDevice = deviceName
|
||||||
lastIpcDevice = deviceName
|
lastIpcDevice = deviceName
|
||||||
|
|
||||||
// Only save to session if explicitly requested (user choice)
|
|
||||||
if (saveToSession) {
|
if (saveToSession) {
|
||||||
SessionData.setLastBrightnessDevice(deviceName)
|
SessionData.setLastBrightnessDevice(deviceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
deviceSwitched()
|
deviceSwitched()
|
||||||
|
|
||||||
// Check if this is a DDC device
|
|
||||||
const deviceInfo = getCurrentDeviceInfoByName(deviceName)
|
const deviceInfo = getCurrentDeviceInfoByName(deviceName)
|
||||||
if (deviceInfo && deviceInfo.class === "ddc") {
|
if (deviceInfo && deviceInfo.class === "ddc") {
|
||||||
// For DDC devices, never read after initial - just use cached values
|
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
// For regular devices, use brightnessctl
|
|
||||||
brightnessGetProcess.command = ["brightnessctl", "-m", "-d", deviceName, "get"]
|
brightnessGetProcess.command = ["brightnessctl", "-m", "-d", deviceName, "get"]
|
||||||
brightnessGetProcess.running = true
|
brightnessGetProcess.running = true
|
||||||
}
|
}
|
||||||
@@ -116,19 +107,19 @@ Singleton {
|
|||||||
const allDevices = [...devices, ...ddcDevices]
|
const allDevices = [...devices, ...ddcDevices]
|
||||||
|
|
||||||
allDevices.sort((a, b) => {
|
allDevices.sort((a, b) => {
|
||||||
if (a.class === "backlight"
|
if (a.class === "backlight" && b.class !== "backlight") {
|
||||||
&& b.class !== "backlight")
|
return -1
|
||||||
return -1
|
}
|
||||||
if (a.class !== "backlight"
|
if (a.class !== "backlight" && b.class === "backlight") {
|
||||||
&& b.class === "backlight")
|
return 1
|
||||||
return 1
|
}
|
||||||
|
|
||||||
if (a.class === "ddc" && b.class !== "ddc"
|
if (a.class === "ddc" && b.class !== "ddc" && b.class !== "backlight") {
|
||||||
&& b.class !== "backlight")
|
return -1
|
||||||
return -1
|
}
|
||||||
if (a.class !== "ddc" && b.class === "ddc"
|
if (a.class !== "ddc" && b.class === "ddc" && a.class !== "backlight") {
|
||||||
&& a.class !== "backlight")
|
return 1
|
||||||
return 1
|
}
|
||||||
|
|
||||||
return a.name.localeCompare(b.name)
|
return a.name.localeCompare(b.name)
|
||||||
})
|
})
|
||||||
@@ -141,25 +132,26 @@ Singleton {
|
|||||||
if (deviceExists) {
|
if (deviceExists) {
|
||||||
setCurrentDevice(lastDevice, false)
|
setCurrentDevice(lastDevice, false)
|
||||||
} else {
|
} else {
|
||||||
const nonKbdDevice = devices.find(d => !d.name.includes("kbd"))
|
const nonKbdDevice = devices.find(d => !d.name.includes("kbd")) || devices[0]
|
||||||
|| devices[0]
|
|
||||||
setCurrentDevice(nonKbdDevice.name, false)
|
setCurrentDevice(nonKbdDevice.name, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDeviceBrightness(deviceName) {
|
function getDeviceBrightness(deviceName) {
|
||||||
if (!deviceName) return 50
|
if (!deviceName) {
|
||||||
|
return
|
||||||
|
} 50
|
||||||
|
|
||||||
const deviceInfo = getCurrentDeviceInfoByName(deviceName)
|
const deviceInfo = getCurrentDeviceInfoByName(deviceName)
|
||||||
if (!deviceInfo) return 50
|
if (!deviceInfo) {
|
||||||
|
return 50
|
||||||
// For DDC devices, always use cached values
|
}
|
||||||
|
|
||||||
if (deviceInfo.class === "ddc") {
|
if (deviceInfo.class === "ddc") {
|
||||||
return deviceBrightness[deviceName] || 50
|
return deviceBrightness[deviceName] || 50
|
||||||
}
|
}
|
||||||
|
|
||||||
// For regular devices, try cache first, then device info
|
|
||||||
return deviceBrightness[deviceName] || deviceInfo.percentage || 50
|
return deviceBrightness[deviceName] || deviceInfo.percentage || 50
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,11 +165,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentDeviceInfo() {
|
function getCurrentDeviceInfo() {
|
||||||
const deviceToUse = lastIpcDevice === "" ? getDefaultDevice(
|
const deviceToUse = lastIpcDevice === "" ? getDefaultDevice() : (lastIpcDevice || currentDevice)
|
||||||
) : (lastIpcDevice
|
if (!deviceToUse) {
|
||||||
|| currentDevice)
|
|
||||||
if (!deviceToUse)
|
|
||||||
return null
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
for (const device of devices) {
|
for (const device of devices) {
|
||||||
if (device.name === deviceToUse) {
|
if (device.name === deviceToUse) {
|
||||||
@@ -188,11 +179,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isCurrentDeviceReady() {
|
function isCurrentDeviceReady() {
|
||||||
const deviceToUse = lastIpcDevice === "" ? getDefaultDevice(
|
const deviceToUse = lastIpcDevice === "" ? getDefaultDevice() : (lastIpcDevice || currentDevice)
|
||||||
) : (lastIpcDevice
|
if (!deviceToUse) {
|
||||||
|| currentDevice)
|
|
||||||
if (!deviceToUse)
|
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if (ddcPendingInit[deviceToUse]) {
|
if (ddcPendingInit[deviceToUse]) {
|
||||||
return false
|
return false
|
||||||
@@ -202,8 +192,9 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentDeviceInfoByName(deviceName) {
|
function getCurrentDeviceInfoByName(deviceName) {
|
||||||
if (!deviceName)
|
if (!deviceName) {
|
||||||
return null
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
for (const device of devices) {
|
for (const device of devices) {
|
||||||
if (device.name === deviceName) {
|
if (device.name === deviceName) {
|
||||||
@@ -219,8 +210,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const displayId = ddcInitQueue.shift()
|
const displayId = ddcInitQueue.shift()
|
||||||
ddcInitialBrightnessProcess.command = ["ddcutil", "getvcp", "-d", String(
|
ddcInitialBrightnessProcess.command = ["ddcutil", "getvcp", "-d", String(displayId), "10", "--brief"]
|
||||||
displayId), "10", "--brief"]
|
|
||||||
ddcInitialBrightnessProcess.running = true
|
ddcInitialBrightnessProcess.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +223,7 @@ Singleton {
|
|||||||
|
|
||||||
nightModeEnabled = true
|
nightModeEnabled = true
|
||||||
SessionData.setNightModeEnabled(true)
|
SessionData.setNightModeEnabled(true)
|
||||||
|
|
||||||
// Apply immediately or start automation
|
// Apply immediately or start automation
|
||||||
if (SessionData.nightModeAutoEnabled) {
|
if (SessionData.nightModeAutoEnabled) {
|
||||||
startAutomation()
|
startAutomation()
|
||||||
@@ -266,10 +256,7 @@ Singleton {
|
|||||||
|
|
||||||
function applyNightModeDirectly() {
|
function applyNightModeDirectly() {
|
||||||
const temperature = SessionData.nightModeTemperature || 4500
|
const temperature = SessionData.nightModeTemperature || 4500
|
||||||
gammaStepProcess.command = buildGammastepCommand([
|
gammaStepProcess.command = buildGammastepCommand(["-m", "wayland", "-O", String(temperature)])
|
||||||
"-m", "wayland",
|
|
||||||
"-O", String(temperature)
|
|
||||||
])
|
|
||||||
gammaStepProcess.running = true
|
gammaStepProcess.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,17 +266,19 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startAutomation() {
|
function startAutomation() {
|
||||||
if (!automationAvailable) return
|
if (!automationAvailable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const mode = SessionData.nightModeAutoMode || "time"
|
const mode = SessionData.nightModeAutoMode || "time"
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case "time":
|
case "time":
|
||||||
startTimeBasedMode()
|
startTimeBasedMode()
|
||||||
break
|
break
|
||||||
case "location":
|
case "location":
|
||||||
startLocationBasedMode()
|
startLocationBasedMode()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,29 +299,19 @@ Singleton {
|
|||||||
function startLocationBasedMode() {
|
function startLocationBasedMode() {
|
||||||
const temperature = SessionData.nightModeTemperature || 4500
|
const temperature = SessionData.nightModeTemperature || 4500
|
||||||
const dayTemp = 6500
|
const dayTemp = 6500
|
||||||
|
|
||||||
if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
|
if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
|
||||||
automationProcess.command = buildGammastepCommand([
|
automationProcess.command = buildGammastepCommand(["-m", "wayland", "-l", `${SessionData.latitude.toFixed(6)}:${SessionData.longitude.toFixed(6)}`, "-t", `${dayTemp}:${temperature}`, "-v"])
|
||||||
"-m", "wayland",
|
|
||||||
"-l", `${SessionData.latitude.toFixed(6)}:${SessionData.longitude.toFixed(6)}`,
|
|
||||||
"-t", `${dayTemp}:${temperature}`,
|
|
||||||
"-v"
|
|
||||||
])
|
|
||||||
automationProcess.running = true
|
automationProcess.running = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SessionData.nightModeLocationProvider === "geoclue2") {
|
if (SessionData.nightModeLocationProvider === "geoclue2") {
|
||||||
automationProcess.command = buildGammastepCommand([
|
automationProcess.command = buildGammastepCommand(["-m", "wayland", "-l", "geoclue2", "-t", `${dayTemp}:${temperature}`, "-v"])
|
||||||
"-m", "wayland",
|
|
||||||
"-l", "geoclue2",
|
|
||||||
"-t", `${dayTemp}:${temperature}`,
|
|
||||||
"-v"
|
|
||||||
])
|
|
||||||
automationProcess.running = true
|
automationProcess.running = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn("DisplayService: Location mode selected but no coordinates or geoclue provider set")
|
console.warn("DisplayService: Location mode selected but no coordinates or geoclue provider set")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +326,7 @@ Singleton {
|
|||||||
const endMinutes = SessionData.nightModeEndHour * 60 + SessionData.nightModeEndMinute
|
const endMinutes = SessionData.nightModeEndHour * 60 + SessionData.nightModeEndMinute
|
||||||
|
|
||||||
let shouldBeNight = false
|
let shouldBeNight = false
|
||||||
|
|
||||||
if (startMinutes > endMinutes) {
|
if (startMinutes > endMinutes) {
|
||||||
shouldBeNight = (currentTime >= startMinutes) || (currentTime < endMinutes)
|
shouldBeNight = (currentTime >= startMinutes) || (currentTime < endMinutes)
|
||||||
} else {
|
} else {
|
||||||
@@ -356,7 +335,7 @@ Singleton {
|
|||||||
|
|
||||||
if (shouldBeNight !== isAutomaticNightTime) {
|
if (shouldBeNight !== isAutomaticNightTime) {
|
||||||
isAutomaticNightTime = shouldBeNight
|
isAutomaticNightTime = shouldBeNight
|
||||||
|
|
||||||
if (shouldBeNight) {
|
if (shouldBeNight) {
|
||||||
applyNightModeDirectly()
|
applyNightModeDirectly()
|
||||||
} else {
|
} else {
|
||||||
@@ -373,14 +352,14 @@ Singleton {
|
|||||||
SessionData.setNightModeAutoMode(mode)
|
SessionData.setNightModeAutoMode(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
function evaluateNightMode() {
|
function evaluateNightMode() {
|
||||||
// Always stop all processes first to clean slate
|
// Always stop all processes first to clean slate
|
||||||
stopAutomation()
|
stopAutomation()
|
||||||
|
|
||||||
if (!nightModeEnabled) {
|
if (!nightModeEnabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SessionData.nightModeAutoEnabled) {
|
if (SessionData.nightModeAutoEnabled) {
|
||||||
restartTimer.nextAction = "automation"
|
restartTimer.nextAction = "automation"
|
||||||
restartTimer.start()
|
restartTimer.start()
|
||||||
@@ -399,7 +378,7 @@ Singleton {
|
|||||||
property string nextAction: ""
|
property string nextAction: ""
|
||||||
interval: 100
|
interval: 100
|
||||||
repeat: false
|
repeat: false
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (nextAction === "automation") {
|
if (nextAction === "automation") {
|
||||||
startAutomation()
|
startAutomation()
|
||||||
@@ -476,8 +455,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ddcDevices = newDdcDevices
|
ddcDevices = newDdcDevices
|
||||||
console.log("DisplayService: Found", ddcDevices.length,
|
console.log("DisplayService: Found", ddcDevices.length, "DDC displays")
|
||||||
"DDC displays")
|
|
||||||
|
|
||||||
// Queue initial brightness readings for DDC devices
|
// Queue initial brightness readings for DDC devices
|
||||||
ddcInitQueue = []
|
ddcInitQueue = []
|
||||||
@@ -496,16 +474,13 @@ Singleton {
|
|||||||
// Retry setting last device now that DDC devices are available
|
// Retry setting last device now that DDC devices are available
|
||||||
const lastDevice = SessionData.lastBrightnessDevice || ""
|
const lastDevice = SessionData.lastBrightnessDevice || ""
|
||||||
if (lastDevice) {
|
if (lastDevice) {
|
||||||
const deviceExists = devices.some(
|
const deviceExists = devices.some(d => d.name === lastDevice)
|
||||||
d => d.name === lastDevice)
|
if (deviceExists && (!currentDevice || currentDevice !== lastDevice)) {
|
||||||
if (deviceExists && (!currentDevice
|
|
||||||
|| currentDevice !== lastDevice)) {
|
|
||||||
setCurrentDevice(lastDevice, false)
|
setCurrentDevice(lastDevice, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("DisplayService: Failed to parse DDC devices:",
|
console.warn("DisplayService: Failed to parse DDC devices:", error)
|
||||||
error)
|
|
||||||
ddcDevices = []
|
ddcDevices = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -513,8 +488,7 @@ Singleton {
|
|||||||
|
|
||||||
onExited: function (exitCode) {
|
onExited: function (exitCode) {
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
console.warn("DisplayService: Failed to detect DDC displays:",
|
console.warn("DisplayService: Failed to detect DDC displays:", exitCode)
|
||||||
exitCode)
|
|
||||||
ddcDevices = []
|
ddcDevices = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -526,8 +500,7 @@ Singleton {
|
|||||||
command: ["brightnessctl", "-m", "-l"]
|
command: ["brightnessctl", "-m", "-l"]
|
||||||
onExited: function (exitCode) {
|
onExited: function (exitCode) {
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
console.warn("DisplayService: Failed to list devices:",
|
console.warn("DisplayService: Failed to list devices:", exitCode)
|
||||||
exitCode)
|
|
||||||
brightnessAvailable = false
|
brightnessAvailable = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -542,7 +515,7 @@ Singleton {
|
|||||||
const newDevices = []
|
const newDevices = []
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const parts = line.split(",")
|
const parts = line.split(",")
|
||||||
if (parts.length >= 5)
|
if (parts.length >= 5) {
|
||||||
newDevices.push({
|
newDevices.push({
|
||||||
"name": parts[0],
|
"name": parts[0],
|
||||||
"class": parts[1],
|
"class": parts[1],
|
||||||
@@ -550,10 +523,11 @@ Singleton {
|
|||||||
"percentage": parseInt(parts[3]),
|
"percentage": parseInt(parts[3]),
|
||||||
"max": parseInt(parts[4])
|
"max": parseInt(parts[4])
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Store brightnessctl devices separately
|
// Store brightnessctl devices separately
|
||||||
devices = newDevices
|
devices = newDevices
|
||||||
|
|
||||||
// Always refresh to combine with DDC devices and set up device selection
|
// Always refresh to combine with DDC devices and set up device selection
|
||||||
refreshDevicesInternal()
|
refreshDevicesInternal()
|
||||||
}
|
}
|
||||||
@@ -565,9 +539,9 @@ Singleton {
|
|||||||
|
|
||||||
running: false
|
running: false
|
||||||
onExited: function (exitCode) {
|
onExited: function (exitCode) {
|
||||||
if (exitCode !== 0)
|
if (exitCode !== 0) {
|
||||||
console.warn("DisplayService: Failed to set brightness:",
|
console.warn("DisplayService: Failed to set brightness:", exitCode)
|
||||||
exitCode)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,10 +550,9 @@ Singleton {
|
|||||||
|
|
||||||
running: false
|
running: false
|
||||||
onExited: function (exitCode) {
|
onExited: function (exitCode) {
|
||||||
if (exitCode !== 0)
|
if (exitCode !== 0) {
|
||||||
console.warn(
|
console.warn("DisplayService: Failed to set DDC brightness:", exitCode)
|
||||||
"DisplayService: Failed to set DDC brightness:",
|
}
|
||||||
exitCode)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,9 +561,9 @@ Singleton {
|
|||||||
|
|
||||||
running: false
|
running: false
|
||||||
onExited: function (exitCode) {
|
onExited: function (exitCode) {
|
||||||
if (exitCode !== 0)
|
if (exitCode !== 0) {
|
||||||
console.warn("DisplayService: Failed to get initial DDC brightness:",
|
console.warn("DisplayService: Failed to get initial DDC brightness:", exitCode)
|
||||||
exitCode)
|
}
|
||||||
|
|
||||||
processNextDdcInit()
|
processNextDdcInit()
|
||||||
}
|
}
|
||||||
@@ -598,7 +571,7 @@ Singleton {
|
|||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
if (!text.trim())
|
if (!text.trim())
|
||||||
return
|
return
|
||||||
|
|
||||||
const parts = text.trim().split(" ")
|
const parts = text.trim().split(" ")
|
||||||
if (parts.length >= 5) {
|
if (parts.length >= 5) {
|
||||||
@@ -619,8 +592,7 @@ Singleton {
|
|||||||
delete newPending[deviceName]
|
delete newPending[deviceName]
|
||||||
ddcPendingInit = newPending
|
ddcPendingInit = newPending
|
||||||
|
|
||||||
console.log("DisplayService: Initial DDC Device",
|
console.log("DisplayService: Initial DDC Device", deviceName, "brightness:", brightness + "%")
|
||||||
deviceName, "brightness:", brightness + "%")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -632,15 +604,15 @@ Singleton {
|
|||||||
|
|
||||||
running: false
|
running: false
|
||||||
onExited: function (exitCode) {
|
onExited: function (exitCode) {
|
||||||
if (exitCode !== 0)
|
if (exitCode !== 0) {
|
||||||
console.warn("DisplayService: Failed to get brightness:",
|
console.warn("DisplayService: Failed to get brightness:", exitCode)
|
||||||
exitCode)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
if (!text.trim())
|
if (!text.trim())
|
||||||
return
|
return
|
||||||
|
|
||||||
const parts = text.trim().split(",")
|
const parts = text.trim().split(",")
|
||||||
if (parts.length >= 5) {
|
if (parts.length >= 5) {
|
||||||
@@ -657,8 +629,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
brightnessInitialized = true
|
brightnessInitialized = true
|
||||||
console.log("DisplayService: Device", currentDevice,
|
console.log("DisplayService: Device", currentDevice, "brightness:", brightness + "%")
|
||||||
"brightness:", brightness + "%")
|
|
||||||
brightnessChanged()
|
brightnessChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -670,16 +641,15 @@ Singleton {
|
|||||||
|
|
||||||
running: false
|
running: false
|
||||||
onExited: function (exitCode) {
|
onExited: function (exitCode) {
|
||||||
if (exitCode !== 0)
|
if (exitCode !== 0) {
|
||||||
console.warn(
|
console.warn("DisplayService: Failed to get DDC brightness:", exitCode)
|
||||||
"DisplayService: Failed to get DDC brightness:",
|
}
|
||||||
exitCode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
if (!text.trim())
|
if (!text.trim())
|
||||||
return
|
return
|
||||||
|
|
||||||
// Parse ddcutil getvcp output format: "VCP 10 C 50 100"
|
// Parse ddcutil getvcp output format: "VCP 10 C 50 100"
|
||||||
const parts = text.trim().split(" ")
|
const parts = text.trim().split(" ")
|
||||||
@@ -697,8 +667,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
brightnessInitialized = true
|
brightnessInitialized = true
|
||||||
console.log("DisplayService: DDC Device", currentDevice,
|
console.log("DisplayService: DDC Device", currentDevice, "brightness:", brightness + "%")
|
||||||
"brightness:", brightness + "%")
|
|
||||||
brightnessChanged()
|
brightnessChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -709,12 +678,12 @@ Singleton {
|
|||||||
id: gammastepAvailabilityProcess
|
id: gammastepAvailabilityProcess
|
||||||
command: ["which", "gammastep"]
|
command: ["which", "gammastep"]
|
||||||
running: false
|
running: false
|
||||||
|
|
||||||
onExited: function(exitCode) {
|
onExited: function (exitCode) {
|
||||||
automationAvailable = (exitCode === 0)
|
automationAvailable = (exitCode === 0)
|
||||||
if (automationAvailable) {
|
if (automationAvailable) {
|
||||||
detectLocationProviders()
|
detectLocationProviders()
|
||||||
|
|
||||||
// If night mode should be enabled on startup
|
// If night mode should be enabled on startup
|
||||||
if (nightModeEnabled && SessionData.nightModeAutoEnabled) {
|
if (nightModeEnabled && SessionData.nightModeAutoEnabled) {
|
||||||
startAutomation()
|
startAutomation()
|
||||||
@@ -748,7 +717,7 @@ Singleton {
|
|||||||
automationAvailable = true
|
automationAvailable = true
|
||||||
nightModeEnabled = true
|
nightModeEnabled = true
|
||||||
SessionData.setNightModeEnabled(true)
|
SessionData.setNightModeEnabled(true)
|
||||||
|
|
||||||
if (SessionData.nightModeAutoEnabled) {
|
if (SessionData.nightModeAutoEnabled) {
|
||||||
startAutomation()
|
startAutomation()
|
||||||
} else {
|
} else {
|
||||||
@@ -789,121 +758,149 @@ Singleton {
|
|||||||
// Session Data Connections
|
// Session Data Connections
|
||||||
Connections {
|
Connections {
|
||||||
target: SessionData
|
target: SessionData
|
||||||
|
|
||||||
function onNightModeEnabledChanged() {
|
function onNightModeEnabledChanged() {
|
||||||
nightModeEnabled = SessionData.nightModeEnabled
|
nightModeEnabled = SessionData.nightModeEnabled
|
||||||
evaluateNightMode()
|
evaluateNightMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
function onNightModeAutoEnabledChanged() { evaluateNightMode() }
|
function onNightModeAutoEnabledChanged() {
|
||||||
function onNightModeAutoModeChanged() { evaluateNightMode() }
|
evaluateNightMode()
|
||||||
function onNightModeStartHourChanged() { evaluateNightMode() }
|
}
|
||||||
function onNightModeStartMinuteChanged() { evaluateNightMode() }
|
function onNightModeAutoModeChanged() {
|
||||||
function onNightModeEndHourChanged() { evaluateNightMode() }
|
evaluateNightMode()
|
||||||
function onNightModeEndMinuteChanged() { evaluateNightMode() }
|
}
|
||||||
function onNightModeTemperatureChanged() { evaluateNightMode() }
|
function onNightModeStartHourChanged() {
|
||||||
function onLatitudeChanged() { evaluateNightMode() }
|
evaluateNightMode()
|
||||||
function onLongitudeChanged() { evaluateNightMode() }
|
}
|
||||||
function onNightModeLocationProviderChanged() { evaluateNightMode() }
|
function onNightModeStartMinuteChanged() {
|
||||||
|
evaluateNightMode()
|
||||||
|
}
|
||||||
|
function onNightModeEndHourChanged() {
|
||||||
|
evaluateNightMode()
|
||||||
|
}
|
||||||
|
function onNightModeEndMinuteChanged() {
|
||||||
|
evaluateNightMode()
|
||||||
|
}
|
||||||
|
function onNightModeTemperatureChanged() {
|
||||||
|
evaluateNightMode()
|
||||||
|
}
|
||||||
|
function onLatitudeChanged() {
|
||||||
|
evaluateNightMode()
|
||||||
|
}
|
||||||
|
function onLongitudeChanged() {
|
||||||
|
evaluateNightMode()
|
||||||
|
}
|
||||||
|
function onNightModeLocationProviderChanged() {
|
||||||
|
evaluateNightMode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPC Handler for external control
|
// IPC Handler for external control
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
function set(percentage: string, device: string): string {
|
function set(percentage: string, device: string): string {
|
||||||
if (!root.brightnessAvailable)
|
if (!root.brightnessAvailable) {
|
||||||
return "Brightness control not available"
|
return "Brightness control not available"
|
||||||
|
}
|
||||||
|
|
||||||
const value = parseInt(percentage)
|
const value = parseInt(percentage)
|
||||||
if (isNaN(value)) {
|
if (isNaN(value)) {
|
||||||
return "Invalid brightness value: " + percentage
|
return "Invalid brightness value: " + percentage
|
||||||
}
|
}
|
||||||
|
|
||||||
const clampedValue = Math.max(1, Math.min(100, value))
|
const clampedValue = Math.max(1, Math.min(100, value))
|
||||||
const targetDevice = device || ""
|
const targetDevice = device || ""
|
||||||
|
|
||||||
// Ensure device exists if specified
|
// Ensure device exists if specified
|
||||||
if (targetDevice && !root.devices.some(d => d.name === targetDevice)) {
|
if (targetDevice && !root.devices.some(d => d.name === targetDevice)) {
|
||||||
return "Device not found: " + targetDevice
|
return "Device not found: " + targetDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
root.lastIpcDevice = targetDevice
|
root.lastIpcDevice = targetDevice
|
||||||
if (targetDevice && targetDevice !== root.currentDevice) {
|
if (targetDevice && targetDevice !== root.currentDevice) {
|
||||||
root.setCurrentDevice(targetDevice, false)
|
root.setCurrentDevice(targetDevice, false)
|
||||||
}
|
}
|
||||||
root.setBrightness(clampedValue, targetDevice)
|
root.setBrightness(clampedValue, targetDevice)
|
||||||
|
|
||||||
if (targetDevice)
|
if (targetDevice) {
|
||||||
return "Brightness set to " + clampedValue + "% on " + targetDevice
|
return "Brightness set to " + clampedValue + "% on " + targetDevice
|
||||||
else
|
} else {
|
||||||
return "Brightness set to " + clampedValue + "%"
|
return "Brightness set to " + clampedValue + "%"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function increment(step: string, device: string): string {
|
function increment(step: string, device: string): string {
|
||||||
if (!root.brightnessAvailable)
|
if (!root.brightnessAvailable) {
|
||||||
return "Brightness control not available"
|
return "Brightness control not available"
|
||||||
|
}
|
||||||
|
|
||||||
const targetDevice = device || ""
|
const targetDevice = device || ""
|
||||||
const actualDevice = targetDevice === "" ? root.getDefaultDevice() : targetDevice
|
const actualDevice = targetDevice === "" ? root.getDefaultDevice() : targetDevice
|
||||||
|
|
||||||
// Ensure device exists
|
// Ensure device exists
|
||||||
if (actualDevice && !root.devices.some(d => d.name === actualDevice)) {
|
if (actualDevice && !root.devices.some(d => d.name === actualDevice)) {
|
||||||
return "Device not found: " + actualDevice
|
return "Device not found: " + actualDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentLevel = actualDevice ? root.getDeviceBrightness(actualDevice) : root.brightnessLevel
|
const currentLevel = actualDevice ? root.getDeviceBrightness(actualDevice) : root.brightnessLevel
|
||||||
const stepValue = parseInt(step || "10")
|
const stepValue = parseInt(step || "10")
|
||||||
const newLevel = Math.max(1, Math.min(100, currentLevel + stepValue))
|
const newLevel = Math.max(1, Math.min(100, currentLevel + stepValue))
|
||||||
|
|
||||||
root.lastIpcDevice = targetDevice
|
root.lastIpcDevice = targetDevice
|
||||||
if (targetDevice && targetDevice !== root.currentDevice) {
|
if (targetDevice && targetDevice !== root.currentDevice) {
|
||||||
root.setCurrentDevice(targetDevice, false)
|
root.setCurrentDevice(targetDevice, false)
|
||||||
}
|
}
|
||||||
root.setBrightness(newLevel, targetDevice)
|
root.setBrightness(newLevel, targetDevice)
|
||||||
|
|
||||||
if (targetDevice)
|
if (targetDevice) {
|
||||||
return "Brightness increased to " + newLevel + "% on " + targetDevice
|
return "Brightness increased to " + newLevel + "% on " + targetDevice
|
||||||
else
|
} else {
|
||||||
return "Brightness increased to " + newLevel + "%"
|
return "Brightness increased to " + newLevel + "%"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function decrement(step: string, device: string): string {
|
function decrement(step: string, device: string): string {
|
||||||
if (!root.brightnessAvailable)
|
if (!root.brightnessAvailable) {
|
||||||
return "Brightness control not available"
|
return "Brightness control not available"
|
||||||
|
}
|
||||||
|
|
||||||
const targetDevice = device || ""
|
const targetDevice = device || ""
|
||||||
const actualDevice = targetDevice === "" ? root.getDefaultDevice() : targetDevice
|
const actualDevice = targetDevice === "" ? root.getDefaultDevice() : targetDevice
|
||||||
|
|
||||||
// Ensure device exists
|
// Ensure device exists
|
||||||
if (actualDevice && !root.devices.some(d => d.name === actualDevice)) {
|
if (actualDevice && !root.devices.some(d => d.name === actualDevice)) {
|
||||||
return "Device not found: " + actualDevice
|
return "Device not found: " + actualDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentLevel = actualDevice ? root.getDeviceBrightness(actualDevice) : root.brightnessLevel
|
const currentLevel = actualDevice ? root.getDeviceBrightness(actualDevice) : root.brightnessLevel
|
||||||
const stepValue = parseInt(step || "10")
|
const stepValue = parseInt(step || "10")
|
||||||
const newLevel = Math.max(1, Math.min(100, currentLevel - stepValue))
|
const newLevel = Math.max(1, Math.min(100, currentLevel - stepValue))
|
||||||
|
|
||||||
root.lastIpcDevice = targetDevice
|
root.lastIpcDevice = targetDevice
|
||||||
if (targetDevice && targetDevice !== root.currentDevice) {
|
if (targetDevice && targetDevice !== root.currentDevice) {
|
||||||
root.setCurrentDevice(targetDevice, false)
|
root.setCurrentDevice(targetDevice, false)
|
||||||
}
|
}
|
||||||
root.setBrightness(newLevel, targetDevice)
|
root.setBrightness(newLevel, targetDevice)
|
||||||
|
|
||||||
if (targetDevice)
|
if (targetDevice) {
|
||||||
return "Brightness decreased to " + newLevel + "% on " + targetDevice
|
return "Brightness decreased to " + newLevel + "% on " + targetDevice
|
||||||
else
|
} else {
|
||||||
return "Brightness decreased to " + newLevel + "%"
|
return "Brightness decreased to " + newLevel + "%"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function status(): string {
|
function status(): string {
|
||||||
if (!root.brightnessAvailable)
|
if (!root.brightnessAvailable) {
|
||||||
return "Brightness control not available"
|
return "Brightness control not available"
|
||||||
|
}
|
||||||
|
|
||||||
return "Device: " + root.currentDevice + " - Brightness: " + root.brightnessLevel + "%"
|
return "Device: " + root.currentDevice + " - Brightness: " + root.brightnessLevel + "%"
|
||||||
}
|
}
|
||||||
|
|
||||||
function list(): string {
|
function list(): string {
|
||||||
if (!root.brightnessAvailable)
|
if (!root.brightnessAvailable) {
|
||||||
return "No brightness devices available"
|
return "No brightness devices available"
|
||||||
|
}
|
||||||
|
|
||||||
let result = "Available devices:\\n"
|
let result = "Available devices:\\n"
|
||||||
for (const device of root.devices) {
|
for (const device of root.devices) {
|
||||||
@@ -974,4 +971,4 @@ Singleton {
|
|||||||
|
|
||||||
target: "night"
|
target: "night"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
|||||||
@@ -1,56 +1,60 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Services.Mpris
|
import Quickshell.Services.Mpris
|
||||||
import Quickshell.Widgets
|
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property list<MprisPlayer> availablePlayers: Mpris.players.values
|
readonly property list<MprisPlayer> availablePlayers: Mpris.players.values
|
||||||
|
|
||||||
property MprisPlayer activePlayer: availablePlayers.find(p => p.isPlaying)
|
property MprisPlayer activePlayer: availablePlayers.find(p => p.isPlaying) ?? availablePlayers.find(p => p.canControl && p.canPlay) ?? null
|
||||||
?? availablePlayers.find(
|
|
||||||
p => p.canControl
|
|
||||||
&& p.canPlay) ?? null
|
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
target: "mpris"
|
target: "mpris"
|
||||||
|
|
||||||
function list(): string {
|
function list(): string {
|
||||||
return root.availablePlayers.map(p => p.identity).join("")
|
return root.availablePlayers.map(p => p.identity).join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
function play(): void {
|
function play(): void {
|
||||||
if (root.activePlayer?.canPlay)
|
if (root.activePlayer && root.activePlayer.canPlay) {
|
||||||
root.activePlayer.play()
|
root.activePlayer.play()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pause(): void {
|
function pause(): void {
|
||||||
if (root.activePlayer?.canPause)
|
if (root.activePlayer && root.activePlayer.canPause) {
|
||||||
root.activePlayer.pause()
|
root.activePlayer.pause()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function playPause(): void {
|
function playPause(): void {
|
||||||
if (root.activePlayer?.canTogglePlaying)
|
if (root.activePlayer && root.activePlayer.canTogglePlaying) {
|
||||||
root.activePlayer.togglePlaying()
|
root.activePlayer.togglePlaying()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function previous(): void {
|
function previous(): void {
|
||||||
if (root.activePlayer?.canGoPrevious)
|
if (root.activePlayer && root.activePlayer.canGoPrevious) {
|
||||||
root.activePlayer.previous()
|
root.activePlayer.previous()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function next(): void {
|
function next(): void {
|
||||||
if (root.activePlayer?.canGoNext)
|
if (root.activePlayer && root.activePlayer.canGoNext) {
|
||||||
root.activePlayer.next()
|
root.activePlayer.next()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop(): void {
|
function stop(): void {
|
||||||
root.activePlayer?.stop()
|
if (root.activePlayer) {
|
||||||
|
root.activePlayer.stop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -9,35 +10,32 @@ import qs.Common
|
|||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
// Core network state
|
|
||||||
property int refCount: 0
|
property int refCount: 0
|
||||||
property string networkStatus: "disconnected" // "ethernet", "wifi", "disconnected"
|
property string networkStatus: "disconnected"
|
||||||
property string primaryConnection: "" // Active connection UUID
|
property string primaryConnection: ""
|
||||||
|
|
||||||
// Ethernet properties
|
|
||||||
property string ethernetIP: ""
|
property string ethernetIP: ""
|
||||||
property string ethernetInterface: ""
|
property string ethernetInterface: ""
|
||||||
property bool ethernetConnected: false
|
property bool ethernetConnected: false
|
||||||
property string ethernetConnectionUuid: ""
|
property string ethernetConnectionUuid: ""
|
||||||
|
|
||||||
// WiFi properties
|
|
||||||
property string wifiIP: ""
|
property string wifiIP: ""
|
||||||
property string wifiInterface: ""
|
property string wifiInterface: ""
|
||||||
property bool wifiConnected: false
|
property bool wifiConnected: false
|
||||||
property bool wifiEnabled: true
|
property bool wifiEnabled: true
|
||||||
property string wifiConnectionUuid: ""
|
property string wifiConnectionUuid: ""
|
||||||
|
|
||||||
// WiFi details
|
|
||||||
property string currentWifiSSID: ""
|
property string currentWifiSSID: ""
|
||||||
property int wifiSignalStrength: 0
|
property int wifiSignalStrength: 0
|
||||||
property var wifiNetworks: []
|
property var wifiNetworks: []
|
||||||
property var savedConnections: []
|
property var savedConnections: []
|
||||||
property var ssidToConnectionName: {}
|
property var ssidToConnectionName: {
|
||||||
|
|
||||||
|
}
|
||||||
property var wifiSignalIcon: {
|
property var wifiSignalIcon: {
|
||||||
if (!wifiConnected || networkStatus !== "wifi") {
|
if (!wifiConnected || networkStatus !== "wifi") {
|
||||||
return "signal_wifi_off"
|
return "signal_wifi_off"
|
||||||
}
|
}
|
||||||
// Use nmcli signal strength percentage
|
|
||||||
if (wifiSignalStrength >= 70) {
|
if (wifiSignalStrength >= 70) {
|
||||||
return "signal_wifi_4_bar"
|
return "signal_wifi_4_bar"
|
||||||
}
|
}
|
||||||
@@ -53,17 +51,14 @@ Singleton {
|
|||||||
return "signal_wifi_bad"
|
return "signal_wifi_bad"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connection management
|
|
||||||
property string userPreference: "auto" // "auto", "wifi", "ethernet"
|
property string userPreference: "auto" // "auto", "wifi", "ethernet"
|
||||||
property bool isConnecting: false
|
property bool isConnecting: false
|
||||||
property string connectingSSID: ""
|
property string connectingSSID: ""
|
||||||
property string connectionError: ""
|
property string connectionError: ""
|
||||||
|
|
||||||
// Scanning
|
|
||||||
property bool isScanning: false
|
property bool isScanning: false
|
||||||
property bool autoScan: false
|
property bool autoScan: false
|
||||||
|
|
||||||
// Legacy compatibility properties
|
|
||||||
property bool wifiAvailable: true
|
property bool wifiAvailable: true
|
||||||
property bool wifiToggling: false
|
property bool wifiToggling: false
|
||||||
property bool changingPreference: false
|
property bool changingPreference: false
|
||||||
@@ -76,7 +71,6 @@ Singleton {
|
|||||||
property string wifiPassword: ""
|
property string wifiPassword: ""
|
||||||
property string forgetSSID: ""
|
property string forgetSSID: ""
|
||||||
|
|
||||||
// Network info properties
|
|
||||||
property string networkInfoSSID: ""
|
property string networkInfoSSID: ""
|
||||||
property string networkInfoDetails: ""
|
property string networkInfoDetails: ""
|
||||||
property bool networkInfoLoading: false
|
property bool networkInfoLoading: false
|
||||||
@@ -84,15 +78,13 @@ Singleton {
|
|||||||
signal networksUpdated
|
signal networksUpdated
|
||||||
signal connectionChanged
|
signal connectionChanged
|
||||||
|
|
||||||
// Helper: split nmcli -t output respecting escaped colons (\:)
|
|
||||||
function splitNmcliFields(line) {
|
function splitNmcliFields(line) {
|
||||||
let parts = []
|
const parts = []
|
||||||
let cur = ""
|
let cur = ""
|
||||||
let escape = false
|
let escape = false
|
||||||
for (let i = 0; i < line.length; i++) {
|
for (var i = 0; i < line.length; i++) {
|
||||||
const ch = line[i]
|
const ch = line[i]
|
||||||
if (escape) {
|
if (escape) {
|
||||||
// Keep literal for escaped colon and other sequences
|
|
||||||
cur += ch
|
cur += ch
|
||||||
escape = false
|
escape = false
|
||||||
} else if (ch === '\\') {
|
} else if (ch === '\\') {
|
||||||
@@ -140,11 +132,7 @@ Singleton {
|
|||||||
stdout: SplitParser {
|
stdout: SplitParser {
|
||||||
splitMarker: "\n"
|
splitMarker: "\n"
|
||||||
onRead: line => {
|
onRead: line => {
|
||||||
if (line.includes("StateChanged") || line.includes(
|
if (line.includes("StateChanged") || line.includes("PrimaryConnectionChanged") || line.includes("WirelessEnabled") || line.includes("ActiveConnection") || line.includes("PropertiesChanged")) {
|
||||||
"PrimaryConnectionChanged") || line.includes(
|
|
||||||
"WirelessEnabled") || line.includes(
|
|
||||||
"ActiveConnection") || line.includes(
|
|
||||||
"PropertiesChanged")) {
|
|
||||||
refreshNetworkState()
|
refreshNetworkState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,8 +304,9 @@ Singleton {
|
|||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
const match = text.match(/inet (\d+\.\d+\.\d+\.\d+)/)
|
const match = text.match(/inet (\d+\.\d+\.\d+\.\d+)/)
|
||||||
if (match)
|
if (match) {
|
||||||
root.ethernetIP = match[1]
|
root.ethernetIP = match[1]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -415,22 +404,21 @@ Singleton {
|
|||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
const match = text.match(/inet (\d+\.\d+\.\d+\.\d+)/)
|
const match = text.match(/inet (\d+\.\d+\.\d+\.\d+)/)
|
||||||
if (match)
|
if (match) {
|
||||||
root.wifiIP = match[1]
|
root.wifiIP = match[1]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: getCurrentWifiInfo
|
id: getCurrentWifiInfo
|
||||||
// Prefer IN-USE,SIGNAL,SSID, but we'll also parse legacy ACTIVE format
|
|
||||||
command: root.wifiInterface ? ["nmcli", "-t", "-f", "IN-USE,SIGNAL,SSID", "device", "wifi", "list", "ifname", root.wifiInterface] : []
|
command: root.wifiInterface ? ["nmcli", "-t", "-f", "IN-USE,SIGNAL,SSID", "device", "wifi", "list", "ifname", root.wifiInterface] : []
|
||||||
running: false
|
running: false
|
||||||
|
|
||||||
stdout: SplitParser {
|
stdout: SplitParser {
|
||||||
splitMarker: "\n"
|
splitMarker: "\n"
|
||||||
onRead: line => {
|
onRead: line => {
|
||||||
// IN-USE format: "*:SIGNAL:SSID"
|
|
||||||
if (line.startsWith("*:")) {
|
if (line.startsWith("*:")) {
|
||||||
const rest = line.substring(2)
|
const rest = line.substring(2)
|
||||||
const parts = root.splitNmcliFields(rest)
|
const parts = root.splitNmcliFields(rest)
|
||||||
@@ -455,7 +443,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function updateActiveConnections() {
|
function updateActiveConnections() {
|
||||||
getActiveConnections.running = true
|
getActiveConnections.running = true
|
||||||
}
|
}
|
||||||
@@ -475,11 +462,9 @@ Singleton {
|
|||||||
const type = parts[1]
|
const type = parts[1]
|
||||||
const device = parts[2]
|
const device = parts[2]
|
||||||
const state = parts[3]
|
const state = parts[3]
|
||||||
if (type === "802-3-ethernet"
|
if (type === "802-3-ethernet" && state === "activated") {
|
||||||
&& state === "activated") {
|
|
||||||
root.ethernetConnectionUuid = uuid
|
root.ethernetConnectionUuid = uuid
|
||||||
} else if (type === "802-11-wireless"
|
} else if (type === "802-11-wireless" && state === "activated") {
|
||||||
&& state === "activated") {
|
|
||||||
root.wifiConnectionUuid = uuid
|
root.wifiConnectionUuid = uuid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -514,8 +499,9 @@ Singleton {
|
|||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
if (!root.currentWifiSSID) {
|
if (!root.currentWifiSSID) {
|
||||||
const name = text.trim()
|
const name = text.trim()
|
||||||
if (name)
|
if (name) {
|
||||||
root.currentWifiSSID = name
|
root.currentWifiSSID = name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -539,8 +525,9 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function scanWifi() {
|
function scanWifi() {
|
||||||
if (root.isScanning || !root.wifiEnabled)
|
if (root.isScanning || !root.wifiEnabled) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
root.isScanning = true
|
root.isScanning = true
|
||||||
requestWifiScan.running = true
|
requestWifiScan.running = true
|
||||||
@@ -578,7 +565,7 @@ Singleton {
|
|||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
let networks = []
|
const networks = []
|
||||||
const lines = text.trim().split('\n')
|
const lines = text.trim().split('\n')
|
||||||
const seen = new Set()
|
const seen = new Set()
|
||||||
|
|
||||||
@@ -596,7 +583,7 @@ Singleton {
|
|||||||
"secured": parts[2] !== "",
|
"secured": parts[2] !== "",
|
||||||
"bssid": parts[3],
|
"bssid": parts[3],
|
||||||
"connected": ssid === root.currentWifiSSID,
|
"connected": ssid === root.currentWifiSSID,
|
||||||
"saved": false // Will be updated by saved connections check
|
"saved": false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -617,8 +604,8 @@ Singleton {
|
|||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
let saved = []
|
const saved = []
|
||||||
let mapping = {}
|
const mapping = {}
|
||||||
const lines = text.trim().split('\n')
|
const lines = text.trim().split('\n')
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
@@ -640,8 +627,8 @@ Singleton {
|
|||||||
root.savedWifiNetworks = saved
|
root.savedWifiNetworks = saved
|
||||||
root.ssidToConnectionName = mapping
|
root.ssidToConnectionName = mapping
|
||||||
|
|
||||||
let updated = [...root.wifiNetworks]
|
const updated = [...root.wifiNetworks]
|
||||||
for (let network of updated) {
|
for (const network of updated) {
|
||||||
network.saved = saved.some(s => s.ssid === network.ssid)
|
network.saved = saved.some(s => s.ssid === network.ssid)
|
||||||
}
|
}
|
||||||
root.wifiNetworks = updated
|
root.wifiNetworks = updated
|
||||||
@@ -650,15 +637,15 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function connectToWifi(ssid, password = "") {
|
function connectToWifi(ssid, password = "") {
|
||||||
if (root.isConnecting)
|
if (root.isConnecting) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
root.isConnecting = true
|
root.isConnecting = true
|
||||||
root.connectingSSID = ssid
|
root.connectingSSID = ssid
|
||||||
root.connectionError = ""
|
root.connectionError = ""
|
||||||
root.connectionStatus = "connecting"
|
root.connectionStatus = "connecting"
|
||||||
|
|
||||||
// For saved networks without password, try connection up first
|
|
||||||
if (!password && root.ssidToConnectionName[ssid]) {
|
if (!password && root.ssidToConnectionName[ssid]) {
|
||||||
const connectionName = root.ssidToConnectionName[ssid]
|
const connectionName = root.ssidToConnectionName[ssid]
|
||||||
wifiConnector.command = ["nmcli", "connection", "up", connectionName]
|
wifiConnector.command = ["nmcli", "connection", "up", connectionName]
|
||||||
@@ -670,10 +657,6 @@ Singleton {
|
|||||||
wifiConnector.running = true
|
wifiConnector.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectToWifiWithPassword(ssid, password) {
|
|
||||||
connectToWifi(ssid, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: wifiConnector
|
id: wifiConnector
|
||||||
running: false
|
running: false
|
||||||
@@ -688,8 +671,7 @@ Singleton {
|
|||||||
root.connectionError = ""
|
root.connectionError = ""
|
||||||
root.connectionStatus = "connected"
|
root.connectionStatus = "connected"
|
||||||
|
|
||||||
if (root.userPreference === "wifi"
|
if (root.userPreference === "wifi" || root.userPreference === "auto") {
|
||||||
|| root.userPreference === "auto") {
|
|
||||||
setConnectionPriority("wifi")
|
setConnectionPriority("wifi")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -701,8 +683,7 @@ Singleton {
|
|||||||
root.connectionError = text
|
root.connectionError = text
|
||||||
root.lastConnectionError = text
|
root.lastConnectionError = text
|
||||||
if (!wifiConnector.connectionSucceeded && text.trim() !== "") {
|
if (!wifiConnector.connectionSucceeded && text.trim() !== "") {
|
||||||
if (text.includes("password") || text.includes(
|
if (text.includes("password") || text.includes("authentication")) {
|
||||||
"authentication")) {
|
|
||||||
root.connectionStatus = "invalid_password"
|
root.connectionStatus = "invalid_password"
|
||||||
root.passwordDialogShouldReopen = true
|
root.passwordDialogShouldReopen = true
|
||||||
} else {
|
} else {
|
||||||
@@ -715,7 +696,6 @@ Singleton {
|
|||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
if (exitCode === 0 || wifiConnector.connectionSucceeded) {
|
if (exitCode === 0 || wifiConnector.connectionSucceeded) {
|
||||||
if (!wifiConnector.connectionSucceeded) {
|
if (!wifiConnector.connectionSucceeded) {
|
||||||
// Command succeeded but we didn't see "successfully" - still mark as success
|
|
||||||
ToastService.showInfo(`Connected to ${root.connectingSSID}`)
|
ToastService.showInfo(`Connected to ${root.connectingSSID}`)
|
||||||
root.connectionStatus = "connected"
|
root.connectionStatus = "connected"
|
||||||
}
|
}
|
||||||
@@ -724,11 +704,9 @@ Singleton {
|
|||||||
root.connectionStatus = "failed"
|
root.connectionStatus = "failed"
|
||||||
}
|
}
|
||||||
if (root.connectionStatus === "invalid_password") {
|
if (root.connectionStatus === "invalid_password") {
|
||||||
ToastService.showError(
|
ToastService.showError(`Invalid password for ${root.connectingSSID}`)
|
||||||
`Invalid password for ${root.connectingSSID}`)
|
|
||||||
} else {
|
} else {
|
||||||
ToastService.showError(
|
ToastService.showError(`Failed to connect to ${root.connectingSSID}`)
|
||||||
`Failed to connect to ${root.connectingSSID}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -740,8 +718,9 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function disconnectWifi() {
|
function disconnectWifi() {
|
||||||
if (!root.wifiInterface)
|
if (!root.wifiInterface) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
wifiDisconnector.command = ["nmcli", "dev", "disconnect", root.wifiInterface]
|
wifiDisconnector.command = ["nmcli", "dev", "disconnect", root.wifiInterface]
|
||||||
wifiDisconnector.running = true
|
wifiDisconnector.running = true
|
||||||
@@ -776,13 +755,11 @@ Singleton {
|
|||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
ToastService.showInfo(`Forgot network ${root.forgetSSID}`)
|
ToastService.showInfo(`Forgot network ${root.forgetSSID}`)
|
||||||
|
|
||||||
root.savedConnections = root.savedConnections.filter(
|
root.savedConnections = root.savedConnections.filter(s => s.ssid !== root.forgetSSID)
|
||||||
s => s.ssid !== root.forgetSSID)
|
root.savedWifiNetworks = root.savedWifiNetworks.filter(s => s.ssid !== root.forgetSSID)
|
||||||
root.savedWifiNetworks = root.savedWifiNetworks.filter(
|
|
||||||
s => s.ssid !== root.forgetSSID)
|
|
||||||
|
|
||||||
let updated = [...root.wifiNetworks]
|
const updated = [...root.wifiNetworks]
|
||||||
for (let network of updated) {
|
for (const network of updated) {
|
||||||
if (network.ssid === root.forgetSSID) {
|
if (network.ssid === root.forgetSSID) {
|
||||||
network.saved = false
|
network.saved = false
|
||||||
if (network.connected) {
|
if (network.connected) {
|
||||||
@@ -800,8 +777,9 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggleWifiRadio() {
|
function toggleWifiRadio() {
|
||||||
if (root.wifiToggling)
|
if (root.wifiToggling) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
root.wifiToggling = true
|
root.wifiToggling = true
|
||||||
const targetState = root.wifiEnabled ? "off" : "on"
|
const targetState = root.wifiEnabled ? "off" : "on"
|
||||||
@@ -819,15 +797,12 @@ Singleton {
|
|||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
root.wifiToggling = false
|
root.wifiToggling = false
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
// Don't manually toggle wifiEnabled - let DBus monitoring handle it
|
ToastService.showInfo(targetState === "on" ? "WiFi enabled" : "WiFi disabled")
|
||||||
ToastService.showInfo(
|
|
||||||
targetState === "on" ? "WiFi enabled" : "WiFi disabled")
|
|
||||||
}
|
}
|
||||||
refreshNetworkState()
|
refreshNetworkState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Network Preference Management =====
|
|
||||||
function setNetworkPreference(preference) {
|
function setNetworkPreference(preference) {
|
||||||
root.userPreference = preference
|
root.userPreference = preference
|
||||||
root.changingPreference = true
|
root.changingPreference = true
|
||||||
@@ -839,7 +814,6 @@ Singleton {
|
|||||||
} else if (preference === "ethernet") {
|
} else if (preference === "ethernet") {
|
||||||
setConnectionPriority("ethernet")
|
setConnectionPriority("ethernet")
|
||||||
}
|
}
|
||||||
// "auto" uses default NetworkManager behavior
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setConnectionPriority(type) {
|
function setConnectionPriority(type) {
|
||||||
@@ -865,9 +839,7 @@ Singleton {
|
|||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: restartConnections
|
id: restartConnections
|
||||||
command: ["bash", "-c", "nmcli -t -f UUID,TYPE connection show --active | "
|
command: ["bash", "-c", "nmcli -t -f UUID,TYPE connection show --active | " + "grep -E '802-11-wireless|802-3-ethernet' | cut -d: -f1 | " + "xargs -I {} sh -c 'nmcli connection down {} && nmcli connection up {}'"]
|
||||||
+ "grep -E '802-11-wireless|802-3-ethernet' | cut -d: -f1 | "
|
|
||||||
+ "xargs -I {} sh -c 'nmcli connection down {} && nmcli connection up {}'"]
|
|
||||||
running: false
|
running: false
|
||||||
|
|
||||||
onExited: {
|
onExited: {
|
||||||
@@ -890,7 +862,6 @@ Singleton {
|
|||||||
root.autoRefreshEnabled = false
|
root.autoRefreshEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Network Info =====
|
|
||||||
function fetchNetworkInfo(ssid) {
|
function fetchNetworkInfo(ssid) {
|
||||||
root.networkInfoSSID = ssid
|
root.networkInfoSSID = ssid
|
||||||
root.networkInfoLoading = true
|
root.networkInfoLoading = true
|
||||||
@@ -907,23 +878,21 @@ Singleton {
|
|||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
let details = ""
|
let details = ""
|
||||||
if (text.trim()) {
|
if (text.trim()) {
|
||||||
let lines = text.trim().split('\n')
|
const lines = text.trim().split('\n')
|
||||||
let bands = []
|
const bands = []
|
||||||
|
|
||||||
// Collect all access points for this SSID
|
for (const line of lines) {
|
||||||
for (let line of lines) {
|
const parts = line.split(':')
|
||||||
let parts = line.split(':')
|
|
||||||
if (parts.length >= 11 && parts[0] === root.networkInfoSSID) {
|
if (parts.length >= 11 && parts[0] === root.networkInfoSSID) {
|
||||||
let signal = parts[1] || "0"
|
const signal = parts[1] || "0"
|
||||||
let security = parts[2] || "Open"
|
const security = parts[2] || "Open"
|
||||||
let freq = parts[3] || "Unknown"
|
const freq = parts[3] || "Unknown"
|
||||||
let rate = parts[4] || "Unknown"
|
const rate = parts[4] || "Unknown"
|
||||||
let channel = parts[6] || "Unknown"
|
const channel = parts[6] || "Unknown"
|
||||||
let isActive = parts[9] === "yes"
|
const isActive = parts[9] === "yes"
|
||||||
// BSSID is the last field, find it by counting colons
|
|
||||||
let colonCount = 0
|
let colonCount = 0
|
||||||
let bssidStart = -1
|
let bssidStart = -1
|
||||||
for (let i = 0; i < line.length; i++) {
|
for (var i = 0; i < line.length; i++) {
|
||||||
if (line[i] === ':') {
|
if (line[i] === ':') {
|
||||||
colonCount++
|
colonCount++
|
||||||
if (colonCount === 10) {
|
if (colonCount === 10) {
|
||||||
@@ -932,10 +901,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let bssid = bssidStart >= 0 ? line.substring(bssidStart).replace(/\\:/g, ":") : ""
|
const bssid = bssidStart >= 0 ? line.substring(bssidStart).replace(/\\:/g, ":") : ""
|
||||||
|
|
||||||
let band = "Unknown"
|
let band = "Unknown"
|
||||||
let freqNum = parseInt(freq)
|
const freqNum = parseInt(freq)
|
||||||
if (freqNum >= 2400 && freqNum <= 2500) {
|
if (freqNum >= 2400 && freqNum <= 2500) {
|
||||||
band = "2.4 GHz"
|
band = "2.4 GHz"
|
||||||
} else if (freqNum >= 5000 && freqNum <= 6000) {
|
} else if (freqNum >= 5000 && freqNum <= 6000) {
|
||||||
@@ -945,28 +914,31 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bands.push({
|
bands.push({
|
||||||
band: band,
|
"band": band,
|
||||||
freq: freq,
|
"freq": freq,
|
||||||
channel: channel,
|
"channel": channel,
|
||||||
signal: signal,
|
"signal": signal,
|
||||||
rate: rate,
|
"rate": rate,
|
||||||
security: security,
|
"security": security,
|
||||||
isActive: isActive,
|
"isActive": isActive,
|
||||||
bssid: bssid
|
"bssid": bssid
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bands.length > 0) {
|
if (bands.length > 0) {
|
||||||
// Sort bands: active first, then by signal strength
|
|
||||||
bands.sort((a, b) => {
|
bands.sort((a, b) => {
|
||||||
if (a.isActive && !b.isActive) return -1
|
if (a.isActive && !b.isActive) {
|
||||||
if (!a.isActive && b.isActive) return 1
|
return -1
|
||||||
return parseInt(b.signal) - parseInt(a.signal)
|
}
|
||||||
})
|
if (!a.isActive && b.isActive) {
|
||||||
|
return 1
|
||||||
for (let i = 0; i < bands.length; i++) {
|
}
|
||||||
let b = bands[i]
|
return parseInt(b.signal) - parseInt(a.signal)
|
||||||
|
})
|
||||||
|
|
||||||
|
for (var i = 0; i < bands.length; i++) {
|
||||||
|
const b = bands[i]
|
||||||
if (b.isActive) {
|
if (b.isActive) {
|
||||||
details += "● " + b.band + " (Connected) - " + b.signal + "%\\n"
|
details += "● " + b.band + " (Connected) - " + b.signal + "%\\n"
|
||||||
} else {
|
} else {
|
||||||
@@ -998,18 +970,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshNetworkStatus() {
|
|
||||||
refreshNetworkState()
|
|
||||||
}
|
|
||||||
|
|
||||||
function delayedRefreshNetworkStatus() {
|
|
||||||
refreshNetworkState()
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCurrentWifiInfo() {
|
|
||||||
getCurrentWifiInfo.running = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function enableWifiDevice() {
|
function enableWifiDevice() {
|
||||||
wifiDeviceEnabler.running = true
|
wifiDeviceEnabler.running = true
|
||||||
}
|
}
|
||||||
@@ -1030,7 +990,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function connectToWifiAndSetPreference(ssid, password) {
|
function connectToWifiAndSetPreference(ssid, password) {
|
||||||
connectToWifiWithPassword(ssid, password)
|
connectToWifi(ssid, password)
|
||||||
setNetworkPreference("wifi")
|
setNetworkPreference("wifi")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1066,8 +1026,9 @@ Singleton {
|
|||||||
|
|
||||||
function getNetworkInfo(ssid) {
|
function getNetworkInfo(ssid) {
|
||||||
const network = root.wifiNetworks.find(n => n.ssid === ssid)
|
const network = root.wifiNetworks.find(n => n.ssid === ssid)
|
||||||
if (!network)
|
if (!network) {
|
||||||
return null
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"ssid": network.ssid,
|
"ssid": network.ssid,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -9,7 +10,6 @@ import Quickshell.Wayland
|
|||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
// Workspace management
|
|
||||||
property var workspaces: ({})
|
property var workspaces: ({})
|
||||||
property var allWorkspaces: []
|
property var allWorkspaces: []
|
||||||
property int focusedWorkspaceIndex: 0
|
property int focusedWorkspaceIndex: 0
|
||||||
@@ -17,24 +17,18 @@ Singleton {
|
|||||||
property var currentOutputWorkspaces: []
|
property var currentOutputWorkspaces: []
|
||||||
property string currentOutput: ""
|
property string currentOutput: ""
|
||||||
|
|
||||||
// Output/Monitor management
|
property var outputs: ({})
|
||||||
property var outputs: ({}) // Map of output name to output info with positions
|
|
||||||
|
|
||||||
// Window management
|
|
||||||
property var windows: []
|
property var windows: []
|
||||||
|
|
||||||
// Overview state
|
|
||||||
property bool inOverview: false
|
property bool inOverview: false
|
||||||
|
|
||||||
// Keyboard layout state
|
|
||||||
property int currentKeyboardLayoutIndex: 0
|
property int currentKeyboardLayoutIndex: 0
|
||||||
property var keyboardLayoutNames: []
|
property var keyboardLayoutNames: []
|
||||||
|
|
||||||
// Internal state (not exposed to external components)
|
|
||||||
property string configValidationOutput: ""
|
property string configValidationOutput: ""
|
||||||
property bool hasInitialConnection: false
|
property bool hasInitialConnection: false
|
||||||
|
|
||||||
|
|
||||||
readonly property string socketPath: Quickshell.env("NIRI_SOCKET")
|
readonly property string socketPath: Quickshell.env("NIRI_SOCKET")
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
@@ -54,11 +48,9 @@ Singleton {
|
|||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
try {
|
try {
|
||||||
var outputsData = JSON.parse(text)
|
const outputsData = JSON.parse(text)
|
||||||
outputs = outputsData
|
outputs = outputsData
|
||||||
console.log("NiriService: Loaded",
|
console.log("NiriService: Loaded", Object.keys(outputsData).length, "outputs")
|
||||||
Object.keys(outputsData).length, "outputs")
|
|
||||||
// Re-sort windows with monitor positions
|
|
||||||
if (windows.length > 0) {
|
if (windows.length > 0) {
|
||||||
windows = sortWindowsByLayout(windows)
|
windows = sortWindowsByLayout(windows)
|
||||||
}
|
}
|
||||||
@@ -70,9 +62,7 @@ Singleton {
|
|||||||
|
|
||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
console.warn(
|
console.warn("NiriService: Failed to fetch outputs, exit code:", exitCode)
|
||||||
"NiriService: Failed to fetch outputs, exit code:",
|
|
||||||
exitCode)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,59 +98,40 @@ Singleton {
|
|||||||
|
|
||||||
function sortWindowsByLayout(windowList) {
|
function sortWindowsByLayout(windowList) {
|
||||||
return [...windowList].sort((a, b) => {
|
return [...windowList].sort((a, b) => {
|
||||||
// Get workspace info for both windows
|
const aWorkspace = workspaces[a.workspace_id]
|
||||||
var aWorkspace = workspaces[a.workspace_id]
|
const bWorkspace = workspaces[b.workspace_id]
|
||||||
var bWorkspace = workspaces[b.workspace_id]
|
|
||||||
|
|
||||||
if (aWorkspace && bWorkspace) {
|
if (aWorkspace && bWorkspace) {
|
||||||
var aOutput = aWorkspace.output
|
const aOutput = aWorkspace.output
|
||||||
var bOutput = bWorkspace.output
|
const bOutput = bWorkspace.output
|
||||||
|
|
||||||
// 1. First, sort by monitor position (left to right, top to bottom)
|
const aOutputInfo = outputs[aOutput]
|
||||||
var aOutputInfo = outputs[aOutput]
|
const bOutputInfo = outputs[bOutput]
|
||||||
var bOutputInfo = outputs[bOutput]
|
|
||||||
|
|
||||||
if (aOutputInfo && bOutputInfo
|
if (aOutputInfo && bOutputInfo && aOutputInfo.logical && bOutputInfo.logical) {
|
||||||
&& aOutputInfo.logical
|
if (aOutputInfo.logical.x !== bOutputInfo.logical.x) {
|
||||||
&& bOutputInfo.logical) {
|
return aOutputInfo.logical.x - bOutputInfo.logical.x
|
||||||
// Sort by monitor X position (left to right)
|
|
||||||
if (aOutputInfo.logical.x
|
|
||||||
!== bOutputInfo.logical.x) {
|
|
||||||
return aOutputInfo.logical.x
|
|
||||||
- bOutputInfo.logical.x
|
|
||||||
}
|
}
|
||||||
// If same X, sort by Y position (top to bottom)
|
if (aOutputInfo.logical.y !== bOutputInfo.logical.y) {
|
||||||
if (aOutputInfo.logical.y
|
return aOutputInfo.logical.y - bOutputInfo.logical.y
|
||||||
!== bOutputInfo.logical.y) {
|
|
||||||
return aOutputInfo.logical.y
|
|
||||||
- bOutputInfo.logical.y
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. If same monitor, sort by workspace index
|
if (aOutput === bOutput && aWorkspace.idx !== bWorkspace.idx) {
|
||||||
if (aOutput === bOutput
|
|
||||||
&& aWorkspace.idx !== bWorkspace.idx) {
|
|
||||||
return aWorkspace.idx - bWorkspace.idx
|
return aWorkspace.idx - bWorkspace.idx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. If same workspace, sort by actual position within workspace
|
if (a.workspace_id === b.workspace_id && a.layout && b.layout) {
|
||||||
if (a.workspace_id === b.workspace_id
|
|
||||||
&& a.layout && b.layout) {
|
|
||||||
|
|
||||||
// Use pos_in_scrolling_layout [x, y] coordinates
|
if (a.layout.pos_in_scrolling_layout && b.layout.pos_in_scrolling_layout) {
|
||||||
if (a.layout.pos_in_scrolling_layout
|
const aPos = a.layout.pos_in_scrolling_layout
|
||||||
&& b.layout.pos_in_scrolling_layout) {
|
const bPos = b.layout.pos_in_scrolling_layout
|
||||||
var aPos = a.layout.pos_in_scrolling_layout
|
|
||||||
var bPos = b.layout.pos_in_scrolling_layout
|
|
||||||
|
|
||||||
if (aPos.length > 1
|
if (aPos.length > 1 && bPos.length > 1) {
|
||||||
&& bPos.length > 1) {
|
|
||||||
// Sort by X (horizontal) position first
|
|
||||||
if (aPos[0] !== bPos[0]) {
|
if (aPos[0] !== bPos[0]) {
|
||||||
return aPos[0] - bPos[0]
|
return aPos[0] - bPos[0]
|
||||||
}
|
}
|
||||||
// Then sort by Y (vertical) position
|
|
||||||
if (aPos[1] !== bPos[1]) {
|
if (aPos[1] !== bPos[1]) {
|
||||||
return aPos[1] - bPos[1]
|
return aPos[1] - bPos[1]
|
||||||
}
|
}
|
||||||
@@ -168,37 +139,50 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Fallback to window ID for consistent ordering
|
|
||||||
return a.id - b.id
|
return a.id - b.id
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNiriEvent(event) {
|
function handleNiriEvent(event) {
|
||||||
if (event.WorkspacesChanged) {
|
const eventType = Object.keys(event)[0];
|
||||||
handleWorkspacesChanged(event.WorkspacesChanged)
|
|
||||||
} else if (event.WorkspaceActivated) {
|
switch (eventType) {
|
||||||
handleWorkspaceActivated(event.WorkspaceActivated)
|
case 'WorkspacesChanged':
|
||||||
} else if (event.WorkspaceActiveWindowChanged) {
|
handleWorkspacesChanged(event.WorkspacesChanged);
|
||||||
handleWorkspaceActiveWindowChanged(
|
break;
|
||||||
event.WorkspaceActiveWindowChanged)
|
case 'WorkspaceActivated':
|
||||||
} else if (event.WindowsChanged) {
|
handleWorkspaceActivated(event.WorkspaceActivated);
|
||||||
handleWindowsChanged(event.WindowsChanged)
|
break;
|
||||||
} else if (event.WindowClosed) {
|
case 'WorkspaceActiveWindowChanged':
|
||||||
handleWindowClosed(event.WindowClosed)
|
handleWorkspaceActiveWindowChanged(event.WorkspaceActiveWindowChanged);
|
||||||
} else if (event.WindowOpenedOrChanged) {
|
break;
|
||||||
handleWindowOpenedOrChanged(event.WindowOpenedOrChanged)
|
case 'WindowsChanged':
|
||||||
} else if (event.WindowLayoutsChanged) {
|
handleWindowsChanged(event.WindowsChanged);
|
||||||
handleWindowLayoutsChanged(event.WindowLayoutsChanged)
|
break;
|
||||||
} else if (event.OutputsChanged) {
|
case 'WindowClosed':
|
||||||
handleOutputsChanged(event.OutputsChanged)
|
handleWindowClosed(event.WindowClosed);
|
||||||
} else if (event.OverviewOpenedOrClosed) {
|
break;
|
||||||
handleOverviewChanged(event.OverviewOpenedOrClosed)
|
case 'WindowOpenedOrChanged':
|
||||||
} else if (event.ConfigLoaded) {
|
handleWindowOpenedOrChanged(event.WindowOpenedOrChanged);
|
||||||
handleConfigLoaded(event.ConfigLoaded)
|
break;
|
||||||
} else if (event.KeyboardLayoutsChanged) {
|
case 'WindowLayoutsChanged':
|
||||||
handleKeyboardLayoutsChanged(event.KeyboardLayoutsChanged)
|
handleWindowLayoutsChanged(event.WindowLayoutsChanged);
|
||||||
} else if (event.KeyboardLayoutSwitched) {
|
break;
|
||||||
handleKeyboardLayoutSwitched(event.KeyboardLayoutSwitched)
|
case 'OutputsChanged':
|
||||||
|
handleOutputsChanged(event.OutputsChanged);
|
||||||
|
break;
|
||||||
|
case 'OverviewOpenedOrClosed':
|
||||||
|
handleOverviewChanged(event.OverviewOpenedOrClosed);
|
||||||
|
break;
|
||||||
|
case 'ConfigLoaded':
|
||||||
|
handleConfigLoaded(event.ConfigLoaded);
|
||||||
|
break;
|
||||||
|
case 'KeyboardLayoutsChanged':
|
||||||
|
handleKeyboardLayoutsChanged(event.KeyboardLayoutsChanged);
|
||||||
|
break;
|
||||||
|
case 'KeyboardLayoutSwitched':
|
||||||
|
handleKeyboardLayoutSwitched(event.KeyboardLayoutSwitched);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +198,7 @@ Singleton {
|
|||||||
|
|
||||||
focusedWorkspaceIndex = allWorkspaces.findIndex(w => w.is_focused)
|
focusedWorkspaceIndex = allWorkspaces.findIndex(w => w.is_focused)
|
||||||
if (focusedWorkspaceIndex >= 0) {
|
if (focusedWorkspaceIndex >= 0) {
|
||||||
var focusedWs = allWorkspaces[focusedWorkspaceIndex]
|
const focusedWs = allWorkspaces[focusedWorkspaceIndex]
|
||||||
focusedWorkspaceId = focusedWs.id
|
focusedWorkspaceId = focusedWs.id
|
||||||
currentOutput = focusedWs.output || ""
|
currentOutput = focusedWs.output || ""
|
||||||
} else {
|
} else {
|
||||||
@@ -227,8 +211,9 @@ Singleton {
|
|||||||
|
|
||||||
function handleWorkspaceActivated(data) {
|
function handleWorkspaceActivated(data) {
|
||||||
const ws = root.workspaces[data.id]
|
const ws = root.workspaces[data.id]
|
||||||
if (!ws)
|
if (!ws) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
const output = ws.output
|
const output = ws.output
|
||||||
|
|
||||||
for (const id in root.workspaces) {
|
for (const id in root.workspaces) {
|
||||||
@@ -251,23 +236,18 @@ Singleton {
|
|||||||
currentOutput = allWorkspaces[focusedWorkspaceIndex].output || ""
|
currentOutput = allWorkspaces[focusedWorkspaceIndex].output || ""
|
||||||
}
|
}
|
||||||
|
|
||||||
allWorkspaces = Object.values(root.workspaces).sort(
|
allWorkspaces = Object.values(root.workspaces).sort((a, b) => a.idx - b.idx)
|
||||||
(a, b) => a.idx - b.idx)
|
|
||||||
|
|
||||||
updateCurrentOutputWorkspaces()
|
updateCurrentOutputWorkspaces()
|
||||||
workspacesChanged()
|
workspacesChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleWorkspaceActiveWindowChanged(data) {
|
function handleWorkspaceActiveWindowChanged(data) {
|
||||||
// Update the focused window when workspace's active window changes
|
if (data.active_window_id !== null && data.active_window_id !== undefined) {
|
||||||
// This is crucial for handling floating window close scenarios
|
const updatedWindows = []
|
||||||
if (data.active_window_id !== null
|
|
||||||
&& data.active_window_id !== undefined) {
|
|
||||||
// Create new windows array with updated focus states to trigger property change
|
|
||||||
let updatedWindows = []
|
|
||||||
for (var i = 0; i < windows.length; i++) {
|
for (var i = 0; i < windows.length; i++) {
|
||||||
let w = windows[i]
|
const w = windows[i]
|
||||||
let updatedWindow = {}
|
const updatedWindow = {}
|
||||||
for (let prop in w) {
|
for (let prop in w) {
|
||||||
updatedWindow[prop] = w[prop]
|
updatedWindow[prop] = w[prop]
|
||||||
}
|
}
|
||||||
@@ -276,17 +256,14 @@ Singleton {
|
|||||||
}
|
}
|
||||||
windows = updatedWindows
|
windows = updatedWindows
|
||||||
} else {
|
} else {
|
||||||
// No active window in this workspace
|
const updatedWindows = []
|
||||||
// Create new windows array with cleared focus states for this workspace
|
|
||||||
let updatedWindows = []
|
|
||||||
for (var i = 0; i < windows.length; i++) {
|
for (var i = 0; i < windows.length; i++) {
|
||||||
let w = windows[i]
|
const w = windows[i]
|
||||||
let updatedWindow = {}
|
const updatedWindow = {}
|
||||||
for (let prop in w) {
|
for (let prop in w) {
|
||||||
updatedWindow[prop] = w[prop]
|
updatedWindow[prop] = w[prop]
|
||||||
}
|
}
|
||||||
updatedWindow.is_focused = w.workspace_id
|
updatedWindow.is_focused = w.workspace_id == data.workspace_id ? false : w.is_focused
|
||||||
== data.workspace_id ? false : w.is_focused
|
|
||||||
updatedWindows.push(updatedWindow)
|
updatedWindows.push(updatedWindow)
|
||||||
}
|
}
|
||||||
windows = updatedWindows
|
windows = updatedWindows
|
||||||
@@ -302,28 +279,28 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleWindowOpenedOrChanged(data) {
|
function handleWindowOpenedOrChanged(data) {
|
||||||
if (!data.window)
|
if (!data.window) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const window = data.window
|
const window = data.window
|
||||||
const existingIndex = windows.findIndex(w => w.id === window.id)
|
const existingIndex = windows.findIndex(w => w.id === window.id)
|
||||||
|
|
||||||
if (existingIndex >= 0) {
|
if (existingIndex >= 0) {
|
||||||
let updatedWindows = [...windows]
|
const updatedWindows = [...windows]
|
||||||
updatedWindows[existingIndex] = window
|
updatedWindows[existingIndex] = window
|
||||||
windows = sortWindowsByLayout(updatedWindows)
|
windows = sortWindowsByLayout(updatedWindows)
|
||||||
} else {
|
} else {
|
||||||
windows = sortWindowsByLayout([...windows, window])
|
windows = sortWindowsByLayout([...windows, window])
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleWindowLayoutsChanged(data) {
|
function handleWindowLayoutsChanged(data) {
|
||||||
// Update layout positions for windows that have changed
|
if (!data.changes) {
|
||||||
if (!data.changes)
|
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let updatedWindows = [...windows]
|
const updatedWindows = [...windows]
|
||||||
let hasChanges = false
|
let hasChanges = false
|
||||||
|
|
||||||
for (const change of data.changes) {
|
for (const change of data.changes) {
|
||||||
@@ -332,8 +309,7 @@ Singleton {
|
|||||||
|
|
||||||
const windowIndex = updatedWindows.findIndex(w => w.id === windowId)
|
const windowIndex = updatedWindows.findIndex(w => w.id === windowId)
|
||||||
if (windowIndex >= 0) {
|
if (windowIndex >= 0) {
|
||||||
// Create a new object with updated layout
|
const updatedWindow = {}
|
||||||
var updatedWindow = {}
|
|
||||||
for (var prop in updatedWindows[windowIndex]) {
|
for (var prop in updatedWindows[windowIndex]) {
|
||||||
updatedWindow[prop] = updatedWindows[windowIndex][prop]
|
updatedWindow[prop] = updatedWindows[windowIndex][prop]
|
||||||
}
|
}
|
||||||
@@ -345,7 +321,6 @@ Singleton {
|
|||||||
|
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
windows = sortWindowsByLayout(updatedWindows)
|
windows = sortWindowsByLayout(updatedWindows)
|
||||||
// Trigger update in dock and widgets
|
|
||||||
windowsChanged()
|
windowsChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,7 +328,6 @@ Singleton {
|
|||||||
function handleOutputsChanged(data) {
|
function handleOutputsChanged(data) {
|
||||||
if (data.outputs) {
|
if (data.outputs) {
|
||||||
outputs = data.outputs
|
outputs = data.outputs
|
||||||
// Re-sort windows with new monitor positions
|
|
||||||
windows = sortWindowsByLayout(windows)
|
windows = sortWindowsByLayout(windows)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,8 +341,7 @@ Singleton {
|
|||||||
validateProcess.running = true
|
validateProcess.running = true
|
||||||
} else {
|
} else {
|
||||||
configValidationOutput = ""
|
configValidationOutput = ""
|
||||||
if (ToastService.toastVisible
|
if (ToastService.toastVisible && ToastService.currentLevel === ToastService.levelError) {
|
||||||
&& ToastService.currentLevel === ToastService.levelError) {
|
|
||||||
ToastService.hideToast()
|
ToastService.hideToast()
|
||||||
}
|
}
|
||||||
if (hasInitialConnection) {
|
if (hasInitialConnection) {
|
||||||
@@ -398,13 +371,10 @@ Singleton {
|
|||||||
stderr: StdioCollector {
|
stderr: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
const lines = text.split('\n')
|
const lines = text.split('\n')
|
||||||
const trimmedLines = lines.map(line => line.replace(/\s+$/,
|
const trimmedLines = lines.map(line => line.replace(/\s+$/, '')).filter(line => line.length > 0)
|
||||||
'')).filter(
|
|
||||||
line => line.length > 0)
|
|
||||||
configValidationOutput = trimmedLines.join('\n').trim()
|
configValidationOutput = trimmedLines.join('\n').trim()
|
||||||
if (hasInitialConnection) {
|
if (hasInitialConnection) {
|
||||||
ToastService.showError("niri: failed to load config",
|
ToastService.showError("niri: failed to load config", configValidationOutput)
|
||||||
configValidationOutput)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -422,13 +392,14 @@ Singleton {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputWs = allWorkspaces.filter(w => w.output === currentOutput)
|
const outputWs = allWorkspaces.filter(w => w.output === currentOutput)
|
||||||
currentOutputWorkspaces = outputWs
|
currentOutputWorkspaces = outputWs
|
||||||
}
|
}
|
||||||
|
|
||||||
function send(request) {
|
function send(request) {
|
||||||
if (!CompositorService.isNiri || !requestSocket.connected)
|
if (!CompositorService.isNiri || !requestSocket.connected) {
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
requestSocket.write(JSON.stringify(request) + "\n")
|
requestSocket.write(JSON.stringify(request) + "\n")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -444,41 +415,36 @@ Singleton {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function focusWindow(windowId) {
|
function focusWindow(windowId) {
|
||||||
return send({
|
return send({
|
||||||
"Action": {
|
"Action": {
|
||||||
"FocusWindow": {
|
"FocusWindow": {
|
||||||
"id": windowId
|
"id": windowId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getCurrentOutputWorkspaceNumbers() {
|
function getCurrentOutputWorkspaceNumbers() {
|
||||||
return currentOutputWorkspaces.map(
|
return currentOutputWorkspaces.map(w => w.idx + 1)
|
||||||
w => w.idx + 1) // niri uses 0-based, UI shows 1-based
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentWorkspaceNumber() {
|
function getCurrentWorkspaceNumber() {
|
||||||
if (focusedWorkspaceIndex >= 0
|
if (focusedWorkspaceIndex >= 0 && focusedWorkspaceIndex < allWorkspaces.length) {
|
||||||
&& focusedWorkspaceIndex < allWorkspaces.length) {
|
|
||||||
return allWorkspaces[focusedWorkspaceIndex].idx + 1
|
return allWorkspaces[focusedWorkspaceIndex].idx + 1
|
||||||
}
|
}
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentKeyboardLayoutName() {
|
function getCurrentKeyboardLayoutName() {
|
||||||
if (currentKeyboardLayoutIndex >= 0
|
if (currentKeyboardLayoutIndex >= 0 && currentKeyboardLayoutIndex < keyboardLayoutNames.length) {
|
||||||
&& currentKeyboardLayoutIndex < keyboardLayoutNames.length) {
|
|
||||||
return keyboardLayoutNames[currentKeyboardLayoutIndex]
|
return keyboardLayoutNames[currentKeyboardLayoutIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function cycleKeyboardLayout() {
|
function cycleKeyboardLayout() {
|
||||||
return send({
|
return send({
|
||||||
"Action": {
|
"Action": {
|
||||||
@@ -499,16 +465,19 @@ Singleton {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function findNiriWindow(toplevel) {
|
function findNiriWindow(toplevel) {
|
||||||
if (!toplevel.appId) return null
|
if (!toplevel.appId) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
for (var j = 0; j < windows.length; j++) {
|
for (var j = 0; j < windows.length; j++) {
|
||||||
var niriWindow = windows[j]
|
const niriWindow = windows[j]
|
||||||
if (niriWindow.app_id === toplevel.appId) {
|
if (niriWindow.app_id === toplevel.appId) {
|
||||||
if (!niriWindow.title || niriWindow.title === toplevel.title) {
|
if (!niriWindow.title || niriWindow.title === toplevel.title) {
|
||||||
return { niriIndex: j, niriWindow: niriWindow }
|
return {
|
||||||
|
"niriIndex": j,
|
||||||
|
"niriWindow": niriWindow
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -519,69 +488,71 @@ Singleton {
|
|||||||
if (!toplevels || toplevels.length === 0 || !CompositorService.isNiri || windows.length === 0) {
|
if (!toplevels || toplevels.length === 0 || !CompositorService.isNiri || windows.length === 0) {
|
||||||
return [...toplevels]
|
return [...toplevels]
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...toplevels].sort((a, b) => {
|
return [...toplevels].sort((a, b) => {
|
||||||
var aNiri = findNiriWindow(a)
|
const aNiri = findNiriWindow(a)
|
||||||
var bNiri = findNiriWindow(b)
|
const bNiri = findNiriWindow(b)
|
||||||
|
|
||||||
if (!aNiri && !bNiri) return 0
|
if (!aNiri && !bNiri) {
|
||||||
if (!aNiri) return 1
|
return 0
|
||||||
if (!bNiri) return -1
|
}
|
||||||
|
if (!aNiri) {
|
||||||
var aWindow = aNiri.niriWindow
|
return 1
|
||||||
var bWindow = bNiri.niriWindow
|
}
|
||||||
var aWorkspace = allWorkspaces.find(ws => ws.id === aWindow.workspace_id)
|
if (!bNiri) {
|
||||||
var bWorkspace = allWorkspaces.find(ws => ws.id === bWindow.workspace_id)
|
return -1
|
||||||
|
}
|
||||||
if (aWorkspace && bWorkspace) {
|
|
||||||
if (aWorkspace.output !== bWorkspace.output) {
|
const aWindow = aNiri.niriWindow
|
||||||
return aWorkspace.output.localeCompare(bWorkspace.output)
|
const bWindow = bNiri.niriWindow
|
||||||
}
|
const aWorkspace = allWorkspaces.find(ws => ws.id === aWindow.workspace_id)
|
||||||
|
const bWorkspace = allWorkspaces.find(ws => ws.id === bWindow.workspace_id)
|
||||||
if (aWorkspace.output === bWorkspace.output && aWorkspace.idx !== bWorkspace.idx) {
|
|
||||||
return aWorkspace.idx - bWorkspace.idx
|
if (aWorkspace && bWorkspace) {
|
||||||
}
|
if (aWorkspace.output !== bWorkspace.output) {
|
||||||
}
|
return aWorkspace.output.localeCompare(bWorkspace.output)
|
||||||
|
}
|
||||||
if (aWindow.workspace_id === bWindow.workspace_id &&
|
|
||||||
aWindow.layout && bWindow.layout &&
|
if (aWorkspace.output === bWorkspace.output && aWorkspace.idx !== bWorkspace.idx) {
|
||||||
aWindow.layout.pos_in_scrolling_layout &&
|
return aWorkspace.idx - bWorkspace.idx
|
||||||
bWindow.layout.pos_in_scrolling_layout) {
|
}
|
||||||
var aPos = aWindow.layout.pos_in_scrolling_layout
|
}
|
||||||
var bPos = bWindow.layout.pos_in_scrolling_layout
|
|
||||||
|
if (aWindow.workspace_id === bWindow.workspace_id && aWindow.layout && bWindow.layout && aWindow.layout.pos_in_scrolling_layout && bWindow.layout.pos_in_scrolling_layout) {
|
||||||
if (aPos.length > 1 && bPos.length > 1) {
|
const aPos = aWindow.layout.pos_in_scrolling_layout
|
||||||
if (aPos[0] !== bPos[0]) {
|
const bPos = bWindow.layout.pos_in_scrolling_layout
|
||||||
return aPos[0] - bPos[0]
|
|
||||||
}
|
if (aPos.length > 1 && bPos.length > 1) {
|
||||||
if (aPos[1] !== bPos[1]) {
|
if (aPos[0] !== bPos[0]) {
|
||||||
return aPos[1] - bPos[1]
|
return aPos[0] - bPos[0]
|
||||||
}
|
}
|
||||||
}
|
if (aPos[1] !== bPos[1]) {
|
||||||
}
|
return aPos[1] - bPos[1]
|
||||||
|
}
|
||||||
return aWindow.id - bWindow.id
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
return aWindow.id - bWindow.id
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterCurrentWorkspace(toplevels, screenName){
|
function filterCurrentWorkspace(toplevels, screenName) {
|
||||||
var currentWorkspaceId = null
|
let currentWorkspaceId = null
|
||||||
for (var i = 0; i < allWorkspaces.length; i++) {
|
for (var i = 0; i < allWorkspaces.length; i++) {
|
||||||
var ws = allWorkspaces[i]
|
const ws = allWorkspaces[i]
|
||||||
if (ws.output === screenName && ws.is_active){
|
if (ws.output === screenName && ws.is_active) {
|
||||||
currentWorkspaceId = ws.id
|
currentWorkspaceId = ws.id
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentWorkspaceId === null) {
|
if (currentWorkspaceId === null) {
|
||||||
return toplevels
|
return toplevels
|
||||||
}
|
}
|
||||||
|
|
||||||
return toplevels.filter(toplevel => {
|
return toplevels.filter(toplevel => {
|
||||||
var niriMatch = findNiriWindow(toplevel)
|
const niriMatch = findNiriWindow(toplevel)
|
||||||
return niriMatch && niriMatch.niriWindow.workspace_id === currentWorkspaceId
|
return niriMatch && niriMatch.niriWindow.workspace_id === currentWorkspaceId
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -13,8 +14,7 @@ Singleton {
|
|||||||
|
|
||||||
readonly property list<NotifWrapper> notifications: []
|
readonly property list<NotifWrapper> notifications: []
|
||||||
readonly property list<NotifWrapper> allWrappers: []
|
readonly property list<NotifWrapper> allWrappers: []
|
||||||
readonly property list<NotifWrapper> popups: allWrappers.filter(
|
readonly property list<NotifWrapper> popups: allWrappers.filter(n => n && n.popup)
|
||||||
n => n && n.popup)
|
|
||||||
|
|
||||||
property list<NotifWrapper> notificationQueue: []
|
property list<NotifWrapper> notificationQueue: []
|
||||||
property list<NotifWrapper> visibleNotifications: []
|
property list<NotifWrapper> visibleNotifications: []
|
||||||
@@ -34,14 +34,19 @@ Singleton {
|
|||||||
property int _dismissBatchSize: 8
|
property int _dismissBatchSize: 8
|
||||||
property int _dismissTickMs: 8
|
property int _dismissTickMs: 8
|
||||||
property bool _suspendGrouping: false
|
property bool _suspendGrouping: false
|
||||||
property var _groupCache: ({"notifications": [], "popups": []})
|
property var _groupCache: ({
|
||||||
|
"notifications": [],
|
||||||
|
"popups": []
|
||||||
|
})
|
||||||
property bool _groupsDirty: false
|
property bool _groupsDirty: false
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
_recomputeGroups()
|
_recomputeGroups()
|
||||||
}
|
}
|
||||||
|
|
||||||
function _nowSec() { return Date.now() / 1000.0 }
|
function _nowSec() {
|
||||||
|
return Date.now() / 1000.0
|
||||||
|
}
|
||||||
|
|
||||||
function _ingressAllowed(notif) {
|
function _ingressAllowed(notif) {
|
||||||
const t = _nowSec()
|
const t = _nowSec()
|
||||||
@@ -50,22 +55,26 @@ Singleton {
|
|||||||
_ingressCountThisSec = 0
|
_ingressCountThisSec = 0
|
||||||
}
|
}
|
||||||
_ingressCountThisSec += 1
|
_ingressCountThisSec += 1
|
||||||
if (notif.urgency === NotificationUrgency.Critical)
|
if (notif.urgency === NotificationUrgency.Critical) {
|
||||||
return true
|
return true
|
||||||
|
}
|
||||||
return _ingressCountThisSec <= maxIngressPerSecond
|
return _ingressCountThisSec <= maxIngressPerSecond
|
||||||
}
|
}
|
||||||
|
|
||||||
function _enqueuePopup(wrapper) {
|
function _enqueuePopup(wrapper) {
|
||||||
if (notificationQueue.length >= maxQueueSize) {
|
if (notificationQueue.length >= maxQueueSize) {
|
||||||
const gk = getGroupKey(wrapper)
|
const gk = getGroupKey(wrapper)
|
||||||
let idx = notificationQueue.findIndex(w =>
|
let idx = notificationQueue.findIndex(w => w && getGroupKey(w) === gk && w.urgency !== NotificationUrgency.Critical)
|
||||||
w && getGroupKey(w) === gk && w.urgency !== NotificationUrgency.Critical)
|
|
||||||
if (idx === -1) {
|
if (idx === -1) {
|
||||||
idx = notificationQueue.findIndex(w => w && w.urgency !== NotificationUrgency.Critical)
|
idx = notificationQueue.findIndex(w => w && w.urgency !== NotificationUrgency.Critical)
|
||||||
}
|
}
|
||||||
if (idx === -1) idx = 0
|
if (idx === -1) {
|
||||||
|
idx = 0
|
||||||
|
}
|
||||||
const victim = notificationQueue[idx]
|
const victim = notificationQueue[idx]
|
||||||
if (victim) victim.popup = false
|
if (victim) {
|
||||||
|
victim.popup = false
|
||||||
|
}
|
||||||
notificationQueue.splice(idx, 1)
|
notificationQueue.splice(idx, 1)
|
||||||
}
|
}
|
||||||
notificationQueue = [...notificationQueue, wrapper]
|
notificationQueue = [...notificationQueue, wrapper]
|
||||||
@@ -80,18 +89,26 @@ Singleton {
|
|||||||
function _trimStored() {
|
function _trimStored() {
|
||||||
if (notifications.length > maxStoredNotifications) {
|
if (notifications.length > maxStoredNotifications) {
|
||||||
const overflow = notifications.length - maxStoredNotifications
|
const overflow = notifications.length - maxStoredNotifications
|
||||||
let toDrop = []
|
const toDrop = []
|
||||||
for (let i = notifications.length - 1; i >= 0 && toDrop.length < overflow; --i) {
|
for (var i = notifications.length - 1; i >= 0 && toDrop.length < overflow; --i) {
|
||||||
const w = notifications[i]
|
const w = notifications[i]
|
||||||
if (w && w.notification && w.urgency !== NotificationUrgency.Critical)
|
if (w && w.notification && w.urgency !== NotificationUrgency.Critical) {
|
||||||
toDrop.push(w)
|
toDrop.push(w)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (let i = notifications.length - 1; i >= 0 && toDrop.length < overflow; --i) {
|
for (var i = notifications.length - 1; i >= 0 && toDrop.length < overflow; --i) {
|
||||||
const w = notifications[i]
|
const w = notifications[i]
|
||||||
if (w && w.notification && toDrop.indexOf(w) === -1)
|
if (w && w.notification && toDrop.indexOf(w) === -1) {
|
||||||
toDrop.push(w)
|
toDrop.push(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const w of toDrop) {
|
||||||
|
try {
|
||||||
|
w.notification.dismiss()
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (const w of toDrop) { try { w.notification.dismiss() } catch(e) {} }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,11 +161,15 @@ Singleton {
|
|||||||
running: false
|
running: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
let n = Math.min(_dismissBatchSize, _dismissQueue.length)
|
let n = Math.min(_dismissBatchSize, _dismissQueue.length)
|
||||||
for (let i = 0; i < n; ++i) {
|
for (var i = 0; i < n; ++i) {
|
||||||
const w = _dismissQueue.pop()
|
const w = _dismissQueue.pop()
|
||||||
try {
|
try {
|
||||||
if (w && w.notification) w.notification.dismiss()
|
if (w && w.notification) {
|
||||||
} catch (e) {}
|
w.notification.dismiss()
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (_dismissQueue.length === 0) {
|
if (_dismissQueue.length === 0) {
|
||||||
dismissPump.stop()
|
dismissPump.stop()
|
||||||
@@ -195,7 +216,11 @@ Singleton {
|
|||||||
|
|
||||||
if (!_ingressAllowed(notif)) {
|
if (!_ingressAllowed(notif)) {
|
||||||
if (notif.urgency !== NotificationUrgency.Critical) {
|
if (notif.urgency !== NotificationUrgency.Critical) {
|
||||||
try { notif.dismiss() } catch(e) {}
|
try {
|
||||||
|
notif.dismiss()
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,8 +237,8 @@ Singleton {
|
|||||||
_trimStored()
|
_trimStored()
|
||||||
|
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
_initWrapperPersistence(wrapper)
|
_initWrapperPersistence(wrapper)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (shouldShowPopup) {
|
if (shouldShowPopup) {
|
||||||
_enqueuePopup(wrapper)
|
_enqueuePopup(wrapper)
|
||||||
@@ -241,8 +266,9 @@ Singleton {
|
|||||||
|
|
||||||
readonly property Timer timer: Timer {
|
readonly property Timer timer: Timer {
|
||||||
interval: {
|
interval: {
|
||||||
if (!wrapper.notification)
|
if (!wrapper.notification) {
|
||||||
return 5000
|
return 5000
|
||||||
|
}
|
||||||
|
|
||||||
switch (wrapper.notification.urgency) {
|
switch (wrapper.notification.urgency) {
|
||||||
case NotificationUrgency.Low:
|
case NotificationUrgency.Low:
|
||||||
@@ -273,17 +299,15 @@ Singleton {
|
|||||||
const hours = Math.floor(minutes / 60)
|
const hours = Math.floor(minutes / 60)
|
||||||
|
|
||||||
if (hours < 1) {
|
if (hours < 1) {
|
||||||
if (minutes < 1)
|
if (minutes < 1) {
|
||||||
return "now"
|
return "now"
|
||||||
|
}
|
||||||
return `${minutes}m ago`
|
return `${minutes}m ago`
|
||||||
}
|
}
|
||||||
|
|
||||||
const nowDate = new Date(now.getFullYear(), now.getMonth(),
|
const nowDate = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||||
now.getDate())
|
const timeDate = new Date(time.getFullYear(), time.getMonth(), time.getDate())
|
||||||
const timeDate = new Date(time.getFullYear(), time.getMonth(),
|
const daysDiff = Math.floor((nowDate - timeDate) / (1000 * 60 * 60 * 24))
|
||||||
time.getDate())
|
|
||||||
const daysDiff = Math.floor(
|
|
||||||
(nowDate - timeDate) / (1000 * 60 * 60 * 24))
|
|
||||||
|
|
||||||
if (daysDiff === 0) {
|
if (daysDiff === 0) {
|
||||||
return formatTime(time)
|
return formatTime(time)
|
||||||
@@ -299,8 +323,7 @@ Singleton {
|
|||||||
function formatTime(date) {
|
function formatTime(date) {
|
||||||
let use24Hour = true
|
let use24Hour = true
|
||||||
try {
|
try {
|
||||||
if (typeof SettingsData !== "undefined"
|
if (typeof SettingsData !== "undefined" && SettingsData.use24HourClock !== undefined) {
|
||||||
&& SettingsData.use24HourClock !== undefined) {
|
|
||||||
use24Hour = SettingsData.use24HourClock
|
use24Hour = SettingsData.use24HourClock
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -318,7 +341,9 @@ Singleton {
|
|||||||
readonly property string summary: notification.summary
|
readonly property string summary: notification.summary
|
||||||
readonly property string body: notification.body
|
readonly property string body: notification.body
|
||||||
readonly property string htmlBody: {
|
readonly property string htmlBody: {
|
||||||
if (!popup && !root.popupsDisabled) return ""
|
if (!popup && !root.popupsDisabled) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
if (body && (body.includes('<') && body.includes('>'))) {
|
if (body && (body.includes('<') && body.includes('>'))) {
|
||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
@@ -337,8 +362,9 @@ Singleton {
|
|||||||
readonly property string desktopEntry: notification.desktopEntry
|
readonly property string desktopEntry: notification.desktopEntry
|
||||||
readonly property string image: notification.image
|
readonly property string image: notification.image
|
||||||
readonly property string cleanImage: {
|
readonly property string cleanImage: {
|
||||||
if (!image)
|
if (!image) {
|
||||||
return ""
|
return ""
|
||||||
|
}
|
||||||
if (image.startsWith("file://")) {
|
if (image.startsWith("file://")) {
|
||||||
return image.substring(7)
|
return image.substring(7)
|
||||||
}
|
}
|
||||||
@@ -354,12 +380,12 @@ Singleton {
|
|||||||
root.allWrappers = root.allWrappers.filter(w => w !== wrapper)
|
root.allWrappers = root.allWrappers.filter(w => w !== wrapper)
|
||||||
root.notifications = root.notifications.filter(w => w !== wrapper)
|
root.notifications = root.notifications.filter(w => w !== wrapper)
|
||||||
|
|
||||||
if (root.bulkDismissing)
|
if (root.bulkDismissing) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const groupKey = getGroupKey(wrapper)
|
const groupKey = getGroupKey(wrapper)
|
||||||
const remainingInGroup = root.notifications.filter(
|
const remainingInGroup = root.notifications.filter(n => getGroupKey(n) === groupKey)
|
||||||
n => getGroupKey(n) === groupKey)
|
|
||||||
|
|
||||||
if (remainingInGroup.length <= 1) {
|
if (remainingInGroup.length <= 1) {
|
||||||
clearGroupExpansionState(groupKey)
|
clearGroupExpansionState(groupKey)
|
||||||
@@ -392,20 +418,23 @@ Singleton {
|
|||||||
visibleNotifications = []
|
visibleNotifications = []
|
||||||
|
|
||||||
_dismissQueue = notifications.slice()
|
_dismissQueue = notifications.slice()
|
||||||
if (notifications.length)
|
if (notifications.length) {
|
||||||
notifications = []
|
notifications = []
|
||||||
|
}
|
||||||
expandedGroups = {}
|
expandedGroups = {}
|
||||||
expandedMessages = {}
|
expandedMessages = {}
|
||||||
|
|
||||||
_suspendGrouping = true
|
_suspendGrouping = true
|
||||||
|
|
||||||
if (!dismissPump.running && _dismissQueue.length)
|
if (!dismissPump.running && _dismissQueue.length) {
|
||||||
dismissPump.start()
|
dismissPump.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function dismissNotification(wrapper) {
|
function dismissNotification(wrapper) {
|
||||||
if (!wrapper || !wrapper.notification)
|
if (!wrapper || !wrapper.notification) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
wrapper.popup = false
|
wrapper.popup = false
|
||||||
wrapper.notification.dismiss()
|
wrapper.notification.dismiss()
|
||||||
}
|
}
|
||||||
@@ -422,14 +451,18 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function processQueue() {
|
function processQueue() {
|
||||||
if (addGateBusy)
|
if (addGateBusy) {
|
||||||
return
|
return
|
||||||
if (popupsDisabled)
|
}
|
||||||
|
if (popupsDisabled) {
|
||||||
return
|
return
|
||||||
if (SessionData.doNotDisturb)
|
}
|
||||||
|
if (SessionData.doNotDisturb) {
|
||||||
return
|
return
|
||||||
if (notificationQueue.length === 0)
|
}
|
||||||
|
if (notificationQueue.length === 0) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const activePopupCount = visibleNotifications.filter(n => n && n.popup).length
|
const activePopupCount = visibleNotifications.filter(n => n && n.popup).length
|
||||||
if (activePopupCount >= 4) {
|
if (activePopupCount >= 4) {
|
||||||
@@ -461,10 +494,12 @@ Singleton {
|
|||||||
|
|
||||||
if (w && w.destroy && !w.isPersistent && notifications.indexOf(w) === -1) {
|
if (w && w.destroy && !w.isPersistent && notifications.indexOf(w) === -1) {
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
try {
|
try {
|
||||||
w.destroy()
|
w.destroy()
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
})
|
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,8 +525,9 @@ Singleton {
|
|||||||
|
|
||||||
function _recomputeGroupsLater() {
|
function _recomputeGroupsLater() {
|
||||||
_groupsDirty = true
|
_groupsDirty = true
|
||||||
if (!groupsDebounce.running)
|
if (!groupsDebounce.running) {
|
||||||
groupsDebounce.start()
|
groupsDebounce.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _calcGroupedNotifications() {
|
function _calcGroupedNotifications() {
|
||||||
@@ -520,15 +556,12 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Object.values(groups).sort((a, b) => {
|
return Object.values(groups).sort((a, b) => {
|
||||||
const aUrgency = a.latestNotification.urgency
|
const aUrgency = a.latestNotification.urgency || NotificationUrgency.Low
|
||||||
|| NotificationUrgency.Low
|
const bUrgency = b.latestNotification.urgency || NotificationUrgency.Low
|
||||||
const bUrgency = b.latestNotification.urgency
|
|
||||||
|| NotificationUrgency.Low
|
|
||||||
if (aUrgency !== bUrgency) {
|
if (aUrgency !== bUrgency) {
|
||||||
return bUrgency - aUrgency
|
return bUrgency - aUrgency
|
||||||
}
|
}
|
||||||
return b.latestNotification.time.getTime(
|
return b.latestNotification.time.getTime() - a.latestNotification.time.getTime()
|
||||||
) - a.latestNotification.time.getTime()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,8 +591,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Object.values(groups).sort((a, b) => {
|
return Object.values(groups).sort((a, b) => {
|
||||||
return b.latestNotification.time.getTime(
|
return b.latestNotification.time.getTime() - a.latestNotification.time.getTime()
|
||||||
) - a.latestNotification.time.getTime()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,8 +614,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const notif of allWrappers) {
|
for (const notif of allWrappers) {
|
||||||
if (notif && notif.notification && getGroupKey(
|
if (notif && notif.notification && getGroupKey(notif) === groupKey) {
|
||||||
notif) === groupKey) {
|
|
||||||
notif.notification.dismiss()
|
notif.notification.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -617,8 +648,7 @@ Singleton {
|
|||||||
expandedGroups = newExpandedGroups
|
expandedGroups = newExpandedGroups
|
||||||
let newExpandedMessages = {}
|
let newExpandedMessages = {}
|
||||||
for (const messageId in expandedMessages) {
|
for (const messageId in expandedMessages) {
|
||||||
if (currentMessageIds.has(messageId)
|
if (currentMessageIds.has(messageId) && expandedMessages[messageId]) {
|
||||||
&& expandedMessages[messageId]) {
|
|
||||||
newExpandedMessages[messageId] = true
|
newExpandedMessages[messageId] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -14,9 +15,7 @@ Singleton {
|
|||||||
property bool settingsPortalAvailable: false
|
property bool settingsPortalAvailable: false
|
||||||
property int systemColorScheme: 0 // 0=default, 1=prefer-dark, 2=prefer-light
|
property int systemColorScheme: 0 // 0=default, 1=prefer-dark, 2=prefer-light
|
||||||
|
|
||||||
function init() {
|
function init() {}
|
||||||
// Stub just to force IPC registration
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSystemProfileImage() {
|
function getSystemProfileImage() {
|
||||||
systemProfileCheckProcess.running = true
|
systemProfileCheckProcess.running = true
|
||||||
@@ -40,22 +39,23 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setSystemColorScheme(isLightMode) {
|
function setSystemColorScheme(isLightMode) {
|
||||||
if (!settingsPortalAvailable)
|
if (!settingsPortalAvailable) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var colorScheme = isLightMode ? "prefer-light" : "prefer-dark"
|
const colorScheme = isLightMode ? "prefer-light" : "prefer-dark"
|
||||||
var script = "gsettings set org.gnome.desktop.interface color-scheme '" + colorScheme + "'"
|
const script = `gsettings set org.gnome.desktop.interface color-scheme '${colorScheme}'`
|
||||||
|
|
||||||
systemColorSchemeSetProcess.command = ["bash", "-c", script]
|
systemColorSchemeSetProcess.command = ["bash", "-c", script]
|
||||||
systemColorSchemeSetProcess.running = true
|
systemColorSchemeSetProcess.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSystemProfileImage(imagePath) {
|
function setSystemProfileImage(imagePath) {
|
||||||
if (!accountsServiceAvailable || !imagePath)
|
if (!accountsServiceAvailable || !imagePath) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var script = ["dbus-send --system --print-reply --dest=org.freedesktop.Accounts", "/org/freedesktop/Accounts/User$(id -u)", "org.freedesktop.Accounts.User.SetIconFile", "string:'" + imagePath + "'"].join(
|
const script = `dbus-send --system --print-reply --dest=org.freedesktop.Accounts /org/freedesktop/Accounts/User$(id -u) org.freedesktop.Accounts.User.SetIconFile string:'${imagePath}'`
|
||||||
" ")
|
|
||||||
|
|
||||||
systemProfileSetProcess.command = ["bash", "-c", script]
|
systemProfileSetProcess.command = ["bash", "-c", script]
|
||||||
systemProfileSetProcess.running = true
|
systemProfileSetProcess.running = true
|
||||||
@@ -94,9 +94,8 @@ Singleton {
|
|||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
var match = text.match(/string\s+"([^"]+)"/)
|
const match = text.match(/string\s+"([^"]+)"/)
|
||||||
if (match && match[1] && match[1] !== ""
|
if (match && match[1] && match[1] !== "" && match[1] !== "/var/lib/AccountsService/icons/") {
|
||||||
&& match[1] !== "/var/lib/AccountsService/icons/") {
|
|
||||||
root.systemProfileImage = match[1]
|
root.systemProfileImage = match[1]
|
||||||
|
|
||||||
if (!root.profileImage || root.profileImage === "") {
|
if (!root.profileImage || root.profileImage === "") {
|
||||||
@@ -144,12 +143,12 @@ Singleton {
|
|||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
var match = text.match(/uint32 (\d+)/)
|
const match = text.match(/uint32 (\d+)/)
|
||||||
if (match && match[1]) {
|
if (match && match[1]) {
|
||||||
root.systemColorScheme = parseInt(match[1])
|
root.systemColorScheme = parseInt(match[1])
|
||||||
|
|
||||||
if (typeof Theme !== "undefined") {
|
if (typeof Theme !== "undefined") {
|
||||||
var shouldBeLightMode = (root.systemColorScheme === 2)
|
const shouldBeLightMode = (root.systemColorScheme === 2)
|
||||||
if (Theme.isLightMode !== shouldBeLightMode) {
|
if (Theme.isLightMode !== shouldBeLightMode) {
|
||||||
Theme.isLightMode = shouldBeLightMode
|
Theme.isLightMode = shouldBeLightMode
|
||||||
if (typeof SessionData !== "undefined") {
|
if (typeof SessionData !== "undefined") {
|
||||||
@@ -193,9 +192,7 @@ Singleton {
|
|||||||
return "ERROR: No path provided"
|
return "ERROR: No path provided"
|
||||||
}
|
}
|
||||||
|
|
||||||
var absolutePath = path.startsWith(
|
const absolutePath = path.startsWith("/") ? path : `${StandardPaths.writableLocation(StandardPaths.HomeLocation)}/${path}`
|
||||||
"/") ? path : StandardPaths.writableLocation(
|
|
||||||
StandardPaths.HomeLocation) + "/" + path
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
root.setProfileImage(absolutePath)
|
root.setProfileImage(absolutePath)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -10,13 +11,15 @@ Singleton {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property bool microphoneActive: {
|
readonly property bool microphoneActive: {
|
||||||
if (!Pipewire.ready || !Pipewire.nodes?.values)
|
if (!Pipewire.ready || !Pipewire.nodes?.values) {
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < Pipewire.nodes.values.length; i++) {
|
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
|
||||||
const node = Pipewire.nodes.values[i]
|
const node = Pipewire.nodes.values[i]
|
||||||
if (!node)
|
if (!node) {
|
||||||
continue
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if ((node.type & PwNodeType.AudioInStream) === PwNodeType.AudioInStream) {
|
if ((node.type & PwNodeType.AudioInStream) === PwNodeType.AudioInStream) {
|
||||||
if (!looksLikeSystemVirtualMic(node)) {
|
if (!looksLikeSystemVirtualMic(node)) {
|
||||||
@@ -32,22 +35,21 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PwObjectTracker {
|
PwObjectTracker {
|
||||||
objects: Pipewire.nodes.values.filter(
|
objects: Pipewire.nodes.values.filter(node => node.audio && !node.isStream)
|
||||||
node => node.audio && !node.isStream
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool cameraActive: {
|
readonly property bool cameraActive: {
|
||||||
if (!Pipewire.ready || !Pipewire.nodes?.values)
|
if (!Pipewire.ready || !Pipewire.nodes?.values) {
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < Pipewire.nodes.values.length; i++) {
|
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
|
||||||
const node = Pipewire.nodes.values[i]
|
const node = Pipewire.nodes.values[i]
|
||||||
if (!node || !node.ready)
|
if (!node || !node.ready) {
|
||||||
continue
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if (node.properties
|
if (node.properties && node.properties["media.class"] === "Stream/Input/Video") {
|
||||||
&& node.properties["media.class"] === "Stream/Input/Video") {
|
|
||||||
if (node.properties["stream.is-live"] === "true") {
|
if (node.properties["stream.is-live"] === "true") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -57,13 +59,15 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool screensharingActive: {
|
readonly property bool screensharingActive: {
|
||||||
if (!Pipewire.ready || !Pipewire.nodes?.values)
|
if (!Pipewire.ready || !Pipewire.nodes?.values) {
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < Pipewire.nodes.values.length; i++) {
|
for (let i = 0; i < Pipewire.nodes.values.length; i++) {
|
||||||
const node = Pipewire.nodes.values[i]
|
const node = Pipewire.nodes.values[i]
|
||||||
if (!node || !node.ready)
|
if (!node || !node.ready) {
|
||||||
continue
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if ((node.type & PwNodeType.VideoSource) === PwNodeType.VideoSource) {
|
if ((node.type & PwNodeType.VideoSource) === PwNodeType.VideoSource) {
|
||||||
if (looksLikeScreencast(node)) {
|
if (looksLikeScreencast(node)) {
|
||||||
@@ -71,15 +75,11 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.properties
|
if (node.properties && node.properties["media.class"] === "Stream/Input/Audio") {
|
||||||
&& node.properties["media.class"] === "Stream/Input/Audio") {
|
const mediaName = (node.properties["media.name"] || "").toLowerCase()
|
||||||
const mediaName = (node.properties["media.name"]
|
const appName = (node.properties["application.name"] || "").toLowerCase()
|
||||||
|| "").toLowerCase()
|
|
||||||
const appName = (node.properties["application.name"]
|
|
||||||
|| "").toLowerCase()
|
|
||||||
|
|
||||||
if (mediaName.includes("desktop") || appName.includes("screen")
|
if (mediaName.includes("desktop") || appName.includes("screen") || appName === "obs") {
|
||||||
|| appName === "obs") {
|
|
||||||
if (node.properties["stream.is-live"] === "true") {
|
if (node.properties["stream.is-live"] === "true") {
|
||||||
if (node.audio && node.audio.muted) {
|
if (node.audio && node.audio.muted) {
|
||||||
return false
|
return false
|
||||||
@@ -92,30 +92,27 @@ Singleton {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool anyPrivacyActive: microphoneActive || cameraActive
|
readonly property bool anyPrivacyActive: microphoneActive || cameraActive || screensharingActive
|
||||||
|| screensharingActive
|
|
||||||
|
|
||||||
function looksLikeSystemVirtualMic(node) {
|
function looksLikeSystemVirtualMic(node) {
|
||||||
if (!node)
|
if (!node) {
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
const name = (node.name || "").toLowerCase()
|
const name = (node.name || "").toLowerCase()
|
||||||
const mediaName = (node.properties && node.properties["media.name"]
|
const mediaName = (node.properties && node.properties["media.name"] || "").toLowerCase()
|
||||||
|| "").toLowerCase()
|
const appName = (node.properties && node.properties["application.name"] || "").toLowerCase()
|
||||||
const appName = (node.properties && node.properties["application.name"]
|
|
||||||
|| "").toLowerCase()
|
|
||||||
const combined = name + " " + mediaName + " " + appName
|
const combined = name + " " + mediaName + " " + appName
|
||||||
return /cava|monitor|system/.test(combined)
|
return /cava|monitor|system/.test(combined)
|
||||||
}
|
}
|
||||||
|
|
||||||
function looksLikeScreencast(node) {
|
function looksLikeScreencast(node) {
|
||||||
if (!node)
|
if (!node) {
|
||||||
return false
|
return false
|
||||||
const appName = (node.properties && node.properties["application.name"]
|
}
|
||||||
|| "").toLowerCase()
|
const appName = (node.properties && node.properties["application.name"] || "").toLowerCase()
|
||||||
const nodeName = (node.name || "").toLowerCase()
|
const nodeName = (node.name || "").toLowerCase()
|
||||||
const combined = appName + " " + nodeName
|
const combined = appName + " " + nodeName
|
||||||
return /xdg-desktop-portal|xdpw|screencast|screen|gnome shell|kwin|obs/.test(
|
return /xdg-desktop-portal|xdpw|screencast|screen|gnome shell|kwin|obs/.test(combined)
|
||||||
combined)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMicrophoneStatus() {
|
function getMicrophoneStatus() {
|
||||||
@@ -132,14 +129,16 @@ Singleton {
|
|||||||
|
|
||||||
function getPrivacySummary() {
|
function getPrivacySummary() {
|
||||||
const active = []
|
const active = []
|
||||||
if (microphoneActive)
|
if (microphoneActive) {
|
||||||
active.push("microphone")
|
active.push("microphone")
|
||||||
if (cameraActive)
|
}
|
||||||
|
if (cameraActive) {
|
||||||
active.push("camera")
|
active.push("camera")
|
||||||
if (screensharingActive)
|
}
|
||||||
|
if (screensharingActive) {
|
||||||
active.push("screensharing")
|
active.push("screensharing")
|
||||||
|
}
|
||||||
|
|
||||||
return active.length > 0 ? "Privacy active: " + active.join(
|
return active.length > 0 ? `Privacy active: ${active.join(", ")}` : "No privacy concerns detected"
|
||||||
", ") : "No privacy concerns detected"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -41,11 +42,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ! TODO - hacky because uwsm doesnt behave as expected
|
|
||||||
// uwsm idk, always passes the is-active check even if it's not a session
|
|
||||||
// It reutrns exit code 0 when uwsm stop fails
|
|
||||||
// They have flaws in their system, so we need to be hacky to just try it and
|
|
||||||
// detect random text
|
|
||||||
Process {
|
Process {
|
||||||
id: uwsmLogout
|
id: uwsmLogout
|
||||||
command: ["uwsm", "stop"]
|
command: ["uwsm", "stop"]
|
||||||
@@ -53,14 +49,14 @@ Singleton {
|
|||||||
|
|
||||||
stdout: SplitParser {
|
stdout: SplitParser {
|
||||||
splitMarker: "\n"
|
splitMarker: "\n"
|
||||||
onRead: (data) => {
|
onRead: data => {
|
||||||
if (data.trim().toLowerCase().includes("not running")) {
|
if (data.trim().toLowerCase().includes("not running")) {
|
||||||
_logout()
|
_logout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onExited: function(exitCode) {
|
onExited: function (exitCode) {
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -69,8 +65,9 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
if (hasUwsm)
|
if (hasUwsm) {
|
||||||
uwsmLogout.running = true
|
uwsmLogout.running = true
|
||||||
|
}
|
||||||
_logout()
|
_logout()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,15 +97,17 @@ Singleton {
|
|||||||
signal inhibitorChanged
|
signal inhibitorChanged
|
||||||
|
|
||||||
function enableIdleInhibit() {
|
function enableIdleInhibit() {
|
||||||
if (idleInhibited)
|
if (idleInhibited) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
idleInhibited = true
|
idleInhibited = true
|
||||||
inhibitorChanged()
|
inhibitorChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableIdleInhibit() {
|
function disableIdleInhibit() {
|
||||||
if (!idleInhibited)
|
if (!idleInhibited) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
idleInhibited = false
|
idleInhibited = false
|
||||||
inhibitorChanged()
|
inhibitorChanged()
|
||||||
}
|
}
|
||||||
@@ -129,8 +128,9 @@ Singleton {
|
|||||||
idleInhibited = false
|
idleInhibited = false
|
||||||
|
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
if (wasActive)
|
if (wasActive) {
|
||||||
idleInhibited = true
|
idleInhibited = true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,16 +143,14 @@ Singleton {
|
|||||||
return ["true"]
|
return ["true"]
|
||||||
}
|
}
|
||||||
|
|
||||||
return [isElogind ? "elogind-inhibit" : "systemd-inhibit", "--what=idle", "--who=quickshell", "--why="
|
return [isElogind ? "elogind-inhibit" : "systemd-inhibit", "--what=idle", "--who=quickshell", `--why=${inhibitReason}`, "--mode=block", "sleep", "infinity"]
|
||||||
+ inhibitReason, "--mode=block", "sleep", "infinity"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
running: idleInhibited
|
running: idleInhibited
|
||||||
|
|
||||||
onExited: function (exitCode) {
|
onExited: function (exitCode) {
|
||||||
if (idleInhibited && exitCode !== 0) {
|
if (idleInhibited && exitCode !== 0) {
|
||||||
console.warn("SessionService: Inhibitor process crashed with exit code:",
|
console.warn("SessionService: Inhibitor process crashed with exit code:", exitCode)
|
||||||
exitCode)
|
|
||||||
idleInhibited = false
|
idleInhibited = false
|
||||||
ToastService.showWarning("Idle inhibitor failed")
|
ToastService.showWarning("Idle inhibitor failed")
|
||||||
}
|
}
|
||||||
@@ -181,11 +179,11 @@ Singleton {
|
|||||||
|
|
||||||
function reason(newReason: string): string {
|
function reason(newReason: string): string {
|
||||||
if (!newReason) {
|
if (!newReason) {
|
||||||
return "Current reason: " + root.inhibitReason
|
return `Current reason: ${root.inhibitReason}`
|
||||||
}
|
}
|
||||||
|
|
||||||
root.setInhibitReason(newReason)
|
root.setInhibitReason(newReason)
|
||||||
return "Inhibit reason set to: " + newReason
|
return `Inhibit reason set to: ${newReason}`
|
||||||
}
|
}
|
||||||
|
|
||||||
target: "inhibit"
|
target: "inhibit"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -24,8 +25,9 @@ Singleton {
|
|||||||
"level": level,
|
"level": level,
|
||||||
"details": details
|
"details": details
|
||||||
})
|
})
|
||||||
if (!toastVisible)
|
if (!toastVisible) {
|
||||||
processQueue()
|
processQueue()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showInfo(message, details = "") {
|
function showInfo(message, details = "") {
|
||||||
@@ -48,13 +50,15 @@ Singleton {
|
|||||||
currentLevel = levelInfo
|
currentLevel = levelInfo
|
||||||
toastTimer.stop()
|
toastTimer.stop()
|
||||||
resetToastState()
|
resetToastState()
|
||||||
if (toastQueue.length > 0)
|
if (toastQueue.length > 0) {
|
||||||
processQueue()
|
processQueue()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processQueue() {
|
function processQueue() {
|
||||||
if (toastQueue.length === 0)
|
if (toastQueue.length === 0) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const toast = toastQueue.shift()
|
const toast = toastQueue.shift()
|
||||||
currentMessage = toast.message
|
currentMessage = toast.message
|
||||||
@@ -68,8 +72,7 @@ Singleton {
|
|||||||
toastTimer.interval = 8000
|
toastTimer.interval = 8000
|
||||||
toastTimer.start()
|
toastTimer.start()
|
||||||
} else {
|
} else {
|
||||||
toastTimer.interval = toast.level
|
toastTimer.interval = toast.level === levelError ? 5000 : toast.level === levelWarn ? 4000 : 3000
|
||||||
=== levelError ? 5000 : toast.level === levelWarn ? 4000 : 3000
|
|
||||||
toastTimer.start()
|
toastTimer.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -33,7 +34,6 @@ Singleton {
|
|||||||
getUptime()
|
getUptime()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get username and full name
|
|
||||||
Process {
|
Process {
|
||||||
id: userInfoProcess
|
id: userInfoProcess
|
||||||
|
|
||||||
@@ -60,7 +60,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get system uptime
|
|
||||||
Process {
|
Process {
|
||||||
id: uptimeProcess
|
id: uptimeProcess
|
||||||
|
|
||||||
@@ -81,17 +80,21 @@ Singleton {
|
|||||||
const minutes = Math.floor((seconds % 3600) / 60)
|
const minutes = Math.floor((seconds % 3600) / 60)
|
||||||
|
|
||||||
const parts = []
|
const parts = []
|
||||||
if (days > 0)
|
if (days > 0) {
|
||||||
parts.push(`${days} day${days === 1 ? "" : "s"}`)
|
parts.push(`${days} day${days === 1 ? "" : "s"}`)
|
||||||
if (hours > 0)
|
}
|
||||||
parts.push(`${hours} hour${hours === 1 ? "" : "s"}`)
|
if (hours > 0) {
|
||||||
if (minutes > 0)
|
parts.push(`${hours} hour${hours === 1 ? "" : "s"}`)
|
||||||
parts.push(`${minutes} minute${minutes === 1 ? "" : "s"}`)
|
}
|
||||||
|
if (minutes > 0) {
|
||||||
|
parts.push(`${minutes} minute${minutes === 1 ? "" : "s"}`)
|
||||||
|
}
|
||||||
|
|
||||||
if (parts.length > 0)
|
if (parts.length > 0) {
|
||||||
root.uptime = "up " + parts.join(", ")
|
root.uptime = `up ${parts.join(", ")}`
|
||||||
else
|
} else {
|
||||||
root.uptime = `up ${seconds} seconds`
|
root.uptime = `up ${seconds} seconds`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -34,7 +35,6 @@ Singleton {
|
|||||||
property int minFetchInterval: 30000 // 30 seconds minimum between fetches
|
property int minFetchInterval: 30000 // 30 seconds minimum between fetches
|
||||||
property int persistentRetryCount: 0 // Track persistent retry attempts for backoff
|
property int persistentRetryCount: 0 // Track persistent retry attempts for backoff
|
||||||
|
|
||||||
// Weather icon mapping (based on wttr.in weather codes)
|
|
||||||
property var weatherIcons: ({
|
property var weatherIcons: ({
|
||||||
"113": "clear_day",
|
"113": "clear_day",
|
||||||
"116": "partly_cloudy_day",
|
"116": "partly_cloudy_day",
|
||||||
@@ -105,9 +105,7 @@ Singleton {
|
|||||||
function addRef() {
|
function addRef() {
|
||||||
refCount++
|
refCount++
|
||||||
|
|
||||||
if (refCount === 1 && !weather.available
|
if (refCount === 1 && !weather.available && SettingsData.weatherEnabled) {
|
||||||
&& SettingsData.weatherEnabled) {
|
|
||||||
// Start fetching when first consumer appears and weather is enabled
|
|
||||||
fetchWeather()
|
fetchWeather()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,7 +115,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function fetchWeather() {
|
function fetchWeather() {
|
||||||
// Only fetch if someone is consuming the data and weather is enabled
|
|
||||||
if (root.refCount === 0 || !SettingsData.weatherEnabled) {
|
if (root.refCount === 0 || !SettingsData.weatherEnabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -127,7 +124,6 @@ Singleton {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we've fetched recently to prevent spam
|
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
if (now - root.lastFetchTime < root.minFetchInterval) {
|
if (now - root.lastFetchTime < root.minFetchInterval) {
|
||||||
console.log("Weather fetch throttled, too soon since last fetch")
|
console.log("Weather fetch throttled, too soon since last fetch")
|
||||||
@@ -137,9 +133,7 @@ Singleton {
|
|||||||
console.log("Fetching weather from:", getWeatherUrl())
|
console.log("Fetching weather from:", getWeatherUrl())
|
||||||
root.lastFetchTime = now
|
root.lastFetchTime = now
|
||||||
root.weather.loading = true
|
root.weather.loading = true
|
||||||
weatherFetcher.command
|
weatherFetcher.command = ["bash", "-c", `curl -s --connect-timeout 10 --max-time 30 '${getWeatherUrl()}'`]
|
||||||
= ["bash", "-c", `curl -s --connect-timeout 10 --max-time 30 '${getWeatherUrl(
|
|
||||||
)}'`]
|
|
||||||
weatherFetcher.running = true
|
weatherFetcher.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,12 +145,10 @@ Singleton {
|
|||||||
|
|
||||||
function handleWeatherSuccess() {
|
function handleWeatherSuccess() {
|
||||||
root.retryAttempts = 0
|
root.retryAttempts = 0
|
||||||
root.persistentRetryCount = 0 // Reset persistent retry count on success
|
root.persistentRetryCount = 0
|
||||||
// Stop any persistent retry timer if running
|
|
||||||
if (persistentRetryTimer.running) {
|
if (persistentRetryTimer.running) {
|
||||||
persistentRetryTimer.stop()
|
persistentRetryTimer.stop()
|
||||||
}
|
}
|
||||||
// Don't restart the timer - let it continue its normal interval
|
|
||||||
if (updateTimer.interval !== root.updateInterval) {
|
if (updateTimer.interval !== root.updateInterval) {
|
||||||
updateTimer.interval = root.updateInterval
|
updateTimer.interval = root.updateInterval
|
||||||
}
|
}
|
||||||
@@ -165,18 +157,14 @@ Singleton {
|
|||||||
function handleWeatherFailure() {
|
function handleWeatherFailure() {
|
||||||
root.retryAttempts++
|
root.retryAttempts++
|
||||||
if (root.retryAttempts < root.maxRetryAttempts) {
|
if (root.retryAttempts < root.maxRetryAttempts) {
|
||||||
console.log(`Weather fetch failed, retrying in ${root.retryDelay
|
console.log(`Weather fetch failed, retrying in ${root.retryDelay / 1000}s (attempt ${root.retryAttempts}/${root.maxRetryAttempts})`)
|
||||||
/ 1000}s (attempt ${root.retryAttempts}/${root.maxRetryAttempts})`)
|
|
||||||
retryTimer.start()
|
retryTimer.start()
|
||||||
} else {
|
} else {
|
||||||
console.warn("Weather fetch failed after maximum retry attempts, will keep trying...")
|
console.warn("Weather fetch failed after maximum retry attempts, will keep trying...")
|
||||||
root.weather.available = false
|
root.weather.available = false
|
||||||
root.weather.loading = false
|
root.weather.loading = false
|
||||||
// Reset retry count but keep trying with exponential backoff
|
|
||||||
root.retryAttempts = 0
|
root.retryAttempts = 0
|
||||||
// Use exponential backoff: 1min, 2min, 4min, then cap at 5min
|
const backoffDelay = Math.min(60000 * Math.pow(2, persistentRetryCount), 300000)
|
||||||
const backoffDelay = Math.min(60000 * Math.pow(
|
|
||||||
2, persistentRetryCount), 300000)
|
|
||||||
persistentRetryCount++
|
persistentRetryCount++
|
||||||
console.log(`Scheduling persistent retry in ${backoffDelay / 1000}s`)
|
console.log(`Scheduling persistent retry in ${backoffDelay / 1000}s`)
|
||||||
persistentRetryTimer.interval = backoffDelay
|
persistentRetryTimer.interval = backoffDelay
|
||||||
@@ -186,8 +174,7 @@ Singleton {
|
|||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: weatherFetcher
|
id: weatherFetcher
|
||||||
command: ["bash", "-c", `curl -s --connect-timeout 10 --max-time 30 '${root.getWeatherUrl(
|
command: ["bash", "-c", `curl -s --connect-timeout 10 --max-time 30 '${root.getWeatherUrl()}'`]
|
||||||
)}'`]
|
|
||||||
running: false
|
running: false
|
||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
@@ -206,8 +193,7 @@ Singleton {
|
|||||||
const location = data.nearest_area[0] || {}
|
const location = data.nearest_area[0] || {}
|
||||||
const astronomy = data.weather[0]?.astronomy[0] || {}
|
const astronomy = data.weather[0]?.astronomy[0] || {}
|
||||||
|
|
||||||
if (!Object.keys(current).length || !Object.keys(
|
if (!Object.keys(current).length || !Object.keys(location).length) {
|
||||||
location).length) {
|
|
||||||
throw new Error("Required fields missing")
|
throw new Error("Required fields missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,8 +212,7 @@ Singleton {
|
|||||||
"pressure": Number(current.pressure) || 0
|
"pressure": Number(current.pressure) || 0
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Weather updated:", root.weather.city,
|
console.log("Weather updated:", root.weather.city, `${root.weather.temp}°C`)
|
||||||
`${root.weather.temp}°C`)
|
|
||||||
|
|
||||||
root.handleWeatherSuccess()
|
root.handleWeatherSuccess()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -268,7 +253,7 @@ Singleton {
|
|||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: persistentRetryTimer
|
id: persistentRetryTimer
|
||||||
interval: 60000 // Will be dynamically set
|
interval: 60000
|
||||||
running: false
|
running: false
|
||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
@@ -279,8 +264,7 @@ Singleton {
|
|||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
SettingsData.weatherCoordinatesChanged.connect(() => {
|
SettingsData.weatherCoordinatesChanged.connect(() => {
|
||||||
console.log(
|
console.log("Weather location changed, force refreshing weather")
|
||||||
"Weather location changed, force refreshing weather")
|
|
||||||
root.weather = {
|
root.weather = {
|
||||||
"available": false,
|
"available": false,
|
||||||
"loading": true,
|
"loading": true,
|
||||||
@@ -300,16 +284,13 @@ Singleton {
|
|||||||
})
|
})
|
||||||
|
|
||||||
SettingsData.weatherLocationChanged.connect(() => {
|
SettingsData.weatherLocationChanged.connect(() => {
|
||||||
console.log(
|
console.log("Weather location display name changed")
|
||||||
"Weather location display name changed")
|
const currentWeather = Object.assign({}, root.weather)
|
||||||
const currentWeather = Object.assign(
|
|
||||||
{}, root.weather)
|
|
||||||
root.weather = currentWeather
|
root.weather = currentWeather
|
||||||
})
|
})
|
||||||
|
|
||||||
SettingsData.useAutoLocationChanged.connect(() => {
|
SettingsData.useAutoLocationChanged.connect(() => {
|
||||||
console.log(
|
console.log("Auto location setting changed, force refreshing weather")
|
||||||
"Auto location setting changed, force refreshing weather")
|
|
||||||
root.weather = {
|
root.weather = {
|
||||||
"available": false,
|
"available": false,
|
||||||
"loading": true,
|
"loading": true,
|
||||||
@@ -329,16 +310,10 @@ Singleton {
|
|||||||
})
|
})
|
||||||
|
|
||||||
SettingsData.weatherEnabledChanged.connect(() => {
|
SettingsData.weatherEnabledChanged.connect(() => {
|
||||||
console.log(
|
console.log("Weather enabled setting changed:", SettingsData.weatherEnabled)
|
||||||
"Weather enabled setting changed:",
|
if (SettingsData.weatherEnabled && root.refCount > 0 && !root.weather.available) {
|
||||||
SettingsData.weatherEnabled)
|
|
||||||
if (SettingsData.weatherEnabled
|
|
||||||
&& root.refCount > 0
|
|
||||||
&& !root.weather.available) {
|
|
||||||
// Start fetching when weather is re-enabled
|
|
||||||
root.forceRefresh()
|
root.forceRefresh()
|
||||||
} else if (!SettingsData.weatherEnabled) {
|
} else if (!SettingsData.weatherEnabled) {
|
||||||
// Stop all timers when weather is disabled
|
|
||||||
updateTimer.stop()
|
updateTimer.stop()
|
||||||
retryTimer.stop()
|
retryTimer.stop()
|
||||||
persistentRetryTimer.stop()
|
persistentRetryTimer.stop()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# https://github.com/jesperhh/qmlfmt
|
# https://github.com/jesperhh/qmlfmt
|
||||||
find . -name "*.qml" -exec qmlfmt -t 4 -i 4 -w {} \;
|
find . -name "*.qml" -exec qmlfmt -t 4 -i 4 -b 250 -w {} \;
|
||||||
|
|||||||
Reference in New Issue
Block a user