Startup: proper display of a dialog to confirm anonymous telemetry acceptance.

This commit is contained in:
Lemmy
2026-01-16 11:48:05 -05:00
parent bb76a177b2
commit e6a80703d8
4 changed files with 191 additions and 66 deletions
+46 -21
View File
@@ -5,6 +5,7 @@ import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Modules.MainScreen
import qs.Services.Noctalia
import qs.Services.System
import qs.Services.UI
import qs.Widgets
@@ -12,10 +13,15 @@ import qs.Widgets
SmartPanel {
id: root
// When true, only shows step 0 with modified text for returning users (telemetry notification)
property bool telemetryOnlyMode: false
signal telemetryWizardCompleted
preferredWidth: Math.round(preferredWidthRatio * 2560 * Style.uiScaleRatio)
preferredHeight: Math.round(preferredHeightRatio * 1440 * Style.uiScaleRatio)
preferredWidthRatio: 0.4
preferredHeightRatio: 0.6
preferredHeightRatio: root.telemetryOnlyMode ? 0.45 : 0.6
panelAnchorHorizontalCenter: true
panelAnchorVerticalCenter: true
@@ -27,7 +33,7 @@ SmartPanel {
// Wizard state (lazy-loaded with panelContent)
property int currentStep: 0
readonly property int totalSteps: 5
readonly property int totalSteps: root.telemetryOnlyMode ? 1 : 5
property bool isCompleting: false
// Setup wizard data
@@ -72,27 +78,44 @@ SmartPanel {
}
try {
Logger.i("SetupWizard", "Completing setup with selected options");
Logger.i("SetupWizard", root.telemetryOnlyMode ? "Completing telemetry wizard" : "Completing setup with selected options");
isCompleting = true;
if (typeof WallpaperService !== "undefined" && WallpaperService.refreshWallpapersList) {
if (selectedWallpaperDirectory !== Settings.data.wallpaper.directory) {
Settings.data.wallpaper.directory = selectedWallpaperDirectory;
WallpaperService.refreshWallpapersList();
// In telemetry-only mode, we only need to save the telemetry setting
if (!root.telemetryOnlyMode) {
if (typeof WallpaperService !== "undefined" && WallpaperService.refreshWallpapersList) {
if (selectedWallpaperDirectory !== Settings.data.wallpaper.directory) {
Settings.data.wallpaper.directory = selectedWallpaperDirectory;
WallpaperService.refreshWallpapersList();
}
if (selectedWallpaper !== "") {
WallpaperService.changeWallpaper(selectedWallpaper, undefined);
}
}
if (selectedWallpaper !== "") {
WallpaperService.changeWallpaper(selectedWallpaper, undefined);
}
Settings.data.general.scaleRatio = selectedScaleRatio;
Settings.data.bar.position = selectedBarPosition;
}
Settings.data.general.scaleRatio = selectedScaleRatio;
Settings.data.bar.position = selectedBarPosition;
// Mark the current version as seen to prevent telemetry wizard on next startup
// (only for full setup wizard - telemetry wizard lets changelog mark it seen)
if (!root.telemetryOnlyMode) {
UpdateService.markChangelogSeen(UpdateService.currentVersion);
}
// Initialize telemetry now that user has made their choice
TelemetryService.init();
// Save settings immediately and wait for settingsSaved signal before closing
Settings.saveImmediate();
Logger.i("SetupWizard", "Setup completed successfully, waiting for settings save confirmation");
// Emit signal for telemetry wizard completion (shell.qml will show changelog)
if (root.telemetryOnlyMode) {
root.telemetryWizardCompleted();
}
// Fallback: if settingsSaved signal doesn't fire within 2 seconds, close anyway
closeTimer.start();
} catch (error) {
@@ -140,7 +163,7 @@ SmartPanel {
Item {
ColumnLayout {
anchors.centerIn: parent
width: Math.round(Math.min(parent.width - Style.marginXL * 2, 420))
width: Math.round(Math.max(parent.width * 0.5, 420))
spacing: Style.marginXL
// Logo with subtle glow effect
@@ -210,7 +233,7 @@ SmartPanel {
spacing: Style.marginM
NText {
text: "Welcome to Noctalia! ✨"
text: root.telemetryOnlyMode ? I18n.tr("setup.telemetry-wizard-title") : I18n.tr("setup.welcome-title")
pointSize: Style.fontSizeXXL * 1.4
font.weight: Style.fontWeightBold
color: Color.mOnSurface
@@ -219,7 +242,7 @@ SmartPanel {
}
NText {
text: "Let's make your desktop uniquely yours"
text: root.telemetryOnlyMode ? I18n.tr("setup.telemetry-wizard-subtitle") : I18n.tr("setup.welcome-subtitle")
pointSize: Style.fontSizeL
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
@@ -234,12 +257,11 @@ SmartPanel {
Layout.preferredHeight: childrenRect.height + Style.marginXL
color: Color.mSurfaceVariant
radius: Style.radiusL
opacity: 0.4
NText {
anchors.centerIn: parent
width: parent.width - Style.marginL * 2
text: I18n.tr("setup.welcome-note")
text: root.telemetryOnlyMode ? I18n.tr("setup.telemetry-wizard-note") : I18n.tr("setup.welcome-note")
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignHCenter
@@ -308,12 +330,14 @@ SmartPanel {
Layout.preferredHeight: 1
color: Color.mOutline
opacity: 0.2
visible: !root.telemetryOnlyMode
}
// Modern progress indicator with labels
Item {
Layout.fillWidth: true
Layout.preferredHeight: 32
visible: !root.telemetryOnlyMode
RowLayout {
anchors.centerIn: parent
@@ -410,8 +434,9 @@ SmartPanel {
spacing: Style.marginM
NButton {
text: "Skip Setup"
text: I18n.tr("setup.skip-setup")
outlined: true
visible: !root.telemetryOnlyMode
Layout.preferredHeight: 44
onClicked: {
panelContent.completeSetup();
@@ -423,9 +448,9 @@ SmartPanel {
}
NButton {
text: "← Back"
text: "← " + I18n.tr("common.back")
outlined: true
visible: currentStep > 0
visible: currentStep > 0 && !root.telemetryOnlyMode
Layout.preferredHeight: 44
onClicked: {
if (currentStep > 0) {
@@ -435,7 +460,7 @@ SmartPanel {
}
NButton {
text: currentStep === totalSteps - 1 ? "All Done!" : "Continue →"
text: root.telemetryOnlyMode ? I18n.tr("setup.telemetry-wizard-done") : (currentStep === totalSteps - 1 ? I18n.tr("setup.all-done") : I18n.tr("common.continue") + " →")
Layout.preferredHeight: 44
onClicked: {
if (currentStep < totalSteps - 1) {
+62 -17
View File
@@ -11,11 +11,14 @@ Singleton {
id: root
// Version properties
readonly property string baseVersion: "4.0.1"
readonly property string baseVersion: "4.0.2"
readonly property bool isDevelopment: true
readonly property string developmentSuffix: "-git"
readonly property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + developmentSuffix}`
// Telemetry was introduced in this version - users upgrading from earlier need to see the wizard
readonly property string telemetryIntroVersion: "4.0.2"
// URLs
readonly property string discordUrl: "https://discord.noctalia.dev"
readonly property string feedbackUrl: Quickshell.env("NOCTALIA_CHANGELOG_FEEDBACK_URL") || ""
@@ -35,6 +38,7 @@ Singleton {
property string changelogLastSeenVersion: ""
property bool changelogStateLoaded: false
property bool pendingShowRequest: false
property bool pendingTelemetryWizardCheck: false
// Fix for FileView race condition
property bool saveInProgress: false
@@ -53,6 +57,7 @@ Singleton {
}
signal popupQueued(string fromVersion, string toVersion)
signal telemetryWizardNeeded
function init() {
if (initialized)
@@ -211,6 +216,47 @@ Singleton {
return 0;
}
// Check if user is upgrading from a version before telemetry was introduced
function shouldShowTelemetryWizard() {
if (!changelogStateLoaded)
return false;
if (Settings.isFreshInstall)
return false;
if (Settings.shouldOpenSetupWizard)
return false;
// No previous version recorded but settings exist - assume upgrading from old version
// (e.g., user deleted shell-state.json but has existing settings)
if (!changelogLastSeenVersion || changelogLastSeenVersion === "")
return true;
// Check if last seen version is before telemetry introduction
return compareVersions(changelogLastSeenVersion, telemetryIntroVersion) < 0;
}
// Called by shell.qml to check for telemetry wizard after init
// If state isn't loaded yet, sets a pending flag and emits telemetryWizardNeeded later
function checkTelemetryWizardOrChangelog() {
Logger.d("UpdateService", "checkTelemetryWizardOrChangelog called, stateLoaded:", changelogStateLoaded);
if (!changelogStateLoaded) {
// State not loaded yet, set pending flags
Logger.d("UpdateService", "State not loaded yet, setting pending flags");
pendingTelemetryWizardCheck = true;
pendingShowRequest = true;
return;
}
// State is already loaded, check immediately
const needsTelemetryWizard = shouldShowTelemetryWizard();
Logger.d("UpdateService", "shouldShowTelemetryWizard:", needsTelemetryWizard, "lastSeenVersion:", changelogLastSeenVersion);
if (needsTelemetryWizard) {
Logger.i("UpdateService", "Emitting telemetryWizardNeeded signal");
root.telemetryWizardNeeded();
} else {
showLatestChangelog();
}
}
function parseReleaseNotes(body) {
if (!body)
return [];
@@ -239,19 +285,11 @@ Singleton {
return;
}
const monitors = Settings.data.bar.monitors || [];
const allowPanelsOnScreenWithoutBar = Settings.data.general.allowPanelsOnScreenWithoutBar;
function canShowPanelsOnScreen(screen) {
const name = screen?.name || "";
return allowPanelsOnScreenWithoutBar || monitors.length === 0 || monitors.includes(name);
}
let targetScreen = viewChangelogTargetScreen;
if (targetScreen) {
// Explicit screen requested - validate it
if (!canShowPanelsOnScreen(targetScreen)) {
if (!PanelService.canShowPanelsOnScreen(targetScreen)) {
Logger.w("UpdateService", "Changelog cannot be shown on screen without bar:", targetScreen.name);
popupScheduled = false;
viewChangelogTargetScreen = null;
@@ -259,13 +297,7 @@ Singleton {
}
} else {
// No explicit screen - find one that can show panels
for (let i = 0; i < Quickshell.screens.length; i++) {
if (canShowPanelsOnScreen(Quickshell.screens[i])) {
targetScreen = Quickshell.screens[i];
break;
}
}
targetScreen = PanelService.findScreenForPanels();
if (!targetScreen) {
Logger.w("UpdateService", "No screen available to show changelog");
popupScheduled = false;
@@ -367,6 +399,19 @@ Singleton {
Logger.e("UpdateService", "Failed to load changelog state:", error);
}
changelogStateLoaded = true;
// Handle pending telemetry wizard check first
if (pendingTelemetryWizardCheck) {
pendingTelemetryWizardCheck = false;
if (shouldShowTelemetryWizard()) {
root.telemetryWizardNeeded();
} else if (pendingShowRequest) {
pendingShowRequest = false;
Qt.callLater(root.showLatestChangelog);
}
return;
}
if (pendingShowRequest) {
pendingShowRequest = false;
Qt.callLater(root.showLatestChangelog);
+18
View File
@@ -92,6 +92,24 @@ Singleton {
return name in registeredPanels;
}
// Check if panels can be shown on a given screen (has bar enabled or allowPanelsOnScreenWithoutBar)
function canShowPanelsOnScreen(screen) {
const name = screen?.name || "";
const monitors = Settings.data.bar.monitors || [];
const allowPanelsOnScreenWithoutBar = Settings.data.general.allowPanelsOnScreenWithoutBar;
return allowPanelsOnScreenWithoutBar || monitors.length === 0 || monitors.includes(name);
}
// Find a screen that can show panels
function findScreenForPanels() {
for (let i = 0; i < Quickshell.screens.length; i++) {
if (canShowPanelsOnScreen(Quickshell.screens[i])) {
return Quickshell.screens[i];
}
}
return null;
}
// Timer to switch from Exclusive to OnDemand keyboard focus on Hyprland
Timer {
id: keyboardInitTimer
+65 -28
View File
@@ -102,10 +102,8 @@ ShellRoot {
PowerProfileService.init();
HostService.init();
GitHubService.init();
TelemetryService.init();
delayedInitTimer.running = true;
checkSetupWizard();
}
Overview {}
@@ -152,7 +150,7 @@ ShellRoot {
}
// ---------------------------------------------
// Delayed timer
// Delayed initialization and wizard/changelog
// ---------------------------------------------
Timer {
id: delayedInitTimer
@@ -161,42 +159,81 @@ ShellRoot {
onTriggered: {
FontService.init();
UpdateService.init();
UpdateService.showLatestChangelog();
showWizardOrChangelog();
}
}
// ---------------------------------------------
// Setup Wizard
// ---------------------------------------------
// Retry timer for when panel isn't ready yet
Timer {
id: setupWizardTimer
id: wizardRetryTimer
running: false
interval: 2000
onTriggered: {
showSetupWizard();
interval: 500
property string pendingWizardType: "" // "setup", "telemetry", or ""
onTriggered: showWizardOrChangelog()
}
// Connect to telemetry wizard signal from UpdateService (for async state loading)
Connections {
target: UpdateService
function onTelemetryWizardNeeded() {
wizardRetryTimer.pendingWizardType = "telemetry";
showWizardOrChangelog();
}
}
function checkSetupWizard() {
// Only open the setup wizard for new users
if (!Settings.shouldOpenSetupWizard) {
property var telemetryWizardConnection: null
function showWizardOrChangelog() {
// Determine what to show: setup wizard > telemetry wizard > changelog
var wizardType = wizardRetryTimer.pendingWizardType;
if (wizardType === "") {
// First call - determine wizard type
if (Settings.shouldOpenSetupWizard) {
wizardType = "setup";
} else if (UpdateService.shouldShowTelemetryWizard()) {
wizardType = "telemetry";
} else {
// No wizard needed - init telemetry and show changelog
TelemetryService.init();
UpdateService.checkTelemetryWizardOrChangelog();
return;
}
}
var targetScreen = PanelService.findScreenForPanels();
if (!targetScreen) {
Logger.w("Shell", "No screen available to show wizard");
wizardRetryTimer.pendingWizardType = "";
return;
}
setupWizardTimer.start();
}
function showSetupWizard() {
// Open Setup Wizard as a panel in the same windowing system as Settings/ControlCenter
if (Quickshell.screens.length > 0) {
var targetScreen = Quickshell.screens[0];
var setupPanel = PanelService.getPanel("setupWizardPanel", targetScreen);
if (setupPanel) {
setupPanel.open();
} else {
// If not yet loaded, ensure it loads and try again shortly
setupWizardTimer.restart();
}
var setupPanel = PanelService.getPanel("setupWizardPanel", targetScreen);
if (!setupPanel) {
// Panel not ready, retry
wizardRetryTimer.pendingWizardType = wizardType;
wizardRetryTimer.restart();
return;
}
// Panel is ready, show it
wizardRetryTimer.pendingWizardType = "";
if (wizardType === "telemetry") {
setupPanel.telemetryOnlyMode = true;
// Connect to completion signal to show changelog afterward
if (telemetryWizardConnection) {
setupPanel.telemetryWizardCompleted.disconnect(telemetryWizardConnection);
}
telemetryWizardConnection = function () {
UpdateService.showLatestChangelog();
};
setupPanel.telemetryWizardCompleted.connect(telemetryWizardConnection);
} else {
setupPanel.telemetryOnlyMode = false;
}
setupPanel.open();
}
}