mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Startup: proper display of a dialog to confirm anonymous telemetry acceptance.
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user