From 2209a4696d797798f77ef89ca6d96cb801eb999f Mon Sep 17 00:00:00 2001 From: Lemmy Date: Mon, 12 Jan 2026 20:36:53 -0500 Subject: [PATCH] Added anonymous telemetry --- Assets/Translations/de.json | 6 + Assets/Translations/en.json | 6 + Assets/Translations/es.json | 6 + Assets/Translations/fr.json | 6 + Assets/Translations/hu.json | 6 + Assets/Translations/ja.json | 6 + Assets/Translations/ku.json | 6 + Assets/Translations/nl.json | 6 + Assets/Translations/pl.json | 6 + Assets/Translations/pt.json | 6 + Assets/Translations/ru.json | 6 + Assets/Translations/tr.json | 6 + Assets/Translations/uk-UA.json | 6 + Assets/Translations/zh-CN.json | 6 + Assets/settings-default.json | 3 +- Commons/Settings.qml | 1 + .../Settings/Tabs/About/VersionSubTab.qml | 127 +++++++++++- Services/Noctalia/TelemetryService.qml | 189 ++++++++++++++++++ shell.qml | 1 + 19 files changed, 396 insertions(+), 9 deletions(-) create mode 100644 Services/Noctalia/TelemetryService.qml diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index ef2dba65a..6ce73ddd4 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -609,12 +609,18 @@ "system-kernel": "Kernel:", "system-loading": "Systeminformationen werden geladen...", "system-memory": "Speicher", + "system-monitor": "Monitor:", "system-not-installed": "fastfetch ist nicht installiert", "system-os": "Betriebssystem:", "system-packages": "Pakete:", "system-title": "Systeminformationen", "system-uptime": "Betriebszeit:", "system-wm": "WM:", + "telemetry-data-copied": "Telemetriedaten in die Zwischenablage kopiert", + "telemetry-desc": "Helfen Sie mit, Noctalia zu verbessern, indem Sie beim Start vollständig anonyme Systeminformationen senden.", + "telemetry-enabled": "Anonyme Systeminformationen senden", + "telemetry-show-data": "Daten anzeigen", + "telemetry-title": "Datenschutz", "title": "Über", "up-to-date": "Auf dem neuesten Stand!", "update-available": "Update verfügbar", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 0ef22d780..c882a4432 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -609,12 +609,18 @@ "system-kernel": "Kernel:", "system-loading": "Loading system info...", "system-memory": "Memory:", + "system-monitor": "Monitor:", "system-not-installed": "fastfetch is not installed", "system-os": "OS:", "system-packages": "Packages:", "system-title": "System Information", "system-uptime": "Uptime:", "system-wm": "WM:", + "telemetry-data-copied": "Telemetry data copied to clipboard", + "telemetry-desc": "Help improve Noctalia by sending completely anonymous system information on startup.", + "telemetry-enabled": "Send anonymous system information", + "telemetry-show-data": "View data", + "telemetry-title": "Privacy", "title": "About", "up-to-date": "You're up to date!", "update-available": "Update available", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index fee573718..fce9e1160 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -609,12 +609,18 @@ "system-kernel": "Núcleo:", "system-loading": "Cargando información del sistema...", "system-memory": "Memoria:", + "system-monitor": "Monitor:", "system-not-installed": "fastfetch no está instalado", "system-os": "SO:", "system-packages": "Paquetes:", "system-title": "Información del sistema", "system-uptime": "Tiempo de actividad:", "system-wm": "WM:", + "telemetry-data-copied": "Datos de telemetría copiados al portapapeles", + "telemetry-desc": "Ayuda a mejorar Noctalia enviando información del sistema completamente anónima al inicio.", + "telemetry-enabled": "Enviar información anónima del sistema", + "telemetry-show-data": "Ver datos", + "telemetry-title": "Privacidad", "title": "Acerca de", "up-to-date": "¡Estás al día!", "update-available": "Actualización disponible", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 89df093d6..b06c9fa48 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -609,12 +609,18 @@ "system-kernel": "Noyau :", "system-loading": "Chargement des informations système...", "system-memory": "Mémoire :", + "system-monitor": "Moniteur", "system-not-installed": "fastfetch n'est pas installé", "system-os": "Système d'exploitation :", "system-packages": "Paquets :", "system-title": "Informations système", "system-uptime": "Temps de disponibilité :", "system-wm": "WM :", + "telemetry-data-copied": "Données de télémétrie copiées dans le presse-papiers", + "telemetry-desc": "Aidez à améliorer Noctalia en envoyant des informations système complètement anonymes au démarrage.", + "telemetry-enabled": "Envoyer des informations système anonymes", + "telemetry-show-data": "Afficher les données", + "telemetry-title": "Confidentialité", "title": "À propos", "up-to-date": "Vous êtes à jour !", "update-available": "Mise à jour disponible", diff --git a/Assets/Translations/hu.json b/Assets/Translations/hu.json index a9241cca2..d2cce6ed8 100644 --- a/Assets/Translations/hu.json +++ b/Assets/Translations/hu.json @@ -609,12 +609,18 @@ "system-kernel": "Kernel:", "system-loading": "Rendszerinformációk betöltése...", "system-memory": "Memória:", + "system-monitor": "Monitor:", "system-not-installed": "A fastfetch nincs telepítve", "system-os": "Operációs rendszer:", "system-packages": "Csomagok:", "system-title": "Rendszerinformációk", "system-uptime": "Üzemidő:", "system-wm": "WM:", + "telemetry-data-copied": "Telemetriai adatok a vágólapra másolva", + "telemetry-desc": "Segíts fejleszteni a Noctaliát azzal, hogy teljesen anonim rendszerinformációkat küldesz indításkor.", + "telemetry-enabled": "Névtelen rendszerinformációk küldése", + "telemetry-show-data": "Adatok megtekintése", + "telemetry-title": "Adatvédelem", "title": "Névjegy", "up-to-date": "Naprakész vagy!", "update-available": "Frissítés elérhető", diff --git a/Assets/Translations/ja.json b/Assets/Translations/ja.json index e1bb4aad1..a52bb1052 100644 --- a/Assets/Translations/ja.json +++ b/Assets/Translations/ja.json @@ -609,12 +609,18 @@ "system-kernel": "カーネル", "system-loading": "システム情報を読み込み中...", "system-memory": "記憶", + "system-monitor": "モニター", "system-not-installed": "fastfetch がインストールされていません", "system-os": "OS:", "system-packages": "パッケージ:", "system-title": "システム情報", "system-uptime": "稼働時間:", "system-wm": "WM:", + "telemetry-data-copied": "テレメトリーデータをクリップボードにコピーしました。", + "telemetry-desc": "起動時に完全に匿名化されたシステム情報を送信して、Noctaliaの改善にご協力ください。", + "telemetry-enabled": "匿名でシステム情報を送信する", + "telemetry-show-data": "データの表示", + "telemetry-title": "プライバシー", "title": "バージョン情報", "up-to-date": "最新の状態です!", "update-available": "アップデートがあります", diff --git a/Assets/Translations/ku.json b/Assets/Translations/ku.json index 18860a042..108a1b917 100644 --- a/Assets/Translations/ku.json +++ b/Assets/Translations/ku.json @@ -609,12 +609,18 @@ "system-kernel": "Kernel:", "system-loading": "Agahiyên pergalê bar dikin...", "system-memory": "Bîr:", + "system-monitor": "Çavdêr:", "system-not-installed": "fastfetch nehatiye sazkirin", "system-os": "OS: OS", "system-packages": "Pakkêtan:", "system-title": "Agahiyên Sîstemê", "system-uptime": "Demdirêjî:", "system-wm": "WM: WM:", + "telemetry-data-copied": "Daneyên telemetriyê hatin kopîkirin bo clipboardê", + "telemetry-desc": "Alîkariya baştirkirina Noctalia bi şandina agahiyên pergalê yên bi tevahî anonîm di destpêkê de.", + "telemetry-enabled": "Agahiyên pergalê yên nenas bişîne", + "telemetry-show-data": "Dîtina daneyan", + "telemetry-title": "Nepênî", "title": "Derbar", "up-to-date": "Tu nû ye!", "update-available": "Nûvekirin heye", diff --git a/Assets/Translations/nl.json b/Assets/Translations/nl.json index c1a05370d..c8ec49c8d 100644 --- a/Assets/Translations/nl.json +++ b/Assets/Translations/nl.json @@ -609,12 +609,18 @@ "system-kernel": "Kernel:", "system-loading": "Systeeminformatie laden...", "system-memory": "Geheugen:", + "system-monitor": "Monitor:", "system-not-installed": "fastfetch is niet geïnstalleerd", "system-os": "Besturingssysteem:", "system-packages": "Pakketten:", "system-title": "Systeeminformatie", "system-uptime": "Uptime:", "system-wm": "WM:", + "telemetry-data-copied": "Telemetriegegevens gekopieerd naar klembord", + "telemetry-desc": "Help Noctalia te verbeteren door bij het opstarten volledig anonieme systeeminformatie te verzenden.", + "telemetry-enabled": "Anonieme systeeminformatie verzenden", + "telemetry-show-data": "Gegevens bekijken", + "telemetry-title": "Privacy", "title": "Over", "up-to-date": "Je bent up-to-date!", "update-available": "Update beschikbaar", diff --git a/Assets/Translations/pl.json b/Assets/Translations/pl.json index cc2d42642..d240b25e4 100644 --- a/Assets/Translations/pl.json +++ b/Assets/Translations/pl.json @@ -609,12 +609,18 @@ "system-kernel": "Jądro:", "system-loading": "Ładowanie informacji o systemie...", "system-memory": "Pamięć:", + "system-monitor": "Monitor: Monitor", "system-not-installed": "fastfetch nie jest zainstalowany", "system-os": "System operacyjny:", "system-packages": "Paczki:", "system-title": "Informacje o systemie", "system-uptime": "Czas pracy:", "system-wm": "WM:", + "telemetry-data-copied": "Dane telemetryczne skopiowane do schowka.", + "telemetry-desc": "Pomóż ulepszyć Noctalię, wysyłając całkowicie anonimowe informacje o systemie podczas uruchamiania.", + "telemetry-enabled": "Wyślij anonimowe informacje o systemie.", + "telemetry-show-data": "Wyświetl dane", + "telemetry-title": "Prywatność", "title": "O programie", "up-to-date": "Jesteś na bieżąco!", "update-available": "Dostępna aktualizacja", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 45736cc07..1d09b23d3 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -609,12 +609,18 @@ "system-kernel": "Kernel:", "system-loading": "Carregando informações do sistema...", "system-memory": "Memória:", + "system-monitor": "Monitor:", "system-not-installed": "fastfetch não está instalado", "system-os": "SO:", "system-packages": "Pacotes:", "system-title": "Informações do Sistema", "system-uptime": "Tempo de atividade:", "system-wm": "WM:", + "telemetry-data-copied": "Dados de telemetria copiados para a área de transferência.", + "telemetry-desc": "Ajude a melhorar o Noctalia enviando informações de sistema completamente anônimas na inicialização.", + "telemetry-enabled": "Enviar informações anônimas do sistema", + "telemetry-show-data": "Visualizar dados", + "telemetry-title": "Privacidade", "title": "Sobre", "up-to-date": "Você está atualizado!", "update-available": "Atualização disponível", diff --git a/Assets/Translations/ru.json b/Assets/Translations/ru.json index 9e2bbd1c1..1c3a9d881 100644 --- a/Assets/Translations/ru.json +++ b/Assets/Translations/ru.json @@ -609,12 +609,18 @@ "system-kernel": "Ядро:", "system-loading": "Загрузка системной информации...", "system-memory": "Память:", + "system-monitor": "Монитор", "system-not-installed": "fastfetch не установлен", "system-os": "ОС:", "system-packages": "Пакеты:", "system-title": "Системная информация", "system-uptime": "Время работы:", "system-wm": "ВМ:", + "telemetry-data-copied": "Данные телеметрии скопированы в буфер обмена.", + "telemetry-desc": "Помогите улучшить Noctalia, отправляя полностью анонимную системную информацию при запуске.", + "telemetry-enabled": "Отправить анонимную системную информацию", + "telemetry-show-data": "Просмотр данных", + "telemetry-title": "Конфиденциальность", "title": "О программе", "up-to-date": "У вас всё актуально!", "update-available": "Доступно обновление", diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index 2975c231c..9e2c7f813 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -609,12 +609,18 @@ "system-kernel": "Çekirdek:", "system-loading": "Sistem bilgileri yükleniyor...", "system-memory": "Hafıza:", + "system-monitor": "Monitör", "system-not-installed": "fastfetch kurulu değil", "system-os": "İşletim sistemi:", "system-packages": "Paketler:", "system-title": "Sistem Bilgisi", "system-uptime": "Çalışma süresi:", "system-wm": "WM:", + "telemetry-data-copied": "Telemetri verileri panoya kopyalandı.", + "telemetry-desc": "Noctalia'yı başlatılırken tamamen anonim sistem bilgileri göndererek geliştirmeye yardımcı olun.", + "telemetry-enabled": "Anonim sistem bilgisi gönder.", + "telemetry-show-data": "Verileri görüntüle", + "telemetry-title": "Gizlilik", "title": "Hakkında", "up-to-date": "Güncelsiniz!", "update-available": "Güncelleme mevcut", diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index c10100daf..61be0ad04 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -609,12 +609,18 @@ "system-kernel": "Ядро:", "system-loading": "Завантаження системної інформації...", "system-memory": "Пам'ять:", + "system-monitor": "Монітор:", "system-not-installed": "fastfetch не встановлено", "system-os": "ОС:", "system-packages": "Пакунки:", "system-title": "Інформація про систему", "system-uptime": "Час безперебійної роботи:", "system-wm": "WM:\nВМ:", + "telemetry-data-copied": "Дані телеметрії скопійовано до буфера обміну", + "telemetry-desc": "Допоможіть покращити Noctalia, надсилаючи повністю анонімну системну інформацію під час запуску.", + "telemetry-enabled": "Відправити анонімну системну інформацію", + "telemetry-show-data": "Перегляд даних", + "telemetry-title": "Конфіденційність", "title": "Про Noctalia", "up-to-date": "У вас остання версія!", "update-available": "Доступне оновлення", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 6792eea5d..782445858 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -609,12 +609,18 @@ "system-kernel": "内核:", "system-loading": "正在加载系统信息...", "system-memory": "内存:", + "system-monitor": "监视器", "system-not-installed": "未安装 fastfetch", "system-os": "操作系统:", "system-packages": "软件包:", "system-title": "系统信息", "system-uptime": "正常运行时间:", "system-wm": "窗口管理器:", + "telemetry-data-copied": "遥测数据已复制到剪贴板", + "telemetry-desc": "启动时发送完全匿名的系统信息,以帮助改进 Noctalia。", + "telemetry-enabled": "发送匿名系统信息", + "telemetry-show-data": "查看数据", + "telemetry-title": "隐私", "title": "关于", "up-to-date": "您已是最新版本!", "update-available": "有可用更新", diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 37dbd5ca5..6404b70d5 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -81,7 +81,8 @@ "shadowOffsetY": 3, "language": "", "allowPanelsOnScreenWithoutBar": true, - "showChangelogOnStartup": true + "showChangelogOnStartup": true, + "telemetryEnabled": true }, "ui": { "fontDefault": "", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 877f0a403..19eb5ee10 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -281,6 +281,7 @@ Singleton { property string language: "" property bool allowPanelsOnScreenWithoutBar: true property bool showChangelogOnStartup: true + property bool telemetryEnabled: true } // ui diff --git a/Modules/Panels/Settings/Tabs/About/VersionSubTab.qml b/Modules/Panels/Settings/Tabs/About/VersionSubTab.qml index 2e30a1064..6cbc8ffc4 100644 --- a/Modules/Panels/Settings/Tabs/About/VersionSubTab.qml +++ b/Modules/Panels/Settings/Tabs/About/VersionSubTab.qml @@ -4,6 +4,7 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import qs.Commons +import qs.Services.Compositor import qs.Services.Noctalia import qs.Services.System import qs.Services.UI @@ -17,6 +18,7 @@ ColumnLayout { property string commitInfo: "" readonly property bool isGitVersion: root.currentVersion.endsWith("-git") + readonly property int giga: (1024 * 1024 * 1024) // Update status: compare versions (strip -git suffix for comparison) readonly property string installedBase: root.currentVersion.replace("-git", "") @@ -44,10 +46,63 @@ ColumnLayout { return root.systemInfo.find(m => m.type === type); } + function getMonitorsText(separator) { + const sep = separator || "\n"; + const screens = Quickshell.screens || []; + const scales = CompositorService.displayScales || {}; + let lines = []; + for (let i = 0; i < screens.length; i++) { + const screen = screens[i]; + const name = screen.name || "Unknown"; + const scaleData = scales[name]; + const scaleValue = (typeof scaleData === "object" && scaleData !== null) ? (scaleData.scale || 1.0) : (scaleData || 1.0); + lines.push(name + ": " + screen.width + "x" + screen.height + " @ " + scaleValue + "x"); + } + return lines.join(sep); + } + + function getTelemetryPayload() { + const screens = Quickshell.screens || []; + const scales = CompositorService.displayScales || {}; + const monitors = []; + for (let i = 0; i < screens.length; i++) { + const screen = screens[i]; + const name = screen.name || "Unknown"; + const scaleData = scales[name]; + const scaleValue = (typeof scaleData === "object" && scaleData !== null) ? (scaleData.scale || 1.0) : (scaleData || 1.0); + monitors.push({ + width: screen.width || 0, + height: screen.height || 0, + scale: scaleValue + }); + } + return { + instanceId: TelemetryService.getInstanceId(), + version: UpdateService.currentVersion, + compositor: CompositorService.isHyprland ? "Hyprland" : CompositorService.isNiri ? "Niri" : CompositorService.isSway ? "Sway" : CompositorService.isMango ? "MangoWC" : CompositorService.isLabwc ? "LabWC" : "Unknown", + os: HostService.osPretty || "Unknown", + ramGb: Math.round((root.getModule("Memory")?.result?.total || 0) / root.giga), + monitors: monitors, + ui: { + scaleRatio: Settings.data.general.scaleRatio, + fontDefault: Settings.data.ui.fontDefault || "default", + fontDefaultScale: Settings.data.ui.fontDefaultScale, + fontFixed: Settings.data.ui.fontFixed || "default", + fontFixedScale: Settings.data.ui.fontFixedScale + } + }; + } + + function copyTelemetryData() { + const payload = getTelemetryPayload(); + const json = JSON.stringify(payload, null, 2); + Quickshell.execDetached(["wl-copy", json]); + ToastService.showNotice(I18n.tr("panels.about.telemetry-title"), I18n.tr("panels.about.telemetry-data-copied")); + } + function copyInfoToClipboard() { let info = "Noctalia Shell\n"; info += "==============\n"; - info += "Latest version: " + root.latestVersion + "\n"; info += "Installed version: " + root.currentVersion + "\n"; if (root.isGitVersion && root.commitInfo) { info += "Git commit: " + root.commitInfo + "\n"; @@ -70,12 +125,21 @@ ColumnLayout { info += "GPU: " + gpu.result.map(g => g.name || "Unknown").join(", ") + "\n"; } if (mem?.result) { - info += "Memory: " + root.formatBytes(mem.result.total) + "\n"; + info += "Memory: " + SystemStatService.formatMemoryGb(mem.result.total / root.giga) + "\n"; } if (wm?.result) { info += "WM: " + (wm.result.prettyName || wm.result.processName || "N/A") + "\n"; } } + const monitors = getMonitorsText("\n").split("\n"); + for (const mon of monitors) { + info += "Monitor: " + mon + "\n"; + } + info += "\nSettings\n"; + info += "========\n"; + info += "UI Scale: " + Settings.data.general.scaleRatio + "\n"; + info += "Default Font: " + (Settings.data.ui.fontDefault || "default") + " @ " + Settings.data.ui.fontDefaultScale + "x\n"; + info += "Fixed Font: " + (Settings.data.ui.fontFixed || "default") + " @ " + Settings.data.ui.fontFixedScale + "x\n"; Quickshell.execDetached(["wl-copy", info]); ToastService.showNotice(I18n.tr("panels.about.title"), I18n.tr("panels.about.info-copied")); } @@ -567,9 +631,8 @@ ColumnLayout { const mem = root.getModule("Memory"); if (!mem?.result) return "N/A"; - const giga = (1024 * 1024 * 1024); - const used = SystemStatService.formatMemoryGb(mem.result.used / giga); - const total = SystemStatService.formatMemoryGb(mem.result.total / giga); + const used = SystemStatService.formatMemoryGb(mem.result.used / root.giga); + const total = SystemStatService.formatMemoryGb(mem.result.total / root.giga); return used + " / " + total; } color: Color.mOnSurface @@ -590,9 +653,8 @@ ColumnLayout { const rootDisk = disk.result.find(d => d.mountpoint === "/"); if (!rootDisk?.bytes) return "N/A"; - const giga = (1024 * 1024 * 1024); - const used = SystemStatService.formatMemoryGb(rootDisk.bytes.used / giga); - const total = SystemStatService.formatMemoryGb(rootDisk.bytes.total / giga); + const used = SystemStatService.formatMemoryGb(rootDisk.bytes.used / root.giga); + const total = SystemStatService.formatMemoryGb(rootDisk.bytes.total / root.giga); return used + " / " + total + " (" + rootDisk.filesystem + ")"; } color: Color.mOnSurface @@ -663,5 +725,54 @@ ColumnLayout { Layout.fillWidth: true wrapMode: Text.Wrap } + + // Monitors (2 items per screen: label + value) + Repeater { + model: Quickshell.screens.length * 2 + + NText { + readonly property int screenIndex: Math.floor(index / 2) + readonly property bool isLabel: index % 2 === 0 + readonly property var screen: Quickshell.screens[screenIndex] + + text: { + if (isLabel) + return I18n.tr("panels.about.system-monitor"); + const name = screen?.name || "Unknown"; + const scales = CompositorService.displayScales || {}; + const scaleData = scales[name]; + const scaleValue = (typeof scaleData === "object" && scaleData !== null) ? (scaleData.scale || 1.0) : (scaleData || 1.0); + return name + ": " + (screen?.width || 0) + "x" + (screen?.height || 0) + " @ " + scaleValue + "x"; + } + color: isLabel ? Color.mOnSurfaceVariant : Color.mOnSurface + Layout.fillWidth: !isLabel + wrapMode: Text.Wrap + } + } + } + + // Telemetry Section + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginL + } + + NHeader { + label: I18n.tr("panels.about.telemetry-title") + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.about.telemetry-enabled") + description: I18n.tr("panels.about.telemetry-desc") + checked: Settings.data.general.telemetryEnabled + onCheckedChanged: Settings.data.general.telemetryEnabled = checked + } + + NButton { + icon: "eye" + text: I18n.tr("panels.about.telemetry-show-data") + outlined: true + onClicked: root.copyTelemetryData() } } diff --git a/Services/Noctalia/TelemetryService.qml b/Services/Noctalia/TelemetryService.qml new file mode 100644 index 000000000..5c503733d --- /dev/null +++ b/Services/Noctalia/TelemetryService.qml @@ -0,0 +1,189 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Services.Compositor +import qs.Services.Noctalia +import qs.Services.System + +Singleton { + id: root + + property bool initialized: false + property bool isSending: false + property int totalRamGb: 0 + property string instanceId: "" + + readonly property string telemetryEndpoint: Quickshell.env("NOCTALIA_TELEMETRY_ENDPOINT") || "https://noctalia.dev:7777/ping" + readonly property string instanceIdSalt: "noctalia-telemetry-2025" + + function init() { + if (initialized) + return; + + initialized = true; + + if (!Settings.data.general.telemetryEnabled) { + Logger.d("Telemetry", "Telemetry disabled by user"); + return; + } + + // Read machine-id to generate instance ID, then read RAM, then send ping + machineIdProcess.running = true; + } + + Process { + id: machineIdProcess + command: ["cat", "/etc/machine-id"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + const machineId = text.trim(); + if (machineId && machineId.length > 0) { + root.instanceId = root.hashString(machineId + root.instanceIdSalt); + Logger.d("Telemetry", "Generated instance ID from machine-id"); + } else { + root.instanceId = root.generateRandomId(); + Logger.d("Telemetry", "Using random instance ID (machine-id unavailable)"); + } + memInfoProcess.running = true; + } + } + + onExited: function (exitCode) { + if (exitCode !== 0) { + root.instanceId = root.generateRandomId(); + Logger.d("Telemetry", "Using random instance ID (machine-id read failed)"); + memInfoProcess.running = true; + } + } + } + + function hashString(str) { + // Simple hash function that produces a UUID-like string + let hash = 0; + for (let i = 0; i < str.length; i++) { + const c = str.charCodeAt(i); + hash = ((hash << 5) - hash) + c; + hash = hash & hash; + } + // Convert to hex and pad to create UUID-like format + const hex = Math.abs(hash).toString(16).padStart(8, '0'); + const hex2 = Math.abs(hash * 31).toString(16).padStart(8, '0'); + const hex3 = Math.abs(hash * 37).toString(16).padStart(8, '0'); + const hex4 = Math.abs(hash * 41).toString(16).padStart(8, '0'); + return `${hex.slice(0, 8)}-${hex2.slice(0, 4)}-${hex2.slice(4, 8)}-${hex3.slice(0, 4)}-${hex3.slice(4, 8)}${hex4.slice(0, 4)}`; + } + + function generateRandomId() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + } + + function getInstanceId() { + return instanceId; + } + + Process { + id: memInfoProcess + command: ["sh", "-c", "grep MemTotal /proc/meminfo | awk '{print int($2/1048576)}'"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + const ramGb = parseInt(text.trim()) || 0; + root.totalRamGb = ramGb; + root.sendPing(); + } + } + + onExited: function (exitCode) { + if (exitCode !== 0) { + // Still send ping even if RAM detection fails + root.sendPing(); + } + } + } + + function sendPing() { + if (isSending) + return; + + isSending = true; + + const payload = { + instanceId: instanceId, + version: UpdateService.currentVersion, + compositor: getCompositorType(), + os: HostService.osPretty || "Unknown", + ramGb: totalRamGb, + monitors: getMonitorInfo(), + ui: { + scaleRatio: Settings.data.general.scaleRatio, + fontDefault: Settings.data.ui.fontDefault || "default", + fontDefaultScale: Settings.data.ui.fontDefaultScale, + fontFixed: Settings.data.ui.fontFixed || "default", + fontFixedScale: Settings.data.ui.fontFixedScale + } + }; + + Logger.d("Telemetry", "Sending anonymous ping:", JSON.stringify(payload)); + + const request = new XMLHttpRequest(); + request.onreadystatechange = function () { + if (request.readyState === XMLHttpRequest.DONE) { + if (request.status >= 200 && request.status < 300) { + Logger.d("Telemetry", "Ping sent successfully"); + } else { + Logger.d("Telemetry", "Ping failed with status:", request.status); + } + isSending = false; + } + }; + + request.open("POST", telemetryEndpoint); + request.setRequestHeader("Content-Type", "application/json"); + request.send(JSON.stringify(payload)); + } + + function getMonitorInfo() { + const monitors = []; + const screens = Quickshell.screens || []; + const scales = CompositorService.displayScales || {}; + + for (let i = 0; i < screens.length; i++) { + const screen = screens[i]; + const name = screen.name || "Unknown"; + const scaleData = scales[name]; + // Extract just the numeric scale value + const scaleValue = (typeof scaleData === "object" && scaleData !== null) ? (scaleData.scale || 1.0) : (scaleData || 1.0); + monitors.push({ + width: screen.width || 0, + height: screen.height || 0, + scale: scaleValue + }); + } + + return monitors; + } + + function getCompositorType() { + if (CompositorService.isHyprland) + return "Hyprland"; + if (CompositorService.isNiri) + return "Niri"; + if (CompositorService.isSway) + return "Sway"; + if (CompositorService.isMango) + return "MangoWC"; + if (CompositorService.isLabwc) + return "LabWC"; + return "Unknown"; + } +} diff --git a/shell.qml b/shell.qml index 5b969b74e..44428369a 100644 --- a/shell.qml +++ b/shell.qml @@ -102,6 +102,7 @@ ShellRoot { PowerProfileService.init(); HostService.init(); GitHubService.init(); + TelemetryService.init(); delayedInitTimer.running = true; checkSetupWizard();