diff --git a/Modals/SettingsModal.qml b/Modals/SettingsModal.qml
index f8b382fe..9b6c7e86 100644
--- a/Modals/SettingsModal.qml
+++ b/Modals/SettingsModal.qml
@@ -5,6 +5,7 @@ import Quickshell
import Quickshell.Io
import qs.Common
import qs.Modules.Settings
+import qs.Services
import qs.Widgets
pragma ComponentBehavior
@@ -63,7 +64,10 @@ DankModal {
Column {
anchors.fill: parent
- anchors.margins: Theme.spacingM
+ anchors.leftMargin: Theme.spacingM
+ anchors.rightMargin: Theme.spacingM
+ anchors.topMargin: Theme.spacingM
+ anchors.bottomMargin: Theme.spacingXL
spacing: Theme.spacingS
// Header row with title and close button
@@ -127,6 +131,212 @@ DankModal {
anchors.topMargin: Theme.spacingM + 2
spacing: Theme.spacingXS
+ // Profile header box
+ Rectangle {
+ width: parent.width - Theme.spacingS * 2
+ height: 110
+ radius: Theme.cornerRadius
+ color: "transparent"
+ border.width: 0
+
+ Row {
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.leftMargin: Theme.spacingM
+ anchors.rightMargin: Theme.spacingM
+ spacing: Theme.spacingM
+
+ // Profile image container with hover overlay
+ Item {
+ id: profileImageContainer
+ width: 80
+ height: 80
+ anchors.verticalCenter: parent.verticalCenter
+
+ property bool hasImage: profileImageSource.status === Image.Ready
+
+ Rectangle {
+ anchors.fill: parent
+ radius: width / 2
+ color: "transparent"
+ border.color: Theme.primary
+ border.width: 1
+ visible: parent.hasImage
+ }
+
+ Image {
+ id: profileImageSource
+ source: {
+ if (PortalService.profileImage === "")
+ return "";
+ if (PortalService.profileImage.startsWith("/"))
+ return "file://" + PortalService.profileImage;
+ return PortalService.profileImage;
+ }
+ smooth: true
+ asynchronous: true
+ mipmap: true
+ cache: true
+ visible: false
+ }
+
+ MultiEffect {
+ anchors.fill: parent
+ anchors.margins: 5
+ source: profileImageSource
+ maskEnabled: true
+ maskSource: profileCircularMask
+ visible: profileImageContainer.hasImage
+ maskThresholdMin: 0.5
+ maskSpreadAtMin: 1
+ }
+
+ Item {
+ id: profileCircularMask
+ width: 70
+ height: 70
+ layer.enabled: true
+ layer.smooth: true
+ visible: false
+
+ Rectangle {
+ anchors.fill: parent
+ radius: width / 2
+ color: "black"
+ antialiasing: true
+ }
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ radius: width / 2
+ color: Theme.primary
+ visible: !parent.hasImage
+
+ DankIcon {
+ anchors.centerIn: parent
+ name: "person"
+ size: Theme.iconSizeLarge
+ color: Theme.primaryText
+ }
+ }
+
+ DankIcon {
+ anchors.centerIn: parent
+ name: "warning"
+ size: Theme.iconSizeLarge
+ color: Theme.error
+ visible: PortalService.profileImage !== "" && profileImageSource.status === Image.Error
+ }
+
+ // Hover overlay with edit and clear buttons
+ Rectangle {
+ anchors.fill: parent
+ radius: width / 2
+ color: Qt.rgba(0, 0, 0, 0.7)
+ visible: profileMouseArea.containsMouse
+
+ Row {
+ anchors.centerIn: parent
+ spacing: 4
+
+ Rectangle {
+ width: 28
+ height: 28
+ radius: 14
+ color: Qt.rgba(255, 255, 255, 0.9)
+
+ DankIcon {
+ anchors.centerIn: parent
+ name: "edit"
+ size: 16
+ color: "black"
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ settingsModal.allowFocusOverride = true;
+ settingsModal.shouldHaveFocus = false;
+ profileBrowser.open();
+ }
+ }
+ }
+
+ Rectangle {
+ width: 28
+ height: 28
+ radius: 14
+ color: Qt.rgba(255, 255, 255, 0.9)
+ visible: profileImageContainer.hasImage
+
+ DankIcon {
+ anchors.centerIn: parent
+ name: "close"
+ size: 16
+ color: "black"
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ PortalService.setProfileImage("");
+ }
+ }
+ }
+ }
+ }
+
+ MouseArea {
+ id: profileMouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ propagateComposedEvents: true
+ acceptedButtons: Qt.NoButton
+ }
+ }
+
+ // User info column
+ Column {
+ width: 120
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: Theme.spacingXS
+
+ StyledText {
+ text: UserInfoService.fullName || "User"
+ font.pixelSize: Theme.fontSizeLarge
+ font.weight: Font.Medium
+ color: Theme.surfaceText
+ elide: Text.ElideRight
+ width: parent.width
+ }
+
+ StyledText {
+ text: DgopService.distribution || "Linux"
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceVariantText
+ elide: Text.ElideRight
+ width: parent.width
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ width: parent.width - Theme.spacingS * 2
+ height: 1
+ color: Theme.outline
+ opacity: 0.2
+ }
+
+ Item {
+ width: parent.width
+ height: Theme.spacingL
+ }
+
Repeater {
id: sidebarRepeater
@@ -345,24 +555,88 @@ DankModal {
// Footer
Row {
anchors.horizontalCenter: parent.horizontalCenter
- spacing: Theme.spacingS
+ spacing: Theme.spacingXS
- StyledText {
- text: `DankMaterialShell • niri edition •`
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.surfaceVariantText
- linkColor: Theme.primary
+ // Dank logo
+ Item {
+ width: 68
+ height: 16
anchors.verticalCenter: parent.verticalCenter
- onLinkActivated: link => Qt.openUrlExternally(link)
-
+
+ Image {
+ anchors.fill: parent
+ source: Qt.resolvedUrl(".").toString().replace("file://", "").replace("/Modals/", "") + "/assets/dank.svg"
+ sourceSize: Qt.size(68, 16)
+ smooth: true
+ fillMode: Image.PreserveAspectFit
+ layer.enabled: true
+
+ layer.effect: MultiEffect {
+ colorization: 1
+ colorizationColor: Theme.primary
+ }
+ }
+
MouseArea {
anchors.fill: parent
- cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
- acceptedButtons: Qt.NoButton
- propagateComposedEvents: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: Qt.openUrlExternally("https://github.com/AvengeMedia/DankMaterialShell")
}
}
+ StyledText {
+ text: "•"
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Rectangle {
+ width: Theme.spacingXS
+ height: 1
+ color: "transparent"
+ }
+
+ // Niri logo
+ Item {
+ width: 24
+ height: 24
+ anchors.verticalCenter: parent.verticalCenter
+
+ Image {
+ anchors.fill: parent
+ source: Qt.resolvedUrl(".").toString().replace("file://", "").replace("/Modals/", "") + "/assets/niri.svg"
+ sourceSize: Qt.size(24, 24)
+ smooth: true
+ fillMode: Image.PreserveAspectFit
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ onClicked: Qt.openUrlExternally("https://github.com/YaLTeR/niri")
+ }
+ }
+
+ Rectangle {
+ width: Theme.spacingXS
+ height: 1
+ color: "transparent"
+ }
+
+ StyledText {
+ text: "•"
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Rectangle {
+ width: Theme.spacingM
+ height: 1
+ color: "transparent"
+ }
+
// Matrix button
Item {
width: 32
@@ -390,6 +664,12 @@ DankModal {
}
}
+ Rectangle {
+ width: Theme.spacingM
+ height: 1
+ color: "transparent"
+ }
+
StyledText {
text: "•"
font.pixelSize: Theme.fontSizeSmall
@@ -397,6 +677,12 @@ DankModal {
anchors.verticalCenter: parent.verticalCenter
}
+ Rectangle {
+ width: Theme.spacingM
+ height: 1
+ color: "transparent"
+ }
+
// Discord button
Item {
width: 16
@@ -417,6 +703,46 @@ DankModal {
onClicked: Qt.openUrlExternally("https://discord.gg/vT8Sfjy7sx")
}
}
+
+ Rectangle {
+ width: Theme.spacingM
+ height: 1
+ color: "transparent"
+ }
+
+ StyledText {
+ text: "•"
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Rectangle {
+ width: Theme.spacingM
+ height: 1
+ color: "transparent"
+ }
+
+ // Reddit button
+ Item {
+ width: 18
+ height: 18
+ anchors.verticalCenter: parent.verticalCenter
+
+ Image {
+ anchors.fill: parent
+ source: Qt.resolvedUrl(".").toString().replace("file://", "").replace("/Modals/", "") + "/assets/reddit.svg"
+ sourceSize: Qt.size(18, 18)
+ smooth: true
+ fillMode: Image.PreserveAspectFit
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ onClicked: Qt.openUrlExternally("https://reddit.com/r/niri")
+ }
+ }
}
}
@@ -425,4 +751,25 @@ DankModal {
}
+ FileBrowserModal {
+ id: profileBrowser
+
+ browserTitle: "Select Profile Image"
+ browserIcon: "person"
+ browserType: "profile"
+ fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
+ onFileSelected: (path) => {
+ PortalService.setProfileImage(path);
+ close();
+ }
+ onDialogClosed: {
+ if (settingsModal) {
+ settingsModal.allowFocusOverride = false;
+ settingsModal.shouldHaveFocus = Qt.binding(() => {
+ return settingsModal.shouldBeVisible;
+ });
+ }
+ }
+ }
+
}
diff --git a/Modules/Settings/PersonalizationTab.qml b/Modules/Settings/PersonalizationTab.qml
index 553bb27e..d0287d66 100644
--- a/Modules/Settings/PersonalizationTab.qml
+++ b/Modules/Settings/PersonalizationTab.qml
@@ -10,7 +10,6 @@ import qs.Widgets
Item {
id: personalizationTab
- property alias profileBrowser: profileBrowser
property alias wallpaperBrowser: wallpaperBrowser
property var parentModal: null
property var cachedFontFamilies: []
@@ -84,269 +83,6 @@ Item {
width: parent.width
spacing: Theme.spacingXL
- // Profile Image Section
- StyledRect {
- width: parent.width
- height: profileSection.implicitHeight + Theme.spacingL * 2
- radius: Theme.cornerRadius
- color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
- border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
- border.width: 1
-
- Column {
- id: profileSection
-
- anchors.fill: parent
- anchors.margins: Theme.spacingL
- spacing: Theme.spacingM
-
- Row {
- width: parent.width
- spacing: Theme.spacingM
-
- DankIcon {
- name: "person"
- size: Theme.iconSize
- color: Theme.primary
- anchors.verticalCenter: parent.verticalCenter
- }
-
- StyledText {
- text: "Profile Image"
- font.pixelSize: Theme.fontSizeLarge
- font.weight: Font.Medium
- color: Theme.surfaceText
- anchors.verticalCenter: parent.verticalCenter
- }
-
- }
-
- Row {
- width: parent.width
- spacing: Theme.spacingL
-
- Item {
- id: avatarContainer
-
- property bool hasImage: avatarImageSource.status === Image.Ready
-
- width: 80
- height: 80
-
- Rectangle {
- anchors.fill: parent
- radius: width / 2
- color: "transparent"
- border.color: Theme.primary
- border.width: 1
- visible: parent.hasImage
- }
-
- Image {
- id: avatarImageSource
-
- source: {
- if (PortalService.profileImage === "")
- return "";
-
- if (PortalService.profileImage.startsWith("/"))
- return "file://" + PortalService.profileImage;
-
- return PortalService.profileImage;
- }
- smooth: true
- asynchronous: true
- mipmap: true
- cache: true
- visible: false
- }
-
- MultiEffect {
- anchors.fill: parent
- anchors.margins: 5
- source: avatarImageSource
- maskEnabled: true
- maskSource: settingsCircularMask
- visible: avatarContainer.hasImage
- maskThresholdMin: 0.5
- maskSpreadAtMin: 1
- }
-
- Item {
- id: settingsCircularMask
-
- width: 70
- height: 70
- layer.enabled: true
- layer.smooth: true
- visible: false
-
- Rectangle {
- anchors.fill: parent
- radius: width / 2
- color: "black"
- antialiasing: true
- }
-
- }
-
- Rectangle {
- anchors.fill: parent
- radius: width / 2
- color: Theme.primary
- visible: !parent.hasImage
-
- DankIcon {
- anchors.centerIn: parent
- name: "person"
- size: Theme.iconSizeLarge + 8
- color: Theme.primaryText
- }
-
- }
-
- DankIcon {
- anchors.centerIn: parent
- name: "warning"
- size: Theme.iconSizeLarge
- color: Theme.error
- visible: PortalService.profileImage !== "" && avatarImageSource.status === Image.Error
- }
-
- }
-
- Column {
- width: parent.width - 80 - Theme.spacingL
- spacing: Theme.spacingS
- anchors.verticalCenter: parent.verticalCenter
-
- StyledText {
- text: PortalService.profileImage ? PortalService.profileImage.split('/').pop() : "No profile image selected"
- font.pixelSize: Theme.fontSizeLarge
- color: Theme.surfaceText
- elide: Text.ElideMiddle
- width: parent.width
- }
-
- StyledText {
- text: PortalService.profileImage ? PortalService.profileImage : ""
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.surfaceVariantText
- elide: Text.ElideMiddle
- width: parent.width
- visible: PortalService.profileImage !== ""
- }
-
- Row {
- spacing: Theme.spacingXS
- visible: !PortalService.accountsServiceAvailable
-
- DankIcon {
- name: "error"
- size: Theme.iconSizeSmall
- color: Theme.error
- anchors.verticalCenter: parent.verticalCenter
- }
-
- StyledText {
- text: "accountsservice missing or not accessible"
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.error
- anchors.verticalCenter: parent.verticalCenter
- }
-
- }
-
- Row {
- spacing: Theme.spacingS
-
- StyledRect {
- width: 100
- height: 32
- radius: Theme.cornerRadius
- color: Theme.primary
-
- Row {
- anchors.centerIn: parent
- spacing: Theme.spacingXS
-
- DankIcon {
- name: "folder_open"
- size: Theme.iconSizeSmall
- color: Theme.primaryText
- anchors.verticalCenter: parent.verticalCenter
- }
-
- StyledText {
- text: "Browse"
- color: Theme.primaryText
- font.pixelSize: Theme.fontSizeSmall
- anchors.verticalCenter: parent.verticalCenter
- }
-
- }
-
- MouseArea {
- anchors.fill: parent
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- if (parentModal) {
- parentModal.allowFocusOverride = true;
- parentModal.shouldHaveFocus = false;
- }
- profileBrowser.open();
- }
- }
-
- }
-
- StyledRect {
- width: 80
- height: 32
- radius: Theme.cornerRadius
- color: Theme.surfaceVariant
- opacity: PortalService.profileImage !== "" ? 1 : 0.5
-
- Row {
- anchors.centerIn: parent
- spacing: Theme.spacingXS
-
- DankIcon {
- name: "clear"
- size: Theme.iconSizeSmall
- color: Theme.surfaceVariantText
- anchors.verticalCenter: parent.verticalCenter
- }
-
- StyledText {
- text: "Clear"
- color: Theme.surfaceVariantText
- font.pixelSize: Theme.fontSizeSmall
- anchors.verticalCenter: parent.verticalCenter
- }
-
- }
-
- MouseArea {
- anchors.fill: parent
- enabled: PortalService.profileImage !== ""
- cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- onClicked: {
- PortalService.setProfileImage("");
- }
- }
-
- }
-
- }
-
- }
-
- }
-
- }
-
- }
// Wallpaper Section
StyledRect {
@@ -1148,26 +884,6 @@ Item {
}
- FileBrowserModal {
- id: profileBrowser
-
- browserTitle: "Select Profile Image"
- browserIcon: "person"
- browserType: "profile"
- fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
- onFileSelected: (path) => {
- PortalService.setProfileImage(path);
- close();
- }
- onDialogClosed: {
- if (parentModal) {
- parentModal.allowFocusOverride = false;
- parentModal.shouldHaveFocus = Qt.binding(() => {
- return parentModal.shouldBeVisible;
- });
- }
- }
- }
FileBrowserModal {
id: wallpaperBrowser
diff --git a/Services/DgopService.qml b/Services/DgopService.qml
index bb4f6fb3..fe44834e 100644
--- a/Services/DgopService.qml
+++ b/Services/DgopService.qml
@@ -632,7 +632,47 @@ Singleton {
}
}
+ Process {
+ id: osReleaseProcess
+ command: ["cat", "/etc/os-release"]
+ running: false
+ onExited: exitCode => {
+ if (exitCode !== 0) {
+ console.warn("Failed to read /etc/os-release")
+ }
+ }
+ stdout: StdioCollector {
+ onStreamFinished: {
+ if (text.trim()) {
+ try {
+ const lines = text.trim().split('\n')
+ let prettyName = ""
+ let name = ""
+
+ for (const line of lines) {
+ const trimmedLine = line.trim()
+ if (trimmedLine.startsWith('PRETTY_NAME=')) {
+ prettyName = trimmedLine.substring(12).replace(/^["']|["']$/g, '')
+ } else if (trimmedLine.startsWith('NAME=')) {
+ name = trimmedLine.substring(5).replace(/^["']|["']$/g, '')
+ }
+ }
+
+ // Prefer PRETTY_NAME, fallback to NAME
+ const distroName = prettyName || name || "Linux"
+ distribution = distroName
+ console.log("Detected distribution:", distroName)
+ } catch (e) {
+ console.warn("Failed to parse /etc/os-release:", e)
+ distribution = "Linux"
+ }
+ }
+ }
+ }
+ }
+
Component.onCompleted: {
dgopCheckProcess.running = true
+ osReleaseProcess.running = true
}
}
\ No newline at end of file
diff --git a/assets/dank.svg b/assets/dank.svg
new file mode 100644
index 00000000..a78609e7
--- /dev/null
+++ b/assets/dank.svg
@@ -0,0 +1,49 @@
+
\ No newline at end of file
diff --git a/assets/niri.svg b/assets/niri.svg
new file mode 100644
index 00000000..5bd2de04
--- /dev/null
+++ b/assets/niri.svg
@@ -0,0 +1,405 @@
+
+
+
+
diff --git a/assets/reddit.svg b/assets/reddit.svg
new file mode 100644
index 00000000..114d4255
--- /dev/null
+++ b/assets/reddit.svg
@@ -0,0 +1,20 @@
+
+
\ No newline at end of file