diff --git a/flake.nix b/flake.nix index 4691532..a0ba69c 100644 --- a/flake.nix +++ b/flake.nix @@ -148,49 +148,24 @@ -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/raw.txt 2>&1 || true + > $TMPDIR/output.txt 2>&1 || true # Extract unique warning messages (file:message, without line numbers) - grep -E "^Warning:" $TMPDIR/raw.txt \ + grep -E "^Warning:" $TMPDIR/output.txt \ | sed 's/^Warning: //' \ | sed 's/\([^:]*\):[0-9]*:[0-9]*: /\1: /' \ | sort -u > $TMPDIR/current.txt - # 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 + # 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 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 - if [ -s $TMPDIR/stale-warnings.txt ]; then - echo "stale warnings:" - sed 's/^/ /' $TMPDIR/stale-warnings.txt - failed=1 - fi - - [ "$failed" -eq 0 ] || exit 1 + cp $TMPDIR/output.txt $out ''; nova-stats-clippy = (pkgs.callPackage ./nix/stats-daemon.nix { }).overrideAttrs (old: { pname = "nova-stats-clippy"; diff --git a/shell/lock/LockSurface.qml b/shell/lock/LockSurface.qml index 27f14f9..6c1620d 100644 --- a/shell/lock/LockSurface.qml +++ b/shell/lock/LockSurface.qml @@ -229,22 +229,6 @@ 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 diff --git a/shell/modules/BarIcon.qml b/shell/modules/BarIcon.qml index ebad597..34e32b4 100644 --- a/shell/modules/BarIcon.qml +++ b/shell/modules/BarIcon.qml @@ -60,17 +60,17 @@ Text { onHoveredChanged: { root._hovered = hovered; if (hovered && root.tooltip !== "") { - 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; + 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; } else if (!hovered && root.tooltip !== "") { - M.TooltipState.visible = false; + M.FlyoutState.visible = false; } } } onTooltipChanged: if (_hovered && tooltip !== "") - M.TooltipState.text = tooltip + M.FlyoutState.text = tooltip } diff --git a/shell/modules/BarLabel.qml b/shell/modules/BarLabel.qml index b794001..eba1b8d 100644 --- a/shell/modules/BarLabel.qml +++ b/shell/modules/BarLabel.qml @@ -31,17 +31,17 @@ Text { onHoveredChanged: { root._hovered = hovered; if (hovered && root.tooltip !== "") { - 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; + 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; } else if (!hovered && root.tooltip !== "") { - M.TooltipState.visible = false; + M.FlyoutState.visible = false; } } } onTooltipChanged: if (_hovered && tooltip !== "") - M.TooltipState.text = tooltip + M.FlyoutState.text = tooltip } diff --git a/shell/modules/BarSection.qml b/shell/modules/BarSection.qml index 1082e5d..bb23e7d 100644 --- a/shell/modules/BarSection.qml +++ b/shell/modules/BarSection.qml @@ -20,17 +20,17 @@ Row { onHoveredChanged: { root._hovered = hovered; if (hovered && root.tooltip !== "") { - 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; + 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; } else if (!hovered && root.tooltip !== "") { - M.TooltipState.visible = false; + M.FlyoutState.visible = false; } } } onTooltipChanged: if (_hovered && tooltip !== "") - M.TooltipState.text = tooltip + M.FlyoutState.text = tooltip } diff --git a/shell/modules/Tooltip.qml b/shell/modules/Flyout.qml similarity index 86% rename from shell/modules/Tooltip.qml rename to shell/modules/Flyout.qml index 7424211..f90016b 100644 --- a/shell/modules/Tooltip.qml +++ b/shell/modules/Flyout.qml @@ -13,7 +13,7 @@ PanelWindow { color: "transparent" property bool _winVisible: false - property bool _shown: M.TooltipState.visible && M.TooltipState.screen === root.screen + property bool _shown: M.FlyoutState.visible && M.FlyoutState.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.TooltipState.itemX - implicitWidth / 2), screen.width - implicitWidth)) + margins.left: Math.max(0, Math.min(Math.round(M.FlyoutState.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.TooltipState.accentColor + accentColor: M.FlyoutState.accentColor } Text { id: label anchors.centerIn: parent - text: M.TooltipState.text.replace(/\n/g, "
") + text: M.FlyoutState.text.replace(/\n/g, "
") textFormat: Text.RichText color: S.Theme.base05 font.pixelSize: S.Theme.fontSize diff --git a/shell/modules/TooltipState.qml b/shell/modules/FlyoutState.qml similarity index 100% rename from shell/modules/TooltipState.qml rename to shell/modules/FlyoutState.qml diff --git a/shell/modules/PowerModule.qml b/shell/modules/PowerModule.qml index 6321b2b..f73c114 100644 --- a/shell/modules/PowerModule.qml +++ b/shell/modules/PowerModule.qml @@ -20,7 +20,7 @@ M.BarIcon { cursorShape: Qt.PointingHandCursor onClicked: { menuLoader.active = !menuLoader.active; - M.TooltipState.visible = false; + M.FlyoutState.visible = false; } } diff --git a/shell/modules/TrayModule.qml b/shell/modules/TrayModule.qml index a8ed98f..2224c9f 100644 --- a/shell/modules/TrayModule.qml +++ b/shell/modules/TrayModule.qml @@ -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.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; + 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; } else if (!hovered) { - M.TooltipState.visible = false; + M.FlyoutState.visible = false; } } } @@ -81,7 +81,7 @@ RowLayout { if (root._activeMenu && root._activeMenu !== menuLoader) root._activeMenu.active = false; menuLoader.active = true; - M.TooltipState.visible = false; + M.FlyoutState.visible = false; root._activeMenu = menuLoader; } else { iconItem.modelData.secondaryActivate(); diff --git a/shell/modules/WorkspacesModule.qml b/shell/modules/WorkspacesModule.qml index 6da1cfe..ed13927 100644 --- a/shell/modules/WorkspacesModule.qml +++ b/shell/modules/WorkspacesModule.qml @@ -68,13 +68,13 @@ Row { pill._hovered = hovered; const name = pill.modelData.name || ("Workspace " + pill.modelData.idx); if (hovered) { - 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; + 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; } else { - M.TooltipState.visible = false; + M.FlyoutState.visible = false; } } } diff --git a/shell/modules/qmldir b/shell/modules/qmldir index d52222c..13ce5bd 100644 --- a/shell/modules/qmldir +++ b/shell/modules/qmldir @@ -12,6 +12,7 @@ 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 @@ -35,12 +36,11 @@ 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 TooltipState 1.0 TooltipState.qml +singleton FlyoutState 1.0 FlyoutState.qml # keep-sorted end diff --git a/shell/services/SleepService.qml b/shell/services/SleepService.qml deleted file mode 100644 index 4c84577..0000000 --- a/shell/services/SleepService.qml +++ /dev/null @@ -1,22 +0,0 @@ -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(); - } - } - } -} diff --git a/shell/services/SystemStats.qml b/shell/services/SystemStats.qml index b2c67f0..7345727 100644 --- a/shell/services/SystemStats.qml +++ b/shell/services/SystemStats.qml @@ -122,10 +122,10 @@ QtObject { } } - // One-time: per-core max freq (numerically sorted) + // One-time: per-core max freq property var _maxFreqProc: Process { running: true - 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"] + 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"] stdout: StdioCollector { onStreamFinished: { root.cpuCoreMaxFreq = text.trim().split("\n").filter(l => l).map(l => parseInt(l) / 1e6); @@ -134,79 +134,16 @@ 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", 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 - `] + 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"] stdout: StdioCollector { onStreamFinished: { - 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; - } + root.cpuCoreTypes = text.trim().split("\n").filter(l => l).map(l => l.trim()); } } } - // 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 diff --git a/shell/services/qmldir b/shell/services/qmldir index 98ac21f..901fd4e 100644 --- a/shell/services/qmldir +++ b/shell/services/qmldir @@ -13,7 +13,6 @@ 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 diff --git a/shell/shell.qml b/shell/shell.qml index 032703f..01ddb67 100644 --- a/shell/shell.qml +++ b/shell/shell.qml @@ -19,7 +19,7 @@ ShellRoot { screen: scope.modelData } - Tooltip { + Flyout { screen: scope.modelData } diff --git a/test/qmllint-baseline.txt b/test/qmllint-baseline.txt index a5112d2..05382fc 100644 --- a/test/qmllint-baseline.txt +++ b/test/qmllint-baseline.txt @@ -37,6 +37,11 @@ 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] @@ -64,11 +69,6 @@ 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]