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 \
shell/shell.qml shell/modules/*.qml shell/services/*.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)
grep -E "^Warning:" $TMPDIR/output.txt \
grep -E "^Warning:" $TMPDIR/raw.txt \
| sed 's/^Warning: //' \
| sed 's/\([^:]*\):[0-9]*:[0-9]*: /\1: /' \
| sort -u > $TMPDIR/current.txt
# Diff against known baseline - new warnings = failure
if ! diff -u test/qmllint-baseline.txt $TMPDIR/current.txt > $TMPDIR/diff.txt 2>&1; then
new=$(grep '^+[^+]' $TMPDIR/diff.txt || true)
if [ -n "$new" ]; then
echo "qmllint found new warnings not in baseline:"
echo "$new"
exit 1
# Compare against baseline
touch $TMPDIR/new-warnings.txt $TMPDIR/stale-warnings.txt
while IFS= read -r line; do
if ! grep -qFx "$line" test/qmllint-baseline.txt 2>/dev/null; then
echo "$line" >> $TMPDIR/new-warnings.txt
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
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: {
pname = "nova-stats-clippy";

View file

@ -229,6 +229,22 @@ WlSessionLockSurface {
_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)
Connections {
target: root.auth

View file

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

View file

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

View file

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

View file

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

View file

@ -13,7 +13,7 @@ PanelWindow {
color: "transparent"
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: {
if (_shown) {
@ -34,7 +34,7 @@ PanelWindow {
anchors.left: true
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
implicitHeight: label.implicitHeight + S.Theme.barPadding * 2
@ -86,13 +86,13 @@ PanelWindow {
M.PopupBackground {
anchors.fill: parent
accentColor: M.FlyoutState.accentColor
accentColor: M.TooltipState.accentColor
}
Text {
id: label
anchors.centerIn: parent
text: M.FlyoutState.text.replace(/\n/g, "<br>")
text: M.TooltipState.text.replace(/\n/g, "<br>")
textFormat: Text.RichText
color: S.Theme.base05
font.pixelSize: S.Theme.fontSize

View file

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

View file

@ -68,13 +68,13 @@ Row {
pill._hovered = hovered;
const name = pill.modelData.name || ("Workspace " + pill.modelData.idx);
if (hovered) {
M.FlyoutState.text = name;
M.FlyoutState.itemX = pill.mapToGlobal(pill.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
M.FlyoutState.screen = QsWindow.window?.screen ?? null;
M.FlyoutState.accentColor = root.parent?.accentColor ?? S.Theme.base05;
M.FlyoutState.visible = true;
M.TooltipState.text = name;
M.TooltipState.itemX = pill.mapToGlobal(pill.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
M.TooltipState.screen = QsWindow.window?.screen ?? null;
M.TooltipState.accentColor = root.parent?.accentColor ?? S.Theme.base05;
M.TooltipState.visible = true;
} 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
CpuModule 1.0 CpuModule.qml
DiskModule 1.0 DiskModule.qml
Flyout 1.0 Flyout.qml
GpuModule 1.0 GpuModule.qml
HoverPanel 1.0 HoverPanel.qml
IdleInhibitorModule 1.0 IdleInhibitorModule.qml
@ -36,11 +35,12 @@ ScreenCapture 1.0 ScreenCapture.qml
ScreenCorners 1.0 ScreenCorners.qml
TemperatureModule 1.0 TemperatureModule.qml
ThemedIcon 1.0 ThemedIcon.qml
Tooltip 1.0 Tooltip.qml
TrayMenu 1.0 TrayMenu.qml
TrayModule 1.0 TrayModule.qml
VolumeModule 1.0 VolumeModule.qml
WeatherModule 1.0 WeatherModule.qml
WindowTitleModule 1.0 WindowTitleModule.qml
WorkspacesModule 1.0 WorkspacesModule.qml
singleton FlyoutState 1.0 FlyoutState.qml
singleton TooltipState 1.0 TooltipState.qml
# 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 {
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 {
onStreamFinished: {
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
// Priority: cpu_core/cpu_atom sysfs > topology/core_type > freq-gap heuristic
property var _coreTypesProc: Process {
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 {
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
property var _diskProc: Process {
id: diskProc

View file

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

View file

@ -19,7 +19,7 @@ ShellRoot {
screen: scope.modelData
}
Flyout {
Tooltip {
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/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/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/HoverPanel.qml: Could not find property "top". [missing-property]
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/TemperatureModule.qml: Member "screen" not found on type "QObject" [missing-property]
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/TrayModule.qml: Member "length" not found on type "UntypedObjectModel" [missing-property]
shell/modules/TrayModule.qml: Member "screen" not found on type "QObject" [missing-property]