Files
DankMaterialShell/quickshell/Modules/Settings/GreeterTab.qml
T
Zer0TraceXandclaude[bot] 87a4da9e13 feat(freebsd): Add initial native FreeBSD support (#3559)
* feat(freebsd): add comprehensive native FreeBSD support

* Update BatteryService.qml

* Add battery probe completion and fallback updates

* Add isVideoSource function and update PwObjectTracker filter

* Add isPipewireVideoSource function to AudioService

Added a function to check if a Pipewire node is a video source.

* Refactor video source check to use AudioService

* Remove unnecessary call in BatteryService.qml

* Add call to updateFreeBsdBatteryFallback on completion

* Simplify AudioService by eliminating redundant bindings

Removed unnecessary bindings in the AudioService loader to streamline the loading process.

* Improve error handling in CheckUpdates function

Handle specific exit error for package upgrade check.

* Enhance FreeBSD network detection logic

* Implement FreeBSD Wi-Fi hardware discovery in Initialize

Added FreeBSD Wi-Fi hardware discovery and updated initialization logic to handle cases where no wpa_supplicant control socket is found.

* Implement Ethernet state monitoring in StartMonitoring

Added monitoring for Ethernet state updates in StartMonitoring function.

* Add Ethernet management functions to backend

* Remove unimplemented Ethernet control methods

Removed unimplemented Ethernet control methods from WpaSupplicantBackend.

* Implement SetWiFiEnabled method for WiFi control

Add error handling and state management for WiFi control

* Update core/internal/server/network/backend_wpa_ethernet.go

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* Implement Ethernet monitoring in WpaSupplicantBackend

* Refactor startEthernetMonitor and add debounce logic

Refactor the startEthernetMonitor function to improve readability and maintainability. Introduce a debounce mechanism for processing relevant FreeBSD route events.

* Reorder import statements in backend_pkg.go

* Remove unnecessary blank line in backend_wpa_ethernet.go

* Implement FreeBSD audio polling based on UI visibility

Introduce a polling mechanism for FreeBSD audio device updates based on visibility of UI elements.

* Add readonly freeBsdPollingRequested property and optimize polling

Introduce a readonly property to determine if FreeBSD polling is requested based on various UI conditions. Update command execution to prioritize polling efficiency.

* Simplify freeBsdPollingRequested logic

Removed unnecessary conditions from freeBsdPollingRequested property.

* Implement reference counting for battery consumers

Added reference counting to manage battery information consumers and updated related functions for better battery state handling.

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-10-05 22:03:32 -04:00

554 lines
22 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.DCommon.Widgets
import qs.Modules.Settings.Widgets
Item {
id: root
property var parentModal: null
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
ConfirmModal {
id: greeterActionConfirm
}
ConfirmModal {
id: unlinkConfirm
}
ConfirmModal {
id: unlinkFinalConfirm
}
function promptUnlink() {
unlinkConfirm.showWithOptions({
"title": I18n.tr("Unlink login screen", "greeter advanced action"),
"message": I18n.tr("The login screen stops following your DMS theme, wallpaper, widgets and settings. greetd, authentication and other users are not affected.", "greeter unlink confirmation"),
"confirmText": I18n.tr("Unlink", "verb, greeter unlink button"),
"cancelText": I18n.tr("Cancel"),
"confirmColor": Theme.error,
"onConfirm": () => Qt.callLater(root.promptUnlinkFinal),
"onCancel": () => {}
});
}
function promptUnlinkFinal() {
unlinkFinalConfirm.showWithOptions({
"title": I18n.tr("Unlink now?", "greeter unlink second confirmation"),
"message": I18n.tr("Your greeter slot and any cache links to your home are removed. Administrator access may be required. Set up links them again.", "greeter unlink second confirmation"),
"confirmText": I18n.tr("Unlink", "verb, greeter unlink button"),
"cancelText": I18n.tr("Cancel"),
"confirmColor": Theme.error,
"onConfirm": () => GreeterService.unlink(),
"onCancel": () => {}
});
}
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
readonly property bool greeterEnabled: GreeterService.enabled
property bool embeddedGreeterConfigured: false
readonly property bool embeddedGreeterOnly: embeddedGreeterConfigured && !greeterBinaryExists
readonly property bool busy: greeterSyncRunning || greeterInstallActionRunning
readonly property string greeterState: {
if (!greeterBinaryExists)
return embeddedGreeterOnly ? "embedded" : "missing";
if (!greeterEnabled)
return "inactive";
switch (GreeterService.slotState) {
case "live":
return "linked";
case "snapshot":
return "snapshot";
}
return "unlinked";
}
readonly property bool greeterStateWarn: greeterState !== "linked"
readonly property string greeterStateIcon: {
switch (greeterState) {
case "linked":
return "link";
case "snapshot":
return "history";
case "inactive":
return "login";
case "unlinked":
return "link_off";
}
return "info";
}
readonly property string greeterStateTitle: {
switch (greeterState) {
case "linked":
return I18n.tr("Linked", "greeter status, login screen follows DMS settings live");
case "snapshot":
return I18n.tr("Linked as a snapshot", "greeter status, login screen reads a copy of DMS settings");
case "inactive":
return I18n.tr("Not active", "greeter status, greetd does not run the DMS greeter");
case "unlinked":
return I18n.tr("Not linked", "greeter status, no greeter slot for this user");
case "embedded":
return I18n.tr("Bundled greeter active", "greeter status");
}
return I18n.tr("Not installed", "greeter status");
}
readonly property string greeterStateSubtitle: {
switch (greeterState) {
case "linked":
return I18n.tr("The login screen follows your theme, wallpaper, widgets and behavior live. Only authentication changes need applying.", "greeter status");
case "snapshot":
return I18n.tr("This system cannot link settings live. Sync again after changing them.", "greeter status");
case "inactive":
return I18n.tr("greetd is not using the DMS greeter yet.", "greeter status");
case "unlinked":
return GreeterService.profileSyncSufficient ? I18n.tr("Link your DMS settings so the login screen follows them.", "greeter status") : I18n.tr("Link your DMS settings so the login screen follows them. Administrator access is required.", "greeter status");
case "embedded":
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");
}
return I18n.tr("dms-greeter is not installed. Install the dms-greeter package to manage the greeter.", "greeter status placeholder");
}
readonly property string greeterStateAction: {
switch (greeterState) {
case "inactive":
return I18n.tr("Activate");
case "unlinked":
return I18n.tr("Set up", "greeter setup button, links DMS settings to the login screen");
case "snapshot":
return I18n.tr("Sync", "verb, button that copies settings to the login greeter");
}
return "";
}
function runStateAction() {
if (greeterState === "inactive") {
promptGreeterActionConfirm();
return;
}
GreeterService.link();
}
readonly property string greeterStatusOutput: greeterStatusRunning ? I18n.tr("Checking...", "greeter status loading") : greeterStatusText
onGreeterSyncStatusChanged: greeterStatusText = greeterSyncStatus
function checkGreeterInstallState() {
GreeterService.refresh();
embeddedGreeterCheckProcess.running = true;
}
function runGreeterStatus() {
greeterStatusText = "";
greeterStatusStdout = "";
greeterStatusStderr = "";
greeterStatusRunning = true;
greeterStatusProcess.running = true;
}
function runGreeterInstallAction() {
greeterStatusText = I18n.tr("Opening terminal: ") + I18n.tr("Activate") + "...";
greeterInstallActionRunning = true;
greeterInstallActionProcess.running = true;
}
function promptGreeterActionConfirm() {
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: ["sh", "-c", "if [ \"$(uname -s)\" = FreeBSD ]; then service -e 2>/dev/null | grep -Eq \"/greetd$\" && echo enabled || echo disabled; else systemctl is-enabled greetd 2>/dev/null; fi"]
running: false
}
function showWidgetBrowser() {
greeterWidgetBrowserLoader.active = true;
greeterWidgetBrowserLoader.item?.show();
}
LazyLoader {
id: greeterWidgetBrowserLoader
active: false
DesktopWidgetBrowser {
parentModal: root.parentModal
listKey: "greeterWidgetInstances"
title: I18n.tr("Add widget")
onWidgetAdded: ToastService.showInfo(I18n.tr("Widget added"))
}
}
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: ["dms-greeter", "enable", "--terminal"]
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"
tags: ["greeter", "login", "sync", "status", "link"]
SettingsRow {
iconName: root.greeterStateIcon
iconColor: root.greeterStateWarn ? Theme.warning : Theme.primary
title: root.greeterStateTitle
subtitle: root.greeterStateSubtitle
DButton {
visible: root.greeterStateAction !== ""
anchors.verticalCenter: parent.verticalCenter
text: root.greeterStateAction
busy: root.greeterSyncRunning
enabled: !root.busy
onClicked: root.runStateAction()
}
}
SettingsNoteRow {
visible: root.greeterStatusOutput !== ""
noteIconName: ""
monospace: true
maxHeight: SettingsMetrics.noteMaxHeight
text: root.greeterStatusOutput
tint: root.greeterStatusRunning ? Theme.surfaceVariantText : Theme.surfaceText
tintBackground: SettingsMetrics.controlColor
}
}
SettingsCard {
SettingsNavRow {
settingKey: "greeterAuth"
tags: ["greeter", "login", "authentication", "pam", "fingerprint", "security", "key"]
title: I18n.tr("Authentication")
iconName: "fingerprint"
onClicked: keyboard => root.parentModal?.navigateTo("greeter_auth", keyboard)
}
}
SettingsCard {
width: parent.width
iconName: "widgets"
title: I18n.tr("Widgets")
settingKey: "greeterWidgets"
tags: ["greeter", "login", "widgets", "clock", "session", "layout"]
SettingsToggleRow {
settingKey: "greeterFollowLockScreen"
tags: ["greeter", "login", "lock", "widgets", "layout", "follow"]
text: I18n.tr("Follow lock screen", "greeter widgets toggle")
description: I18n.tr("The login screen shows the lock screen widgets it supports and its own session picker. Turn off to arrange it separately.", "greeter widgets toggle")
checked: SettingsData.greeterFollowLockScreen
onToggled: checked => SettingsData.setGreeterFollowLockScreen(checked)
}
SettingsReorderList {
id: greeterWidgetList
model: SettingsData.greeterWidgetInstances || []
delegate: DesktopWidgetInstanceCard {
required property var modelData
reorderList: greeterWidgetList
reorderEnabled: false
instanceData: modelData
fixed: SettingsData.greeterFollowLockScreen || modelData.widgetType === "lockAuth" || modelData.widgetType === "greeterSession"
onConfigureRequested: {
SettingsUiState.selectedDesktopWidgetId = instanceId;
SettingsUiState.selectedWidgetTitle = widgetName;
root.parentModal?.navigateTo("desktop_widget");
}
onDuplicateRequested: SettingsData.duplicateDesktopWidgetInstance(instanceId)
onDeleteRequested: {
SettingsData.removeDesktopWidgetInstance(instanceId);
ToastService.showInfo(I18n.tr("Widget removed"));
}
}
}
SettingsRow {
iconName: "restart_alt"
title: I18n.tr("Reset to default")
visible: !SettingsData.greeterFollowLockScreen
clickable: true
onClicked: SettingsData.resetGreeterWidgets()
}
}
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: "wallpaper"
onClicked: keyboard => root.parentModal?.navigateTo("personalization", keyboard)
}
SettingsNavRow {
title: I18n.tr("Fonts & motion")
iconName: "text_fields"
onClicked: keyboard => root.parentModal?.navigateTo("typography", keyboard)
}
SettingsNavRow {
title: I18n.tr("Lock screen")
iconName: "lock"
onClicked: keyboard => root.parentModal?.navigateTo("lock_screen", keyboard)
}
}
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: "tune"
title: I18n.tr("Advanced")
settingKey: "greeterAdvanced"
tags: ["greeter", "login", "sync", "status", "repair"]
collapsible: true
expanded: false
SettingsRow {
iconName: "sync"
title: I18n.tr("Run full sync", "greeter advanced action")
subtitle: I18n.tr("Re-applies the greetd command, permissions and PAM authentication. Needs administrator access.", "greeter advanced action")
clickable: true
enabled: root.greeterBinaryExists && !root.busy
onClicked: GreeterService.sync()
}
SettingsRow {
iconName: "fact_check"
title: I18n.tr("Check status", "greeter settings button, runs dms-greeter status")
clickable: true
enabled: root.greeterBinaryExists && !root.greeterStatusRunning
onClicked: root.runGreeterStatus()
}
SettingsRow {
iconName: "link_off"
iconColor: Theme.error
title: I18n.tr("Unlink login screen", "greeter advanced action")
subtitle: I18n.tr("Stops syncing your DMS settings to the greeter. Set up links them again.", "greeter advanced action")
visible: GreeterService.slotState !== ""
clickable: true
enabled: root.greeterBinaryExists && !root.busy
onClicked: root.promptUnlink()
}
}
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=\"" + Site.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
}
}
}
}
SettingsFabBar {
id: greeterFabs
readonly property bool syncPending: SessionData.greeterSyncPending && GreeterService.binaryExists
DFab {
visible: greeterFabs.syncPending
text: I18n.tr("Revert")
iconName: "undo"
colorRole: "secondaryContainer"
enabled: !GreeterService.syncing
onClicked: SettingsData.revertGreeterSyncPending()
}
DFab {
visible: greeterFabs.syncPending
text: GreeterService.syncing ? I18n.tr("Syncing...", "greeter settings status while sync is running") : I18n.tr("Apply changes")
iconName: "check"
colorRole: "primary"
busy: GreeterService.syncing
enabled: !root.busy
onClicked: GreeterService.sync()
}
DFab {
visible: !greeterFabs.syncPending
text: I18n.tr("Edit widgets")
iconName: "edit"
colorRole: SettingsData.greeterFollowLockScreen ? "primary" : "secondaryContainer"
onClicked: SessionService.greeterEditorRequested()
}
DFab {
visible: !greeterFabs.syncPending && !SettingsData.greeterFollowLockScreen
text: I18n.tr("Add widget")
iconName: "add"
onClicked: root.showWidgetBrowser()
}
}
}
}