mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-02 03:28:28 -04:00
9967db0c5a
- Introduce external management of greetd PAM
- New functionality to validate and apply custom PAM service paths in lockscreen
Port 1.5
(cherry picked from commit b169fe0d77)
788 lines
34 KiB
QML
788 lines
34 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import Quickshell.Io
|
|
import qs.Common
|
|
import qs.Modals.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
import qs.Modules.Settings.Widgets
|
|
|
|
Item {
|
|
id: root
|
|
|
|
LayoutMirroring.enabled: I18n.isRtl
|
|
LayoutMirroring.childrenInherit: true
|
|
|
|
readonly property bool greeterFprintToggleAvailable: SettingsData.greeterFingerprintCanEnable || SettingsData.greeterEnableFprint
|
|
readonly property bool greeterU2fToggleAvailable: SettingsData.greeterU2fCanEnable || SettingsData.greeterEnableU2f
|
|
|
|
function greeterFingerprintDescription() {
|
|
if (SettingsData.greeterPamExternallyManaged)
|
|
return I18n.tr("greetd PAM is externally managed. DMS will not change fingerprint auth; configure it yourself.");
|
|
|
|
const source = SettingsData.greeterFingerprintSource;
|
|
const reason = SettingsData.greeterFingerprintReason;
|
|
|
|
if (source === "pam") {
|
|
switch (reason) {
|
|
case "configured_externally":
|
|
return SettingsData.greeterEnableFprint ? I18n.tr("Enabled. PAM already provides fingerprint auth.") : I18n.tr("PAM already provides fingerprint auth. Enable this to show it at login.");
|
|
case "missing_enrollment":
|
|
return SettingsData.greeterEnableFprint ? I18n.tr("Enabled. PAM provides fingerprint auth, but no prints are enrolled yet.") : I18n.tr("PAM provides fingerprint auth, but no prints are enrolled yet.");
|
|
case "missing_reader":
|
|
return I18n.tr("PAM provides fingerprint auth, but no reader was detected.");
|
|
default:
|
|
return I18n.tr("PAM provides fingerprint auth, but availability could not be confirmed.");
|
|
}
|
|
}
|
|
|
|
switch (reason) {
|
|
case "ready":
|
|
return SettingsData.greeterEnableFprint ? I18n.tr("Authentication changes apply automatically. Fingerprint-only login may not unlock Keyring.") : I18n.tr("Only affects DMS-managed PAM. If greetd already includes pam_fprintd, fingerprint stays enabled.");
|
|
case "missing_enrollment":
|
|
if (SettingsData.greeterEnableFprint)
|
|
return I18n.tr("Enabled, but no prints are enrolled yet. Enroll fingerprints and run Sync.");
|
|
return I18n.tr("Fingerprint reader detected, but no prints are enrolled yet. You can enable this now and run Sync later.");
|
|
case "missing_reader":
|
|
return SettingsData.greeterEnableFprint ? I18n.tr("Enabled, but no fingerprint reader was detected.") : I18n.tr("No fingerprint reader detected.");
|
|
case "missing_pam_support":
|
|
return I18n.tr("Not available — install fprintd and pam_fprintd, or configure greetd PAM.");
|
|
default:
|
|
return SettingsData.greeterEnableFprint ? I18n.tr("Enabled, but fingerprint availability could not be confirmed.") : I18n.tr("Fingerprint availability could not be confirmed.");
|
|
}
|
|
}
|
|
|
|
function greeterU2fDescription() {
|
|
if (SettingsData.greeterPamExternallyManaged)
|
|
return I18n.tr("greetd PAM is externally managed. DMS will not change security-key auth; configure it yourself.");
|
|
|
|
const source = SettingsData.greeterU2fSource;
|
|
const reason = SettingsData.greeterU2fReason;
|
|
|
|
if (source === "pam") {
|
|
return SettingsData.greeterEnableU2f ? I18n.tr("Enabled. PAM already provides security-key auth.") : I18n.tr("PAM already provides security-key auth. Enable this to show it at login.");
|
|
}
|
|
|
|
switch (reason) {
|
|
case "ready":
|
|
return SettingsData.greeterEnableU2f ? I18n.tr("Authentication changes apply automatically.") : I18n.tr("Available.");
|
|
case "missing_key_registration":
|
|
if (SettingsData.greeterEnableU2f)
|
|
return I18n.tr("Enabled, but no registered security key was found yet. Register a key and run Sync.");
|
|
return I18n.tr("Security-key support was detected, but no registered key was found yet. You can enable this now and register one later.");
|
|
case "missing_pam_support":
|
|
return I18n.tr("Not available — install or configure pam_u2f, or configure greetd PAM.");
|
|
default:
|
|
return SettingsData.greeterEnableU2f ? I18n.tr("Enabled, but security-key availability could not be confirmed.") : I18n.tr("Security-key availability could not be confirmed.");
|
|
}
|
|
}
|
|
|
|
function refreshAuthDetection() {
|
|
SettingsData.refreshAuthAvailability();
|
|
}
|
|
|
|
onVisibleChanged: {
|
|
if (visible)
|
|
refreshAuthDetection();
|
|
}
|
|
|
|
ConfirmModal {
|
|
id: greeterActionConfirm
|
|
}
|
|
|
|
property string greeterStatusText: ""
|
|
property bool greeterStatusRunning: false
|
|
property bool greeterSyncRunning: false
|
|
property bool greeterInstallActionRunning: false
|
|
property string greeterStatusStdout: ""
|
|
property string greeterStatusStderr: ""
|
|
property string greeterSyncStdout: ""
|
|
property string greeterSyncStderr: ""
|
|
property string greeterSudoProbeStderr: ""
|
|
property string greeterTerminalFallbackStderr: ""
|
|
property bool greeterTerminalFallbackFromPrecheck: false
|
|
property bool greeterBinaryExists: false
|
|
property bool greeterEnabled: false
|
|
readonly property bool greeterInstalled: greeterBinaryExists || greeterEnabled
|
|
|
|
readonly property string greeterActionLabel: {
|
|
if (!root.greeterInstalled)
|
|
return I18n.tr("Install");
|
|
if (!root.greeterEnabled)
|
|
return I18n.tr("Activate");
|
|
return I18n.tr("Uninstall");
|
|
}
|
|
readonly property string greeterActionIcon: {
|
|
if (!root.greeterInstalled)
|
|
return "download";
|
|
if (!root.greeterEnabled)
|
|
return "login";
|
|
return "delete";
|
|
}
|
|
readonly property var greeterActionCommand: {
|
|
if (!root.greeterInstalled)
|
|
return ["dms", "greeter", "install", "--terminal"];
|
|
if (!root.greeterEnabled)
|
|
return ["dms", "greeter", "enable", "--terminal"];
|
|
return ["dms", "greeter", "uninstall", "--terminal", "--yes"];
|
|
}
|
|
property string greeterPendingAction: ""
|
|
|
|
function checkGreeterInstallState() {
|
|
greetdEnabledCheckProcess.running = true;
|
|
greeterBinaryCheckProcess.running = true;
|
|
}
|
|
|
|
function runGreeterStatus() {
|
|
greeterStatusText = "";
|
|
greeterStatusStdout = "";
|
|
greeterStatusStderr = "";
|
|
greeterStatusRunning = true;
|
|
greeterStatusProcess.running = true;
|
|
}
|
|
|
|
function runGreeterInstallAction() {
|
|
root.greeterPendingAction = !root.greeterInstalled ? "install" : !root.greeterEnabled ? "activate" : "uninstall";
|
|
greeterStatusText = I18n.tr("Opening terminal: ") + root.greeterActionLabel + "...";
|
|
greeterInstallActionRunning = true;
|
|
greeterInstallActionProcess.running = true;
|
|
}
|
|
|
|
function promptGreeterActionConfirm() {
|
|
var title, message, confirmText;
|
|
if (!root.greeterInstalled) {
|
|
title = I18n.tr("Install Greeter", "greeter action confirmation");
|
|
message = I18n.tr("Install the DMS greeter? A terminal will open for sudo authentication.");
|
|
confirmText = I18n.tr("Install");
|
|
} else if (!root.greeterEnabled) {
|
|
title = I18n.tr("Activate Greeter", "greeter action confirmation");
|
|
message = I18n.tr("Activate the DMS greeter? A terminal will open for sudo authentication. Run Sync after activation to apply your settings.");
|
|
confirmText = I18n.tr("Activate");
|
|
} else {
|
|
title = I18n.tr("Uninstall Greeter", "greeter action confirmation");
|
|
message = I18n.tr("Uninstall the DMS greeter? This will remove configuration and restore your previous display manager. A terminal will open for sudo authentication.");
|
|
confirmText = I18n.tr("Uninstall");
|
|
}
|
|
greeterActionConfirm.showWithOptions({
|
|
"title": title,
|
|
"message": message,
|
|
"confirmText": confirmText,
|
|
"cancelText": I18n.tr("Cancel"),
|
|
"confirmColor": Theme.primary,
|
|
"onConfirm": () => root.runGreeterInstallAction(),
|
|
"onCancel": () => {}
|
|
});
|
|
}
|
|
|
|
function runGreeterSync() {
|
|
greeterSyncStdout = "";
|
|
greeterSyncStderr = "";
|
|
greeterSudoProbeStderr = "";
|
|
greeterTerminalFallbackStderr = "";
|
|
greeterTerminalFallbackFromPrecheck = false;
|
|
greeterStatusText = I18n.tr("Checking whether sudo authentication is needed...");
|
|
greeterSyncRunning = true;
|
|
greeterSudoProbeProcess.running = true;
|
|
}
|
|
|
|
function launchGreeterSyncTerminalFallback(fromPrecheck, statusText) {
|
|
greeterTerminalFallbackFromPrecheck = fromPrecheck;
|
|
if (statusText && statusText !== "")
|
|
greeterStatusText = statusText;
|
|
greeterTerminalFallbackStderr = "";
|
|
greeterTerminalFallbackProcess.running = true;
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
refreshAuthDetection();
|
|
Qt.callLater(checkGreeterInstallState);
|
|
}
|
|
|
|
Process {
|
|
id: greetdEnabledCheckProcess
|
|
command: ["systemctl", "is-enabled", "greetd"]
|
|
running: false
|
|
|
|
stdout: StdioCollector {
|
|
onStreamFinished: root.greeterEnabled = text.trim() === "enabled"
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: greeterBinaryCheckProcess
|
|
command: ["sh", "-c", "test -f /usr/bin/dms-greeter || test -f /usr/local/bin/dms-greeter"]
|
|
running: false
|
|
|
|
onExited: exitCode => {
|
|
root.greeterBinaryExists = (exitCode === 0);
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: greeterStatusProcess
|
|
command: ["dms", "greeter", "status"]
|
|
running: false
|
|
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
root.greeterStatusStdout = text || "";
|
|
}
|
|
}
|
|
|
|
stderr: StdioCollector {
|
|
onStreamFinished: root.greeterStatusStderr = text || ""
|
|
}
|
|
|
|
onExited: exitCode => {
|
|
root.greeterStatusRunning = false;
|
|
const out = (root.greeterStatusStdout || "").trim();
|
|
const err = (root.greeterStatusStderr || "").trim();
|
|
if (exitCode === 0) {
|
|
root.greeterStatusText = out !== "" ? out : I18n.tr("No status output.");
|
|
if (err !== "")
|
|
root.greeterStatusText = root.greeterStatusText + "\n\nstderr:\n" + err;
|
|
return;
|
|
}
|
|
var failure = I18n.tr("Failed to run 'dms greeter status'. Ensure DMS is installed and dms is in PATH.", "greeter status error") + " (exit " + exitCode + ")";
|
|
if (out !== "")
|
|
failure = failure + "\n\n" + out;
|
|
if (err !== "")
|
|
failure = failure + "\n\nstderr:\n" + err;
|
|
root.greeterStatusText = failure;
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: greeterSyncProcess
|
|
command: ["dms", "greeter", "sync", "--yes"]
|
|
running: false
|
|
|
|
stdout: StdioCollector {
|
|
onStreamFinished: root.greeterSyncStdout = text || ""
|
|
}
|
|
|
|
stderr: StdioCollector {
|
|
onStreamFinished: root.greeterSyncStderr = text || ""
|
|
}
|
|
|
|
onExited: exitCode => {
|
|
root.greeterSyncRunning = false;
|
|
const out = (root.greeterSyncStdout || "").trim();
|
|
const err = (root.greeterSyncStderr || "").trim();
|
|
if (exitCode === 0) {
|
|
var success = I18n.tr("Sync completed successfully.");
|
|
if (out !== "")
|
|
success = success + "\n\n" + out;
|
|
if (err !== "")
|
|
success = success + "\n\nstderr:\n" + err;
|
|
root.greeterStatusText = success;
|
|
SettingsData.clearGreeterSyncPending();
|
|
ToastService.showInfo(I18n.tr("Greeter sync complete"));
|
|
} else {
|
|
var failure = I18n.tr("Sync failed in background mode. Trying terminal mode so you can authenticate interactively.") + " (exit " + exitCode + ")";
|
|
if (out !== "")
|
|
failure = failure + "\n\n" + out;
|
|
if (err !== "")
|
|
failure = failure + "\n\nstderr:\n" + err;
|
|
root.greeterStatusText = failure;
|
|
root.launchGreeterSyncTerminalFallback(false, "");
|
|
}
|
|
root.checkGreeterInstallState();
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: greeterSudoProbeProcess
|
|
command: ["sudo", "-n", "true"]
|
|
running: false
|
|
|
|
stderr: StdioCollector {
|
|
onStreamFinished: root.greeterSudoProbeStderr = text || ""
|
|
}
|
|
|
|
onExited: exitCode => {
|
|
const err = (root.greeterSudoProbeStderr || "").trim();
|
|
if (exitCode === 0) {
|
|
root.greeterStatusText = I18n.tr("Running greeter sync...");
|
|
greeterSyncProcess.running = true;
|
|
return;
|
|
}
|
|
|
|
var authNeeded = I18n.tr("Sync needs sudo authentication. Opening terminal so you can use password or fingerprint.");
|
|
if (err !== "")
|
|
authNeeded = authNeeded + "\n\n" + err;
|
|
root.launchGreeterSyncTerminalFallback(true, authNeeded);
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: greeterTerminalFallbackProcess
|
|
command: ["dms", "greeter", "sync", "--terminal", "--yes"]
|
|
running: false
|
|
|
|
stderr: StdioCollector {
|
|
onStreamFinished: root.greeterTerminalFallbackStderr = text || ""
|
|
}
|
|
|
|
onExited: exitCode => {
|
|
root.greeterSyncRunning = false;
|
|
if (exitCode === 0) {
|
|
var launched = root.greeterTerminalFallbackFromPrecheck ? I18n.tr("Terminal opened. Complete sync authentication there; it will close automatically when done.") : I18n.tr("Terminal fallback opened. Complete sync there; it will close automatically when done.");
|
|
root.greeterStatusText = root.greeterStatusText ? root.greeterStatusText + "\n\n" + launched : launched;
|
|
SettingsData.clearGreeterSyncPending();
|
|
return;
|
|
}
|
|
var fallback = I18n.tr("Terminal fallback failed. Install one of the supported terminal emulators or run 'dms greeter sync' manually.") + " (exit " + exitCode + ")";
|
|
const err = (root.greeterTerminalFallbackStderr || "").trim();
|
|
if (err !== "")
|
|
fallback = fallback + "\n\nstderr:\n" + err;
|
|
root.greeterStatusText = root.greeterStatusText ? root.greeterStatusText + "\n\n" + fallback : fallback;
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: greeterInstallActionProcess
|
|
command: root.greeterActionCommand
|
|
running: false
|
|
|
|
onExited: exitCode => {
|
|
root.greeterInstallActionRunning = false;
|
|
const pending = root.greeterPendingAction;
|
|
root.greeterPendingAction = "";
|
|
if (exitCode === 0) {
|
|
if (pending === "install")
|
|
root.greeterStatusText = I18n.tr("Install complete. Greeter has been installed.");
|
|
else if (pending === "activate")
|
|
root.greeterStatusText = I18n.tr("Greeter activated. greetd is now enabled.");
|
|
else
|
|
root.greeterStatusText = I18n.tr("Uninstall complete. Greeter has been removed.");
|
|
} else {
|
|
root.greeterStatusText = I18n.tr("Action failed or terminal was closed.") + " (exit " + exitCode + ")";
|
|
}
|
|
root.checkGreeterInstallState();
|
|
}
|
|
}
|
|
|
|
readonly property var _lockDateFormatPresets: [
|
|
{
|
|
format: "",
|
|
label: I18n.tr("System Default", "date format option")
|
|
},
|
|
{
|
|
format: "ddd d",
|
|
label: I18n.tr("Day Date", "date format option")
|
|
},
|
|
{
|
|
format: "ddd MMM d",
|
|
label: I18n.tr("Day Month Date", "date format option")
|
|
},
|
|
{
|
|
format: "MMM d",
|
|
label: I18n.tr("Month Date", "date format option")
|
|
},
|
|
{
|
|
format: "M/d",
|
|
label: I18n.tr("Numeric (M/D)", "date format option")
|
|
},
|
|
{
|
|
format: "d/M",
|
|
label: I18n.tr("Numeric (D/M)", "date format option")
|
|
},
|
|
{
|
|
format: "ddd d MMM yyyy",
|
|
label: I18n.tr("Full with Year", "date format option")
|
|
},
|
|
{
|
|
format: "yyyy-MM-dd",
|
|
label: I18n.tr("ISO Date", "date format option")
|
|
},
|
|
{
|
|
format: "dddd, MMMM d",
|
|
label: I18n.tr("Full Day & Month", "date format option")
|
|
}
|
|
]
|
|
|
|
DankFlickable {
|
|
anchors.fill: parent
|
|
clip: true
|
|
contentHeight: mainColumn.height + Theme.spacingXL + (syncPendingPill.shown ? syncPendingPill.height + Theme.spacingL : 0)
|
|
contentWidth: width
|
|
|
|
Column {
|
|
id: mainColumn
|
|
topPadding: 4
|
|
width: Math.min(550, parent.width - Theme.spacingL * 2)
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
spacing: Theme.spacingXL
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
iconName: "info"
|
|
title: I18n.tr("Greeter Status")
|
|
settingKey: "greeterStatus"
|
|
|
|
StyledText {
|
|
text: I18n.tr("Sync applies your theme and settings to the login screen. Other users should run dms greeter sync --profile instead of a full sync. Authentication changes apply automatically.")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceVariantText
|
|
width: parent.width
|
|
wrapMode: Text.Wrap
|
|
horizontalAlignment: Text.AlignLeft
|
|
}
|
|
|
|
Item {
|
|
width: 1
|
|
height: Theme.spacingS
|
|
}
|
|
|
|
Rectangle {
|
|
width: parent.width
|
|
height: Math.min(180, statusTextArea.implicitHeight + Theme.spacingM * 2)
|
|
radius: Theme.cornerRadius
|
|
color: Theme.surfaceContainerHighest
|
|
|
|
StyledText {
|
|
id: statusTextArea
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.spacingM
|
|
text: root.greeterStatusRunning ? I18n.tr("Checking...", "greeter status loading") : (root.greeterStatusText || I18n.tr("Click Refresh to check status.", "greeter status placeholder"))
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
font.family: "monospace"
|
|
color: root.greeterStatusRunning ? Theme.surfaceVariantText : Theme.surfaceText
|
|
wrapMode: Text.Wrap
|
|
verticalAlignment: Text.AlignTop
|
|
}
|
|
}
|
|
|
|
Item {
|
|
width: 1
|
|
height: Theme.spacingM
|
|
}
|
|
|
|
Flow {
|
|
width: parent.width
|
|
spacing: Theme.spacingS
|
|
|
|
DankButton {
|
|
text: root.greeterActionLabel
|
|
iconName: root.greeterActionIcon
|
|
horizontalPadding: Theme.spacingL
|
|
onClicked: root.promptGreeterActionConfirm()
|
|
enabled: !root.greeterInstallActionRunning && !root.greeterSyncRunning
|
|
}
|
|
|
|
DankButton {
|
|
text: I18n.tr("Refresh")
|
|
iconName: "refresh"
|
|
horizontalPadding: Theme.spacingL
|
|
onClicked: root.runGreeterStatus()
|
|
enabled: !root.greeterStatusRunning
|
|
}
|
|
|
|
DankButton {
|
|
text: I18n.tr("Sync")
|
|
iconName: "sync"
|
|
horizontalPadding: Theme.spacingL
|
|
onClicked: root.runGreeterSync()
|
|
enabled: root.greeterInstalled && !root.greeterSyncRunning && !root.greeterInstallActionRunning
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
iconName: "fingerprint"
|
|
title: I18n.tr("Login Authentication")
|
|
settingKey: "greeterAuth"
|
|
|
|
StyledText {
|
|
text: I18n.tr("Enable fingerprint or security key for DMS Greeter. Authentication changes apply automatically.")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceVariantText
|
|
width: parent.width
|
|
wrapMode: Text.Wrap
|
|
horizontalAlignment: Text.AlignLeft
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "greeterPamExternallyManaged"
|
|
tags: ["greeter", "pam", "managed", "external", "greetd", "auth"]
|
|
text: I18n.tr("Manage greetd PAM automatically")
|
|
description: SettingsData.greeterPamExternallyManaged ? I18n.tr("DMS never touches /etc/pam.d/greetd; any existing DMS block is removed on next sync. Configure fingerprint and security-key auth yourself.") : I18n.tr("DMS writes its managed block into /etc/pam.d/greetd during sync.")
|
|
checked: !SettingsData.greeterPamExternallyManaged
|
|
onToggled: checked => SettingsData.set("greeterPamExternallyManaged", !checked)
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "greeterEnableFprint"
|
|
tags: ["greeter", "fingerprint", "fprintd", "login", "auth"]
|
|
text: I18n.tr("Enable fingerprint at login")
|
|
description: root.greeterFingerprintDescription()
|
|
descriptionColor: (SettingsData.greeterFingerprintReason === "ready" || SettingsData.greeterFingerprintReason === "configured_externally") ? Theme.surfaceVariantText : Theme.warning
|
|
checked: SettingsData.greeterEnableFprint
|
|
enabled: root.greeterFprintToggleAvailable && !SettingsData.greeterPamExternallyManaged
|
|
onToggled: checked => SettingsData.set("greeterEnableFprint", checked)
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "greeterEnableU2f"
|
|
tags: ["greeter", "u2f", "security", "key", "login", "auth"]
|
|
text: I18n.tr("Enable security key at login")
|
|
description: root.greeterU2fDescription()
|
|
descriptionColor: (SettingsData.greeterU2fReason === "ready" || SettingsData.greeterU2fReason === "configured_externally") ? Theme.surfaceVariantText : Theme.warning
|
|
checked: SettingsData.greeterEnableU2f
|
|
enabled: root.greeterU2fToggleAvailable && !SettingsData.greeterPamExternallyManaged
|
|
onToggled: checked => SettingsData.set("greeterEnableU2f", checked)
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
iconName: "palette"
|
|
title: I18n.tr("Greeter Appearance")
|
|
settingKey: "greeterAppearance"
|
|
|
|
StyledText {
|
|
text: I18n.tr("Font")
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
font.weight: Font.Medium
|
|
color: Theme.surfaceText
|
|
topPadding: Theme.spacingM
|
|
width: parent.width
|
|
horizontalAlignment: Text.AlignLeft
|
|
}
|
|
|
|
SettingsFontDropdownRow {
|
|
settingKey: "greeterFontFamily"
|
|
tags: ["greeter", "font", "typography"]
|
|
text: I18n.tr("Greeter font")
|
|
description: I18n.tr("Font used on the login screen")
|
|
currentFont: SettingsData.greeterFontFamily || ""
|
|
onFontSelected: family => SettingsData.set("greeterFontFamily", family)
|
|
}
|
|
|
|
StyledText {
|
|
text: I18n.tr("Date format on greeter")
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
font.weight: Font.Medium
|
|
color: Theme.surfaceText
|
|
topPadding: Theme.spacingM
|
|
width: parent.width
|
|
horizontalAlignment: Text.AlignLeft
|
|
}
|
|
|
|
SettingsDropdownRow {
|
|
settingKey: "greeterLockDateFormat"
|
|
tags: ["greeter", "date", "format"]
|
|
text: I18n.tr("Date Format")
|
|
description: I18n.tr("Greeter only — format for the date on the login screen")
|
|
options: root._lockDateFormatPresets.map(p => p.label)
|
|
currentValue: {
|
|
var current = (SettingsData.greeterLockDateFormat !== undefined && SettingsData.greeterLockDateFormat !== "") ? SettingsData.greeterLockDateFormat : SettingsData.lockDateFormat || "";
|
|
var match = root._lockDateFormatPresets.find(p => p.format === current);
|
|
return match ? match.label : (current ? I18n.tr("Custom: ") + current : root._lockDateFormatPresets[0].label);
|
|
}
|
|
onValueChanged: value => {
|
|
var preset = root._lockDateFormatPresets.find(p => p.label === value);
|
|
SettingsData.set("greeterLockDateFormat", preset ? preset.format : "");
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
text: I18n.tr("Background")
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
font.weight: Font.Medium
|
|
color: Theme.surfaceText
|
|
topPadding: Theme.spacingM
|
|
width: parent.width
|
|
horizontalAlignment: Text.AlignLeft
|
|
}
|
|
|
|
StyledText {
|
|
text: I18n.tr("Use a custom image for the login screen, or leave empty to use your desktop wallpaper.")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceVariantText
|
|
width: parent.width
|
|
wrapMode: Text.Wrap
|
|
horizontalAlignment: Text.AlignLeft
|
|
}
|
|
|
|
SettingsWallpaperPicker {
|
|
width: parent.width
|
|
path: SettingsData.greeterWallpaperPath
|
|
fillMode: SettingsData.greeterWallpaperFillMode
|
|
fallbackFillMode: SettingsData.wallpaperFillMode || "Fill"
|
|
browserTitle: I18n.tr("Select greeter background image")
|
|
fillModeSettingKey: "greeterWallpaperFillMode"
|
|
fillModeTags: ["greeter", "wallpaper", "background", "fill"]
|
|
onPathSelected: path => SettingsData.set("greeterWallpaperPath", path)
|
|
onFillModeSelected: mode => SettingsData.set("greeterWallpaperFillMode", mode)
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
iconName: "history"
|
|
title: I18n.tr("Greeter Behavior")
|
|
settingKey: "greeterBehavior"
|
|
|
|
StyledText {
|
|
text: I18n.tr("Convenience options for the login screen. Sync to apply.")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceVariantText
|
|
width: parent.width
|
|
wrapMode: Text.Wrap
|
|
horizontalAlignment: Text.AlignLeft
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "greeterRememberLastSession"
|
|
tags: ["greeter", "session", "remember", "login"]
|
|
text: I18n.tr("Remember last session")
|
|
description: I18n.tr("Pre-select the last used session on the greeter")
|
|
checked: SettingsData.greeterRememberLastSession
|
|
onToggled: checked => SettingsData.set("greeterRememberLastSession", checked)
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "greeterRememberLastUser"
|
|
tags: ["greeter", "user", "remember", "login", "username"]
|
|
text: I18n.tr("Remember last user")
|
|
description: I18n.tr("Pre-fill the last successful username on the greeter")
|
|
checked: SettingsData.greeterRememberLastUser
|
|
onToggled: checked => SettingsData.set("greeterRememberLastUser", checked)
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "greeterAutoLogin"
|
|
tags: ["greeter", "autologin", "login", "startup", "password"]
|
|
text: I18n.tr("Auto-login on startup")
|
|
description: SettingsData.greeterRememberLastUser && SettingsData.greeterRememberLastSession ? I18n.tr("Skip the greeter password after boot until you sign out. Lock screen unlock is unchanged. Takes effect on the next reboot after sync.") : I18n.tr("Requires remembering the last user and session. Enable those options first.")
|
|
checked: SettingsData.greeterAutoLogin
|
|
enabled: SettingsData.greeterRememberLastUser && SettingsData.greeterRememberLastSession
|
|
onToggled: checked => SettingsData.set("greeterAutoLogin", checked)
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
iconName: "extension"
|
|
title: I18n.tr("Dependencies & documentation")
|
|
settingKey: "greeterDeps"
|
|
|
|
StyledText {
|
|
text: I18n.tr("Requires greetd, dms-greeter, and your user in the greeter group (plus fprintd/pam_fprintd for fingerprint, pam_u2f for security keys). Auth changes apply automatically and may open a terminal for sudo.")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceVariantText
|
|
width: parent.width
|
|
wrapMode: Text.Wrap
|
|
horizontalAlignment: Text.AlignLeft
|
|
}
|
|
|
|
StyledText {
|
|
text: I18n.tr("Installation and PAM setup: see the ") + "<a href=\"https://danklinux.com/docs/dankgreeter/installation\" style=\"text-decoration:none; color:" + Theme.primary + ";\">DankGreeter docs</a> " + I18n.tr("or run ") + "'dms greeter install'."
|
|
textFormat: Text.RichText
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceVariantText
|
|
linkColor: Theme.primary
|
|
width: parent.width
|
|
wrapMode: Text.Wrap
|
|
horizontalAlignment: Text.AlignLeft
|
|
onLinkActivated: url => Qt.openUrlExternally(url)
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
acceptedButtons: Qt.NoButton
|
|
propagateComposedEvents: true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: syncPendingPill
|
|
|
|
readonly property bool shown: SettingsData.greeterSyncPending && root.greeterInstalled
|
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.bottom: parent.bottom
|
|
anchors.bottomMargin: shown ? Theme.spacingL : Theme.spacingXS
|
|
width: pillRow.implicitWidth + Theme.spacingL * 2
|
|
height: 44
|
|
radius: height / 2
|
|
color: Theme.primary
|
|
opacity: shown ? 1 : 0
|
|
visible: opacity > 0
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Theme.shortDuration
|
|
easing.type: Theme.standardEasing
|
|
}
|
|
}
|
|
|
|
Behavior on anchors.bottomMargin {
|
|
NumberAnimation {
|
|
duration: Theme.shortDuration
|
|
easing.type: Theme.standardEasing
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
acceptedButtons: Qt.AllButtons
|
|
cursorShape: !root.greeterSyncRunning && !root.greeterInstallActionRunning ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
onClicked: mouse => {
|
|
if (mouse.button === Qt.LeftButton && !root.greeterSyncRunning && !root.greeterInstallActionRunning)
|
|
root.runGreeterSync();
|
|
}
|
|
}
|
|
|
|
Row {
|
|
id: pillRow
|
|
anchors.centerIn: parent
|
|
spacing: Theme.spacingS
|
|
|
|
DankIcon {
|
|
id: syncPillIcon
|
|
name: "sync"
|
|
size: Theme.iconSize - 4
|
|
color: Theme.primaryText
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
RotationAnimation on rotation {
|
|
running: root.greeterSyncRunning && syncPendingPill.shown
|
|
from: 0
|
|
to: 360
|
|
duration: 1000
|
|
loops: Animation.Infinite
|
|
onRunningChanged: {
|
|
if (!running)
|
|
syncPillIcon.rotation = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
text: root.greeterSyncRunning ? I18n.tr("Syncing...") : I18n.tr("Sync to apply")
|
|
color: Theme.primaryText
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
DankActionButton {
|
|
iconName: "close"
|
|
iconSize: Theme.iconSize - 6
|
|
iconColor: Theme.primaryText
|
|
buttonSize: 28
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
onClicked: SettingsData.revertGreeterSyncPending()
|
|
}
|
|
}
|
|
}
|
|
}
|