1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-02 02:22:06 -04:00

Compare commits

...

9 Commits

Author SHA1 Message Date
purian23
177a4c4095 (greeter): PAM auth improvements and defaults update 2026-03-10 15:02:26 -04:00
lpv
63df19ab78 dock: restore Hyprland special workspace windows on click (#1924)
* dock: restore Hyprland special workspace windows on click

* settings: add dock special workspace restore key to spec
2026-03-10 12:55:36 -04:00
Adarsh219
54e0eb5979 feat: Add Zed editor theming support (#1954)
* feat: Add Zed editor theming support

* fix formatting and switch to CONFIG_DIR
2026-03-10 12:03:01 -04:00
bbedward
185284d422 fix(lock): restore login config fallback 2026-03-10 11:33:44 -04:00
bbedward
ce240405d9 system tray: fix shadow consistency
fixes #1946
2026-03-10 11:10:18 -04:00
Marcin Jahn
58b700ed0d fix(shell): cover edge cases of compact focused app widget (#1918)
Fixes two cases:

- some apps (e.g., Zen browser use the "—" character at the end of
  webpage name)
- in compact mode, when app has only appName, and not window name, we
  should display the appName to avoid empty title.
2026-03-10 10:49:28 -04:00
Vladimir
d436fa4920 fix(quickshell): stabilize control center numeric widths (#1943) 2026-03-10 10:48:13 -04:00
Augusto César Dias
d58486193e feature(notification): show notification only on current focused display (#1923) 2026-03-10 10:46:04 -04:00
bbedward
e9404eb9b6 i18n: add russian 2026-03-10 10:43:46 -04:00
40 changed files with 17933 additions and 1590 deletions

View File

@@ -1249,7 +1249,17 @@ func extractGreeterWrapperFromCommand(command string) string {
if len(tokens) == 0 {
return ""
}
return strings.Trim(tokens[0], "\"")
wrapper := strings.Trim(tokens[0], "\"")
if wrapper == "" {
return ""
}
if len(tokens) > 1 {
next := strings.Trim(tokens[1], "\"")
if next != "" && (filepath.Base(wrapper) == "bash" || filepath.Base(wrapper) == "sh") && strings.Contains(filepath.Base(next), "dms-greeter") {
return fmt.Sprintf("%s (script: %s)", wrapper, next)
}
}
return wrapper
}
func extractGreeterPathOverrideFromCommand(command string) string {
@@ -1335,6 +1345,26 @@ func packageInstallHint() string {
}
}
func systemPamManagerRemediationHint() string {
osInfo, err := distros.GetOSInfo()
if err != nil {
return "Disable it in your PAM manager (authselect/pam-auth-update) or in the included PAM stack to force password-only greeter login."
}
config, exists := distros.Registry[osInfo.Distribution.ID]
if !exists {
return "Disable it in your PAM manager (authselect/pam-auth-update) or in the included PAM stack to force password-only greeter login."
}
switch config.Family {
case distros.FamilyFedora:
return "Disable it in authselect to force password-only greeter login."
case distros.FamilyDebian, distros.FamilyUbuntu:
return "Disable it in pam-auth-update to force password-only greeter login."
default:
return "Disable it in your distro PAM manager (authselect/pam-auth-update) or in the included PAM stack to force password-only greeter login."
}
}
func isPackageOnlyGreeterDistro() bool {
osInfo, err := distros.GetOSInfo()
if err != nil {
@@ -1568,22 +1598,56 @@ func checkGreeterStatus() error {
fmt.Println(" ⚠ Legacy unmanaged DMS PAM lines detected. Run 'dms greeter sync' to normalize.")
allGood = false
}
enableFprintToggle, enableU2fToggle := false, false
if enableFprint, enableU2f, settingsErr := greeter.ReadGreeterAuthToggles(homeDir); settingsErr == nil {
enableFprintToggle = enableFprint
enableU2fToggle = enableU2f
} else {
fmt.Printf(" Could not read greeter auth toggles from settings: %v\n", settingsErr)
}
includedFprintFile := greeter.DetectIncludedPamModule(string(pamData), "pam_fprintd.so")
showIncludedFprintNotice := false
if includedFprintFile != "" {
if enableFprint, _, settingsErr := greeter.ReadGreeterAuthToggles(homeDir); settingsErr == nil && enableFprint {
showIncludedFprintNotice = greeter.FingerprintAuthAvailableForCurrentUser()
includedU2fFile := greeter.DetectIncludedPamModule(string(pamData), "pam_u2f.so")
fprintAvailableForCurrentUser := greeter.FingerprintAuthAvailableForCurrentUser()
if managedFprint && includedFprintFile != "" {
fmt.Printf(" ⚠ pam_fprintd found in both DMS managed block and %s.\n", includedFprintFile)
fmt.Println(" Double fingerprint auth detected — run 'dms greeter sync' to resolve.")
allGood = false
}
if managedU2f && includedU2fFile != "" {
fmt.Printf(" ⚠ pam_u2f found in both DMS managed block and %s.\n", includedU2fFile)
fmt.Println(" Double security-key auth detected — run 'dms greeter sync' to resolve.")
allGood = false
}
if includedFprintFile != "" && !managedFprint {
if enableFprintToggle {
fmt.Printf(" Fingerprint auth is enabled via included %s.\n", includedFprintFile)
if fprintAvailableForCurrentUser {
fmt.Println(" DMS toggle is enabled, and effective auth is coming from the included PAM stack.")
} else {
fmt.Println(" No enrolled fingerprints detected for the current user; password auth remains the effective path.")
}
} else {
if fprintAvailableForCurrentUser {
fmt.Printf(" Fingerprint auth is active via included %s while DMS fingerprint toggle is off.\n", includedFprintFile)
fmt.Println(" Password login will work but may be delayed while the fingerprint module runs first.")
fmt.Printf(" To eliminate the delay, %s\n", systemPamManagerRemediationHint())
} else {
fmt.Printf(" pam_fprintd is present via included %s, but no enrolled fingerprints were detected for user %s.\n", includedFprintFile, currentUser.Username)
fmt.Println(" Password auth remains the effective login path.")
}
}
}
if managedFprint {
if includedFprintFile != "" {
fmt.Printf(" ⚠ pam_fprintd found in both DMS managed block and %s.\n", includedFprintFile)
fmt.Println(" Double fingerprint auth detected — run 'dms greeter sync' to resolve.")
allGood = false
if includedU2fFile != "" && !managedU2f {
if enableU2fToggle {
fmt.Printf(" Security-key auth is enabled via included %s.\n", includedU2fFile)
fmt.Println(" DMS toggle is enabled, but effective auth is coming from the included PAM stack.")
} else {
fmt.Printf(" ⚠ Security-key auth is active via included %s while DMS security-key toggle is off.\n", includedU2fFile)
fmt.Printf(" %s\n", systemPamManagerRemediationHint())
}
} else if includedFprintFile != "" && showIncludedFprintNotice {
fmt.Printf(" Fingerprint auth is enabled via included %s.\n", includedFprintFile)
fmt.Println(" The DMS toggle only controls the managed block; disable fingerprint in authselect/pam-auth-update for password-only greeter login.")
}
}

View File

@@ -1424,9 +1424,30 @@ func FingerprintAuthAvailableForCurrentUser() bool {
return FingerprintAuthAvailableForUser(username)
}
func pamManagerHintForCurrentDistro() string {
osInfo, err := distros.GetOSInfo()
if err != nil {
return "Disable it in your PAM manager (authselect/pam-auth-update) or in the included PAM stack to force password-only greeter login."
}
config, exists := distros.Registry[osInfo.Distribution.ID]
if !exists {
return "Disable it in your PAM manager (authselect/pam-auth-update) or in the included PAM stack to force password-only greeter login."
}
switch config.Family {
case distros.FamilyFedora:
return "Disable it in authselect to force password-only greeter login."
case distros.FamilyDebian, distros.FamilyUbuntu:
return "Disable it in pam-auth-update to force password-only greeter login."
default:
return "Disable it in your distro PAM manager (authselect/pam-auth-update) or in the included PAM stack to force password-only greeter login."
}
}
func syncGreeterPamConfig(homeDir string, logFunc func(string), sudoPassword string, forceAuth bool) error {
var wantFprint, wantU2f bool
fprintToggleEnabled := forceAuth
u2fToggleEnabled := forceAuth
if forceAuth {
wantFprint = pamModuleExists("pam_fprintd.so")
wantU2f = pamModuleExists("pam_u2f.so")
@@ -1436,6 +1457,7 @@ func syncGreeterPamConfig(homeDir string, logFunc func(string), sudoPassword str
return err
}
fprintToggleEnabled = settings.GreeterEnableFprint
u2fToggleEnabled = settings.GreeterEnableU2f
fprintModule := pamModuleExists("pam_fprintd.so")
u2fModule := pamModuleExists("pam_u2f.so")
wantFprint = settings.GreeterEnableFprint && fprintModule
@@ -1464,14 +1486,43 @@ func syncGreeterPamConfig(homeDir string, logFunc func(string), sudoPassword str
content, _ = stripLegacyGreeterPamLines(content)
includedFprintFile := DetectIncludedPamModule(content, "pam_fprintd.so")
includedU2fFile := DetectIncludedPamModule(content, "pam_u2f.so")
fprintAvailableForCurrentUser := FingerprintAuthAvailableForCurrentUser()
if wantFprint && includedFprintFile != "" {
logFunc("⚠ pam_fprintd already present in included " + includedFprintFile + " (managed by authselect/pam-auth-update). Skipping DMS fprint block to avoid double-fingerprint auth.")
wantFprint = false
}
showIncludedFprintNotice := fprintToggleEnabled && FingerprintAuthAvailableForCurrentUser()
if !wantFprint && includedFprintFile != "" && showIncludedFprintNotice {
logFunc(" Fingerprint auth is still enabled via included " + includedFprintFile + ".")
logFunc(" Disable fingerprint in your system PAM manager (authselect/pam-auth-update) to force password-only greeter login.")
if wantU2f && includedU2fFile != "" {
logFunc("⚠ pam_u2f already present in included " + includedU2fFile + " (managed by authselect/pam-auth-update). Skipping DMS U2F block to avoid double security-key auth.")
wantU2f = false
}
if !wantFprint && includedFprintFile != "" {
if fprintToggleEnabled {
logFunc(" Fingerprint auth is still enabled via included " + includedFprintFile + ".")
if fprintAvailableForCurrentUser {
logFunc(" DMS toggle is enabled, and effective auth is provided by the included PAM stack.")
} else {
logFunc(" No enrolled fingerprints detected for the current user; password auth remains the effective path.")
}
} else {
if fprintAvailableForCurrentUser {
logFunc(" Fingerprint auth is active via included " + includedFprintFile + " while DMS fingerprint toggle is off.")
logFunc(" Password login will work but may be delayed while the fingerprint module runs first.")
logFunc(" To eliminate the delay, " + pamManagerHintForCurrentDistro())
} else {
logFunc(" pam_fprintd is present via included " + includedFprintFile + ", but no enrolled fingerprints were detected for the current user.")
logFunc(" Password auth remains the effective login path.")
}
}
}
if !wantU2f && includedU2fFile != "" {
if u2fToggleEnabled {
logFunc(" Security-key auth is still enabled via included " + includedU2fFile + ".")
logFunc(" DMS toggle is enabled, but effective auth is provided by the included PAM stack.")
} else {
logFunc("⚠ Security-key auth is active via included " + includedU2fFile + " while DMS security-key toggle is off.")
logFunc(" " + pamManagerHintForCurrentDistro())
}
}
if wantFprint || wantU2f {

View File

@@ -71,6 +71,7 @@ var templateRegistry = []TemplateDef{
{ID: "kcolorscheme", ConfigFile: "kcolorscheme.toml", RunUnconditionally: true},
{ID: "vscode", Kind: TemplateKindVSCode},
{ID: "emacs", Commands: []string{"emacs"}, ConfigFile: "emacs.toml", Kind: TemplateKindEmacs},
{ID: "zed", Commands: []string{"zed"}, ConfigFile: "zed.toml"},
}
func (c *ColorMode) GTKTheme() string {

View File

@@ -474,11 +474,13 @@ Singleton {
property bool matugenTemplateKcolorscheme: true
property bool matugenTemplateVscode: true
property bool matugenTemplateEmacs: true
property bool matugenTemplateZed: true
property bool showDock: false
property bool dockAutoHide: false
property bool dockSmartAutoHide: false
property bool dockGroupByApp: false
property bool dockRestoreSpecialWorkspaceOnClick: false
property bool dockOpenOnOverview: false
property int dockPosition: SettingsData.Position.Bottom
property real dockSpacing: 4
@@ -549,6 +551,7 @@ Singleton {
property bool notificationHistorySaveNormal: true
property bool notificationHistorySaveCritical: true
property var notificationRules: []
property bool notificationFocusedMonitor: false
property bool osdAlwaysShowValue: false
property int osdPosition: SettingsData.Position.BottomCenter

View File

@@ -1551,7 +1551,7 @@ Singleton {
if (typeof SettingsData !== "undefined") {
const skipTemplates = [];
if (!SettingsData.runDmsMatugenTemplates) {
skipTemplates.push("gtk", "nvim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "zenbrowser", "vesktop", "equibop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode", "emacs");
skipTemplates.push("gtk", "nvim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "zenbrowser", "vesktop", "equibop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode", "emacs", "zed");
} else {
if (!SettingsData.matugenTemplateGtk)
skipTemplates.push("gtk");
@@ -1595,6 +1595,8 @@ Singleton {
skipTemplates.push("vscode");
if (!SettingsData.matugenTemplateEmacs)
skipTemplates.push("emacs");
if (!SettingsData.matugenTemplateZed)
skipTemplates.push("zed");
}
if (skipTemplates.length > 0) {
args.push("--skip-templates", skipTemplates.join(","));

View File

@@ -289,11 +289,13 @@ var SPEC = {
matugenTemplateKcolorscheme: { def: true },
matugenTemplateVscode: { def: true },
matugenTemplateEmacs: { def: true },
matugenTemplateZed: { def: true },
showDock: { def: false },
dockAutoHide: { def: false },
dockSmartAutoHide: { def: false },
dockGroupByApp: { def: false },
dockRestoreSpecialWorkspaceOnClick: { def: false },
dockOpenOnOverview: { def: false },
dockPosition: { def: 1 },
dockSpacing: { def: 4 },
@@ -363,6 +365,7 @@ var SPEC = {
notificationHistorySaveNormal: { def: true },
notificationHistorySaveCritical: { def: true },
notificationRules: { def: [] },
notificationFocusedMonitor: { def: false },
osdAlwaysShowValue: { def: false },
osdPosition: { def: 5 },

View File

@@ -313,7 +313,7 @@ Item {
}
Variants {
model: SettingsData.getFilteredScreens("notifications")
model: SettingsData.notificationFocusedMonitor ? Quickshell.screens : SettingsData.getFilteredScreens("notifications")
delegate: NotificationPopupManager {
modelData: item

View File

@@ -390,10 +390,11 @@ BasePill {
anchors.top: parent.top
}
StyledText {
NumericText {
id: audioPercentV
visible: root.showAudioPercent
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
reserveText: "100%"
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor
anchors.horizontalCenter: parent.horizontalCenter
@@ -416,10 +417,11 @@ BasePill {
anchors.top: parent.top
}
StyledText {
NumericText {
id: micPercentV
visible: root.showMicPercent
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
reserveText: "100%"
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor
anchors.horizontalCenter: parent.horizontalCenter
@@ -442,10 +444,11 @@ BasePill {
anchors.top: parent.top
}
StyledText {
NumericText {
id: brightnessPercentV
visible: root.showBrightnessPercent
text: Math.round(getBrightness() * 100) + "%"
reserveText: "100%"
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor
anchors.horizontalCenter: parent.horizontalCenter
@@ -536,7 +539,8 @@ BasePill {
}
Rectangle {
width: audioIcon.implicitWidth + (root.showAudioPercent ? audioPercent.implicitWidth : 0) + 4
width: audioIcon.implicitWidth + (root.showAudioPercent ? audioPercent.reservedWidth : 0) + 4
implicitWidth: width
height: root.widgetThickness - root.horizontalPadding * 2
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
@@ -552,20 +556,23 @@ BasePill {
anchors.leftMargin: 2
}
StyledText {
NumericText {
id: audioPercent
visible: root.showAudioPercent
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
reserveText: "100%"
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor
anchors.verticalCenter: parent.verticalCenter
anchors.left: audioIcon.right
anchors.leftMargin: 2
width: reservedWidth
}
}
Rectangle {
width: micIcon.implicitWidth + (root.showMicPercent ? micPercent.implicitWidth : 0) + 4
width: micIcon.implicitWidth + (root.showMicPercent ? micPercent.reservedWidth : 0) + 4
implicitWidth: width
height: root.widgetThickness - root.horizontalPadding * 2
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
@@ -581,20 +588,22 @@ BasePill {
anchors.leftMargin: 2
}
StyledText {
NumericText {
id: micPercent
visible: root.showMicPercent
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
reserveText: "100%"
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor
anchors.verticalCenter: parent.verticalCenter
anchors.left: micIcon.right
anchors.leftMargin: 2
width: reservedWidth
}
}
Rectangle {
width: brightnessIcon.implicitWidth + (root.showBrightnessPercent ? brightnessPercent.implicitWidth : 0) + 4
width: brightnessIcon.implicitWidth + (root.showBrightnessPercent ? brightnessPercent.reservedWidth : 0) + 4
height: root.widgetThickness - root.horizontalPadding * 2
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
@@ -610,15 +619,17 @@ BasePill {
anchors.leftMargin: 2
}
StyledText {
NumericText {
id: brightnessPercent
visible: root.showBrightnessPercent
text: Math.round(getBrightness() * 100) + "%"
reserveText: "100%"
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor
anchors.verticalCenter: parent.verticalCenter
anchors.left: brightnessIcon.right
anchors.leftMargin: 2
width: reservedWidth
}
}

View File

@@ -211,16 +211,17 @@ BasePill {
text: {
const title = activeWindow && activeWindow.title ? activeWindow.title : "";
const appName = appText.text;
if (compactMode && title === appName) {
return title;
}
if (!title || !appName) {
return title;
}
if (title.endsWith(" - " + appName)) {
return title.substring(0, title.length - (" - " + appName).length);
}
if (title.endsWith(appName)) {
return title.substring(0, title.length - appName.length).replace(/ - $/, "");
return title.substring(0, title.length - appName.length).replace(/ (-|—) $/, "");
}
return title;

View File

@@ -1,5 +1,4 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Hyprland
import Quickshell.Services.SystemTray
@@ -162,6 +161,23 @@ BasePill {
return 0;
}
readonly property string autoBarShadowDirection: {
const edge = root.axis?.edge;
switch (edge) {
case "top":
return "top";
case "bottom":
return "bottom";
case "left":
return "left";
case "right":
return "right";
default:
return "bottom";
}
}
readonly property string effectiveShadowDirection: Theme.elevationLightDirection === "autoBar" ? autoBarShadowDirection : Theme.elevationLightDirection
property bool menuOpen: false
property var currentTrayMenu: null
@@ -940,13 +956,6 @@ BasePill {
}
})(), overflowMenu.dpr)
readonly property var elev: Theme.elevationLevel2
property real shadowBlurPx: elev && elev.blurPx !== undefined ? elev.blurPx : 8
property real shadowSpreadPx: elev && elev.spreadPx !== undefined ? elev.spreadPx : 0
property real shadowBaseAlpha: elev && elev.alpha !== undefined ? elev.alpha : 0.25
readonly property real popupSurfaceAlpha: Theme.popupTransparency
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha))
opacity: root.menuOpen ? 1 : 0
scale: root.menuOpen ? 1 : 0.85
@@ -967,19 +976,14 @@ BasePill {
ElevationShadow {
id: bgShadowLayer
anchors.fill: parent
level: menuContainer.elev
fallbackOffset: 4
shadowBlurPx: menuContainer.shadowBlurPx
shadowSpreadPx: menuContainer.shadowSpreadPx
shadowColor: {
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
return Theme.withAlpha(baseColor, menuContainer.effectiveShadowAlpha);
}
level: Theme.elevationLevel3
direction: root.effectiveShadowDirection
fallbackOffset: 6
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
targetRadius: Theme.cornerRadius
sourceRect.antialiasing: true
sourceRect.smooth: true
shadowEnabled: Theme.elevationEnabled
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
layer.smooth: true
layer.textureSize: Qt.size(Math.round(width * overflowMenu.dpr * 2), Math.round(height * overflowMenu.dpr * 2))
layer.textureMirroring: ShaderEffectSource.MirrorVertically
@@ -1402,13 +1406,6 @@ BasePill {
}
})(), menuWindow.dpr)
readonly property var elev: Theme.elevationLevel2
property real shadowBlurPx: elev && elev.blurPx !== undefined ? elev.blurPx : 8
property real shadowSpreadPx: elev && elev.spreadPx !== undefined ? elev.spreadPx : 0
property real shadowBaseAlpha: elev && elev.alpha !== undefined ? elev.alpha : 0.25
readonly property real popupSurfaceAlpha: Theme.popupTransparency
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha))
opacity: menuRoot.showMenu ? 1 : 0
scale: menuRoot.showMenu ? 1 : 0.85
@@ -1429,18 +1426,13 @@ BasePill {
ElevationShadow {
id: menuBgShadowLayer
anchors.fill: parent
level: menuContainer.elev
fallbackOffset: 4
shadowBlurPx: menuContainer.shadowBlurPx
shadowSpreadPx: menuContainer.shadowSpreadPx
shadowColor: {
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
return Theme.withAlpha(baseColor, menuContainer.effectiveShadowAlpha);
}
level: Theme.elevationLevel3
direction: root.effectiveShadowDirection
fallbackOffset: 6
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
targetRadius: Theme.cornerRadius
sourceRect.antialiasing: true
shadowEnabled: Theme.elevationEnabled
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
layer.smooth: true
layer.textureSize: Qt.size(Math.round(width * menuWindow.dpr), Math.round(height * menuWindow.dpr))
layer.textureMirroring: ShaderEffectSource.MirrorVertically

View File

@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
@@ -133,6 +134,40 @@ Item {
function getGroupedToplevels() {
return appData?.allWindows?.map(w => w.toplevel).filter(t => t !== null) || [];
}
function getHyprToplevelForWayland(waylandToplevel) {
if (!waylandToplevel || !CompositorService.isHyprland || !Hyprland.toplevels)
return null;
const hyprToplevels = Array.from(Hyprland.toplevels.values);
for (let i = 0; i < hyprToplevels.length; i++) {
if (hyprToplevels[i].wayland === waylandToplevel)
return hyprToplevels[i];
}
return null;
}
function getSpecialWorkspaceName(waylandToplevel) {
const hyprToplevel = getHyprToplevelForWayland(waylandToplevel);
if (!hyprToplevel)
return "";
const wsName = String(hyprToplevel.lastIpcObject?.workspace?.name || hyprToplevel.workspace?.name || "");
if (!wsName.startsWith("special:"))
return "";
return wsName.slice("special:".length);
}
function restoreSpecialWorkspaceWindow(waylandToplevel) {
if (!SettingsData.dockRestoreSpecialWorkspaceOnClick || !CompositorService.isHyprland || !waylandToplevel)
return false;
const specialName = getSpecialWorkspaceName(waylandToplevel);
if (!specialName)
return false;
Hyprland.dispatch("togglespecialworkspace " + specialName);
Qt.callLater(() => waylandToplevel.activate());
return true;
}
onIsHoveredChanged: {
if (mouseArea.pressed || dragging)
return;
@@ -276,8 +311,11 @@ Item {
break;
case "window":
const windowToplevel = getToplevelObject();
if (windowToplevel)
if (windowToplevel) {
if (restoreSpecialWorkspaceWindow(windowToplevel))
return;
windowToplevel.activate();
}
break;
case "grouped":
if (appData.windowCount === 0) {
@@ -300,8 +338,11 @@ Item {
SessionService.launchDesktopEntry(groupedEntry);
} else if (appData.windowCount === 1) {
const groupedToplevel = getToplevelObject();
if (groupedToplevel)
if (groupedToplevel) {
if (restoreSpecialWorkspaceWindow(groupedToplevel))
return;
groupedToplevel.activate();
}
} else if (contextMenu) {
const shouldHidePin = appData.appId === "org.quickshell";
contextMenu.showForButton(root, appData, root.height + 25, shouldHidePin, cachedDesktopEntry, parentDockScreen, dockApps);

View File

@@ -35,8 +35,8 @@ Item {
property bool pendingPasswordResponse: false
property bool passwordSubmitRequested: false
property bool cancelingExternalAuthForPassword: false
property int defaultAuthTimeoutMs: 12000
property int externalAuthTimeoutMs: 45000
property int defaultAuthTimeoutMs: 10000
property int externalAuthTimeoutMs: 36000
property int memoryFlushDelayMs: 120
property string pendingLaunchCommand: ""
property var pendingLaunchEnv: []
@@ -50,9 +50,12 @@ Item {
property string faillockConfigText: ""
property bool greeterWallpaperOverrideExists: false
property string externalAuthAutoStartedForUser: ""
property int passwordSessionTransitionRetryCount: 0
property int maxPasswordSessionTransitionRetries: 2
readonly property bool greeterPamHasFprint: pamModuleEnabled(greetdPamText, "pam_fprintd") || (greetdPamText.includes("system-auth") && pamModuleEnabled(systemAuthPamText, "pam_fprintd")) || (greetdPamText.includes("common-auth") && pamModuleEnabled(commonAuthPamText, "pam_fprintd")) || (greetdPamText.includes("password-auth") && pamModuleEnabled(passwordAuthPamText, "pam_fprintd"))
readonly property bool greeterPamHasU2f: pamModuleEnabled(greetdPamText, "pam_u2f") || (greetdPamText.includes("system-auth") && pamModuleEnabled(systemAuthPamText, "pam_u2f")) || (greetdPamText.includes("common-auth") && pamModuleEnabled(commonAuthPamText, "pam_u2f")) || (greetdPamText.includes("password-auth") && pamModuleEnabled(passwordAuthPamText, "pam_u2f"))
readonly property bool greeterExternalAuthAvailable: (greeterPamHasFprint && GreetdSettings.greeterEnableFprint) || (greeterPamHasU2f && GreetdSettings.greeterEnableU2f)
readonly property bool greeterPamHasExternalAuth: greeterPamHasFprint || greeterPamHasU2f
function initWeatherService() {
if (weatherInitialized)
@@ -208,6 +211,13 @@ Item {
authFeedbackMessage = "";
}
function resetPasswordSessionTransition(clearSubmitRequest) {
cancelingExternalAuthForPassword = false;
passwordSessionTransitionRetryCount = 0;
if (clearSubmitRequest)
passwordSubmitRequested = false;
}
Connections {
target: GreetdSettings
function onSettingsLoadedChanged() {
@@ -348,8 +358,7 @@ Item {
PortalService.getGreeterUserProfileImage(user);
GreeterState.passwordBuffer = "";
pendingPasswordResponse = false;
passwordSubmitRequested = false;
cancelingExternalAuthForPassword = false;
resetPasswordSessionTransition(true);
maybeAutoStartExternalAuth();
}
@@ -357,8 +366,7 @@ Item {
if (!GreeterState.passwordBuffer || GreeterState.passwordBuffer.length === 0)
return false;
pendingPasswordResponse = false;
passwordSubmitRequested = false;
cancelingExternalAuthForPassword = false;
resetPasswordSessionTransition(true);
awaitingExternalAuth = false;
authTimeout.interval = defaultAuthTimeoutMs;
authTimeout.restart();
@@ -369,9 +377,24 @@ Item {
}
function requestPasswordSessionTransition() {
if (!GreeterState.passwordBuffer || GreeterState.passwordBuffer.length === 0)
return;
if (cancelingExternalAuthForPassword)
return;
if (passwordSessionTransitionRetryCount >= maxPasswordSessionTransitionRetries) {
pendingPasswordResponse = false;
awaitingExternalAuth = false;
authTimeout.interval = defaultAuthTimeoutMs;
authTimeout.stop();
resetPasswordSessionTransition(true);
GreeterState.pamState = "error";
authFeedbackMessage = currentAuthMessage();
placeholderDelay.restart();
Greetd.cancelSession();
return;
}
cancelingExternalAuthForPassword = true;
passwordSessionTransitionRetryCount = passwordSessionTransitionRetryCount + 1;
awaitingExternalAuth = false;
pendingPasswordResponse = false;
authTimeout.interval = defaultAuthTimeoutMs;
@@ -388,9 +411,7 @@ Item {
if (Greetd.state !== GreetdState.Inactive) {
if (pendingPasswordResponse && hasPasswordBuffer)
submitBufferedPassword();
else if (awaitingExternalAuth && hasPasswordBuffer) {
passwordSubmitRequested = true;
} else if (hasPasswordBuffer)
else if (hasPasswordBuffer)
passwordSubmitRequested = true;
return;
}
@@ -404,7 +425,10 @@ Item {
pendingPasswordResponse = false;
passwordSubmitRequested = hasPasswordBuffer;
awaitingExternalAuth = !hasPasswordBuffer && root.greeterExternalAuthAvailable;
authTimeout.interval = awaitingExternalAuth ? externalAuthTimeoutMs : defaultAuthTimeoutMs;
// Included PAM stacks (system-auth/common-auth/password-auth) may still run
// biometric/U2F modules before password even when DMS toggles are off.
const waitingOnPamExternalBeforePassword = hasPasswordBuffer && root.greeterPamHasExternalAuth;
authTimeout.interval = (awaitingExternalAuth || waitingOnPamExternalBeforePassword) ? externalAuthTimeoutMs : defaultAuthTimeoutMs;
authTimeout.restart();
Greetd.createSession(GreeterState.username);
}
@@ -1559,18 +1583,30 @@ Item {
function onAuthMessage(message, error, responseRequired, echoResponse) {
if (responseRequired) {
cancelingExternalAuthForPassword = false;
passwordSessionTransitionRetryCount = 0;
awaitingExternalAuth = false;
authTimeout.interval = defaultAuthTimeoutMs;
authTimeout.restart();
pendingPasswordResponse = true;
const hasPasswordBuffer = GreeterState.passwordBuffer && GreeterState.passwordBuffer.length > 0;
if (!passwordSubmitRequested && hasPasswordBuffer)
passwordSubmitRequested = true;
if (passwordSubmitRequested && !root.submitBufferedPassword())
passwordSubmitRequested = false;
if (passwordSubmitRequested || hasPasswordBuffer) {
authTimeout.interval = defaultAuthTimeoutMs;
authTimeout.restart();
} else {
authTimeout.stop();
}
return;
}
pendingPasswordResponse = false;
const externalPrompt = root.isExternalAuthPrompt(message, responseRequired);
if (!passwordSubmitRequested)
awaitingExternalAuth = root.isExternalAuthPrompt(message, responseRequired);
authTimeout.interval = awaitingExternalAuth ? externalAuthTimeoutMs : defaultAuthTimeoutMs;
awaitingExternalAuth = root.greeterExternalAuthAvailable && externalPrompt;
if (awaitingExternalAuth || (passwordSubmitRequested && externalPrompt && root.greeterPamHasExternalAuth))
authTimeout.interval = externalAuthTimeoutMs;
else
authTimeout.interval = defaultAuthTimeoutMs;
authTimeout.restart();
Greetd.respond("");
}
@@ -1587,15 +1623,14 @@ Item {
Qt.callLater(root.startAuthSession);
return;
}
passwordSubmitRequested = false;
resetPasswordSessionTransition(true);
}
}
function onReadyToLaunch() {
awaitingExternalAuth = false;
pendingPasswordResponse = false;
passwordSubmitRequested = false;
cancelingExternalAuthForPassword = false;
resetPasswordSessionTransition(true);
authTimeout.interval = defaultAuthTimeoutMs;
authTimeout.stop();
passwordFailureCount = 0;
@@ -1629,8 +1664,7 @@ Item {
function onAuthFailure(message) {
awaitingExternalAuth = false;
pendingPasswordResponse = false;
passwordSubmitRequested = false;
cancelingExternalAuthForPassword = false;
resetPasswordSessionTransition(true);
authTimeout.interval = defaultAuthTimeoutMs;
authTimeout.stop();
launchTimeout.stop();
@@ -1651,8 +1685,7 @@ Item {
function onError(error) {
awaitingExternalAuth = false;
pendingPasswordResponse = false;
passwordSubmitRequested = false;
cancelingExternalAuthForPassword = false;
resetPasswordSessionTransition(true);
authTimeout.interval = defaultAuthTimeoutMs;
authTimeout.stop();
launchTimeout.stop();
@@ -1688,8 +1721,7 @@ Item {
return;
awaitingExternalAuth = false;
pendingPasswordResponse = false;
passwordSubmitRequested = false;
cancelingExternalAuthForPassword = false;
resetPasswordSessionTransition(true);
authTimeout.interval = defaultAuthTimeoutMs;
GreeterState.pamState = "error";
authFeedbackMessage = currentAuthMessage();
@@ -1707,8 +1739,7 @@ Item {
if (!GreeterState.unlocking)
return;
pendingPasswordResponse = false;
passwordSubmitRequested = false;
cancelingExternalAuthForPassword = false;
resetPasswordSessionTransition(true);
GreeterState.unlocking = false;
GreeterState.pamState = "error";
authFeedbackMessage = currentAuthMessage();

View File

@@ -90,6 +90,13 @@ Scope {
printErrors: false
}
FileView {
id: loginConfigWatcher
path: "/etc/pam.d/login"
printErrors: false
}
FileView {
id: u2fConfigWatcher
@@ -101,7 +108,7 @@ Scope {
id: passwd
config: dankshellConfigWatcher.loaded ? "dankshell" : "login"
configDirectory: dankshellConfigWatcher.loaded ? "/etc/pam.d" : Quickshell.shellDir + "/assets/pam"
configDirectory: dankshellConfigWatcher.loaded || loginConfigWatcher.loaded ? "/etc/pam.d" : Quickshell.shellDir + "/assets/pam"
onMessageChanged: {
if (message.startsWith("The account is locked"))

View File

@@ -108,6 +108,13 @@ QtObject {
return p && p.status !== Component.Null && !p._isDestroying && p.hasValidData;
}
function _isFocusedScreen() {
if (!SettingsData.notificationFocusedMonitor)
return true;
const focused = CompositorService.getFocusedScreen();
return focused && manager.modelData && focused.name === manager.modelData.name;
}
function _sync(newWrappers) {
for (const p of popupWindows.slice()) {
if (!_isValidWindow(p) || p.exiting)
@@ -118,7 +125,7 @@ QtObject {
}
}
for (const w of newWrappers) {
if (w && !_hasWindowFor(w))
if (w && !_hasWindowFor(w) && _isFocusedScreen())
_insertAtTop(w);
}
}

View File

@@ -417,6 +417,15 @@ Item {
}
}
DankToggle {
width: parent.width
text: I18n.tr("Focused monitor only")
description: I18n.tr("Show notifications only on the currently focused monitor")
visible: parent.componentId === "notifications"
checked: SettingsData.notificationFocusedMonitor
onToggled: checked => SettingsData.set("notificationFocusedMonitor", checked)
}
DankToggle {
width: parent.width
text: I18n.tr("Show on Last Display")

View File

@@ -160,6 +160,16 @@ Item {
onToggled: checked => SettingsData.set("dockGroupByApp", checked)
}
SettingsToggleRow {
settingKey: "dockRestoreSpecialWorkspaceOnClick"
tags: ["dock", "hyprland", "special", "workspace", "restore"]
text: I18n.tr("Restore Special Workspace Windows")
description: I18n.tr("When clicking a dock window in a Hyprland special workspace, bring that special workspace back before focusing the window")
checked: SettingsData.dockRestoreSpecialWorkspaceOnClick
visible: CompositorService.isHyprland
onToggled: checked => SettingsData.set("dockRestoreSpecialWorkspaceOnClick", checked)
}
SettingsButtonGroupRow {
settingKey: "dockIndicatorStyle"
tags: ["dock", "indicator", "style", "circle", "line"]

View File

@@ -288,6 +288,15 @@ Item {
onToggled: checked => SettingsData.set("notificationPopupPrivacyMode", checked)
}
SettingsToggleRow {
settingKey: "notificationFocusedMonitor"
tags: ["notification", "popup", "focused", "monitor", "display", "screen", "active"]
text: I18n.tr("Focused Monitor Only")
description: I18n.tr("Show notification popups only on the currently focused monitor")
checked: SettingsData.notificationFocusedMonitor
onToggled: checked => SettingsData.set("notificationFocusedMonitor", checked)
}
Item {
width: parent.width
height: notificationAnimationColumn.implicitHeight + Theme.spacingM * 2

View File

@@ -2638,6 +2638,18 @@ Item {
checked: SettingsData.matugenTemplateEmacs
onToggled: checked => SettingsData.set("matugenTemplateEmacs", checked)
}
SettingsToggleRow {
tab: "theme"
tags: ["matugen", "zed", "template"]
settingKey: "matugenTemplateZed"
text: "Zed"
description: getTemplateDescription("zed", "")
descriptionColor: getTemplateDescriptionColor("zed")
visible: SettingsData.runDmsMatugenTemplates
checked: SettingsData.matugenTemplateZed
onToggled: checked => SettingsData.set("matugenTemplateZed", checked)
}
}
Rectangle {

View File

@@ -239,7 +239,7 @@ Item {
StyledRect {
id: valueTooltip
width: tooltipText.contentWidth + Theme.spacingS * 2
width: tooltipText.reservedWidth + Theme.spacingS * 2
height: tooltipText.contentHeight + Theme.spacingXS * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainer
@@ -251,10 +251,22 @@ Item {
visible: slider.alwaysShowValue ? slider.showValue : ((sliderMouseArea.containsMouse && slider.showValue) || (slider.isDragging && slider.showValue))
opacity: visible ? 1 : 0
StyledText {
NumericText {
id: tooltipText
text: (slider.valueOverride >= 0 ? Math.round(slider.valueOverride) : slider.value) + slider.unit
reserveText: {
let widest = "";
const samples = [slider.minimum, slider.maximum];
if (slider.valueOverride >= 0)
samples.push(slider.valueOverride);
for (let i = 0; i < samples.length; i++) {
const candidate = Math.round(samples[i]) + slider.unit;
if (candidate.length > widest.length)
widest = candidate;
}
return widest;
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium

View File

@@ -0,0 +1,22 @@
import QtQuick
import qs.Common
StyledText {
id: root
property string reserveText: ""
readonly property real reservedWidth: reserveText !== "" ? Math.max(contentWidth, reserveMetrics.width) : contentWidth
isMonospace: true
wrapMode: Text.NoWrap
StyledTextMetrics {
id: reserveMetrics
isMonospace: root.isMonospace
font.pixelSize: root.font.pixelSize
font.family: root.font.family
font.weight: root.font.weight
font.hintingPreference: root.font.hintingPreference
text: root.reserveText
}
}

View File

@@ -0,0 +1,3 @@
[templates.dmszed]
input_path = "SHELL_DIR/matugen/templates/dank-zed.json"
output_path = "CONFIG_DIR/zed/themes/dank-zed-theme.json"

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,8 @@ LANGUAGES = {
"hu": "hu.json",
"fa": "fa.json",
"fr": "fr.json",
"nl": "nl.json"
"nl": "nl.json",
"ru": "ru.json"
}
def error(msg):

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2571,6 +2571,23 @@
"theme"
]
},
{
"section": "matugenTemplateZed",
"label": "Zed",
"tabIndex": 10,
"category": "Theme & Colors",
"keywords": [
"appearance",
"colors",
"look",
"matugen",
"scheme",
"style",
"template",
"theme",
"zed"
]
},
{
"section": "matugenTemplateFirefox",
"label": "Firefox",

File diff suppressed because it is too large Load Diff