Compare commits

..
16 changed files with 188 additions and 61 deletions

View file

@ -148,24 +148,49 @@
-I shell/modules -I shell/services -I shell/applets -I shell/lock \ -I shell/modules -I shell/services -I shell/applets -I shell/lock \
shell/shell.qml shell/modules/*.qml shell/services/*.qml \ shell/shell.qml shell/modules/*.qml shell/services/*.qml \
shell/applets/*.qml shell/lock/*.qml \ shell/applets/*.qml shell/lock/*.qml \
> $TMPDIR/output.txt 2>&1 || true > $TMPDIR/raw.txt 2>&1 || true
# Extract unique warning messages (file:message, without line numbers) # Extract unique warning messages (file:message, without line numbers)
grep -E "^Warning:" $TMPDIR/output.txt \ grep -E "^Warning:" $TMPDIR/raw.txt \
| sed 's/^Warning: //' \ | sed 's/^Warning: //' \
| sed 's/\([^:]*\):[0-9]*:[0-9]*: /\1: /' \ | sed 's/\([^:]*\):[0-9]*:[0-9]*: /\1: /' \
| sort -u > $TMPDIR/current.txt | sort -u > $TMPDIR/current.txt
# Diff against known baseline - new warnings = failure # Compare against baseline
if ! diff -u test/qmllint-baseline.txt $TMPDIR/current.txt > $TMPDIR/diff.txt 2>&1; then touch $TMPDIR/new-warnings.txt $TMPDIR/stale-warnings.txt
new=$(grep '^+[^+]' $TMPDIR/diff.txt || true) while IFS= read -r line; do
if [ -n "$new" ]; then if ! grep -qFx "$line" test/qmllint-baseline.txt 2>/dev/null; then
echo "qmllint found new warnings not in baseline:" echo "$line" >> $TMPDIR/new-warnings.txt
echo "$new"
exit 1
fi fi
done < $TMPDIR/current.txt
while IFS= read -r line; do
if ! grep -qFx "$line" $TMPDIR/current.txt; then
echo "$line" >> $TMPDIR/stale-warnings.txt
fi
done < test/qmllint-baseline.txt
# Output dir with individual files for inspection
mkdir -p $out
cp $TMPDIR/raw.txt $out/raw.txt
cp $TMPDIR/current.txt $out/current.txt
cp test/qmllint-baseline.txt $out/baseline.txt
cp $TMPDIR/new-warnings.txt $out/new-warnings.txt
cp $TMPDIR/stale-warnings.txt $out/stale-warnings.txt
failed=0
if [ -s $TMPDIR/new-warnings.txt ]; then
echo "new warnings:"
sed 's/^/ /' $TMPDIR/new-warnings.txt
failed=1
fi fi
cp $TMPDIR/output.txt $out if [ -s $TMPDIR/stale-warnings.txt ]; then
echo "stale warnings:"
sed 's/^/ /' $TMPDIR/stale-warnings.txt
failed=1
fi
[ "$failed" -eq 0 ] || exit 1
''; '';
nova-stats-clippy = (pkgs.callPackage ./nix/stats-daemon.nix { }).overrideAttrs (old: { nova-stats-clippy = (pkgs.callPackage ./nix/stats-daemon.nix { }).overrideAttrs (old: {
pname = "nova-stats-clippy"; pname = "nova-stats-clippy";

View file

@ -229,6 +229,22 @@ WlSessionLockSurface {
_keyInput.forceActiveFocus(); _keyInput.forceActiveFocus();
} }
// Skip entrance animations on wake from sleep - lock was already visible
Connections {
target: S.SleepService
function onWokeFromSleep() {
if (!root.lock.locked)
return;
// Qt.callLater ensures this runs after onVisibleChanged resets
Qt.callLater(() => {
_overlay._revealed = true;
_clockItem._revealed = true;
_widgetCol._revealed = true;
});
}
}
// Sync TextInput when auth clears buffer externally (PAM submit, lock reset) // Sync TextInput when auth clears buffer externally (PAM submit, lock reset)
Connections { Connections {
target: root.auth target: root.auth

View file

@ -60,17 +60,17 @@ Text {
onHoveredChanged: { onHoveredChanged: {
root._hovered = hovered; root._hovered = hovered;
if (hovered && root.tooltip !== "") { if (hovered && root.tooltip !== "") {
M.FlyoutState.text = root.tooltip; M.TooltipState.text = root.tooltip;
M.FlyoutState.itemX = root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0); M.TooltipState.itemX = root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
M.FlyoutState.screen = QsWindow.window?.screen ?? null; M.TooltipState.screen = QsWindow.window?.screen ?? null;
M.FlyoutState.accentColor = root.accentColor; M.TooltipState.accentColor = root.accentColor;
M.FlyoutState.visible = true; M.TooltipState.visible = true;
} else if (!hovered && root.tooltip !== "") { } else if (!hovered && root.tooltip !== "") {
M.FlyoutState.visible = false; M.TooltipState.visible = false;
} }
} }
} }
onTooltipChanged: if (_hovered && tooltip !== "") onTooltipChanged: if (_hovered && tooltip !== "")
M.FlyoutState.text = tooltip M.TooltipState.text = tooltip
} }

View file

@ -31,17 +31,17 @@ Text {
onHoveredChanged: { onHoveredChanged: {
root._hovered = hovered; root._hovered = hovered;
if (hovered && root.tooltip !== "") { if (hovered && root.tooltip !== "") {
M.FlyoutState.text = root.tooltip; M.TooltipState.text = root.tooltip;
M.FlyoutState.itemX = root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0); M.TooltipState.itemX = root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
M.FlyoutState.screen = QsWindow.window?.screen ?? null; M.TooltipState.screen = QsWindow.window?.screen ?? null;
M.FlyoutState.accentColor = root.accentColor; M.TooltipState.accentColor = root.accentColor;
M.FlyoutState.visible = true; M.TooltipState.visible = true;
} else if (!hovered && root.tooltip !== "") { } else if (!hovered && root.tooltip !== "") {
M.FlyoutState.visible = false; M.TooltipState.visible = false;
} }
} }
} }
onTooltipChanged: if (_hovered && tooltip !== "") onTooltipChanged: if (_hovered && tooltip !== "")
M.FlyoutState.text = tooltip M.TooltipState.text = tooltip
} }

View file

@ -20,17 +20,17 @@ Row {
onHoveredChanged: { onHoveredChanged: {
root._hovered = hovered; root._hovered = hovered;
if (hovered && root.tooltip !== "") { if (hovered && root.tooltip !== "") {
M.FlyoutState.text = root.tooltip; M.TooltipState.text = root.tooltip;
M.FlyoutState.itemX = root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0); M.TooltipState.itemX = root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
M.FlyoutState.screen = QsWindow.window?.screen ?? null; M.TooltipState.screen = QsWindow.window?.screen ?? null;
M.FlyoutState.accentColor = root.accentColor; M.TooltipState.accentColor = root.accentColor;
M.FlyoutState.visible = true; M.TooltipState.visible = true;
} else if (!hovered && root.tooltip !== "") { } else if (!hovered && root.tooltip !== "") {
M.FlyoutState.visible = false; M.TooltipState.visible = false;
} }
} }
} }
onTooltipChanged: if (_hovered && tooltip !== "") onTooltipChanged: if (_hovered && tooltip !== "")
M.FlyoutState.text = tooltip M.TooltipState.text = tooltip
} }

View file

@ -20,7 +20,7 @@ M.BarIcon {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
menuLoader.active = !menuLoader.active; menuLoader.active = !menuLoader.active;
M.FlyoutState.visible = false; M.TooltipState.visible = false;
} }
} }

View file

@ -13,7 +13,7 @@ PanelWindow {
color: "transparent" color: "transparent"
property bool _winVisible: false property bool _winVisible: false
property bool _shown: M.FlyoutState.visible && M.FlyoutState.screen === root.screen property bool _shown: M.TooltipState.visible && M.TooltipState.screen === root.screen
on_ShownChanged: { on_ShownChanged: {
if (_shown) { if (_shown) {
@ -34,7 +34,7 @@ PanelWindow {
anchors.left: true anchors.left: true
margins.top: 0 margins.top: 0
margins.left: Math.max(0, Math.min(Math.round(M.FlyoutState.itemX - implicitWidth / 2), screen.width - implicitWidth)) margins.left: Math.max(0, Math.min(Math.round(M.TooltipState.itemX - implicitWidth / 2), screen.width - implicitWidth))
implicitWidth: label.implicitWidth + S.Theme.barPadding * 2 implicitWidth: label.implicitWidth + S.Theme.barPadding * 2
implicitHeight: label.implicitHeight + S.Theme.barPadding * 2 implicitHeight: label.implicitHeight + S.Theme.barPadding * 2
@ -86,13 +86,13 @@ PanelWindow {
M.PopupBackground { M.PopupBackground {
anchors.fill: parent anchors.fill: parent
accentColor: M.FlyoutState.accentColor accentColor: M.TooltipState.accentColor
} }
Text { Text {
id: label id: label
anchors.centerIn: parent anchors.centerIn: parent
text: M.FlyoutState.text.replace(/\n/g, "<br>") text: M.TooltipState.text.replace(/\n/g, "<br>")
textFormat: Text.RichText textFormat: Text.RichText
color: S.Theme.base05 color: S.Theme.base05
font.pixelSize: S.Theme.fontSize font.pixelSize: S.Theme.fontSize

View file

@ -59,13 +59,13 @@ RowLayout {
iconItem._hovered = hovered; iconItem._hovered = hovered;
const tip = [iconItem.modelData.tooltipTitle, iconItem.modelData.tooltipDescription].filter(s => s).join("\n") || iconItem.modelData.title; const tip = [iconItem.modelData.tooltipTitle, iconItem.modelData.tooltipDescription].filter(s => s).join("\n") || iconItem.modelData.title;
if (hovered && tip) { if (hovered && tip) {
M.FlyoutState.text = tip; M.TooltipState.text = tip;
M.FlyoutState.itemX = iconItem.mapToGlobal(iconItem.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0); M.TooltipState.itemX = iconItem.mapToGlobal(iconItem.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
M.FlyoutState.screen = QsWindow.window?.screen ?? null; M.TooltipState.screen = QsWindow.window?.screen ?? null;
M.FlyoutState.accentColor = root.parent?.accentColor ?? S.Theme.base05; M.TooltipState.accentColor = root.parent?.accentColor ?? S.Theme.base05;
M.FlyoutState.visible = true; M.TooltipState.visible = true;
} else if (!hovered) { } else if (!hovered) {
M.FlyoutState.visible = false; M.TooltipState.visible = false;
} }
} }
} }
@ -81,7 +81,7 @@ RowLayout {
if (root._activeMenu && root._activeMenu !== menuLoader) if (root._activeMenu && root._activeMenu !== menuLoader)
root._activeMenu.active = false; root._activeMenu.active = false;
menuLoader.active = true; menuLoader.active = true;
M.FlyoutState.visible = false; M.TooltipState.visible = false;
root._activeMenu = menuLoader; root._activeMenu = menuLoader;
} else { } else {
iconItem.modelData.secondaryActivate(); iconItem.modelData.secondaryActivate();

View file

@ -68,13 +68,13 @@ Row {
pill._hovered = hovered; pill._hovered = hovered;
const name = pill.modelData.name || ("Workspace " + pill.modelData.idx); const name = pill.modelData.name || ("Workspace " + pill.modelData.idx);
if (hovered) { if (hovered) {
M.FlyoutState.text = name; M.TooltipState.text = name;
M.FlyoutState.itemX = pill.mapToGlobal(pill.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0); M.TooltipState.itemX = pill.mapToGlobal(pill.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
M.FlyoutState.screen = QsWindow.window?.screen ?? null; M.TooltipState.screen = QsWindow.window?.screen ?? null;
M.FlyoutState.accentColor = root.parent?.accentColor ?? S.Theme.base05; M.TooltipState.accentColor = root.parent?.accentColor ?? S.Theme.base05;
M.FlyoutState.visible = true; M.TooltipState.visible = true;
} else { } else {
M.FlyoutState.visible = false; M.TooltipState.visible = false;
} }
} }
} }

View file

@ -12,7 +12,6 @@ BluetoothModule 1.0 BluetoothModule.qml
ClockModule 1.0 ClockModule.qml ClockModule 1.0 ClockModule.qml
CpuModule 1.0 CpuModule.qml CpuModule 1.0 CpuModule.qml
DiskModule 1.0 DiskModule.qml DiskModule 1.0 DiskModule.qml
Flyout 1.0 Flyout.qml
GpuModule 1.0 GpuModule.qml GpuModule 1.0 GpuModule.qml
HoverPanel 1.0 HoverPanel.qml HoverPanel 1.0 HoverPanel.qml
IdleInhibitorModule 1.0 IdleInhibitorModule.qml IdleInhibitorModule 1.0 IdleInhibitorModule.qml
@ -36,11 +35,12 @@ ScreenCapture 1.0 ScreenCapture.qml
ScreenCorners 1.0 ScreenCorners.qml ScreenCorners 1.0 ScreenCorners.qml
TemperatureModule 1.0 TemperatureModule.qml TemperatureModule 1.0 TemperatureModule.qml
ThemedIcon 1.0 ThemedIcon.qml ThemedIcon 1.0 ThemedIcon.qml
Tooltip 1.0 Tooltip.qml
TrayMenu 1.0 TrayMenu.qml TrayMenu 1.0 TrayMenu.qml
TrayModule 1.0 TrayModule.qml TrayModule 1.0 TrayModule.qml
VolumeModule 1.0 VolumeModule.qml VolumeModule 1.0 VolumeModule.qml
WeatherModule 1.0 WeatherModule.qml WeatherModule 1.0 WeatherModule.qml
WindowTitleModule 1.0 WindowTitleModule.qml WindowTitleModule 1.0 WindowTitleModule.qml
WorkspacesModule 1.0 WorkspacesModule.qml WorkspacesModule 1.0 WorkspacesModule.qml
singleton FlyoutState 1.0 FlyoutState.qml singleton TooltipState 1.0 TooltipState.qml
# keep-sorted end # keep-sorted end

View file

@ -0,0 +1,22 @@
pragma Singleton
import QtQuick
import Quickshell.Io
QtObject {
id: root
signal wokeFromSleep
property Process _monitor: Process {
running: true
command: ["gdbus", "monitor", "--system", "--dest", "org.freedesktop.login1", "--object-path", "/org/freedesktop/login1"]
stdout: SplitParser {
onRead: data => {
if (data.indexOf("PrepareForSleep") !== -1 && data.indexOf("false") !== -1)
root.wokeFromSleep();
}
}
}
}

View file

@ -122,10 +122,10 @@ QtObject {
} }
} }
// One-time: per-core max freq // One-time: per-core max freq (numerically sorted)
property var _maxFreqProc: Process { property var _maxFreqProc: Process {
running: true running: true
command: ["sh", "-c", "for f in /sys/devices/system/cpu/cpu[0-9]*/cpufreq/cpuinfo_max_freq; do [ -f \"$f\" ] && cat \"$f\" || echo 0; done 2>/dev/null"] command: ["sh", "-c", "ls -d /sys/devices/system/cpu/cpu[0-9]* 2>/dev/null | sort -V | while read d; do f=\"$d/cpufreq/cpuinfo_max_freq\"; [ -f \"$f\" ] && cat \"$f\" || echo 0; done"]
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
root.cpuCoreMaxFreq = text.trim().split("\n").filter(l => l).map(l => parseInt(l) / 1e6); root.cpuCoreMaxFreq = text.trim().split("\n").filter(l => l).map(l => parseInt(l) / 1e6);
@ -134,16 +134,79 @@ QtObject {
} }
// One-time: P/E-core topology // One-time: P/E-core topology
// Priority: cpu_core/cpu_atom sysfs > topology/core_type > freq-gap heuristic
property var _coreTypesProc: Process { property var _coreTypesProc: Process {
running: true running: true
command: ["sh", "-c", "for d in /sys/devices/system/cpu/cpu[0-9]*/topology/core_type; do [ -f \"$d\" ] && cat \"$d\" || echo Performance; done 2>/dev/null"] command: ["sh", "-c", String.raw`
# Intel hybrid: /sys/devices/cpu_core/cpus and cpu_atom/cpus give CPU ranges
if [ -f /sys/devices/cpu_core/cpus ] && [ -f /sys/devices/cpu_atom/cpus ]; then
core=$(cat /sys/devices/cpu_core/cpus)
atom=$(cat /sys/devices/cpu_atom/cpus)
echo "hybrid:$core:$atom"
exit 0
fi
# Fallback: per-core topology/core_type
ls -d /sys/devices/system/cpu/cpu[0-9]* 2>/dev/null | sort -V | while read d; do
f="$d/topology/core_type"
[ -f "$f" ] && cat "$f"
done
`]
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
root.cpuCoreTypes = text.trim().split("\n").filter(l => l).map(l => l.trim()); const out = text.trim();
if (!out)
return;
if (out.startsWith("hybrid:")) {
// Parse cpu_core/cpu_atom ranges into per-cpu type array
const parts = out.split(":");
const coreRange = parts[1];
const atomRange = parts[2];
function expandRange(s) {
const cpus = new Set();
for (const part of s.split(",")) {
if (part.includes("-")) {
const [a, b] = part.split("-").map(Number);
for (let i = a; i <= b; i++)
cpus.add(i);
} else {
cpus.add(Number(part));
}
}
return cpus;
}
const pCores = expandRange(coreRange);
const eCores = expandRange(atomRange);
const maxCpu = Math.max(...pCores, ...eCores);
const types = [];
for (let i = 0; i <= maxCpu; i++)
types.push(eCores.has(i) ? "Efficiency" : "Performance");
root.cpuCoreTypes = types;
} else {
// topology/core_type output
const types = out.split("\n").filter(l => l).map(l => l.trim());
if (types.length > 0)
root.cpuCoreTypes = types;
}
} }
} }
} }
// Fallback: infer P/E from max freq gap when no sysfs topology is available
function _inferCoreTypesFromFreq() {
if (cpuCoreTypes.length > 0 || cpuCoreMaxFreq.length < 2)
return;
const freqs = cpuCoreMaxFreq.filter(f => f > 0);
if (!freqs.length)
return;
const maxF = Math.max(...freqs);
const minF = Math.min(...freqs);
if (maxF > 0 && minF > 0 && (maxF - minF) / maxF > 0.15) {
const threshold = (maxF + minF) / 2;
cpuCoreTypes = cpuCoreMaxFreq.map(f => f >= threshold ? "Performance" : "Efficiency");
}
}
onCpuCoreMaxFreqChanged: Qt.callLater(_inferCoreTypesFromFreq)
// Disk via df // Disk via df
property var _diskProc: Process { property var _diskProc: Process {
id: diskProc id: diskProc

View file

@ -13,6 +13,7 @@ singleton NiriIpc 1.0 NiriIpc.qml
singleton NotifService 1.0 NotifService.qml singleton NotifService 1.0 NotifService.qml
singleton PowerProfileService 1.0 PowerProfileService.qml singleton PowerProfileService 1.0 PowerProfileService.qml
singleton ScreenshotService 1.0 ScreenshotService.qml singleton ScreenshotService 1.0 ScreenshotService.qml
singleton SleepService 1.0 SleepService.qml
singleton SystemStats 1.0 SystemStats.qml singleton SystemStats 1.0 SystemStats.qml
singleton Theme 1.0 Theme.qml singleton Theme 1.0 Theme.qml
singleton WeatherService 1.0 WeatherService.qml singleton WeatherService 1.0 WeatherService.qml

View file

@ -19,7 +19,7 @@ ShellRoot {
screen: scope.modelData screen: scope.modelData
} }
Flyout { Tooltip {
screen: scope.modelData screen: scope.modelData
} }

View file

@ -37,11 +37,6 @@ shell/modules/BluetoothModule.qml: Unqualified access [unqualified]
shell/modules/ClockModule.qml: Member "screen" not found on type "QObject" [missing-property] shell/modules/ClockModule.qml: Member "screen" not found on type "QObject" [missing-property]
shell/modules/CpuModule.qml: Member "screen" not found on type "QObject" [missing-property] shell/modules/CpuModule.qml: Member "screen" not found on type "QObject" [missing-property]
shell/modules/DiskModule.qml: Member "screen" not found on type "QObject" [missing-property] shell/modules/DiskModule.qml: Member "screen" not found on type "QObject" [missing-property]
shell/modules/Flyout.qml: Could not find property "left". [missing-property]
shell/modules/Flyout.qml: Could not find property "top". [missing-property]
shell/modules/Flyout.qml: Type PanelWindow is not creatable. [uncreatable-type]
shell/modules/Flyout.qml: Type margins is used but it is not resolved [unresolved-type]
shell/modules/Flyout.qml: unknown grouped property scope margins. [unqualified]
shell/modules/GpuModule.qml: Member "screen" not found on type "QObject" [missing-property] shell/modules/GpuModule.qml: Member "screen" not found on type "QObject" [missing-property]
shell/modules/HoverPanel.qml: Could not find property "top". [missing-property] shell/modules/HoverPanel.qml: Could not find property "top". [missing-property]
shell/modules/HoverPanel.qml: Type PanelWindow is not creatable. [uncreatable-type] shell/modules/HoverPanel.qml: Type PanelWindow is not creatable. [uncreatable-type]
@ -69,6 +64,11 @@ shell/modules/ScreenCorners.qml: Type PanelWindow is not creatable. [uncreatable
shell/modules/ScreenCorners.qml: Unqualified access [unqualified] shell/modules/ScreenCorners.qml: Unqualified access [unqualified]
shell/modules/TemperatureModule.qml: Member "screen" not found on type "QObject" [missing-property] shell/modules/TemperatureModule.qml: Member "screen" not found on type "QObject" [missing-property]
shell/modules/ThemedIcon.qml: Unqualified access [unqualified] shell/modules/ThemedIcon.qml: Unqualified access [unqualified]
shell/modules/Tooltip.qml: Could not find property "left". [missing-property]
shell/modules/Tooltip.qml: Could not find property "top". [missing-property]
shell/modules/Tooltip.qml: Type PanelWindow is not creatable. [uncreatable-type]
shell/modules/Tooltip.qml: Type margins is used but it is not resolved [unresolved-type]
shell/modules/Tooltip.qml: unknown grouped property scope margins. [unqualified]
shell/modules/TrayMenu.qml: Unqualified access [unqualified] shell/modules/TrayMenu.qml: Unqualified access [unqualified]
shell/modules/TrayModule.qml: Member "length" not found on type "UntypedObjectModel" [missing-property] shell/modules/TrayModule.qml: Member "length" not found on type "UntypedObjectModel" [missing-property]
shell/modules/TrayModule.qml: Member "screen" not found on type "QObject" [missing-property] shell/modules/TrayModule.qml: Member "screen" not found on type "QObject" [missing-property]