1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00

Compare commits

...

14 Commits

Author SHA1 Message Date
github-actions[bot]
0877a97a1e Add VERSION file for v0.0.30 2025-10-03 23:31:30 +00:00
bbedward
90854e1dd4 version in about tab and ci 2025-10-03 19:31:07 -04:00
bbedward
f96e3b04be Disable powermenu bg on cc 2025-10-03 18:29:06 -04:00
bbedward
44449e26a0 Handle urgent workspaces 2025-10-03 18:17:24 -04:00
bbedward
ddc88fd360 Fix positioning of power menu 2025-10-03 17:21:43 -04:00
bbedward
fedec450cb Keep the modal, but relatively positioned 2025-10-03 17:16:34 -04:00
bbedward
04ea742830 Reapply "Always use power menu modal"
This reverts commit 5a5c860cef.
2025-10-03 17:14:43 -04:00
bbedward
5a5c860cef Revert "Always use power menu modal"
This reverts commit 55d06a43f8.

mm, not sure how I feel about it
2025-10-03 17:14:08 -04:00
bbedward
55d06a43f8 Always use power menu modal 2025-10-03 16:54:10 -04:00
bbedward
71eecd6e7b Revert "betterbird/thunderbird matugen template"
This reverts commit 6f3019f84b.
2025-10-03 16:20:21 -04:00
bbedward
6f3019f84b betterbird/thunderbird matugen template 2025-10-03 16:16:47 -04:00
bbedward
e95d3126b2 Modal/Popout layout alterations 2025-10-03 15:15:44 -04:00
bbedward
5da265bf0b Instruct fonts to be global (makes sense for greeter) 2025-10-03 13:52:39 -04:00
bbedward
2ce9c43b8c disable layer debug opt 2025-10-03 11:21:43 -04:00
15 changed files with 250 additions and 198 deletions

View File

@@ -17,6 +17,17 @@ jobs:
with:
fetch-depth: 0 # Fetch full history for changelog generation
# Create VERSION file
- name: Create VERSION file
run: |
echo "${{ github.ref_name }}" > VERSION
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add VERSION
git commit -m "Add VERSION file for ${{ github.ref_name }}"
git tag -f ${{ github.ref_name }}
git push origin ${{ github.ref_name }} --force
# Generate changelog
- name: Generate Changelog
id: changelog

View File

@@ -678,6 +678,10 @@ Singleton {
function withAlpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a); }
function snap(value, dpr) {
return Math.round(value * dpr) / dpr
}
Process {
id: matugenCheck
command: ["which", "matugen"]

View File

@@ -143,36 +143,13 @@ ShellRoot {
active: false
property var modalRef: colorPickerModal
property LazyLoader powerModalLoaderRef: powerMenuModalLoader
ControlCenterPopout {
id: controlCenterPopout
colorPickerModal: controlCenterLoader.modalRef
powerMenuModalLoader: controlCenterLoader.powerModalLoaderRef
onPowerActionRequested: (action, title, message) => {
powerConfirmModalLoader.active = true
if (powerConfirmModalLoader.item) {
powerConfirmModalLoader.item.confirmButtonColor = action === "poweroff" ? Theme.error : action === "reboot" ? Theme.warning : Theme.primary
powerConfirmModalLoader.item.show(title, message, function () {
switch (action) {
case "logout":
SessionService.logout()
break
case "suspend":
SessionService.suspend()
break
case "hibernate":
SessionService.hibernate()
break
case "reboot":
SessionService.reboot()
break
case "poweroff":
SessionService.poweroff()
break
}
}, function () {})
}
}
onLockRequested: {
lock.activate()
}
@@ -395,7 +372,7 @@ ShellRoot {
function open() {
powerMenuModalLoader.active = true
if (powerMenuModalLoader.item)
powerMenuModalLoader.item.open()
powerMenuModalLoader.item.openCentered()
return "POWERMENU_OPEN_SUCCESS"
}
@@ -409,8 +386,13 @@ ShellRoot {
function toggle() {
powerMenuModalLoader.active = true
if (powerMenuModalLoader.item)
powerMenuModalLoader.item.toggle()
if (powerMenuModalLoader.item) {
if (powerMenuModalLoader.item.shouldBeVisible) {
powerMenuModalLoader.item.close()
} else {
powerMenuModalLoader.item.openCentered()
}
}
return "POWERMENU_TOGGLE_SUCCESS"
}

View File

@@ -157,8 +157,7 @@ PanelWindow {
radius: root.cornerRadius
border.color: root.borderColor
border.width: root.borderWidth
layer.enabled: root.enableShadow
opacity: root.shouldBeVisible ? 1 : 0
layer.enabled: true
transform: root.animationType === "slide" ? slideTransform : null
Translate {
@@ -176,13 +175,6 @@ PanelWindow {
asynchronous: false
}
Behavior on opacity {
NumberAnimation {
duration: root.animationDuration
easing.type: root.animationEasing
}
}
layer.effect: MultiEffect {
shadowEnabled: true
shadowHorizontalOffset: 0
@@ -190,6 +182,15 @@ PanelWindow {
shadowBlur: 1
shadowColor: Theme.shadowStrong
shadowOpacity: 0.3
source: contentContainer
opacity: root.shouldBeVisible ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: animationDuration
easing.type: animationEasing
}
}
}
}

View File

@@ -9,9 +9,25 @@ DankModal {
property int selectedIndex: 0
property int optionCount: SessionService.hibernateSupported ? 5 : 4
property rect parentBounds: Qt.rect(0, 0, 0, 0)
property var parentScreen: null
signal powerActionRequested(string action, string title, string message)
function openCentered() {
parentBounds = Qt.rect(0, 0, 0, 0)
parentScreen = null
backgroundOpacity = 0.5
open()
}
function openFromControlCenter(bounds, targetScreen) {
parentBounds = bounds
parentScreen = targetScreen
backgroundOpacity = 0
open()
}
function selectOption(action) {
close();
const actions = {
@@ -47,6 +63,16 @@ DankModal {
width: 320
height: contentLoader.item ? contentLoader.item.implicitHeight : 300
enableShadow: true
screen: parentScreen
positioning: parentBounds.width > 0 ? "custom" : "center"
customPosition: {
if (parentBounds.width > 0) {
const centerX = parentBounds.x + (parentBounds.width - width) / 2
const centerY = parentBounds.y + (parentBounds.height - height) / 2
return Qt.point(centerX, centerY)
}
return Qt.point(0, 0)
}
onBackgroundClicked: () => {
return close();
}

View File

@@ -6,10 +6,9 @@ import qs.Widgets
Rectangle {
id: root
property bool powerOptionsExpanded: false
property bool editMode: false
signal powerActionRequested(string action, string title, string message)
signal powerButtonClicked()
signal lockRequested()
signal editModeToggled()
@@ -83,13 +82,11 @@ Rectangle {
DankActionButton {
buttonSize: 36
iconName: root.powerOptionsExpanded ? "expand_less" : "power_settings_new"
iconName: "power_settings_new"
iconSize: Theme.iconSize - 4
iconColor: root.powerOptionsExpanded ? Theme.primary : Theme.surfaceText
iconColor: Theme.surfaceText
backgroundColor: "transparent"
onClicked: {
root.powerOptionsExpanded = !root.powerOptionsExpanded
}
onClicked: root.powerButtonClicked()
}
DankActionButton {

View File

@@ -1,70 +0,0 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
property bool expanded: false
signal powerActionRequested(string action, string title, string message)
implicitHeight: expanded ? 60 : 0
height: implicitHeight
clip: true
Rectangle {
width: parent.width
height: 60
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.08)
border.width: root.expanded ? 1 : 0
opacity: root.expanded ? 1 : 0
clip: true
Row {
anchors.centerIn: parent
spacing: SessionService.hibernateSupported ? Theme.spacingS : Theme.spacingL
visible: root.expanded
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "logout"
text: "Logout"
onPressed: root.powerActionRequested("logout", "Logout", "Are you sure you want to logout?")
}
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "restart_alt"
text: "Restart"
onPressed: root.powerActionRequested("reboot", "Restart", "Are you sure you want to restart?")
}
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "bedtime"
text: "Suspend"
onPressed: root.powerActionRequested("suspend", "Suspend", "Are you sure you want to suspend?")
}
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "ac_unit"
text: "Hibernate"
visible: SessionService.hibernateSupported
onPressed: root.powerActionRequested("hibernate", "Hibernate", "Are you sure you want to hibernate?")
}
PowerButton {
width: SessionService.hibernateSupported ? 85 : 100
iconName: "power_settings_new"
text: "Shutdown"
onPressed: root.powerActionRequested("poweroff", "Shutdown", "Are you sure you want to shutdown?")
}
}
}
}

View File

@@ -21,13 +21,11 @@ DankPopout {
id: root
property string expandedSection: ""
property bool powerOptionsExpanded: false
property var triggerScreen: null
property bool editMode: false
property int expandedWidgetIndex: -1
property var expandedWidgetData: null
signal powerActionRequested(string action, string title, string message)
signal lockRequested
function collapseAll() {
@@ -122,28 +120,24 @@ DankPopout {
HeaderPane {
id: headerPane
width: parent.width
powerOptionsExpanded: root.powerOptionsExpanded
editMode: root.editMode
onPowerOptionsExpandedChanged: root.powerOptionsExpanded = powerOptionsExpanded
onEditModeToggled: root.editMode = !root.editMode
onPowerActionRequested: (action, title, message) => root.powerActionRequested(action, title, message)
onPowerButtonClicked: {
if (powerMenuModalLoader) {
powerMenuModalLoader.active = true
if (powerMenuModalLoader.item) {
const popoutPos = controlContent.mapToItem(null, 0, 0)
const bounds = Qt.rect(popoutPos.x, popoutPos.y, controlContent.width, controlContent.height)
powerMenuModalLoader.item.openFromControlCenter(bounds, root.triggerScreen)
}
}
}
onLockRequested: {
root.close()
root.lockRequested()
}
}
PowerOptionsPane {
id: powerOptionsPane
width: parent.width
expanded: root.powerOptionsExpanded
onPowerActionRequested: (action, title, message) => {
root.powerOptionsExpanded = false
root.close()
root.powerActionRequested(action, title, message)
}
}
DragDropGrid {
id: widgetGrid
width: parent.width
@@ -226,4 +220,5 @@ DankPopout {
}
property var colorPickerModal: null
property var powerMenuModalLoader: null
}

View File

@@ -293,19 +293,30 @@ Rectangle {
property bool isHovered: mouseArea.containsMouse
property var loadedWorkspaceData: null
property bool loadedIsUrgent: false
property bool isUrgent: {
if (CompositorService.isHyprland) {
return modelData?.urgent ?? false
}
if (CompositorService.isNiri) {
return loadedIsUrgent
}
return false
}
property var loadedIconData: null
property bool loadedHasIcon: false
property var loadedIcons: []
Timer {
id: dataUpdateTimer
interval: 50 // Defer data calculation by 50ms
interval: 50
onTriggered: {
if (isPlaceholder) {
delegateRoot.loadedWorkspaceData = null
delegateRoot.loadedIconData = null
delegateRoot.loadedHasIcon = false
delegateRoot.loadedIcons = []
delegateRoot.loadedIsUrgent = false
return
}
@@ -316,6 +327,7 @@ Rectangle {
wsData = modelData;
}
delegateRoot.loadedWorkspaceData = wsData;
delegateRoot.loadedIsUrgent = wsData?.is_urgent ?? false;
var icData = null;
if (wsData?.name) {
@@ -363,7 +375,10 @@ Rectangle {
}
}
radius: Math.min(width, height) / 2
color: isActive ? Theme.primary : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha
color: isActive ? Theme.primary : isUrgent ? Theme.error : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha
border.width: isUrgent && !isActive ? 2 : 0
border.color: isUrgent && !isActive ? Theme.error : Theme.withAlpha(Theme.error, 0)
Behavior on width {
enabled: (!SettingsData.showWorkspaceApps || SettingsData.maxWorkspaceIcons <= 3)
@@ -381,6 +396,20 @@ Rectangle {
}
}
Behavior on color {
ColorAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on border.width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
}
MouseArea {
id: mouseArea
@@ -619,6 +648,7 @@ Rectangle {
target: NiriService
enabled: CompositorService.isNiri
function onAllWorkspacesChanged() { delegateRoot.updateAllData() }
function onWindowUrgentChanged() { delegateRoot.updateAllData() }
}
Connections {
target: SettingsData

View File

@@ -56,7 +56,7 @@ Item {
}
StyledText {
text: "DankMaterialShell"
text: SystemUpdateService.shellVersion ? `dms ${SystemUpdateService.shellVersion}` : "dms"
font.pixelSize: Theme.fontSizeXLarge
font.weight: Font.Bold
color: Theme.surfaceText
@@ -257,8 +257,8 @@ Item {
}
StyledText {
text: `DankMaterialShell is a modern desktop with a <a href="https://m3.material.io/" style="text-decoration:none; color:${Theme.primary};">material</a>-ish design.
<br /><br/>The goal is to provide a high level of functionality and customization so that it can be a suitable replacement for complete desktop environments like Gnome, KDE, or Cosmic.
text: `dms is a highly customizable, modern desktop shell with a <a href="https://m3.material.io/" style="text-decoration:none; color:${Theme.primary};">material 3 inspired</a> design.
<br /><br/>It is built on top of <a href="https://quickshell.org" style="text-decoration:none; color:${Theme.primary};">Quickshell</a>, a QT6 framework for building desktop shells.
`
textFormat: Text.RichText
font.pixelSize: Theme.fontSizeMedium
@@ -478,6 +478,34 @@ Item {
anchors.verticalCenter: parent.verticalCenter
}
}
StyledText {
text: "Dank Suite:"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
Row {
spacing: 4
StyledText {
text: `<a href="https://danklinux.com" style="text-decoration:none; color:${Theme.primary};">danklinux.com</a>`
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
linkColor: Theme.primary
textFormat: Text.RichText
onLinkActivated: url => Qt.openUrlExternally(url)
anchors.verticalCenter: parent.verticalCenter
MouseArea {
anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.NoButton
propagateComposedEvents: true
}
}
}
}
}
}

View File

@@ -247,17 +247,16 @@ sudo dnf copr enable errornointernet/quickshell && sudo dnf install quickshell-g
**2.1 Install Material Symbols**
```bash
mkdir -p ~/.local/share/fonts &&
curl -L "https://github.com/google/material-design-icons/raw/master/variablefont/MaterialSymbolsRounded%5BFILL%2CGRAD%2Copsz%2Cwght%5D.ttf" -o ~/.local/share/fonts/MaterialSymbolsRounded.ttf
sudo curl -L "https://github.com/google/material-design-icons/raw/master/variablefont/MaterialSymbolsRounded%5BFILL%2CGRAD%2Copsz%2Cwght%5D.ttf" -o /usr/share/fonts/MaterialSymbolsRounded.ttf
```
**2.2 Install Inter Variable**
```bash
curl -L "https://github.com/rsms/inter/raw/refs/tags/v4.1/docs/font-files/InterVariable.ttf" -o ~/.local/share/fonts/InterVariable.ttf
sudo curl -L "https://github.com/rsms/inter/raw/refs/tags/v4.1/docs/font-files/InterVariable.ttf" -o /usr/share/fonts/InterVariable.ttf
```
**2.3 Install Fira Code (monospace font)**
```bash
curl -L "https://github.com/tonsky/FiraCode/releases/latest/download/FiraCode-Regular.ttf" -o ~/.local/share/fonts/FiraCode-Regular.ttf
sudo curl -L "https://github.com/tonsky/FiraCode/releases/latest/download/FiraCode-Regular.ttf" -o /usr/share/fonts/FiraCode-Regular.ttf
```
**2.4 Refresh font cache**

View File

@@ -23,6 +23,8 @@ Singleton {
property var windows: []
signal windowUrgentChanged()
property bool inOverview: false
property int currentKeyboardLayoutIndex: 0
@@ -189,6 +191,9 @@ Singleton {
case 'KeyboardLayoutSwitched':
handleKeyboardLayoutSwitched(event.KeyboardLayoutSwitched);
break;
case 'WorkspaceUrgencyChanged':
handleWorkspaceUrgencyChanged(event.WorkspaceUrgencyChanged);
break;
}
}
@@ -373,6 +378,22 @@ Singleton {
currentKeyboardLayoutIndex = data.idx
}
function handleWorkspaceUrgencyChanged(data) {
const ws = root.workspaces[data.id]
if (!ws) {
return
}
ws.is_urgent = data.urgent
const idx = allWorkspaces.findIndex(w => w.id === data.id)
if (idx >= 0) {
allWorkspaces[idx].is_urgent = data.urgent
}
windowUrgentChanged()
}
Process {
id: validateProcess
command: ["niri", "validate"]

View File

@@ -17,6 +17,7 @@ Singleton {
property string pkgManager: ""
property string distribution: ""
property bool distributionSupported: false
property string shellVersion: ""
readonly property list<string> supportedDistributions: ["arch", "cachyos", "manjaro", "endeavouros"]
readonly property int updateCount: availableUpdates.length
@@ -43,6 +44,23 @@ Singleton {
}
stdout: StdioCollector {}
Component.onCompleted: {
versionDetection.running = true
}
}
Process {
id: versionDetection
command: ["sh", "-c", "if [ -d .git ]; then echo \"(git) $(git rev-parse --short HEAD)\"; elif [ -f VERSION ]; then cat VERSION; fi"]
onExited: (exitCode) => {
if (exitCode === 0) {
shellVersion = stdout.text.trim()
}
}
stdout: StdioCollector {}
}
Process {

1
VERSION Normal file
View File

@@ -0,0 +1 @@
v0.0.30

View File

@@ -68,55 +68,69 @@ PanelWindow {
bottom: true
}
readonly property real screenWidth: root.screen.width
readonly property real screenHeight: root.screen.height
readonly property real dpr: root.screen.devicePixelRatio
readonly property real calculatedX: {
if (SettingsData.dankBarPosition === SettingsData.Position.Left) {
return triggerY
} else if (SettingsData.dankBarPosition === SettingsData.Position.Right) {
return screenWidth - triggerY - popupWidth
} else {
const centerX = triggerX + (triggerWidth / 2) - (popupWidth / 2)
return Math.max(Theme.popupDistance, Math.min(screenWidth - popupWidth - Theme.popupDistance, centerX))
}
}
readonly property real calculatedY: {
if (SettingsData.dankBarPosition === SettingsData.Position.Left || SettingsData.dankBarPosition === SettingsData.Position.Right) {
const centerY = triggerX + (triggerWidth / 2) - (popupHeight / 2)
return Math.max(Theme.popupDistance, Math.min(screenHeight - popupHeight - Theme.popupDistance, centerY))
} else if (SettingsData.dankBarPosition === SettingsData.Position.Bottom) {
return Math.max(Theme.popupDistance, Math.min(screenHeight - popupHeight - Theme.popupDistance, screenHeight - triggerY - popupHeight + Theme.popupDistance))
} else {
return Math.max(Theme.popupDistance, Math.min(screenHeight - popupHeight - Theme.popupDistance, triggerY + Theme.popupDistance))
}
}
readonly property real alignedWidth: Theme.snap(popupWidth, dpr)
readonly property real alignedHeight: Theme.snap(popupHeight, dpr)
readonly property real alignedX: Theme.snap(calculatedX, dpr)
readonly property real alignedY: Theme.snap(calculatedY, dpr)
MouseArea {
anchors.fill: parent
enabled: shouldBeVisible
onClicked: mouse => {
var localPos = mapToItem(contentContainer, mouse.x, mouse.y)
if (localPos.x < 0 || localPos.x > contentContainer.width || localPos.y < 0 || localPos.y > contentContainer.height) {
backgroundClicked()
close()
}
}
if (mouse.x < alignedX || mouse.x > alignedX + alignedWidth ||
mouse.y < alignedY || mouse.y > alignedY + alignedHeight) {
backgroundClicked()
close()
}
}
}
Item {
id: contentContainer
layer.enabled: true
Loader {
id: contentLoader
x: alignedX
y: alignedY
width: alignedWidth
height: alignedHeight
active: root.visible
asynchronous: false
opacity: Quickshell.env("DMS_DISABLE_LAYER") === "true" ? (shouldBeVisible ? 1 : 0) : 1
layer.enabled: Quickshell.env("DMS_DISABLE_LAYER") !== "true"
layer.effect: MultiEffect {
source: contentLoader
opacity: shouldBeVisible ? 1 : 0
readonly property real screenWidth: root.screen.width
readonly property real screenHeight: root.screen.height
readonly property real gothOffset: SettingsData.dankBarGothCornersEnabled ? Theme.cornerRadius : 0
readonly property real calculatedX: {
if (SettingsData.dankBarPosition === SettingsData.Position.Left) {
return triggerY
} else if (SettingsData.dankBarPosition === SettingsData.Position.Right) {
return screenWidth - triggerY - popupWidth
} else {
const centerX = triggerX + (triggerWidth / 2) - (popupWidth / 2)
return Math.max(Theme.popupDistance, Math.min(screenWidth - popupWidth - Theme.popupDistance, centerX))
Behavior on opacity {
NumberAnimation {
duration: animationDuration
easing.type: animationEasing
}
}
}
readonly property real calculatedY: {
if (SettingsData.dankBarPosition === SettingsData.Position.Left || SettingsData.dankBarPosition === SettingsData.Position.Right) {
const centerY = triggerX + (triggerWidth / 2) - (popupHeight / 2)
return Math.max(Theme.popupDistance, Math.min(screenHeight - popupHeight - Theme.popupDistance, centerY))
} else if (SettingsData.dankBarPosition === SettingsData.Position.Bottom) {
return Math.max(Theme.popupDistance, Math.min(screenHeight - popupHeight - Theme.popupDistance, screenHeight - triggerY - popupHeight + Theme.popupDistance))
} else {
return Math.max(Theme.popupDistance, Math.min(screenHeight - popupHeight - Theme.popupDistance, triggerY + Theme.popupDistance))
}
}
readonly property real dpr: root.screen.devicePixelRatio
function snap(v) { return Math.round(v * dpr) / dpr }
width: snap(popupWidth)
height: snap(popupHeight)
x: snap(calculatedX)
y: snap(calculatedY)
opacity: shouldBeVisible ? 1 : 0
scale: 1
Behavior on opacity {
NumberAnimation {
@@ -124,26 +138,21 @@ PanelWindow {
easing.type: animationEasing
}
}
}
Loader {
id: contentLoader
anchors.fill: parent
active: root.visible
asynchronous: false
}
Item {
anchors.fill: parent
focus: true
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
close()
event.accepted = true
}
}
Component.onCompleted: forceActiveFocus()
onVisibleChanged: if (visible)
forceActiveFocus()
Item {
x: alignedX
y: alignedY
width: alignedWidth
height: alignedHeight
focus: true
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
close()
event.accepted = true
}
}
Component.onCompleted: forceActiveFocus()
onVisibleChanged: if (visible) forceActiveFocus()
}
}