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

Merge branch 'master' of github.com:bbedward/DankMaterialShell into wip/plugins

This commit is contained in:
bbedward
2025-10-01 13:38:49 -04:00
48 changed files with 3303 additions and 1057 deletions

View File

@@ -1,6 +1,6 @@
# Contributing # Contributing
Contributions are welcome and encourages. Contributions are welcome and encouraged.
## Formatting ## Formatting
@@ -27,4 +27,4 @@ Sometimes it just breaks code though. Like turning `"_\""` into `"_""`, so you m
## Pull request ## Pull request
Include screenshots/video if applicable in your pull request if applicable, to visualize what your change is affecting. Include screenshots/video if applicable in your pull request if applicable, to visualize what your change is affecting.

53
Common/Facts.qml Normal file
View File

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

View File

@@ -12,6 +12,8 @@ import qs.Services
Singleton { Singleton {
id: root id: root
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
enum Position { enum Position {
Top, Top,
Bottom, Bottom,
@@ -19,6 +21,14 @@ Singleton {
Right Right
} }
enum AnimationSpeed {
None,
Shortest,
Short,
Medium,
Long
}
// Theme settings // Theme settings
property string currentThemeName: "blue" property string currentThemeName: "blue"
property string customThemeFile: "" property string customThemeFile: ""
@@ -151,6 +161,7 @@ Singleton {
property int notificationTimeoutCritical: 0 property int notificationTimeoutCritical: 0
property int notificationPopupPosition: SettingsData.Position.Top property int notificationPopupPosition: SettingsData.Position.Top
property var screenPreferences: ({}) property var screenPreferences: ({})
property int animationSpeed: SettingsData.AnimationSpeed.Short
readonly property string defaultFontFamily: "Inter Variable" readonly property string defaultFontFamily: "Inter Variable"
readonly property string defaultMonoFontFamily: "Fira Code" readonly property string defaultMonoFontFamily: "Fira Code"
readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation) readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation)
@@ -353,6 +364,7 @@ Singleton {
surfaceBase = settings.surfaceBase !== undefined ? settings.surfaceBase : "s" surfaceBase = settings.surfaceBase !== undefined ? settings.surfaceBase : "s"
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({}) screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({})
pluginSettings = settings.pluginSettings !== undefined ? settings.pluginSettings : ({}) pluginSettings = settings.pluginSettings !== undefined ? settings.pluginSettings : ({})
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : SettingsData.AnimationSpeed.Short
applyStoredTheme() applyStoredTheme()
detectAvailableIconThemes() detectAvailableIconThemes()
detectQtTools() detectQtTools()
@@ -472,7 +484,8 @@ Singleton {
"notificationTimeoutCritical": notificationTimeoutCritical, "notificationTimeoutCritical": notificationTimeoutCritical,
"notificationPopupPosition": notificationPopupPosition, "notificationPopupPosition": notificationPopupPosition,
"screenPreferences": screenPreferences, "screenPreferences": screenPreferences,
"pluginSettings": pluginSettings "pluginSettings": pluginSettings,
"animationSpeed": animationSpeed
}, null, 2)) }, null, 2))
} }
@@ -984,13 +997,23 @@ Singleton {
function setShowDock(enabled) { function setShowDock(enabled) {
showDock = enabled showDock = enabled
if (enabled && dankBarPosition === SettingsData.Position.Top) { if (enabled && dockPosition === dankBarPosition) {
setDockPosition(SettingsData.Position.Bottom) if (dankBarPosition === SettingsData.Position.Top) {
return setDockPosition(SettingsData.Position.Bottom)
} return
if (enabled && dankBarPosition === SettingsData.Position.Top) { }
setDockPosition(SettingsData.Position.Bottom) if (dankBarPosition === SettingsData.Position.Bottom) {
return 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
}
} }
saveSettings() saveSettings()
} }
@@ -1137,14 +1160,22 @@ Singleton {
function setDankBarPosition(position) { function setDankBarPosition(position) {
dankBarPosition = position dankBarPosition = position
if (position === SettingsData.Position.Bottom && showDock) { if (position === SettingsData.Position.Bottom && dockPosition === SettingsData.Position.Bottom && showDock) {
setDockPosition(SettingsData.Position.Top) setDockPosition(SettingsData.Position.Top)
return return
} }
if (position === SettingsData.Position.Top && showDock) { if (position === SettingsData.Position.Top && dockPosition === SettingsData.Position.Top && showDock) {
setDockPosition(SettingsData.Position.Bottom) setDockPosition(SettingsData.Position.Bottom)
return 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() saveSettings()
} }
@@ -1156,6 +1187,12 @@ Singleton {
if (position === SettingsData.Position.Top && dankBarPosition === SettingsData.Position.Top && showDock) { if (position === SettingsData.Position.Top && dankBarPosition === SettingsData.Position.Top && showDock) {
setDankBarPosition(SettingsData.Position.Bottom) 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() saveSettings()
Qt.callLater(() => forceDockLayoutRefresh()) Qt.callLater(() => forceDockLayoutRefresh())
} }
@@ -1260,14 +1297,21 @@ Singleton {
return pluginSettings[pluginId] || {} return pluginSettings[pluginId] || {}
} }
function setAnimationSpeed(speed) {
animationSpeed = speed
saveSettings()
}
function _shq(s) { function _shq(s) {
return "'" + String(s).replace(/'/g, "'\\''") + "'" return "'" + String(s).replace(/'/g, "'\\''") + "'"
} }
Component.onCompleted: { Component.onCompleted: {
loadSettings() if (!isGreeterMode) {
fontCheckTimer.start() loadSettings()
initializeListModels() fontCheckTimer.start()
initializeListModels()
}
} }
ListModel { ListModel {
@@ -1308,20 +1352,22 @@ Singleton {
FileView { FileView {
id: settingsFile id: settingsFile
path: StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json" path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
blockLoading: true blockLoading: isGreeterMode
blockWrites: true blockWrites: true
atomicWrites: true atomicWrites: true
watchChanges: true watchChanges: !isGreeterMode
onLoaded: { onLoaded: {
parseSettings(settingsFile.text()) if (!isGreeterMode) {
hasTriedDefaultSettings = false parseSettings(settingsFile.text())
hasTriedDefaultSettings = false
}
} }
onLoadFailed: error => { onLoadFailed: error => {
if (!hasTriedDefaultSettings) { if (!isGreeterMode && !hasTriedDefaultSettings) {
hasTriedDefaultSettings = true hasTriedDefaultSettings = true
defaultSettingsCheckProcess.running = true defaultSettingsCheckProcess.running = true
} else { } else if (!isGreeterMode) {
applyStoredTheme() applyStoredTheme()
} }
} }

View File

@@ -31,9 +31,8 @@ Singleton {
readonly property string shellDir: Paths.strip(Qt.resolvedUrl(".").toString()).replace("/Common/", "") readonly property string shellDir: Paths.strip(Qt.resolvedUrl(".").toString()).replace("/Common/", "")
readonly property string wallpaperPath: { readonly property string wallpaperPath: {
if (typeof SessionData === "undefined") return "" if (typeof SessionData === "undefined") return ""
if (SessionData.perMonitorWallpaper) { if (SessionData.perMonitorWallpaper) {
// Use first monitor's wallpaper for dynamic theming
var screens = Quickshell.screens var screens = Quickshell.screens
if (screens.length > 0) { if (screens.length > 0) {
var firstMonitorWallpaper = SessionData.getMonitorWallpaper(screens[0].name) var firstMonitorWallpaper = SessionData.getMonitorWallpaper(screens[0].name)
@@ -93,6 +92,13 @@ Singleton {
} }
} }
function applyGreeterTheme(themeName) {
switchTheme(themeName, false, false)
if (themeName === dynamic && dynamicColorsFileView.path) {
dynamicColorsFileView.reload()
}
}
function getMatugenColor(path, fallback) { function getMatugenColor(path, fallback) {
colorUpdateTrigger colorUpdateTrigger
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark" const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"
@@ -235,11 +241,22 @@ Singleton {
property color shadowMedium: Qt.rgba(0, 0, 0, 0.08) property color shadowMedium: Qt.rgba(0, 0, 0, 0.08)
property color shadowStrong: Qt.rgba(0, 0, 0, 0.3) property color shadowStrong: Qt.rgba(0, 0, 0, 0.3)
property int shorterDuration: 100 readonly property var animationDurations: [
property int shortDuration: 150 { shorter: 0, short: 0, medium: 0, long: 0, extraLong: 0 },
property int mediumDuration: 300 { shorter: 50, short: 75, medium: 150, long: 250, extraLong: 500 },
property int longDuration: 500 { shorter: 100, short: 150, medium: 300, long: 500, extraLong: 1000 },
property int extraLongDuration: 1000 { shorter: 150, short: 225, medium: 450, long: 750, extraLong: 1500 },
{ shorter: 200, short: 300, medium: 600, long: 1000, extraLong: 2000 }
]
readonly property int currentAnimationSpeed: typeof SettingsData !== "undefined" ? SettingsData.animationSpeed : SettingsData.AnimationSpeed.Short
readonly property var currentDurations: animationDurations[currentAnimationSpeed] || animationDurations[SettingsData.AnimationSpeed.Short]
property int shorterDuration: currentDurations.shorter
property int shortDuration: currentDurations.short
property int mediumDuration: currentDurations.medium
property int longDuration: currentDurations.long
property int extraLongDuration: currentDurations.extraLong
property int standardEasing: Easing.OutCubic property int standardEasing: Easing.OutCubic
property int emphasizedEasing: Easing.OutQuart property int emphasizedEasing: Easing.OutQuart
@@ -292,10 +309,13 @@ Singleton {
currentThemeCategory = "generic" currentThemeCategory = "generic"
} }
} }
if (savePrefs && typeof SettingsData !== "undefined") const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
if (savePrefs && typeof SettingsData !== "undefined" && !isGreeterMode)
SettingsData.setTheme(currentTheme) SettingsData.setTheme(currentTheme)
generateSystemThemesFromCurrentTheme() if (!isGreeterMode) {
generateSystemThemesFromCurrentTheme()
}
} }
function setLightMode(light, savePrefs = true, enableTransition = false) { function setLightMode(light, savePrefs = true, enableTransition = false) {
@@ -307,11 +327,14 @@ Singleton {
return return
} }
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
isLightMode = light isLightMode = light
if (savePrefs && typeof SessionData !== "undefined") if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode)
SessionData.setLightMode(isLightMode) SessionData.setLightMode(isLightMode)
PortalService.setLightMode(isLightMode) if (!isGreeterMode) {
generateSystemThemesFromCurrentTheme() PortalService.setLightMode(isLightMode)
generateSystemThemesFromCurrentTheme()
}
} }
function toggleLightMode(savePrefs = true) { function toggleLightMode(savePrefs = true) {
@@ -588,7 +611,8 @@ Singleton {
} }
function generateSystemThemesFromCurrentTheme() { function generateSystemThemesFromCurrentTheme() {
if (!matugenAvailable) const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
if (!matugenAvailable || isGreeterMode)
return return
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode)
@@ -659,8 +683,9 @@ Singleton {
command: ["which", "matugen"] command: ["which", "matugen"]
onExited: code => { onExited: code => {
matugenAvailable = (code === 0) && !envDisableMatugen matugenAvailable = (code === 0) && !envDisableMatugen
if (!matugenAvailable) { const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
console.log("matugen not not available in path or disabled via DMS_DISABLE_MATUGEN")
if (!matugenAvailable || isGreeterMode) {
return return
} }
@@ -711,10 +736,7 @@ Singleton {
onExited: exitCode => { onExited: exitCode => {
workerRunning = false workerRunning = false
if (exitCode === 2) { if (exitCode !== 0 && 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") { if (typeof ToastService !== "undefined") {
ToastService.showError("Theme worker failed (" + exitCode + ")") ToastService.showError("Theme worker failed (" + exitCode + ")")
} }
@@ -803,8 +825,14 @@ Singleton {
FileView { FileView {
id: dynamicColorsFileView id: dynamicColorsFileView
path: stateDir + "/dms-colors.json" path: {
watchChanges: currentTheme === dynamic 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
function parseAndLoadColors() { function parseAndLoadColors() {
try { try {
@@ -817,6 +845,7 @@ Singleton {
} }
} }
} catch (e) { } catch (e) {
console.error("Theme: Failed to parse dynamic colors:", e)
if (typeof ToastService !== "undefined") { if (typeof ToastService !== "undefined") {
ToastService.wallpaperErrorStatus = "error" ToastService.wallpaperErrorStatus = "error"
ToastService.showError("Dynamic colors parse error: " + e.message) ToastService.showError("Dynamic colors parse error: " + e.message)

25
DMSGreeter.qml Normal file
View File

@@ -0,0 +1,25 @@
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
}
}
}

640
DMSShell.qml Normal file
View File

@@ -0,0 +1,640 @@
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
// Initialize PluginService by accessing its properties
PluginService.pluginDirectory
}
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

@@ -74,6 +74,42 @@ QtObject {
selectPrevious() selectPrevious()
event.accepted = true event.accepted = true
} }
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
if (!modal.keyboardNavigationActive) {
modal.keyboardNavigationActive = true
modal.selectedIndex = 0
} else {
selectNext()
}
event.accepted = true
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
if (!modal.keyboardNavigationActive) {
modal.keyboardNavigationActive = true
modal.selectedIndex = 0
} else if (modal.selectedIndex === 0) {
modal.keyboardNavigationActive = false
} else {
selectPrevious()
}
event.accepted = true
} else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
if (!modal.keyboardNavigationActive) {
modal.keyboardNavigationActive = true
modal.selectedIndex = 0
} else {
selectNext()
}
event.accepted = true
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
if (!modal.keyboardNavigationActive) {
modal.keyboardNavigationActive = true
modal.selectedIndex = 0
} else if (modal.selectedIndex === 0) {
modal.keyboardNavigationActive = false
} else {
selectPrevious()
}
event.accepted = true
} else if (event.key === Qt.Key_Delete && (event.modifiers & Qt.ShiftModifier)) { } else if (event.key === Qt.Key_Delete && (event.modifiers & Qt.ShiftModifier)) {
modal.clearAll() modal.clearAll()
modal.hide() modal.hide()

View File

@@ -93,6 +93,48 @@ DankModal {
selectedButton = 1 selectedButton = 1
event.accepted = true event.accepted = true
break break
case Qt.Key_N:
if (event.modifiers & Qt.ControlModifier) {
keyboardNavigation = true
selectedButton = (selectedButton + 1) % 2
event.accepted = true
}
break
case Qt.Key_P:
if (event.modifiers & Qt.ControlModifier) {
keyboardNavigation = true
selectedButton = selectedButton === -1 ? 1 : (selectedButton - 1 + 2) % 2
event.accepted = true
}
break
case Qt.Key_J:
if (event.modifiers & Qt.ControlModifier) {
keyboardNavigation = true
selectedButton = 1
event.accepted = true
}
break
case Qt.Key_K:
if (event.modifiers & Qt.ControlModifier) {
keyboardNavigation = true
selectedButton = 0
event.accepted = true
}
break
case Qt.Key_H:
if (event.modifiers & Qt.ControlModifier) {
keyboardNavigation = true
selectedButton = 0
event.accepted = true
}
break
case Qt.Key_L:
if (event.modifiers & Qt.ControlModifier) {
keyboardNavigation = true
selectedButton = 1
event.accepted = true
}
break
case Qt.Key_Tab: case Qt.Key_Tab:
keyboardNavigation = true keyboardNavigation = true
selectedButton = selectedButton === -1 ? 0 : (selectedButton + 1) % 2 selectedButton = selectedButton === -1 ? 0 : (selectedButton + 1) % 2

View File

@@ -22,7 +22,7 @@ PanelWindow {
property bool closeOnEscapeKey: true property bool closeOnEscapeKey: true
property bool closeOnBackgroundClick: true property bool closeOnBackgroundClick: true
property string animationType: "scale" property string animationType: "scale"
property int animationDuration: Theme.shorterDuration property int animationDuration: Theme.shortDuration
property var animationEasing: Theme.emphasizedEasing property var animationEasing: Theme.emphasizedEasing
property color backgroundColor: Theme.surfaceContainer property color backgroundColor: Theme.surfaceContainer
property color borderColor: Theme.outlineMedium property color borderColor: Theme.outlineMedium
@@ -34,6 +34,7 @@ PanelWindow {
property bool shouldHaveFocus: shouldBeVisible property bool shouldHaveFocus: shouldBeVisible
property bool allowFocusOverride: false property bool allowFocusOverride: false
property bool allowStacking: false property bool allowStacking: false
property bool keepContentLoaded: false
signal opened signal opened
signal dialogClosed signal dialogClosed
@@ -90,7 +91,7 @@ PanelWindow {
Timer { Timer {
id: closeTimer id: closeTimer
interval: animationDuration + 50 interval: animationDuration + 100
onTriggered: { onTriggered: {
visible = false visible = false
} }
@@ -158,7 +159,6 @@ PanelWindow {
border.width: root.borderWidth border.width: root.borderWidth
layer.enabled: root.enableShadow layer.enabled: root.enableShadow
opacity: root.shouldBeVisible ? 1 : 0 opacity: root.shouldBeVisible ? 1 : 0
scale: root.animationType === "scale" ? (root.shouldBeVisible ? 1 : 0.9) : 1
transform: root.animationType === "slide" ? slideTransform : null transform: root.animationType === "slide" ? slideTransform : null
Translate { Translate {
@@ -172,7 +172,7 @@ PanelWindow {
id: contentLoader id: contentLoader
anchors.fill: parent anchors.fill: parent
active: root.shouldBeVisible || root.visible active: root.keepContentLoaded || root.shouldBeVisible || root.visible
asynchronous: false asynchronous: false
} }
@@ -183,15 +183,6 @@ PanelWindow {
} }
} }
Behavior on scale {
enabled: root.animationType === "scale"
NumberAnimation {
duration: root.animationDuration
easing.type: root.animationEasing
}
}
layer.effect: MultiEffect { layer.effect: MultiEffect {
shadowEnabled: true shadowEnabled: true
shadowHorizontalOffset: 0 shadowHorizontalOffset: 0

View File

@@ -239,7 +239,12 @@ DankModal {
return return
} }
if (!keyboardNavigationActive) { if (!keyboardNavigationActive) {
if (event.key === Qt.Key_Tab || event.key === Qt.Key_Down || event.key === Qt.Key_Right) { const isInitKey = event.key === Qt.Key_Tab || event.key === Qt.Key_Down || event.key === Qt.Key_Right ||
(event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) ||
(event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) ||
(event.key === Qt.Key_L && event.modifiers & Qt.ControlModifier)
if (isInitKey) {
keyboardNavigationActive = true keyboardNavigationActive = true
if (currentPath !== homeDir) { if (currentPath !== homeDir) {
backButtonFocused = true backButtonFocused = true
@@ -281,6 +286,69 @@ DankModal {
} }
event.accepted = true event.accepted = true
break break
case Qt.Key_N:
if (event.modifiers & Qt.ControlModifier) {
if (backButtonFocused) {
backButtonFocused = false
selectedIndex = 0
} else if (selectedIndex < totalItems - 1) {
selectedIndex++
}
event.accepted = true
}
break
case Qt.Key_P:
if (event.modifiers & Qt.ControlModifier) {
if (selectedIndex > 0) {
selectedIndex--
} else if (currentPath !== homeDir) {
backButtonFocused = true
selectedIndex = -1
}
event.accepted = true
}
break
case Qt.Key_J:
if (event.modifiers & Qt.ControlModifier) {
if (selectedIndex < totalItems - 1) {
selectedIndex++
}
event.accepted = true
}
break
case Qt.Key_K:
if (event.modifiers & Qt.ControlModifier) {
if (selectedIndex > 0) {
selectedIndex--
} else if (currentPath !== homeDir) {
backButtonFocused = true
selectedIndex = -1
}
event.accepted = true
}
break
case Qt.Key_H:
if (event.modifiers & Qt.ControlModifier) {
if (!backButtonFocused && selectedIndex > 0) {
selectedIndex--
} else if (currentPath !== homeDir) {
backButtonFocused = true
selectedIndex = -1
}
event.accepted = true
}
break
case Qt.Key_L:
if (event.modifiers & Qt.ControlModifier) {
if (backButtonFocused) {
backButtonFocused = false
selectedIndex = 0
} else if (selectedIndex < totalItems - 1) {
selectedIndex++
}
event.accepted = true
}
break
case Qt.Key_Left: case Qt.Key_Left:
if (backButtonFocused) if (backButtonFocused)
return return

View File

@@ -76,6 +76,30 @@ DankModal {
} }
event.accepted = true; event.accepted = true;
break; break;
case Qt.Key_N:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex + 1) % optionCount;
event.accepted = true;
}
break;
case Qt.Key_P:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex - 1 + optionCount) % optionCount;
event.accepted = true;
}
break;
case Qt.Key_J:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex + 1) % optionCount;
event.accepted = true;
}
break;
case Qt.Key_K:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex - 1 + optionCount) % optionCount;
event.accepted = true;
}
break;
} }
} }

View File

@@ -33,6 +33,18 @@ Item {
} else if (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") { } else if (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow() appLauncher.selectPreviousInRow()
event.accepted = true event.accepted = true
} else if (event.key == Qt.Key_J && event.modifiers & Qt.ControlModifier) {
appLauncher.selectNext()
event.accepted = true
} else if (event.key == Qt.Key_K && event.modifiers & Qt.ControlModifier) {
appLauncher.selectPrevious()
event.accepted = true
} else if (event.key == Qt.Key_L && event.modifiers & Qt.ControlModifier && appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow()
event.accepted = true
} else if (event.key == Qt.Key_H && event.modifiers & Qt.ControlModifier && appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow()
event.accepted = true
} else if (event.key === Qt.Key_Tab) { } else if (event.key === Qt.Key_Tab) {
if (appLauncher.viewMode === "grid") { if (appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow() appLauncher.selectNextInRow()
@@ -47,6 +59,20 @@ Item {
appLauncher.selectPrevious() appLauncher.selectPrevious()
} }
event.accepted = true event.accepted = true
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
if (appLauncher.viewMode === "grid") {
appLauncher.selectNextInRow()
} else {
appLauncher.selectNext()
}
event.accepted = true
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
if (appLauncher.viewMode === "grid") {
appLauncher.selectPreviousInRow()
} else {
appLauncher.selectPrevious()
}
event.accepted = true
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
appLauncher.launchSelected() appLauncher.launchSelected()
event.accepted = true event.accepted = true

View File

@@ -32,11 +32,7 @@ DankModal {
function hide() { function hide() {
spotlightOpen = false spotlightOpen = false
close() close()
if (contentLoader.item && contentLoader.item.appLauncher) { cleanupTimer.restart()
contentLoader.item.appLauncher.searchQuery = ""
contentLoader.item.appLauncher.selectedIndex = 0
contentLoader.item.appLauncher.setCategory("All")
}
} }
function toggle() { function toggle() {
@@ -55,6 +51,7 @@ DankModal {
borderColor: Theme.outlineMedium borderColor: Theme.outlineMedium
borderWidth: 1 borderWidth: 1
enableShadow: true enableShadow: true
keepContentLoaded: true
onVisibleChanged: () => { onVisibleChanged: () => {
if (visible && !spotlightOpen) { if (visible && !spotlightOpen) {
show() show()
@@ -72,6 +69,19 @@ DankModal {
} }
content: spotlightContent content: spotlightContent
Timer {
id: cleanupTimer
interval: animationDuration + 50
onTriggered: {
if (contentLoader.item && contentLoader.item.appLauncher) {
contentLoader.item.appLauncher.searchQuery = ""
contentLoader.item.appLauncher.selectedIndex = 0
contentLoader.item.appLauncher.setCategory("All")
}
}
}
Connections { Connections {
function onCloseAllModalsExcept(excludedModal) { function onCloseAllModalsExcept(excludedModal) {
if (excludedModal !== spotlightModal && !allowStacking && spotlightOpen) { if (excludedModal !== spotlightModal && !allowStacking && spotlightOpen) {

View File

@@ -127,6 +127,44 @@ DankPopout {
return return
} }
if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
appLauncher.selectNext()
event.accepted = true
return
}
if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
appLauncher.selectPrevious()
event.accepted = true
return
}
if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
appLauncher.selectNext()
event.accepted = true
return
}
if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
appLauncher.selectPrevious()
event.accepted = true
return
}
if (appLauncher.viewMode === "grid") {
if (event.key === Qt.Key_L && event.modifiers & Qt.ControlModifier) {
appLauncher.selectNextInRow()
event.accepted = true
return
}
if (event.key === Qt.Key_H && event.modifiers & Qt.ControlModifier) {
appLauncher.selectPreviousInRow()
event.accepted = true
return
}
}
if (!searchField.activeFocus && event.text && /[a-zA-Z0-9\s]/.test(event.text)) { if (!searchField.activeFocus && event.text && /[a-zA-Z0-9\s]/.test(event.text)) {
searchField.forceActiveFocus() searchField.forceActiveFocus()
searchField.insertText(event.text) searchField.insertText(event.text)

View File

@@ -47,7 +47,6 @@ PanelWindow {
border.width: 0 border.width: 0
opacity: powerMenuVisible ? 1 : 0 opacity: powerMenuVisible ? 1 : 0
scale: powerMenuVisible ? 1 : 0.85 scale: powerMenuVisible ? 1 : 0.85
antialiasing: true
MouseArea { MouseArea {

View File

@@ -338,32 +338,28 @@ Rectangle {
width: { width: {
if (root.isVertical) { if (root.isVertical) {
// Vertical mode: width is like horizontal height (small and fixed) return SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5;
return SettingsData.showWorkspaceApps ? widgetHeight * 0.8 : widgetHeight * 0.6;
} else { } else {
// Horizontal mode - original logic
if (SettingsData.showWorkspaceApps && loadedIcons.length > 0) { if (SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons); const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons);
const iconsWidth = numIcons * 18 + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0); const iconsWidth = numIcons * 18 + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0);
const baseWidth = isActive ? root.widgetHeight * 1.0 + Theme.spacingXS : root.widgetHeight * 0.8; const baseWidth = isActive ? root.widgetHeight * 0.9 + Theme.spacingXS : root.widgetHeight * 0.7;
return baseWidth + iconsWidth; return baseWidth + iconsWidth;
} }
return isActive ? root.widgetHeight * 1.2 : root.widgetHeight * 0.8; return isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7;
} }
} }
height: { height: {
if (root.isVertical) { if (root.isVertical) {
// Vertical mode: height is like horizontal width (dynamic)
if (SettingsData.showWorkspaceApps && loadedIcons.length > 0) { if (SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons); const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons);
const iconsHeight = numIcons * 18 + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0); const iconsHeight = numIcons * 18 + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0);
const baseHeight = isActive ? root.widgetHeight * 1.0 + Theme.spacingXS : root.widgetHeight * 0.8; const baseHeight = isActive ? root.widgetHeight * 0.9 + Theme.spacingXS : root.widgetHeight * 0.7;
return baseHeight + iconsHeight; return baseHeight + iconsHeight;
} }
return isActive ? root.widgetHeight * 1.2 : root.widgetHeight * 0.8; return isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7;
} else { } else {
// Horizontal mode - original logic return SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5;
return SettingsData.showWorkspaceApps ? widgetHeight * 0.8 : widgetHeight * 0.6;
} }
} }
radius: Math.min(width, height) / 2 radius: Math.min(width, height) / 2
@@ -388,7 +384,9 @@ Rectangle {
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.centerIn: parent
width: root.isVertical ? parent.width + Theme.spacingXL : parent.width
height: root.isVerical ? parent.height : parent.height + Theme.spacingXL
hoverEnabled: !isPlaceholder hoverEnabled: !isPlaceholder
cursorShape: isPlaceholder ? Qt.ArrowCursor : Qt.PointingHandCursor cursorShape: isPlaceholder ? Qt.ArrowCursor : Qt.PointingHandCursor
enabled: !isPlaceholder enabled: !isPlaceholder

View File

@@ -63,7 +63,6 @@ DankPopout {
implicitHeight: contentColumn.height + Theme.spacingM * 2 implicitHeight: contentColumn.height + Theme.spacingM * 2
color: Theme.surfaceContainer color: Theme.surfaceContainer
radius: Theme.cornerRadius radius: Theme.cornerRadius
antialiasing: true
focus: true focus: true
Component.onCompleted: { Component.onCompleted: {
@@ -94,7 +93,6 @@ DankPopout {
anchors.fill: parent anchors.fill: parent
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04) color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04)
radius: parent.radius radius: parent.radius
antialiasing: true
SequentialAnimation on opacity { SequentialAnimation on opacity {
running: root.shouldBeVisible running: root.shouldBeVisible

View File

@@ -271,7 +271,6 @@ Item {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
radius: Theme.cornerRadius radius: Theme.cornerRadius
antialiasing: true
opacity: 1.0 opacity: 1.0
gradient: Gradient { gradient: Gradient {
GradientStop { GradientStop {
@@ -360,7 +359,6 @@ Item {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
border.width: 2 border.width: 2
radius: Theme.cornerRadius * 2 radius: Theme.cornerRadius * 2
antialiasing: true
layer.enabled: true layer.enabled: true
layer.effect: MultiEffect { layer.effect: MultiEffect {
@@ -433,11 +431,10 @@ Item {
delegate: Rectangle { delegate: Rectangle {
required property var modelData required property var modelData
required property int index required property int index
width: parent.width width: parent.width
height: 48 height: 48
radius: Theme.cornerRadius radius: Theme.cornerRadius
antialiasing: true
color: deviceMouseAreaLeft.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.surfaceContainerHigh color: deviceMouseAreaLeft.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.surfaceContainerHigh
border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: modelData === AudioService.sink ? 2 : 1 border.width: modelData === AudioService.sink ? 2 : 1
@@ -520,7 +517,6 @@ Item {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
border.width: 2 border.width: 2
radius: Theme.cornerRadius * 2 radius: Theme.cornerRadius * 2
antialiasing: true
layer.enabled: true layer.enabled: true
layer.effect: MultiEffect { layer.effect: MultiEffect {
@@ -597,7 +593,6 @@ Item {
width: parent.width width: parent.width
height: 48 height: 48
radius: Theme.cornerRadius radius: Theme.cornerRadius
antialiasing: true
color: playerMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.surfaceContainerHigh color: playerMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.surfaceContainerHigh
border.color: modelData === activePlayer ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) border.color: modelData === activePlayer ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: modelData === activePlayer ? 2 : 1 border.width: modelData === activePlayer ? 2 : 1
@@ -843,7 +838,6 @@ Item {
height: 40 height: 40
radius: 20 radius: 20
anchors.centerIn: parent anchors.centerIn: parent
antialiasing: true
color: shuffleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: shuffleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
DankIcon { DankIcon {
@@ -885,7 +879,6 @@ Item {
height: 40 height: 40
radius: 20 radius: 20
anchors.centerIn: parent anchors.centerIn: parent
antialiasing: true
color: prevBtnArea.containsMouse ? Theme.surfaceContainerHigh : "transparent" color: prevBtnArea.containsMouse ? Theme.surfaceContainerHigh : "transparent"
DankIcon { DankIcon {
@@ -925,7 +918,6 @@ Item {
height: 50 height: 50
radius: 25 radius: 25
anchors.centerIn: parent anchors.centerIn: parent
antialiasing: true
color: Theme.primary color: Theme.primary
DankIcon { DankIcon {
@@ -965,7 +957,6 @@ Item {
height: 40 height: 40
radius: 20 radius: 20
anchors.centerIn: parent anchors.centerIn: parent
antialiasing: true
color: nextBtnArea.containsMouse ? Theme.surfaceContainerHigh : "transparent" color: nextBtnArea.containsMouse ? Theme.surfaceContainerHigh : "transparent"
DankIcon { DankIcon {
@@ -996,7 +987,6 @@ Item {
height: 40 height: 40
radius: 20 radius: 20
anchors.centerIn: parent anchors.centerIn: parent
antialiasing: true
color: repeatArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: repeatArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
DankIcon { DankIcon {
@@ -1057,7 +1047,6 @@ Item {
radius: 20 radius: 20
x: parent.width - 40 - Theme.spacingM x: parent.width - 40 - Theme.spacingM
y: 185 y: 185
antialiasing: true
color: playerSelectorArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent" color: playerSelectorArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
border.width: 1 border.width: 1
@@ -1111,7 +1100,6 @@ Item {
radius: 20 radius: 20
x: parent.width - 40 - Theme.spacingM x: parent.width - 40 - Theme.spacingM
y: 130 y: 130
antialiasing: true
color: volumeButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent" color: volumeButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
border.width: 1 border.width: 1
@@ -1166,8 +1154,7 @@ Item {
height: 40 height: 40
radius: 20 radius: 20
x: parent.width - 40 - Theme.spacingM x: parent.width - 40 - Theme.spacingM
y: 240 y: 240
antialiasing: true
color: audioDevicesArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent" color: audioDevicesArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
border.width: 1 border.width: 1
@@ -1229,7 +1216,6 @@ Item {
background: Rectangle { background: Rectangle {
radius: Theme.cornerRadius * 2 radius: Theme.cornerRadius * 2
antialiasing: true
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
border.width: 1 border.width: 1
@@ -1290,7 +1276,6 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
color: Theme.surfaceContainerHigh color: Theme.surfaceContainerHigh
radius: Theme.cornerRadius radius: Theme.cornerRadius
antialiasing: true
} }
Rectangle { Rectangle {
@@ -1304,7 +1289,6 @@ Item {
bottomRightRadius: Theme.cornerRadius bottomRightRadius: Theme.cornerRadius
topLeftRadius: 0 topLeftRadius: 0
topRightRadius: 0 topRightRadius: 0
antialiasing: true
} }
Rectangle { Rectangle {
@@ -1312,7 +1296,6 @@ Item {
width: parent.width + 8 width: parent.width + 8
height: 8 height: 8
radius: Theme.cornerRadius radius: Theme.cornerRadius
antialiasing: true
y: { y: {
const ratio = defaultSink ? Math.min(1.0, defaultSink.audio.volume) : 0 const ratio = defaultSink ? Math.min(1.0, defaultSink.audio.volume) : 0
const travel = parent.height - height const travel = parent.height - height
@@ -1326,7 +1309,6 @@ Item {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
radius: Theme.cornerRadius radius: Theme.cornerRadius
antialiasing: true
color: Theme.onPrimary color: Theme.onPrimary
opacity: volumeSliderArea.pressed ? 0.16 : (volumeSliderArea.containsMouse ? 0.08 : 0) opacity: volumeSliderArea.pressed ? 0.16 : (volumeSliderArea.containsMouse ? 0.08 : 0)
visible: opacity > 0 visible: opacity > 0
@@ -1338,7 +1320,6 @@ Item {
width: 0 width: 0
height: 0 height: 0
radius: width / 2 radius: width / 2
antialiasing: true
color: Theme.onPrimary color: Theme.onPrimary
opacity: 0 opacity: 0

View File

@@ -13,83 +13,22 @@ Card {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
Item { DankCircularImage {
id: avatarContainer id: avatarContainer
property bool hasImage: profileImageLoader.status === Image.Ready
width: 77 width: 77
height: 77 height: 77
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
imageSource: {
Rectangle { if (PortalService.profileImage === "")
anchors.fill: parent return ""
radius: 36
color: Theme.primary if (PortalService.profileImage.startsWith("/"))
visible: !avatarContainer.hasImage return "file://" + PortalService.profileImage
StyledText { return PortalService.profileImage
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 { Column {
@@ -104,7 +43,7 @@ Card {
elide: Text.ElideRight elide: Text.ElideRight
width: parent.parent.parent.width - avatarContainer.width - Theme.spacingM * 3 width: parent.parent.parent.width - avatarContainer.width - Theme.spacingM * 3
} }
Row { Row {
spacing: Theme.spacingS spacing: Theme.spacingS
@@ -128,7 +67,7 @@ Card {
width: parent.parent.parent.parent.width - avatarContainer.width - Theme.spacingM * 3 - 16 - Theme.spacingS width: parent.parent.parent.parent.width - avatarContainer.width - Theme.spacingM * 3 - 16 - Theme.spacingS
} }
} }
Row { Row {
spacing: Theme.spacingS spacing: Theme.spacingS
@@ -141,7 +80,7 @@ Card {
StyledText { StyledText {
id: uptimeText id: uptimeText
property real availableWidth: parent.parent.parent.parent.width - avatarContainer.width - Theme.spacingM * 3 - 16 - Theme.spacingS property real availableWidth: parent.parent.parent.parent.width - avatarContainer.width - Theme.spacingM * 3 - 16 - Theme.spacingS
property real longTextWidth: { property real longTextWidth: {
const fontSize = Math.round(Theme.fontSizeSmall || 12) const fontSize = Math.round(Theme.fontSizeSmall || 12)

View File

@@ -218,7 +218,6 @@ Item {
width: parent.width width: parent.width
height: 1 height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1) color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
antialiasing: true
} }
GridLayout { GridLayout {
@@ -233,7 +232,6 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh color: Theme.surfaceContainerHigh
antialiasing: true
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
@@ -245,7 +243,6 @@ Item {
radius: 16 radius: 16
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
antialiasing: true
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -282,7 +279,6 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh color: Theme.surfaceContainerHigh
antialiasing: true
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
@@ -294,7 +290,6 @@ Item {
radius: 16 radius: 16
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
antialiasing: true
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -331,7 +326,6 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh color: Theme.surfaceContainerHigh
antialiasing: true
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
@@ -343,7 +337,6 @@ Item {
radius: 16 radius: 16
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
antialiasing: true
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -380,7 +373,6 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh color: Theme.surfaceContainerHigh
antialiasing: true
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
@@ -392,7 +384,6 @@ Item {
radius: 16 radius: 16
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
antialiasing: true
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -429,7 +420,6 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh color: Theme.surfaceContainerHigh
antialiasing: true
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
@@ -441,7 +431,6 @@ Item {
radius: 16 radius: 16
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
antialiasing: true
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -478,7 +467,6 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh color: Theme.surfaceContainerHigh
antialiasing: true
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
@@ -490,7 +478,6 @@ Item {
radius: 16 radius: 16
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
antialiasing: true
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -527,7 +514,6 @@ Item {
width: parent.width width: parent.width
height: 1 height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1) color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
antialiasing: true
} }
Column { Column {
@@ -554,8 +540,7 @@ Item {
width: (parent.width - Theme.spacingXS * 6) / 7 width: (parent.width - Theme.spacingXS * 6) / 7
height: parent.height height: parent.height
radius: Theme.cornerRadius radius: Theme.cornerRadius
antialiasing: true
property var dayDate: { property var dayDate: {
const date = new Date() const date = new Date()
date.setDate(date.getDate() + index) date.setDate(date.getDate() + index)

View File

@@ -20,11 +20,13 @@ Variants {
WlrLayershell.namespace: "quickshell:dock" WlrLayershell.namespace: "quickshell:dock"
readonly property bool isVertical: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
anchors { anchors {
top: SettingsData.dockPosition === SettingsData.Position.Top top: !isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top) : true
bottom: SettingsData.dockPosition === SettingsData.Position.Bottom bottom: !isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom) : true
left: true left: !isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Left)
right: true right: !isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Right)
} }
property var modelData: item property var modelData: item
@@ -35,12 +37,20 @@ Variants {
readonly property real widgetHeight: Math.max(20, 26 + SettingsData.dankBarInnerPadding * 0.6) 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 effectiveBarHeight: Math.max(widgetHeight + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding))
readonly property real barSpacing: { 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 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 samePosition = (SettingsData.dockPosition === SettingsData.dankBarPosition)
return (SettingsData.dankBarVisible && barIsHorizontal && samePosition) const dockIsHorizontal = !isVertical
? (SettingsData.dankBarSpacing + effectiveBarHeight + SettingsData.dankBarBottomGap) const dockIsVertical = isVertical
: 0
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
} }
readonly property real dockMargin: SettingsData.dockSpacing readonly property real dockMargin: SettingsData.dockSpacing
@@ -103,6 +113,83 @@ Variants {
item: dockMouseArea 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 { Item {
id: dockCore id: dockCore
anchors.fill: parent anchors.fill: parent
@@ -125,14 +212,31 @@ Variants {
id: dockMouseArea id: dockMouseArea
property real currentScreen: modelData ? modelData : dock.screen property real currentScreen: modelData ? modelData : dock.screen
property real screenWidth: currentScreen ? currentScreen.geometry.width : 1920 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 maxDockWidth: Math.min(screenWidth * 0.8, 1200)
property real maxDockHeight: Math.min(screenHeight * 0.8, 1200)
height: dock.reveal ? px(58 + SettingsData.dockSpacing + SettingsData.dockBottomGap) : 1 height: {
width: dock.reveal ? Math.min(dockBackground.implicitWidth + 32, maxDockWidth) : Math.min(Math.max(dockBackground.implicitWidth + 64, 200), screenWidth * 0.5) 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)
}
}
anchors { anchors {
top: SettingsData.dockPosition === SettingsData.Position.Bottom ? undefined : parent.top top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? undefined : parent.top) : undefined
bottom: SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined) : undefined
horizontalCenter: parent.horizontalCenter 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
} }
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.NoButton acceptedButtons: Qt.NoButton
@@ -144,6 +248,12 @@ Variants {
} }
} }
Behavior on width {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
Item { Item {
id: dockContainer id: dockContainer
@@ -151,7 +261,17 @@ Variants {
transform: Translate { transform: Translate {
id: dockSlide 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: { y: {
if (dock.isVertical) return 0
if (dock.reveal) return 0 if (dock.reveal) return 0
if (SettingsData.dockPosition === SettingsData.Position.Bottom) { if (SettingsData.dockPosition === SettingsData.Position.Bottom) {
return 60 return 60
@@ -160,6 +280,13 @@ Variants {
} }
} }
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
Behavior on y { Behavior on y {
NumberAnimation { NumberAnimation {
duration: 200 duration: 200
@@ -172,15 +299,20 @@ Variants {
id: dockBackground id: dockBackground
objectName: "dockBackground" objectName: "dockBackground"
anchors { anchors {
top: SettingsData.dockPosition === SettingsData.Position.Bottom ? undefined : parent.top top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? undefined : parent.top) : undefined
bottom: SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined) : undefined
horizontalCenter: parent.horizontalCenter 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
} }
anchors.topMargin: SettingsData.dockPosition === SettingsData.Position.Bottom ? 0 : barSpacing + 4 anchors.topMargin: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? 0 : barSpacing + 4) : 0
anchors.bottomMargin: SettingsData.dockPosition === SettingsData.Position.Bottom ? barSpacing + 1 : 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
implicitWidth: dockApps.implicitWidth + SettingsData.dockSpacing * 2 implicitWidth: dock.isVertical ? (dockApps.implicitHeight + SettingsData.dockSpacing * 2) : (dockApps.implicitWidth + SettingsData.dockSpacing * 2)
implicitHeight: dockApps.implicitHeight + SettingsData.dockSpacing * 2 implicitHeight: dock.isVertical ? (dockApps.implicitWidth + SettingsData.dockSpacing * 2) : (dockApps.implicitHeight + SettingsData.dockSpacing * 2)
width: implicitWidth width: implicitWidth
height: implicitHeight height: implicitHeight
@@ -199,69 +331,24 @@ Variants {
DockApps { DockApps {
id: dockApps id: dockApps
anchors.top: parent.top anchors.top: !dock.isVertical ? parent.top : undefined
anchors.bottom: parent.bottom anchors.bottom: !dock.isVertical ? parent.bottom : undefined
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: !dock.isVertical ? parent.horizontalCenter : undefined
anchors.topMargin: SettingsData.dockSpacing anchors.left: dock.isVertical ? parent.left : undefined
anchors.bottomMargin: SettingsData.dockSpacing 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
contextMenu: dockVariants.contextMenu contextMenu: dockVariants.contextMenu
groupByApp: dock.groupByApp 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,9 +13,10 @@ Item {
property bool requestDockShow: false property bool requestDockShow: false
property int pinnedAppCount: 0 property int pinnedAppCount: 0
property bool groupByApp: false property bool groupByApp: false
property bool isVertical: false
implicitWidth: row.width implicitWidth: isVertical ? appLayout.height : appLayout.width
implicitHeight: row.height implicitHeight: isVertical ? appLayout.width : appLayout.height
function movePinnedApp(fromIndex, toIndex) { function movePinnedApp(fromIndex, toIndex) {
if (fromIndex === toIndex) { if (fromIndex === toIndex) {
@@ -33,11 +34,16 @@ Item {
SessionData.setPinnedApps(currentPinned) SessionData.setPinnedApps(currentPinned)
} }
Row { Item {
id: row id: appLayout
spacing: 8
anchors.centerIn: parent anchors.centerIn: parent
height: 40 width: layoutFlow.width
height: layoutFlow.height
Flow {
id: layoutFlow
flow: root.isVertical ? Flow.TopToBottom : Flow.LeftToRight
spacing: 8
Repeater { Repeater {
id: repeater id: repeater
@@ -218,6 +224,7 @@ Item {
} }
} }
} }
}
} }
Connections { Connections {

View File

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

View File

@@ -0,0 +1,105 @@
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

@@ -0,0 +1,114 @@
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

@@ -0,0 +1,28 @@
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

@@ -0,0 +1,18 @@
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
}
}

79
Modules/Greetd/README.md Normal file
View File

@@ -0,0 +1,79 @@
# 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

@@ -0,0 +1,6 @@
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

@@ -0,0 +1,21 @@
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

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

View File

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

View File

@@ -44,10 +44,8 @@ Item {
powerDialogVisible = false 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() { function pickRandomFact() {
randomFact = facts[Math.floor(Math.random() * facts.length)] randomFact = Facts.getRandomFact()
} }
Component.onCompleted: { Component.onCompleted: {
@@ -180,93 +178,21 @@ Item {
spacing: Theme.spacingL spacing: Theme.spacingL
Layout.fillWidth: true Layout.fillWidth: true
Item { DankCircularImage {
id: avatarContainer
property bool hasImage: profileImageLoader.status === Image.Ready
Layout.preferredWidth: 60 Layout.preferredWidth: 60
Layout.preferredHeight: 60 Layout.preferredHeight: 60
imageSource: {
Rectangle { if (PortalService.profileImage === "") {
anchors.fill: parent return ""
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 { if (PortalService.profileImage.startsWith("/")) {
anchors.fill: parent return "file://" + PortalService.profileImage
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 { return PortalService.profileImage
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 { Rectangle {

View File

@@ -411,6 +411,78 @@ QtObject {
selectPrevious() selectPrevious()
event.accepted = true event.accepted = true
} }
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
if (!keyboardNavigationActive) {
keyboardNavigationActive = true
rebuildFlatNavigation()
selectedFlatIndex = 0
updateSelectedIdFromIndex()
if (listView) {
listView.keyboardActive = true
}
selectionVersion++
ensureVisible()
} else {
selectNext()
}
event.accepted = true
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
if (!keyboardNavigationActive) {
keyboardNavigationActive = true
rebuildFlatNavigation()
selectedFlatIndex = 0
updateSelectedIdFromIndex()
if (listView) {
listView.keyboardActive = true
}
selectionVersion++
ensureVisible()
} else if (selectedFlatIndex === 0) {
keyboardNavigationActive = false
if (listView) {
listView.keyboardActive = false
}
selectionVersion++
} else {
selectPrevious()
}
event.accepted = true
} else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
if (!keyboardNavigationActive) {
keyboardNavigationActive = true
rebuildFlatNavigation()
selectedFlatIndex = 0
updateSelectedIdFromIndex()
if (listView) {
listView.keyboardActive = true
}
selectionVersion++
ensureVisible()
} else {
selectNext()
}
event.accepted = true
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
if (!keyboardNavigationActive) {
keyboardNavigationActive = true
rebuildFlatNavigation()
selectedFlatIndex = 0
updateSelectedIdFromIndex()
if (listView) {
listView.keyboardActive = true
}
selectionVersion++
ensureVisible()
} else if (selectedFlatIndex === 0) {
keyboardNavigationActive = false
if (listView) {
listView.keyboardActive = false
}
selectionVersion++
} else {
selectPrevious()
}
event.accepted = true
} else if (keyboardNavigationActive) { } else if (keyboardNavigationActive) {
if (event.key === Qt.Key_Space) { if (event.key === Qt.Key_Space) {
toggleGroupExpanded() toggleGroupExpanded()

View File

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

View File

@@ -1702,6 +1702,77 @@ Item {
} }
} }
// Animation Settings
StyledRect {
width: parent.width
height: animationSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 0
Column {
id: animationSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "animation"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Animations"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: "Animation Speed"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: "Control the speed of animations throughout the interface"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
DankButtonGroup {
id: animationSpeedGroup
width: parent.width
model: ["None", "Shortest", "Short", "Medium", "Long"]
selectionMode: "single"
currentIndex: SettingsData.animationSpeed
onSelectionChanged: (index, selected) => {
if (selected) {
SettingsData.setAnimationSpeed(index)
}
}
}
}
}
}
// Lock Screen Settings // Lock Screen Settings
StyledRect { StyledRect {
width: parent.width width: parent.width

View File

@@ -120,6 +120,18 @@ Popup {
} else if (event.key === Qt.Key_Up) { } else if (event.key === Qt.Key_Up) {
root.selectPrevious() root.selectPrevious()
event.accepted = true event.accepted = true
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
root.selectNext()
event.accepted = true
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
root.selectPrevious()
event.accepted = true
} else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
root.selectNext()
event.accepted = true
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
root.selectPrevious()
event.accepted = true
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
if (root.keyboardNavigationActive) { if (root.keyboardNavigationActive) {
root.selectWidget() root.selectWidget()

View File

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

View File

@@ -18,7 +18,7 @@ A modern Wayland desktop shell built with [Quickshell](https://quickshell.org/)
<div align="center"> <div align="center">
<div style="max-width: 700px; margin: 0 auto;"> <div style="max-width: 700px; margin: 0 auto;">
https://github.com/user-attachments/assets/fd619c0e-6edc-457e-b3d6-5a5c3bae7173 https://github.com/user-attachments/assets/9b99dbbf-42d3-44ab-83b6-fae6c2aa3cc0
</div> </div>
</div> </div>
@@ -43,7 +43,7 @@ https://github.com/user-attachments/assets/fd619c0e-6edc-457e-b3d6-5a5c3bae7173
### Control Center ### Control Center
<img width="600" alt="Control Center" src="https://github.com/user-attachments/assets/98889bd8-55d2-44c7-b278-75ca49c596fa" /> <img width="600" alt="Control Center" src="https://github.com/user-attachments/assets/732c30de-5f4a-4a2b-a995-c8ab656cecd5" />
### System Monitor ### System Monitor
@@ -440,6 +440,12 @@ bindl = , XF86MonBrightnessDown, exec, dms ipc call brightness decrement 5 ""
bind = SUPERSHIFT, N, exec, dms ipc call night toggle 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 ## 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). 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

@@ -21,10 +21,42 @@ Singleton {
systemProfileCheckProcess.running = true 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) { function setProfileImage(imagePath) {
profileImage = imagePath profileImage = imagePath
if (accountsServiceAvailable && imagePath) { if (accountsServiceAvailable) {
setSystemProfileImage(imagePath) if (imagePath) {
setSystemProfileImage(imagePath)
} else {
setSystemProfileImage("")
}
} }
} }
@@ -51,11 +83,12 @@ Singleton {
} }
function setSystemProfileImage(imagePath) { function setSystemProfileImage(imagePath) {
if (!accountsServiceAvailable || !imagePath) { if (!accountsServiceAvailable) {
return return
} }
const script = `dbus-send --system --print-reply --dest=org.freedesktop.Accounts /org/freedesktop/Accounts/User$(id -u) org.freedesktop.Accounts.User.SetIconFile string:'${imagePath}'` 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}'`
systemProfileSetProcess.command = ["bash", "-c", script] systemProfileSetProcess.command = ["bash", "-c", script]
systemProfileSetProcess.running = true systemProfileSetProcess.running = true
@@ -123,6 +156,29 @@ 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 { Process {
id: settingsPortalCheckProcess 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"] 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"]

View File

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

View File

@@ -16,6 +16,9 @@ Rectangle {
property bool enableFuzzySearch: false property bool enableFuzzySearch: false
property int popupWidthOffset: 0 property int popupWidthOffset: 0
property int maxPopupHeight: 400 property int maxPopupHeight: 400
property bool openUpwards: false
property int popupWidth: 0
property bool alignPopupRight: false
signal valueChanged(string value) signal valueChanged(string value)
@@ -102,10 +105,33 @@ Rectangle {
return return
} }
const pos = dropdown.mapToItem(Overlay.overlay, 0, dropdown.height + 4) if (root.openUpwards || root.alignPopupRight) {
popup.x = pos.x - (root.popupWidthOffset / 2) popup.open()
popup.y = pos.y Qt.callLater(() => {
popup.open() 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()
}
} }
} }
@@ -213,7 +239,7 @@ Rectangle {
} }
parent: Overlay.overlay parent: Overlay.overlay
width: dropdown.width + root.popupWidthOffset width: root.popupWidth > 0 ? root.popupWidth : (dropdown.width + root.popupWidthOffset)
height: Math.min(root.maxPopupHeight, (root.enableFuzzySearch ? 54 : 0) + Math.min(filteredOptions.length, 10) * 36 + 16) height: Math.min(root.maxPopupHeight, (root.enableFuzzySearch ? 54 : 0) + Math.min(filteredOptions.length, 10) * 36 + 16)
padding: 0 padding: 0
modal: true modal: true
@@ -266,6 +292,21 @@ Rectangle {
Keys.onUpPressed: selectPrevious() Keys.onUpPressed: selectPrevious()
Keys.onReturnPressed: selectCurrent() Keys.onReturnPressed: selectCurrent()
Keys.onEnterPressed: selectCurrent() Keys.onEnterPressed: selectCurrent()
Keys.onPressed: event => {
if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
selectNext()
event.accepted = true
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
selectPrevious()
event.accepted = true
} else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
selectNext()
event.accepted = true
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
selectPrevious()
event.accepted = true
}
}
} }
} }
@@ -323,8 +364,9 @@ Rectangle {
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: isCurrentValue ? Theme.primary : Theme.surfaceText color: isCurrentValue ? Theme.primary : Theme.surfaceText
font.weight: isCurrentValue ? Font.Medium : Font.Normal font.weight: isCurrentValue ? Font.Medium : Font.Normal
width: parent.parent.width - parent.x - Theme.spacingS width: root.popupWidth > 0 ? undefined : (parent.parent.width - parent.x - Theme.spacingS)
elide: Text.ElideRight elide: root.popupWidth > 0 ? Text.ElideNone : Text.ElideRight
wrapMode: Text.NoWrap
} }
} }

View File

@@ -18,7 +18,7 @@ PanelWindow {
property real triggerWidth: 40 property real triggerWidth: 40
property string triggerSection: "" property string triggerSection: ""
property string positioning: "center" property string positioning: "center"
property int animationDuration: Theme.mediumDuration property int animationDuration: Theme.shortDuration
property var animationEasing: Theme.emphasizedEasing property var animationEasing: Theme.emphasizedEasing
property bool shouldBeVisible: false property bool shouldBeVisible: false
@@ -57,16 +57,8 @@ PanelWindow {
} }
color: "transparent" color: "transparent"
WlrLayershell.layer: WlrLayershell.Top // if set to overlay -> virtual keyboards can be stuck under popup WlrLayershell.layer: WlrLayershell.Top
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
// WlrLayershell.keyboardFocus should be set to Exclusive,
// if popup contains input fields and does NOT create new popups/modals
// with input fields.
// With OnDemand virtual keyboards can't send input to popup
// If set to Exclusive AND this popup creates other popups, that also have
// input fields -> they can't get keyboard focus, because the parent popup
// already took the lock
WlrLayershell.keyboardFocus: shouldBeVisible ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None WlrLayershell.keyboardFocus: shouldBeVisible ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
anchors { anchors {
@@ -116,12 +108,12 @@ PanelWindow {
} }
} }
width: popupWidth width: Math.round(popupWidth)
height: popupHeight height: Math.round(popupHeight)
x: calculatedX x: Math.round(calculatedX)
y: calculatedY y: Math.round(calculatedY)
opacity: shouldBeVisible ? 1 : 0 opacity: shouldBeVisible ? 1 : 0
scale: shouldBeVisible ? 1 : 0.9 scale: 1
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
@@ -130,12 +122,7 @@ PanelWindow {
} }
} }
Behavior on scale {
NumberAnimation {
duration: animationDuration
easing.type: animationEasing
}
}
Loader { Loader {
id: contentLoader id: contentLoader

View File

@@ -4,7 +4,6 @@ import qs.Common
Rectangle { Rectangle {
color: "transparent" color: "transparent"
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
antialiasing: true
readonly property var standardAnimation: { readonly property var standardAnimation: {
"duration": Appearance.anim.durations.normal, "duration": Appearance.anim.durations.normal,

View File

@@ -32,7 +32,6 @@ Text {
elide: Text.ElideRight elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
antialiasing: true antialiasing: true
renderType: Text.NativeRendering
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {

635
shell.qml
View File

@@ -2,644 +2,23 @@
//@ pragma UseQApplication //@ pragma UseQApplication
import QtQuick import QtQuick
import Quickshell 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 { ShellRoot {
id: root id: root
Component.onCompleted: { readonly property bool runGreeter: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
PortalService.init()
// Initialize DisplayService night mode functionality
DisplayService.nightModeEnabled
// Initialize WallpaperCyclingService
WallpaperCyclingService.cyclingActive
// Initialize PluginService by accessing its properties
PluginService.pluginDirectory
}
WallpaperBackground {}
Lock {
id: lock
anchors.fill: parent
}
Loader { Loader {
id: dankBarLoader id: dmsShellLoader
asynchronous: false asynchronous: false
sourceComponent: DMSShell{}
property var currentPosition: SettingsData.dankBarPosition active: !root.runGreeter
sourceComponent: DankBar {
onColorPickerRequested: colorPickerModal.show()
}
onCurrentPositionChanged: {
const component = sourceComponent
sourceComponent = null
Qt.callLater(() => {
sourceComponent = component
})
}
} }
Loader { Loader {
id: dockLoader id: dmsGreeterLoader
active: true
asynchronous: false asynchronous: false
sourceComponent: DMSGreeter{}
property var currentPosition: SettingsData.dockPosition active: root.runGreeter
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
}
} }
} }