add noctalia and fuzzel
This commit is contained in:
+48
@@ -0,0 +1,48 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginM
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("common.info")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.contributors")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.supporters")
|
||||
tabIndex: 2
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
VersionSubTab {}
|
||||
ContributorsSubTab {}
|
||||
SupportersSubTab {}
|
||||
}
|
||||
}
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property var contributors: GitHubService.contributors
|
||||
property int avatarCacheVersion: 0
|
||||
|
||||
readonly property int topContributorsCount: 20
|
||||
|
||||
Connections {
|
||||
target: GitHubService
|
||||
function onCachedAvatarsChanged() {
|
||||
root.avatarCacheVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
NHeader {
|
||||
description: I18n.trp("panels.about.contributors-description", root.contributors.length)
|
||||
enableDescriptionRichText: true
|
||||
}
|
||||
|
||||
// Top 20 contributors with full cards (avoids GridView shader crashes on Qt 6.8)
|
||||
Flow {
|
||||
id: topContributorsFlow
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
Repeater {
|
||||
model: Math.min(root.contributors.length, root.topContributorsCount)
|
||||
|
||||
delegate: Rectangle {
|
||||
width: Math.max(Math.round(topContributorsFlow.width / 2 - Style.marginM - 1), Math.round(Style.baseWidgetSize * 4))
|
||||
height: Math.round(Style.baseWidgetSize * 2.3)
|
||||
radius: Style.radiusM
|
||||
color: contributorArea.containsMouse ? Color.mHover : "transparent"
|
||||
border.width: 1
|
||||
border.color: contributorArea.containsMouse ? Color.mPrimary : Color.mOutline
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
// Avatar container with rectangular design (modern, no shader issues)
|
||||
Item {
|
||||
id: wrapper
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 1.8
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 1.8
|
||||
|
||||
property bool isRounded: false
|
||||
|
||||
// Background and image container
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
// Simple circular image (pre-rendered, no shaders)
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: {
|
||||
// Depend on avatarCacheVersion to trigger re-evaluation
|
||||
var _ = root.avatarCacheVersion;
|
||||
// Try cached circular version first
|
||||
var username = root.contributors[index].login;
|
||||
var cached = GitHubService.getAvatarPath(username);
|
||||
if (cached) {
|
||||
wrapper.isRounded = true;
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Fall back to original avatar URL
|
||||
return root.contributors[index].avatar_url || "";
|
||||
}
|
||||
fillMode: Image.PreserveAspectFit // Fit since image is already circular with transparency
|
||||
mipmap: true
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: root.contributors[index].avatar_url !== undefined && root.contributors[index].avatar_url !== ""
|
||||
opacity: status === Image.Ready ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback icon
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: !root.contributors[index].avatar_url || root.contributors[index].avatar_url === ""
|
||||
icon: "person"
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: wrapper.isRounded
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
radius: width * 0.5
|
||||
border.width: Style.borderM
|
||||
border.color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
|
||||
// Info column
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: root.contributors[index].login || "Unknown"
|
||||
font.weight: Style.fontWeightBold
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NIcon {
|
||||
icon: "git-commit"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `${(root.contributors[index].contributions || 0).toString()} commits`
|
||||
pointSize: Style.fontSizeXS
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hover indicator
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: "arrow-right"
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mPrimary
|
||||
opacity: contributorArea.containsMouse ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: contributorArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.contributors[index].html_url)
|
||||
Quickshell.execDetached(["xdg-open", root.contributors[index].html_url]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remaining contributors (simple text links)
|
||||
Flow {
|
||||
id: remainingContributorsFlow
|
||||
visible: root.contributors.length > root.topContributorsCount
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
spacing: Style.marginS
|
||||
|
||||
Repeater {
|
||||
model: Math.max(0, root.contributors.length - root.topContributorsCount)
|
||||
|
||||
delegate: Rectangle {
|
||||
width: nameText.implicitWidth + Style.margin2M
|
||||
height: nameText.implicitHeight + Style.margin2S
|
||||
radius: Style.radiusS
|
||||
color: nameArea.containsMouse ? Color.mHover : "transparent"
|
||||
border.width: Style.borderS
|
||||
border.color: nameArea.containsMouse ? Color.mPrimary : Color.mOutline
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
id: nameText
|
||||
anchors.centerIn: parent
|
||||
text: root.contributors[index + root.topContributorsCount].login || "Unknown"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: nameArea.containsMouse ? Color.mOnHover : Color.mOnSurface
|
||||
font.weight: Style.fontWeightMedium
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nameArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.contributors[index + root.topContributorsCount].html_url)
|
||||
Quickshell.execDetached(["xdg-open", root.contributors[index + root.topContributorsCount].html_url]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property var supporters: SupporterService.supporters
|
||||
property int avatarCacheVersion: 0
|
||||
|
||||
Connections {
|
||||
target: SupporterService
|
||||
function onCachedAvatarsChanged() {
|
||||
root.avatarCacheVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
NHeader {
|
||||
description: root.supporters.length === 0 ? I18n.tr("panels.about.supporters-loading") : I18n.trp("panels.about.supporters-desc", root.supporters.length)
|
||||
enableDescriptionRichText: true
|
||||
}
|
||||
|
||||
NButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
icon: "heart"
|
||||
text: I18n.tr("panels.about.become-supporter")
|
||||
outlined: true
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["xdg-open", "https://buymeacoffee.com/noctalia"]);
|
||||
ToastService.showNotice(I18n.tr("panels.about.support"), I18n.tr("toast.donation-opened"));
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
// Supporter cards
|
||||
Flow {
|
||||
id: supportersFlow
|
||||
visible: root.supporters.length > 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL
|
||||
|
||||
Repeater {
|
||||
model: root.supporters.length
|
||||
|
||||
delegate: Rectangle {
|
||||
id: supporterCard
|
||||
|
||||
property bool hasGithub: !!root.supporters[index].github_username
|
||||
|
||||
width: Math.max(Math.round(supportersFlow.width / 2 - Style.marginL - 1), Math.round(Style.baseWidgetSize * 4.5))
|
||||
height: Math.round(Style.baseWidgetSize * 2.6)
|
||||
radius: Style.radiusM
|
||||
color: supporterArea.containsMouse && hasGithub ? Color.mHover : Qt.alpha(Color.mPrimary, 0.05)
|
||||
border.width: Style.borderM
|
||||
border.color: supporterArea.containsMouse && hasGithub ? Color.mPrimary : Qt.alpha(Color.mPrimary, 0.5)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
// Avatar with heart badge
|
||||
Item {
|
||||
id: avatarWrapper
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 2.0
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 2.0
|
||||
|
||||
property bool isRounded: false
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
visible: supporterCard.hasGithub
|
||||
source: {
|
||||
if (!supporterCard.hasGithub)
|
||||
return "";
|
||||
var _ = root.avatarCacheVersion;
|
||||
var username = root.supporters[index].github_username;
|
||||
var cached = SupporterService.getAvatarPath(username);
|
||||
if (cached) {
|
||||
avatarWrapper.isRounded = true;
|
||||
return cached;
|
||||
}
|
||||
return "https://github.com/" + username + ".png?size=256";
|
||||
}
|
||||
fillMode: Image.PreserveAspectFit
|
||||
mipmap: true
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
opacity: status === Image.Ready ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback logo for supporters without GitHub
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: !supporterCard.hasGithub
|
||||
radius: width * 0.5
|
||||
color: Qt.alpha(Color.mPrimary, 0.15)
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
source: "../../../../../Assets/noctalia.svg"
|
||||
width: parent.width * 0.75
|
||||
height: width
|
||||
fillMode: Image.PreserveAspectFit
|
||||
mipmap: true
|
||||
smooth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: avatarWrapper.isRounded || !supporterCard.hasGithub
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
radius: width * 0.5
|
||||
border.width: Style.borderM
|
||||
border.color: Color.mPrimary
|
||||
}
|
||||
|
||||
// Heart badge
|
||||
Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.rightMargin: -2
|
||||
anchors.bottomMargin: -2
|
||||
width: Style.fontSizeM + Style.marginS
|
||||
height: width
|
||||
radius: width * 0.5
|
||||
color: Color.mPrimary
|
||||
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: "heart-filled"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Info column
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: root.supporters[index].name || root.supporters[index].github_username || "Unknown"
|
||||
font.weight: Style.fontWeightBold
|
||||
color: supporterArea.containsMouse && supporterCard.hasGithub ? Color.mOnHover : Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.about.supporter-badge")
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mPrimary
|
||||
font.weight: Style.fontWeightMedium
|
||||
}
|
||||
}
|
||||
|
||||
// Hover indicator (only for supporters with GitHub)
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: "arrow-right"
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mPrimary
|
||||
visible: supporterCard.hasGithub
|
||||
opacity: supporterArea.containsMouse ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: supporterArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: supporterCard.hasGithub ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
var username = root.supporters[index].github_username;
|
||||
if (username) {
|
||||
Quickshell.execDetached(["xdg-open", "https://github.com/" + username]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+985
@@ -0,0 +1,985 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
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
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
opacity: 0
|
||||
|
||||
onSystemInfoLoadingChanged: {
|
||||
if (!systemInfoLoading)
|
||||
tabAppearAnim.start();
|
||||
}
|
||||
|
||||
NumberAnimation on opacity {
|
||||
id: tabAppearAnim
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Style.animationSlowest
|
||||
easing.type: Easing.OutCubic
|
||||
running: false
|
||||
}
|
||||
|
||||
property string latestVersion: GitHubService.latestVersion
|
||||
property string currentVersion: UpdateService.currentVersion
|
||||
property string commitInfo: ""
|
||||
property string qsVersion: ""
|
||||
property string qsRevision: ""
|
||||
|
||||
readonly property bool isGitVersion: root.currentVersion.endsWith("-git")
|
||||
readonly property int gigaB: (1024 * 1024 * 1024)
|
||||
readonly property int gigaD: (1000 * 1000 * 1000)
|
||||
|
||||
// Update status: compare versions
|
||||
readonly property bool updateAvailable: {
|
||||
if (!root.latestVersion || !root.currentVersion || root.latestVersion === I18n.tr("common.unknown"))
|
||||
return false;
|
||||
return UpdateService.compareVersions(root.latestVersion, root.currentVersion) > 0 && !root.isGitVersion;
|
||||
}
|
||||
readonly property bool isUpToDate: {
|
||||
if (!root.latestVersion || !root.currentVersion || root.latestVersion === I18n.tr("common.unknown"))
|
||||
return false;
|
||||
return UpdateService.compareVersions(root.latestVersion, root.currentVersion) <= 0;
|
||||
}
|
||||
|
||||
readonly property bool qsUpdateAvailable: {
|
||||
if (!GitHubService.latestQSVersion || !root.qsVersion || GitHubService.latestQSVersion === I18n.tr("common.unknown"))
|
||||
return false;
|
||||
return UpdateService.compareVersions(GitHubService.latestQSVersion, root.qsVersion) > 0;
|
||||
}
|
||||
|
||||
readonly property bool qsIsUpToDate: {
|
||||
if (!GitHubService.latestQSVersion || !root.qsVersion || GitHubService.latestQSVersion === I18n.tr("common.unknown"))
|
||||
return false;
|
||||
return UpdateService.compareVersions(GitHubService.latestQSVersion, root.qsVersion) <= 0;
|
||||
}
|
||||
|
||||
// System info properties
|
||||
property var systemInfo: null
|
||||
property bool systemInfoLoading: true
|
||||
property bool systemInfoAvailable: true
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
function getModule(type) {
|
||||
if (!root.systemInfo)
|
||||
return null;
|
||||
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: TelemetryService.getCompositorType(),
|
||||
os: HostService.osPretty || "Unknown",
|
||||
ramGb: Math.round((root.getModule("Memory")?.result?.total || 0) / root.gigaB),
|
||||
monitors: monitors,
|
||||
ui: {
|
||||
scaleRatio: Settings.data.general.scaleRatio,
|
||||
fontDefaultScale: Settings.data.ui.fontDefaultScale,
|
||||
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: " + root.currentVersion;
|
||||
if (root.isGitVersion && root.commitInfo) {
|
||||
info += " (" + root.commitInfo + ")";
|
||||
}
|
||||
info += "\n";
|
||||
|
||||
if (root.qsVersion) {
|
||||
let qsV = root.qsVersion.startsWith("v") ? root.qsVersion : "v" + root.qsVersion;
|
||||
info += "Noctalia QS: " + qsV;
|
||||
if (root.qsRevision) {
|
||||
info += " (" + root.qsRevision + ")";
|
||||
}
|
||||
info += "\n";
|
||||
}
|
||||
|
||||
info += "\nSystem Information\n";
|
||||
info += "==================\n";
|
||||
if (root.systemInfo) {
|
||||
const os = root.getModule("OS");
|
||||
const kernel = root.getModule("Kernel");
|
||||
const title = root.getModule("Title");
|
||||
const product = root.getModule("Host");
|
||||
const board = root.getModule("Board");
|
||||
const cpu = root.getModule("CPU");
|
||||
const gpu = root.getModule("GPU");
|
||||
const mem = root.getModule("Memory");
|
||||
const wm = root.getModule("WM");
|
||||
info += "OS: " + (os?.result?.prettyName || "N/A") + "\n";
|
||||
info += "Kernel: " + (kernel?.result?.release || "N/A") + "\n";
|
||||
info += "Host: " + (title?.result?.hostName || "N/A") + "\n";
|
||||
info += "Product: " + (product?.result?.name || "N/A") + "\n";
|
||||
info += "Board: " + (board?.result?.name || "N/A") + "\n";
|
||||
info += "CPU: " + (cpu?.result?.cpu || "N/A") + "\n";
|
||||
if (gpu?.result && Array.isArray(gpu.result) && gpu.result.length > 0) {
|
||||
info += "GPU: " + gpu.result.map(g => g.name || "Unknown").join(", ") + "\n";
|
||||
}
|
||||
if (mem?.result) {
|
||||
info += "Memory: " + (mem.result.total / root.gigaB).toFixed(1) + " GB \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"));
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Check if fastfetch is available before trying to run it
|
||||
checkFastfetchProcess.running = true;
|
||||
qsVersionProcess.running = true;
|
||||
|
||||
Logger.d("VersionSubTab", "Current version:", root.currentVersion);
|
||||
Logger.d("VersionSubTab", "Is git version:", root.isGitVersion);
|
||||
// Only fetch commit info for -git versions
|
||||
if (root.isGitVersion) {
|
||||
// On NixOS, extract commit hash from the store path first
|
||||
if (HostService.isNixOS) {
|
||||
var shellDir = Quickshell.shellDir || "";
|
||||
Logger.d("VersionSubTab", "Component.onCompleted - NixOS detected, shellDir:", shellDir);
|
||||
if (shellDir) {
|
||||
// Extract commit hash from path like: /nix/store/...-noctalia-shell-2025-11-30_225e6d3/share/noctalia-shell
|
||||
// Pattern matches: noctalia-shell-YYYY-MM-DD_<commit_hash>
|
||||
var match = shellDir.match(/noctalia-shell-\d{4}-\d{2}-\d{2}_([0-9a-f]{7,})/i);
|
||||
if (match && match[1]) {
|
||||
// Use first 7 characters of the commit hash
|
||||
root.commitInfo = match[1].substring(0, 7);
|
||||
Logger.d("VersionSubTab", "Component.onCompleted - Extracted commit from NixOS path:", root.commitInfo);
|
||||
return;
|
||||
} else {
|
||||
Logger.d("VersionSubTab", "Component.onCompleted - Could not extract commit from NixOS path, trying fallback");
|
||||
}
|
||||
}
|
||||
}
|
||||
fetchGitCommit();
|
||||
}
|
||||
}
|
||||
|
||||
function fetchGitCommit() {
|
||||
var shellDir = Quickshell.shellDir || "";
|
||||
Logger.d("VersionSubTab", "fetchGitCommit - shellDir:", shellDir);
|
||||
if (!shellDir) {
|
||||
Logger.d("VersionSubTab", "fetchGitCommit - Cannot determine shell directory, skipping git commit fetch");
|
||||
return;
|
||||
}
|
||||
|
||||
gitProcess.workingDirectory = shellDir;
|
||||
gitProcess.running = true;
|
||||
}
|
||||
|
||||
Process {
|
||||
id: gitProcess
|
||||
command: ["git", "rev-parse", "--short", "HEAD"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
Logger.d("VersionSubTab", "gitProcess - Process exited with code:", exitCode);
|
||||
if (exitCode === 0) {
|
||||
var gitOutput = stdout.text.trim();
|
||||
Logger.d("VersionSubTab", "gitProcess - gitOutput:", gitOutput);
|
||||
if (gitOutput) {
|
||||
root.commitInfo = gitOutput;
|
||||
Logger.d("VersionSubTab", "gitProcess - Set commitInfo to:", root.commitInfo);
|
||||
}
|
||||
} else {
|
||||
Logger.d("VersionSubTab", "gitProcess - Git command failed. Exit code:", exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: qsVersionProcess
|
||||
command: ["qs", "--version"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
var output = stdout.text.trim();
|
||||
// Format (old): "noctalia-qs 0.3.0, revision abc12345, distributed by: ..."
|
||||
// Format (new): "noctalia-qs 0.0.9 (revision b602b69c81d96a1d7c645328feb7b1e1d4b7b7a4, distributed by Unset)"
|
||||
// Only set if this is actually noctalia-qs; leave empty for upstream quickshell
|
||||
var match = output.match(/noctalia-qs\s+(\S+?)[\s,(]+revision\s*([0-9a-f]*)/i);
|
||||
if (match) {
|
||||
root.qsVersion = match[1];
|
||||
root.qsRevision = match[2] ? match[2].substring(0, 9) : "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
// Check if fastfetch is available before attempting to run it
|
||||
Process {
|
||||
id: checkFastfetchProcess
|
||||
command: ["sh", "-c", "command -v fastfetch"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
// fastfetch is available, run it
|
||||
Logger.d("VersionSubTab", "fastfetch found, running it");
|
||||
fastfetchProcess.running = true;
|
||||
} else {
|
||||
// fastfetch not found, show error state immediately
|
||||
Logger.w("VersionSubTab", "fastfetch not found");
|
||||
root.systemInfoLoading = false;
|
||||
root.systemInfoAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: fastfetchProcess
|
||||
command: ["fastfetch", "--format", "json", "--config", Quickshell.shellDir + "/Assets/Services/fastfetch/system-info.jsonc"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
root.systemInfoLoading = false;
|
||||
if (exitCode === 0) {
|
||||
try {
|
||||
root.systemInfo = JSON.parse(stdout.text);
|
||||
root.systemInfoAvailable = true;
|
||||
} catch (e) {
|
||||
Logger.w("VersionSubTab", "Failed to parse fastfetch JSON: " + e);
|
||||
root.systemInfoAvailable = false;
|
||||
}
|
||||
} else {
|
||||
root.systemInfoAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Style.marginXL
|
||||
|
||||
// Noctalia logo
|
||||
Image {
|
||||
source: "../../../../../Assets/noctalia.svg"
|
||||
width: 96 * Style.uiScaleRatio
|
||||
height: width
|
||||
fillMode: Image.PreserveAspectFit
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
mipmap: true
|
||||
smooth: true
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
rotation: Settings.isDebug ? 180 : 0
|
||||
|
||||
Behavior on rotation {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlowest
|
||||
easing.type: Easing.OutBack
|
||||
}
|
||||
}
|
||||
|
||||
property int debugTapCount: 0
|
||||
|
||||
Timer {
|
||||
id: debugTapTimer
|
||||
interval: 5000
|
||||
onTriggered: parent.debugTapCount = 0
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (parent.debugTapCount === 0) {
|
||||
debugTapTimer.restart();
|
||||
}
|
||||
parent.debugTapCount++;
|
||||
if (parent.debugTapCount >= 8) {
|
||||
parent.debugTapCount = 0;
|
||||
debugTapTimer.stop();
|
||||
Settings.isDebug = !Settings.isDebug;
|
||||
if (Settings.isDebug) {
|
||||
ToastService.showNotice("Debug", I18n.tr("panels.about.debug-enabled"));
|
||||
} else {
|
||||
ToastService.showNotice("Debug", I18n.tr("panels.about.debug-disabled"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
NHeader {
|
||||
label: "Noctalia Shell"
|
||||
}
|
||||
|
||||
// Versions
|
||||
GridLayout {
|
||||
columns: 2
|
||||
rowSpacing: Style.marginXS
|
||||
columnSpacing: Style.marginM
|
||||
|
||||
// Installed Version (Shell)
|
||||
NText {
|
||||
text: "Noctalia Shell:"
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: root.currentVersion
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
// Git commit in parentheses
|
||||
NText {
|
||||
id: commitText
|
||||
visible: root.isGitVersion
|
||||
text: "(" + (root.commitInfo || I18n.tr("common.loading")) + ")"
|
||||
color: commitMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeXS
|
||||
font.underline: commitMouseArea.containsMouse && root.commitInfo
|
||||
|
||||
MouseArea {
|
||||
id: commitMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: root.commitInfo ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onEntered: {
|
||||
if (root.commitInfo) {
|
||||
TooltipService.show(commitText, I18n.tr("panels.about.view-commit"));
|
||||
}
|
||||
}
|
||||
onExited: TooltipService.hide()
|
||||
onClicked: {
|
||||
if (root.commitInfo) {
|
||||
Quickshell.execDetached(["xdg-open", "https://github.com/noctalia-dev/noctalia-shell/commit/" + root.commitInfo]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update status indicator
|
||||
NIcon {
|
||||
id: upToDateIcon
|
||||
visible: root.isUpToDate
|
||||
icon: "circle-check"
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mPrimary
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: TooltipService.show(upToDateIcon, I18n.tr("panels.about.up-to-date"))
|
||||
onExited: TooltipService.hide()
|
||||
}
|
||||
}
|
||||
|
||||
NIcon {
|
||||
id: updateAvailableIcon
|
||||
visible: root.updateAvailable
|
||||
icon: "arrow-up-circle"
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mPrimary
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: TooltipService.show(updateAvailableIcon, I18n.tr("panels.about.update-available"))
|
||||
onExited: TooltipService.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Latest Version (Shell)
|
||||
NText {
|
||||
visible: root.updateAvailable
|
||||
text: I18n.tr("panels.about.noctalia-available")
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: root.updateAvailable
|
||||
text: root.latestVersion
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
// Divider-like spacing
|
||||
Item {
|
||||
visible: root.qsUpdateAvailable || root.updateAvailable
|
||||
Layout.columnSpan: 2
|
||||
Layout.preferredHeight: Style.marginXS
|
||||
}
|
||||
|
||||
// Quickshell Version
|
||||
NText {
|
||||
visible: root.qsVersion !== ""
|
||||
text: "Noctalia QS:"
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: root.qsVersion !== ""
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: root.qsVersion.startsWith("v") ? root.qsVersion : "v" + root.qsVersion
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
// Git revision in parentheses
|
||||
NText {
|
||||
id: qsRevisionText
|
||||
visible: root.qsRevision !== ""
|
||||
text: "(" + root.qsRevision + ")"
|
||||
color: qsRevisionMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeXS
|
||||
font.underline: qsRevisionMouseArea.containsMouse
|
||||
|
||||
MouseArea {
|
||||
id: qsRevisionMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: TooltipService.show(qsRevisionText, I18n.tr("panels.about.view-commit"))
|
||||
onExited: TooltipService.hide()
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["xdg-open", "https://github.com/noctalia-dev/noctalia-qs/commit/" + root.qsRevision]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update status indicator
|
||||
NIcon {
|
||||
id: qsUpToDateIcon
|
||||
visible: root.qsIsUpToDate
|
||||
icon: "circle-check"
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mPrimary
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: TooltipService.show(qsUpToDateIcon, I18n.tr("panels.about.up-to-date"))
|
||||
onExited: TooltipService.hide()
|
||||
}
|
||||
}
|
||||
|
||||
NIcon {
|
||||
id: qsUpdateAvailableIcon
|
||||
visible: root.qsUpdateAvailable
|
||||
icon: "arrow-up-circle"
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mPrimary
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: TooltipService.show(qsUpdateAvailableIcon, I18n.tr("panels.about.update-available"))
|
||||
onExited: TooltipService.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Latest Quickshell Version
|
||||
NText {
|
||||
visible: root.qsUpdateAvailable
|
||||
text: I18n.tr("panels.about.noctalia-available")
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: root.qsUpdateAvailable
|
||||
text: GitHubService.latestQSVersion
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: actionsGrid
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Style.marginM
|
||||
Layout.bottomMargin: Style.marginM
|
||||
rowSpacing: Style.marginM
|
||||
columnSpacing: Style.marginM
|
||||
|
||||
columns: (changelogBtn.implicitWidth + copyBtn.implicitWidth + supportBtn.implicitWidth + 2 * columnSpacing) < root.width ? 3 : 1
|
||||
|
||||
NButton {
|
||||
id: changelogBtn
|
||||
icon: "sparkles"
|
||||
text: I18n.tr("panels.about.changelog")
|
||||
outlined: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: {
|
||||
var screen = PanelService.openedPanel?.screen || SettingsPanelService.settingsWindow?.screen || PanelService.findScreenForPanels();
|
||||
SettingsPanelService.close(screen);
|
||||
UpdateService.viewChangelog(screen);
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
id: copyBtn
|
||||
icon: "copy"
|
||||
text: I18n.tr("panels.about.copy-info")
|
||||
outlined: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: root.copyInfoToClipboard()
|
||||
}
|
||||
|
||||
NButton {
|
||||
id: supportBtn
|
||||
icon: "heart"
|
||||
text: I18n.tr("panels.about.support")
|
||||
outlined: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["xdg-open", "https://buymeacoffee.com/noctalia"]);
|
||||
ToastService.showNotice(I18n.tr("panels.about.support"), I18n.tr("toast.donation-opened"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.about.changelog-on-startup")
|
||||
description: I18n.tr("panels.about.changelog-on-startup-desc")
|
||||
checked: Settings.data.general.showChangelogOnStartup
|
||||
onToggled: checked => Settings.data.general.showChangelogOnStartup = checked
|
||||
}
|
||||
|
||||
// System Information Section
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("panels.about.system-title")
|
||||
}
|
||||
|
||||
// Error state (fastfetch not installed)
|
||||
ColumnLayout {
|
||||
visible: !root.systemInfoAvailable
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-not-installed")
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-install-hint")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeXS
|
||||
}
|
||||
}
|
||||
|
||||
// System info grid
|
||||
GridLayout {
|
||||
id: sysInfo
|
||||
readonly property real textSize: Style.fontSizeS
|
||||
|
||||
visible: root.systemInfoAvailable && root.systemInfo
|
||||
Layout.fillWidth: true
|
||||
columns: 2
|
||||
rowSpacing: Style.marginXS
|
||||
columnSpacing: Style.marginM
|
||||
|
||||
// OS
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-os")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const os = root.getModule("OS");
|
||||
return os?.result?.prettyName || "N/A";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Kernel
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-kernel")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const kernel = root.getModule("Kernel");
|
||||
return kernel?.result?.release || "N/A";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Host
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-host")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const title = root.getModule("Title");
|
||||
return title?.result?.hostName || "N/A";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Product name
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-product")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const title = root.getModule("Host");
|
||||
return title?.result?.name || "N/A";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Board name
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-board")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const title = root.getModule("Board");
|
||||
return title?.result?.name || "N/A";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Uptime
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-uptime")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const value = root.getModule("Uptime")?.result?.uptime;
|
||||
return value ? Time.formatVagueHumanReadableDuration(value / 1000) : "-";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// CPU
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-cpu")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const cpu = root.getModule("CPU");
|
||||
if (!cpu?.result)
|
||||
return "N/A";
|
||||
let cpuText = cpu.result.cpu || "N/A";
|
||||
const cores = cpu.result.cores;
|
||||
if (cores?.logical) {
|
||||
cpuText += " (" + cores.logical + " threads)";
|
||||
}
|
||||
return cpuText;
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// GPU
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-gpu")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const gpu = root.getModule("GPU");
|
||||
if (!gpu?.result || !Array.isArray(gpu.result) || gpu.result.length === 0)
|
||||
return "N/A";
|
||||
return gpu.result.map(g => g.name || "Unknown").join(", ");
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Memory
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-memory")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const mem = root.getModule("Memory");
|
||||
if (!mem?.result)
|
||||
return "N/A";
|
||||
const used = (mem.result.used / root.gigaB).toFixed(1);
|
||||
const total = (mem.result.total / root.gigaB).toFixed(1);
|
||||
return used + " GiB / " + total + " GiB";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Disk
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-disk")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const disk = root.getModule("Disk");
|
||||
if (!disk?.result || !Array.isArray(disk.result) || disk.result.length === 0)
|
||||
return "N/A";
|
||||
const rootDisk = disk.result.find(d => d.mountpoint === "/");
|
||||
if (!rootDisk?.bytes)
|
||||
return "N/A";
|
||||
const used = (rootDisk.bytes.used / root.gigaD).toFixed(1);
|
||||
const total = (rootDisk.bytes.total / root.gigaD).toFixed(1);
|
||||
return used + " GB / " + total + " GB" + " (" + rootDisk.filesystem + ")";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// WM
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-wm")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const wm = root.getModule("WM");
|
||||
if (!wm?.result)
|
||||
return "N/A";
|
||||
let wmText = wm.result.prettyName || wm.result.processName || "N/A";
|
||||
if (wm.result.protocolName) {
|
||||
wmText += " (" + wm.result.protocolName + ")";
|
||||
}
|
||||
return wmText;
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Packages
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-packages")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const pkg = root.getModule("Packages");
|
||||
if (!pkg?.result)
|
||||
return "N/A";
|
||||
const result = pkg.result;
|
||||
if (result.all) {
|
||||
const managers = [];
|
||||
if (result.rpm > 0)
|
||||
managers.push("rpm: " + result.rpm);
|
||||
if (result.pacman > 0)
|
||||
managers.push("pacman: " + result.pacman);
|
||||
if (result.dpkg > 0)
|
||||
managers.push("dpkg: " + result.dpkg);
|
||||
if (result.flatpakSystem > 0 || result.flatpakUser > 0) {
|
||||
const flatpak = (result.flatpakSystem || 0) + (result.flatpakUser || 0);
|
||||
managers.push("flatpak: " + flatpak);
|
||||
}
|
||||
if (result.snap > 0)
|
||||
managers.push("snap: " + result.snap);
|
||||
if (result.nixSystem > 0 || result.nixUser > 0 || result.nixDefault > 0) {
|
||||
const nix = (result.nixSystem || 0) + (result.nixUser || 0) + (result.nixDefault || 0);
|
||||
managers.push("nix: " + nix);
|
||||
}
|
||||
if (result.brew > 0)
|
||||
managers.push("brew: " + result.brew);
|
||||
if (managers.length > 0) {
|
||||
return result.all + " (" + managers.join(", ") + ")";
|
||||
}
|
||||
return result.all.toString();
|
||||
}
|
||||
return "N/A";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
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
|
||||
pointSize: sysInfo.textSize
|
||||
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
|
||||
onToggled: checked => Settings.data.general.telemetryEnabled = checked
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
NButton {
|
||||
icon: "eye"
|
||||
text: I18n.tr("panels.about.telemetry-show-data")
|
||||
outlined: true
|
||||
onClicked: root.copyTelemetryData()
|
||||
}
|
||||
|
||||
NButton {
|
||||
icon: "shield-lock"
|
||||
text: I18n.tr("panels.about.privacy-policy")
|
||||
outlined: true
|
||||
onClicked: Quickshell.execDetached(["xdg-open", "https://noctalia.dev/privacy"])
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user