mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Merge branch 'noctalia-dev:main' into pr/networking-refactor-pt2
This commit is contained in:
@@ -22,3 +22,9 @@ foreground {{colors.terminal_foreground.default.hex}}
|
||||
selection_background {{colors.terminal_foreground.default.hex}}
|
||||
active_border_color {{colors.primary.default.hex}}
|
||||
inactive_border_color {{colors.secondary.default.hex}}
|
||||
|
||||
active_tab_foreground {{colors.on_primary.default.hex}}
|
||||
active_tab_background {{colors.primary.default.hex}}
|
||||
inactive_tab_foreground {{colors.on_surface_variant.default.hex}}
|
||||
inactive_tab_background {{colors.surface_variant.default.hex}}
|
||||
cursor_trail_color {{colors.on_surface_variant.default.hex}}
|
||||
|
||||
@@ -24,3 +24,9 @@ selection_background {{colors.surface_variant.default.hex}}
|
||||
active_border_color {{colors.primary.default.hex}}
|
||||
inactive_border_color {{colors.surface_variant.default.hex}}
|
||||
url_color {{colors.primary.default.hex}}
|
||||
|
||||
active_tab_foreground {{colors.on_primary.default.hex}}
|
||||
active_tab_background {{colors.primary.default.hex}}
|
||||
inactive_tab_foreground {{colors.on_surface_variant.default.hex}}
|
||||
inactive_tab_background {{colors.surface_variant.default.hex}}
|
||||
cursor_trail_color {{colors.on_surface_variant.default.hex}}
|
||||
|
||||
@@ -175,7 +175,12 @@ Item {
|
||||
PanelService.closeContextMenu(screen);
|
||||
|
||||
if (action === "sysmon-settings") {
|
||||
SettingsPanelService.openToTab(SettingsPanel.Tab.System, 0, screen);
|
||||
let monitorCmd = Settings.data.systemMonitor.externalMonitor;
|
||||
if (monitorCmd && monitorCmd.trim() !== "") {
|
||||
openExternalMonitor();
|
||||
} else {
|
||||
SettingsPanelService.openToTab(SettingsPanel.Tab.System, 0, screen);
|
||||
}
|
||||
} else if (action === "widget-settings") {
|
||||
BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings);
|
||||
}
|
||||
|
||||
@@ -692,9 +692,9 @@ Loader {
|
||||
}
|
||||
}
|
||||
|
||||
// PEEK WINDOW
|
||||
// PEEK WINDOW — only needed when dock can auto-hide or is in attached mode
|
||||
Loader {
|
||||
active: (barIsReady || !hasBar) && modelData && (Settings.data.dock.monitors.length === 0 || Settings.data.dock.monitors.includes(modelData.name))
|
||||
active: (autoHide || isAttachedMode) && (barIsReady || !hasBar) && modelData && (Settings.data.dock.monitors.length === 0 || Settings.data.dock.monitors.includes(modelData.name))
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
id: peekWindow
|
||||
@@ -754,9 +754,9 @@ Loader {
|
||||
}
|
||||
}
|
||||
|
||||
// DOCK INDICATOR WINDOW
|
||||
// DOCK INDICATOR WINDOW — only needed when dock can auto-hide/attach and indicator is enabled
|
||||
Loader {
|
||||
active: (barIsReady || !hasBar) && modelData && (Settings.data.dock.monitors.length === 0 || Settings.data.dock.monitors.includes(modelData.name))
|
||||
active: (autoHide || isAttachedMode) && Settings.data.dock.showDockIndicator && (barIsReady || !hasBar) && modelData && (Settings.data.dock.monitors.length === 0 || Settings.data.dock.monitors.includes(modelData.name))
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
id: dockIndicatorWindow
|
||||
@@ -780,13 +780,16 @@ Loader {
|
||||
implicitHeight: isVertical ? peekEdgeLength : indicatorThickness
|
||||
implicitWidth: isVertical ? indicatorThickness : peekEdgeLength
|
||||
|
||||
// Hide the window surface when indicator is not visible, so the compositor
|
||||
// can skip compositing this layer-shell surface entirely (saves GPU on NVIDIA)
|
||||
visible: indicatorRect.opacity > 0 || indicatorVisible
|
||||
|
||||
Rectangle {
|
||||
id: indicatorRect
|
||||
anchors.fill: parent
|
||||
radius: indicatorThickness
|
||||
color: Qt.alpha(Color.resolveColorKey(indicatorColorKey), indicatorOpacity)
|
||||
opacity: indicatorVisible ? 1 : 0
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
|
||||
@@ -135,7 +135,7 @@ ColumnLayout {
|
||||
property bool selected: false
|
||||
signal clicked
|
||||
|
||||
radius: height * 0.5
|
||||
radius: Style.iRadiusS
|
||||
color: selected ? Color.mPrimary : Color.mSurfaceVariant
|
||||
implicitHeight: Math.max(Style.baseWidgetSize * 0.55, 24)
|
||||
implicitWidth: chipLabel.implicitWidth + Style.marginM * 1.5
|
||||
|
||||
@@ -114,10 +114,6 @@ We welcome contributions of any size - bug fixes, new features, documentation im
|
||||
- **Want to code?** Check out our [development guidelines](https://docs.noctalia.dev/development/guideline)
|
||||
- **Need help?** Join our [Discord](https://discord.noctalia.dev)
|
||||
|
||||
### ✨ Nix DevShell
|
||||
|
||||
Nix users can use the flake's devShell to access a development environment. Run `nix develop` in the repo root to enter the dev shell. It includes packages, utilities and environment variables needed to develop Noctalia.
|
||||
|
||||
---
|
||||
|
||||
## ⭐ Star History
|
||||
|
||||
@@ -2,7 +2,7 @@ pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
|
||||
@@ -36,181 +36,25 @@ Singleton {
|
||||
|
||||
property var values: []
|
||||
property int barsCount: 32
|
||||
|
||||
// Idle detection to reduce GPU usage when there's no audio
|
||||
property bool isIdle: true
|
||||
property int idleFrameCount: 0
|
||||
readonly property int idleThreshold: 30 // Frames of silence before considered idle (0.5s at 60fps)
|
||||
|
||||
// Crash tracking for auto-restart
|
||||
property int _crashCount: 0
|
||||
property int _maxCrashes: 5
|
||||
PwAudioSpectrum {
|
||||
id: spectrum
|
||||
node: Pipewire.defaultAudioSink
|
||||
enabled: root.shouldRun
|
||||
barCount: root.barsCount
|
||||
frameRate: Settings.data.audio.spectrumFrameRate
|
||||
lowerCutoff: 50
|
||||
upperCutoff: 12000
|
||||
noiseReduction: 0.77
|
||||
smoothing: true
|
||||
|
||||
// Pre-allocated double buffer to avoid per-frame GC pressure.
|
||||
// We alternate between two arrays so the reference always changes (triggering QML bindings)
|
||||
// without allocating a new array on every audio frame.
|
||||
property var _buf0: new Array(barsCount).fill(0)
|
||||
property var _buf1: new Array(barsCount).fill(0)
|
||||
property bool _bufToggle: false
|
||||
|
||||
// Simple config
|
||||
property var config: ({
|
||||
"general": {
|
||||
"bars": barsCount,
|
||||
"framerate": Settings.data.audio.spectrumFrameRate,
|
||||
"autosens": 1,
|
||||
"sensitivity": 100,
|
||||
"lower_cutoff_freq": 50,
|
||||
"higher_cutoff_freq": 12000
|
||||
},
|
||||
"smoothing": {
|
||||
"monstercat": 1,
|
||||
"noise_reduction"// Enable monstercat smoothing for less jittery animation
|
||||
: 77
|
||||
},
|
||||
"output": {
|
||||
"method": "raw",
|
||||
"data_format": "ascii",
|
||||
"ascii_max_range": 100,
|
||||
"bit_format": "8bit",
|
||||
"channels": "mono",
|
||||
"mono_option": "average"
|
||||
}
|
||||
})
|
||||
|
||||
// Manage process lifecycle imperatively to avoid broken bindings.
|
||||
// A declarative `running: shouldRun` binding would be destroyed when
|
||||
// the restart timer or the Process itself sets `running` imperatively,
|
||||
// causing cava to keep running after all components unregister.
|
||||
onShouldRunChanged: {
|
||||
if (shouldRun && !process.running) {
|
||||
process.running = true;
|
||||
} else if (!shouldRun && process.running) {
|
||||
process.running = false;
|
||||
onValuesChanged: {
|
||||
root.values = spectrum.values;
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: restartTimer
|
||||
interval: 2000
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.shouldRun && !process.running) {
|
||||
Logger.w("Spectrum", "Restarting after crash...");
|
||||
process.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: process
|
||||
stdinEnabled: true
|
||||
command: ["cava", "-p", "/dev/stdin"]
|
||||
onRunningChanged: {
|
||||
Logger.d("Spectrum", "Process running:", running);
|
||||
}
|
||||
onExited: {
|
||||
stdinEnabled = true;
|
||||
values = Array(barsCount).fill(0);
|
||||
if (root.shouldRun) {
|
||||
root._crashCount++;
|
||||
if (root._crashCount <= root._maxCrashes) {
|
||||
Logger.w("Spectrum", "Process exited unexpectedly, restarting in 2s... (attempt " + root._crashCount + "/" + root._maxCrashes + ")");
|
||||
restartTimer.start();
|
||||
} else {
|
||||
Logger.e("Spectrum", "Process crashed too many times (" + root._maxCrashes + "), giving up");
|
||||
}
|
||||
} else {
|
||||
Logger.d("Spectrum", "Process exited (no longer needed)");
|
||||
root._crashCount = 0;
|
||||
}
|
||||
}
|
||||
onStarted: {
|
||||
Logger.d("Spectrum", "Process started");
|
||||
for (const k in config) {
|
||||
if (typeof config[k] !== "object") {
|
||||
write(k + "=" + config[k] + "\n");
|
||||
continue;
|
||||
}
|
||||
write("[" + k + "]\n");
|
||||
const obj = config[k];
|
||||
for (const k2 in obj) {
|
||||
write(k2 + "=" + obj[k2] + "\n");
|
||||
}
|
||||
}
|
||||
stdinEnabled = false;
|
||||
values = Array(barsCount).fill(0);
|
||||
}
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
if (root._crashCount > 0)
|
||||
root._crashCount = 0;
|
||||
|
||||
// Optimized parsing directly into pre-allocated buffer (alternating to trigger bindings)
|
||||
const buffer = root._bufToggle ? root._buf0 : root._buf1;
|
||||
let idx = 0;
|
||||
let num = 0;
|
||||
let allZero = true;
|
||||
|
||||
for (let i = 0, len = data.length - 1; i < len; i++) {
|
||||
const c = data.charCodeAt(i);
|
||||
if (c === 59) {
|
||||
// semicolon
|
||||
const val = num * 0.01;
|
||||
buffer[idx++] = val;
|
||||
if (val >= 0.01) {
|
||||
allZero = false;
|
||||
}
|
||||
num = 0;
|
||||
} else if (c >= 48 && c <= 57) {
|
||||
// digit 0-9
|
||||
num = num * 10 + (c - 48);
|
||||
}
|
||||
}
|
||||
// Handle last value if no trailing semicolon
|
||||
if (num > 0 || idx < root.barsCount) {
|
||||
const val = num * 0.01;
|
||||
buffer[idx++] = val;
|
||||
if (val >= 0.01) {
|
||||
allZero = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allZero) {
|
||||
root.idleFrameCount++;
|
||||
if (root.idleFrameCount >= root.idleThreshold) {
|
||||
// We're idle - stop updating values to save GPU
|
||||
if (!root.isIdle) {
|
||||
root.isIdle = true;
|
||||
// Set all values to 0 one final time
|
||||
root.values = Array(root.barsCount).fill(0);
|
||||
Logger.d("Spectrum", "Idle detected - stopped rendering");
|
||||
}
|
||||
// Don't update values while idle
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Audio detected - resume updates
|
||||
root.idleFrameCount = 0;
|
||||
if (root.isIdle) {
|
||||
root.isIdle = false;
|
||||
Logger.d("Spectrum", "Audio detected - resumed rendering");
|
||||
}
|
||||
}
|
||||
|
||||
// Update values only if not idle - swap to the other buffer to trigger binding updates
|
||||
if (!root.isIdle) {
|
||||
root._bufToggle = !root._bufToggle;
|
||||
root.values = buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.trim()) {
|
||||
Logger.w("Spectrum", "Error", text);
|
||||
}
|
||||
}
|
||||
onIdleChanged: {
|
||||
root.isIdle = spectrum.idle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ Item {
|
||||
property real minimumSignalValue: 0.05 // Default to 5% of height
|
||||
|
||||
// Pre compute horizontal mirroring
|
||||
readonly property int valuesCount: (values && Array.isArray(values)) ? values.length : 0
|
||||
readonly property int valuesCount: (values && values.length !== undefined) ? values.length : 0
|
||||
readonly property int totalBars: valuesCount * 2
|
||||
readonly property real barSlotSize: totalBars > 0 ? (vertical ? height : width) / totalBars : 0
|
||||
readonly property bool highQuality: (Settings.data.audio.visualizerType === "low") ? false : true
|
||||
|
||||
@@ -14,7 +14,7 @@ Item {
|
||||
property real minimumSignalValue: 0.05 // Default to 5% of height
|
||||
|
||||
// Pre-compute mirroring
|
||||
readonly property int valuesCount: (values && Array.isArray(values)) ? values.length : 0
|
||||
readonly property int valuesCount: (values && values.length !== undefined) ? values.length : 0
|
||||
readonly property int totalBars: valuesCount * 2
|
||||
readonly property real barSlotSize: totalBars > 0 ? (vertical ? height : width) / totalBars : 0
|
||||
readonly property bool highQuality: (Settings.data.audio.visualizerType === "low") ? false : true
|
||||
|
||||
@@ -14,7 +14,7 @@ Item {
|
||||
property bool showMinimumSignal: false
|
||||
property real minimumSignalValue: 0.05 // Default to 5% of height
|
||||
|
||||
readonly property int valuesCount: (values && Array.isArray(values)) ? values.length : 0
|
||||
readonly property int valuesCount: (values && values.length !== undefined) ? values.length : 0
|
||||
readonly property bool hasData: valuesCount >= 2
|
||||
|
||||
// Data texture: one pixel per value, R channel = amplitude
|
||||
|
||||
Reference in New Issue
Block a user