Files
DankMaterialShell/quickshell/Modules/Settings/GreeterTab.qml
T
2026-09-26 16:25:34 -04:00

362 lines
14 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
property var parentModal: null
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
ConfirmModal {
id: greeterActionConfirm
}
property string greeterStatusText: ""
property bool greeterStatusRunning: false
readonly property bool greeterSyncRunning: GreeterService.syncing
readonly property string greeterSyncStatus: GreeterService.syncStatus
property bool greeterInstallActionRunning: false
property string greeterStatusStdout: ""
property string greeterStatusStderr: ""
readonly property bool greeterBinaryExists: GreeterService.binaryExists
property bool greeterEnabled: false
property bool embeddedGreeterConfigured: false
readonly property bool embeddedGreeterOnly: embeddedGreeterConfigured && !greeterBinaryExists
readonly property string greeterAction: greeterBinaryExists && !greeterEnabled ? "activate" : ""
readonly property bool greeterActionAvailable: greeterAction !== ""
readonly property string greeterActionLabel: greeterAction === "activate" ? I18n.tr("Activate") : ""
readonly property string greeterActionIcon: greeterAction === "activate" ? "login" : ""
readonly property var greeterActionCommand: greeterAction === "activate" ? ["dms-greeter", "enable", "--terminal"] : []
readonly property string greeterStatusOutput: {
if (greeterStatusRunning)
return I18n.tr("Checking...", "greeter status loading");
if (greeterStatusText !== "")
return greeterStatusText;
if (embeddedGreeterOnly)
return I18n.tr("The greeter bundled with DMS is active (archinstall setup). It keeps working as is, but syncing theme and settings needs the standalone greeter. Install greetd-dms-greeter-bin from the AUR, then run Sync to migrate the login screen.", "embedded greeter status");
if (!greeterBinaryExists && greeterEnabled)
return I18n.tr("dms-greeter is not installed. Install the dms-greeter package to manage the greeter.", "greeter status placeholder");
return "";
}
onGreeterSyncStatusChanged: greeterStatusText = greeterSyncStatus
function checkGreeterInstallState() {
greetdEnabledCheckProcess.running = true;
GreeterService.refresh();
embeddedGreeterCheckProcess.running = true;
}
function runGreeterStatus() {
greeterStatusText = "";
greeterStatusStdout = "";
greeterStatusStderr = "";
greeterStatusRunning = true;
greeterStatusProcess.running = true;
}
function runGreeterInstallAction() {
greeterStatusText = I18n.tr("Opening terminal: ") + root.greeterActionLabel + "...";
greeterInstallActionRunning = true;
greeterInstallActionProcess.running = true;
}
function promptGreeterActionConfirm() {
if (!root.greeterActionAvailable)
return;
greeterActionConfirm.showWithOptions({
"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", "verb, enable the greeter, also activate a wired network profile"),
"cancelText": I18n.tr("Cancel"),
"confirmColor": Theme.primary,
"onConfirm": () => root.runGreeterInstallAction(),
"onCancel": () => {}
});
}
Component.onCompleted: {
Qt.callLater(checkGreeterInstallState);
}
Process {
id: greetdEnabledCheckProcess
command: ["systemctl", "is-enabled", "greetd"]
running: false
stdout: StdioCollector {
onStreamFinished: root.greeterEnabled = text.trim() === "enabled"
}
}
Process {
id: embeddedGreeterCheckProcess
// archinstall's DMS profile points greetd at this launcher inside the packaged DMS tree
command: ["sh", "-c", "grep -q 'Modules/Greetd/assets/dms-greeter' /etc/greetd/config.toml 2>/dev/null"]
running: false
onExited: exitCode => {
root.embeddedGreeterConfigured = (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 the dms-greeter package is installed.", "greeter status error") + " (exit " + exitCode + ")";
if (out !== "")
failure = failure + "\n\n" + out;
if (err !== "")
failure = failure + "\n\nstderr:\n" + err;
root.greeterStatusText = failure;
}
}
Connections {
target: GreeterService
function onSyncFinished() {
root.checkGreeterInstallState();
}
}
Process {
id: greeterInstallActionProcess
command: root.greeterActionCommand
running: false
onExited: exitCode => {
root.greeterInstallActionRunning = false;
root.checkGreeterInstallState();
if (exitCode !== 0) {
root.greeterStatusText = I18n.tr("Action failed or terminal was closed.") + " (exit " + exitCode + ")";
return;
}
root.greeterStatusText = I18n.tr("Greeter activated. greetd is now enabled.");
}
}
SettingsPage {
id: mainColumn
SettingsCard {
width: parent.width
iconName: "info"
title: I18n.tr("Status")
settingKey: "greeterStatus"
SettingsRow {
subtitle: I18n.tr("Sync applies your theme and settings to the login screen. Shared users should run dms-greeter sync --profile instead of a primary user sync.")
body: Flow {
width: parent.width
spacing: Theme.spacingS
layoutDirection: Qt.RightToLeft
DankButton {
text: I18n.tr("Sync", "verb, button that copies settings to the login greeter")
iconName: "sync"
busy: root.greeterSyncRunning
enabled: root.greeterBinaryExists && !root.greeterSyncRunning && !root.greeterInstallActionRunning
onClicked: GreeterService.sync()
}
DankButton {
text: I18n.tr("Check status", "greeter settings button, runs dms-greeter status")
iconName: "fact_check"
backgroundColor: Theme.secondaryContainer
textColor: Theme.onSecondaryContainer
busy: root.greeterStatusRunning
enabled: !root.greeterStatusRunning
onClicked: root.runGreeterStatus()
}
DankButton {
visible: root.greeterActionAvailable
text: root.greeterActionLabel
iconName: root.greeterActionIcon
backgroundColor: Theme.secondaryContainer
textColor: Theme.onSecondaryContainer
enabled: !root.greeterInstallActionRunning && !root.greeterSyncRunning
onClicked: root.promptGreeterActionConfirm()
}
}
}
SettingsRow {
visible: root.greeterStatusOutput !== ""
body: Rectangle {
width: parent.width
height: statusTextArea.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.floatingWindowFieldColor
border.color: Theme.outlineMedium
border.width: Theme.layerOutlineWidth
StyledText {
id: statusTextArea
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacingM
text: root.greeterStatusOutput
font.pixelSize: Theme.fontSizeSmall
isMonospace: true
color: root.greeterStatusRunning ? Theme.surfaceVariantText : Theme.surfaceText
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignLeft
}
}
}
}
SettingsCard {
SettingsNavRow {
settingKey: "greeterAuth"
tags: ["greeter", "login", "authentication", "pam", "fingerprint", "security", "key"]
title: I18n.tr("Authentication")
iconName: "fingerprint"
onClicked: root.parentModal?.navigateTo("greeter_auth")
}
}
SettingsCard {
title: I18n.tr("Appearance")
settingKey: "greeterAppearance"
tags: ["greeter", "login", "sync", "theme", "wallpaper"]
SettingsRow {
subtitle: I18n.tr("Uses your wallpaper, fonts and lock screen settings.", "login screen appearance")
}
SettingsNavRow {
title: I18n.tr("Wallpaper & colors")
iconName: "palette"
onClicked: root.parentModal?.navigateTo("personalization")
}
SettingsNavRow {
title: I18n.tr("Fonts & motion")
iconName: "text_fields"
onClicked: root.parentModal?.navigateTo("typography")
}
SettingsNavRow {
title: I18n.tr("Lock screen")
iconName: "lock"
onClicked: root.parentModal?.navigateTo("lock_screen")
}
}
SettingsCard {
width: parent.width
iconName: "history"
title: I18n.tr("Behavior")
settingKey: "greeterBehavior"
SettingsToggleRow {
settingKey: "greeterRememberLastSession"
tags: ["greeter", "session", "remember", "login"]
text: I18n.tr("Remember last session")
checked: SettingsData.greeterRememberLastSession
onToggled: checked => SettingsData.set("greeterRememberLastSession", checked)
}
SettingsToggleRow {
settingKey: "greeterRememberLastUser"
tags: ["greeter", "user", "remember", "login", "username"]
text: I18n.tr("Remember last user")
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"
SettingsRow {
body: 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).")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignLeft
}
}
SettingsRow {
body: StyledText {
text: I18n.tr("Installation and PAM setup are documented in the ") + "<a href=\"https://danklinux.com/docs/dankgreeter/installation\" style=\"text-decoration:none; color:" + Theme.primary + ";\">DankGreeter docs.</a> "
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
}
}
}
}
GreeterSyncFabBar {
blocked: root.greeterInstallActionRunning
}
}
}