Merge branch 'github-main'
This commit is contained in:
commit
6e009d3551
72 changed files with 2510 additions and 1137 deletions
BIN
Assets/Wallpaper/noctalia.png
Normal file
BIN
Assets/Wallpaper/noctalia.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
|
|
@ -9,3 +9,24 @@ for i in {1..8}; do
|
|||
done
|
||||
|
||||
echo "All notifications sent!"
|
||||
|
||||
# Additional tests for icon/image handling
|
||||
if command -v notify-send >/dev/null 2>&1; then
|
||||
echo "Sending icon/image tests..."
|
||||
|
||||
# 1) Themed icon name
|
||||
notify-send -i dialog-information "Icon name test" "Should resolve from theme (dialog-information)"
|
||||
|
||||
# 2) Absolute path if a sample image exists
|
||||
SAMPLE_IMG="/usr/share/pixmaps/debian-logo.png"
|
||||
if [ -f "$SAMPLE_IMG" ]; then
|
||||
notify-send -i "$SAMPLE_IMG" "Absolute path test" "Should show the provided image path"
|
||||
fi
|
||||
|
||||
# 3) file:// URL form
|
||||
if [ -f "$SAMPLE_IMG" ]; then
|
||||
notify-send -i "file://$SAMPLE_IMG" "file:// URL test" "Should display after stripping scheme"
|
||||
fi
|
||||
|
||||
echo "Icon/image tests sent!"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ Singleton {
|
|||
// FileView to load custom colors data from colors.json
|
||||
FileView {
|
||||
id: customColorsFile
|
||||
path: Settings.configDir + "colors.json"
|
||||
path: Settings.directoriesCreated ? (Settings.configDir + "colors.json") : ""
|
||||
watchChanges: true
|
||||
onFileChanged: {
|
||||
Logger.log("Color", "Reloading colors from disk")
|
||||
|
|
@ -112,6 +112,13 @@ Singleton {
|
|||
Logger.log("Color", "Writing colors to disk")
|
||||
writeAdapter()
|
||||
}
|
||||
|
||||
// Trigger initial load when path changes from empty to actual path
|
||||
onPathChanged: {
|
||||
if (path === Settings.configDir + "colors.json") {
|
||||
reload()
|
||||
}
|
||||
}
|
||||
onLoadFailed: function (error) {
|
||||
if (error.toString().includes("No such file") || error === 2) {
|
||||
// File doesn't exist, create it with default values
|
||||
|
|
|
|||
|
|
@ -26,11 +26,13 @@ Singleton {
|
|||
property string defaultWallpapersDirectory: Quickshell.env("HOME") + "/Pictures/Wallpapers"
|
||||
property string defaultVideosDirectory: Quickshell.env("HOME") + "/Videos"
|
||||
property string defaultLocation: "Tokyo"
|
||||
property string defaultWallpaper: Quickshell.shellDir + "/Assets/Wallpaper/noctalia.png"
|
||||
|
||||
// Used to access via Settings.data.xxx.yyy
|
||||
readonly property alias data: adapter
|
||||
|
||||
property bool isLoaded: false
|
||||
property bool directoriesCreated: false
|
||||
|
||||
// Signal emitted when settings are loaded after startupcale changes
|
||||
signal settingsLoaded
|
||||
|
|
@ -71,34 +73,93 @@ Singleton {
|
|||
|
||||
// -----------------------------------------------------
|
||||
// If the settings structure has changed, ensure
|
||||
// backward compatibility
|
||||
// backward compatibility by upgrading the settings
|
||||
function upgradeSettingsData() {
|
||||
for (var i = 0; i < adapter.bar.widgets.left.length; i++) {
|
||||
var obj = adapter.bar.widgets.left[i]
|
||||
if (typeof obj === "string") {
|
||||
adapter.bar.widgets.left[i] = {
|
||||
"id": obj
|
||||
|
||||
const sections = ["left", "center", "right"]
|
||||
|
||||
// -----------------
|
||||
// 1st. check our settings are not super old, when we only had the widget type as a plain string
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s]
|
||||
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
|
||||
var widget = adapter.bar.widgets[sectionName][i]
|
||||
if (typeof widget === "string") {
|
||||
adapter.bar.widgets[sectionName][i] = {
|
||||
"id": widget
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < adapter.bar.widgets.center.length; i++) {
|
||||
var obj = adapter.bar.widgets.center[i]
|
||||
if (typeof obj === "string") {
|
||||
adapter.bar.widgets.center[i] = {
|
||||
"id": obj
|
||||
|
||||
// -----------------
|
||||
// 2nd. migrate global settings to user settings
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s]
|
||||
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
|
||||
var widget = adapter.bar.widgets[sectionName][i]
|
||||
|
||||
// Check if widget registry supports user settings, if it does not, then there is nothing to do
|
||||
const reg = BarWidgetRegistry.widgetMetadata[widget.id]
|
||||
if ((reg === undefined) || (reg.allowUserSettings === undefined) || !reg.allowUserSettings) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < adapter.bar.widgets.right.length; i++) {
|
||||
var obj = adapter.bar.widgets.right[i]
|
||||
if (typeof obj === "string") {
|
||||
adapter.bar.widgets.right[i] = {
|
||||
"id": obj
|
||||
|
||||
// Check that the widget was not previously migrated and skip if necessary
|
||||
const keys = Object.keys(widget)
|
||||
if (keys.length > 1) {
|
||||
continue
|
||||
}
|
||||
|
||||
migrateWidget(widget)
|
||||
Logger.log("Settings", JSON.stringify(widget))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
function migrateWidget(widget) {
|
||||
Logger.log("Settings", `Migrating '${widget.id}' widget`)
|
||||
|
||||
switch (widget.id) {
|
||||
case "ActiveWindow":
|
||||
widget.showIcon = adapter.bar.showActiveWindowIcon
|
||||
break
|
||||
case "Battery":
|
||||
widget.alwaysShowPercentage = adapter.bar.alwaysShowBatteryPercentage
|
||||
break
|
||||
case "Brightness":
|
||||
widget.alwaysShowPercentage = BarWidgetRegistry.widgetMetadata[widget.id].alwaysShowPercentage
|
||||
break
|
||||
case "Clock":
|
||||
widget.showDate = adapter.location.showDateWithClock
|
||||
widget.use12HourClock = adapter.location.use12HourClock
|
||||
widget.reverseDayMonth = adapter.location.reverseDayMonth
|
||||
widget.showSeconds = BarWidgetRegistry.widgetMetadata[widget.id].showSeconds
|
||||
break
|
||||
case "MediaMini":
|
||||
widget.showAlbumArt = adapter.audio.showMiniplayerAlbumArt
|
||||
widget.showVisualizer = adapter.audio.showMiniplayerCava
|
||||
widget.visualizerType = BarWidgetRegistry.widgetMetadata[widget.id].visualizerType
|
||||
break
|
||||
case "NotificationHistory":
|
||||
widget.showUnreadBadge = BarWidgetRegistry.widgetMetadata[widget.id].showUnreadBadge
|
||||
widget.hideWhenZero = BarWidgetRegistry.widgetMetadata[widget.id].hideWhenZero
|
||||
break
|
||||
case "SidePanelToggle":
|
||||
widget.useDistroLogo = adapter.bar.useDistroLogo
|
||||
break
|
||||
case "SystemMonitor":
|
||||
widget.showNetworkStats = adapter.bar.showNetworkStats
|
||||
break
|
||||
case "Volume":
|
||||
widget.alwaysShowPercentage = BarWidgetRegistry.widgetMetadata[widget.id].alwaysShowPercentage
|
||||
break
|
||||
case "Workspace":
|
||||
widget.labelMode = adapter.bar.showWorkspaceLabel
|
||||
break
|
||||
}
|
||||
}
|
||||
// -----------------------------------------------------
|
||||
// Kickoff essential services
|
||||
function kickOffServices() {
|
||||
|
|
@ -117,14 +178,15 @@ Singleton {
|
|||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
Item {
|
||||
Component.onCompleted: {
|
||||
// Ensure directories exist before FileView tries to read files
|
||||
Component.onCompleted: {
|
||||
// ensure settings dir exists
|
||||
Quickshell.execDetached(["mkdir", "-p", configDir])
|
||||
Quickshell.execDetached(["mkdir", "-p", cacheDir])
|
||||
Quickshell.execDetached(["mkdir", "-p", cacheDirImages])
|
||||
|
||||
// ensure settings dir exists
|
||||
Quickshell.execDetached(["mkdir", "-p", configDir])
|
||||
Quickshell.execDetached(["mkdir", "-p", cacheDir])
|
||||
Quickshell.execDetached(["mkdir", "-p", cacheDirImages])
|
||||
}
|
||||
// Mark directories as created and trigger file loading
|
||||
directoriesCreated = true
|
||||
}
|
||||
|
||||
// Don't write settings to disk immediately
|
||||
|
|
@ -138,12 +200,16 @@ Singleton {
|
|||
|
||||
FileView {
|
||||
id: settingsFileView
|
||||
path: settingsFile
|
||||
path: directoriesCreated ? settingsFile : ""
|
||||
watchChanges: true
|
||||
onFileChanged: reload()
|
||||
onAdapterUpdated: saveTimer.start()
|
||||
Component.onCompleted: function () {
|
||||
reload()
|
||||
|
||||
// Trigger initial load when path changes from empty to actual path
|
||||
onPathChanged: {
|
||||
if (path === settingsFile) {
|
||||
reload()
|
||||
}
|
||||
}
|
||||
onLoaded: function () {
|
||||
if (!isLoaded) {
|
||||
|
|
@ -174,15 +240,16 @@ Singleton {
|
|||
|
||||
// bar
|
||||
property JsonObject bar: JsonObject {
|
||||
property string position: "top" // Possible values: "top", "bottom"
|
||||
property bool showActiveWindowIcon: true
|
||||
property bool alwaysShowBatteryPercentage: false
|
||||
property bool showNetworkStats: false
|
||||
property string position: "top" // "top" or "bottom"
|
||||
property real backgroundOpacity: 1.0
|
||||
property bool useDistroLogo: false
|
||||
property string showWorkspaceLabel: "none"
|
||||
property list<string> monitors: []
|
||||
|
||||
property bool showActiveWindowIcon: true // TODO: delete
|
||||
property bool alwaysShowBatteryPercentage: false // TODO: delete
|
||||
property bool showNetworkStats: false // TODO: delete
|
||||
property bool useDistroLogo: false // TODO: delete
|
||||
property string showWorkspaceLabel: "none" // TODO: delete
|
||||
|
||||
// Widget configuration for modular bar system
|
||||
property JsonObject widgets
|
||||
widgets: JsonObject {
|
||||
|
|
@ -236,9 +303,10 @@ Singleton {
|
|||
property JsonObject location: JsonObject {
|
||||
property string name: defaultLocation
|
||||
property bool useFahrenheit: false
|
||||
property bool reverseDayMonth: false
|
||||
property bool use12HourClock: false
|
||||
property bool showDateWithClock: false
|
||||
|
||||
property bool reverseDayMonth: false // TODO: delete
|
||||
property bool use12HourClock: false // TODO: delete
|
||||
property bool showDateWithClock: false // TODO: delete
|
||||
}
|
||||
|
||||
// screen recorder
|
||||
|
|
@ -267,6 +335,7 @@ Singleton {
|
|||
property int transitionDuration: 1500 // 1500 ms
|
||||
property string transitionType: "random"
|
||||
property real transitionEdgeSmoothness: 0.05
|
||||
property string defaultWallpaper: root.defaultWallpaper
|
||||
property list<var> monitors: []
|
||||
}
|
||||
|
||||
|
|
@ -299,25 +368,27 @@ Singleton {
|
|||
property JsonObject notifications: JsonObject {
|
||||
property bool doNotDisturb: false
|
||||
property list<string> monitors: []
|
||||
// Last time the user opened the notification history (ms since epoch)
|
||||
property real lastSeenTs: 0
|
||||
}
|
||||
|
||||
// audio
|
||||
property JsonObject audio: JsonObject {
|
||||
property bool showMiniplayerAlbumArt: false
|
||||
property bool showMiniplayerCava: false
|
||||
property string visualizerType: "linear"
|
||||
property int volumeStep: 5
|
||||
property int cavaFrameRate: 60
|
||||
// MPRIS controls
|
||||
property string visualizerType: "linear"
|
||||
property list<string> mprisBlacklist: []
|
||||
property string preferredPlayer: ""
|
||||
|
||||
property bool showMiniplayerAlbumArt: false // TODO: delete
|
||||
property bool showMiniplayerCava: false // TODO: delete
|
||||
}
|
||||
|
||||
// ui
|
||||
property JsonObject ui: JsonObject {
|
||||
property string fontDefault: "Roboto" // Default font for all text
|
||||
property string fontFixed: "DejaVu Sans Mono" // Fixed width font for terminal
|
||||
property string fontBillboard: "Inter" // Large bold font for clocks and prominent displays
|
||||
property string fontDefault: "Roboto"
|
||||
property string fontFixed: "DejaVu Sans Mono"
|
||||
property string fontBillboard: "Inter"
|
||||
property list<var> monitorsScaling: []
|
||||
property bool idleInhibitorEnabled: false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ Singleton {
|
|||
property int animationFast: Math.round(150 * Settings.data.general.animationSpeed)
|
||||
property int animationNormal: Math.round(300 * Settings.data.general.animationSpeed)
|
||||
property int animationSlow: Math.round(450 * Settings.data.general.animationSpeed)
|
||||
property int animationSlowest: Math.round(750 * Settings.data.general.animationSpeed)
|
||||
|
||||
// Dimensions
|
||||
property int barHeight: 36
|
||||
|
|
|
|||
|
|
@ -9,52 +9,38 @@ Singleton {
|
|||
id: root
|
||||
|
||||
property var date: new Date()
|
||||
property string time: {
|
||||
let timeFormat = Settings.data.location.use12HourClock ? "h:mm AP" : "HH:mm"
|
||||
let timeString = Qt.formatDateTime(date, timeFormat)
|
||||
|
||||
if (Settings.data.location.showDateWithClock) {
|
||||
let dayName = date.toLocaleDateString(Qt.locale(), "ddd")
|
||||
dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1)
|
||||
let day = date.getDate()
|
||||
let month = date.toLocaleDateString(Qt.locale(), "MMM")
|
||||
|
||||
return timeString + " - " + (Settings.data.location.reverseDayMonth ? `${dayName}, ${month} ${day}` : `${dayName}, ${day} ${month}`)
|
||||
}
|
||||
|
||||
return timeString
|
||||
// Returns a Unix Timestamp (in seconds)
|
||||
readonly property int timestamp: {
|
||||
return Math.floor(date / 1000)
|
||||
}
|
||||
readonly property string dateString: {
|
||||
|
||||
function formatDate(reverseDayMonth = true) {
|
||||
let now = date
|
||||
let dayName = now.toLocaleDateString(Qt.locale(), "ddd")
|
||||
dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1)
|
||||
let day = now.getDate()
|
||||
let suffix
|
||||
if (day > 3 && day < 21)
|
||||
suffix = 'th'
|
||||
suffix = 'th'
|
||||
else
|
||||
switch (day % 10) {
|
||||
switch (day % 10) {
|
||||
case 1:
|
||||
suffix = "st"
|
||||
break
|
||||
suffix = "st"
|
||||
break
|
||||
case 2:
|
||||
suffix = "nd"
|
||||
break
|
||||
suffix = "nd"
|
||||
break
|
||||
case 3:
|
||||
suffix = "rd"
|
||||
break
|
||||
suffix = "rd"
|
||||
break
|
||||
default:
|
||||
suffix = "th"
|
||||
}
|
||||
suffix = "th"
|
||||
}
|
||||
let month = now.toLocaleDateString(Qt.locale(), "MMMM")
|
||||
let year = now.toLocaleDateString(Qt.locale(), "yyyy")
|
||||
return `${dayName}, `
|
||||
+ (Settings.data.location.reverseDayMonth ? `${month} ${day}${suffix} ${year}` : `${day}${suffix} ${month} ${year}`)
|
||||
}
|
||||
|
||||
// Returns a Unix Timestamp (in seconds)
|
||||
readonly property int timestamp: {
|
||||
return Math.floor(date / 1000)
|
||||
return `${dayName}, ` + (reverseDayMonth ? `${month} ${day}${suffix} ${year}` : `${day}${suffix} ${month} ${year}`)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import Quickshell
|
|||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Modules.SettingsPanel
|
||||
import qs.Widgets
|
||||
|
||||
Variants {
|
||||
id: backgroundVariants
|
||||
|
|
@ -20,6 +22,8 @@ Variants {
|
|||
// Internal state management
|
||||
property string transitionType: "fade"
|
||||
property real transitionProgress: 0
|
||||
// Scaling support for widgets that rely on it
|
||||
property real scaling: ScalingService.getScreenScale(screen)
|
||||
|
||||
readonly property real edgeSmoothness: Settings.data.wallpaper.transitionEdgeSmoothness
|
||||
readonly property var allTransitions: WallpaperService.allTransitions
|
||||
|
|
@ -87,6 +91,15 @@ Variants {
|
|||
left: true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ScalingService
|
||||
function onScaleChanged(screenName, scale) {
|
||||
if ((screen !== null) && (screenName === screen.name)) {
|
||||
scaling = scale
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: debounceTimer
|
||||
interval: 333
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ Variants {
|
|||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
"scaling": ScalingService.getScreenScale(screen),
|
||||
"widgetId": modelData.id,
|
||||
"barSection": parent.objectName,
|
||||
"sectionWidgetIndex": index,
|
||||
"sectionWidgetsCount": Settings.data.bar.widgets.left.length
|
||||
|
|
@ -103,6 +104,7 @@ Variants {
|
|||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
"scaling": ScalingService.getScreenScale(screen),
|
||||
"widgetId": modelData.id,
|
||||
"barSection": parent.objectName,
|
||||
"sectionWidgetIndex": index,
|
||||
"sectionWidgetsCount": Settings.data.bar.widgets.center.length
|
||||
|
|
@ -131,6 +133,7 @@ Variants {
|
|||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
"scaling": ScalingService.getScreenScale(screen),
|
||||
"widgetId": modelData.id,
|
||||
"barSection": parent.objectName,
|
||||
"sectionWidgetIndex": index,
|
||||
"sectionWidgetsCount": Settings.data.bar.widgets.right.length
|
||||
|
|
|
|||
|
|
@ -12,6 +12,27 @@ RowLayout {
|
|||
id: root
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon
|
||||
|
||||
readonly property real minWidth: 160
|
||||
readonly property real maxWidth: 400
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
|
@ -74,7 +95,7 @@ RowLayout {
|
|||
Layout.preferredWidth: Style.fontSizeL * scaling * 1.2
|
||||
Layout.preferredHeight: Style.fontSizeL * scaling * 1.2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: getTitle() !== "" && Settings.data.bar.showActiveWindowIcon
|
||||
visible: getTitle() !== "" && showIcon
|
||||
|
||||
IconImage {
|
||||
id: windowIcon
|
||||
|
|
|
|||
|
|
@ -11,11 +11,42 @@ Item {
|
|||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property int sectionWidgetIndex: 0
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
// Track if we've already notified to avoid spam
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
// Resolve settings: try user settings or defaults from BarWidgetRegistry
|
||||
readonly property bool alwaysShowPercentage: widgetSettings.alwaysShowPercentage
|
||||
!== undefined ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
readonly property real warningThreshold: widgetSettings.warningThreshold
|
||||
!== undefined ? widgetSettings.warningThreshold : widgetMetadata.warningThreshold
|
||||
|
||||
// Test mode
|
||||
readonly property bool testMode: false
|
||||
readonly property int testPercent: 50
|
||||
readonly property bool testCharging: true
|
||||
|
||||
// Main properties
|
||||
readonly property var battery: UPower.displayDevice
|
||||
readonly property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery
|
||||
&& battery.isPresent)
|
||||
readonly property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0)
|
||||
readonly property bool charging: testMode ? testCharging : (isReady ? battery.state === UPowerDeviceState.Charging : false)
|
||||
property bool hasNotifiedLowBattery: false
|
||||
|
||||
implicitWidth: pill.width
|
||||
|
|
@ -23,15 +54,14 @@ Item {
|
|||
|
||||
// Helper to evaluate and possibly notify
|
||||
function maybeNotify(percent, charging) {
|
||||
const p = Math.round(percent)
|
||||
// Only notify exactly at 15%, not at 0% or any other percentage
|
||||
if (!charging && p === 15 && !root.hasNotifiedLowBattery) {
|
||||
// Only notify once we are a below threshold
|
||||
if (!charging && !root.hasNotifiedLowBattery && percent <= warningThreshold) {
|
||||
root.hasNotifiedLowBattery = true
|
||||
// Maybe go with toast ?
|
||||
Quickshell.execDetached(
|
||||
["notify-send", "-u", "critical", "-i", "battery-caution", "Low Battery", `Battery is at ${p}%. Please connect charger.`])
|
||||
root.hasNotifiedLowBattery = true
|
||||
}
|
||||
// Reset when charging starts or when battery recovers above 20%
|
||||
if (charging || p > 20) {
|
||||
} else if (root.hasNotifiedLowBattery && (charging || percent > warningThreshold + 5)) {
|
||||
// Reset when charging starts or when battery recovers 5% above threshold
|
||||
root.hasNotifiedLowBattery = false
|
||||
}
|
||||
}
|
||||
|
|
@ -40,19 +70,10 @@ Item {
|
|||
Connections {
|
||||
target: UPower.displayDevice
|
||||
function onPercentageChanged() {
|
||||
let battery = UPower.displayDevice
|
||||
let isReady = battery && battery.ready && battery.isLaptopBattery && battery.isPresent
|
||||
let percent = isReady ? (battery.percentage * 100) : 0
|
||||
let charging = isReady ? battery.state === UPowerDeviceState.Charging : false
|
||||
|
||||
root.maybeNotify(percent, charging)
|
||||
}
|
||||
|
||||
function onStateChanged() {
|
||||
let battery = UPower.displayDevice
|
||||
let isReady = battery && battery.ready && battery.isLaptopBattery && battery.isPresent
|
||||
let charging = isReady ? battery.state === UPowerDeviceState.Charging : false
|
||||
|
||||
// Reset notification flag when charging starts
|
||||
if (charging) {
|
||||
root.hasNotifiedLowBattery = false
|
||||
|
|
@ -63,76 +84,44 @@ Item {
|
|||
NPill {
|
||||
id: pill
|
||||
|
||||
// Test mode
|
||||
property bool testMode: false
|
||||
property int testPercent: 20
|
||||
property bool testCharging: false
|
||||
property var battery: UPower.displayDevice
|
||||
property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery && battery.isPresent)
|
||||
property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0)
|
||||
property bool charging: testMode ? testCharging : (isReady ? battery.state === UPowerDeviceState.Charging : false)
|
||||
|
||||
// Choose icon based on charge and charging state
|
||||
function batteryIcon() {
|
||||
if (!isReady || !battery.isLaptopBattery)
|
||||
return "battery_android_alert"
|
||||
if (charging)
|
||||
return "battery_android_bolt"
|
||||
if (percent >= 95)
|
||||
return "battery_android_full"
|
||||
// Hardcoded battery symbols
|
||||
if (percent >= 85)
|
||||
return "battery_android_6"
|
||||
if (percent >= 70)
|
||||
return "battery_android_5"
|
||||
if (percent >= 55)
|
||||
return "battery_android_4"
|
||||
if (percent >= 40)
|
||||
return "battery_android_3"
|
||||
if (percent >= 25)
|
||||
return "battery_android_2"
|
||||
if (percent >= 10)
|
||||
return "battery_android_1"
|
||||
if (percent >= 0)
|
||||
return "battery_android_0"
|
||||
}
|
||||
|
||||
rightOpen: BarWidgetRegistry.getNPillDirection(root)
|
||||
icon: batteryIcon()
|
||||
icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent,
|
||||
charging, isReady)
|
||||
iconRotation: -90
|
||||
text: ((isReady && battery.isLaptopBattery) || testMode) ? Math.round(percent) + "%" : "-"
|
||||
textColor: charging ? Color.mPrimary : Color.mOnSurface
|
||||
iconCircleColor: Color.mPrimary
|
||||
collapsedIconColor: Color.mOnSurface
|
||||
autoHide: false
|
||||
forceOpen: isReady && (testMode || battery.isLaptopBattery) && Settings.data.bar.alwaysShowBatteryPercentage
|
||||
forceOpen: isReady && (testMode || battery.isLaptopBattery) && alwaysShowPercentage
|
||||
disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery))
|
||||
tooltipText: {
|
||||
let lines = []
|
||||
if (testMode) {
|
||||
lines.push("Time left: " + Time.formatVagueHumanReadableDuration(12345))
|
||||
lines.push(`Time left: ${Time.formatVagueHumanReadableDuration(12345)}.`)
|
||||
return lines.join("\n")
|
||||
}
|
||||
if (!isReady || !battery.isLaptopBattery) {
|
||||
return "No battery detected"
|
||||
return "No battery detected."
|
||||
}
|
||||
if (battery.timeToEmpty > 0) {
|
||||
lines.push("Time left: " + Time.formatVagueHumanReadableDuration(battery.timeToEmpty))
|
||||
lines.push(`Time left: ${Time.formatVagueHumanReadableDuration(battery.timeToEmpty)}.`)
|
||||
}
|
||||
if (battery.timeToFull > 0) {
|
||||
lines.push("Time until full: " + Time.formatVagueHumanReadableDuration(battery.timeToFull))
|
||||
lines.push(`Time until full: ${Time.formatVagueHumanReadableDuration(battery.timeToFull)}.`)
|
||||
}
|
||||
if (battery.changeRate !== undefined) {
|
||||
const rate = battery.changeRate
|
||||
if (rate > 0) {
|
||||
lines.push(charging ? "Charging rate: " + rate.toFixed(2) + " W" : "Discharging rate: " + rate.toFixed(
|
||||
2) + " W")
|
||||
lines.push(charging ? "Charging rate: " + rate.toFixed(2) + " W." : "Discharging rate: " + rate.toFixed(
|
||||
2) + " W.")
|
||||
} else if (rate < 0) {
|
||||
lines.push("Discharging rate: " + Math.abs(rate).toFixed(2) + " W")
|
||||
lines.push("Discharging rate: " + Math.abs(rate).toFixed(2) + " W.")
|
||||
} else {
|
||||
lines.push("Estimating...")
|
||||
}
|
||||
} else {
|
||||
lines.push(charging ? "Charging" : "Discharging")
|
||||
lines.push(charging ? "Charging." : "Discharging.")
|
||||
}
|
||||
if (battery.healthPercentage !== undefined && battery.healthPercentage > 0) {
|
||||
lines.push("Health: " + Math.round(battery.healthPercentage) + "%")
|
||||
|
|
|
|||
|
|
@ -10,10 +10,28 @@ Item {
|
|||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property int sectionWidgetIndex: 0
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
readonly property bool userAlwaysShowPercentage: (widgetSettings.alwaysShowPercentage
|
||||
!== undefined) ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
|
||||
// Used to avoid opening the pill on Quickshell startup
|
||||
property bool firstBrightnessReceived: false
|
||||
|
||||
|
|
@ -37,28 +55,26 @@ Item {
|
|||
target: getMonitor()
|
||||
ignoreUnknownSignals: true
|
||||
function onBrightnessUpdated() {
|
||||
Logger.log("Bar-Brightness", "OnBrightnessUpdated")
|
||||
var monitor = getMonitor()
|
||||
if (!monitor)
|
||||
return
|
||||
var currentBrightness = monitor.brightness
|
||||
|
||||
// Ignore if this is the first time or if brightness hasn't actually changed
|
||||
// Ignore if this is the first time we receive an update.
|
||||
// Most likely service just kicked off.
|
||||
if (!firstBrightnessReceived) {
|
||||
firstBrightnessReceived = true
|
||||
monitor.lastBrightness = currentBrightness
|
||||
return
|
||||
}
|
||||
|
||||
// Only show pill if brightness actually changed (not just loaded from settings)
|
||||
if (Math.abs(currentBrightness - monitor.lastBrightness) > 0.1) {
|
||||
pill.show()
|
||||
}
|
||||
|
||||
monitor.lastBrightness = currentBrightness
|
||||
pill.show()
|
||||
hideTimerAfterChange.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: hideTimerAfterChange
|
||||
interval: 2500
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: pill.hide()
|
||||
}
|
||||
|
||||
NPill {
|
||||
id: pill
|
||||
|
||||
|
|
@ -71,6 +87,7 @@ Item {
|
|||
var monitor = getMonitor()
|
||||
return monitor ? (Math.round(monitor.brightness * 100) + "%") : ""
|
||||
}
|
||||
forceOpen: userAlwaysShowPercentage
|
||||
tooltipText: {
|
||||
var monitor = getMonitor()
|
||||
if (!monitor)
|
||||
|
|
|
|||
|
|
@ -10,24 +10,70 @@ Rectangle {
|
|||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
// Resolve settings: try user settings or defaults from BarWidgetRegistry
|
||||
readonly property bool showDate: widgetSettings.showDate !== undefined ? widgetSettings.showDate : widgetMetadata.showDate
|
||||
readonly property bool use12h: widgetSettings.use12HourClock !== undefined ? widgetSettings.use12HourClock : widgetMetadata.use12HourClock
|
||||
readonly property bool showSeconds: widgetSettings.showSeconds !== undefined ? widgetSettings.showSeconds : widgetMetadata.showSeconds
|
||||
readonly property bool reverseDayMonth: widgetSettings.reverseDayMonth
|
||||
!== undefined ? widgetSettings.reverseDayMonth : widgetMetadata.reverseDayMonth
|
||||
|
||||
implicitWidth: clock.width + Style.marginM * 2 * scaling
|
||||
implicitHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
radius: Math.round(Style.radiusM * scaling)
|
||||
color: Color.mSurfaceVariant
|
||||
|
||||
// Clock Icon with attached calendar
|
||||
NClock {
|
||||
NText {
|
||||
id: clock
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: {
|
||||
const now = Time.date
|
||||
const timeFormat = use12h ? (showSeconds ? "h:mm:ss AP" : "h:mm AP") : (showSeconds ? "HH:mm:ss" : "HH:mm")
|
||||
const timeString = Qt.formatDateTime(now, timeFormat)
|
||||
|
||||
NTooltip {
|
||||
id: tooltip
|
||||
text: `${Time.dateString}.`
|
||||
target: clock
|
||||
positionAbove: Settings.data.bar.position === "bottom"
|
||||
if (showDate) {
|
||||
let dayName = now.toLocaleDateString(Qt.locale(), "ddd")
|
||||
dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1)
|
||||
let day = now.getDate()
|
||||
let month = now.toLocaleDateString(Qt.locale(), "MMM")
|
||||
return timeString + " - " + (reverseDayMonth ? `${dayName}, ${month} ${day}` : `${dayName}, ${day} ${month}`)
|
||||
}
|
||||
return timeString
|
||||
}
|
||||
anchors.centerIn: parent
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
NTooltip {
|
||||
id: tooltip
|
||||
text: `${Time.formatDate(reverseDayMonth)}.`
|
||||
target: clock
|
||||
positionAbove: Settings.data.bar.position === "bottom"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clockMouseArea
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onEntered: {
|
||||
if (!PanelService.getPanel("calendarPanel")?.active) {
|
||||
tooltip.show()
|
||||
|
|
|
|||
|
|
@ -13,11 +13,13 @@ NIconButton {
|
|||
property var screen
|
||||
property real scaling: 1.0
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
// Get user settings from Settings data
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
|
|
@ -30,30 +32,27 @@ NIconButton {
|
|||
}
|
||||
|
||||
// Use settings or defaults from BarWidgetRegistry
|
||||
readonly property string userIcon: widgetSettings.icon || BarWidgetRegistry.widgetMetadata["CustomButton"].icon
|
||||
readonly property string userLeftClickExec: widgetSettings.leftClickExec
|
||||
|| BarWidgetRegistry.widgetMetadata["CustomButton"].leftClickExec
|
||||
readonly property string userRightClickExec: widgetSettings.rightClickExec
|
||||
|| BarWidgetRegistry.widgetMetadata["CustomButton"].rightClickExec
|
||||
readonly property string userMiddleClickExec: widgetSettings.middleClickExec
|
||||
|| BarWidgetRegistry.widgetMetadata["CustomButton"].middleClickExec
|
||||
readonly property bool hasExec: (userLeftClickExec || userRightClickExec || userMiddleClickExec)
|
||||
readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon
|
||||
readonly property string leftClickExec: widgetSettings.leftClickExec || widgetMetadata.leftClickExec
|
||||
readonly property string rightClickExec: widgetSettings.rightClickExec || widgetMetadata.rightClickExec
|
||||
readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec
|
||||
readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec)
|
||||
|
||||
sizeRatio: 0.8
|
||||
icon: userIcon
|
||||
icon: customIcon
|
||||
tooltipText: {
|
||||
if (!hasExec) {
|
||||
return "Custom Button - Configure in settings"
|
||||
} else {
|
||||
var lines = []
|
||||
if (userLeftClickExec !== "") {
|
||||
lines.push(`Left click: <i>${userLeftClickExec}</i>.`)
|
||||
if (leftClickExec !== "") {
|
||||
lines.push(`Left click: <i>${leftClickExec}</i>.`)
|
||||
}
|
||||
if (userRightClickExec !== "") {
|
||||
lines.push(`Right click: <i>${userRightClickExec}</i>.`)
|
||||
if (rightClickExec !== "") {
|
||||
lines.push(`Right click: <i>${rightClickExec}</i>.`)
|
||||
}
|
||||
if (userMiddleClickExec !== "") {
|
||||
lines.push(`Middle click: <i>${userMiddleClickExec}</i>.`)
|
||||
if (middleClickExec !== "") {
|
||||
lines.push(`Middle click: <i>${middleClickExec}</i>.`)
|
||||
}
|
||||
return lines.join("<br/>")
|
||||
}
|
||||
|
|
@ -61,9 +60,9 @@ NIconButton {
|
|||
opacity: hasExec ? Style.opacityFull : Style.opacityMedium
|
||||
|
||||
onClicked: {
|
||||
if (userLeftClickExec) {
|
||||
Quickshell.execDetached(["sh", "-c", userLeftClickExec])
|
||||
Logger.log("CustomButton", `Executing command: ${userLeftClickExec}`)
|
||||
if (leftClickExec) {
|
||||
Quickshell.execDetached(["sh", "-c", leftClickExec])
|
||||
Logger.log("CustomButton", `Executing command: ${leftClickExec}`)
|
||||
} else if (!hasExec) {
|
||||
// No script was defined, open settings
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
||||
|
|
@ -73,16 +72,16 @@ NIconButton {
|
|||
}
|
||||
|
||||
onRightClicked: {
|
||||
if (userRightClickExec) {
|
||||
Quickshell.execDetached(["sh", "-c", userRightClickExec])
|
||||
Logger.log("CustomButton", `Executing command: ${userRightClickExec}`)
|
||||
if (rightClickExec) {
|
||||
Quickshell.execDetached(["sh", "-c", rightClickExec])
|
||||
Logger.log("CustomButton", `Executing command: ${rightClickExec}`)
|
||||
}
|
||||
}
|
||||
|
||||
onMiddleClicked: {
|
||||
if (userMiddleClickExec) {
|
||||
Quickshell.execDetached(["sh", "-c", userMiddleClickExec])
|
||||
Logger.log("CustomButton", `Executing command: ${userMiddleClickExec}`)
|
||||
if (middleClickExec) {
|
||||
Quickshell.execDetached(["sh", "-c", middleClickExec])
|
||||
Logger.log("CustomButton", `Executing command: ${middleClickExec}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,6 @@ Item {
|
|||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
property string barSection: ""
|
||||
property int sectionWidgetIndex: 0
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
// Use the shared service for keyboard layout
|
||||
property string currentLayout: KeyboardLayoutService.currentLayout
|
||||
|
|
|
|||
|
|
@ -12,18 +12,44 @@ RowLayout {
|
|||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
readonly property bool showAlbumArt: (widgetSettings.showAlbumArt
|
||||
!== undefined) ? widgetSettings.showAlbumArt : widgetMetadata.showAlbumArt
|
||||
readonly property bool showVisualizer: (widgetSettings.showVisualizer
|
||||
!== undefined) ? widgetSettings.showVisualizer : widgetMetadata.showVisualizer
|
||||
readonly property string visualizerType: (widgetSettings.visualizerType !== undefined && widgetSettings.visualizerType
|
||||
!== "") ? widgetSettings.visualizerType : widgetMetadata.visualizerType
|
||||
|
||||
readonly property real minWidth: 160
|
||||
readonly property real maxWidth: 400
|
||||
|
||||
function getTitle() {
|
||||
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "")
|
||||
}
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Style.marginS * scaling
|
||||
visible: MediaService.currentPlayer !== null && MediaService.canPlay
|
||||
Layout.preferredWidth: MediaService.currentPlayer !== null && MediaService.canPlay ? implicitWidth : 0
|
||||
|
||||
function getTitle() {
|
||||
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "")
|
||||
}
|
||||
|
||||
// A hidden text element to safely measure the full title width
|
||||
NText {
|
||||
id: fullTitleMetrics
|
||||
|
|
@ -58,8 +84,7 @@ RowLayout {
|
|||
Loader {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "linear"
|
||||
&& MediaService.isPlaying
|
||||
active: showVisualizer && visualizerType == "linear" && MediaService.isPlaying
|
||||
z: 0
|
||||
|
||||
sourceComponent: LinearSpectrum {
|
||||
|
|
@ -74,8 +99,7 @@ RowLayout {
|
|||
Loader {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "mirrored"
|
||||
&& MediaService.isPlaying
|
||||
active: showVisualizer && visualizerType == "mirrored" && MediaService.isPlaying
|
||||
z: 0
|
||||
|
||||
sourceComponent: MirroredSpectrum {
|
||||
|
|
@ -90,8 +114,7 @@ RowLayout {
|
|||
Loader {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "wave"
|
||||
&& MediaService.isPlaying
|
||||
active: showVisualizer && visualizerType == "wave" && MediaService.isPlaying
|
||||
z: 0
|
||||
|
||||
sourceComponent: WaveSpectrum {
|
||||
|
|
@ -115,12 +138,12 @@ RowLayout {
|
|||
font.pointSize: Style.fontSizeL * scaling
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: !Settings.data.audio.showMiniplayerAlbumArt && getTitle() !== "" && !trackArt.visible
|
||||
visible: !showAlbumArt && getTitle() !== "" && !trackArt.visible
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: Settings.data.audio.showMiniplayerAlbumArt
|
||||
visible: showAlbumArt
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
|
|
|
|||
|
|
@ -12,10 +12,28 @@ Item {
|
|||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property int sectionWidgetIndex: 0
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
readonly property bool alwaysShowPercentage: (widgetSettings.alwaysShowPercentage
|
||||
!== undefined) ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
|
||||
// Used to avoid opening the pill on Quickshell startup
|
||||
property bool firstInputVolumeReceived: false
|
||||
property int wheelAccumulator: 0
|
||||
|
|
@ -78,6 +96,7 @@ Item {
|
|||
collapsedIconColor: Color.mOnSurface
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: Math.floor(AudioService.inputVolume * 100) + "%"
|
||||
forceOpen: alwaysShowPercentage
|
||||
tooltipText: "Microphone: " + Math.round(AudioService.inputVolume * 100)
|
||||
+ "%\nLeft click for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute."
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import QtQuick.Controls
|
|||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Modules.SettingsPanel
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
|
|
@ -14,7 +15,6 @@ NIconButton {
|
|||
property real scaling: 1.0
|
||||
|
||||
sizeRatio: 0.8
|
||||
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorFg: Color.mOnSurface
|
||||
colorBorder: Color.transparent
|
||||
|
|
@ -26,7 +26,7 @@ NIconButton {
|
|||
|
||||
onRightClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Brightness
|
||||
settingsPanel.open(screen)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,45 @@ NIconButton {
|
|||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
readonly property bool showUnreadBadge: (widgetSettings.showUnreadBadge
|
||||
!== undefined) ? widgetSettings.showUnreadBadge : widgetMetadata.showUnreadBadge
|
||||
readonly property bool hideWhenZero: (widgetSettings.hideWhenZero
|
||||
!== undefined) ? widgetSettings.hideWhenZero : widgetMetadata.hideWhenZero
|
||||
|
||||
function lastSeenTs() {
|
||||
return Settings.data.notifications?.lastSeenTs || 0
|
||||
}
|
||||
|
||||
function computeUnreadCount() {
|
||||
var since = lastSeenTs()
|
||||
var count = 0
|
||||
var model = NotificationService.historyModel
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
var item = model.get(i)
|
||||
var ts = item.timestamp instanceof Date ? item.timestamp.getTime() : item.timestamp
|
||||
if (ts > since)
|
||||
count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
sizeRatio: 0.8
|
||||
icon: Settings.data.notifications.doNotDisturb ? "notifications_off" : "notifications"
|
||||
tooltipText: Settings.data.notifications.doNotDisturb ? "Notification history.\nRight-click to disable 'Do Not Disturb'." : "Notification history.\nRight-click to enable 'Do Not Disturb'."
|
||||
|
|
@ -21,7 +60,40 @@ NIconButton {
|
|||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
|
||||
onClicked: PanelService.getPanel("notificationHistoryPanel")?.toggle(screen, this)
|
||||
onClicked: {
|
||||
var panel = PanelService.getPanel("notificationHistoryPanel")
|
||||
panel?.toggle(screen, this)
|
||||
Settings.data.notifications.lastSeenTs = Time.timestamp * 1000
|
||||
}
|
||||
|
||||
onRightClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
|
||||
|
||||
Loader {
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: -4 * scaling
|
||||
anchors.topMargin: -4 * scaling
|
||||
z: 2
|
||||
active: showUnreadBadge && (!hideWhenZero || computeUnreadCount() > 0)
|
||||
sourceComponent: Rectangle {
|
||||
id: badge
|
||||
readonly property int count: computeUnreadCount()
|
||||
readonly property string label: count <= 99 ? String(count) : "99+"
|
||||
readonly property real pad: 8 * scaling
|
||||
height: 16 * scaling
|
||||
width: Math.max(height, textNode.implicitWidth + pad)
|
||||
radius: height / 2
|
||||
color: Color.mError
|
||||
border.color: Color.mSurface
|
||||
border.width: 1
|
||||
visible: count > 0 || !hideWhenZero
|
||||
NText {
|
||||
id: textNode
|
||||
anchors.centerIn: parent
|
||||
text: badge.label
|
||||
font.pointSize: Style.fontSizeXXS * scaling
|
||||
color: Color.mOnError
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick.Effects
|
||||
|
|
@ -11,7 +12,28 @@ NIconButton {
|
|||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
icon: Settings.data.bar.useDistroLogo ? "" : "widgets"
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
readonly property bool useDistroLogo: (widgetSettings.useDistroLogo
|
||||
!== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo
|
||||
|
||||
icon: useDistroLogo ? "" : "widgets"
|
||||
tooltipText: "Open side panel."
|
||||
sizeRatio: 0.8
|
||||
|
||||
|
|
@ -24,14 +46,13 @@ NIconButton {
|
|||
onClicked: PanelService.getPanel("sidePanel")?.toggle(screen, this)
|
||||
onRightClicked: PanelService.getPanel("settingsPanel")?.toggle(screen)
|
||||
|
||||
// When enabled, draw the distro logo instead of the icon glyph
|
||||
IconImage {
|
||||
id: logo
|
||||
anchors.centerIn: parent
|
||||
width: root.width * 0.6
|
||||
height: width
|
||||
source: Settings.data.bar.useDistroLogo ? DistroLogoService.osLogo : ""
|
||||
visible: false //Settings.data.bar.useDistroLogo && source !== ""
|
||||
source: useDistroLogo ? DistroLogoService.osLogo : ""
|
||||
visible: useDistroLogo && source !== ""
|
||||
smooth: true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,11 +12,13 @@ Item {
|
|||
property var screen
|
||||
property real scaling: 1.0
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
// Get user settings from Settings data - make it reactive
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
|
|
@ -29,19 +31,10 @@ Item {
|
|||
}
|
||||
|
||||
// Use settings or defaults from BarWidgetRegistry
|
||||
readonly property int userWidth: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex].width || BarWidgetRegistry.widgetMetadata["Spacer"].width
|
||||
}
|
||||
}
|
||||
return BarWidgetRegistry.widgetMetadata["Spacer"].width
|
||||
}
|
||||
readonly property int spacerWidth: widgetSettings.width !== undefined ? widgetSettings.width : widgetMetadata.width
|
||||
|
||||
// Set the width based on user settings
|
||||
implicitWidth: userWidth * scaling
|
||||
implicitWidth: spacerWidth * scaling
|
||||
implicitHeight: Style.barHeight * scaling
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
|
@ -51,6 +44,6 @@ Item {
|
|||
anchors.fill: parent
|
||||
color: Qt.rgba(1, 0, 0, 0.1) // Very subtle red tint
|
||||
visible: Settings.data.general.debugMode || false
|
||||
radius: 2 * scaling
|
||||
radius: Style.radiusXXS * scaling
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,34 @@ RowLayout {
|
|||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
readonly property bool showCpuUsage: (widgetSettings.showCpuUsage
|
||||
!== undefined) ? widgetSettings.showCpuUsage : widgetMetadata.showCpuUsage
|
||||
readonly property bool showCpuTemp: (widgetSettings.showCpuTemp !== undefined) ? widgetSettings.showCpuTemp : widgetMetadata.showCpuTemp
|
||||
readonly property bool showMemoryUsage: (widgetSettings.showMemoryUsage
|
||||
!== undefined) ? widgetSettings.showMemoryUsage : widgetMetadata.showMemoryUsage
|
||||
readonly property bool showMemoryAsPercent: (widgetSettings.showMemoryAsPercent
|
||||
!== undefined) ? widgetSettings.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
|
||||
readonly property bool showNetworkStats: (widgetSettings.showNetworkStats
|
||||
!== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
|
|
@ -34,6 +62,7 @@ RowLayout {
|
|||
id: cpuUsageLayout
|
||||
spacing: Style.marginXS * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showCpuUsage
|
||||
|
||||
NIcon {
|
||||
id: cpuUsageIcon
|
||||
|
|
@ -59,6 +88,7 @@ RowLayout {
|
|||
// spacing is thin here to compensate for the vertical thermometer icon
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showCpuTemp
|
||||
|
||||
NIcon {
|
||||
text: "thermometer"
|
||||
|
|
@ -81,6 +111,7 @@ RowLayout {
|
|||
id: memoryUsageLayout
|
||||
spacing: Style.marginXS * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showMemoryUsage
|
||||
|
||||
NIcon {
|
||||
text: "memory"
|
||||
|
|
@ -88,7 +119,7 @@ RowLayout {
|
|||
}
|
||||
|
||||
NText {
|
||||
text: `${SystemStatService.memoryUsageGb}G`
|
||||
text: showMemoryAsPercent ? `${SystemStatService.memPercent}%` : `${SystemStatService.memGb}G`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
|
|
@ -103,7 +134,7 @@ RowLayout {
|
|||
id: networkDownloadLayout
|
||||
spacing: Style.marginXS * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: Settings.data.bar.showNetworkStats
|
||||
visible: showNetworkStats
|
||||
|
||||
NIcon {
|
||||
text: "download"
|
||||
|
|
@ -126,7 +157,7 @@ RowLayout {
|
|||
id: networkUploadLayout
|
||||
spacing: Style.marginXS * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: Settings.data.bar.showNetworkStats
|
||||
visible: showNetworkStats
|
||||
|
||||
NIcon {
|
||||
text: "upload"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ Rectangle {
|
|||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
readonly property real itemSize: 24 * scaling
|
||||
|
||||
function onLoaded() {
|
||||
|
|
|
|||
|
|
@ -12,10 +12,28 @@ Item {
|
|||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property int sectionWidgetIndex: 0
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
readonly property bool alwaysShowPercentage: (widgetSettings.alwaysShowPercentage
|
||||
!== undefined) ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
|
||||
// Used to avoid opening the pill on Quickshell startup
|
||||
property bool firstVolumeReceived: false
|
||||
property int wheelAccumulator: 0
|
||||
|
|
@ -63,6 +81,7 @@ Item {
|
|||
collapsedIconColor: Color.mOnSurface
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: Math.floor(AudioService.volume * 100) + "%"
|
||||
forceOpen: alwaysShowPercentage
|
||||
tooltipText: "Volume: " + Math.round(AudioService.volume * 100)
|
||||
+ "%\nLeft click for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute."
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,6 @@ NIconButton {
|
|||
return "signal_wifi_bad"
|
||||
}
|
||||
}
|
||||
tooltipText: "Network / Wi-Fi."
|
||||
tooltipText: "Manage Wi-Fi."
|
||||
onClicked: PanelService.getPanel("wifiPanel")?.toggle(screen, this)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,26 @@ Item {
|
|||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode
|
||||
|
||||
property bool isDestroying: false
|
||||
property bool hovered: false
|
||||
|
||||
|
|
@ -22,8 +42,8 @@ Item {
|
|||
property bool effectsActive: false
|
||||
property color effectColor: Color.mPrimary
|
||||
|
||||
property int horizontalPadding: Math.round(16 * scaling)
|
||||
property int spacingBetweenPills: Math.round(8 * scaling)
|
||||
property int horizontalPadding: Math.round(Style.marginS * scaling)
|
||||
property int spacingBetweenPills: Math.round(Style.marginXS * scaling)
|
||||
|
||||
signal workspaceChanged(int workspaceId, color accentColor)
|
||||
|
||||
|
|
@ -124,7 +144,7 @@ Item {
|
|||
|
||||
Rectangle {
|
||||
id: workspaceBackground
|
||||
width: parent.width - Style.marginS * scaling * 2
|
||||
width: parent.width
|
||||
|
||||
height: Math.round(Style.capsuleHeight * scaling)
|
||||
radius: Math.round(Style.radiusM * scaling)
|
||||
|
|
@ -145,7 +165,7 @@ Item {
|
|||
model: localWorkspaces
|
||||
Item {
|
||||
id: workspacePillContainer
|
||||
height: (Settings.data.bar.showWorkspaceLabel !== "none") ? Math.round(18 * scaling) : Math.round(14 * scaling)
|
||||
height: (labelMode !== "none") ? Math.round(18 * scaling) : Math.round(14 * scaling)
|
||||
width: root.calculatedWsWidth(model)
|
||||
|
||||
Rectangle {
|
||||
|
|
@ -153,15 +173,13 @@ Item {
|
|||
anchors.fill: parent
|
||||
|
||||
Loader {
|
||||
active: (Settings.data.bar.showWorkspaceLabel !== "none")
|
||||
active: (labelMode !== "none")
|
||||
sourceComponent: Component {
|
||||
Text {
|
||||
// Center horizontally
|
||||
x: (pill.width - width) / 2
|
||||
// Center vertically accounting for font metrics
|
||||
y: (pill.height - height) / 2 + (height - contentHeight) / 2
|
||||
text: {
|
||||
if (Settings.data.bar.showWorkspaceLabel === "name" && model.name && model.name.length > 0) {
|
||||
if (labelMode === "name" && model.name && model.name.length > 0) {
|
||||
return model.name.substring(0, 2)
|
||||
} else {
|
||||
return model.idx.toString()
|
||||
|
|
|
|||
|
|
@ -34,24 +34,28 @@ Variants {
|
|||
|
||||
WlrLayershell.namespace: "noctalia-dock"
|
||||
|
||||
property bool autoHide: Settings.data.dock.autoHide
|
||||
property bool hidden: autoHide
|
||||
property int hideDelay: 500
|
||||
property int showDelay: 100
|
||||
property int hideAnimationDuration: Style.animationFast
|
||||
property int showAnimationDuration: Style.animationFast
|
||||
property int peekHeight: 7 * scaling
|
||||
property int fullHeight: dockContainer.height
|
||||
property int iconSize: 36
|
||||
readonly property bool autoHide: Settings.data.dock.autoHide
|
||||
readonly property int hideDelay: 500
|
||||
readonly property int showDelay: 100
|
||||
readonly property int hideAnimationDuration: Style.animationFast
|
||||
readonly property int showAnimationDuration: Style.animationFast
|
||||
readonly property int peekHeight: 7 * scaling
|
||||
readonly property int fullHeight: dockContainer.height
|
||||
readonly property int iconSize: 36 * scaling
|
||||
readonly property int floatingMargin: 12 * scaling // Margin to make dock float
|
||||
|
||||
// Bar positioning properties
|
||||
property bool barAtBottom: Settings.data.bar.position === "bottom"
|
||||
property int barHeight: barAtBottom ? (Settings.data.bar.height || 30) * scaling : 0
|
||||
property int dockSpacing: 4 * scaling // Space between dock and bar
|
||||
// Bar detection and positioning properties
|
||||
readonly property bool hasBar: modelData.name ? (Settings.data.bar.monitors.includes(modelData.name)
|
||||
|| (Settings.data.bar.monitors.length === 0)) : false
|
||||
readonly property bool barAtBottom: hasBar && Settings.data.bar.position === "bottom"
|
||||
readonly property bool barAtTop: hasBar && Settings.data.bar.position === "top"
|
||||
readonly property int barHeight: (barAtBottom || barAtTop) ? (Settings.data.bar.height || 30) * scaling : 0
|
||||
readonly property int dockSpacing: 8 * scaling // Space between dock and bar/edge
|
||||
|
||||
// Track hover state
|
||||
property bool dockHovered: false
|
||||
property bool anyAppHovered: false
|
||||
property bool hidden: autoHide
|
||||
|
||||
// Dock is positioned at the bottom
|
||||
anchors.bottom: true
|
||||
|
|
@ -63,11 +67,11 @@ Variants {
|
|||
// Make the window transparent
|
||||
color: Color.transparent
|
||||
|
||||
// Set the window size - always include space for peek area when auto-hide is enabled
|
||||
implicitWidth: dockContainer.width
|
||||
implicitHeight: fullHeight + (barAtBottom ? barHeight + dockSpacing : 0)
|
||||
// Set the window size - include extra height only if bar is at bottom
|
||||
implicitWidth: dockContainer.width + (floatingMargin * 2)
|
||||
implicitHeight: fullHeight + floatingMargin + (barAtBottom ? barHeight + dockSpacing : 0)
|
||||
|
||||
// Position the entire window above the bar when bar is at bottom
|
||||
// Position the entire window above the bar only when bar is at bottom
|
||||
margins.bottom: barAtBottom ? barHeight : 0
|
||||
|
||||
// Watch for autoHide setting changes
|
||||
|
|
@ -111,7 +115,7 @@ Variants {
|
|||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: peekHeight + dockSpacing
|
||||
height: peekHeight + floatingMargin + (barAtBottom ? dockSpacing : 0)
|
||||
hoverEnabled: autoHide
|
||||
visible: autoHide
|
||||
|
||||
|
|
@ -130,24 +134,32 @@ Variants {
|
|||
|
||||
Rectangle {
|
||||
id: dockContainer
|
||||
width: dockLayout.implicitWidth + 48 * scaling
|
||||
height: iconSize * 1.4 * scaling
|
||||
width: dockLayout.implicitWidth + Style.marginL * scaling * 2
|
||||
height: Math.round(iconSize * 1.6)
|
||||
color: Qt.alpha(Color.mSurface, Settings.data.dock.backgroundOpacity)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: dockSpacing
|
||||
topLeftRadius: Style.radiusL * scaling
|
||||
topRightRadius: Style.radiusL * scaling
|
||||
anchors.bottomMargin: floatingMargin + (barAtBottom ? dockSpacing : 0)
|
||||
radius: Style.radiusL * scaling
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
border.color: Color.mOutline
|
||||
|
||||
// Animate the dock sliding up and down
|
||||
transform: Translate {
|
||||
y: hidden ? (fullHeight - peekHeight) : 0
|
||||
// Fade and zoom animation properties
|
||||
opacity: hidden ? 0 : 1
|
||||
scale: hidden ? 0.85 : 1
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: hidden ? hideAnimationDuration : showAnimationDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: hidden ? hideAnimationDuration : showAnimationDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: hidden ? hideAnimationDuration : showAnimationDuration
|
||||
easing.type: hidden ? Easing.InQuad : Easing.OutBack
|
||||
easing.overshoot: hidden ? 0 : 1.05
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -179,15 +191,9 @@ Variants {
|
|||
Item {
|
||||
id: dock
|
||||
width: dockLayout.implicitWidth
|
||||
height: parent.height - (20 * scaling)
|
||||
height: parent.height - (Style.marginM * 2 * scaling)
|
||||
anchors.centerIn: parent
|
||||
|
||||
NTooltip {
|
||||
id: appTooltip
|
||||
visible: false
|
||||
positionAbove: true
|
||||
}
|
||||
|
||||
function getAppIcon(toplevel: Toplevel): string {
|
||||
if (!toplevel)
|
||||
return ""
|
||||
|
|
@ -203,39 +209,48 @@ Variants {
|
|||
Repeater {
|
||||
model: ToplevelManager ? ToplevelManager.toplevels : null
|
||||
|
||||
delegate: Rectangle {
|
||||
delegate: Item {
|
||||
id: appButton
|
||||
Layout.preferredWidth: iconSize * scaling
|
||||
Layout.preferredHeight: iconSize * scaling
|
||||
Layout.preferredWidth: iconSize
|
||||
Layout.preferredHeight: iconSize
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
color: Color.transparent
|
||||
radius: Style.radiusM * scaling
|
||||
|
||||
property bool isActive: ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === modelData
|
||||
property bool hovered: appMouseArea.containsMouse
|
||||
property string appId: modelData ? modelData.appId : ""
|
||||
property string appTitle: modelData ? modelData.title : ""
|
||||
|
||||
// The icon
|
||||
// Individual tooltip for this app
|
||||
NTooltip {
|
||||
id: appTooltip
|
||||
target: appButton
|
||||
positionAbove: true
|
||||
visible: false
|
||||
}
|
||||
|
||||
// The icon with better quality settings
|
||||
Image {
|
||||
id: appIcon
|
||||
width: iconSize * scaling
|
||||
height: iconSize * scaling
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
anchors.centerIn: parent
|
||||
source: dock.getAppIcon(modelData)
|
||||
visible: source.toString() !== ""
|
||||
sourceSize.width: iconSize * 2
|
||||
sourceSize.height: iconSize * 2
|
||||
smooth: true
|
||||
mipmap: false
|
||||
antialiasing: false
|
||||
mipmap: true
|
||||
antialiasing: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
cache: true
|
||||
|
||||
scale: appButton.hovered ? 1.1 : 1.0
|
||||
scale: appButton.hovered ? 1.15 : 1.0
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutBack
|
||||
easing.overshoot: 1.2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -246,15 +261,15 @@ Variants {
|
|||
visible: !appIcon.visible
|
||||
text: "question_mark"
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pointSize: iconSize * 0.7 * scaling
|
||||
font.pointSize: iconSize * 0.7
|
||||
color: appButton.isActive ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
|
||||
scale: appButton.hovered ? 1.1 : 1.0
|
||||
scale: appButton.hovered ? 1.15 : 1.0
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutBack
|
||||
easing.overshoot: 1.2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -270,7 +285,6 @@ Variants {
|
|||
anyAppHovered = true
|
||||
const appName = appButton.appTitle || appButton.appId || "Unknown"
|
||||
appTooltip.text = appName.length > 40 ? appName.substring(0, 37) + "..." : appName
|
||||
appTooltip.target = appButton
|
||||
appTooltip.isVisible = true
|
||||
if (autoHide) {
|
||||
showTimer.stop()
|
||||
|
|
@ -300,15 +314,32 @@ Variants {
|
|||
}
|
||||
}
|
||||
|
||||
// Active indicator
|
||||
Rectangle {
|
||||
visible: isActive
|
||||
width: iconSize * 0.25
|
||||
height: 4 * scaling
|
||||
width: iconSize * 0.2
|
||||
height: iconSize * 0.1
|
||||
color: Color.mPrimary
|
||||
radius: Style.radiusXS
|
||||
radius: Style.radiusXS * scaling
|
||||
anchors.top: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: Style.marginXXS * scaling
|
||||
anchors.topMargin: Style.marginXXS * 1.5 * scaling
|
||||
|
||||
// Pulse animation for active indicator
|
||||
SequentialAnimation on opacity {
|
||||
running: isActive
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation {
|
||||
to: 0.6
|
||||
duration: Style.animationSlowest
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
NumberAnimation {
|
||||
to: 1.0
|
||||
duration: Style.animationSlowest
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,29 +58,6 @@ Loader {
|
|||
property real percent: isReady ? (battery.percentage * 100) : 0
|
||||
property bool charging: isReady ? battery.state === UPowerDeviceState.Charging : false
|
||||
property bool batteryVisible: isReady && percent > 0
|
||||
|
||||
function getIcon() {
|
||||
if (!batteryVisible)
|
||||
return ""
|
||||
if (charging)
|
||||
return "battery_android_bolt"
|
||||
if (percent >= 95)
|
||||
return "battery_android_full"
|
||||
if (percent >= 85)
|
||||
return "battery_android_6"
|
||||
if (percent >= 70)
|
||||
return "battery_android_5"
|
||||
if (percent >= 55)
|
||||
return "battery_android_4"
|
||||
if (percent >= 40)
|
||||
return "battery_android_3"
|
||||
if (percent >= 25)
|
||||
return "battery_android_2"
|
||||
if (percent >= 10)
|
||||
return "battery_android_1"
|
||||
if (percent >= 0)
|
||||
return "battery_android_0"
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
|
|
@ -420,7 +397,7 @@ Loader {
|
|||
anchors.bottomMargin: Style.marginM * scaling
|
||||
anchors.leftMargin: Style.marginL * scaling
|
||||
anchors.rightMargin: Style.marginL * scaling
|
||||
spacing: Style.marginM * scaling
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NText {
|
||||
text: "SECURE TERMINAL"
|
||||
|
|
@ -431,23 +408,6 @@ Loader {
|
|||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
visible: batteryIndicator.batteryVisible
|
||||
NIcon {
|
||||
text: batteryIndicator.getIcon()
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurface
|
||||
}
|
||||
NText {
|
||||
text: Math.round(batteryIndicator.percent) + "%"
|
||||
color: Color.mOnSurface
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
NText {
|
||||
|
|
@ -463,6 +423,25 @@ Loader {
|
|||
color: Color.mOnSurface
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
visible: batteryIndicator.batteryVisible
|
||||
NIcon {
|
||||
text: BatteryService.getIcon(batteryIndicator.percent, batteryIndicator.charging,
|
||||
batteryIndicator.isReady)
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurface
|
||||
rotation: -90
|
||||
}
|
||||
NText {
|
||||
text: Math.round(batteryIndicator.percent) + "%"
|
||||
color: Color.mOnSurface
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ NBox {
|
|||
const totalSum = JSON.stringify(widget).split('').reduce((acc, character) => {
|
||||
return acc + character.charCodeAt(0)
|
||||
}, 0)
|
||||
switch (totalSum % 10) {
|
||||
switch (totalSum % 5) {
|
||||
case 0:
|
||||
return Color.mPrimary
|
||||
case 1:
|
||||
|
|
@ -50,16 +50,6 @@ NBox {
|
|||
return Color.mError
|
||||
case 4:
|
||||
return Color.mOnSurface
|
||||
case 5:
|
||||
return Qt.darker(Color.mPrimary, 1.3)
|
||||
case 6:
|
||||
return Qt.darker(Color.mSecondary, 1.3)
|
||||
case 7:
|
||||
return Qt.darker(Color.mTertiary, 1.3)
|
||||
case 8:
|
||||
return Qt.darker(Color.mError, 1.3)
|
||||
case 9:
|
||||
return Qt.darker(Color.mOnSurface, 1.3)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +65,7 @@ NBox {
|
|||
text: sectionName + " Section"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
|
|
@ -89,7 +79,7 @@ NBox {
|
|||
description: ""
|
||||
placeholder: "Select a widget to add..."
|
||||
onSelected: key => comboBox.currentKey = key
|
||||
popupHeight: 240 * scaling
|
||||
popupHeight: 340 * scaling
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
|
@ -188,13 +178,33 @@ NBox {
|
|||
colorBgHover: Qt.alpha(Color.mOnPrimary, Style.opacityLight)
|
||||
colorFgHover: Color.mOnPrimary
|
||||
onClicked: {
|
||||
var dialog = Qt.createComponent("BarWidgetSettingsDialog.qml").createObject(root, {
|
||||
"widgetIndex": index,
|
||||
"widgetData": modelData,
|
||||
"widgetId": modelData.id,
|
||||
"parent": Overlay.overlay
|
||||
})
|
||||
dialog.open()
|
||||
var component = Qt.createComponent(Qt.resolvedUrl("BarWidgetSettingsDialog.qml"))
|
||||
function instantiateAndOpen() {
|
||||
var dialog = component.createObject(root, {
|
||||
"widgetIndex": index,
|
||||
"widgetData": modelData,
|
||||
"widgetId": modelData.id,
|
||||
"parent": Overlay.overlay
|
||||
})
|
||||
if (dialog) {
|
||||
dialog.open()
|
||||
} else {
|
||||
Logger.error("BarSectionEditor", "Failed to create settings dialog instance")
|
||||
}
|
||||
}
|
||||
if (component.status === Component.Ready) {
|
||||
instantiateAndOpen()
|
||||
} else if (component.status === Component.Error) {
|
||||
Logger.error("BarSectionEditor", component.errorString())
|
||||
} else {
|
||||
component.statusChanged.connect(function () {
|
||||
if (component.status === Component.Ready) {
|
||||
instantiateAndOpen()
|
||||
} else if (component.status === Component.Error) {
|
||||
Logger.error("BarSectionEditor", component.errorString())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -221,7 +231,7 @@ NBox {
|
|||
MouseArea {
|
||||
id: flowDragArea
|
||||
anchors.fill: parent
|
||||
z: 999 // Above all widgets to ensure it gets events first
|
||||
z: -1 // Ensure this mouse area is below the Settings and Close buttons
|
||||
|
||||
// Critical properties for proper event handling
|
||||
acceptedButtons: Qt.LeftButton
|
||||
134
Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml
Normal file
134
Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
import "./WidgetSettings" as WidgetSettings
|
||||
|
||||
// Widget Settings Dialog Component
|
||||
Popup {
|
||||
id: settingsPopup
|
||||
|
||||
property int widgetIndex: -1
|
||||
property var widgetData: null
|
||||
property string widgetId: ""
|
||||
|
||||
// Center popup in parent
|
||||
x: (parent.width - width) * 0.5
|
||||
y: (parent.height - height) * 0.5
|
||||
|
||||
width: 420 * scaling
|
||||
height: content.implicitHeight + padding * 2
|
||||
padding: Style.marginXL * scaling
|
||||
modal: true
|
||||
|
||||
background: Rectangle {
|
||||
id: bgRect
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusL * scaling
|
||||
border.color: Color.mPrimary
|
||||
border.width: Style.borderM * scaling
|
||||
}
|
||||
|
||||
// Load settings when popup opens with data
|
||||
onOpened: {
|
||||
if (widgetData && widgetId) {
|
||||
loadWidgetSettings()
|
||||
}
|
||||
}
|
||||
|
||||
function loadWidgetSettings() {
|
||||
const widgetSettingsMap = {
|
||||
"ActiveWindow": "WidgetSettings/ActiveWindowSettings.qml",
|
||||
"Battery": "WidgetSettings/BatterySettings.qml",
|
||||
"Brightness": "WidgetSettings/BrightnessSettings.qml",
|
||||
"Clock": "WidgetSettings/ClockSettings.qml",
|
||||
"CustomButton": "WidgetSettings/CustomButtonSettings.qml",
|
||||
"MediaMini": "WidgetSettings/MediaMiniSettings.qml",
|
||||
"Microphone": "WidgetSettings/MicrophoneSettings.qml",
|
||||
"NotificationHistory": "WidgetSettings/NotificationHistorySettings.qml",
|
||||
"Workspace": "WidgetSettings/WorkspaceSettings.qml",
|
||||
"SidePanelToggle": "WidgetSettings/SidePanelToggleSettings.qml",
|
||||
"Spacer": "WidgetSettings/SpacerSettings.qml",
|
||||
"SystemMonitor": "WidgetSettings/SystemMonitorSettings.qml",
|
||||
"Volume": "WidgetSettings/VolumeSettings.qml"
|
||||
}
|
||||
|
||||
const source = widgetSettingsMap[widgetId]
|
||||
if (source) {
|
||||
// Use setSource to pass properties at creation time
|
||||
settingsLoader.setSource(source, {
|
||||
"widgetData": widgetData,
|
||||
"widgetMetadata": BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
width: parent.width
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Title
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: `${settingsPopup.widgetId} Settings`
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
onClicked: settingsPopup.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: Color.mOutline
|
||||
}
|
||||
|
||||
// Settings based on widget type
|
||||
// Will be triggered via settingsLoader.setSource()
|
||||
Loader {
|
||||
id: settingsLoader
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Action buttons
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM * scaling
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: "Cancel"
|
||||
outlined: true
|
||||
onClicked: settingsPopup.close()
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: "Apply"
|
||||
icon: "check"
|
||||
onClicked: {
|
||||
if (settingsLoader.item && settingsLoader.item.saveSettings) {
|
||||
var newSettings = settingsLoader.item.saveSettings()
|
||||
root.updateWidgetSettings(sectionId, settingsPopup.widgetIndex, newSettings)
|
||||
settingsPopup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local state
|
||||
property bool valueShowIcon: widgetData.showIcon !== undefined ? widgetData.showIcon : widgetMetadata.showIcon
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.showIcon = valueShowIcon
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showIcon
|
||||
Layout.fillWidth: true
|
||||
label: "Show app icon"
|
||||
checked: root.valueShowIcon
|
||||
onToggled: checked => root.valueShowIcon = checked
|
||||
}
|
||||
}
|
||||
31
Modules/SettingsPanel/Bar/WidgetSettings/BatterySettings.qml
Normal file
31
Modules/SettingsPanel/Bar/WidgetSettings/BatterySettings.qml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local state
|
||||
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
|
||||
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.alwaysShowPercentage = valueAlwaysShowPercentage
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Always show percentage"
|
||||
checked: root.valueAlwaysShowPercentage
|
||||
onToggled: checked => root.valueAlwaysShowPercentage = checked
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local state
|
||||
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
|
||||
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.alwaysShowPercentage = valueAlwaysShowPercentage
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Always show percentage"
|
||||
checked: valueAlwaysShowPercentage
|
||||
onToggled: checked => valueAlwaysShowPercentage = checked
|
||||
}
|
||||
}
|
||||
54
Modules/SettingsPanel/Bar/WidgetSettings/ClockSettings.qml
Normal file
54
Modules/SettingsPanel/Bar/WidgetSettings/ClockSettings.qml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local state
|
||||
property bool valueShowDate: widgetData.showDate !== undefined ? widgetData.showDate : widgetMetadata.showDate
|
||||
property bool valueUse12h: widgetData.use12HourClock !== undefined ? widgetData.use12HourClock : widgetMetadata.use12HourClock
|
||||
property bool valueShowSeconds: widgetData.showSeconds !== undefined ? widgetData.showSeconds : widgetMetadata.showSeconds
|
||||
property bool valueReverseDayMonth: widgetData.reverseDayMonth !== undefined ? widgetData.reverseDayMonth : widgetMetadata.reverseDayMonth
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.showDate = valueShowDate
|
||||
settings.use12HourClock = valueUse12h
|
||||
settings.showSeconds = valueShowSeconds
|
||||
settings.reverseDayMonth = valueReverseDayMonth
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show date"
|
||||
checked: valueShowDate
|
||||
onToggled: checked => valueShowDate = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Use 12-hour clock"
|
||||
checked: valueUse12h
|
||||
onToggled: checked => valueUse12h = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show seconds"
|
||||
checked: valueShowSeconds
|
||||
onToggled: checked => valueShowSeconds = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Reverse day and month"
|
||||
checked: valueReverseDayMonth
|
||||
onToggled: checked => valueReverseDayMonth = checked
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.icon = iconInput.text
|
||||
settings.leftClickExec = leftClickExecInput.text
|
||||
settings.rightClickExec = rightClickExecInput.text
|
||||
settings.middleClickExec = middleClickExecInput.text
|
||||
return settings
|
||||
}
|
||||
|
||||
// Icon setting
|
||||
NTextInput {
|
||||
id: iconInput
|
||||
Layout.fillWidth: true
|
||||
label: "Icon Name"
|
||||
description: "Choose a name from the Material Icon set."
|
||||
placeholderText: "Enter icon name (e.g., favorite, home, settings)"
|
||||
text: widgetData?.icon || widgetMetadata.icon
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: leftClickExecInput
|
||||
Layout.fillWidth: true
|
||||
label: "Left Click Command"
|
||||
placeholderText: "Enter command to execute (app or custom script)"
|
||||
text: widgetData?.leftClickExec || widgetMetadata.leftClickExec
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: rightClickExecInput
|
||||
Layout.fillWidth: true
|
||||
label: "Right Click Command"
|
||||
placeholderText: "Enter command to execute (app or custom script)"
|
||||
text: widgetData?.rightClickExec || widgetMetadata.rightClickExec
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: middleClickExecInput
|
||||
Layout.fillWidth: true
|
||||
label: "Middle Click Command"
|
||||
placeholderText: "Enter command to execute (app or custom script)"
|
||||
text: widgetData.middleClickExec || widgetMetadata.middleClickExec
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local state
|
||||
property bool valueShowAlbumArt: widgetData.showAlbumArt !== undefined ? widgetData.showAlbumArt : widgetMetadata.showAlbumArt
|
||||
property bool valueShowVisualizer: widgetData.showVisualizer !== undefined ? widgetData.showVisualizer : widgetMetadata.showVisualizer
|
||||
property string valueVisualizerType: widgetData.visualizerType || widgetMetadata.visualizerType
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.showAlbumArt = valueShowAlbumArt
|
||||
settings.showVisualizer = valueShowVisualizer
|
||||
settings.visualizerType = valueVisualizerType
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show album art"
|
||||
checked: valueShowAlbumArt
|
||||
onToggled: checked => valueShowAlbumArt = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show visualizer"
|
||||
checked: valueShowVisualizer
|
||||
onToggled: checked => valueShowVisualizer = checked
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
visible: valueShowVisualizer
|
||||
label: "Visualizer type"
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "linear"
|
||||
name: "Linear"
|
||||
}
|
||||
ListElement {
|
||||
key: "mirrored"
|
||||
name: "Mirrored"
|
||||
}
|
||||
ListElement {
|
||||
key: "wave"
|
||||
name: "Wave"
|
||||
}
|
||||
}
|
||||
currentKey: valueVisualizerType
|
||||
onSelected: key => valueVisualizerType = key
|
||||
minimumWidth: 200 * scaling
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local state
|
||||
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
|
||||
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.alwaysShowPercentage = valueAlwaysShowPercentage
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Always show percentage"
|
||||
checked: valueAlwaysShowPercentage
|
||||
onToggled: checked => valueAlwaysShowPercentage = checked
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local state
|
||||
property bool valueShowUnreadBadge: widgetData.showUnreadBadge !== undefined ? widgetData.showUnreadBadge : widgetMetadata.showUnreadBadge
|
||||
property bool valueHideWhenZero: widgetData.hideWhenZero !== undefined ? widgetData.hideWhenZero : widgetMetadata.hideWhenZero
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.showUnreadBadge = valueShowUnreadBadge
|
||||
settings.hideWhenZero = valueHideWhenZero
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show unread badge"
|
||||
checked: valueShowUnreadBadge
|
||||
onToggled: checked => valueShowUnreadBadge = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Hide badge when zero"
|
||||
checked: valueHideWhenZero
|
||||
onToggled: checked => valueHideWhenZero = checked
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local state
|
||||
property bool valueUseDistroLogo: widgetData.useDistroLogo !== undefined ? widgetData.useDistroLogo : widgetMetadata.useDistroLogo
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.useDistroLogo = valueUseDistroLogo
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Use distro logo instead of icon"
|
||||
checked: valueUseDistroLogo
|
||||
onToggled: checked => valueUseDistroLogo = checked
|
||||
}
|
||||
}
|
||||
30
Modules/SettingsPanel/Bar/WidgetSettings/SpacerSettings.qml
Normal file
30
Modules/SettingsPanel/Bar/WidgetSettings/SpacerSettings.qml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.width = parseInt(widthInput.text) || widgetMetadata.width
|
||||
return settings
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: widthInput
|
||||
Layout.fillWidth: true
|
||||
label: "Width"
|
||||
description: "Spacing width in pixels"
|
||||
text: widgetData.width || widgetMetadata.width
|
||||
placeholderText: "Enter width in pixels"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local, editable state for checkboxes
|
||||
property bool valueShowCpuUsage: widgetData.showCpuUsage !== undefined ? widgetData.showCpuUsage : widgetMetadata.showCpuUsage
|
||||
property bool valueShowCpuTemp: widgetData.showCpuTemp !== undefined ? widgetData.showCpuTemp : widgetMetadata.showCpuTemp
|
||||
property bool valueShowMemoryUsage: widgetData.showMemoryUsage !== undefined ? widgetData.showMemoryUsage : widgetMetadata.showMemoryUsage
|
||||
property bool valueShowMemoryAsPercent: widgetData.showMemoryAsPercent
|
||||
!== undefined ? widgetData.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
|
||||
property bool valueShowNetworkStats: widgetData.showNetworkStats
|
||||
!== undefined ? widgetData.showNetworkStats : widgetMetadata.showNetworkStats
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.showCpuUsage = valueShowCpuUsage
|
||||
settings.showCpuTemp = valueShowCpuTemp
|
||||
settings.showMemoryUsage = valueShowMemoryUsage
|
||||
settings.showMemoryAsPercent = valueShowMemoryAsPercent
|
||||
settings.showNetworkStats = valueShowNetworkStats
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showCpuUsage
|
||||
Layout.fillWidth: true
|
||||
label: "CPU usage"
|
||||
checked: valueShowCpuUsage
|
||||
onToggled: checked => valueShowCpuUsage = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showCpuTemp
|
||||
Layout.fillWidth: true
|
||||
label: "CPU temperature"
|
||||
checked: valueShowCpuTemp
|
||||
onToggled: checked => valueShowCpuTemp = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showMemoryUsage
|
||||
Layout.fillWidth: true
|
||||
label: "Memory usage"
|
||||
checked: valueShowMemoryUsage
|
||||
onToggled: checked => valueShowMemoryUsage = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showMemoryAsPercent
|
||||
Layout.fillWidth: true
|
||||
label: "Show memory as percentage"
|
||||
checked: valueShowMemoryAsPercent
|
||||
onToggled: checked => valueShowMemoryAsPercent = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showNetworkStats
|
||||
Layout.fillWidth: true
|
||||
label: "Network traffic"
|
||||
checked: valueShowNetworkStats
|
||||
onToggled: checked => valueShowNetworkStats = checked
|
||||
}
|
||||
}
|
||||
31
Modules/SettingsPanel/Bar/WidgetSettings/VolumeSettings.qml
Normal file
31
Modules/SettingsPanel/Bar/WidgetSettings/VolumeSettings.qml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local state
|
||||
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
|
||||
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.alwaysShowPercentage = valueAlwaysShowPercentage
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Always show percentage"
|
||||
checked: valueAlwaysShowPercentage
|
||||
onToggled: checked => valueAlwaysShowPercentage = checked
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.labelMode = labelModeCombo.currentKey
|
||||
return settings
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
id: labelModeCombo
|
||||
|
||||
label: "Label Mode"
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "none"
|
||||
name: "None"
|
||||
}
|
||||
ListElement {
|
||||
key: "index"
|
||||
name: "Index"
|
||||
}
|
||||
ListElement {
|
||||
key: "name"
|
||||
name: "Name"
|
||||
}
|
||||
}
|
||||
currentKey: widgetData.labelMode || widgetMetadata.labelMode
|
||||
onSelected: key => labelModeCombo.currentKey = key
|
||||
minimumWidth: 200 * scaling
|
||||
}
|
||||
}
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
// Widget Settings Dialog Component
|
||||
Popup {
|
||||
id: settingsPopup
|
||||
|
||||
property int widgetIndex: -1
|
||||
property var widgetData: null
|
||||
property string widgetId: ""
|
||||
|
||||
// Center popup in parent
|
||||
x: (parent.width - width) * 0.5
|
||||
y: (parent.height - height) * 0.5
|
||||
|
||||
width: 420 * scaling
|
||||
height: content.implicitHeight + padding * 2
|
||||
padding: Style.marginXL * scaling
|
||||
modal: true
|
||||
|
||||
background: Rectangle {
|
||||
id: bgRect
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusL * scaling
|
||||
border.color: Color.mPrimary
|
||||
border.width: Style.borderM * scaling
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
width: parent.width
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Title
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Widget Settings: " + settingsPopup.widgetId
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
onClicked: settingsPopup.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: Color.mOutline
|
||||
}
|
||||
|
||||
// Settings based on widget type
|
||||
Loader {
|
||||
id: settingsLoader
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: {
|
||||
if (settingsPopup.widgetId === "CustomButton") {
|
||||
return customButtonSettings
|
||||
} else if (settingsPopup.widgetId === "Spacer") {
|
||||
return spacerSettings
|
||||
}
|
||||
// Add more widget settings components here as needed
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// Action buttons
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM * scaling
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: "Cancel"
|
||||
outlined: true
|
||||
onClicked: settingsPopup.close()
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: "Save"
|
||||
onClicked: {
|
||||
if (settingsLoader.item && settingsLoader.item.saveSettings) {
|
||||
var newSettings = settingsLoader.item.saveSettings()
|
||||
root.updateWidgetSettings(sectionId, settingsPopup.widgetIndex, newSettings)
|
||||
settingsPopup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CustomButton settings component
|
||||
Component {
|
||||
id: customButtonSettings
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, settingsPopup.widgetData)
|
||||
settings.icon = iconInput.text
|
||||
settings.leftClickExec = leftClickExecInput.text
|
||||
settings.rightClickExec = rightClickExecInput.text
|
||||
settings.middleClickExec = middleClickExecInput.text
|
||||
return settings
|
||||
}
|
||||
|
||||
// Icon setting
|
||||
NTextInput {
|
||||
id: iconInput
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
label: "Icon Name"
|
||||
description: "Use Material Icon names from the icon set."
|
||||
text: settingsPopup.widgetData.icon || ""
|
||||
placeholderText: "Enter icon name (e.g., favorite, home, settings)"
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: leftClickExecInput
|
||||
Layout.fillWidth: true
|
||||
label: "Left Click Command"
|
||||
description: "Command or application to run when left clicked."
|
||||
text: settingsPopup.widgetData.leftClickExec || ""
|
||||
placeholderText: "Enter command to execute (app or custom script)"
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: rightClickExecInput
|
||||
Layout.fillWidth: true
|
||||
label: "Right Click Command"
|
||||
description: "Command or application to run when right clicked."
|
||||
text: settingsPopup.widgetData.rightClickExec || ""
|
||||
placeholderText: "Enter command to execute (app or custom script)"
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: middleClickExecInput
|
||||
Layout.fillWidth: true
|
||||
label: "Middle Click Command"
|
||||
description: "Command or application to run when middle clicked."
|
||||
text: settingsPopup.widgetData.middleClickExec || ""
|
||||
placeholderText: "Enter command to execute (app or custom script)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer settings component
|
||||
Component {
|
||||
id: spacerSettings
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, settingsPopup.widgetData)
|
||||
settings.width = parseInt(widthInput.text) || 20
|
||||
return settings
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: widthInput
|
||||
Layout.fillWidth: true
|
||||
label: "Width (pixels)"
|
||||
description: "Width of the spacer in pixels."
|
||||
text: settingsPopup.widgetData.width || "20"
|
||||
placeholderText: "Enter width in pixels"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ NPanel {
|
|||
General,
|
||||
Network,
|
||||
ScreenRecorder,
|
||||
TimeWeather,
|
||||
Weather,
|
||||
Wallpaper,
|
||||
WallpaperSelector
|
||||
}
|
||||
|
|
@ -90,8 +90,8 @@ NPanel {
|
|||
Tabs.NetworkTab {}
|
||||
}
|
||||
Component {
|
||||
id: timeWeatherTab
|
||||
Tabs.TimeWeatherTab {}
|
||||
id: weatherTab
|
||||
Tabs.WeatherTab {}
|
||||
}
|
||||
Component {
|
||||
id: colorSchemeTab
|
||||
|
|
@ -156,10 +156,10 @@ NPanel {
|
|||
"icon": "brightness_6",
|
||||
"source": brightnessTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.TimeWeather,
|
||||
"label": "Time & Weather",
|
||||
"icon": "schedule",
|
||||
"source": timeWeatherTab
|
||||
"id": SettingsPanel.Tab.Weather,
|
||||
"label": "Weather",
|
||||
"icon": "partly_cloudy_day",
|
||||
"source": weatherTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.ColorScheme,
|
||||
"label": "Color Scheme",
|
||||
|
|
@ -368,7 +368,7 @@ NPanel {
|
|||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS * scaling
|
||||
spacing: Style.marginXS * 1.5 * scaling
|
||||
spacing: Style.marginXS * scaling
|
||||
|
||||
Repeater {
|
||||
id: sections
|
||||
|
|
@ -398,7 +398,8 @@ NPanel {
|
|||
RowLayout {
|
||||
id: tabEntryRow
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS * scaling
|
||||
anchors.leftMargin: Style.marginS * scaling
|
||||
anchors.rightMargin: Style.marginS * scaling
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
// Tab icon
|
||||
|
|
|
|||
|
|
@ -242,21 +242,7 @@ ColumnLayout {
|
|||
Layout.bottomMargin: Style.marginS * scaling
|
||||
}
|
||||
|
||||
// Miniplayer section
|
||||
NToggle {
|
||||
label: "Show Album Art In Bar Media Player"
|
||||
description: "Show the album art of the currently playing song next to the title."
|
||||
checked: Settings.data.audio.showMiniplayerAlbumArt
|
||||
onToggled: checked => Settings.data.audio.showMiniplayerAlbumArt = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show Audio Visualizer In Bar Media Player"
|
||||
description: "Shows an audio visualizer in the background of the miniplayer."
|
||||
checked: Settings.data.audio.showMiniplayerCava
|
||||
onToggled: checked => Settings.data.audio.showMiniplayerCava = checked
|
||||
}
|
||||
// Preferred player (persistent)
|
||||
// Preferred player
|
||||
NTextInput {
|
||||
label: "Preferred Player"
|
||||
description: "Substring to match MPRIS player (identity/bus/desktop)."
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import QtQuick.Layouts
|
|||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.SettingsPanel.Extras
|
||||
import qs.Modules.SettingsPanel.Bar
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
|
@ -70,57 +70,6 @@ ColumnLayout {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show Active Window's Icon"
|
||||
description: "Display the app icon next to the title of the currently focused window."
|
||||
checked: Settings.data.bar.showActiveWindowIcon
|
||||
onToggled: checked => Settings.data.bar.showActiveWindowIcon = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show Battery Percentage"
|
||||
description: "Display battery percentage at all times."
|
||||
checked: Settings.data.bar.alwaysShowBatteryPercentage
|
||||
onToggled: checked => Settings.data.bar.alwaysShowBatteryPercentage = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show Network Statistics"
|
||||
description: "Display network upload and download speeds in the system monitor."
|
||||
checked: Settings.data.bar.showNetworkStats
|
||||
onToggled: checked => Settings.data.bar.showNetworkStats = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Replace SidePanel toggle with distro logo"
|
||||
description: "Show distro logo instead of the SidePanel toggle button in the bar."
|
||||
checked: Settings.data.bar.useDistroLogo
|
||||
onToggled: checked => {
|
||||
Settings.data.bar.useDistroLogo = checked
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: "Show Workspaces Labels"
|
||||
description: "Show the workspace name or index within the workspace indicator."
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "none"
|
||||
name: "None"
|
||||
}
|
||||
ListElement {
|
||||
key: "index"
|
||||
name: "Index"
|
||||
}
|
||||
ListElement {
|
||||
key: "name"
|
||||
name: "Name"
|
||||
}
|
||||
}
|
||||
currentKey: Settings.data.bar.showWorkspaceLabel
|
||||
onSelected: key => Settings.data.bar.showWorkspaceLabel = key
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
|
|
@ -138,7 +87,7 @@ ColumnLayout {
|
|||
text: "Widgets Positioning"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
color: Color.mSecondary
|
||||
Layout.bottomMargin: Style.marginS * scaling
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -299,8 +299,7 @@ ColumnLayout {
|
|||
currentKey: Settings.data.nightLight.manualSunrise
|
||||
placeholder: "Select start time"
|
||||
onSelected: key => Settings.data.nightLight.manualSunrise = key
|
||||
|
||||
preferredWidth: 120 * scaling
|
||||
minimumWidth: 120 * scaling
|
||||
}
|
||||
|
||||
Item {// add a little more spacing
|
||||
|
|
@ -316,8 +315,7 @@ ColumnLayout {
|
|||
currentKey: Settings.data.nightLight.manualSunset
|
||||
placeholder: "Select stop time"
|
||||
onSelected: key => Settings.data.nightLight.manualSunset = key
|
||||
|
||||
preferredWidth: 120 * scaling
|
||||
minimumWidth: 120 * scaling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import qs.Widgets
|
|||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
// Cache for scheme JSON (can be flat or {dark, light})
|
||||
property var schemeColorsCache: ({})
|
||||
|
|
@ -103,225 +104,218 @@ ColumnLayout {
|
|||
}
|
||||
}
|
||||
|
||||
// Main Toggles - Dark Mode / Matugen
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 0
|
||||
// Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants)
|
||||
NToggle {
|
||||
label: "Dark Mode"
|
||||
description: Settings.data.colorSchemes.useWallpaperColors ? "Generate dark theme colors when using Matugen." : "Use a dark variant if available."
|
||||
checked: Settings.data.colorSchemes.darkMode
|
||||
enabled: true
|
||||
onToggled: checked => Settings.data.colorSchemes.darkMode = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
// Use Matugen
|
||||
NToggle {
|
||||
label: "Enable Matugen"
|
||||
description: "Automatically generate colors based on your active wallpaper."
|
||||
checked: Settings.data.colorSchemes.useWallpaperColors
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
// Check if matugen is installed
|
||||
matugenCheck.running = true
|
||||
} else {
|
||||
Settings.data.colorSchemes.useWallpaperColors = false
|
||||
ToastService.showNotice("Matugen", "Disabled")
|
||||
|
||||
// Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants)
|
||||
NToggle {
|
||||
label: "Dark Mode"
|
||||
description: Settings.data.colorSchemes.useWallpaperColors ? "Generate dark theme colors when using Matugen." : "Use a dark variant if available."
|
||||
checked: Settings.data.colorSchemes.darkMode
|
||||
enabled: true
|
||||
onToggled: checked => Settings.data.colorSchemes.darkMode = checked
|
||||
}
|
||||
if (Settings.data.colorSchemes.predefinedScheme) {
|
||||
|
||||
// Use Matugen
|
||||
NToggle {
|
||||
label: "Enable Matugen"
|
||||
description: "Automatically generate colors based on your active wallpaper."
|
||||
checked: Settings.data.colorSchemes.useWallpaperColors
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
// Check if matugen is installed
|
||||
matugenCheck.running = true
|
||||
} else {
|
||||
Settings.data.colorSchemes.useWallpaperColors = false
|
||||
ToastService.showNotice("Matugen", "Disabled")
|
||||
|
||||
if (Settings.data.colorSchemes.predefinedScheme) {
|
||||
|
||||
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
|
||||
}
|
||||
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
Layout.fillWidth: true
|
||||
// Predefined Color Schemes
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Predefined Color Schemes"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
}
|
||||
NText {
|
||||
text: "Predefined Color Schemes"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "To use these color schemes, you must turn off Matugen. With Matugen enabled, colors are automatically generated from your wallpaper."
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
// Color Schemes Grid
|
||||
GridLayout {
|
||||
columns: 3
|
||||
rowSpacing: Style.marginM * scaling
|
||||
columnSpacing: Style.marginM * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
Repeater {
|
||||
model: ColorSchemeService.schemes
|
||||
|
||||
Rectangle {
|
||||
id: schemeCard
|
||||
|
||||
property string schemePath: modelData
|
||||
|
||||
NText {
|
||||
text: "These color schemes are only active when 'Use Matugen' is turned off. With Matugen enabled, colors will be automatically generated from your wallpaper. You can still switch between light and dark themes while using Matugen."
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
Layout.preferredHeight: 120 * scaling
|
||||
radius: Style.radiusM * scaling
|
||||
color: getSchemeColor(modelData, "mSurface")
|
||||
border.width: Math.max(1, Style.borderL * scaling)
|
||||
border.color: (!Settings.data.colorSchemes.useWallpaperColors
|
||||
&& (Settings.data.colorSchemes.predefinedScheme === modelData)) ? Color.mPrimary : Color.mOutline
|
||||
scale: root.cardScaleLow
|
||||
|
||||
// Color Schemes Grid
|
||||
GridLayout {
|
||||
columns: 3
|
||||
rowSpacing: Style.marginM * scaling
|
||||
columnSpacing: Style.marginM * scaling
|
||||
Layout.fillWidth: true
|
||||
// Mouse area for selection
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
// Disable useWallpaperColors when picking a predefined color scheme
|
||||
Settings.data.colorSchemes.useWallpaperColors = false
|
||||
Logger.log("ColorSchemeTab", "Disabled matugen setting")
|
||||
|
||||
Repeater {
|
||||
model: ColorSchemeService.schemes
|
||||
Settings.data.colorSchemes.predefinedScheme = schemePath
|
||||
ColorSchemeService.applyScheme(schemePath)
|
||||
}
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
Rectangle {
|
||||
id: schemeCard
|
||||
|
||||
property string schemePath: modelData
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 120 * scaling
|
||||
radius: Style.radiusM * scaling
|
||||
color: getSchemeColor(modelData, "mSurface")
|
||||
border.width: Math.max(1, Style.borderL * scaling)
|
||||
border.color: (!Settings.data.colorSchemes.useWallpaperColors
|
||||
&& (Settings.data.colorSchemes.predefinedScheme === modelData)) ? Color.mPrimary : Color.mOutline
|
||||
scale: root.cardScaleLow
|
||||
|
||||
// Mouse area for selection
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
// Disable useWallpaperColors when picking a predefined color scheme
|
||||
Settings.data.colorSchemes.useWallpaperColors = false
|
||||
Logger.log("ColorSchemeTab", "Disabled matugen setting")
|
||||
|
||||
Settings.data.colorSchemes.predefinedScheme = schemePath
|
||||
ColorSchemeService.applyScheme(schemePath)
|
||||
}
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onEntered: {
|
||||
schemeCard.scale = root.cardScaleHigh
|
||||
}
|
||||
|
||||
onExited: {
|
||||
schemeCard.scale = root.cardScaleLow
|
||||
}
|
||||
onEntered: {
|
||||
schemeCard.scale = root.cardScaleHigh
|
||||
}
|
||||
|
||||
// Card content
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXL * scaling
|
||||
onExited: {
|
||||
schemeCard.scale = root.cardScaleLow
|
||||
}
|
||||
}
|
||||
|
||||
// Card content
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXL * scaling
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
// Scheme name
|
||||
NText {
|
||||
text: {
|
||||
// Remove json and the full path
|
||||
var chunks = schemePath.replace(".json", "").split("/")
|
||||
return chunks[chunks.length - 1]
|
||||
}
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: getSchemeColor(modelData, "mOnSurface")
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
// Color swatches
|
||||
RowLayout {
|
||||
id: swatches
|
||||
|
||||
spacing: Style.marginS * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
// Scheme name
|
||||
NText {
|
||||
text: {
|
||||
// Remove json and the full path
|
||||
var chunks = schemePath.replace(".json", "").split("/")
|
||||
return chunks[chunks.length - 1]
|
||||
}
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: getSchemeColor(modelData, "mOnSurface")
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
readonly property int swatchSize: 20 * scaling
|
||||
|
||||
// Primary color swatch
|
||||
Rectangle {
|
||||
width: swatches.swatchSize
|
||||
height: swatches.swatchSize
|
||||
radius: width * 0.5
|
||||
color: getSchemeColor(modelData, "mPrimary")
|
||||
}
|
||||
|
||||
// Color swatches
|
||||
RowLayout {
|
||||
id: swatches
|
||||
// Secondary color swatch
|
||||
Rectangle {
|
||||
width: swatches.swatchSize
|
||||
height: swatches.swatchSize
|
||||
radius: width * 0.5
|
||||
color: getSchemeColor(modelData, "mSecondary")
|
||||
}
|
||||
|
||||
spacing: Style.marginS * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
// Tertiary color swatch
|
||||
Rectangle {
|
||||
width: swatches.swatchSize
|
||||
height: swatches.swatchSize
|
||||
radius: width * 0.5
|
||||
color: getSchemeColor(modelData, "mTertiary")
|
||||
}
|
||||
|
||||
readonly property int swatchSize: 20 * scaling
|
||||
|
||||
// Primary color swatch
|
||||
Rectangle {
|
||||
width: swatches.swatchSize
|
||||
height: swatches.swatchSize
|
||||
radius: width * 0.5
|
||||
color: getSchemeColor(modelData, "mPrimary")
|
||||
}
|
||||
|
||||
// Secondary color swatch
|
||||
Rectangle {
|
||||
width: swatches.swatchSize
|
||||
height: swatches.swatchSize
|
||||
radius: width * 0.5
|
||||
color: getSchemeColor(modelData, "mSecondary")
|
||||
}
|
||||
|
||||
// Tertiary color swatch
|
||||
Rectangle {
|
||||
width: swatches.swatchSize
|
||||
height: swatches.swatchSize
|
||||
radius: width * 0.5
|
||||
color: getSchemeColor(modelData, "mTertiary")
|
||||
}
|
||||
|
||||
// Error color swatch
|
||||
Rectangle {
|
||||
width: swatches.swatchSize
|
||||
height: swatches.swatchSize
|
||||
radius: width * 0.5
|
||||
color: getSchemeColor(modelData, "mError")
|
||||
}
|
||||
// Error color swatch
|
||||
Rectangle {
|
||||
width: swatches.swatchSize
|
||||
height: swatches.swatchSize
|
||||
radius: width * 0.5
|
||||
color: getSchemeColor(modelData, "mError")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Selection indicator (Checkmark)
|
||||
Rectangle {
|
||||
visible: !Settings.data.colorSchemes.useWallpaperColors
|
||||
&& (Settings.data.colorSchemes.predefinedScheme === schemePath)
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Style.marginS * scaling
|
||||
width: 24 * scaling
|
||||
height: 24 * scaling
|
||||
radius: width * 0.5
|
||||
color: Color.mPrimary
|
||||
// Selection indicator (Checkmark)
|
||||
Rectangle {
|
||||
visible: !Settings.data.colorSchemes.useWallpaperColors
|
||||
&& (Settings.data.colorSchemes.predefinedScheme === schemePath)
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Style.marginS * scaling
|
||||
width: 24 * scaling
|
||||
height: 24 * scaling
|
||||
radius: width * 0.5
|
||||
color: Color.mPrimary
|
||||
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
text: "✓"
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnPrimary
|
||||
}
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
text: "✓"
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnPrimary
|
||||
}
|
||||
}
|
||||
|
||||
// Smooth animations
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
// Smooth animations
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
}
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.width {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
Behavior on border.width {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,52 +70,6 @@ ColumnLayout {
|
|||
onToggled: checked => Settings.data.general.dimDesktop = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Auto-hide Dock"
|
||||
description: "Automatically hide the dock when not in use."
|
||||
checked: Settings.data.dock.autoHide
|
||||
onToggled: checked => Settings.data.dock.autoHide = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Dock Background Opacity"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Adjust the background opacity of the dock."
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
NSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.dock.backgroundOpacity
|
||||
onMoved: Settings.data.dock.backgroundOpacity = value
|
||||
cutoutColor: Color.mSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: Style.marginS * scaling
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
|
@ -175,7 +129,70 @@ ColumnLayout {
|
|||
}
|
||||
}
|
||||
}
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Dock
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
NText {
|
||||
text: "Dock"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
Layout.bottomMargin: Style.marginS * scaling
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Auto-hide Dock"
|
||||
description: "Automatically hide the dock when not in use."
|
||||
checked: Settings.data.dock.autoHide
|
||||
onToggled: checked => Settings.data.dock.autoHide = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Dock Background Opacity"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Adjust the background opacity of the dock."
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
NSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.dock.backgroundOpacity
|
||||
onMoved: Settings.data.dock.backgroundOpacity = value
|
||||
cutoutColor: Color.mSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: Style.marginS * scaling
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
|
|
@ -206,6 +223,7 @@ ColumnLayout {
|
|||
currentKey: Settings.data.ui.fontDefault
|
||||
placeholder: "Select default font..."
|
||||
popupHeight: 420 * scaling
|
||||
minimumWidth: 300 * scaling
|
||||
onSelected: function (key) {
|
||||
Settings.data.ui.fontDefault = key
|
||||
}
|
||||
|
|
@ -218,6 +236,7 @@ ColumnLayout {
|
|||
currentKey: Settings.data.ui.fontFixed
|
||||
placeholder: "Select monospace font..."
|
||||
popupHeight: 320 * scaling
|
||||
minimumWidth: 300 * scaling
|
||||
onSelected: function (key) {
|
||||
Settings.data.ui.fontFixed = key
|
||||
}
|
||||
|
|
@ -230,6 +249,7 @@ ColumnLayout {
|
|||
currentKey: Settings.data.ui.fontBillboard
|
||||
placeholder: "Select display font..."
|
||||
popupHeight: 320 * scaling
|
||||
minimumWidth: 300 * scaling
|
||||
onSelected: function (key) {
|
||||
Settings.data.ui.fontBillboard = key
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,20 +52,6 @@ ColumnLayout {
|
|||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Enable Clipboard History"
|
||||
description: "Show clipboard history in the launcher."
|
||||
checked: Settings.data.appLauncher.enableClipboardHistory
|
||||
onToggled: checked => Settings.data.appLauncher.enableClipboardHistory = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Use App2Unit for Launching"
|
||||
description: "Use app2unit -- 'desktop-entry' when launching applications for better systemd integration."
|
||||
checked: Settings.data.appLauncher.useApp2Unit
|
||||
onToggled: checked => Settings.data.appLauncher.useApp2Unit = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
|
@ -105,6 +91,20 @@ ColumnLayout {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Enable Clipboard History"
|
||||
description: "Show clipboard history in the launcher."
|
||||
checked: Settings.data.appLauncher.enableClipboardHistory
|
||||
onToggled: checked => Settings.data.appLauncher.enableClipboardHistory = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Use App2Unit for Launching"
|
||||
description: "Use app2unit -- 'desktop-entry' when launching applications for better systemd integration."
|
||||
checked: Settings.data.appLauncher.useApp2Unit
|
||||
onToggled: checked => Settings.data.appLauncher.useApp2Unit = checked
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
|
|
|
|||
|
|
@ -52,46 +52,6 @@ ColumnLayout {
|
|||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Time section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Time Format"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Use 12-Hour Clock"
|
||||
description: "Display time in 12-hour format (AM/PM) instead of 24-hour."
|
||||
checked: Settings.data.location.use12HourClock
|
||||
onToggled: checked => Settings.data.location.use12HourClock = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Reverse Day/Month"
|
||||
description: "Display date as dd/mm instead of mm/dd."
|
||||
checked: Settings.data.location.reverseDayMonth
|
||||
onToggled: checked => Settings.data.location.reverseDayMonth = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show Date with Clock"
|
||||
description: "Display date alongside time (e.g., 18:12 - Sat, 23 Aug)."
|
||||
checked: Settings.data.location.showDateWithClock
|
||||
onToggled: checked => Settings.data.location.showDateWithClock = checked
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Weather section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM * scaling
|
||||
|
|
@ -111,10 +71,4 @@ ColumnLayout {
|
|||
onToggled: checked => Settings.data.location.useFahrenheit = checked
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ NBox {
|
|||
height: 68 * scaling
|
||||
}
|
||||
NCircleStat {
|
||||
value: SystemStatService.memoryUsagePer
|
||||
value: SystemStatService.memPercent
|
||||
icon: "memory"
|
||||
flat: true
|
||||
contentScale: 0.8
|
||||
|
|
@ -48,7 +48,7 @@ NBox {
|
|||
height: 68 * scaling
|
||||
}
|
||||
NCircleStat {
|
||||
value: SystemStatService.diskUsage
|
||||
value: SystemStatService.diskPercent
|
||||
icon: "hard_drive"
|
||||
flat: true
|
||||
contentScale: 0.8
|
||||
|
|
|
|||
|
|
@ -11,71 +11,92 @@ NPanel {
|
|||
id: root
|
||||
|
||||
panelWidth: 460 * scaling
|
||||
panelHeight: 708 * scaling
|
||||
panelHeight: contentHeight
|
||||
|
||||
// Default height, will be modified via binding when the content is fully loaded
|
||||
property real contentHeight: 720 * scaling
|
||||
|
||||
panelContent: Item {
|
||||
id: content
|
||||
|
||||
property real cardSpacing: Style.marginL * scaling
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: content.cardSpacing
|
||||
implicitHeight: layout.implicitHeight
|
||||
width: root.panelWidth
|
||||
implicitHeight: layout.implicitHeight + (2 * cardSpacing)
|
||||
height: implicitHeight
|
||||
|
||||
// Layout content (not vertically anchored so implicitHeight is valid)
|
||||
// Update parent's contentHeight whenever our height changes
|
||||
onHeightChanged: {
|
||||
root.contentHeight = height
|
||||
}
|
||||
|
||||
onImplicitHeightChanged: {
|
||||
if (implicitHeight > 0) {
|
||||
root.contentHeight = implicitHeight
|
||||
}
|
||||
}
|
||||
|
||||
// Layout content
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
// Use the same spacing value horizontally and vertically
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
x: content.cardSpacing
|
||||
y: content.cardSpacing
|
||||
width: parent.width - (2 * content.cardSpacing)
|
||||
spacing: content.cardSpacing
|
||||
|
||||
// Cards (consistent inter-card spacing via ColumnLayout spacing)
|
||||
ProfileCard {// Layout.topMargin: 0
|
||||
// Layout.bottomMargin: 0
|
||||
ProfileCard {
|
||||
id: profileCard
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
WeatherCard {// Layout.topMargin: 0
|
||||
// Layout.bottomMargin: 0
|
||||
|
||||
WeatherCard {
|
||||
id: weatherCard
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Middle section: media + stats column
|
||||
RowLayout {
|
||||
id: middleRow
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 0
|
||||
Layout.bottomMargin: 0
|
||||
Layout.minimumHeight: 280 * scaling
|
||||
Layout.preferredHeight: Math.max(280 * scaling, statsCard.implicitHeight)
|
||||
spacing: content.cardSpacing
|
||||
|
||||
// Media card
|
||||
MediaCard {
|
||||
id: mediaCard
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: statsCard.implicitHeight
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
// System monitors combined in one card
|
||||
SystemMonitorCard {
|
||||
id: statsCard
|
||||
Layout.alignment: Qt.AlignTop
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom actions (two grouped rows of round buttons)
|
||||
RowLayout {
|
||||
id: bottomRow
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 0
|
||||
Layout.bottomMargin: 0
|
||||
Layout.minimumHeight: 60 * scaling
|
||||
Layout.preferredHeight: Math.max(60 * scaling, powerProfilesCard.implicitHeight, utilitiesCard.implicitHeight)
|
||||
spacing: content.cardSpacing
|
||||
|
||||
// Power Profiles switcher
|
||||
PowerProfilesCard {
|
||||
id: powerProfilesCard
|
||||
spacing: content.cardSpacing
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Utilities buttons
|
||||
UtilitiesCard {
|
||||
id: utilitiesCard
|
||||
spacing: content.cardSpacing
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -397,6 +397,7 @@ NPanel {
|
|||
}
|
||||
outlined: !hovered
|
||||
fontSize: Style.fontSizeXS * scaling
|
||||
enabled: !NetworkService.connecting
|
||||
onClicked: {
|
||||
if (modelData.existing || modelData.cached || !NetworkService.isSecured(modelData.security)) {
|
||||
NetworkService.connect(modelData.ssid)
|
||||
|
|
@ -461,7 +462,7 @@ NPanel {
|
|||
onVisibleChanged: if (visible)
|
||||
forceActiveFocus()
|
||||
onAccepted: {
|
||||
if (text) {
|
||||
if (text && !NetworkService.connecting) {
|
||||
NetworkService.connect(passwordSsid, text)
|
||||
passwordSsid = ""
|
||||
passwordInput = ""
|
||||
|
|
@ -481,7 +482,7 @@ NPanel {
|
|||
NButton {
|
||||
text: "Connect"
|
||||
fontSize: Style.fontSizeXXS * scaling
|
||||
enabled: passwordInput.length > 0
|
||||
enabled: passwordInput.length > 0 && !NetworkService.connecting
|
||||
outlined: true
|
||||
onClicked: {
|
||||
NetworkService.connect(passwordSsid, passwordInput)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,26 @@ Singleton {
|
|||
})
|
||||
|
||||
property var widgetMetadata: ({
|
||||
"ActiveWindow": {
|
||||
"allowUserSettings": true,
|
||||
"showIcon": true
|
||||
},
|
||||
"Battery": {
|
||||
"allowUserSettings": true,
|
||||
"alwaysShowPercentage": false,
|
||||
"warningThreshold": 30
|
||||
},
|
||||
"Brightness": {
|
||||
"allowUserSettings": true,
|
||||
"alwaysShowPercentage": false
|
||||
},
|
||||
"Clock": {
|
||||
"allowUserSettings": true,
|
||||
"showDate": false,
|
||||
"use12HourClock": false,
|
||||
"showSeconds": false,
|
||||
"reverseDayMonth": true
|
||||
},
|
||||
"CustomButton": {
|
||||
"allowUserSettings": true,
|
||||
"icon": "favorite",
|
||||
|
|
@ -45,10 +65,44 @@ Singleton {
|
|||
"rightClickExec": "",
|
||||
"middleClickExec": ""
|
||||
},
|
||||
"Microphone": {
|
||||
"allowUserSettings": true,
|
||||
"alwaysShowPercentage": false
|
||||
},
|
||||
"NotificationHistory": {
|
||||
"allowUserSettings": true,
|
||||
"showUnreadBadge": true,
|
||||
"hideWhenZero": true
|
||||
},
|
||||
"Spacer": {
|
||||
"allowUserSettings": true,
|
||||
"icon": "space_bar",
|
||||
"width": 20
|
||||
},
|
||||
"SystemMonitor": {
|
||||
"allowUserSettings": true,
|
||||
"showCpuUsage": true,
|
||||
"showCpuTemp": true,
|
||||
"showMemoryUsage": true,
|
||||
"showMemoryAsPercent": false,
|
||||
"showNetworkStats": false
|
||||
},
|
||||
"Workspace": {
|
||||
"allowUserSettings": true,
|
||||
"labelMode": "index"
|
||||
},
|
||||
"MediaMini": {
|
||||
"allowUserSettings": true,
|
||||
"showAlbumArt": false,
|
||||
"showVisualizer": false,
|
||||
"visualizerType": "linear"
|
||||
},
|
||||
"SidePanelToggle": {
|
||||
"allowUserSettings": true,
|
||||
"useDistroLogo": false
|
||||
},
|
||||
"Volume": {
|
||||
"allowUserSettings": true,
|
||||
"alwaysShowPercentage": false
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
49
Services/BatteryService.qml
Normal file
49
Services/BatteryService.qml
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Services.UPower
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Choose icon based on charge and charging state
|
||||
function getIcon(percent, charging, isReady) {
|
||||
if (!isReady) {
|
||||
return "battery_error"
|
||||
}
|
||||
|
||||
if (charging) {
|
||||
if (percent >= 95)
|
||||
return "battery_full"
|
||||
if (percent >= 85)
|
||||
return "battery_charging_90"
|
||||
if (percent >= 65)
|
||||
return "battery_charging_80"
|
||||
if (percent >= 55)
|
||||
return "battery_charging_60"
|
||||
if (percent >= 45)
|
||||
return "battery_charging_50"
|
||||
if (percent >= 25)
|
||||
return "battery_charging_30"
|
||||
if (percent >= 0)
|
||||
return "battery_charging_20"
|
||||
} else {
|
||||
if (percent >= 95)
|
||||
return "battery_full"
|
||||
if (percent >= 85)
|
||||
return "battery_6_bar"
|
||||
if (percent >= 70)
|
||||
return "battery_5_bar"
|
||||
if (percent >= 55)
|
||||
return "battery_4_bar"
|
||||
if (percent >= 40)
|
||||
return "battery_3_bar"
|
||||
if (percent >= 25)
|
||||
return "battery_2_bar"
|
||||
if (percent >= 10)
|
||||
return "battery_1_bar"
|
||||
if (percent >= 0)
|
||||
return "battery_0_bar"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -110,9 +110,43 @@ Singleton {
|
|||
property real lastBrightness: 0
|
||||
property real queuedBrightness: NaN
|
||||
|
||||
// For internal displays - store the backlight device path
|
||||
property string backlightDevice: ""
|
||||
property string brightnessPath: ""
|
||||
property string maxBrightnessPath: ""
|
||||
property int maxBrightness: 100
|
||||
property bool ignoreNextChange: false
|
||||
|
||||
// Signal for brightness changes
|
||||
signal brightnessUpdated(real newBrightness)
|
||||
|
||||
// FileView to watch for external brightness changes (internal displays only)
|
||||
readonly property FileView brightnessWatcher: FileView {
|
||||
id: brightnessWatcher
|
||||
// Only set path for internal displays with a valid brightness path
|
||||
path: (!monitor.isDdc && !monitor.isAppleDisplay && monitor.brightnessPath !== "") ? monitor.brightnessPath : ""
|
||||
watchChanges: path !== ""
|
||||
onFileChanged: {
|
||||
reload()
|
||||
if (monitor.ignoreNextChange) {
|
||||
monitor.ignoreNextChange = false
|
||||
return
|
||||
}
|
||||
if (text() === "")
|
||||
return
|
||||
var current = parseInt(text().trim())
|
||||
if (!isNaN(current) && monitor.maxBrightness > 0) {
|
||||
var newBrightness = current / monitor.maxBrightness
|
||||
// Only update if it's actually different (avoid feedback loops)
|
||||
if (Math.abs(newBrightness - monitor.brightness) > 0.01) {
|
||||
monitor.brightness = newBrightness
|
||||
monitor.brightnessUpdated(monitor.brightness)
|
||||
//Logger.log("Brightness", "External change detected:", monitor.modelData.name, monitor.brightness)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize brightness
|
||||
readonly property Process initProc: Process {
|
||||
stdout: StdioCollector {
|
||||
|
|
@ -121,8 +155,8 @@ Singleton {
|
|||
if (dataText === "") {
|
||||
return
|
||||
}
|
||||
Logger.log("Brightness", "Raw brightness data for", monitor.modelData.name + ":", dataText)
|
||||
|
||||
//Logger.log("Brightness", "Raw brightness data for", monitor.modelData.name + ":", dataText)
|
||||
if (monitor.isAppleDisplay) {
|
||||
var val = parseInt(dataText)
|
||||
if (!isNaN(val)) {
|
||||
|
|
@ -140,14 +174,20 @@ Singleton {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// Internal backlight
|
||||
var parts = dataText.split(" ")
|
||||
if (parts.length >= 2) {
|
||||
var current = parseInt(parts[0])
|
||||
var max = parseInt(parts[1])
|
||||
// Internal backlight - parse the response which includes device path
|
||||
var lines = dataText.split("\n")
|
||||
if (lines.length >= 3) {
|
||||
monitor.backlightDevice = lines[0]
|
||||
monitor.brightnessPath = monitor.backlightDevice + "/brightness"
|
||||
monitor.maxBrightnessPath = monitor.backlightDevice + "/max_brightness"
|
||||
|
||||
var current = parseInt(lines[1])
|
||||
var max = parseInt(lines[2])
|
||||
if (!isNaN(current) && !isNaN(max) && max > 0) {
|
||||
monitor.maxBrightness = max
|
||||
monitor.brightness = current / max
|
||||
Logger.log("Brightness", "Internal brightness:", current + "/" + max + " =", monitor.brightness)
|
||||
Logger.log("Brightness", "Using backlight device:", monitor.backlightDevice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -171,7 +211,7 @@ Singleton {
|
|||
|
||||
function increaseBrightness(): void {
|
||||
var stepSize = Settings.data.brightness.brightnessStep / 100.0
|
||||
setBrightnessDebounced(brightness + stepSize)
|
||||
setBrightnessDebounced(monitor.brightness + stepSize)
|
||||
}
|
||||
|
||||
function decreaseBrightness(): void {
|
||||
|
|
@ -183,22 +223,23 @@ Singleton {
|
|||
value = Math.max(0, Math.min(1, value))
|
||||
var rounded = Math.round(value * 100)
|
||||
|
||||
if (Math.round(brightness * 100) === rounded)
|
||||
if (Math.round(monitor.brightness * 100) === rounded)
|
||||
return
|
||||
|
||||
if (isDdc && timer.running) {
|
||||
queuedBrightness = value
|
||||
monitor.queuedBrightness = value
|
||||
return
|
||||
}
|
||||
|
||||
brightness = value
|
||||
brightnessUpdated(brightness)
|
||||
monitor.brightness = value
|
||||
brightnessUpdated(monitor.brightness)
|
||||
|
||||
if (isAppleDisplay) {
|
||||
Quickshell.execDetached(["asdbctl", "set", rounded])
|
||||
} else if (isDdc) {
|
||||
Quickshell.execDetached(["ddcutil", "-b", busNum, "setvcp", "10", rounded])
|
||||
} else {
|
||||
monitor.ignoreNextChange = true
|
||||
Quickshell.execDetached(["brightnessctl", "s", rounded + "%"])
|
||||
}
|
||||
|
||||
|
|
@ -208,7 +249,7 @@ Singleton {
|
|||
}
|
||||
|
||||
function setBrightnessDebounced(value: real): void {
|
||||
queuedBrightness = value
|
||||
monitor.queuedBrightness = value
|
||||
timer.restart()
|
||||
}
|
||||
|
||||
|
|
@ -218,8 +259,11 @@ Singleton {
|
|||
} else if (isDdc) {
|
||||
initProc.command = ["ddcutil", "-b", busNum, "getvcp", "10", "--brief"]
|
||||
} else {
|
||||
// Internal backlight - try to find the first available backlight device
|
||||
initProc.command = ["sh", "-c", "for dev in /sys/class/backlight/*; do if [ -f \"$dev/brightness\" ] && [ -f \"$dev/max_brightness\" ]; then echo \"$(cat $dev/brightness) $(cat $dev/max_brightness)\"; break; fi; done"]
|
||||
// Internal backlight - find the first available backlight device and get its info
|
||||
// This now returns: device_path, current_brightness, max_brightness (on separate lines)
|
||||
initProc.command = ["sh", "-c", "for dev in /sys/class/backlight/*; do "
|
||||
+ " if [ -f \"$dev/brightness\" ] && [ -f \"$dev/max_brightness\" ]; then " + " echo \"$dev\"; "
|
||||
+ " cat \"$dev/brightness\"; " + " cat \"$dev/max_brightness\"; " + " break; " + " fi; " + "done"]
|
||||
}
|
||||
initProc.running = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,9 +37,7 @@ Singleton {
|
|||
Process {
|
||||
id: process
|
||||
stdinEnabled: true
|
||||
running: (Settings.data.audio.visualizerType !== "none")
|
||||
&& (PanelService.getPanel("sidePanel").active || Settings.data.audio.showMiniplayerCava
|
||||
|| (PanelService.lockScreen && PanelService.lockScreen.active))
|
||||
running: true
|
||||
command: ["cava", "-p", "/dev/stdin"]
|
||||
onExited: {
|
||||
stdinEnabled = true
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ Singleton {
|
|||
|
||||
property string version: "Unknown"
|
||||
property var contributors: []
|
||||
property double timestamp: 0
|
||||
property real timestamp: 0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ Singleton {
|
|||
property string disconnectingFrom: ""
|
||||
property string forgettingNetwork: ""
|
||||
|
||||
property bool ignoreScanResults: false
|
||||
property bool scanPending: false
|
||||
|
||||
// Persistent cache
|
||||
property string cacheFile: Settings.cacheDir + "network.json"
|
||||
readonly property string cachedLastConnected: cacheAdapter.lastConnected
|
||||
|
|
@ -54,7 +57,7 @@ Singleton {
|
|||
Component.onCompleted: {
|
||||
Logger.log("Network", "Service initialized")
|
||||
syncWifiState()
|
||||
refresh()
|
||||
scan()
|
||||
}
|
||||
|
||||
// Save cache with debounce
|
||||
|
|
@ -75,6 +78,16 @@ Singleton {
|
|||
onTriggered: scan()
|
||||
}
|
||||
|
||||
// Ethernet check timer
|
||||
// Always running every 30s
|
||||
Timer {
|
||||
id: ethernetCheckTimer
|
||||
interval: 30000
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: ethernetStateProcess.running = true
|
||||
}
|
||||
|
||||
// Core functions
|
||||
function syncWifiState() {
|
||||
wifiStateProcess.running = true
|
||||
|
|
@ -82,26 +95,26 @@ Singleton {
|
|||
|
||||
function setWifiEnabled(enabled) {
|
||||
Settings.data.network.wifiEnabled = enabled
|
||||
|
||||
wifiToggleProcess.action = enabled ? "on" : "off"
|
||||
wifiToggleProcess.running = true
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
ethernetStateProcess.running = true
|
||||
|
||||
if (Settings.data.network.wifiEnabled) {
|
||||
scan()
|
||||
}
|
||||
}
|
||||
|
||||
function scan() {
|
||||
if (scanning)
|
||||
if (!Settings.data.network.wifiEnabled)
|
||||
return
|
||||
|
||||
if (scanning) {
|
||||
// Mark current scan results to be ignored and schedule a new scan
|
||||
Logger.log("Network", "Scan already in progress, will ignore results and rescan")
|
||||
ignoreScanResults = true
|
||||
scanPending = true
|
||||
return
|
||||
}
|
||||
|
||||
scanning = true
|
||||
lastError = ""
|
||||
scanProcess.running = true
|
||||
ignoreScanResults = false
|
||||
|
||||
// Get existing profiles first, then scan
|
||||
profileCheckProcess.running = true
|
||||
Logger.log("Network", "Wi-Fi scan in progress...")
|
||||
}
|
||||
|
||||
|
|
@ -174,8 +187,7 @@ Singleton {
|
|||
"ssid": ssid,
|
||||
"security": "--",
|
||||
"signal": 100,
|
||||
"connected"// Default to good signal until real scan
|
||||
: true,
|
||||
"connected": true,
|
||||
"existing": true,
|
||||
"cached": true
|
||||
}
|
||||
|
|
@ -206,7 +218,7 @@ Singleton {
|
|||
// Processes
|
||||
Process {
|
||||
id: ethernetStateProcess
|
||||
running: false
|
||||
running: true
|
||||
command: ["nmcli", "-t", "-f", "DEVICE,TYPE,STATE", "device"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
|
|
@ -239,30 +251,34 @@ Singleton {
|
|||
}
|
||||
}
|
||||
|
||||
// Helper process to get existing profiles
|
||||
Process {
|
||||
id: wifiToggleProcess
|
||||
property string action: "on"
|
||||
id: profileCheckProcess
|
||||
running: false
|
||||
command: ["nmcli", "radio", "wifi", action]
|
||||
command: ["nmcli", "-t", "-f", "NAME", "connection", "show"]
|
||||
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
if (action === "on") {
|
||||
// Clear networks immediately and start delayed scan
|
||||
root.networks = ({})
|
||||
delayedScanTimer.interval = 8000
|
||||
delayedScanTimer.restart()
|
||||
} else {
|
||||
root.networks = ({})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.trim()) {
|
||||
Logger.warn("Network", "WiFi toggle error: " + text)
|
||||
if (root.ignoreScanResults) {
|
||||
Logger.log("Network", "Ignoring profile check results (new scan requested)")
|
||||
root.scanning = false
|
||||
|
||||
// Check if we need to start a new scan
|
||||
if (root.scanPending) {
|
||||
root.scanPending = false
|
||||
delayedScanTimer.interval = 100
|
||||
delayedScanTimer.restart()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const profiles = {}
|
||||
const lines = text.split("\n").filter(l => l.trim())
|
||||
for (const line of lines) {
|
||||
profiles[line.trim()] = true
|
||||
}
|
||||
scanProcess.existingProfiles = profiles
|
||||
scanProcess.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -270,74 +286,103 @@ Singleton {
|
|||
Process {
|
||||
id: scanProcess
|
||||
running: false
|
||||
command: ["sh", "-c", `
|
||||
# Get list of saved connection profiles (just the names)
|
||||
profiles=$(nmcli -t -f NAME connection show | tr '\n' '|')
|
||||
command: ["nmcli", "-t", "-f", "SSID,SECURITY,SIGNAL,IN-USE", "device", "wifi", "list", "--rescan", "yes"]
|
||||
|
||||
# Get WiFi networks
|
||||
nmcli -t -f SSID,SECURITY,SIGNAL,IN-USE device wifi list --rescan yes | while read line; do
|
||||
ssid=$(echo "$line" | cut -d: -f1)
|
||||
security=$(echo "$line" | cut -d: -f2)
|
||||
signal=$(echo "$line" | cut -d: -f3)
|
||||
in_use=$(echo "$line" | cut -d: -f4)
|
||||
|
||||
# Skip empty SSIDs
|
||||
if [ -z "$ssid" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check if SSID matches any profile name (simple check)
|
||||
# This covers most cases where profile name equals or contains the SSID
|
||||
existing=false
|
||||
if echo "$profiles" | grep -qF "$ssid|"; then
|
||||
existing=true
|
||||
fi
|
||||
|
||||
echo "$ssid|$security|$signal|$in_use|$existing"
|
||||
done
|
||||
`]
|
||||
property var existingProfiles: ({})
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const nets = {}
|
||||
const lines = text.split("\n").filter(l => l.trim())
|
||||
if (root.ignoreScanResults) {
|
||||
Logger.log("Network", "Ignoring scan results (new scan requested)")
|
||||
root.scanning = false
|
||||
|
||||
for (const line of lines) {
|
||||
const parts = line.split("|")
|
||||
if (parts.length < 5)
|
||||
// Check if we need to start a new scan
|
||||
if (root.scanPending) {
|
||||
root.scanPending = false
|
||||
delayedScanTimer.interval = 100
|
||||
delayedScanTimer.restart()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Process the scan results as before...
|
||||
const lines = text.split("\n")
|
||||
const networksMap = {}
|
||||
|
||||
for (var i = 0; i < lines.length; ++i) {
|
||||
const line = lines[i].trim()
|
||||
if (!line)
|
||||
continue
|
||||
|
||||
const ssid = parts[0]
|
||||
if (!ssid || ssid.trim() === "")
|
||||
continue
|
||||
|
||||
const network = {
|
||||
"ssid": ssid,
|
||||
"security": parts[1] || "--",
|
||||
"signal": parseInt(parts[2]) || 0,
|
||||
"connected": parts[3] === "*",
|
||||
"existing": parts[4] === "true",
|
||||
"cached": ssid in cacheAdapter.knownNetworks
|
||||
// Parse from the end to handle SSIDs with colons
|
||||
// Format is SSID:SECURITY:SIGNAL:IN-USE
|
||||
// We know the last 3 fields, so everything else is SSID
|
||||
const lastColonIdx = line.lastIndexOf(":")
|
||||
if (lastColonIdx === -1) {
|
||||
Logger.warn("Network", "Malformed nmcli output line:", line)
|
||||
continue
|
||||
}
|
||||
|
||||
// Track connected network
|
||||
if (network.connected && cacheAdapter.lastConnected !== ssid) {
|
||||
cacheAdapter.lastConnected = ssid
|
||||
saveCache()
|
||||
const inUse = line.substring(lastColonIdx + 1)
|
||||
const remainingLine = line.substring(0, lastColonIdx)
|
||||
|
||||
const secondLastColonIdx = remainingLine.lastIndexOf(":")
|
||||
if (secondLastColonIdx === -1) {
|
||||
Logger.warn("Network", "Malformed nmcli output line:", line)
|
||||
continue
|
||||
}
|
||||
|
||||
// Keep best signal for duplicate SSIDs
|
||||
if (!nets[ssid] || network.signal > nets[ssid].signal) {
|
||||
nets[ssid] = network
|
||||
const signal = remainingLine.substring(secondLastColonIdx + 1)
|
||||
const remainingLine2 = remainingLine.substring(0, secondLastColonIdx)
|
||||
|
||||
const thirdLastColonIdx = remainingLine2.lastIndexOf(":")
|
||||
if (thirdLastColonIdx === -1) {
|
||||
Logger.warn("Network", "Malformed nmcli output line:", line)
|
||||
continue
|
||||
}
|
||||
|
||||
const security = remainingLine2.substring(thirdLastColonIdx + 1)
|
||||
const ssid = remainingLine2.substring(0, thirdLastColonIdx)
|
||||
|
||||
if (ssid) {
|
||||
const signalInt = parseInt(signal) || 0
|
||||
const connected = inUse === "*"
|
||||
|
||||
// Track connected network in cache
|
||||
if (connected && cacheAdapter.lastConnected !== ssid) {
|
||||
cacheAdapter.lastConnected = ssid
|
||||
saveCache()
|
||||
}
|
||||
|
||||
if (!networksMap[ssid]) {
|
||||
networksMap[ssid] = {
|
||||
"ssid": ssid,
|
||||
"security": security || "--",
|
||||
"signal": signalInt,
|
||||
"connected": connected,
|
||||
"existing": ssid in scanProcess.existingProfiles,
|
||||
"cached": ssid in cacheAdapter.knownNetworks
|
||||
}
|
||||
} else {
|
||||
// Keep the best signal for duplicate SSIDs
|
||||
const existingNet = networksMap[ssid]
|
||||
if (connected) {
|
||||
existingNet.connected = true
|
||||
}
|
||||
if (signalInt > existingNet.signal) {
|
||||
existingNet.signal = signalInt
|
||||
existingNet.security = security || "--"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For logging purpose only
|
||||
Logger.log("Network", "Wi-Fi scan completed")
|
||||
// Logging
|
||||
const oldSSIDs = Object.keys(root.networks)
|
||||
const newSSIDs = Object.keys(nets)
|
||||
const newSSIDs = Object.keys(networksMap)
|
||||
const newNetworks = newSSIDs.filter(ssid => !oldSSIDs.includes(ssid))
|
||||
const lostNetworks = oldSSIDs.filter(ssid => !newSSIDs.includes(ssid))
|
||||
|
||||
if (newNetworks.length > 0 || lostNetworks.length > 0) {
|
||||
if (newNetworks.length > 0) {
|
||||
Logger.log("Network", "New Wi-Fi SSID discovered:", newNetworks.join(", "))
|
||||
|
|
@ -345,12 +390,19 @@ Singleton {
|
|||
if (lostNetworks.length > 0) {
|
||||
Logger.log("Network", "Wi-Fi SSID disappeared:", lostNetworks.join(", "))
|
||||
}
|
||||
Logger.log("Network", "Total Wi-Fi SSIDs:", Object.keys(nets).length)
|
||||
Logger.log("Network", "Total Wi-Fi SSIDs:", Object.keys(networksMap).length)
|
||||
}
|
||||
|
||||
// Assign the results
|
||||
root.networks = nets
|
||||
Logger.log("Network", "Wi-Fi scan completed")
|
||||
root.networks = networksMap
|
||||
root.scanning = false
|
||||
|
||||
// Check if we need to start a new scan
|
||||
if (root.scanPending) {
|
||||
root.scanPending = false
|
||||
delayedScanTimer.interval = 100
|
||||
delayedScanTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -359,16 +411,14 @@ Singleton {
|
|||
root.scanning = false
|
||||
if (text.trim()) {
|
||||
Logger.warn("Network", "Scan error: " + text)
|
||||
// If scan fails, set a short retry
|
||||
if (Settings.data.network.wifiEnabled) {
|
||||
delayedScanTimer.interval = 5000
|
||||
delayedScanTimer.restart()
|
||||
}
|
||||
|
||||
// If scan fails, retry
|
||||
delayedScanTimer.interval = 5000
|
||||
delayedScanTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: connectProcess
|
||||
property string mode: "new"
|
||||
|
|
@ -390,6 +440,17 @@ Singleton {
|
|||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
// Check if the output actually indicates success
|
||||
// nmcli outputs "Device '...' successfully activated" or "Connection successfully activated"
|
||||
// on success. Empty output or other messages indicate failure.
|
||||
const output = text.trim()
|
||||
|
||||
if (!output || (!output.includes("successfully activated") && !output.includes("Connection successfully"))) {
|
||||
// No success message - likely an error occurred
|
||||
// Don't update anything, let stderr handler deal with it
|
||||
return
|
||||
}
|
||||
|
||||
// Success - update cache
|
||||
let known = cacheAdapter.knownNetworks
|
||||
known[connectProcess.ssid] = {
|
||||
|
|
@ -408,7 +469,7 @@ Singleton {
|
|||
Logger.log("Network", `Connected to network: "${connectProcess.ssid}"`)
|
||||
|
||||
// Still do a scan to get accurate signal and security info
|
||||
delayedScanTimer.interval = 1000
|
||||
delayedScanTimer.interval = 5000
|
||||
delayedScanTimer.restart()
|
||||
}
|
||||
}
|
||||
|
|
@ -464,7 +525,7 @@ Singleton {
|
|||
Logger.warn("Network", "Disconnect error: " + text)
|
||||
}
|
||||
// Still trigger a scan even on error
|
||||
delayedScanTimer.interval = 1000
|
||||
delayedScanTimer.interval = 5000
|
||||
delayedScanTimer.restart()
|
||||
}
|
||||
}
|
||||
|
|
@ -522,8 +583,8 @@ Singleton {
|
|||
|
||||
root.forgettingNetwork = ""
|
||||
|
||||
// Quick scan to verify the profile is gone
|
||||
delayedScanTimer.interval = 500
|
||||
// Scan to verify the profile is gone
|
||||
delayedScanTimer.interval = 5000
|
||||
delayedScanTimer.restart()
|
||||
}
|
||||
}
|
||||
|
|
@ -535,7 +596,7 @@ Singleton {
|
|||
Logger.warn("Network", "Forget error: " + text)
|
||||
}
|
||||
// Still Trigger a scan even on error
|
||||
delayedScanTimer.interval = 500
|
||||
delayedScanTimer.interval = 5000
|
||||
delayedScanTimer.restart()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ Singleton {
|
|||
JsonAdapter {
|
||||
id: historyAdapter
|
||||
property var history: []
|
||||
property double timestamp: 0
|
||||
property real timestamp: 0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -118,12 +118,13 @@ Singleton {
|
|||
|
||||
// Function to add notification to model
|
||||
function addNotification(notification) {
|
||||
const resolvedImage = resolveNotificationImage(notification)
|
||||
notificationModel.insert(0, {
|
||||
"rawNotification": notification,
|
||||
"summary": notification.summary,
|
||||
"body": notification.body,
|
||||
"appName": notification.appName,
|
||||
"image": notification.image,
|
||||
"image": resolvedImage,
|
||||
"appIcon": notification.appIcon,
|
||||
"urgency": notification.urgency,
|
||||
"timestamp": new Date()
|
||||
|
|
@ -139,6 +140,40 @@ Singleton {
|
|||
}
|
||||
}
|
||||
|
||||
// Resolve an image path for a notification, supporting icon names and absolute paths
|
||||
function resolveNotificationImage(notification) {
|
||||
try {
|
||||
// If an explicit image is already provided, prefer it
|
||||
if (notification && notification.image && notification.image !== "") {
|
||||
return notification.image
|
||||
}
|
||||
|
||||
// Fallback to appIcon which may be a name or a path (notify-send -i)
|
||||
const icon = notification ? (notification.appIcon || "") : ""
|
||||
if (!icon)
|
||||
return ""
|
||||
|
||||
// Accept absolute file paths or file URLs directly
|
||||
if (icon.startsWith("/")) {
|
||||
return icon
|
||||
}
|
||||
if (icon.startsWith("file://")) {
|
||||
// Strip the scheme for QML image source compatibility
|
||||
return icon.substring("file://".length)
|
||||
}
|
||||
|
||||
// Resolve themed icon names to absolute paths
|
||||
try {
|
||||
const p = Icons.iconFromName(icon, "")
|
||||
return p || ""
|
||||
} catch (e2) {
|
||||
return ""
|
||||
}
|
||||
} catch (e) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Add a simplified copy into persistent history
|
||||
function addToHistory(notification) {
|
||||
historyModel.insert(0, {
|
||||
|
|
@ -166,12 +201,17 @@ Singleton {
|
|||
const items = historyAdapter.history || []
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
const it = items[i]
|
||||
// Coerce legacy second-based timestamps to milliseconds
|
||||
var ts = it.timestamp
|
||||
if (typeof ts === "number" && ts < 1e12) {
|
||||
ts = ts * 1000
|
||||
}
|
||||
historyModel.append({
|
||||
"summary": it.summary || "",
|
||||
"body": it.body || "",
|
||||
"appName": it.appName || "",
|
||||
"urgency": it.urgency,
|
||||
"timestamp": it.timestamp ? new Date(it.timestamp) : new Date()
|
||||
"timestamp": ts ? new Date(ts) : new Date()
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
@ -190,7 +230,10 @@ Singleton {
|
|||
"body": n.body,
|
||||
"appName": n.appName,
|
||||
"urgency": n.urgency,
|
||||
"timestamp": (n.timestamp instanceof Date) ? n.timestamp.getTime() : n.timestamp
|
||||
"timestamp"// Always persist in milliseconds
|
||||
: (n.timestamp instanceof Date) ? n.timestamp.getTime(
|
||||
) : (typeof n.timestamp === "number"
|
||||
&& n.timestamp < 1e12 ? n.timestamp * 1000 : n.timestamp)
|
||||
})
|
||||
}
|
||||
historyAdapter.history = arr
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import QtQuick
|
|||
import Qt.labs.folderlistmodel
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
|
@ -11,12 +12,313 @@ Singleton {
|
|||
// Public values
|
||||
property real cpuUsage: 0
|
||||
property real cpuTemp: 0
|
||||
property real memoryUsageGb: 0
|
||||
property real memoryUsagePer: 0
|
||||
property real diskUsage: 0
|
||||
property real memGb: 0
|
||||
property real memPercent: 0
|
||||
property real diskPercent: 0
|
||||
property real rxSpeed: 0
|
||||
property real txSpeed: 0
|
||||
|
||||
// Configuration
|
||||
property int sleepDuration: 3000
|
||||
|
||||
// Internal state for CPU calculation
|
||||
property var prevCpuStats: null
|
||||
|
||||
// Internal state for network speed calculation
|
||||
// Previous Bytes need to be stored as 'real' as they represent the total of bytes transfered
|
||||
// since the computer started, so their value will easily overlfow a 32bit int.
|
||||
property real prevRxBytes: 0
|
||||
property real prevTxBytes: 0
|
||||
property real prevTime: 0
|
||||
|
||||
// Cpu temperature is the most complex
|
||||
readonly property var supportedTempCpuSensorNames: ["coretemp", "k10temp", "zenpower"]
|
||||
property string cpuTempSensorName: ""
|
||||
property string cpuTempHwmonPath: ""
|
||||
// For Intel coretemp averaging of all cores/sensors
|
||||
property var intelTempValues: []
|
||||
property int intelTempFilesChecked: 0
|
||||
property int intelTempMaxFiles: 20 // Will test up to temp20_input
|
||||
|
||||
// --------------------------------------------
|
||||
Component.onCompleted: {
|
||||
Logger.log("SystemStat", "Service started with interval:", root.sleepDuration, "ms")
|
||||
|
||||
// Kickoff the cpu name detection for temperature
|
||||
cpuTempNameReader.checkNext()
|
||||
}
|
||||
|
||||
// --------------------------------------------
|
||||
// Timer for periodic updates
|
||||
Timer {
|
||||
id: updateTimer
|
||||
interval: root.sleepDuration
|
||||
repeat: true
|
||||
running: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: {
|
||||
// Trigger all direct system files reads
|
||||
memInfoFile.reload()
|
||||
cpuStatFile.reload()
|
||||
netDevFile.reload()
|
||||
|
||||
// Run df (disk free) one time
|
||||
dfProcess.running = true
|
||||
|
||||
updateCpuTemperature()
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------
|
||||
// FileView components for reading system files
|
||||
FileView {
|
||||
id: memInfoFile
|
||||
path: "/proc/meminfo"
|
||||
onLoaded: parseMemoryInfo(text())
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: cpuStatFile
|
||||
path: "/proc/stat"
|
||||
onLoaded: calculateCpuUsage(text())
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: netDevFile
|
||||
path: "/proc/net/dev"
|
||||
onLoaded: calculateNetworkSpeed(text())
|
||||
}
|
||||
|
||||
// --------------------------------------------
|
||||
// Process to fetch disk usage in percent
|
||||
// Uses 'df' aka 'disk free'
|
||||
Process {
|
||||
id: dfProcess
|
||||
command: ["df", "--output=pcent", "/"]
|
||||
running: false
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const lines = text.trim().split('\n')
|
||||
if (lines.length >= 2) {
|
||||
const percent = lines[1].replace(/[^0-9]/g, '')
|
||||
root.diskPercent = parseInt(percent) || 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------
|
||||
// CPU Temperature
|
||||
// It's more complex.
|
||||
// ----
|
||||
// #1 - Find a common cpu sensor name ie: "coretemp", "k10temp", "zenpower"
|
||||
FileView {
|
||||
id: cpuTempNameReader
|
||||
property int currentIndex: 0
|
||||
|
||||
function checkNext() {
|
||||
if (currentIndex >= 10) {
|
||||
// Check up to hwmon10
|
||||
Logger.warn("No supported temperature sensor found")
|
||||
return
|
||||
}
|
||||
|
||||
//Logger.log("SystemStat", "---- Probing: hwmon", currentIndex)
|
||||
cpuTempNameReader.path = `/sys/class/hwmon/hwmon${currentIndex}/name`
|
||||
cpuTempNameReader.reload()
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
const name = text().trim()
|
||||
if (root.supportedTempCpuSensorNames.includes(name)) {
|
||||
root.cpuTempSensorName = name
|
||||
root.cpuTempHwmonPath = `/sys/class/hwmon/hwmon${currentIndex}`
|
||||
Logger.log("SystemStat", `Found ${root.cpuTempSensorName} CPU thermal sensor at ${root.cpuTempHwmonPath}`)
|
||||
} else {
|
||||
currentIndex++
|
||||
Qt.callLater(() => {
|
||||
// Qt.callLater is mandatory
|
||||
checkNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onLoadFailed: function (error) {
|
||||
currentIndex++
|
||||
Qt.callLater(() => {
|
||||
// Qt.callLater is mandatory
|
||||
checkNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ----
|
||||
// #2 - Read sensor value
|
||||
FileView {
|
||||
id: cpuTempReader
|
||||
printErrors: false
|
||||
|
||||
onLoaded: {
|
||||
const data = text().trim()
|
||||
if (root.cpuTempSensorName === "coretemp") {
|
||||
// For Intel, collect all temperature values
|
||||
const temp = parseInt(data) / 1000.0
|
||||
//console.log(temp, cpuTempReader.path)
|
||||
root.intelTempValues.push(temp)
|
||||
Qt.callLater(() => {
|
||||
// Qt.callLater is mandatory
|
||||
checkNextIntelTemp()
|
||||
})
|
||||
} else {
|
||||
// For AMD sensors (k10temp and zenpower), directly set the temperature
|
||||
root.cpuTemp = Math.round(parseInt(data) / 1000.0)
|
||||
}
|
||||
}
|
||||
onLoadFailed: function (error) {
|
||||
Qt.callLater(() => {
|
||||
// Qt.callLater is mandatory
|
||||
checkNextIntelTemp()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Parse memory info from /proc/meminfo
|
||||
function parseMemoryInfo(text) {
|
||||
if (!text)
|
||||
return
|
||||
|
||||
const lines = text.split('\n')
|
||||
let memTotal = 0
|
||||
let memAvailable = 0
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('MemTotal:')) {
|
||||
memTotal = parseInt(line.split(/\s+/)[1]) || 0
|
||||
} else if (line.startsWith('MemAvailable:')) {
|
||||
memAvailable = parseInt(line.split(/\s+/)[1]) || 0
|
||||
}
|
||||
}
|
||||
|
||||
if (memTotal > 0) {
|
||||
const usageKb = memTotal - memAvailable
|
||||
root.memGb = (usageKb / 1000000).toFixed(1)
|
||||
root.memPercent = Math.round((usageKb / memTotal) * 100)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Calculate CPU usage from /proc/stat
|
||||
function calculateCpuUsage(text) {
|
||||
if (!text)
|
||||
return
|
||||
|
||||
const lines = text.split('\n')
|
||||
const cpuLine = lines[0]
|
||||
|
||||
// First line is total CPU
|
||||
if (!cpuLine.startsWith('cpu '))
|
||||
return
|
||||
|
||||
const parts = cpuLine.split(/\s+/)
|
||||
const stats = {
|
||||
"user": parseInt(parts[1]) || 0,
|
||||
"nice": parseInt(parts[2]) || 0,
|
||||
"system": parseInt(parts[3]) || 0,
|
||||
"idle": parseInt(parts[4]) || 0,
|
||||
"iowait": parseInt(parts[5]) || 0,
|
||||
"irq": parseInt(parts[6]) || 0,
|
||||
"softirq": parseInt(parts[7]) || 0,
|
||||
"steal": parseInt(parts[8]) || 0,
|
||||
"guest": parseInt(parts[9]) || 0,
|
||||
"guestNice": parseInt(parts[10]) || 0
|
||||
}
|
||||
const totalIdle = stats.idle + stats.iowait
|
||||
const total = Object.values(stats).reduce((sum, val) => sum + val, 0)
|
||||
|
||||
if (root.prevCpuStats) {
|
||||
const prevTotalIdle = root.prevCpuStats.idle + root.prevCpuStats.iowait
|
||||
const prevTotal = Object.values(root.prevCpuStats).reduce((sum, val) => sum + val, 0)
|
||||
|
||||
const diffTotal = total - prevTotal
|
||||
const diffIdle = totalIdle - prevTotalIdle
|
||||
|
||||
if (diffTotal > 0) {
|
||||
root.cpuUsage = (((diffTotal - diffIdle) / diffTotal) * 100).toFixed(1)
|
||||
}
|
||||
}
|
||||
|
||||
root.prevCpuStats = stats
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Calculate RX and TX speed from /proc/net/dev
|
||||
// Average speed of all interfaces excepted 'lo'
|
||||
function calculateNetworkSpeed(text) {
|
||||
if (!text) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentTime = Date.now() / 1000
|
||||
const lines = text.split('\n')
|
||||
|
||||
let totalRx = 0
|
||||
let totalTx = 0
|
||||
|
||||
for (var i = 2; i < lines.length; i++) {
|
||||
const line = lines[i].trim()
|
||||
if (!line) {
|
||||
continue
|
||||
}
|
||||
|
||||
const colonIndex = line.indexOf(':')
|
||||
if (colonIndex === -1) {
|
||||
continue
|
||||
}
|
||||
|
||||
const iface = line.substring(0, colonIndex).trim()
|
||||
if (iface === 'lo') {
|
||||
continue
|
||||
}
|
||||
|
||||
const statsLine = line.substring(colonIndex + 1).trim()
|
||||
const stats = statsLine.split(/\s+/)
|
||||
|
||||
const rxBytes = parseInt(stats[0], 10) || 0
|
||||
const txBytes = parseInt(stats[8], 10) || 0
|
||||
|
||||
totalRx += rxBytes
|
||||
totalTx += txBytes
|
||||
}
|
||||
|
||||
// Compute only if we have a previous run to compare to.
|
||||
if (root.prevTime > 0) {
|
||||
const timeDiff = currentTime - root.prevTime
|
||||
|
||||
// Avoid division by zero if time hasn't passed.
|
||||
if (timeDiff > 0) {
|
||||
let rxDiff = totalRx - root.prevRxBytes
|
||||
let txDiff = totalTx - root.prevTxBytes
|
||||
|
||||
// Handle counter resets (e.g., WiFi reconnect), which would cause a negative value.
|
||||
if (rxDiff < 0) {
|
||||
rxDiff = 0
|
||||
}
|
||||
if (txDiff < 0) {
|
||||
txDiff = 0
|
||||
}
|
||||
|
||||
root.rxSpeed = Math.round(rxDiff / timeDiff) // Speed in Bytes/s
|
||||
root.txSpeed = Math.round(txDiff / timeDiff)
|
||||
}
|
||||
}
|
||||
|
||||
root.prevRxBytes = totalRx
|
||||
root.prevTxBytes = totalTx
|
||||
root.prevTime = currentTime
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Helper function to format network speeds
|
||||
function formatSpeed(bytesPerSecond) {
|
||||
if (bytesPerSecond < 1024) {
|
||||
|
|
@ -30,27 +332,44 @@ Singleton {
|
|||
}
|
||||
}
|
||||
|
||||
// Background process emitting one JSON line per sample
|
||||
Process {
|
||||
id: reader
|
||||
running: true
|
||||
command: ["sh", "-c", Quickshell.shellDir + "/Bin/system-stats.sh"]
|
||||
stdout: SplitParser {
|
||||
onRead: function (line) {
|
||||
try {
|
||||
const data = JSON.parse(line)
|
||||
root.cpuUsage = data.cpu
|
||||
root.cpuTemp = data.cputemp
|
||||
root.memoryUsageGb = data.memgb
|
||||
root.memoryUsagePer = data.memper
|
||||
root.diskUsage = data.diskper
|
||||
root.rxSpeed = parseFloat(data.rx_speed) || 0
|
||||
root.txSpeed = parseFloat(data.tx_speed) || 0
|
||||
} catch (e) {
|
||||
|
||||
// ignore malformed lines
|
||||
}
|
||||
}
|
||||
// -------------------------------------------------------
|
||||
// Function to start fetching and computing the cpu temperature
|
||||
function updateCpuTemperature() {
|
||||
// For AMD sensors (k10temp and zenpower), only use Tctl sensor
|
||||
// temp1_input corresponds to Tctl (Temperature Control) on these sensors
|
||||
if (root.cpuTempSensorName === "k10temp" || root.cpuTempSensorName === "zenpower") {
|
||||
cpuTempReader.path = `${root.cpuTempHwmonPath}/temp1_input`
|
||||
cpuTempReader.reload()
|
||||
} // For Intel coretemp, start averaging all available sensors/cores
|
||||
else if (root.cpuTempSensorName === "coretemp") {
|
||||
root.intelTempValues = []
|
||||
root.intelTempFilesChecked = 0
|
||||
checkNextIntelTemp()
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Function to check next Intel temperature sensor
|
||||
function checkNextIntelTemp() {
|
||||
if (root.intelTempFilesChecked >= root.intelTempMaxFiles) {
|
||||
// Calculate average of all found temperatures
|
||||
if (root.intelTempValues.length > 0) {
|
||||
let sum = 0
|
||||
for (var i = 0; i < root.intelTempValues.length; i++) {
|
||||
sum += root.intelTempValues[i]
|
||||
}
|
||||
root.cpuTemp = Math.round(sum / root.intelTempValues.length)
|
||||
//Logger.log("SystemStat", `Averaged ${root.intelTempValues.length} CPU thermal sensors: ${root.cpuTemp}°C`)
|
||||
} else {
|
||||
Logger.warn("SystemStat", "No temperature sensors found for coretemp")
|
||||
root.cpuTemp = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Check next temperature file
|
||||
root.intelTempFilesChecked++
|
||||
cpuTempReader.path = `${root.cpuTempHwmonPath}/temp${root.intelTempFilesChecked}_input`
|
||||
cpuTempReader.reload()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Singleton {
|
|||
id: root
|
||||
|
||||
// Public properties
|
||||
property string baseVersion: "2.6.0"
|
||||
property string baseVersion: "2.7.0"
|
||||
property bool isDevelopment: true
|
||||
|
||||
property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}`
|
||||
|
|
|
|||
|
|
@ -216,7 +216,11 @@ Singleton {
|
|||
// -------------------------------------------------------------------
|
||||
// Get specific monitor wallpaper - now from cache
|
||||
function getWallpaper(screenName) {
|
||||
return currentWallpapers[screenName] || ""
|
||||
var path = currentWallpapers[screenName] || ""
|
||||
if (path === "") {
|
||||
return Settings.data.wallpaper.defaultWallpaper || ""
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -77,10 +77,12 @@ Rectangle {
|
|||
RowLayout {
|
||||
id: contentRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginS * scaling
|
||||
spacing: Style.marginXS * scaling
|
||||
|
||||
// Icon (optional)
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
layoutTopMargin: 1 * scaling
|
||||
visible: root.icon !== ""
|
||||
text: root.icon
|
||||
font.pointSize: root.iconSize
|
||||
|
|
@ -105,6 +107,7 @@ Rectangle {
|
|||
|
||||
// Text
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: root.text !== ""
|
||||
text: root.text
|
||||
font.pointSize: root.fontSize
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ RowLayout {
|
|||
visible: root.label !== "" || root.description !== ""
|
||||
}
|
||||
|
||||
// Spacer to push the checkbox to the far right
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: box
|
||||
|
||||
|
|
@ -39,13 +44,13 @@ RowLayout {
|
|||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
import QtQuick
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
signal entered
|
||||
signal exited
|
||||
signal clicked
|
||||
|
||||
width: textItem.paintedWidth
|
||||
height: textItem.paintedHeight
|
||||
color: Color.transparent
|
||||
|
||||
NText {
|
||||
id: textItem
|
||||
text: Time.time
|
||||
anchors.centerIn: parent
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clockMouseArea
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onEntered: root.entered()
|
||||
onExited: root.exited()
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
}
|
||||
|
|
@ -8,8 +8,7 @@ import qs.Widgets
|
|||
RowLayout {
|
||||
id: root
|
||||
|
||||
readonly property real preferredHeight: Style.baseWidgetSize * 1.1 * scaling
|
||||
property real preferredWidth: 320 * scaling
|
||||
property real minimumWidth: 280 * scaling
|
||||
property real popupHeight: 180 * scaling
|
||||
|
||||
property string label: ""
|
||||
|
|
@ -20,9 +19,11 @@ RowLayout {
|
|||
property string currentKey: ""
|
||||
property string placeholder: ""
|
||||
|
||||
readonly property real preferredHeight: Style.baseWidgetSize * 1.1 * scaling
|
||||
|
||||
signal selected(string key)
|
||||
|
||||
spacing: Style.marginS * scaling
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
function findIndexByKey(key) {
|
||||
|
|
@ -39,11 +40,15 @@ RowLayout {
|
|||
description: root.description
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: combo
|
||||
|
||||
Layout.preferredWidth: root.preferredWidth
|
||||
Layout.preferredHeight: height
|
||||
Layout.minimumWidth: root.minimumWidth
|
||||
Layout.preferredHeight: root.preferredHeight
|
||||
model: model
|
||||
currentIndex: findIndexByKey(currentKey)
|
||||
onActivated: {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import QtQuick
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import QtQuick.Layouts
|
||||
|
||||
Text {
|
||||
// Optional layout nudge for optical alignment when used inside Layouts
|
||||
property real layoutTopMargin: 0
|
||||
text: "question_mark"
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
|
|
@ -12,4 +15,5 @@ Text {
|
|||
}
|
||||
color: Color.mOnSurface
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.topMargin: layoutTopMargin
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ ColumnLayout {
|
|||
font.capitalization: Font.Capitalize
|
||||
color: labelColor
|
||||
visible: label !== ""
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ Item {
|
|||
property color iconCircleColor: Color.mPrimary
|
||||
property color iconTextColor: Color.mSurface
|
||||
property color collapsedIconColor: Color.mOnSurface
|
||||
|
||||
property real iconRotation: 0
|
||||
property real sizeRatio: 0.8
|
||||
property bool autoHide: false
|
||||
property bool forceOpen: false
|
||||
|
|
@ -37,13 +39,13 @@ Item {
|
|||
property bool shouldAnimateHide: false
|
||||
|
||||
// Exposed width logic
|
||||
readonly property int pillHeight: Style.baseWidgetSize * sizeRatio * scaling
|
||||
readonly property int iconSize: Style.baseWidgetSize * sizeRatio * scaling
|
||||
readonly property int pillPaddingHorizontal: Style.marginM * scaling
|
||||
readonly property int iconSize: Math.round(Style.baseWidgetSize * sizeRatio * scaling)
|
||||
readonly property int pillHeight: iconSize
|
||||
readonly property int pillPaddingHorizontal: Style.marginS * scaling
|
||||
readonly property int pillOverlap: iconSize * 0.5
|
||||
readonly property int maxPillWidth: Math.max(1, textItem.implicitWidth + pillPaddingHorizontal * 2 + pillOverlap)
|
||||
|
||||
width: iconSize + (effectiveShown ? maxPillWidth - pillOverlap : 0)
|
||||
width: iconSize + Math.max(0, pill.width - pillOverlap)
|
||||
height: pillHeight
|
||||
|
||||
Rectangle {
|
||||
|
|
@ -65,7 +67,13 @@ Item {
|
|||
|
||||
NText {
|
||||
id: textItem
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: {
|
||||
// Little tweak to have a better text horizontal centering
|
||||
var centerX = (parent.width - width) / 2
|
||||
var offset = rightOpen ? Style.marginXS * scaling : -Style.marginXS * scaling
|
||||
return centerX + offset
|
||||
}
|
||||
text: root.text
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
|
|
@ -97,9 +105,10 @@ Item {
|
|||
// When forced shown, match pill background; otherwise use accent when hovered
|
||||
color: forceOpen ? pillColor : (showPill ? iconCircleColor : Color.mSurfaceVariant)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
border.color: forceOpen ? Qt.alpha(Color.mOutline, 0.5) : Color.transparent
|
||||
|
||||
anchors.left: rightOpen ? parent.left : undefined
|
||||
anchors.right: rightOpen ? undefined : parent.right
|
||||
x: rightOpen ? 0 : (parent.width - width)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
|
|
@ -110,6 +119,7 @@ Item {
|
|||
|
||||
NIcon {
|
||||
text: root.icon
|
||||
rotation: root.iconRotation
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
// When forced shown, use pill text color; otherwise accent color when hovered
|
||||
color: forceOpen ? textColor : (showPill ? iconTextColor : Color.mOnSurface)
|
||||
|
|
|
|||
|
|
@ -13,4 +13,5 @@ Text {
|
|||
font.kerning: true
|
||||
color: Color.mOnSurface
|
||||
renderType: Text.QtRendering
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue