1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-25 05:52:50 -05:00

Compare commits

..

3 Commits

Author SHA1 Message Date
purian23
0a038fa3c9 Revert "wip binds"
This reverts commit 841e55d37f.
2025-10-01 11:49:29 -04:00
bbedward
841e55d37f wip binds 2025-09-30 17:31:17 -04:00
bbedward
287dda5675 niri: generate colors, layout, binds 2025-09-30 17:31:17 -04:00
33 changed files with 1126 additions and 2836 deletions

View File

@@ -1,53 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
readonly property var facts: [
"A photon takes 100,000 to 200,000 years bouncing through the Sun's dense core, then races to Earth in just 8 minutes 20 seconds.",
"A teaspoon of neutron star matter would weigh a billion metric tons here on Earth.",
"Right now, 100 trillion solar neutrinos are passing through your body every second.",
"The Sun converts 4 million metric tons of matter into pure energy every second—enough to power Earth for 500,000 years.",
"The universe still glows with leftover heat from the Big Bang—just 2.7 degrees above absolute zero.",
"There's a nebula out there that's actually colder than empty space itself.",
"We've detected black holes crashing together by measuring spacetime stretch by less than 1/10,000th the width of a proton.",
"Fast radio bursts can release more energy in 5 milliseconds than our Sun produces in 3 days.",
"Our galaxy might be crawling with billions of rogue planets drifting alone in the dark.",
"Distant galaxies can move away from us faster than light because space itself is stretching.",
"The edge of what we can see is 46.5 billion light-years away, even though the universe is only 13.8 billion years old.",
"The universe is mostly invisible: 5% regular matter, 27% dark matter, 68% dark energy.",
"A day on Venus lasts longer than its entire year around the Sun.",
"On Mercury, the time between sunrises is 176 Earth days long.",
"In about 4.5 billion years, our galaxy will smash into Andromeda.",
"Most of the gold in your jewelry was forged when neutron stars collided somewhere in space.",
"PSR J1748-2446ad, the fastest spinning star, rotates 716 times per second—its equator moves at 24% the speed of light.",
"Cosmic rays create particles that shouldn't make it to Earth's surface, but time dilation lets them sneak through.",
"Jupiter's magnetic field is so huge that if we could see it, it would look bigger than the Moon in our sky.",
"Interstellar space is so empty it's like a cube 32 kilometers wide containing just a single grain of sand.",
"Voyager 1 is 24 billion kilometers away but won't leave the Sun's gravitational influence for another 30,000 years.",
"Counting to a billion at one number per second would take over 31 years.",
"Space is so vast, even speeding at light-speed, you'd never return past the cosmic horizon.",
"Astronauts on the ISS age about 0.01 seconds less each year than people on Earth.",
"Sagittarius B2, a dust cloud near our galaxy's center, contains ethyl formate—the compound that gives raspberries their flavor and rum its smell.",
"Beyond 16 billion light-years, the cosmic event horizon marks where space expands too fast for light to ever reach us again.",
"Even at light-speed, you'd never catch up to most galaxies—space expands faster.",
"Only around 5% of galaxies are ever reachable—even at light-speed.",
"If the Sun vanished, we'd still orbit it for 8 minutes before drifting away.",
"If a planet 65 million light-years away looked at Earth now, it'd see dinosaurs.",
"Our oldest radio signals will reach the Milky Way's center in 26,000 years.",
"Every atom in your body heavier than hydrogen was forged in the nuclear furnace of a dying star.",
"The Moon moves 3.8 centimeters farther from Earth every year.",
"The universe creates 275 million new stars every single day.",
"Jupiter's Great Red Spot is a storm twice the size of Earth that has been raging for at least 350 years.",
"If you watched someone fall into a black hole, they'd appear frozen at the event horizon forever—time effectively stops from your perspective.",
"The Boötes Supervoid is a cosmic desert 1.8 billion light-years across with 60% fewer galaxies than it should have."
]
function getRandomFact() {
return facts[Math.floor(Math.random() * facts.length)]
}
}

View File

@@ -12,8 +12,6 @@ Singleton {
id: root
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
property bool isLightMode: false
property string wallpaperPath: ""
property string wallpaperLastPath: ""
@@ -73,17 +71,11 @@ Singleton {
Component.onCompleted: {
if (!isGreeterMode) {
loadSettings()
}
loadSettings()
}
function loadSettings() {
if (isGreeterMode) {
parseSettings(greeterSessionFile.text())
} else {
parseSettings(settingsFile.text())
}
parseSettings(settingsFile.text())
}
function parseSettings(content) {
@@ -150,11 +142,10 @@ Singleton {
batterySuspendTimeout = settings.batterySuspendTimeout !== undefined ? settings.batterySuspendTimeout : 0
batteryHibernateTimeout = settings.batteryHibernateTimeout !== undefined ? settings.batteryHibernateTimeout : 0
lockBeforeSuspend = settings.lockBeforeSuspend !== undefined ? settings.lockBeforeSuspend : false
if (!isGreeterMode) {
if (typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme()
}
// Generate system themes but don't override user's theme choice
if (typeof Theme !== "undefined") {
Theme.generateSystemThemesFromCurrentTheme()
}
}
} catch (e) {
@@ -163,7 +154,6 @@ Singleton {
}
function saveSettings() {
if (isGreeterMode) return
settingsFile.setText(JSON.stringify({
"isLightMode": isLightMode,
"wallpaperPath": wallpaperPath,
@@ -630,43 +620,22 @@ Singleton {
FileView {
id: settingsFile
path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json"
blockLoading: isGreeterMode
path: StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json"
blockLoading: true
blockWrites: true
watchChanges: !isGreeterMode
watchChanges: true
onLoaded: {
if (!isGreeterMode) {
parseSettings(settingsFile.text())
hasTriedDefaultSession = false
}
parseSettings(settingsFile.text())
hasTriedDefaultSession = false
}
onLoadFailed: error => {
if (!isGreeterMode && !hasTriedDefaultSettings) {
hasTriedDefaultSettings = true
if (!hasTriedDefaultSession) {
hasTriedDefaultSession = true
defaultSessionCheckProcess.running = true
}
}
}
FileView {
id: greeterSessionFile
path: {
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms"
return greetCfgDir + "/session.json"
}
preload: isGreeterMode
blockLoading: false
blockWrites: true
watchChanges: false
printErrors: true
onLoaded: {
if (isGreeterMode) {
parseSettings(greeterSessionFile.text())
}
}
}
Process {
id: defaultSessionCheckProcess

View File

@@ -12,8 +12,6 @@ import qs.Services
Singleton {
id: root
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
enum Position {
Top,
Bottom,
@@ -993,23 +991,13 @@ Singleton {
function setShowDock(enabled) {
showDock = enabled
if (enabled && dockPosition === dankBarPosition) {
if (dankBarPosition === SettingsData.Position.Top) {
setDockPosition(SettingsData.Position.Bottom)
return
}
if (dankBarPosition === SettingsData.Position.Bottom) {
setDockPosition(SettingsData.Position.Top)
return
}
if (dankBarPosition === SettingsData.Position.Left) {
setDockPosition(SettingsData.Position.Right)
return
}
if (dankBarPosition === SettingsData.Position.Right) {
setDockPosition(SettingsData.Position.Left)
return
}
if (enabled && dankBarPosition === SettingsData.Position.Top) {
setDockPosition(SettingsData.Position.Bottom)
return
}
if (enabled && dankBarPosition === SettingsData.Position.Top) {
setDockPosition(SettingsData.Position.Bottom)
return
}
saveSettings()
}
@@ -1156,22 +1144,14 @@ Singleton {
function setDankBarPosition(position) {
dankBarPosition = position
if (position === SettingsData.Position.Bottom && dockPosition === SettingsData.Position.Bottom && showDock) {
if (position === SettingsData.Position.Bottom && showDock) {
setDockPosition(SettingsData.Position.Top)
return
}
if (position === SettingsData.Position.Top && dockPosition === SettingsData.Position.Top && showDock) {
if (position === SettingsData.Position.Top && showDock) {
setDockPosition(SettingsData.Position.Bottom)
return
}
if (position === SettingsData.Position.Left && dockPosition === SettingsData.Position.Left && showDock) {
setDockPosition(SettingsData.Position.Right)
return
}
if (position === SettingsData.Position.Right && dockPosition === SettingsData.Position.Right && showDock) {
setDockPosition(SettingsData.Position.Left)
return
}
saveSettings()
}
@@ -1183,12 +1163,6 @@ Singleton {
if (position === SettingsData.Position.Top && dankBarPosition === SettingsData.Position.Top && showDock) {
setDankBarPosition(SettingsData.Position.Bottom)
}
if (position === SettingsData.Position.Left && dankBarPosition === SettingsData.Position.Left && showDock) {
setDankBarPosition(SettingsData.Position.Right)
}
if (position === SettingsData.Position.Right && dankBarPosition === SettingsData.Position.Right && showDock) {
setDankBarPosition(SettingsData.Position.Left)
}
saveSettings()
Qt.callLater(() => forceDockLayoutRefresh())
}
@@ -1276,11 +1250,9 @@ Singleton {
}
Component.onCompleted: {
if (!isGreeterMode) {
loadSettings()
fontCheckTimer.start()
initializeListModels()
}
loadSettings()
fontCheckTimer.start()
initializeListModels()
}
ListModel {
@@ -1321,22 +1293,20 @@ Singleton {
FileView {
id: settingsFile
path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
blockLoading: isGreeterMode
path: StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
blockLoading: true
blockWrites: true
atomicWrites: true
watchChanges: !isGreeterMode
watchChanges: true
onLoaded: {
if (!isGreeterMode) {
parseSettings(settingsFile.text())
hasTriedDefaultSettings = false
}
parseSettings(settingsFile.text())
hasTriedDefaultSettings = false
}
onLoadFailed: error => {
if (!isGreeterMode && !hasTriedDefaultSettings) {
if (!hasTriedDefaultSettings) {
hasTriedDefaultSettings = true
defaultSettingsCheckProcess.running = true
} else if (!isGreeterMode) {
} else {
applyStoredTheme()
}
}

View File

@@ -31,8 +31,9 @@ Singleton {
readonly property string shellDir: Paths.strip(Qt.resolvedUrl(".").toString()).replace("/Common/", "")
readonly property string wallpaperPath: {
if (typeof SessionData === "undefined") return ""
if (SessionData.perMonitorWallpaper) {
// Use first monitor's wallpaper for dynamic theming
var screens = Quickshell.screens
if (screens.length > 0) {
var firstMonitorWallpaper = SessionData.getMonitorWallpaper(screens[0].name)
@@ -92,13 +93,6 @@ Singleton {
}
}
function applyGreeterTheme(themeName) {
switchTheme(themeName, false, false)
if (themeName === dynamic && dynamicColorsFileView.path) {
dynamicColorsFileView.reload()
}
}
function getMatugenColor(path, fallback) {
colorUpdateTrigger
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"
@@ -309,13 +303,10 @@ Singleton {
currentThemeCategory = "generic"
}
}
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
if (savePrefs && typeof SettingsData !== "undefined" && !isGreeterMode)
if (savePrefs && typeof SettingsData !== "undefined")
SettingsData.setTheme(currentTheme)
if (!isGreeterMode) {
generateSystemThemesFromCurrentTheme()
}
generateSystemThemesFromCurrentTheme()
}
function setLightMode(light, savePrefs = true, enableTransition = false) {
@@ -327,14 +318,11 @@ Singleton {
return
}
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
isLightMode = light
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode)
if (savePrefs && typeof SessionData !== "undefined")
SessionData.setLightMode(isLightMode)
if (!isGreeterMode) {
PortalService.setLightMode(isLightMode)
generateSystemThemesFromCurrentTheme()
}
PortalService.setLightMode(isLightMode)
generateSystemThemesFromCurrentTheme()
}
function toggleLightMode(savePrefs = true) {
@@ -611,8 +599,7 @@ Singleton {
}
function generateSystemThemesFromCurrentTheme() {
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
if (!matugenAvailable || isGreeterMode)
if (!matugenAvailable)
return
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
@@ -683,9 +670,8 @@ Singleton {
command: ["which", "matugen"]
onExited: code => {
matugenAvailable = (code === 0) && !envDisableMatugen
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
if (!matugenAvailable || isGreeterMode) {
if (!matugenAvailable) {
console.log("matugen not not available in path or disabled via DMS_DISABLE_MATUGEN")
return
}
@@ -736,7 +722,10 @@ Singleton {
onExited: exitCode => {
workerRunning = false
if (exitCode !== 0 && exitCode !== 2) {
if (exitCode === 2) {
// Exit code 2 means wallpaper/color not found - this is expected on first run
console.log("Theme worker: wallpaper/color not found, skipping theme generation")
} else if (exitCode !== 0) {
if (typeof ToastService !== "undefined") {
ToastService.showError("Theme worker failed (" + exitCode + ")")
}
@@ -825,14 +814,8 @@ Singleton {
FileView {
id: dynamicColorsFileView
path: {
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms"
const colorsPath = SessionData.isGreeterMode
? greetCfgDir + "/colors.json"
: stateDir + "/dms-colors.json"
return colorsPath
}
watchChanges: currentTheme === dynamic && !SessionData.isGreeterMode
path: stateDir + "/dms-colors.json"
watchChanges: currentTheme === dynamic
function parseAndLoadColors() {
try {
@@ -845,7 +828,6 @@ Singleton {
}
}
} catch (e) {
console.error("Theme: Failed to parse dynamic colors:", e)
if (typeof ToastService !== "undefined") {
ToastService.wallpaperErrorStatus = "error"
ToastService.showError("Dynamic colors parse error: " + e.message)

View File

@@ -1,25 +0,0 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Greetd
import qs.Common
import qs.Modules.Greetd
ShellRoot {
id: root
WlSessionLock {
id: sessionLock
locked: true
onLockedChanged: {
if (!locked) {
console.log("Greetd session unlocked, exiting")
}
}
GreeterSurface {
lock: sessionLock
}
}
}

View File

@@ -1,638 +0,0 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Modals
import qs.Modals.Clipboard
import qs.Modals.Common
import qs.Modals.Settings
import qs.Modals.Spotlight
import qs.Modules
import qs.Modules.AppDrawer
import qs.Modules.DankDash
import qs.Modules.ControlCenter
import qs.Modules.Dock
import qs.Modules.Lock
import qs.Modules.Notepad
import qs.Modules.Notifications.Center
import qs.Widgets
import qs.Modules.Notifications.Popup
import qs.Modules.OSD
import qs.Modules.ProcessList
import qs.Modules.Settings
import qs.Modules.DankBar
import qs.Modules.DankBar.Popouts
import qs.Services
Item {
Component.onCompleted: {
PortalService.init()
// Initialize DisplayService night mode functionality
DisplayService.nightModeEnabled
// Initialize WallpaperCyclingService
WallpaperCyclingService.cyclingActive
}
WallpaperBackground {}
Lock {
id: lock
anchors.fill: parent
}
Loader {
id: dankBarLoader
asynchronous: false
property var currentPosition: SettingsData.dankBarPosition
sourceComponent: DankBar {
onColorPickerRequested: colorPickerModal.show()
}
onCurrentPositionChanged: {
const component = sourceComponent
sourceComponent = null
Qt.callLater(() => {
sourceComponent = component
})
}
}
Loader {
id: dockLoader
active: true
asynchronous: false
property var currentPosition: SettingsData.dockPosition
sourceComponent: Dock {
contextMenu: dockContextMenuLoader.item ? dockContextMenuLoader.item : null
}
onLoaded: {
if (item) {
dockContextMenuLoader.active = true
}
}
onCurrentPositionChanged: {
console.log("DEBUG: Dock position changed to:", currentPosition, "- recreating dock")
const comp = sourceComponent
sourceComponent = null
Qt.callLater(() => {
sourceComponent = comp
})
}
}
Loader {
id: dankDashPopoutLoader
active: false
asynchronous: true
sourceComponent: Component {
DankDashPopout {
id: dankDashPopout
}
}
}
LazyLoader {
id: dockContextMenuLoader
active: false
DockContextMenu {
id: dockContextMenu
}
}
LazyLoader {
id: notificationCenterLoader
active: false
NotificationCenterPopout {
id: notificationCenter
}
}
Variants {
model: SettingsData.getFilteredScreens("notifications")
delegate: NotificationPopupManager {
modelData: item
}
}
LazyLoader {
id: controlCenterLoader
active: false
property var modalRef: colorPickerModal
ControlCenterPopout {
id: controlCenterPopout
colorPickerModal: controlCenterLoader.modalRef
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()
}
}
}
LazyLoader {
id: wifiPasswordModalLoader
active: false
WifiPasswordModal {
id: wifiPasswordModal
}
}
LazyLoader {
id: networkInfoModalLoader
active: false
NetworkInfoModal {
id: networkInfoModal
}
}
LazyLoader {
id: batteryPopoutLoader
active: false
BatteryPopout {
id: batteryPopout
}
}
LazyLoader {
id: vpnPopoutLoader
active: false
VpnPopout {
id: vpnPopout
}
}
LazyLoader {
id: powerMenuLoader
active: false
PowerMenu {
id: powerMenu
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 () {})
}
}
}
}
LazyLoader {
id: powerConfirmModalLoader
active: false
ConfirmModal {
id: powerConfirmModal
}
}
LazyLoader {
id: processListPopoutLoader
active: false
ProcessListPopout {
id: processListPopout
}
}
SettingsModal {
id: settingsModal
}
LazyLoader {
id: appDrawerLoader
active: false
AppDrawerPopout {
id: appDrawerPopout
}
}
SpotlightModal {
id: spotlightModal
}
ClipboardHistoryModal {
id: clipboardHistoryModalPopup
}
NotificationModal {
id: notificationModal
}
ColorPickerModal {
id: colorPickerModal
}
LazyLoader {
id: processListModalLoader
active: false
ProcessListModal {
id: processListModal
}
}
LazyLoader {
id: systemUpdateLoader
active: false
SystemUpdatePopout {
id: systemUpdatePopout
}
}
Variants {
id: notepadSlideoutVariants
model: SettingsData.getFilteredScreens("notepad")
delegate: DankSlideout {
id: notepadSlideout
modelData: item
title: qsTr("Notepad")
slideoutWidth: 480
expandable: true
expandedWidthValue: 960
customTransparency: SettingsData.notepadTransparencyOverride
content: Component {
Notepad {
onHideRequested: {
notepadSlideout.hide()
}
}
}
function toggle() {
if (isVisible) {
hide()
} else {
show()
}
}
}
}
LazyLoader {
id: powerMenuModalLoader
active: false
PowerMenuModal {
id: powerMenuModal
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 () {})
}
}
}
}
IpcHandler {
function open() {
powerMenuModalLoader.active = true
if (powerMenuModalLoader.item)
powerMenuModalLoader.item.open()
return "POWERMENU_OPEN_SUCCESS"
}
function close() {
if (powerMenuModalLoader.item)
powerMenuModalLoader.item.close()
return "POWERMENU_CLOSE_SUCCESS"
}
function toggle() {
powerMenuModalLoader.active = true
if (powerMenuModalLoader.item)
powerMenuModalLoader.item.toggle()
return "POWERMENU_TOGGLE_SUCCESS"
}
target: "powermenu"
}
IpcHandler {
function open(): string {
processListModalLoader.active = true
if (processListModalLoader.item)
processListModalLoader.item.show()
return "PROCESSLIST_OPEN_SUCCESS"
}
function close(): string {
if (processListModalLoader.item)
processListModalLoader.item.hide()
return "PROCESSLIST_CLOSE_SUCCESS"
}
function toggle(): string {
processListModalLoader.active = true
if (processListModalLoader.item)
processListModalLoader.item.toggle()
return "PROCESSLIST_TOGGLE_SUCCESS"
}
target: "processlist"
}
IpcHandler {
function open(): string {
controlCenterLoader.active = true
if (controlCenterLoader.item) {
controlCenterLoader.item.open()
return "CONTROL_CENTER_OPEN_SUCCESS"
}
return "CONTROL_CENTER_OPEN_FAILED"
}
function close(): string {
if (controlCenterLoader.item) {
controlCenterLoader.item.close()
return "CONTROL_CENTER_CLOSE_SUCCESS"
}
return "CONTROL_CENTER_CLOSE_FAILED"
}
function toggle(): string {
controlCenterLoader.active = true
if (controlCenterLoader.item) {
controlCenterLoader.item.toggle()
return "CONTROL_CENTER_TOGGLE_SUCCESS"
}
return "CONTROL_CENTER_TOGGLE_FAILED"
}
target: "control-center"
}
IpcHandler {
function open(tab: string): string {
dankDashPopoutLoader.active = true
if (dankDashPopoutLoader.item) {
switch (tab.toLowerCase()) {
case "media":
dankDashPopoutLoader.item.currentTabIndex = 1
break
case "weather":
dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
break
default:
dankDashPopoutLoader.item.currentTabIndex = 0
break
}
dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
dankDashPopoutLoader.item.dashVisible = true
return "DASH_OPEN_SUCCESS"
}
return "DASH_OPEN_FAILED"
}
function close(): string {
if (dankDashPopoutLoader.item) {
dankDashPopoutLoader.item.dashVisible = false
return "DASH_CLOSE_SUCCESS"
}
return "DASH_CLOSE_FAILED"
}
function toggle(tab: string): string {
dankDashPopoutLoader.active = true
if (dankDashPopoutLoader.item) {
if (dankDashPopoutLoader.item.dashVisible) {
dankDashPopoutLoader.item.dashVisible = false
} else {
switch (tab.toLowerCase()) {
case "media":
dankDashPopoutLoader.item.currentTabIndex = 1
break
case "weather":
dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
break
default:
dankDashPopoutLoader.item.currentTabIndex = 0
break
}
dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
dankDashPopoutLoader.item.dashVisible = true
}
return "DASH_TOGGLE_SUCCESS"
}
return "DASH_TOGGLE_FAILED"
}
target: "dash"
}
IpcHandler {
function getFocusedScreenName() {
if (CompositorService.isHyprland && Hyprland.focusedWorkspace && Hyprland.focusedWorkspace.monitor) {
return Hyprland.focusedWorkspace.monitor.name
}
if (CompositorService.isNiri && NiriService.currentOutput) {
return NiriService.currentOutput
}
return ""
}
function getActiveNotepadInstance() {
if (notepadSlideoutVariants.instances.length === 0) {
return null
}
if (notepadSlideoutVariants.instances.length === 1) {
return notepadSlideoutVariants.instances[0]
}
var focusedScreen = getFocusedScreenName()
if (focusedScreen && notepadSlideoutVariants.instances.length > 0) {
for (var i = 0; i < notepadSlideoutVariants.instances.length; i++) {
var slideout = notepadSlideoutVariants.instances[i]
if (slideout.modelData && slideout.modelData.name === focusedScreen) {
return slideout
}
}
}
for (var i = 0; i < notepadSlideoutVariants.instances.length; i++) {
var slideout = notepadSlideoutVariants.instances[i]
if (slideout.isVisible) {
return slideout
}
}
return notepadSlideoutVariants.instances[0]
}
function open(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.show()
return "NOTEPAD_OPEN_SUCCESS"
}
return "NOTEPAD_OPEN_FAILED"
}
function close(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.hide()
return "NOTEPAD_CLOSE_SUCCESS"
}
return "NOTEPAD_CLOSE_FAILED"
}
function toggle(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.toggle()
return "NOTEPAD_TOGGLE_SUCCESS"
}
return "NOTEPAD_TOGGLE_FAILED"
}
target: "notepad"
}
Variants {
model: SettingsData.getFilteredScreens("toast")
delegate: Toast {
modelData: item
visible: ToastService.toastVisible
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: VolumeOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: MicMuteOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: BrightnessOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: IdleInhibitorOSD {
modelData: item
}
}
}

View File

@@ -13,22 +13,83 @@ Card {
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankCircularImage {
Item {
id: avatarContainer
property bool hasImage: profileImageLoader.status === Image.Ready
width: 77
height: 77
anchors.verticalCenter: parent.verticalCenter
imageSource: {
if (PortalService.profileImage === "")
return ""
if (PortalService.profileImage.startsWith("/"))
return "file://" + PortalService.profileImage
return PortalService.profileImage
Rectangle {
anchors.fill: parent
radius: 36
color: Theme.primary
visible: !avatarContainer.hasImage
StyledText {
anchors.centerIn: parent
text: UserInfoService.username.length > 0 ? UserInfoService.username.charAt(0).toUpperCase() : "b"
font.pixelSize: Theme.fontSizeXLarge + 4
font.weight: Font.Bold
color: Theme.background
}
}
Image {
id: profileImageLoader
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: 2
source: profileImageLoader
maskEnabled: true
maskSource: circularMask
visible: avatarContainer.hasImage
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
Item {
id: circularMask
width: 77 - 4
height: 77 - 4
layer.enabled: true
layer.smooth: true
visible: false
Rectangle {
anchors.fill: parent
radius: width / 2
color: "black"
antialiasing: true
}
}
DankIcon {
anchors.centerIn: parent
name: "person"
size: Theme.iconSize + 8
color: Theme.error
visible: PortalService.profileImage !== "" && profileImageLoader.status === Image.Error
}
fallbackIcon: "person"
}
Column {
@@ -43,7 +104,7 @@ Card {
elide: Text.ElideRight
width: parent.parent.parent.width - avatarContainer.width - Theme.spacingM * 3
}
Row {
spacing: Theme.spacingS
@@ -67,7 +128,7 @@ Card {
width: parent.parent.parent.parent.width - avatarContainer.width - Theme.spacingM * 3 - 16 - Theme.spacingS
}
}
Row {
spacing: Theme.spacingS
@@ -80,7 +141,7 @@ Card {
StyledText {
id: uptimeText
property real availableWidth: parent.parent.parent.parent.width - avatarContainer.width - Theme.spacingM * 3 - 16 - Theme.spacingS
property real longTextWidth: {
const fontSize = Math.round(Theme.fontSizeSmall || 12)

View File

@@ -20,13 +20,11 @@ Variants {
WlrLayershell.namespace: "quickshell:dock"
readonly property bool isVertical: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
anchors {
top: !isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top) : true
bottom: !isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom) : true
left: !isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Left)
right: !isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Right)
top: SettingsData.dockPosition === SettingsData.Position.Top
bottom: SettingsData.dockPosition === SettingsData.Position.Bottom
left: true
right: true
}
property var modelData: item
@@ -37,20 +35,12 @@ Variants {
readonly property real widgetHeight: Math.max(20, 26 + SettingsData.dankBarInnerPadding * 0.6)
readonly property real effectiveBarHeight: Math.max(widgetHeight + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding))
readonly property real barSpacing: {
// Only add spacing if bar is visible, horizontal (Top/Bottom), and at same position as dock
const barIsHorizontal = (SettingsData.dankBarPosition === SettingsData.Position.Top || SettingsData.dankBarPosition === SettingsData.Position.Bottom)
const barIsVertical = (SettingsData.dankBarPosition === SettingsData.Position.Left || SettingsData.dankBarPosition === SettingsData.Position.Right)
const samePosition = (SettingsData.dockPosition === SettingsData.dankBarPosition)
const dockIsHorizontal = !isVertical
const dockIsVertical = isVertical
if (!SettingsData.dankBarVisible) return 0
if (dockIsHorizontal && barIsHorizontal && samePosition) {
return SettingsData.dankBarSpacing + effectiveBarHeight + SettingsData.dankBarBottomGap
}
if (dockIsVertical && barIsVertical && samePosition) {
return SettingsData.dankBarSpacing + effectiveBarHeight + SettingsData.dankBarBottomGap
}
return 0
return (SettingsData.dankBarVisible && barIsHorizontal && samePosition)
? (SettingsData.dankBarSpacing + effectiveBarHeight + SettingsData.dankBarBottomGap)
: 0
}
readonly property real dockMargin: SettingsData.dockSpacing
@@ -113,83 +103,6 @@ Variants {
item: dockMouseArea
}
Rectangle {
id: appTooltip
z: 1000
property var hoveredButton: {
if (!dockApps.children[0]) {
return null
}
const layoutItem = dockApps.children[0]
const flowLayout = layoutItem.children[0]
let repeater = null
for (var i = 0; i < flowLayout.children.length; i++) {
const child = flowLayout.children[i]
if (child && typeof child.count !== "undefined" && typeof child.itemAt === "function") {
repeater = child
break
}
}
if (!repeater || !repeater.itemAt) {
return null
}
for (var i = 0; i < repeater.count; i++) {
const item = repeater.itemAt(i)
if (item && item.dockButton && item.dockButton.showTooltip) {
return item.dockButton
}
}
return null
}
property string tooltipText: hoveredButton ? hoveredButton.tooltipText : ""
visible: hoveredButton !== null && tooltipText !== ""
width: px(tooltipLabel.implicitWidth + 24)
height: px(tooltipLabel.implicitHeight + 12)
color: Theme.surfaceContainer
radius: Theme.cornerRadius
border.width: 1
border.color: Theme.outlineMedium
x: {
if (!hoveredButton) return 0
const buttonPos = hoveredButton.mapToItem(dock.contentItem, 0, 0)
if (!dock.isVertical) {
return buttonPos.x + hoveredButton.width / 2 - width / 2
} else {
if (SettingsData.dockPosition === SettingsData.Position.Right) {
return buttonPos.x - width - Theme.spacingS
} else {
return buttonPos.x + hoveredButton.width + Theme.spacingS
}
}
}
y: {
if (!hoveredButton) return 0
const buttonPos = hoveredButton.mapToItem(dock.contentItem, 0, 0)
if (!dock.isVertical) {
if (SettingsData.dockPosition === SettingsData.Position.Bottom) {
return buttonPos.y - height - Theme.spacingS
} else {
return buttonPos.y + hoveredButton.height + Theme.spacingS
}
} else {
return buttonPos.y + hoveredButton.height / 2 - height / 2
}
}
StyledText {
id: tooltipLabel
anchors.centerIn: parent
text: appTooltip.tooltipText
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
}
}
Item {
id: dockCore
anchors.fill: parent
@@ -212,31 +125,14 @@ Variants {
id: dockMouseArea
property real currentScreen: modelData ? modelData : dock.screen
property real screenWidth: currentScreen ? currentScreen.geometry.width : 1920
property real screenHeight: currentScreen ? currentScreen.geometry.height : 1080
property real maxDockWidth: Math.min(screenWidth * 0.8, 1200)
property real maxDockHeight: Math.min(screenHeight * 0.8, 1200)
height: {
if (dock.isVertical) {
return dock.reveal ? Math.min(dockBackground.implicitHeight + 32, maxDockHeight) : Math.min(Math.max(dockBackground.implicitHeight + 64, 200), screenHeight * 0.5)
} else {
return dock.reveal ? px(58 + SettingsData.dockSpacing + SettingsData.dockBottomGap) : 1
}
}
width: {
if (dock.isVertical) {
return dock.reveal ? px(58 + SettingsData.dockSpacing + SettingsData.dockBottomGap) : 1
} else {
return dock.reveal ? Math.min(dockBackground.implicitWidth + 32, maxDockWidth) : Math.min(Math.max(dockBackground.implicitWidth + 64, 200), screenWidth * 0.5)
}
}
height: dock.reveal ? px(58 + SettingsData.dockSpacing + SettingsData.dockBottomGap) : 1
width: dock.reveal ? Math.min(dockBackground.implicitWidth + 32, maxDockWidth) : Math.min(Math.max(dockBackground.implicitWidth + 64, 200), screenWidth * 0.5)
anchors {
top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? undefined : parent.top) : undefined
bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined) : undefined
horizontalCenter: !dock.isVertical ? parent.horizontalCenter : undefined
left: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? undefined : parent.left) : undefined
right: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined) : undefined
verticalCenter: dock.isVertical ? parent.verticalCenter : undefined
top: SettingsData.dockPosition === SettingsData.Position.Bottom ? undefined : parent.top
bottom: SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined
horizontalCenter: parent.horizontalCenter
}
hoverEnabled: true
acceptedButtons: Qt.NoButton
@@ -248,12 +144,6 @@ Variants {
}
}
Behavior on width {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
Item {
id: dockContainer
@@ -261,17 +151,7 @@ Variants {
transform: Translate {
id: dockSlide
x: {
if (!dock.isVertical) return 0
if (dock.reveal) return 0
if (SettingsData.dockPosition === SettingsData.Position.Right) {
return 60
} else {
return -60
}
}
y: {
if (dock.isVertical) return 0
if (dock.reveal) return 0
if (SettingsData.dockPosition === SettingsData.Position.Bottom) {
return 60
@@ -280,13 +160,6 @@ Variants {
}
}
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
Behavior on y {
NumberAnimation {
duration: 200
@@ -299,20 +172,15 @@ Variants {
id: dockBackground
objectName: "dockBackground"
anchors {
top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? undefined : parent.top) : undefined
bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined) : undefined
horizontalCenter: !dock.isVertical ? parent.horizontalCenter : undefined
left: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? undefined : parent.left) : undefined
right: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined) : undefined
verticalCenter: dock.isVertical ? parent.verticalCenter : undefined
top: SettingsData.dockPosition === SettingsData.Position.Bottom ? undefined : parent.top
bottom: SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined
horizontalCenter: parent.horizontalCenter
}
anchors.topMargin: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? 0 : barSpacing + 4) : 0
anchors.bottomMargin: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? barSpacing + 1 : 0) : 0
anchors.leftMargin: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? 0 : barSpacing + 4) : 0
anchors.rightMargin: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? barSpacing + 1 : 0) : 0
anchors.topMargin: SettingsData.dockPosition === SettingsData.Position.Bottom ? 0 : barSpacing + 4
anchors.bottomMargin: SettingsData.dockPosition === SettingsData.Position.Bottom ? barSpacing + 1 : 0
implicitWidth: dock.isVertical ? (dockApps.implicitHeight + SettingsData.dockSpacing * 2) : (dockApps.implicitWidth + SettingsData.dockSpacing * 2)
implicitHeight: dock.isVertical ? (dockApps.implicitWidth + SettingsData.dockSpacing * 2) : (dockApps.implicitHeight + SettingsData.dockSpacing * 2)
implicitWidth: dockApps.implicitWidth + SettingsData.dockSpacing * 2
implicitHeight: dockApps.implicitHeight + SettingsData.dockSpacing * 2
width: implicitWidth
height: implicitHeight
@@ -331,24 +199,69 @@ Variants {
DockApps {
id: dockApps
anchors.top: !dock.isVertical ? parent.top : undefined
anchors.bottom: !dock.isVertical ? parent.bottom : undefined
anchors.horizontalCenter: !dock.isVertical ? parent.horizontalCenter : undefined
anchors.left: dock.isVertical ? parent.left : undefined
anchors.right: dock.isVertical ? parent.right : undefined
anchors.verticalCenter: dock.isVertical ? parent.verticalCenter : undefined
anchors.topMargin: !dock.isVertical ? SettingsData.dockSpacing : 0
anchors.bottomMargin: !dock.isVertical ? SettingsData.dockSpacing : 0
anchors.leftMargin: dock.isVertical ? SettingsData.dockSpacing : 0
anchors.rightMargin: dock.isVertical ? SettingsData.dockSpacing : 0
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: SettingsData.dockSpacing
anchors.bottomMargin: SettingsData.dockSpacing
contextMenu: dockVariants.contextMenu
groupByApp: dock.groupByApp
isVertical: dock.isVertical
}
}
Rectangle {
id: appTooltip
property var hoveredButton: {
if (!dockApps.children[0]) {
return null
}
const row = dockApps.children[0]
let repeater = null
for (var i = 0; i < row.children.length; i++) {
const child = row.children[i]
if (child && typeof child.count !== "undefined" && typeof child.itemAt === "function") {
repeater = child
break
}
}
if (!repeater || !repeater.itemAt) {
return null
}
for (var i = 0; i < repeater.count; i++) {
const item = repeater.itemAt(i)
if (item && item.dockButton && item.dockButton.showTooltip) {
return item.dockButton
}
}
return null
}
property string tooltipText: hoveredButton ? hoveredButton.tooltipText : ""
visible: hoveredButton !== null && tooltipText !== ""
width: px(tooltipLabel.implicitWidth + 24)
height: px(tooltipLabel.implicitHeight + 12)
color: Theme.surfaceContainer
radius: Theme.cornerRadius
border.width: 1
border.color: Theme.outlineMedium
y: SettingsData.dockPosition === SettingsData.Position.Bottom ? -height - Theme.spacingS : parent.height + Theme.spacingS
x: hoveredButton ? hoveredButton.mapToItem(dockContainer, hoveredButton.width / 2, 0).x - width / 2 : 0
StyledText {
id: tooltipLabel
anchors.centerIn: parent
text: appTooltip.tooltipText
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
}
}
}
}
}
}
}

View File

@@ -13,10 +13,9 @@ Item {
property bool requestDockShow: false
property int pinnedAppCount: 0
property bool groupByApp: false
property bool isVertical: false
implicitWidth: isVertical ? appLayout.height : appLayout.width
implicitHeight: isVertical ? appLayout.width : appLayout.height
implicitWidth: row.width
implicitHeight: row.height
function movePinnedApp(fromIndex, toIndex) {
if (fromIndex === toIndex) {
@@ -34,16 +33,11 @@ Item {
SessionData.setPinnedApps(currentPinned)
}
Item {
id: appLayout
Row {
id: row
spacing: 8
anchors.centerIn: parent
width: layoutFlow.width
height: layoutFlow.height
Flow {
id: layoutFlow
flow: root.isVertical ? Flow.TopToBottom : Flow.LeftToRight
spacing: 8
height: 40
Repeater {
id: repeater
@@ -224,7 +218,6 @@ Item {
}
}
}
}
}
Connections {

View File

@@ -92,42 +92,25 @@ PanelWindow {
}
const dockBackground = findDockBackground(dockWindow.contentItem)
let actualDockWidth = dockWindow.width
if (dockBackground) {
actualDockHeight = dockBackground.height
actualDockWidth = dockBackground.width
}
const isVertical = SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
const dockMargin = 16
let buttonScreenX, buttonScreenY
const isDockAtBottom = SettingsData.dockPosition === SettingsData.Position.Bottom
const dockBottomMargin = 16
let buttonScreenY
if (isVertical) {
const dockContentHeight = dockWindow.height
const screenHeight = root.screen.height
const dockTopMargin = Math.round((screenHeight - dockContentHeight) / 2)
buttonScreenY = dockTopMargin + buttonPosInDock.y + anchorItem.height / 2
if (SettingsData.dockPosition === SettingsData.Position.Right) {
buttonScreenX = root.screen.width - actualDockWidth - dockMargin - 20
} else {
buttonScreenX = actualDockWidth + dockMargin + 20
}
if (isDockAtBottom) {
buttonScreenY = root.screen.height - actualDockHeight - dockBottomMargin - 20
} else {
const isDockAtBottom = SettingsData.dockPosition === SettingsData.Position.Bottom
if (isDockAtBottom) {
buttonScreenY = root.screen.height - actualDockHeight - dockMargin - 20
} else {
buttonScreenY = actualDockHeight + dockMargin + 20
}
const dockContentWidth = dockWindow.width
const screenWidth = root.screen.width
const dockLeftMargin = Math.round((screenWidth - dockContentWidth) / 2)
buttonScreenX = dockLeftMargin + buttonPosInDock.x + anchorItem.width / 2
buttonScreenY = actualDockHeight + dockBottomMargin + 20
}
const dockContentWidth = dockWindow.width
const screenWidth = root.screen.width
const dockLeftMargin = Math.round((screenWidth - dockContentWidth) / 2)
const buttonScreenX = dockLeftMargin + buttonPosInDock.x + anchorItem.width / 2
anchorPos = Qt.point(buttonScreenX, buttonScreenY)
}
@@ -138,35 +121,17 @@ PanelWindow {
height: Math.max(60, menuColumn.implicitHeight + Theme.spacingS * 2)
x: {
const isVertical = SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
if (isVertical) {
const isDockAtRight = SettingsData.dockPosition === SettingsData.Position.Right
if (isDockAtRight) {
return Math.max(10, root.anchorPos.x - width + 30)
} else {
return Math.min(root.width - width - 10, root.anchorPos.x - 30)
}
} else {
const left = 10
const right = root.width - width - 10
const want = root.anchorPos.x - width / 2
return Math.max(left, Math.min(right, want))
}
const left = 10
const right = root.width - width - 10
const want = root.anchorPos.x - width / 2
return Math.max(left, Math.min(right, want))
}
y: {
const isVertical = SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
if (isVertical) {
const top = 10
const bottom = root.height - height - 10
const want = root.anchorPos.y - height / 2
return Math.max(top, Math.min(bottom, want))
const isDockAtBottom = SettingsData.dockPosition === SettingsData.Position.Bottom
if (isDockAtBottom) {
return Math.max(10, root.anchorPos.y - height + 30)
} else {
const isDockAtBottom = SettingsData.dockPosition === SettingsData.Position.Bottom
if (isDockAtBottom) {
return Math.max(10, root.anchorPos.y - height + 30)
} else {
return Math.min(root.height - height - 10, root.anchorPos.y - 30)
}
return Math.min(root.height - height - 10, root.anchorPos.y - 30)
}
}
color: Theme.popupBackground()

View File

@@ -1,105 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtCore
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
Singleton {
id: root
readonly property string greetCfgDir: Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms"
readonly property string sessionConfigPath: greetCfgDir + "/session.json"
readonly property string memoryFile: greetCfgDir + "/memory.json"
property string lastSessionId: ""
property string lastSuccessfulUser: ""
property bool isLightMode: false
property bool nightModeEnabled: false
Component.onCompleted: {
Quickshell.execDetached(["mkdir", "-p", greetCfgDir])
loadMemory()
loadSessionConfig()
}
function loadMemory() {
parseMemory(memoryFileView.text())
}
function loadSessionConfig() {
parseSessionConfig(sessionConfigFileView.text())
}
function parseSessionConfig(content) {
try {
if (content && content.trim()) {
const config = JSON.parse(content)
isLightMode = config.isLightMode !== undefined ? config.isLightMode : false
nightModeEnabled = config.nightModeEnabled !== undefined ? config.nightModeEnabled : false
}
} catch (e) {
console.warn("Failed to parse greeter session config:", e)
}
}
function parseMemory(content) {
try {
if (content && content.trim()) {
const memory = JSON.parse(content)
lastSessionId = memory.lastSessionId !== undefined ? memory.lastSessionId : ""
lastSuccessfulUser = memory.lastSuccessfulUser !== undefined ? memory.lastSuccessfulUser : ""
}
} catch (e) {
console.warn("Failed to parse greetd memory:", e)
}
}
function saveMemory() {
memoryFileView.setText(JSON.stringify({
"lastSessionId": lastSessionId,
"lastSuccessfulUser": lastSuccessfulUser
}, null, 2))
}
function setLastSessionId(id) {
lastSessionId = id || ""
saveMemory()
}
function setLastSuccessfulUser(username) {
lastSuccessfulUser = username || ""
saveMemory()
}
FileView {
id: memoryFileView
path: root.memoryFile
blockLoading: false
blockWrites: false
atomicWrites: true
watchChanges: false
printErrors: false
onLoaded: {
parseMemory(memoryFileView.text())
}
}
FileView {
id: sessionConfigFileView
path: root.sessionConfigPath
blockLoading: false
blockWrites: true
atomicWrites: false
watchChanges: false
printErrors: true
onLoaded: {
parseSessionConfig(sessionConfigFileView.text())
}
onLoadFailed: error => {
console.warn("Could not load greeter session config from", root.sessionConfigPath, "error:", error)
}
}
}

View File

@@ -1,114 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtCore
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
Singleton {
id: root
readonly property string configPath: {
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms"
return greetCfgDir + "/settings.json"
}
property string currentThemeName: "blue"
property bool settingsLoaded: false
property string customThemeFile: ""
property string matugenScheme: "scheme-tonal-spot"
property bool use24HourClock: true
property bool useFahrenheit: false
property bool nightModeEnabled: false
property string weatherLocation: "New York, NY"
property string weatherCoordinates: "40.7128,-74.0060"
property bool useAutoLocation: false
property bool weatherEnabled: true
property string iconTheme: "System Default"
property bool useOSLogo: false
property string osLogoColorOverride: ""
property real osLogoBrightness: 0.5
property real osLogoContrast: 1
property string fontFamily: "Inter Variable"
property string monoFontFamily: "Fira Code"
property int fontWeight: Font.Normal
property real fontScale: 1.0
property real cornerRadius: 12
property string widgetBackgroundColor: "sch"
property string surfaceBase: "s"
property string lockDateFormat: ""
property bool lockScreenShowPowerActions: true
property var screenPreferences: ({})
property int animationSpeed: 2
readonly property string defaultFontFamily: "Inter Variable"
readonly property string defaultMonoFontFamily: "Fira Code"
function parseSettings(content) {
try {
if (content && content.trim()) {
const settings = JSON.parse(content)
currentThemeName = settings.currentThemeName !== undefined ? settings.currentThemeName : "blue"
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : ""
matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot"
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true
useFahrenheit = settings.useFahrenheit !== undefined ? settings.useFahrenheit : false
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false
weatherLocation = settings.weatherLocation !== undefined ? settings.weatherLocation : "New York, NY"
weatherCoordinates = settings.weatherCoordinates !== undefined ? settings.weatherCoordinates : "40.7128,-74.0060"
useAutoLocation = settings.useAutoLocation !== undefined ? settings.useAutoLocation : false
weatherEnabled = settings.weatherEnabled !== undefined ? settings.weatherEnabled : true
iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default"
useOSLogo = settings.useOSLogo !== undefined ? settings.useOSLogo : false
osLogoColorOverride = settings.osLogoColorOverride !== undefined ? settings.osLogoColorOverride : ""
osLogoBrightness = settings.osLogoBrightness !== undefined ? settings.osLogoBrightness : 0.5
osLogoContrast = settings.osLogoContrast !== undefined ? settings.osLogoContrast : 1
fontFamily = settings.fontFamily !== undefined ? settings.fontFamily : defaultFontFamily
monoFontFamily = settings.monoFontFamily !== undefined ? settings.monoFontFamily : defaultMonoFontFamily
fontWeight = settings.fontWeight !== undefined ? settings.fontWeight : Font.Normal
fontScale = settings.fontScale !== undefined ? settings.fontScale : 1.0
cornerRadius = settings.cornerRadius !== undefined ? settings.cornerRadius : 12
widgetBackgroundColor = settings.widgetBackgroundColor !== undefined ? settings.widgetBackgroundColor : "sch"
surfaceBase = settings.surfaceBase !== undefined ? settings.surfaceBase : "s"
lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : ""
lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({})
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : 2
settingsLoaded = true
if (typeof Theme !== "undefined") {
Theme.applyGreeterTheme(currentThemeName)
}
}
} catch (e) {
console.warn("Failed to parse greetd settings:", e)
}
}
function getEffectiveLockDateFormat() {
return lockDateFormat && lockDateFormat.length > 0 ? lockDateFormat : Locale.LongFormat
}
function getFilteredScreens(componentId) {
const prefs = screenPreferences && screenPreferences[componentId] || ["all"]
if (prefs.includes("all")) {
return Quickshell.screens
}
return Quickshell.screens.filter(screen => prefs.includes(screen.name))
}
FileView {
id: settingsFile
path: root.configPath
blockLoading: false
blockWrites: true
atomicWrites: false
watchChanges: false
printErrors: true
onLoaded: {
parseSettings(settingsFile.text())
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +0,0 @@
import QtQuick
import Quickshell
pragma Singleton
pragma ComponentBehavior: Bound
Singleton {
id: root
property string passwordBuffer: ""
property string username: ""
property string usernameInput: ""
property bool showPasswordInput: false
property string selectedSession: ""
property string pamState: ""
property bool unlocking: false
property var sessionList: []
property var sessionExecs: []
property int currentSessionIndex: 0
function reset() {
showPasswordInput = false
username = ""
usernameInput = ""
passwordBuffer = ""
pamState = ""
}
}

View File

@@ -1,18 +0,0 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Greetd
WlSessionLockSurface {
id: root
required property WlSessionLock lock
color: "transparent"
GreeterContent {
anchors.fill: parent
screenName: root.screen?.name ?? ""
sessionLock: root.lock
}
}

View File

@@ -1,79 +0,0 @@
# Dank (dms) Greeter
A greeter for [greetd](https://github.com/kennylevinsen/greetd) that follows the aesthetics of the dms lock screen.
## Features
- **Multi user**: Login with any system user
- **dms sync**: Sync settings with dms for consistent styling between shell and greeter
- **niri or Hyprland**: Use either niri or Hyprland for the greeter's compositor.
- **Custom PAM**: Supports custom PAM configuration in `/etc/pam.d/dankshell`
- **Session Memory**: Remembers last selected session and user
## Installation
The easiest thing is to run `dms greeter install` or `dms` for interactive installation.
Manual installation:
1. Install `greetd` (in most distro's standard repositories)
2. Copy `assets/dms-niri.kdl` or `assets/dms-hypr.conf` to `/etc/greetd`
- niri if you want to run the greeter under niri, hypr if you want to run the greeter under Hyprland
3. Copy `assets/greet-niri.sh` or `assets/greet-hyprland.sh` to `/etc/greetd/start-dms.sh`
4. Edit `/etc/greetd/dms-niri.kdl` or `/etc/greetd/dms-hypr.conf` and replace `_DMS_PATH_` with the absolute path to dms, e.g. `/home/joecool/.config/quickshell/dms`
5. Edit or create `/etc/greetd/config.toml`
```toml
[terminal]
# The VT to run the greeter on. Can be "next", "current" or a number
# designating the VT.
vt = 1
# The default session, also known as the greeter.
[default_session]
# `agreety` is the bundled agetty/login-lookalike. You can replace `/bin/sh`
# with whatever you want started, such as `sway`.
# The user to run the command as. The privileges this user must have depends
# on the greeter. A graphical greeter may for example require the user to be
# in the `video` group.
user = "greeter"
command = "/etc/greetd/start-dms.sh"%
```
Enable the greeter with `sudo systemctl enable greetd`
## Usage
To run dms in greeter mode you just need to set `DMS_RUN_GREETER=1` in the environment.
```bash
DMS_RUN_GREETER=1 qs -p /path/to/dms
```
### Configuration
#### Compositor
You can configure compositor specific settings such as outputs/displays the same as you would in niri or Hyprland.
Simply edit `/etc/greetd/dms-niri.kdl` or `/etc/greetd/dms-hypr.conf` to change compositor settings for the greeter
#### Personalization
Wallpapers and themes and weather and clock formats and things are a TODO on the documentation, but it's configured exactly the same as dms.
You can synchronize those configurations with a specific user if you want greeter settings to always mirror the shell.
```bash
# For core settings (theme, clock formats, etc)
sudo ln -sf ~/.config/DankMaterialShell/settings.json /etc/greetd/.dms/settings.json
# For state (mainly you would configure wallpaper in this file)
sudo ln -sf ~/.local/state/DankMaterialShell/session.json /etc/greetd/.dms/session.json
# For wallpaper based theming
sudo ln -sf ~/.cache/quickshell/dankshell/dms-colors.json /etc/greetd/.dms/dms-colors.json
```
You can override the configuration path with the `DMS_GREET_CFG_DIR` environment variable, the default is `/etc/greetd/.dms`
It should be writable by the greeter user.

View File

@@ -1,6 +0,0 @@
env = DMS_RUN_GREETER,1
env = QT_QPA_PLATFORM,wayland
env = QT_WAYLAND_DISABLE_WINDOWDECORATION,1
env = EGL_PLATFORM,gbm
exec = sh -c "qs -p _DMS_PATH_; hyprctl dispatch exit"

View File

@@ -1,21 +0,0 @@
hotkey-overlay {
skip-at-startup
}
environment {
DMS_RUN_GREETER "1"
QT_QPA_PLATFORM "wayland"
QT_WAYLAND_DISABLE_WINDOWDECORATION "1"
}
spawn-at-startup "sh" "-c" "qs -p _DMS_PATH_; niri msg action quit --skip-confirmation"
debug {
keep-max-bpc-unchanged
}
gestures {
hot-corners {
off
}
}

View File

@@ -1,3 +0,0 @@
#!/bin/sh
EGL_PLATFORM=gbm Hyprland -c /etc/greetd/dms-hypr.conf

View File

@@ -1,3 +0,0 @@
#!/bin/sh
EGL_PLATFORM=gbm niri -c /etc/greetd/dms-niri.kdl

View File

@@ -44,8 +44,10 @@ Item {
powerDialogVisible = false
}
property var facts: ["A photon takes 100,000 to 200,000 years bouncing through the Sun's dense core, then races to Earth in just 8 minutes 20 seconds.", "A teaspoon of neutron star matter would weigh a billion metric tons here on Earth.", "Right now, 100 trillion solar neutrinos are passing through your body every second.", "The Sun converts 4 million metric tons of matter into pure energy every second—enough to power Earth for 500,000 years.", "The universe still glows with leftover heat from the Big Bang—just 2.7 degrees above absolute zero.", "There's a nebula out there that's actually colder than empty space itself.", "We've detected black holes crashing together by measuring spacetime stretch by less than 1/10,000th the width of a proton.", "Fast radio bursts can release more energy in 5 milliseconds than our Sun produces in 3 days.", "Our galaxy might be crawling with billions of rogue planets drifting alone in the dark.", "Distant galaxies can move away from us faster than light because space itself is stretching.", "The edge of what we can see is 46.5 billion light-years away, even though the universe is only 13.8 billion years old.", "The universe is mostly invisible: 5% regular matter, 27% dark matter, 68% dark energy.", "A day on Venus lasts longer than its entire year around the Sun.", "On Mercury, the time between sunrises is 176 Earth days long.", "In about 4.5 billion years, our galaxy will smash into Andromeda.", "Most of the gold in your jewelry was forged when neutron stars collided somewhere in space.", "PSR J1748-2446ad, the fastest spinning star, rotates 716 times per second—its equator moves at 24% the speed of light.", "Cosmic rays create particles that shouldn't make it to Earth's surface, but time dilation lets them sneak through.", "Jupiter's magnetic field is so huge that if we could see it, it would look bigger than the Moon in our sky.", "Interstellar space is so empty it's like a cube 32 kilometers wide containing just a single grain of sand.", "Voyager 1 is 24 billion kilometers away but won't leave the Sun's gravitational influence for another 30,000 years.", "Counting to a billion at one number per second would take over 31 years.", "Space is so vast, even speeding at light-speed, you'd never return past the cosmic horizon.", "Astronauts on the ISS age about 0.01 seconds less each year than people on Earth.", "Sagittarius B2, a dust cloud near our galaxy's center, contains ethyl formate—the compound that gives raspberries their flavor and rum its smell.", "Beyond 16 billion light-years, the cosmic event horizon marks where space expands too fast for light to ever reach us again.", "Even at light-speed, you'd never catch up to most galaxies—space expands faster.", "Only around 5% of galaxies are ever reachable—even at light-speed.", "If the Sun vanished, we'd still orbit it for 8 minutes before drifting away.", "If a planet 65 million light-years away looked at Earth now, it'd see dinosaurs.", "Our oldest radio signals will reach the Milky Way's center in 26,000 years.", "Every atom in your body heavier than hydrogen was forged in the nuclear furnace of a dying star.", "The Moon moves 3.8 centimeters farther from Earth every year.", "The universe creates 275 million new stars every single day.", "Jupiter's Great Red Spot is a storm twice the size of Earth that has been raging for at least 350 years.", "If you watched someone fall into a black hole, they'd appear frozen at the event horizon forever—time effectively stops from your perspective.", "The Boötes Supervoid is a cosmic desert 1.8 billion light-years across with 60% fewer galaxies than it should have."]
function pickRandomFact() {
randomFact = Facts.getRandomFact()
randomFact = facts[Math.floor(Math.random() * facts.length)]
}
Component.onCompleted: {
@@ -178,21 +180,93 @@ Item {
spacing: Theme.spacingL
Layout.fillWidth: true
DankCircularImage {
Item {
id: avatarContainer
property bool hasImage: profileImageLoader.status === Image.Ready
Layout.preferredWidth: 60
Layout.preferredHeight: 60
imageSource: {
if (PortalService.profileImage === "") {
return ""
}
if (PortalService.profileImage.startsWith("/")) {
return "file://" + PortalService.profileImage
}
return PortalService.profileImage
Rectangle {
anchors.fill: parent
radius: width / 2
color: "transparent"
border.color: Theme.primary
border.width: 1
visible: parent.hasImage
}
Image {
id: profileImageLoader
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: profileImageLoader
maskEnabled: true
maskSource: circularMask
visible: avatarContainer.hasImage
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
Item {
id: circularMask
width: 60 - 10
height: 60 - 10
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.iconSize + 4
color: Theme.primaryText
}
}
DankIcon {
anchors.centerIn: parent
name: "warning"
size: Theme.iconSize + 4
color: Theme.primaryText
visible: PortalService.profileImage !== "" && profileImageLoader.status === Image.Error
}
fallbackIcon: "person"
}
Rectangle {

View File

@@ -65,24 +65,11 @@ Item {
DankButtonGroup {
id: positionButtonGroup
anchors.verticalCenter: parent.verticalCenter
model: ["Top", "Bottom", "Left", "Right"]
currentIndex: {
switch (SettingsData.dockPosition) {
case SettingsData.Position.Top: return 0
case SettingsData.Position.Bottom: return 1
case SettingsData.Position.Left: return 2
case SettingsData.Position.Right: return 3
default: return 1
}
}
model: ["Top", "Bottom"]
currentIndex: SettingsData.dockPosition === SettingsData.Position.Bottom ? 1 : 0
onSelectionChanged: (index, selected) => {
if (selected) {
switch (index) {
case 0: SettingsData.setDockPosition(SettingsData.Position.Top); break
case 1: SettingsData.setDockPosition(SettingsData.Position.Bottom); break
case 2: SettingsData.setDockPosition(SettingsData.Position.Left); break
case 3: SettingsData.setDockPosition(SettingsData.Position.Right); break
}
SettingsData.setDockPosition(index === 1 ? SettingsData.Position.Bottom : SettingsData.Position.Top)
}
}
}
@@ -183,7 +170,7 @@ Item {
}
StyledText {
text: "Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen"
text: "Display a dock with pinned and running applications that can be positioned at the top or bottom of the screen"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap

View File

@@ -11,12 +11,7 @@ LazyLoader {
active: true
Variants {
model: {
if (SessionData.isGreeterMode) {
return Quickshell.screens
}
return SettingsData.getFilteredScreens("wallpaper")
}
model: SettingsData.getFilteredScreens("wallpaper")
PanelWindow {
id: wallpaperWindow

View File

@@ -18,7 +18,7 @@ A modern Wayland desktop shell built with [Quickshell](https://quickshell.org/)
<div align="center">
<div style="max-width: 700px; margin: 0 auto;">
https://github.com/user-attachments/assets/9b99dbbf-42d3-44ab-83b6-fae6c2aa3cc0
https://github.com/user-attachments/assets/fd619c0e-6edc-457e-b3d6-5a5c3bae7173
</div>
</div>
@@ -43,7 +43,7 @@ https://github.com/user-attachments/assets/9b99dbbf-42d3-44ab-83b6-fae6c2aa3cc0
### Control Center
<img width="600" alt="Control Center" src="https://github.com/user-attachments/assets/732c30de-5f4a-4a2b-a995-c8ab656cecd5" />
<img width="600" alt="Control Center" src="https://github.com/user-attachments/assets/98889bd8-55d2-44c7-b278-75ca49c596fa" />
### System Monitor
@@ -440,12 +440,6 @@ bindl = , XF86MonBrightnessDown, exec, dms ipc call brightness decrement 5 ""
bind = SUPERSHIFT, N, exec, dms ipc call night toggle
```
## Greeter
You can install a matching [greetd](https://github.com/kennylevinsen/greetd) greeter, that will give you a greeter that matches the lock screen.
It's as simple as running `dms greeter install` in most cases, but more information is in the [Greetd module](Modules/Greetd/README.md)
## IPC Commands
Control everything from the command line, or via keybinds. For comprehensive documentation of all available IPC commands, see [docs/IPC.md](docs/IPC.md).

View File

@@ -82,6 +82,7 @@ Singleton {
Component.onCompleted: {
detectCompositor()
NiriService.generateNiriLayoutConfig()
}
function filterCurrentWorkspace(toplevels, screen) {
@@ -192,6 +193,7 @@ Singleton {
root.isHyprland = false
root.compositor = "niri"
console.log("CompositorService: Detected Niri with socket:", root.niriSocket)
NiriService.generateNiriBinds()
} else {
root.isHyprland = false
root.isNiri = true
@@ -200,4 +202,4 @@ Singleton {
}
}
}
}
}

View File

@@ -2,10 +2,12 @@ pragma Singleton
pragma ComponentBehavior: Bound
import QtCore
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import qs.Common
Singleton {
id: root
@@ -31,6 +33,7 @@ Singleton {
property bool suppressConfigToast: true
property bool suppressNextConfigToast: false
property bool matugenSuppression: false
property bool configGenerationPending: false
readonly property string socketPath: Quickshell.env("NIRI_SOCKET")
@@ -412,13 +415,13 @@ Singleton {
}
function doScreenTransition() {
send({
"Action": {
"DoScreenTransition": {
"delay_ms": 0,
return send({
"Action": {
"DoScreenTransition": {
"delay_ms": 0,
}
}
}
})
})
}
function switchToWorkspace(workspaceIndex) {
@@ -652,6 +655,7 @@ Singleton {
return result
}
Timer {
id: suppressToastTimer
interval: 3000
@@ -663,4 +667,101 @@ Singleton {
interval: 2000
onTriggered: root.matugenSuppression = false
}
Timer {
id: configGenerationDebounce
interval: 100
onTriggered: root.doGenerateNiriLayoutConfig()
}
function generateNiriLayoutConfig() {
const niriSocket = Quickshell.env("NIRI_SOCKET")
if (!niriSocket || niriSocket.length === 0) {
return
}
if (configGenerationPending) {
return
}
configGenerationPending = true
configGenerationDebounce.restart()
}
function doGenerateNiriLayoutConfig() {
console.log("NiriService: Generating layout config...")
const cornerRadius = typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12
const gaps = typeof SettingsData !== "undefined" ? Math.max(4, SettingsData.dankBarSpacing) : 4
const configContent = `layout {
gaps ${gaps}
border {
width 2
}
focus-ring {
width 2
}
}
window-rule {
geometry-corner-radius ${cornerRadius}
clip-to-geometry true
tiled-state true
draw-border-with-background false
}`
const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
const niriDmsDir = configDir + "/niri/dms"
const configPath = niriDmsDir + "/layout.kdl"
writeConfigProcess.configContent = configContent
writeConfigProcess.configPath = configPath
writeConfigProcess.command = ["sh", "-c", `mkdir -p "${niriDmsDir}" && cat > "${configPath}" << 'EOF'\n${configContent}\nEOF`]
writeConfigProcess.running = true
configGenerationPending = false
}
function generateNiriBinds() {
console.log("NiriService: Generating binds config...")
const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
const niriDmsDir = configDir + "/niri/dms"
const bindsPath = niriDmsDir + "/binds.kdl"
const sourceBindsPath = Paths.strip(Qt.resolvedUrl("niri-binds.kdl"))
writeBindsProcess.bindsPath = bindsPath
writeBindsProcess.command = ["sh", "-c", `mkdir -p "${niriDmsDir}" && cp "${sourceBindsPath}" "${bindsPath}"`]
writeBindsProcess.running = true
}
Process {
id: writeConfigProcess
property string configContent: ""
property string configPath: ""
onExited: exitCode => {
if (exitCode === 0) {
console.log("NiriService: Generated layout config at", configPath)
} else {
console.warn("NiriService: Failed to write layout config, exit code:", exitCode)
}
}
}
Process {
id: writeBindsProcess
property string bindsPath: ""
onExited: exitCode => {
if (exitCode === 0) {
console.log("NiriService: Generated binds config at", bindsPath)
} else {
console.warn("NiriService: Failed to write binds config, exit code:", exitCode)
}
}
}
}

View File

@@ -21,42 +21,10 @@ Singleton {
systemProfileCheckProcess.running = true
}
function getUserProfileImage(username) {
if (!username) {
profileImage = ""
return
}
if (Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true") {
profileImage = ""
return
}
userProfileCheckProcess.command = [
"bash", "-c",
`uid=$(id -u ${username} 2>/dev/null) && [ -n "$uid" ] && dbus-send --system --print-reply --dest=org.freedesktop.Accounts /org/freedesktop/Accounts/User$uid org.freedesktop.DBus.Properties.Get string:org.freedesktop.Accounts.User string:IconFile 2>/dev/null | grep -oP 'string "\\K[^"]+' || echo ""`
]
userProfileCheckProcess.running = true
}
function getGreeterUserProfileImage(username) {
if (!username) {
profileImage = ""
return
}
userProfileCheckProcess.command = [
"bash", "-c",
`uid=$(id -u ${username} 2>/dev/null) && [ -n "$uid" ] && dbus-send --system --print-reply --dest=org.freedesktop.Accounts /org/freedesktop/Accounts/User$uid org.freedesktop.DBus.Properties.Get string:org.freedesktop.Accounts.User string:IconFile 2>/dev/null | grep -oP 'string "\\K[^"]+' || echo ""`
]
userProfileCheckProcess.running = true
}
function setProfileImage(imagePath) {
profileImage = imagePath
if (accountsServiceAvailable) {
if (imagePath) {
setSystemProfileImage(imagePath)
} else {
setSystemProfileImage("")
}
if (accountsServiceAvailable && imagePath) {
setSystemProfileImage(imagePath)
}
}
@@ -83,12 +51,11 @@ Singleton {
}
function setSystemProfileImage(imagePath) {
if (!accountsServiceAvailable) {
if (!accountsServiceAvailable || !imagePath) {
return
}
const path = imagePath || ""
const script = `dbus-send --system --print-reply --dest=org.freedesktop.Accounts /org/freedesktop/Accounts/User$(id -u) org.freedesktop.Accounts.User.SetIconFile string:'${path}'`
const script = `dbus-send --system --print-reply --dest=org.freedesktop.Accounts /org/freedesktop/Accounts/User$(id -u) org.freedesktop.Accounts.User.SetIconFile string:'${imagePath}'`
systemProfileSetProcess.command = ["bash", "-c", script]
systemProfileSetProcess.running = true
@@ -156,29 +123,6 @@ Singleton {
}
}
Process {
id: userProfileCheckProcess
command: []
running: false
stdout: StdioCollector {
onStreamFinished: {
const trimmed = text.trim()
if (trimmed && trimmed !== "" && !trimmed.includes("Error") && trimmed !== "/var/lib/AccountsService/icons/") {
root.profileImage = trimmed
} else {
root.profileImage = ""
}
}
}
onExited: exitCode => {
if (exitCode !== 0) {
root.profileImage = ""
}
}
}
Process {
id: settingsPortalCheckProcess
command: ["gdbus", "call", "--session", "--dest", "org.freedesktop.portal.Desktop", "--object-path", "/org/freedesktop/portal/desktop", "--method", "org.freedesktop.portal.Settings.ReadOne", "org.freedesktop.appearance", "color-scheme"]

55
Services/niri-binds.kdl Normal file
View File

@@ -0,0 +1,55 @@
binds {
Mod+Space hotkey-overlay-title="Application Launcher" {
spawn "dms" "ipc" "call" "spotlight" "toggle";
}
Mod+V hotkey-overlay-title="Clipboard Manager" {
spawn "dms" "ipc" "call" "clipboard" "toggle";
}
Mod+M hotkey-overlay-title="Task Manager" {
spawn "dms" "ipc" "call" "processlist" "toggle";
}
Mod+Comma hotkey-overlay-title="Settings" {
spawn "dms" "ipc" "call" "settings" "toggle";
}
Mod+N hotkey-overlay-title="Notification Center" {
spawn "dms" "ipc" "call" "notifications" "toggle";
}
Mod+Shift+N hotkey-overlay-title="Notepad" {
spawn "dms" "ipc" "call" "notepad" "toggle";
}
Mod+Alt+L hotkey-overlay-title="Lock Screen" {
spawn "dms" "ipc" "call" "lock" "lock";
}
Ctrl+Alt+Delete hotkey-overlay-title="Task Manager" {
spawn "dms" "ipc" "call" "processlist" "toggle";
}
// Audio
XF86AudioRaiseVolume allow-when-locked=true {
spawn "dms" "ipc" "call" "audio" "increment" "3";
}
XF86AudioLowerVolume allow-when-locked=true {
spawn "dms" "ipc" "call" "audio" "decrement" "3";
}
XF86AudioMute allow-when-locked=true {
spawn "dms" "ipc" "call" "audio" "mute";
}
XF86AudioMicMute allow-when-locked=true {
spawn "dms" "ipc" "call" "audio" "micmute";
}
// BL
XF86MonBrightnessUp allow-when-locked=true {
spawn "dms" "ipc" "call" "brightness" "increment" "5" "";
}
XF86MonBrightnessDown allow-when-locked=true {
spawn "dms" "ipc" "call" "brightness" "decrement" "5" "";
}
}

View File

@@ -69,7 +69,7 @@ Rectangle {
name: root.fallbackIcon
size: parent.width * 0.5
color: Theme.surfaceVariantText
visible: (internalImage.status !== Image.Ready || root.imageSource === "") && root.fallbackIcon !== ""
visible: internalImage.status !== Image.Ready && root.imageSource === "" && root.fallbackIcon !== ""
}
@@ -77,8 +77,8 @@ Rectangle {
anchors.centerIn: parent
visible: root.imageSource === "" && root.fallbackIcon === "" && root.fallbackText !== ""
text: root.fallbackText
font.pixelSize: Math.max(12, parent.width * 0.5)
font.pixelSize: Math.max(12, parent.width * 0.36)
font.weight: Font.Bold
color: Theme.surfaceVariantText
color: Theme.primaryText
}
}

View File

@@ -16,9 +16,6 @@ Rectangle {
property bool enableFuzzySearch: false
property int popupWidthOffset: 0
property int maxPopupHeight: 400
property bool openUpwards: false
property int popupWidth: 0
property bool alignPopupRight: false
signal valueChanged(string value)
@@ -105,33 +102,10 @@ Rectangle {
return
}
if (root.openUpwards || root.alignPopupRight) {
popup.open()
Qt.callLater(() => {
if (root.openUpwards) {
const pos = dropdown.mapToItem(Overlay.overlay, 0, 0)
if (root.alignPopupRight) {
popup.x = pos.x + dropdown.width - popup.width
} else {
popup.x = pos.x - (root.popupWidthOffset / 2)
}
popup.y = pos.y - popup.height - 4
} else {
const pos = dropdown.mapToItem(Overlay.overlay, 0, dropdown.height + 4)
if (root.alignPopupRight) {
popup.x = pos.x + dropdown.width - popup.width
} else {
popup.x = pos.x - (root.popupWidthOffset / 2)
}
popup.y = pos.y
}
})
} else {
const pos = dropdown.mapToItem(Overlay.overlay, 0, dropdown.height + 4)
popup.x = pos.x - (root.popupWidthOffset / 2)
popup.y = pos.y
popup.open()
}
const pos = dropdown.mapToItem(Overlay.overlay, 0, dropdown.height + 4)
popup.x = pos.x - (root.popupWidthOffset / 2)
popup.y = pos.y
popup.open()
}
}
@@ -239,7 +213,7 @@ Rectangle {
}
parent: Overlay.overlay
width: root.popupWidth > 0 ? root.popupWidth : (dropdown.width + root.popupWidthOffset)
width: dropdown.width + root.popupWidthOffset
height: Math.min(root.maxPopupHeight, (root.enableFuzzySearch ? 54 : 0) + Math.min(filteredOptions.length, 10) * 36 + 16)
padding: 0
modal: true
@@ -364,9 +338,8 @@ Rectangle {
font.pixelSize: Theme.fontSizeMedium
color: isCurrentValue ? Theme.primary : Theme.surfaceText
font.weight: isCurrentValue ? Font.Medium : Font.Normal
width: root.popupWidth > 0 ? undefined : (parent.parent.width - parent.x - Theme.spacingS)
elide: root.popupWidth > 0 ? Text.ElideNone : Text.ElideRight
wrapMode: Text.NoWrap
width: parent.parent.width - parent.x - Theme.spacingS
elide: Text.ElideRight
}
}

View File

@@ -82,7 +82,7 @@ PanelWindow {
Item {
id: contentContainer
layer.enabled: true
layer.enabled: false
readonly property real screenWidth: root.screen ? root.screen.width : 1920
readonly property real screenHeight: root.screen ? root.screen.height : 1080

View File

@@ -1,3 +1,3 @@
[templates.niri]
input_path = './matugen/templates/niri-colors.kdl'
output_path = '~/.config/niri/dankshell-colors.kdl'
output_path = '~/.config/niri/dms/colors.kdl'

637
shell.qml
View File

@@ -2,23 +2,642 @@
//@ pragma UseQApplication
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Hyprland
import qs.Common
import qs.Modals
import qs.Modals.Clipboard
import qs.Modals.Common
import qs.Modals.Settings
import qs.Modals.Spotlight
import qs.Modules
import qs.Modules.AppDrawer
import qs.Modules.DankDash
import qs.Modules.ControlCenter
import qs.Modules.Dock
import qs.Modules.Lock
import qs.Modules.Notifications.Center
import qs.Widgets
import "./Modules/Notepad"
import qs.Modules.Notifications.Popup
import qs.Modules.OSD
import qs.Modules.ProcessList
import qs.Modules.Settings
import qs.Modules.DankBar
import qs.Modules.DankBar.Popouts
import qs.Services
ShellRoot {
id: root
readonly property bool runGreeter: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
Component.onCompleted: {
PortalService.init()
// Initialize DisplayService night mode functionality
DisplayService.nightModeEnabled
// Initialize WallpaperCyclingService
WallpaperCyclingService.cyclingActive
}
Loader {
id: dmsShellLoader
asynchronous: false
sourceComponent: DMSShell{}
active: !root.runGreeter
WallpaperBackground {}
Lock {
id: lock
anchors.fill: parent
}
Loader {
id: dmsGreeterLoader
id: dankBarLoader
asynchronous: false
sourceComponent: DMSGreeter{}
active: root.runGreeter
property var currentPosition: SettingsData.dankBarPosition
sourceComponent: DankBar {
onColorPickerRequested: colorPickerModal.show()
}
onCurrentPositionChanged: {
const component = sourceComponent
sourceComponent = null
Qt.callLater(() => {
sourceComponent = component
})
}
}
Loader {
id: dockLoader
active: true
asynchronous: false
property var currentPosition: SettingsData.dockPosition
sourceComponent: Dock {
contextMenu: dockContextMenuLoader.item ? dockContextMenuLoader.item : null
}
onLoaded: {
if (item) {
dockContextMenuLoader.active = true
}
}
onCurrentPositionChanged: {
console.log("DEBUG: Dock position changed to:", currentPosition, "- recreating dock")
const comp = sourceComponent
sourceComponent = null
Qt.callLater(() => {
sourceComponent = comp
})
}
}
Loader {
id: dankDashPopoutLoader
active: false
asynchronous: true
sourceComponent: Component {
DankDashPopout {
id: dankDashPopout
}
}
}
LazyLoader {
id: dockContextMenuLoader
active: false
DockContextMenu {
id: dockContextMenu
}
}
LazyLoader {
id: notificationCenterLoader
active: false
NotificationCenterPopout {
id: notificationCenter
}
}
Variants {
model: SettingsData.getFilteredScreens("notifications")
delegate: NotificationPopupManager {
modelData: item
}
}
LazyLoader {
id: controlCenterLoader
active: false
property var modalRef: colorPickerModal
ControlCenterPopout {
id: controlCenterPopout
colorPickerModal: controlCenterLoader.modalRef
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()
}
}
}
LazyLoader {
id: wifiPasswordModalLoader
active: false
WifiPasswordModal {
id: wifiPasswordModal
}
}
LazyLoader {
id: networkInfoModalLoader
active: false
NetworkInfoModal {
id: networkInfoModal
}
}
LazyLoader {
id: batteryPopoutLoader
active: false
BatteryPopout {
id: batteryPopout
}
}
LazyLoader {
id: vpnPopoutLoader
active: false
VpnPopout {
id: vpnPopout
}
}
LazyLoader {
id: powerMenuLoader
active: false
PowerMenu {
id: powerMenu
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 () {})
}
}
}
}
LazyLoader {
id: powerConfirmModalLoader
active: false
ConfirmModal {
id: powerConfirmModal
}
}
LazyLoader {
id: processListPopoutLoader
active: false
ProcessListPopout {
id: processListPopout
}
}
SettingsModal {
id: settingsModal
}
LazyLoader {
id: appDrawerLoader
active: false
AppDrawerPopout {
id: appDrawerPopout
}
}
SpotlightModal {
id: spotlightModal
}
ClipboardHistoryModal {
id: clipboardHistoryModalPopup
}
NotificationModal {
id: notificationModal
}
ColorPickerModal {
id: colorPickerModal
}
LazyLoader {
id: processListModalLoader
active: false
ProcessListModal {
id: processListModal
}
}
LazyLoader {
id: systemUpdateLoader
active: false
SystemUpdatePopout {
id: systemUpdatePopout
}
}
Variants {
id: notepadSlideoutVariants
model: SettingsData.getFilteredScreens("notepad")
delegate: DankSlideout {
id: notepadSlideout
modelData: item
title: qsTr("Notepad")
slideoutWidth: 480
expandable: true
expandedWidthValue: 960
customTransparency: SettingsData.notepadTransparencyOverride
content: Component {
Notepad {
onHideRequested: {
notepadSlideout.hide()
}
}
}
function toggle() {
if (isVisible) {
hide()
} else {
show()
}
}
}
}
LazyLoader {
id: powerMenuModalLoader
active: false
PowerMenuModal {
id: powerMenuModal
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 () {})
}
}
}
}
IpcHandler {
function open() {
powerMenuModalLoader.active = true
if (powerMenuModalLoader.item)
powerMenuModalLoader.item.open()
return "POWERMENU_OPEN_SUCCESS"
}
function close() {
if (powerMenuModalLoader.item)
powerMenuModalLoader.item.close()
return "POWERMENU_CLOSE_SUCCESS"
}
function toggle() {
powerMenuModalLoader.active = true
if (powerMenuModalLoader.item)
powerMenuModalLoader.item.toggle()
return "POWERMENU_TOGGLE_SUCCESS"
}
target: "powermenu"
}
IpcHandler {
function open(): string {
processListModalLoader.active = true
if (processListModalLoader.item)
processListModalLoader.item.show()
return "PROCESSLIST_OPEN_SUCCESS"
}
function close(): string {
if (processListModalLoader.item)
processListModalLoader.item.hide()
return "PROCESSLIST_CLOSE_SUCCESS"
}
function toggle(): string {
processListModalLoader.active = true
if (processListModalLoader.item)
processListModalLoader.item.toggle()
return "PROCESSLIST_TOGGLE_SUCCESS"
}
target: "processlist"
}
IpcHandler {
function open(): string {
controlCenterLoader.active = true
if (controlCenterLoader.item) {
controlCenterLoader.item.open()
return "CONTROL_CENTER_OPEN_SUCCESS"
}
return "CONTROL_CENTER_OPEN_FAILED"
}
function close(): string {
if (controlCenterLoader.item) {
controlCenterLoader.item.close()
return "CONTROL_CENTER_CLOSE_SUCCESS"
}
return "CONTROL_CENTER_CLOSE_FAILED"
}
function toggle(): string {
controlCenterLoader.active = true
if (controlCenterLoader.item) {
controlCenterLoader.item.toggle()
return "CONTROL_CENTER_TOGGLE_SUCCESS"
}
return "CONTROL_CENTER_TOGGLE_FAILED"
}
target: "control-center"
}
IpcHandler {
function open(tab: string): string {
dankDashPopoutLoader.active = true
if (dankDashPopoutLoader.item) {
switch (tab.toLowerCase()) {
case "media":
dankDashPopoutLoader.item.currentTabIndex = 1
break
case "weather":
dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
break
default:
dankDashPopoutLoader.item.currentTabIndex = 0
break
}
dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
dankDashPopoutLoader.item.dashVisible = true
return "DASH_OPEN_SUCCESS"
}
return "DASH_OPEN_FAILED"
}
function close(): string {
if (dankDashPopoutLoader.item) {
dankDashPopoutLoader.item.dashVisible = false
return "DASH_CLOSE_SUCCESS"
}
return "DASH_CLOSE_FAILED"
}
function toggle(tab: string): string {
dankDashPopoutLoader.active = true
if (dankDashPopoutLoader.item) {
if (dankDashPopoutLoader.item.dashVisible) {
dankDashPopoutLoader.item.dashVisible = false
} else {
switch (tab.toLowerCase()) {
case "media":
dankDashPopoutLoader.item.currentTabIndex = 1
break
case "weather":
dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0
break
default:
dankDashPopoutLoader.item.currentTabIndex = 0
break
}
dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen)
dankDashPopoutLoader.item.dashVisible = true
}
return "DASH_TOGGLE_SUCCESS"
}
return "DASH_TOGGLE_FAILED"
}
target: "dash"
}
IpcHandler {
function getFocusedScreenName() {
if (CompositorService.isHyprland && Hyprland.focusedWorkspace && Hyprland.focusedWorkspace.monitor) {
return Hyprland.focusedWorkspace.monitor.name
}
if (CompositorService.isNiri && NiriService.currentOutput) {
return NiriService.currentOutput
}
return ""
}
function getActiveNotepadInstance() {
if (notepadSlideoutVariants.instances.length === 0) {
return null
}
if (notepadSlideoutVariants.instances.length === 1) {
return notepadSlideoutVariants.instances[0]
}
var focusedScreen = getFocusedScreenName()
if (focusedScreen && notepadSlideoutVariants.instances.length > 0) {
for (var i = 0; i < notepadSlideoutVariants.instances.length; i++) {
var slideout = notepadSlideoutVariants.instances[i]
if (slideout.modelData && slideout.modelData.name === focusedScreen) {
return slideout
}
}
}
for (var i = 0; i < notepadSlideoutVariants.instances.length; i++) {
var slideout = notepadSlideoutVariants.instances[i]
if (slideout.isVisible) {
return slideout
}
}
return notepadSlideoutVariants.instances[0]
}
function open(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.show()
return "NOTEPAD_OPEN_SUCCESS"
}
return "NOTEPAD_OPEN_FAILED"
}
function close(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.hide()
return "NOTEPAD_CLOSE_SUCCESS"
}
return "NOTEPAD_CLOSE_FAILED"
}
function toggle(): string {
var instance = getActiveNotepadInstance()
if (instance) {
instance.toggle()
return "NOTEPAD_TOGGLE_SUCCESS"
}
return "NOTEPAD_TOGGLE_FAILED"
}
target: "notepad"
}
Variants {
model: SettingsData.getFilteredScreens("toast")
delegate: Toast {
modelData: item
visible: ToastService.toastVisible
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: VolumeOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: MicMuteOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: BrightnessOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: IdleInhibitorOSD {
modelData: item
}
}
}