Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
446edaab9c | ||
|
|
e5e15f82fb | ||
|
|
0c955f93f8 |
16 changed files with 188 additions and 61 deletions
45
flake.nix
45
flake.nix
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ M.BarIcon {
|
|||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
menuLoader.active = !menuLoader.active;
|
||||
M.FlyoutState.visible = false;
|
||||
M.TooltipState.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
22
shell/services/SleepService.qml
Normal file
22
shell/services/SleepService.qml
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ ShellRoot {
|
|||
screen: scope.modelData
|
||||
}
|
||||
|
||||
Flyout {
|
||||
Tooltip {
|
||||
screen: scope.modelData
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
Reference in a new issue