1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-15 16:52:47 -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
+77 -13
View File
@@ -1249,7 +1249,17 @@ func extractGreeterWrapperFromCommand(command string) string {
if len(tokens) == 0 { if len(tokens) == 0 {
return "" 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 { 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 { func isPackageOnlyGreeterDistro() bool {
osInfo, err := distros.GetOSInfo() osInfo, err := distros.GetOSInfo()
if err != nil { if err != nil {
@@ -1568,22 +1598,56 @@ func checkGreeterStatus() error {
fmt.Println(" ⚠ Legacy unmanaged DMS PAM lines detected. Run 'dms greeter sync' to normalize.") fmt.Println(" ⚠ Legacy unmanaged DMS PAM lines detected. Run 'dms greeter sync' to normalize.")
allGood = false 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") includedFprintFile := greeter.DetectIncludedPamModule(string(pamData), "pam_fprintd.so")
showIncludedFprintNotice := false includedU2fFile := greeter.DetectIncludedPamModule(string(pamData), "pam_u2f.so")
if includedFprintFile != "" { fprintAvailableForCurrentUser := greeter.FingerprintAuthAvailableForCurrentUser()
if enableFprint, _, settingsErr := greeter.ReadGreeterAuthToggles(homeDir); settingsErr == nil && enableFprint {
showIncludedFprintNotice = 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 includedU2fFile != "" && !managedU2f {
if includedFprintFile != "" { if enableU2fToggle {
fmt.Printf(" ⚠ pam_fprintd found in both DMS managed block and %s.\n", includedFprintFile) fmt.Printf(" Security-key auth is enabled via included %s.\n", includedU2fFile)
fmt.Println(" Double fingerprint auth detected — run 'dms greeter sync' to resolve.") fmt.Println(" DMS toggle is enabled, but effective auth is coming from the included PAM stack.")
allGood = false } 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.")
} }
} }
+55 -4
View File
@@ -1424,9 +1424,30 @@ func FingerprintAuthAvailableForCurrentUser() bool {
return FingerprintAuthAvailableForUser(username) 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 { func syncGreeterPamConfig(homeDir string, logFunc func(string), sudoPassword string, forceAuth bool) error {
var wantFprint, wantU2f bool var wantFprint, wantU2f bool
fprintToggleEnabled := forceAuth fprintToggleEnabled := forceAuth
u2fToggleEnabled := forceAuth
if forceAuth { if forceAuth {
wantFprint = pamModuleExists("pam_fprintd.so") wantFprint = pamModuleExists("pam_fprintd.so")
wantU2f = pamModuleExists("pam_u2f.so") wantU2f = pamModuleExists("pam_u2f.so")
@@ -1436,6 +1457,7 @@ func syncGreeterPamConfig(homeDir string, logFunc func(string), sudoPassword str
return err return err
} }
fprintToggleEnabled = settings.GreeterEnableFprint fprintToggleEnabled = settings.GreeterEnableFprint
u2fToggleEnabled = settings.GreeterEnableU2f
fprintModule := pamModuleExists("pam_fprintd.so") fprintModule := pamModuleExists("pam_fprintd.so")
u2fModule := pamModuleExists("pam_u2f.so") u2fModule := pamModuleExists("pam_u2f.so")
wantFprint = settings.GreeterEnableFprint && fprintModule wantFprint = settings.GreeterEnableFprint && fprintModule
@@ -1464,14 +1486,43 @@ func syncGreeterPamConfig(homeDir string, logFunc func(string), sudoPassword str
content, _ = stripLegacyGreeterPamLines(content) content, _ = stripLegacyGreeterPamLines(content)
includedFprintFile := DetectIncludedPamModule(content, "pam_fprintd.so") includedFprintFile := DetectIncludedPamModule(content, "pam_fprintd.so")
includedU2fFile := DetectIncludedPamModule(content, "pam_u2f.so")
fprintAvailableForCurrentUser := FingerprintAuthAvailableForCurrentUser()
if wantFprint && includedFprintFile != "" { 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.") 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 wantFprint = false
} }
showIncludedFprintNotice := fprintToggleEnabled && FingerprintAuthAvailableForCurrentUser() if wantU2f && includedU2fFile != "" {
if !wantFprint && includedFprintFile != "" && showIncludedFprintNotice { logFunc("⚠ pam_u2f already present in included " + includedU2fFile + " (managed by authselect/pam-auth-update). Skipping DMS U2F block to avoid double security-key auth.")
logFunc(" Fingerprint auth is still enabled via included " + includedFprintFile + ".") wantU2f = false
logFunc(" Disable fingerprint in your system PAM manager (authselect/pam-auth-update) to force password-only greeter login.") }
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 { if wantFprint || wantU2f {
+1
View File
@@ -71,6 +71,7 @@ var templateRegistry = []TemplateDef{
{ID: "kcolorscheme", ConfigFile: "kcolorscheme.toml", RunUnconditionally: true}, {ID: "kcolorscheme", ConfigFile: "kcolorscheme.toml", RunUnconditionally: true},
{ID: "vscode", Kind: TemplateKindVSCode}, {ID: "vscode", Kind: TemplateKindVSCode},
{ID: "emacs", Commands: []string{"emacs"}, ConfigFile: "emacs.toml", Kind: TemplateKindEmacs}, {ID: "emacs", Commands: []string{"emacs"}, ConfigFile: "emacs.toml", Kind: TemplateKindEmacs},
{ID: "zed", Commands: []string{"zed"}, ConfigFile: "zed.toml"},
} }
func (c *ColorMode) GTKTheme() string { func (c *ColorMode) GTKTheme() string {
+3
View File
@@ -474,11 +474,13 @@ Singleton {
property bool matugenTemplateKcolorscheme: true property bool matugenTemplateKcolorscheme: true
property bool matugenTemplateVscode: true property bool matugenTemplateVscode: true
property bool matugenTemplateEmacs: true property bool matugenTemplateEmacs: true
property bool matugenTemplateZed: true
property bool showDock: false property bool showDock: false
property bool dockAutoHide: false property bool dockAutoHide: false
property bool dockSmartAutoHide: false property bool dockSmartAutoHide: false
property bool dockGroupByApp: false property bool dockGroupByApp: false
property bool dockRestoreSpecialWorkspaceOnClick: false
property bool dockOpenOnOverview: false property bool dockOpenOnOverview: false
property int dockPosition: SettingsData.Position.Bottom property int dockPosition: SettingsData.Position.Bottom
property real dockSpacing: 4 property real dockSpacing: 4
@@ -549,6 +551,7 @@ Singleton {
property bool notificationHistorySaveNormal: true property bool notificationHistorySaveNormal: true
property bool notificationHistorySaveCritical: true property bool notificationHistorySaveCritical: true
property var notificationRules: [] property var notificationRules: []
property bool notificationFocusedMonitor: false
property bool osdAlwaysShowValue: false property bool osdAlwaysShowValue: false
property int osdPosition: SettingsData.Position.BottomCenter property int osdPosition: SettingsData.Position.BottomCenter
+3 -1
View File
@@ -1551,7 +1551,7 @@ Singleton {
if (typeof SettingsData !== "undefined") { if (typeof SettingsData !== "undefined") {
const skipTemplates = []; const skipTemplates = [];
if (!SettingsData.runDmsMatugenTemplates) { 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 { } else {
if (!SettingsData.matugenTemplateGtk) if (!SettingsData.matugenTemplateGtk)
skipTemplates.push("gtk"); skipTemplates.push("gtk");
@@ -1595,6 +1595,8 @@ Singleton {
skipTemplates.push("vscode"); skipTemplates.push("vscode");
if (!SettingsData.matugenTemplateEmacs) if (!SettingsData.matugenTemplateEmacs)
skipTemplates.push("emacs"); skipTemplates.push("emacs");
if (!SettingsData.matugenTemplateZed)
skipTemplates.push("zed");
} }
if (skipTemplates.length > 0) { if (skipTemplates.length > 0) {
args.push("--skip-templates", skipTemplates.join(",")); args.push("--skip-templates", skipTemplates.join(","));
@@ -289,11 +289,13 @@ var SPEC = {
matugenTemplateKcolorscheme: { def: true }, matugenTemplateKcolorscheme: { def: true },
matugenTemplateVscode: { def: true }, matugenTemplateVscode: { def: true },
matugenTemplateEmacs: { def: true }, matugenTemplateEmacs: { def: true },
matugenTemplateZed: { def: true },
showDock: { def: false }, showDock: { def: false },
dockAutoHide: { def: false }, dockAutoHide: { def: false },
dockSmartAutoHide: { def: false }, dockSmartAutoHide: { def: false },
dockGroupByApp: { def: false }, dockGroupByApp: { def: false },
dockRestoreSpecialWorkspaceOnClick: { def: false },
dockOpenOnOverview: { def: false }, dockOpenOnOverview: { def: false },
dockPosition: { def: 1 }, dockPosition: { def: 1 },
dockSpacing: { def: 4 }, dockSpacing: { def: 4 },
@@ -363,6 +365,7 @@ var SPEC = {
notificationHistorySaveNormal: { def: true }, notificationHistorySaveNormal: { def: true },
notificationHistorySaveCritical: { def: true }, notificationHistorySaveCritical: { def: true },
notificationRules: { def: [] }, notificationRules: { def: [] },
notificationFocusedMonitor: { def: false },
osdAlwaysShowValue: { def: false }, osdAlwaysShowValue: { def: false },
osdPosition: { def: 5 }, osdPosition: { def: 5 },
+1 -1
View File
@@ -313,7 +313,7 @@ Item {
} }
Variants { Variants {
model: SettingsData.getFilteredScreens("notifications") model: SettingsData.notificationFocusedMonitor ? Quickshell.screens : SettingsData.getFilteredScreens("notifications")
delegate: NotificationPopupManager { delegate: NotificationPopupManager {
modelData: item modelData: item
@@ -390,10 +390,11 @@ BasePill {
anchors.top: parent.top anchors.top: parent.top
} }
StyledText { NumericText {
id: audioPercentV id: audioPercentV
visible: root.showAudioPercent visible: root.showAudioPercent
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%" text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
reserveText: "100%"
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor color: Theme.widgetTextColor
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -416,10 +417,11 @@ BasePill {
anchors.top: parent.top anchors.top: parent.top
} }
StyledText { NumericText {
id: micPercentV id: micPercentV
visible: root.showMicPercent visible: root.showMicPercent
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%" text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
reserveText: "100%"
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor color: Theme.widgetTextColor
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -442,10 +444,11 @@ BasePill {
anchors.top: parent.top anchors.top: parent.top
} }
StyledText { NumericText {
id: brightnessPercentV id: brightnessPercentV
visible: root.showBrightnessPercent visible: root.showBrightnessPercent
text: Math.round(getBrightness() * 100) + "%" text: Math.round(getBrightness() * 100) + "%"
reserveText: "100%"
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor color: Theme.widgetTextColor
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -536,7 +539,8 @@ BasePill {
} }
Rectangle { 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 height: root.widgetThickness - root.horizontalPadding * 2
color: "transparent" color: "transparent"
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -552,20 +556,23 @@ BasePill {
anchors.leftMargin: 2 anchors.leftMargin: 2
} }
StyledText { NumericText {
id: audioPercent id: audioPercent
visible: root.showAudioPercent visible: root.showAudioPercent
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%" text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
reserveText: "100%"
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor color: Theme.widgetTextColor
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: audioIcon.right anchors.left: audioIcon.right
anchors.leftMargin: 2 anchors.leftMargin: 2
width: reservedWidth
} }
} }
Rectangle { 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 height: root.widgetThickness - root.horizontalPadding * 2
color: "transparent" color: "transparent"
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -581,20 +588,22 @@ BasePill {
anchors.leftMargin: 2 anchors.leftMargin: 2
} }
StyledText { NumericText {
id: micPercent id: micPercent
visible: root.showMicPercent visible: root.showMicPercent
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%" text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
reserveText: "100%"
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor color: Theme.widgetTextColor
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: micIcon.right anchors.left: micIcon.right
anchors.leftMargin: 2 anchors.leftMargin: 2
width: reservedWidth
} }
} }
Rectangle { 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 height: root.widgetThickness - root.horizontalPadding * 2
color: "transparent" color: "transparent"
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -610,15 +619,17 @@ BasePill {
anchors.leftMargin: 2 anchors.leftMargin: 2
} }
StyledText { NumericText {
id: brightnessPercent id: brightnessPercent
visible: root.showBrightnessPercent visible: root.showBrightnessPercent
text: Math.round(getBrightness() * 100) + "%" text: Math.round(getBrightness() * 100) + "%"
reserveText: "100%"
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
color: Theme.widgetTextColor color: Theme.widgetTextColor
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: brightnessIcon.right anchors.left: brightnessIcon.right
anchors.leftMargin: 2 anchors.leftMargin: 2
width: reservedWidth
} }
} }
@@ -211,16 +211,17 @@ BasePill {
text: { text: {
const title = activeWindow && activeWindow.title ? activeWindow.title : ""; const title = activeWindow && activeWindow.title ? activeWindow.title : "";
const appName = appText.text; const appName = appText.text;
if (compactMode && title === appName) {
return title;
}
if (!title || !appName) { if (!title || !appName) {
return title; return title;
} }
if (title.endsWith(" - " + appName)) {
return title.substring(0, title.length - (" - " + appName).length);
}
if (title.endsWith(appName)) { if (title.endsWith(appName)) {
return title.substring(0, title.length - appName.length).replace(/ - $/, ""); return title.substring(0, title.length - appName.length).replace(/ (-|—) $/, "");
} }
return title; return title;
@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Hyprland import Quickshell.Hyprland
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
@@ -162,6 +161,23 @@ BasePill {
return 0; 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 bool menuOpen: false
property var currentTrayMenu: null property var currentTrayMenu: null
@@ -940,13 +956,6 @@ BasePill {
} }
})(), overflowMenu.dpr) })(), 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 opacity: root.menuOpen ? 1 : 0
scale: root.menuOpen ? 1 : 0.85 scale: root.menuOpen ? 1 : 0.85
@@ -967,19 +976,14 @@ BasePill {
ElevationShadow { ElevationShadow {
id: bgShadowLayer id: bgShadowLayer
anchors.fill: parent anchors.fill: parent
level: menuContainer.elev level: Theme.elevationLevel3
fallbackOffset: 4 direction: root.effectiveShadowDirection
shadowBlurPx: menuContainer.shadowBlurPx fallbackOffset: 6
shadowSpreadPx: menuContainer.shadowSpreadPx
shadowColor: {
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
return Theme.withAlpha(baseColor, menuContainer.effectiveShadowAlpha);
}
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
targetRadius: Theme.cornerRadius targetRadius: Theme.cornerRadius
sourceRect.antialiasing: true sourceRect.antialiasing: true
sourceRect.smooth: true sourceRect.smooth: true
shadowEnabled: Theme.elevationEnabled shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
layer.smooth: true layer.smooth: true
layer.textureSize: Qt.size(Math.round(width * overflowMenu.dpr * 2), Math.round(height * overflowMenu.dpr * 2)) layer.textureSize: Qt.size(Math.round(width * overflowMenu.dpr * 2), Math.round(height * overflowMenu.dpr * 2))
layer.textureMirroring: ShaderEffectSource.MirrorVertically layer.textureMirroring: ShaderEffectSource.MirrorVertically
@@ -1402,13 +1406,6 @@ BasePill {
} }
})(), menuWindow.dpr) })(), 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 opacity: menuRoot.showMenu ? 1 : 0
scale: menuRoot.showMenu ? 1 : 0.85 scale: menuRoot.showMenu ? 1 : 0.85
@@ -1429,18 +1426,13 @@ BasePill {
ElevationShadow { ElevationShadow {
id: menuBgShadowLayer id: menuBgShadowLayer
anchors.fill: parent anchors.fill: parent
level: menuContainer.elev level: Theme.elevationLevel3
fallbackOffset: 4 direction: root.effectiveShadowDirection
shadowBlurPx: menuContainer.shadowBlurPx fallbackOffset: 6
shadowSpreadPx: menuContainer.shadowSpreadPx
shadowColor: {
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
return Theme.withAlpha(baseColor, menuContainer.effectiveShadowAlpha);
}
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
targetRadius: Theme.cornerRadius targetRadius: Theme.cornerRadius
sourceRect.antialiasing: true sourceRect.antialiasing: true
shadowEnabled: Theme.elevationEnabled shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
layer.smooth: true layer.smooth: true
layer.textureSize: Qt.size(Math.round(width * menuWindow.dpr), Math.round(height * menuWindow.dpr)) layer.textureSize: Qt.size(Math.round(width * menuWindow.dpr), Math.round(height * menuWindow.dpr))
layer.textureMirroring: ShaderEffectSource.MirrorVertically layer.textureMirroring: ShaderEffectSource.MirrorVertically
+43 -2
View File
@@ -1,6 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets import Quickshell.Widgets
import qs.Common import qs.Common
@@ -133,6 +134,40 @@ Item {
function getGroupedToplevels() { function getGroupedToplevels() {
return appData?.allWindows?.map(w => w.toplevel).filter(t => t !== null) || []; 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: { onIsHoveredChanged: {
if (mouseArea.pressed || dragging) if (mouseArea.pressed || dragging)
return; return;
@@ -276,8 +311,11 @@ Item {
break; break;
case "window": case "window":
const windowToplevel = getToplevelObject(); const windowToplevel = getToplevelObject();
if (windowToplevel) if (windowToplevel) {
if (restoreSpecialWorkspaceWindow(windowToplevel))
return;
windowToplevel.activate(); windowToplevel.activate();
}
break; break;
case "grouped": case "grouped":
if (appData.windowCount === 0) { if (appData.windowCount === 0) {
@@ -300,8 +338,11 @@ Item {
SessionService.launchDesktopEntry(groupedEntry); SessionService.launchDesktopEntry(groupedEntry);
} else if (appData.windowCount === 1) { } else if (appData.windowCount === 1) {
const groupedToplevel = getToplevelObject(); const groupedToplevel = getToplevelObject();
if (groupedToplevel) if (groupedToplevel) {
if (restoreSpecialWorkspaceWindow(groupedToplevel))
return;
groupedToplevel.activate(); groupedToplevel.activate();
}
} else if (contextMenu) { } else if (contextMenu) {
const shouldHidePin = appData.appId === "org.quickshell"; const shouldHidePin = appData.appId === "org.quickshell";
contextMenu.showForButton(root, appData, root.height + 25, shouldHidePin, cachedDesktopEntry, parentDockScreen, dockApps); contextMenu.showForButton(root, appData, root.height + 25, shouldHidePin, cachedDesktopEntry, parentDockScreen, dockApps);
+56 -25
View File
@@ -35,8 +35,8 @@ Item {
property bool pendingPasswordResponse: false property bool pendingPasswordResponse: false
property bool passwordSubmitRequested: false property bool passwordSubmitRequested: false
property bool cancelingExternalAuthForPassword: false property bool cancelingExternalAuthForPassword: false
property int defaultAuthTimeoutMs: 12000 property int defaultAuthTimeoutMs: 10000
property int externalAuthTimeoutMs: 45000 property int externalAuthTimeoutMs: 36000
property int memoryFlushDelayMs: 120 property int memoryFlushDelayMs: 120
property string pendingLaunchCommand: "" property string pendingLaunchCommand: ""
property var pendingLaunchEnv: [] property var pendingLaunchEnv: []
@@ -50,9 +50,12 @@ Item {
property string faillockConfigText: "" property string faillockConfigText: ""
property bool greeterWallpaperOverrideExists: false property bool greeterWallpaperOverrideExists: false
property string externalAuthAutoStartedForUser: "" 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 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 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 greeterExternalAuthAvailable: (greeterPamHasFprint && GreetdSettings.greeterEnableFprint) || (greeterPamHasU2f && GreetdSettings.greeterEnableU2f)
readonly property bool greeterPamHasExternalAuth: greeterPamHasFprint || greeterPamHasU2f
function initWeatherService() { function initWeatherService() {
if (weatherInitialized) if (weatherInitialized)
@@ -208,6 +211,13 @@ Item {
authFeedbackMessage = ""; authFeedbackMessage = "";
} }
function resetPasswordSessionTransition(clearSubmitRequest) {
cancelingExternalAuthForPassword = false;
passwordSessionTransitionRetryCount = 0;
if (clearSubmitRequest)
passwordSubmitRequested = false;
}
Connections { Connections {
target: GreetdSettings target: GreetdSettings
function onSettingsLoadedChanged() { function onSettingsLoadedChanged() {
@@ -348,8 +358,7 @@ Item {
PortalService.getGreeterUserProfileImage(user); PortalService.getGreeterUserProfileImage(user);
GreeterState.passwordBuffer = ""; GreeterState.passwordBuffer = "";
pendingPasswordResponse = false; pendingPasswordResponse = false;
passwordSubmitRequested = false; resetPasswordSessionTransition(true);
cancelingExternalAuthForPassword = false;
maybeAutoStartExternalAuth(); maybeAutoStartExternalAuth();
} }
@@ -357,8 +366,7 @@ Item {
if (!GreeterState.passwordBuffer || GreeterState.passwordBuffer.length === 0) if (!GreeterState.passwordBuffer || GreeterState.passwordBuffer.length === 0)
return false; return false;
pendingPasswordResponse = false; pendingPasswordResponse = false;
passwordSubmitRequested = false; resetPasswordSessionTransition(true);
cancelingExternalAuthForPassword = false;
awaitingExternalAuth = false; awaitingExternalAuth = false;
authTimeout.interval = defaultAuthTimeoutMs; authTimeout.interval = defaultAuthTimeoutMs;
authTimeout.restart(); authTimeout.restart();
@@ -369,9 +377,24 @@ Item {
} }
function requestPasswordSessionTransition() { function requestPasswordSessionTransition() {
if (!GreeterState.passwordBuffer || GreeterState.passwordBuffer.length === 0)
return;
if (cancelingExternalAuthForPassword) if (cancelingExternalAuthForPassword)
return; 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; cancelingExternalAuthForPassword = true;
passwordSessionTransitionRetryCount = passwordSessionTransitionRetryCount + 1;
awaitingExternalAuth = false; awaitingExternalAuth = false;
pendingPasswordResponse = false; pendingPasswordResponse = false;
authTimeout.interval = defaultAuthTimeoutMs; authTimeout.interval = defaultAuthTimeoutMs;
@@ -388,9 +411,7 @@ Item {
if (Greetd.state !== GreetdState.Inactive) { if (Greetd.state !== GreetdState.Inactive) {
if (pendingPasswordResponse && hasPasswordBuffer) if (pendingPasswordResponse && hasPasswordBuffer)
submitBufferedPassword(); submitBufferedPassword();
else if (awaitingExternalAuth && hasPasswordBuffer) { else if (hasPasswordBuffer)
passwordSubmitRequested = true;
} else if (hasPasswordBuffer)
passwordSubmitRequested = true; passwordSubmitRequested = true;
return; return;
} }
@@ -404,7 +425,10 @@ Item {
pendingPasswordResponse = false; pendingPasswordResponse = false;
passwordSubmitRequested = hasPasswordBuffer; passwordSubmitRequested = hasPasswordBuffer;
awaitingExternalAuth = !hasPasswordBuffer && root.greeterExternalAuthAvailable; 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(); authTimeout.restart();
Greetd.createSession(GreeterState.username); Greetd.createSession(GreeterState.username);
} }
@@ -1559,18 +1583,30 @@ Item {
function onAuthMessage(message, error, responseRequired, echoResponse) { function onAuthMessage(message, error, responseRequired, echoResponse) {
if (responseRequired) { if (responseRequired) {
cancelingExternalAuthForPassword = false; cancelingExternalAuthForPassword = false;
passwordSessionTransitionRetryCount = 0;
awaitingExternalAuth = false; awaitingExternalAuth = false;
authTimeout.interval = defaultAuthTimeoutMs;
authTimeout.restart();
pendingPasswordResponse = true; pendingPasswordResponse = true;
const hasPasswordBuffer = GreeterState.passwordBuffer && GreeterState.passwordBuffer.length > 0;
if (!passwordSubmitRequested && hasPasswordBuffer)
passwordSubmitRequested = true;
if (passwordSubmitRequested && !root.submitBufferedPassword()) if (passwordSubmitRequested && !root.submitBufferedPassword())
passwordSubmitRequested = false; passwordSubmitRequested = false;
if (passwordSubmitRequested || hasPasswordBuffer) {
authTimeout.interval = defaultAuthTimeoutMs;
authTimeout.restart();
} else {
authTimeout.stop();
}
return; return;
} }
pendingPasswordResponse = false; pendingPasswordResponse = false;
const externalPrompt = root.isExternalAuthPrompt(message, responseRequired);
if (!passwordSubmitRequested) if (!passwordSubmitRequested)
awaitingExternalAuth = root.isExternalAuthPrompt(message, responseRequired); awaitingExternalAuth = root.greeterExternalAuthAvailable && externalPrompt;
authTimeout.interval = awaitingExternalAuth ? externalAuthTimeoutMs : defaultAuthTimeoutMs; if (awaitingExternalAuth || (passwordSubmitRequested && externalPrompt && root.greeterPamHasExternalAuth))
authTimeout.interval = externalAuthTimeoutMs;
else
authTimeout.interval = defaultAuthTimeoutMs;
authTimeout.restart(); authTimeout.restart();
Greetd.respond(""); Greetd.respond("");
} }
@@ -1587,15 +1623,14 @@ Item {
Qt.callLater(root.startAuthSession); Qt.callLater(root.startAuthSession);
return; return;
} }
passwordSubmitRequested = false; resetPasswordSessionTransition(true);
} }
} }
function onReadyToLaunch() { function onReadyToLaunch() {
awaitingExternalAuth = false; awaitingExternalAuth = false;
pendingPasswordResponse = false; pendingPasswordResponse = false;
passwordSubmitRequested = false; resetPasswordSessionTransition(true);
cancelingExternalAuthForPassword = false;
authTimeout.interval = defaultAuthTimeoutMs; authTimeout.interval = defaultAuthTimeoutMs;
authTimeout.stop(); authTimeout.stop();
passwordFailureCount = 0; passwordFailureCount = 0;
@@ -1629,8 +1664,7 @@ Item {
function onAuthFailure(message) { function onAuthFailure(message) {
awaitingExternalAuth = false; awaitingExternalAuth = false;
pendingPasswordResponse = false; pendingPasswordResponse = false;
passwordSubmitRequested = false; resetPasswordSessionTransition(true);
cancelingExternalAuthForPassword = false;
authTimeout.interval = defaultAuthTimeoutMs; authTimeout.interval = defaultAuthTimeoutMs;
authTimeout.stop(); authTimeout.stop();
launchTimeout.stop(); launchTimeout.stop();
@@ -1651,8 +1685,7 @@ Item {
function onError(error) { function onError(error) {
awaitingExternalAuth = false; awaitingExternalAuth = false;
pendingPasswordResponse = false; pendingPasswordResponse = false;
passwordSubmitRequested = false; resetPasswordSessionTransition(true);
cancelingExternalAuthForPassword = false;
authTimeout.interval = defaultAuthTimeoutMs; authTimeout.interval = defaultAuthTimeoutMs;
authTimeout.stop(); authTimeout.stop();
launchTimeout.stop(); launchTimeout.stop();
@@ -1688,8 +1721,7 @@ Item {
return; return;
awaitingExternalAuth = false; awaitingExternalAuth = false;
pendingPasswordResponse = false; pendingPasswordResponse = false;
passwordSubmitRequested = false; resetPasswordSessionTransition(true);
cancelingExternalAuthForPassword = false;
authTimeout.interval = defaultAuthTimeoutMs; authTimeout.interval = defaultAuthTimeoutMs;
GreeterState.pamState = "error"; GreeterState.pamState = "error";
authFeedbackMessage = currentAuthMessage(); authFeedbackMessage = currentAuthMessage();
@@ -1707,8 +1739,7 @@ Item {
if (!GreeterState.unlocking) if (!GreeterState.unlocking)
return; return;
pendingPasswordResponse = false; pendingPasswordResponse = false;
passwordSubmitRequested = false; resetPasswordSessionTransition(true);
cancelingExternalAuthForPassword = false;
GreeterState.unlocking = false; GreeterState.unlocking = false;
GreeterState.pamState = "error"; GreeterState.pamState = "error";
authFeedbackMessage = currentAuthMessage(); authFeedbackMessage = currentAuthMessage();
+8 -1
View File
@@ -90,6 +90,13 @@ Scope {
printErrors: false printErrors: false
} }
FileView {
id: loginConfigWatcher
path: "/etc/pam.d/login"
printErrors: false
}
FileView { FileView {
id: u2fConfigWatcher id: u2fConfigWatcher
@@ -101,7 +108,7 @@ Scope {
id: passwd id: passwd
config: dankshellConfigWatcher.loaded ? "dankshell" : "login" 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: { onMessageChanged: {
if (message.startsWith("The account is locked")) if (message.startsWith("The account is locked"))
@@ -108,6 +108,13 @@ QtObject {
return p && p.status !== Component.Null && !p._isDestroying && p.hasValidData; 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) { function _sync(newWrappers) {
for (const p of popupWindows.slice()) { for (const p of popupWindows.slice()) {
if (!_isValidWindow(p) || p.exiting) if (!_isValidWindow(p) || p.exiting)
@@ -118,7 +125,7 @@ QtObject {
} }
} }
for (const w of newWrappers) { for (const w of newWrappers) {
if (w && !_hasWindowFor(w)) if (w && !_hasWindowFor(w) && _isFocusedScreen())
_insertAtTop(w); _insertAtTop(w);
} }
} }
@@ -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 { DankToggle {
width: parent.width width: parent.width
text: I18n.tr("Show on Last Display") text: I18n.tr("Show on Last Display")
+10
View File
@@ -160,6 +160,16 @@ Item {
onToggled: checked => SettingsData.set("dockGroupByApp", checked) 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 { SettingsButtonGroupRow {
settingKey: "dockIndicatorStyle" settingKey: "dockIndicatorStyle"
tags: ["dock", "indicator", "style", "circle", "line"] tags: ["dock", "indicator", "style", "circle", "line"]
@@ -288,6 +288,15 @@ Item {
onToggled: checked => SettingsData.set("notificationPopupPrivacyMode", checked) 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 { Item {
width: parent.width width: parent.width
height: notificationAnimationColumn.implicitHeight + Theme.spacingM * 2 height: notificationAnimationColumn.implicitHeight + Theme.spacingM * 2
@@ -2638,6 +2638,18 @@ Item {
checked: SettingsData.matugenTemplateEmacs checked: SettingsData.matugenTemplateEmacs
onToggled: checked => SettingsData.set("matugenTemplateEmacs", checked) 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 { Rectangle {
+14 -2
View File
@@ -239,7 +239,7 @@ Item {
StyledRect { StyledRect {
id: valueTooltip id: valueTooltip
width: tooltipText.contentWidth + Theme.spacingS * 2 width: tooltipText.reservedWidth + Theme.spacingS * 2
height: tooltipText.contentHeight + Theme.spacingXS * 2 height: tooltipText.contentHeight + Theme.spacingXS * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceContainer color: Theme.surfaceContainer
@@ -251,10 +251,22 @@ Item {
visible: slider.alwaysShowValue ? slider.showValue : ((sliderMouseArea.containsMouse && slider.showValue) || (slider.isDragging && slider.showValue)) visible: slider.alwaysShowValue ? slider.showValue : ((sliderMouseArea.containsMouse && slider.showValue) || (slider.isDragging && slider.showValue))
opacity: visible ? 1 : 0 opacity: visible ? 1 : 0
StyledText { NumericText {
id: tooltipText id: tooltipText
text: (slider.valueOverride >= 0 ? Math.round(slider.valueOverride) : slider.value) + slider.unit 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 font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
+22
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
}
}
+3
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
+2 -1
View File
@@ -26,7 +26,8 @@ LANGUAGES = {
"hu": "hu.json", "hu": "hu.json",
"fa": "fa.json", "fa": "fa.json",
"fr": "fr.json", "fr": "fr.json",
"nl": "nl.json" "nl": "nl.json",
"ru": "ru.json"
} }
def error(msg): 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
@@ -2571,6 +2571,23 @@
"theme" "theme"
] ]
}, },
{
"section": "matugenTemplateZed",
"label": "Zed",
"tabIndex": 10,
"category": "Theme & Colors",
"keywords": [
"appearance",
"colors",
"look",
"matugen",
"scheme",
"style",
"template",
"theme",
"zed"
]
},
{ {
"section": "matugenTemplateFirefox", "section": "matugenTemplateFirefox",
"label": "Firefox", "label": "Firefox",
File diff suppressed because it is too large Load Diff