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

Compare commits

...

6 Commits

Author SHA1 Message Date
purian23
519a8357a1 fix(lock/greeter): sync auth files with master 2026-03-26 11:37:17 -04:00
purian23
799773c62b refactor: Remove faillock support and related properties from settings 2026-03-26 10:55:32 -04:00
purian23
247a674c79 fix(pam): Update config selection logic for PAM context 2026-03-26 10:55:05 -04:00
purian23
72b598057c fix(lock): Restore system PAM fallback, faillock support, and auth feedback
- Re-add loginConfigWatcher so installs can still fall through to
  /etc/pam.d instead of the bundled PAM assets
- Add login-faillock bundled PAM asset at runtime. Use it as the bundled fallback when dankshell config is absent
- Fix invalid bare property writes (u2fPending, u2fState, unlockInProgress,
  state) in Pam.qml
- Improve lockscreen auth feedback
2026-03-26 10:51:45 -04:00
Patrick Fischer
8180e30e8e fix: restore lock screen U2F/fingerprint auth to working state (#2052)
* fix: restore lock screen U2F/fingerprint auth to working state

* fix(pam): Keep SettingsData as single source of truth for auth availability
- Restores SettingsData for fingerprint/U2F, keeping lock screen and New Greeter Settings UI in sync

---------

Co-authored-by: purian23 <purian23@gmail.com>
2026-03-26 10:48:10 -04:00
purian23
dd9851b4f0 fix(Dock): Replace hardcoded max height mask in vertical mode 2026-03-26 10:41:02 -04:00
10 changed files with 144 additions and 81 deletions

View File

@@ -70,14 +70,12 @@ Singleton {
fingerprintProbeState = forcedFprintAvailable ? "ready" : "probe_failed";
}
if (forcedFprintAvailable === null || forcedU2fAvailable === null) {
pamFprintSupportDetected = false;
pamU2fSupportDetected = false;
pamSupportProbeOutput = "";
pamSupportProbeStreamFinished = false;
pamSupportProbeExited = false;
pamSupportDetectionProcess.running = true;
}
pamFprintSupportDetected = false;
pamU2fSupportDetected = false;
pamSupportProbeOutput = "";
pamSupportProbeStreamFinished = false;
pamSupportProbeExited = false;
pamSupportDetectionProcess.running = true;
recomputeAuthCapabilities();
}

View File

@@ -1,7 +1,5 @@
import QtQuick
import Quickshell
import Quickshell.Services.Greetd
import qs.Common
import qs.Modules.Greetd
Scope {

View File

@@ -225,7 +225,13 @@ Item {
}
StyledText {
text: root.errorCount > 0 ? I18n.tr("%1 issue(s) found", "greeter doctor page error count").arg(root.errorCount) : I18n.tr("All checks passed", "greeter doctor page success")
text: {
if (root.errorCount === 0)
return I18n.tr("All checks passed", "greeter doctor page success");
return root.errorCount === 1
? I18n.tr("%1 issue found", "greeter doctor page error count").arg(root.errorCount)
: I18n.tr("%1 issues found", "greeter doctor page error count").arg(root.errorCount);
}
font.pixelSize: Theme.fontSizeMedium
color: root.errorCount > 0 ? Theme.error : Theme.surfaceVariantText
}

View File

@@ -448,7 +448,7 @@ Variants {
height: {
if (dock.isVertical) {
// Keep the taller hit area regardless of the reveal state to prevent shrinking loop
return Math.min(Math.max(dockBackground.height + 64, 200), screenHeight * 0.5);
return Math.min(Math.max(dockBackground.height + 64, 200), maxDockHeight);
}
return dock.reveal ? px(dock.effectiveBarHeight + SettingsData.dockSpacing + SettingsData.dockBottomGap + SettingsData.dockMargin) : 1;
}

View File

@@ -230,19 +230,19 @@ Item {
function currentAuthMessage() {
if (GreeterState.pamState === "error")
return "Authentication error - try again";
return I18n.tr("Authentication error - try again");
if (GreeterState.pamState === "max")
return "Too many failed attempts - account may be locked";
return I18n.tr("Too many failed attempts - account may be locked");
if (GreeterState.pamState === "fail") {
if (passwordAttemptLimitHint > 0) {
const attempt = Math.max(1, Math.min(passwordFailureCount, passwordAttemptLimitHint));
const remaining = Math.max(passwordAttemptLimitHint - attempt, 0);
if (remaining > 0) {
return "Incorrect password - attempt " + attempt + " of " + passwordAttemptLimitHint + " (lockout may follow)";
return I18n.tr("Incorrect password - attempt %1 of %2 (lockout may follow)").arg(attempt).arg(passwordAttemptLimitHint);
}
return "Incorrect password - next failures may trigger account lockout";
return I18n.tr("Incorrect password - next failures may trigger account lockout");
}
return "Incorrect password";
return I18n.tr("Incorrect password");
}
return "";
}
@@ -767,7 +767,7 @@ Item {
property string fullTimeStr: {
const format = GreetdSettings.getEffectiveTimeFormat();
return systemClock.date.toLocaleTimeString(Qt.locale(), format);
return systemClock.date.toLocaleTimeString(I18n.locale(), format);
}
property var timeParts: fullTimeStr.split(':')
property string hours: timeParts[0] || ""
@@ -876,7 +876,7 @@ Item {
anchors.top: clockContainer.bottom
anchors.topMargin: 4
text: {
return systemClock.date.toLocaleDateString(Qt.locale(), GreetdSettings.getEffectiveLockDateFormat());
return systemClock.date.toLocaleDateString(I18n.locale(), GreetdSettings.getEffectiveLockDateFormat());
}
font.pixelSize: Theme.fontSizeXLarge
color: "white"
@@ -1012,15 +1012,15 @@ Item {
anchors.verticalCenter: parent.verticalCenter
text: {
if (GreeterState.unlocking) {
return "Logging in...";
return I18n.tr("Logging in...");
}
if (Greetd.state !== GreetdState.Inactive && !awaitingExternalAuth && !pendingPasswordResponse) {
return "Authenticating...";
return I18n.tr("Authenticating...");
}
if (GreeterState.showPasswordInput) {
return "Password...";
return I18n.tr("Password...");
}
return "Username...";
return I18n.tr("Username...");
}
color: (GreeterState.unlocking || (Greetd.state !== GreetdState.Inactive && !awaitingExternalAuth && !pendingPasswordResponse)) ? Theme.primary : Theme.outline
font.pixelSize: Theme.fontSizeMedium

View File

@@ -6,7 +6,7 @@ A greeter for [greetd](https://github.com/kennylevinsen/greetd) that follows the
- **Multi user**: Login with any system user
- **dms sync**: Sync settings with dms for consistent styling between shell and greeter
- **Multiple compositors**: Supports niri, Hyprland, Sway, or mangowc.
- **Multiple compositors**: The `dms-greeter` wrapper supports niri, Hyprland, sway, scroll, miracle-wm, labwc, and mangowc.
- **Custom PAM**: Supports custom PAM configuration in `/etc/pam.d/greetd`
- **Session Memory**: Remembers last selected session and user
- Can be disabled via `settings.json` keys: `greeterRememberLastSession` and `greeterRememberLastUser`
@@ -152,8 +152,8 @@ sudo chmod +x /usr/local/bin/dms-greeter
5. Create greeter cache directory with proper permissions:
```bash
sudo mkdir -p /var/cache/dms-greeter
sudo chown greeter:greeter /var/cache/dms-greeter
sudo chmod 750 /var/cache/dms-greeter
sudo chown <greeter-user>:<greeter-group> /var/cache/dms-greeter
sudo chmod 2770 /var/cache/dms-greeter
```
6. Edit or create `/etc/greetd/config.toml`:
@@ -163,7 +163,7 @@ vt = 1
[default_session]
user = "greeter"
# Change compositor to sway, hyprland, or mangowc if preferred
# Change compositor to another wrapper-supported compositor if preferred
command = "/usr/local/bin/dms-greeter --command niri"
```
@@ -238,9 +238,12 @@ DMS_RUN_GREETER=1 qs -p /path/to/dms
#### Compositor
You can configure compositor specific settings such as outputs/displays the same as you would in niri or Hyprland.
For current wrapper-based installs, the `dms-greeter` wrapper supports niri, hyprland, sway, scroll, miracle-wm, labwc, and mangowc.
Simply edit `/etc/greetd/dms-niri.kdl` or `/etc/greetd/dms-hypr.conf` to change compositor settings for the greeter
Only niri currently has a generated greeter config path managed by `dms greeter sync`.
- niri: `dms greeter sync` writes the generated greeter config to `/etc/greetd/niri/config.kdl`. Add local manual tweaks in `/etc/greetd/niri_overrides.kdl`.
- Other wrapper-supported compositors use the wrapper-generated config by default. If you need a custom compositor config, add `-C /path/to/config` to the `dms-greeter` command in `/etc/greetd/config.toml`.
#### Personalization
@@ -271,4 +274,4 @@ sudo ln -sf ~/.cache/DankMaterialShell/dms-colors.json /var/cache/dms-greeter/co
**Advanced:** You can override the configuration path with the `DMS_GREET_CFG_DIR` environment variable or the `--cache-dir` flag when using `dms-greeter`. The default is `/var/cache/dms-greeter`.
The cache directory should be owned by `greeter:greeter` with `770` permissions.
The cache directory should be owned by `<greeter-user>:<greeter-group>` with `2770` permissions. If the greeter user is not available yet, DMS falls back to `root:<greeter-group>`.

View File

@@ -214,18 +214,6 @@ export XDG_STATE_HOME="$CACHE_DIR/.local/state"
export XDG_DATA_HOME="$CACHE_DIR/.local/share"
export XDG_CACHE_HOME="$CACHE_DIR/.cache"
# Propagate correct XDG dirs into the systemd user session so socket-activated
# services (e.g. wireplumber) don't inherit HOME=/ from /etc/passwd.
if command -v systemctl >/dev/null 2>&1; then
systemctl --user set-environment \
HOME="$CACHE_DIR" \
XDG_STATE_HOME="$CACHE_DIR/.local/state" \
XDG_DATA_HOME="$CACHE_DIR/.local/share" \
XDG_CACHE_HOME="$CACHE_DIR/.cache" 2>/dev/null || true
if systemctl --user is-active --quiet wireplumber.service 2>/dev/null; then
systemctl --user restart wireplumber.service 2>/dev/null || true
fi
fi
# Keep greeter VT clean by default; callers can override via env or --debug.
if [[ -z "${RUST_LOG:-}" ]]; then

View File

@@ -39,6 +39,38 @@ Item {
lockerReadyArmed = true;
unlocking = false;
pamState = "";
if (pam)
pam.lockMessage = "";
}
function currentAuthFeedbackText() {
if (!pam)
return "";
if (pam.u2fState === "insert" && !pam.u2fPending)
return I18n.tr("Insert your security key...");
if (pam.u2fState === "waiting" && !pam.u2fPending)
return I18n.tr("Touch your security key...");
if (pam.lockMessage && pam.lockMessage.length > 0)
return pam.lockMessage;
if (pam.fprintState === "error") {
const detail = (pam.fprint.message || "").trim();
return detail.length > 0 ? I18n.tr("Fingerprint error: %1").arg(detail) : I18n.tr("Fingerprint error");
}
if (pam.fprintState === "max")
return I18n.tr("Maximum fingerprint attempts reached. Please use password.");
if (pam.fprintState === "fail")
return I18n.tr("Fingerprint not recognized (%1/%2). Please try again or use password.").arg(pam.fprint.tries).arg(SettingsData.maxFprintTries);
if (root.pamState === "error")
return I18n.tr("Authentication error - try again");
if (root.pamState === "max")
return I18n.tr("Too many attempts - locked out");
if (root.pamState === "fail")
return I18n.tr("Incorrect password - try again");
return "";
}
function authFeedbackIsHint() {
return pam && (pam.u2fState === "waiting" || pam.u2fState === "insert") && !pam.u2fPending;
}
Component.onCompleted: {
@@ -333,9 +365,9 @@ Item {
visible: SettingsData.lockScreenShowDate
text: {
if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0) {
return systemClock.date.toLocaleDateString(Qt.locale(), SettingsData.lockDateFormat);
return systemClock.date.toLocaleDateString(I18n.locale(), SettingsData.lockDateFormat);
}
return systemClock.date.toLocaleDateString(Qt.locale(), Locale.LongFormat);
return systemClock.date.toLocaleDateString(I18n.locale(), Locale.LongFormat);
}
font.pixelSize: Theme.fontSizeXLarge
color: "white"
@@ -687,14 +719,24 @@ Item {
anchors.centerIn: parent
name: {
if (pam.u2fPending)
return "passkey";
if (pam.fprint.tries >= SettingsData.maxFprintTries)
return "fingerprint_off";
if (pam.fprint.active)
return "fingerprint";
if (pam.u2f.active)
return "passkey";
return "lock";
}
size: 20
color: pam.fprint.tries >= SettingsData.maxFprintTries ? Theme.error : (passwordField.activeFocus ? Theme.primary : Theme.surfaceVariantText)
color: {
if (pam.fprint.tries >= SettingsData.maxFprintTries)
return Theme.error;
if (pam.u2fState !== "")
return Theme.tertiary;
return passwordField.activeFocus ? Theme.primary : Theme.surfaceVariantText;
}
opacity: pam.passwd.active ? 0 : 1
Behavior on opacity {
@@ -760,6 +802,11 @@ Item {
}
if (event.key === Qt.Key_Escape) {
if (pam.u2fPending) {
pam.cancelU2fPending();
event.accepted = true;
return;
}
clear();
}
@@ -824,6 +871,11 @@ Item {
if (root.unlocking) {
return "Unlocking...";
}
if (pam.u2fPending) {
if (pam.u2fState === "insert")
return "Insert your security key...";
return "Touch your security key...";
}
if (pam.passwd.active) {
return "Authenticating...";
}
@@ -898,7 +950,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
iconName: "keyboard"
buttonSize: 32
visible: !demoMode && !pam.passwd.active && !root.unlocking
visible: !demoMode && !pam.passwd.active && !root.unlocking && !pam.u2fPending
enabled: visible
onClicked: {
if (keyboardController.isKeyboardActive) {
@@ -999,7 +1051,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
iconName: "keyboard_return"
buttonSize: 36
visible: (demoMode || (!pam.passwd.active && !root.unlocking))
visible: (demoMode || (!pam.passwd.active && !root.unlocking && !pam.u2fPending))
enabled: !demoMode
onClicked: {
if (!demoMode && !root.unlocking && !pam.u2fPending) {
@@ -1025,24 +1077,18 @@ Item {
}
StyledText {
id: authFeedbackText
Layout.fillWidth: true
Layout.preferredHeight: 20
text: {
if (root.pamState === "error") {
return "Authentication error - try again";
}
if (root.pamState === "max") {
return "Too many attempts - locked out";
}
if (root.pamState === "fail") {
return "Incorrect password - try again";
}
return "";
}
color: Theme.error
Layout.preferredHeight: text.length > 0 ? Math.min(implicitHeight, Math.ceil(Theme.fontSizeSmall * 4.5)) : 0
text: root.currentAuthFeedbackText()
color: root.authFeedbackIsHint() ? Theme.outline : Theme.error
font.pixelSize: Theme.fontSizeSmall
horizontalAlignment: Text.AlignHCenter
opacity: root.pamState !== "" ? 1 : 0
wrapMode: Text.WordWrap
maximumLineCount: 3
elide: Text.ElideRight
opacity: text.length > 0 ? 1 : 0
Behavior on opacity {
NumberAnimation {
@@ -1611,6 +1657,14 @@ Item {
root.passwordBuffer = "";
}
}
onU2fPendingChanged: {
if (u2fPending) {
passwordField.text = "";
root.passwordBuffer = "";
if (keyboardController.isKeyboardActive)
keyboardController.hide();
}
}
}
Connections {

View File

@@ -14,9 +14,12 @@ Scope {
readonly property alias passwd: passwd
readonly property alias fprint: fprint
readonly property alias u2f: u2f
property string lockMessage
property string state
property string fprintState
property string u2fState
property bool u2fPending: false
property string buffer
signal flashMsg
@@ -31,14 +34,14 @@ Scope {
u2fPendingTimeout.running = false;
passwdActiveTimeout.running = false;
unlockRequestTimeout.running = false;
u2fPending = false;
u2fState = "";
unlockInProgress = false;
root.u2fPending = false;
root.u2fState = "";
root.unlockInProgress = false;
}
function recoverFromAuthStall(newState: string): void {
resetAuthFlows();
state = newState;
root.state = newState;
flashMsg();
stateReset.restart();
fprint.checkAvail();
@@ -46,16 +49,16 @@ Scope {
}
function completeUnlock(): void {
if (!unlockInProgress) {
unlockInProgress = true;
if (!root.unlockInProgress) {
root.unlockInProgress = true;
passwd.abort();
fprint.abort();
u2f.abort();
errorRetry.running = false;
u2fErrorRetry.running = false;
u2fPendingTimeout.running = false;
u2fPending = false;
u2fState = "";
root.u2fPending = false;
root.u2fState = "";
unlockRequestTimeout.restart();
unlockRequested();
}
@@ -70,13 +73,13 @@ Scope {
}
function cancelU2fPending(): void {
if (!u2fPending)
if (!root.u2fPending)
return;
u2f.abort();
u2fErrorRetry.running = false;
u2fPendingTimeout.running = false;
u2fPending = false;
u2fState = "";
root.u2fPending = false;
root.u2fState = "";
fprint.checkAvail();
}
@@ -94,17 +97,27 @@ Scope {
printErrors: false
}
FileView {
id: u2fConfigWatcher
path: "/etc/pam.d/dankshell-u2f"
printErrors: false
}
PamContext {
id: passwd
config: dankshellConfigWatcher.loaded ? "dankshell" : "login"
configDirectory: dankshellConfigWatcher.loaded || loginConfigWatcher.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"))
if (message.startsWith("The account is locked")) {
root.lockMessage = message;
else if (root.lockMessage && message.endsWith(" left to unlock)"))
} else if (root.lockMessage && message.endsWith(" left to unlock)")) {
root.lockMessage += "\n" + message;
} else if (root.lockMessage && message && message.length > 0) {
root.lockMessage = "";
}
}
onResponseRequiredChanged: {
@@ -117,9 +130,8 @@ Scope {
onCompleted: res => {
if (res === PamResult.Success) {
if (!root.unlockInProgress) {
root.unlockInProgress = true;
fprint.abort();
root.unlockRequested();
root.proceedAfterPrimaryAuth();
}
return;
}
@@ -182,9 +194,8 @@ Scope {
if (res === PamResult.Success) {
if (!root.unlockInProgress) {
root.unlockInProgress = true;
passwd.abort();
root.unlockRequested();
root.proceedAfterPrimaryAuth();
}
return;
}
@@ -244,7 +255,7 @@ Scope {
configDirectory: u2fConfigWatcher.loaded ? "/etc/pam.d" : Quickshell.shellDir + "/assets/pam"
onMessageChanged: {
if (message !== "")
if (message.toLowerCase().includes("touch"))
root.u2fState = "waiting";
}
@@ -346,6 +357,8 @@ Scope {
SettingsData.refreshAuthAvailability();
root.state = "";
root.fprintState = "";
root.u2fState = "";
root.u2fPending = false;
root.lockMessage = "";
root.resetAuthFlows();
fprint.checkAvail();

View File

@@ -0,0 +1,3 @@
#%PAM-1.0
auth required pam_u2f.so cue nouserok timeout=10